Skip to content

Inference with SEIR model and NPE #1369

Description

@francescopinotti92

Description

I'm testing the ability of neural SBI methods to fit epidemic data in a simple setting where an epidemic is growing exponentially starting from a single infected case. I am using a SEIR model with transmission rate $\beta$, recovery rate $\mu$ and rate of infectiousness onset $\sigma$. "True" incidence in week $t$ is calculated from simulations as: $J_t = \sigma \int_{7t}^{7t+7}E(u)du$. Observed cases in the same week are assumed to be Poisson-distributed with mean $\rho \cdot J_t$, where $\rho$ is an under-reporting factor. The last parameter, $T_0$, denotes the time elapsed between the introduction of the first case into the population and the beginning of the observation window; this means I do not observe any data between times $-T_0$ and $0$.

In my experiments, case data cover 23 weeks. Also, I assume that $\mu$, $\sigma$, $\rho$ and population size $N$ are known, which leaves only two parameters to be inferred, $\beta$ and $T_0$. The transmission rate is actually calculated as $\beta = R_0 \mu$, where $R_0$ is my parameter of interest (instead of $\beta$). I chose uniform priors for both $R_0$ and $T_0$. More precisely, $R_0 \sim \text{Uniform}(1,5)$ and $T_0 \sim \text{Uniform}(0,50)$.

In an even simpler scenario, I further assume that $T_0$ is known so that $R_0$ is the only unknown parameter.

I've been playing around with sbi for a few weeks now but unfortunately I can't seem to get good posteriors in this example. More precisely, posteriors obtained with, say, NPE, are typically wider than the exact posteriors (which I obtained via standard MCMC methods and have the correct coverage). The results are actually satisfactory for relatively large $R_0$ values, e.g. 3, but they degrade considerably at lower values.

I believe that this is quite a simple problem that could be tackled by NPE, hence I wonder if I'm missing any fundamental concept. Any advice would be really welcome. Thanks for making this impressive toolkit available!

Code

import numpy as np
import torch
import sbi
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from sbi.inference import NPE
from sbi.utils import BoxUniform
from sbi.utils.user_input_checks import (
    check_sbi_inputs,
    process_prior,
    process_simulator,
)
from sbi.neural_nets import posterior_nn

#=== actual system of ODEs
def SEIR_ODE( y, t, N, beta, sigma, mu ):

    # C is tracking cumulative incidence 
    S, E, I, R, C = y

    dS_dt  = -beta * S * I / N
    dE_dt  =  beta * S * I / N - sigma * E
    dI_dt  =  sigma * E - mu * I
    dR_dt  =  mu * I
    dC_dt =  sigma * E

    return( [ dS_dt, dE_dt, dI_dt, dR_dt, dC_dt ] )

#=== simulator with 2 free parameters, R_0 and T_0
def SBI_simulator( theta ):

  #== fixed parameters
  N = 10628972.
  sigma = 1./11.4
  mu = 1./7.0
  rho = 0.5

  R0, T0 = theta
  beta = R0 * mu

  ts = [ 0., T0 ] + [ T0 + 7. * i for i in range( 1, 24 ) ]
  sol = odeint( SEIR_ODE, [ N - 1.0, 1.0, 0.0, 0.0, 0.0 ], ts, args = ( N, beta, sigma, mu ) )

  C   = sol[:,-1]
  inc = np.diff( C )
  inc_obs  = np.random.poisson( rho * inc )

  return inc_obs[1:]

#=== simulator with R_0 as sole free parameter
def SBI_simulator_R0( theta ):

    R0 = theta[0]
    global T0 # T0 is set outside the simulator

    return SBI_simulator( [ R0, T0 ] )


#=== simulation and fitting

T0 = 30. # set T_0

prior = BoxUniform( low  = torch.tensor( [ 1.] ),
                    high = torch.tensor( [ 5.] ) )

prior, num_parameters, prior_returns_numpy = process_prior( prior )
simulator = process_simulator( SBI_simulator_R0, prior, prior_returns_numpy )

theta, x = simulate_for_sbi( simulator, prior, num_simulations = 200000, num_workers = 4 )

neural_posterior = posterior_nn( model="maf" ) # options for density estimation
inferer_NPE = NPE( prior = prior, density_estimator = neural_posterior ) # inferer object
density_estimator_NPE = inferer_NPE.append_simulations( theta, x ).train( training_batch_size = 512, stop_after_epochs = 50  ) # density estimator
posterior_NPE = inferer_NPE.build_posterior( density_estimator_NPE ) 

# simulate some observed data
data_true = SBI_simulator_R0( torch.tensor([1.6]) ) 
samples_NPE = posterior_NPE.set_default_x( data_true ).sample( ( 5000, ) )

When setting $R_0=1.6$ I get plots like the following:

Image

The true posterior, not shown here, is much tighter around the ground-truth value. I also tried using NSF instead of MAF but results do not improve, unfortunately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions