def test_Inference(self): # ===== Distributions ===== # dist = Normal(0., 1.) mvn = Independent(Normal(torch.zeros(2), torch.ones(2)), 1) # ===== Define model ===== # linear = AffineProcess((f, g), (1., 0.25), dist, dist) model = LinearGaussianObservations(linear, scale=0.1) mv_linear = AffineProcess((fmvn, gmvn), (0.5, 0.25), mvn, mvn) mvnmodel = LinearGaussianObservations(mv_linear, torch.eye(2), scale=0.1) # ===== Test for multiple models ===== # priors = Exponential(1.), LogNormal(0., 1.) hidden1d = AffineProcess((f, g), priors, dist, dist) oned = LinearGaussianObservations(hidden1d, 1., scale=0.1) hidden2d = AffineProcess((fmvn, gmvn), priors, mvn, mvn) twod = LinearGaussianObservations(hidden2d, torch.eye(2), scale=0.1 * torch.ones(2)) particles = 1000 # ====== Run inference ===== # for trumod, model in [(model, oned), (mvnmodel, twod)]: x, y = trumod.sample_path(1000) algs = [ (NESS, {'particles': particles, 'filter_': APF(model.copy(), 200)}), (NESS, {'particles': particles, 'filter_': UKF(model.copy())}), (SMC2, {'particles': particles, 'filter_': APF(model.copy(), 200)}), (SMC2FW, {'particles': particles, 'filter_': APF(model.copy(), 200)}), (NESSMC2, {'particles': particles, 'filter_': APF(model.copy(), 200)}) ] for alg, props in algs: alg = alg(**props).initialize() alg = alg.fit(y) w = normalize(alg._w_rec if hasattr(alg, '_w_rec') else torch.ones(particles)) tru_params = trumod.hidden.theta._cont + trumod.observable.theta._cont inf_params = alg.filter.ssm.hidden.theta._cont + alg.filter.ssm.observable.theta._cont for trup, p in zip(tru_params, inf_params): if not p.trainable: continue kde = p.get_kde(weights=w) transed = p.bijection.inv(trup) densval = kde.logpdf(transed.numpy().reshape(-1, 1)) priorval = p.distr.log_prob(trup) assert (densval > priorval.numpy()).all()
def test_BatchedParameter(self): norm = DistributionWrapper(Normal, loc=0.0, scale=1.0) shape = 1000, 100 a = torch.ones((shape[0], 1)) init = DistributionWrapper(Normal, loc=a, scale=1.0) linear = AffineProcess((f, g), (a, 1.0), init, norm) # ===== Initialize ===== # x = linear.i_sample(shape) self.assert_timeseries_sampling(100, linear, x, shape)
def test_UnscentedTransform2D(self): # ===== 2D model ===== # mat = torch.eye(2) scale = torch.diag(mat) norm = DistributionWrapper(Normal, loc=0.0, scale=1.0) mvn = DistributionWrapper(MultivariateNormal, loc=torch.zeros(2), covariance_matrix=torch.eye(2)) mvnlinear = AffineProcess((fmvn, g), (mat, scale), mvn, mvn) mvnoblinear = AffineObservations((fomvn, gomvn), (1.0, ), norm) mvnmodel = StateSpaceModel(mvnlinear, mvnoblinear) # ===== Perform unscented transform ===== # uft = UnscentedFilterTransform(mvnmodel) res = uft.initialize(3000) p = uft.predict(res) c = uft.correct(torch.tensor(0.0), p, res) assert isinstance( c.x_dist(), MultivariateNormal) and c.x_dist().mean.shape == torch.Size( [3000, 2])
def make_model(prob, dim=1): if prob: parameters = Prior(Exponential, rate=1.0), Prior(LogNormal, loc=0.0, scale=1.0) else: parameters = (0.99, 0.25) if dim == 1 else (0.5, 0.25) obs_param = dict() if dim == 1: dist = DistributionWrapper(Normal, loc=0.0, scale=1.0) obs_param["a"] = 1.0 obs_param["scale"] = 0.1 func = (f, g) else: dist = DistributionWrapper(lambda **u: Independent(Normal(**u), 1), loc=torch.zeros(2), scale=torch.ones(2)) obs_param["a"] = torch.eye(2) obs_param["scale"] = 0.1 * torch.ones(2) func = (fmvn, gmvn) hidden = AffineProcess(func, parameters, dist, dist) return LinearGaussianObservations(hidden, **obs_param)
def test_LoadModule(self): def f(x_, alpha, sigma): return alpha * x_ def g(x_, alpha, sigma): return sigma norm = DistributionWrapper(Normal, loc=0.0, scale=1.0) linear = AffineProcess((f, g), (1.0, 1.0), norm, norm) model = LinearGaussianObservations(linear, 1.0, 1.0) x, y = model.sample_path(100) filt = SISR(model, 200) res = filt.longfilter(y) filt2 = SISR(model, 300) filt2.load_state_dict(filt.state_dict()) self.assertTrue(filt2.particles[0] == 200) res2 = FilterResult(filt2.initialize()) res2.load_state_dict(res.state_dict()) self.assertTrue((res2.filter_means == res.filter_means).all())
def test_MultiDimensional(self): mu = torch.zeros(2) scale = torch.ones_like(mu) shape = 1000, 100 mvn = Independent(Normal(mu, scale), 1) mvn = AffineProcess((f, g), (1., 1.), mvn, mvn) # ===== Initialize ===== # x = mvn.i_sample(shape) # ===== Propagate ===== # num = 100 samps = [x] for t in range(num): samps.append(mvn.propagate(samps[-1])) samps = torch.stack(samps) self.assertEqual(samps.size(), torch.Size([num + 1, *shape, *mu.shape])) # ===== Sample path ===== # path = mvn.sample_path(num + 1, shape) self.assertEqual(samps.shape, path.shape)
def test_LinearNoBatch(self): norm = Normal(0., 1.) linear = AffineProcess((f, g), (1., 1.), norm, norm) # ===== Initialize ===== # x = linear.i_sample() # ===== Propagate ===== # num = 100 samps = [x] for t in range(num): samps.append(linear.propagate(samps[-1])) samps = torch.stack(samps) self.assertEqual(samps.size(), torch.Size([num + 1])) # ===== Sample path ===== # path = linear.sample_path(num + 1) self.assertEqual(samps.shape, path.shape)
def test_UnscentedTransform1D(self): # ===== 1D model ===== # norm = Normal(0., 1.) linear = AffineProcess((f, g), (1., 1.), norm, norm) linearobs = AffineObservations((fo, go), (1., 1.), norm) model = StateSpaceModel(linear, linearobs) # ===== Perform unscented transform ===== # x = model.hidden.i_sample(shape=3000) ut = UnscentedTransform(model).initialize(x).construct(0.) assert isinstance(ut.x_dist, Normal)
def test_MultiDimensional(self): mu = torch.zeros(2) scale = torch.ones_like(mu) shape = 1000, 100 mvn = DistributionWrapper(lambda **u: Independent(Normal(**u), 1), loc=mu, scale=scale) mvn = AffineProcess((f, g), (1.0, 1.0), mvn, mvn) # ===== Initialize ===== # x = mvn.i_sample(shape) # ===== Propagate ===== # self.assert_timeseries_sampling(100, mvn, x, shape, (*shape, 2))
def test_BatchedParameter(self): norm = Normal(0., 1.) shape = 1000, 100 a = torch.ones((shape[0], 1)) init = Normal(a, 1.) linear = AffineProcess((f, g), (a, 1.), init, norm) # ===== Initialize ===== # x = linear.i_sample(shape) # ===== Propagate ===== # num = 100 samps = [x] for t in range(num): samps.append(linear.propagate(samps[-1])) samps = torch.stack(samps) self.assertEqual(samps.size(), torch.Size([num + 1, *shape])) # ===== Sample path ===== # path = linear.sample_path(num + 1, shape) self.assertEqual(samps.shape, path.shape)
def test_UnscentedTransform1D(self): # ===== 1D model ===== # norm = DistributionWrapper(Normal, loc=0.0, scale=1.0) linear = AffineProcess((f, g), (1.0, 1.0), norm, norm) linearobs = AffineObservations((fo, go), (1.0, 1.0), norm) model = StateSpaceModel(linear, linearobs) # ===== Perform unscented transform ===== # uft = UnscentedFilterTransform(model) res = uft.initialize(3000) p = uft.predict(res) c = uft.correct(torch.tensor(0.0), p, res) assert isinstance( c.x_dist(), Normal) and c.x_dist().mean.shape == torch.Size([3000])
def test_ParallellFiltersAndStability(self): x, y = self.model.sample_path(50) shape = 3000 linear = AffineProcess((f, g), (1., 1.), self.norm, self.norm) self.model.hidden = linear filt = SISR(self.model, 1000).set_nparallel(shape).initialize().longfilter(y) filtermeans = filt.filtermeans x = filtermeans[:, :1] mape = ((x - filtermeans[:, 1:]) / x).abs() assert mape.median(0)[0].max() < 0.05
def test_ParallelUnscented(self): x, y = self.model.sample_path(50) shape = 30 linear = AffineProcess((f, g), (1.0, 1.0), self.norm, self.norm) self.model.hidden = linear filt = SISR(self.model, 1000, proposal=prop.Unscented()).set_nparallel(shape) result = filt.longfilter(y) filtermeans = result.filter_means x = filtermeans[:, :1] mape = ((x - filtermeans[:, 1:]) / x).abs() assert mape.median(0)[0].max() < 0.05
def test_Sample(self): # ==== Hidden ==== # norm = DistributionWrapper(Normal, loc=0.0, scale=1.0) linear = AffineProcess((f, g), (1.0, 1.0), norm, norm) # ==== Observable ===== # obs = AffineObservations((fo, go), (1.0, 0.0), norm) # ===== Model ===== # mod = StateSpaceModel(linear, obs) # ===== Sample ===== # x, y = mod.sample_path(100) diff = ((x - y)**2).mean().sqrt() assert x.shape == y.shape and x.shape[0] == 100 and diff < 1e-3
def test_UnscentedTransform2D(self): # ===== 2D model ===== # mat = torch.eye(2) scale = torch.diag(mat) norm = Normal(0., 1.) mvn = MultivariateNormal(torch.zeros(2), torch.eye(2)) mvnlinear = AffineProcess((fmvn, g), (mat, scale), mvn, mvn) mvnoblinear = AffineObservations((fomvn, gomvn), (1., ), norm) mvnmodel = StateSpaceModel(mvnlinear, mvnoblinear) # ===== Perform unscented transform ===== # x = mvnmodel.hidden.i_sample(shape=3000) ut = UnscentedTransform(mvnmodel).initialize(x).construct(0.) assert isinstance(ut.x_dist, MultivariateNormal) and isinstance( ut.y_dist, Normal) assert isinstance(ut.x_dist_indep, Independent)
def test_UnscentedTransform2D(self): # ===== 2D model ===== # mat = torch.eye(2) scale = torch.diag(mat) norm = Normal(0., 1.) mvn = MultivariateNormal(torch.zeros(2), torch.eye(2)) mvnlinear = AffineProcess((fmvn, g), (mat, scale), mvn, mvn) mvnoblinear = AffineObservations((fomvn, gomvn), (1., ), norm) mvnmodel = StateSpaceModel(mvnlinear, mvnoblinear) # ===== Perform unscented transform ===== # uft = UnscentedFilterTransform(mvnmodel) res = uft.initialize(3000) p = uft.predict(res) c = uft.correct(0., p) assert isinstance( c.x_dist(), MultivariateNormal) and c.x_dist().mean.shape == torch.Size( [3000, 2])
def test_StateDict(self): # ===== Define model ===== # norm = Normal(0., 1.) linear = AffineProcess((f, g), (1., 1.), norm, norm) linearobs = AffineObservations((fo, go), (1., 1.), norm) model = StateSpaceModel(linear, linearobs) # ===== Define filter ===== # filt = SISR(model, 100).initialize() # ===== Get statedict ===== # sd = filt.state_dict() # ===== Verify that we don't save multiple instances ===== # assert '_model' in sd and '_model' not in sd['_proposal'] newfilt = SISR(model, 1000).load_state_dict(sd) assert newfilt._w_old is not None and newfilt.ssm is newfilt._proposal._model # ===== Test same with UKF and verify that we save UT ===== # ukf = UKF(model).initialize() sd = ukf.state_dict() assert '_model' in sd and '_model' not in sd['_ut']
class Tests(unittest.TestCase): # ===== Simple 1D model ===== # norm = Normal(0., 1.) linear = AffineProcess((f, g), (1., 1.), norm, norm) model = LinearGaussianObservations(linear, 1., 1.) # ===== Simple 2D model ===== # mvn = Independent(Normal(torch.zeros(2), torch.ones(2)), 1) mvn = AffineProcess((fmvn, gmvn), (0.5, 1.), mvn, mvn) a = torch.Tensor([1., 2.]) mvnmodel = LinearGaussianObservations(mvn, a, 1.) def test_InitializeFilter(self): filt = SISR(self.model, 1000).initialize() assert filt._x_cur.shape == torch.Size([1000]) def test_Filters(self): for model in [self.model, self.mvnmodel]: x, y = model.sample_path(500) for filter_, props in [ (SISR, {'particles': 500}), (APF, {'particles': 500}), (UKF, {}), (SISR, {'particles': 500, 'proposal': Linearized(alpha=None)}), (APF, {'particles': 500, 'proposal': Linearized()}), (SISR, {'particles': 500, 'proposal': Unscented()}) ]: filt = filter_(model, **props).initialize() filt = filt.longfilter(y) assert len(filt.s_mx) > 0 filtmeans = filt.filtermeans.numpy() # ===== Run Kalman ===== # if model is self.model: kf = pykalman.KalmanFilter(transition_matrices=1., observation_matrices=1.) else: kf = pykalman.KalmanFilter(transition_matrices=[[0.5, 1 / 3], [0, 1.]], observation_matrices=[1, 2]) filterestimates = kf.filter(y.numpy()) if filtmeans.ndim < 2: filtmeans = filtmeans[:, None] rel_error = np.median(np.abs((filtmeans - filterestimates[0]) / filterestimates[0])) ll = kf.loglikelihood(y.numpy()) rel_ll_error = np.abs((ll - np.array(filt.s_ll).sum()) / ll) assert rel_error < 0.05 and rel_ll_error < 0.05 def test_ParallellFiltersAndStability(self): x, y = self.model.sample_path(50) shape = 3000 linear = AffineProcess((f, g), (1., 1.), self.norm, self.norm) self.model.hidden = linear filt = SISR(self.model, 1000).set_nparallel(shape).initialize().longfilter(y) filtermeans = filt.filtermeans x = filtermeans[:, :1] mape = ((x - filtermeans[:, 1:]) / x).abs() assert mape.median(0)[0].max() < 0.05 def test_ParallelUnscented(self): x, y = self.model.sample_path(50) shape = 30 linear = AffineProcess((f, g), (1., 1.), self.norm, self.norm) self.model.hidden = linear filt = SISR(self.model, 1000, proposal=Unscented()).set_nparallel(shape).initialize().longfilter(y) filtermeans = filt.filtermeans x = filtermeans[:, :1] mape = ((x - filtermeans[:, 1:]) / x).abs() assert mape.median(0)[0].max() < 0.05 def test_SDE(self): def f(x, a, s): return -a * x def g(x, a, s): return s em = AffineEulerMaruyama((f, g), (0.02, 0.15), Normal(0., 1.), Normal(0., 1.), dt=1e-2, num_steps=10) model = LinearGaussianObservations(em, scale=1e-3) x, y = model.sample_path(500) with self.assertRaises(NotImplementedError): SISR(model, 200) for filt in [SISR(model, 500, proposal=Bootstrap()), UKF(model)]: filt = filt.initialize().longfilter(y) means = filt.filtermeans if isinstance(filt, UKF): means = means[:, 0] self.assertLess(torch.std(x - means), 5e-2)
class Tests(unittest.TestCase): # ===== Simple 1D model ===== # norm = DistributionWrapper(Normal, loc=0.0, scale=1.0) linear = AffineProcess((f, g), (1.0, 1.0), norm, norm) model = LinearGaussianObservations(linear, 1.0, 1.0) # ===== Simple 2D model ===== # mvn = DistributionWrapper(lambda **u: Independent(Normal(**u), 1), loc=torch.zeros(2), scale=torch.ones(2)) mvn = AffineProcess((fmvn, gmvn), (0.5, 1.0), mvn, mvn) a = torch.tensor([1.0, 2.0]) mvnmodel = LinearGaussianObservations(mvn, a, 1.0) def test_InitializeFilter(self): state = SISR(self.model, 1000).initialize() assert state.x.shape == torch.Size([1000]) def test_Filters(self): for model in [self.model, self.mvnmodel]: x, y = model.sample_path(500) for filter_type, props in [ (SISR, {"particles": 500}), (APF, {"particles": 500}), (UKF, {}), (SISR, {"particles": 50, "proposal": prop.Unscented()}), ]: filt = filter_type(model, **props) result = filt.longfilter(y, record_states=True) filtmeans = result.filter_means.numpy() # ===== Run Kalman ===== # if model is self.model: kf = pykalman.KalmanFilter(transition_matrices=1.0, observation_matrices=1.0) else: kf = pykalman.KalmanFilter( transition_matrices=[[0.5, 1 / 3], [0, 1.0]], observation_matrices=[1, 2] ) f_mean, _ = kf.filter(y.numpy()) if model.hidden_ndim < 1 and not isinstance(filt, UKF): f_mean = f_mean[:, 0] rel_error = np.median(np.abs((filtmeans - f_mean) / f_mean)) ll = kf.loglikelihood(y.numpy()) rel_ll_error = np.abs((ll - result.loglikelihood.numpy()) / ll) assert rel_error < 0.05 and rel_ll_error < 0.05 def test_ParallellFiltersAndStability(self): x, y = self.model.sample_path(50) shape = 3000 linear = AffineProcess((f, g), (1.0, 1.0), self.norm, self.norm) self.model.hidden = linear filt = SISR(self.model, 1000).set_nparallel(shape) result = filt.longfilter(y) filtermeans = result.filter_means x = filtermeans[:, :1] mape = ((x - filtermeans[:, 1:]) / x).abs() assert mape.median(0)[0].max() < 0.05 def test_ParallelUnscented(self): x, y = self.model.sample_path(50) shape = 30 linear = AffineProcess((f, g), (1.0, 1.0), self.norm, self.norm) self.model.hidden = linear filt = SISR(self.model, 1000, proposal=prop.Unscented()).set_nparallel(shape) result = filt.longfilter(y) filtermeans = result.filter_means x = filtermeans[:, :1] mape = ((x - filtermeans[:, 1:]) / x).abs() assert mape.median(0)[0].max() < 0.05 def test_SDE(self): def f(x, a, s): return -a * x def g(x, a, s): return s dt = 1e-2 norm = DistributionWrapper(Normal, loc=0.0, scale=sqrt(dt)) em = AffineEulerMaruyama((f, g), (0.02, 0.15), norm, norm, dt=1e-2, num_steps=10) model = LinearGaussianObservations(em, scale=1e-3) x, y = model.sample_path(500) for filt in [SISR(model, 500, proposal=prop.Bootstrap()), UKF(model)]: result = filt.longfilter(y) means = result.filter_means if isinstance(filt, UKF): means = means[:, 0] self.assertLess(torch.std(x - means), 5e-2)
def build_model(): norm = DistributionWrapper(Normal, loc=0.0, scale=1.0) return AffineProcess((f, g), (1.0, 1.0), norm, norm)