def stable_model(): zero = torch.zeros(2) a = pyro.sample("a", dist.Normal(0, 1)) b = pyro.sample("b", dist.LogNormal(0, 1)) c = pyro.sample("c", dist.Stable(1.5, 0.0, b, a)) d = pyro.sample("d", dist.Stable(1.5, 0.0, b, 0.0), obs=a) e = pyro.sample("e", dist.Stable(1.5, 0.1, b, a)) f = pyro.sample("f", dist.Stable(1.5, 0.1, b, 0.0), obs=a) g = pyro.sample("g", dist.Stable(1.5, zero, b, a).to_event(1)) h = pyro.sample("h", dist.Stable(1.5, zero, b, 0).to_event(1), obs=a) i = pyro.sample( "i", dist.TransformedDistribution(dist.Stable(1.5, 0, b, a), dist.transforms.ExpTransform()), ) j = pyro.sample( "j", dist.TransformedDistribution(dist.Stable(1.5, 0, b, a), dist.transforms.ExpTransform()), obs=a.exp(), ) k = pyro.sample( "k", dist.TransformedDistribution(dist.Stable( 1.5, zero, b, a), dist.transforms.ExpTransform()).to_event(1), ) l = pyro.sample( "l", dist.TransformedDistribution(dist.Stable( 1.5, zero, b, a), dist.transforms.ExpTransform()).to_event(1), obs=a.exp() + zero, ) return a, b, c, d, e, f, g, h, i, j, k, l
def model(self, zero_data, covariates): with pyro.plate("batch", len(zero_data), dim=-2): zero_data = pyro.subsample(zero_data, event_dim=1) covariates = pyro.subsample(covariates, event_dim=1) loc = zero_data[..., :1, :] scale = pyro.sample("scale", dist.LogNormal(loc, 1).to_event(1)) with self.time_plate: jumps = pyro.sample("jumps", dist.Normal(0, scale).to_event(1)) prediction = jumps.cumsum(-2) duration, obs_dim = zero_data.shape[-2:] noise_dist = dist.LinearHMM( dist.Stable(1.9, 0).expand([obs_dim]).to_event(1), torch.eye(obs_dim), dist.Stable(1.9, 0).expand([obs_dim]).to_event(1), torch.eye(obs_dim), dist.Stable(1.9, 0).expand([obs_dim]).to_event(1), duration=duration, ) rep = StableReparam() with poutine.reparam( config={"residual": LinearHMMReparam(rep, rep, rep)}): self.predict(noise_dist, prediction)
def test_additive(stability, skew0, skew1, scale0, scale1): num_samples = 10000 d0 = dist.Stable(stability, skew0, scale0, coords="S") d1 = dist.Stable(stability, skew1, scale1, coords="S") expected = d0.sample([num_samples]) + d1.sample([num_samples]) scale = (scale0**stability + scale1**stability)**(1 / stability) skew = ((skew0 * scale0**stability + skew1 * scale1**stability) / (scale0**stability + scale1**stability)) d = dist.Stable(stability, skew, scale, coords="S") actual = d.sample([num_samples]) assert ks_2samp(expected, actual).pvalue > 0.05
def model(): stability = pyro.sample("stability", dist.Uniform(1.0, 2.0)) trans_skew = pyro.sample("trans_skew", dist.Uniform(-1.0, 1.0)) obs_skew = pyro.sample("obs_skew", dist.Uniform(-1.0, 1.0)) scale = pyro.sample("scale", dist.Gamma(3, 1)) # We use separate plates because the .cumsum() op breaks independence. with pyro.plate("time1", len(data)): dz = pyro.sample("dz", dist.Stable(stability, trans_skew)) z = dz.cumsum(-1) with pyro.plate("time2", len(data)): y = pyro.sample("y", dist.Stable(stability, obs_skew, scale, z)) pyro.sample("x", dist.Poisson(y.abs()), obs=data)
def test_sample(alpha, beta): num_samples = 100 d = dist.Stable(alpha, beta, coords="S") def sampler(size): # Temporarily increase radius to test hole-patching logic. # Scipy doesn't handle values of alpha very close to 1. try: old = pyro.distributions.stable.RADIUS pyro.distributions.stable.RADIUS = 0.02 return d.sample([size]) finally: pyro.distributions.stable.RADIUS = old def cdf(x): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always", category=IntegrationWarning) result = levy_stable.cdf(x, alpha, beta) # Scipy has only an experimental .cdf() function for alpha=1, beta!=0. # It sometimes passes and sometimes xfails. if w and alpha == 1 and beta != 0: pytest.xfail(reason="scipy.stats.levy_stable.cdf is unstable") return result assert kstest(sampler, cdf, N=num_samples).pvalue > 0.1
def model(self, zero_data, covariates): duration,data_dim = zero_data.shape feature_dim = covariates.size(-1) drift_stability = pyro.sample("drift_stability", dist.Uniform(1, 2)) drift_scale = pyro.sample("drift_scale", dist.LogNormal(-20, 5)) with pyro.plate("item", data_dim, dim=-2): bias = pyro.sample("bias", dist.Normal(5.0, 10.0)) with pyro.plate('features',feature_dim,dim=-1): weight = pyro.sample("weight", dist.Normal(0.0, 5)) # We'll sample a time-global scale parameter outside the time plate, # then time-local iid noise inside the time plate. with self.time_plate: # We combine two different reparameterizers: the inner SymmetricStableReparam # is needed for the Stable site, and the outer LocScaleReparam is optional but # appears to improve inference. with poutine.reparam(config={"drift": LocScaleReparam()}): with poutine.reparam(config={"drift": SymmetricStableReparam()}): drift = pyro.sample("drift", dist.Stable(drift_stability, 0, drift_scale)) motion = drift.cumsum(-1) # A Brownian motion. # The prediction now includes three terms. regression = torch.matmul(covariates, weight[...,None]) prediction = motion + bias + regression.sum(axis=-1) prediction = prediction.unsqueeze(-1).transpose(-1, -3) # Finally we can construct a noise distribution. # We will share parameters across all time series. obs_scale = pyro.sample("obs_scale", dist.LogNormal(-5, 5)) noise_dist = dist.Normal(loc=0.0, scale=obs_scale.unsqueeze(-1)) self.predict(noise_dist, prediction)
def test_sample(alpha, beta): num_samples = 100 d = dist.Stable(alpha, beta) def sampler(size): # Temporarily increase radius to test hole-patching logic. # Scipy doesn't handle values of alpha very close to 1. try: old = pyro.distributions.stable.RADIUS pyro.distributions.stable.RADIUS = 0.02 x = d.sample([size]) finally: pyro.distributions.stable.RADIUS = old # Convert from Nolan's parametrization S^0 to scipy parametrization S. if alpha == 1: z = x else: z = x + beta * np.tan(np.pi / 2 * alpha) return z def cdf(x): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always", category=IntegrationWarning) result = levy_stable.cdf(x, alpha, beta) # Scipy has only an experimental .cdf() function for alpha=1, beta!=0. # It sometimes passes and sometimes xfails. if w and alpha == 1 and beta != 0: pytest.xfail(reason="scipy.stats.levy_stable.cdf is unstable") return result stat, pvalue = kstest(sampler, cdf, N=num_samples) assert pvalue > 0.1, pvalue
def test_mean(stability, skew, scale, coords): loc = torch.randn(10) d = dist.Stable(stability, skew, scale, loc, coords=coords) if stability <= 1: assert torch.isnan(d.mean).all() else: expected = d.sample((100000, )).mean(0) assert_close(d.mean, expected, atol=0.1)
def test_variance(stability, scale): skew = dist.Uniform(-1, 1).sample((10, )) loc = torch.randn(10) d = dist.Stable(stability, skew, scale, loc) if stability < 2: assert torch.isinf(d.variance).all() else: expected = d.sample((100000, )).var(0) assert_close(d.variance, expected, rtol=0.02)
def test_shape(sample_shape, batch_shape): stability = torch.empty(batch_shape).uniform_(0, 2).requires_grad_() skew = torch.empty(batch_shape).uniform_(-1, 1).requires_grad_() scale = torch.randn(batch_shape).exp().requires_grad_() loc = torch.randn(batch_shape).requires_grad_() d = dist.Stable(stability, skew, scale, loc) assert d.batch_shape == batch_shape x = d.rsample(sample_shape) assert x.shape == sample_shape + batch_shape x.sum().backward()
def test_sample_2(alpha, beta): num_samples = 10000 d = dist.Stable(alpha, beta, coords="S") # Temporarily increase radius to test hole-patching logic. # Scipy doesn't handle values of alpha very close to 1. try: old = pyro.distributions.stable.RADIUS pyro.distributions.stable.RADIUS = 0.02 actual = d.sample([num_samples]) finally: pyro.distributions.stable.RADIUS = old actual = d.sample([num_samples]) expected = levy_stable.rvs(alpha, beta, size=num_samples) assert ks_2samp(expected, actual).pvalue > 0.05
def model(self, zero_data, covariates): duration, data_dim = zero_data.shape # Let's model each time series as a Levy stable process, and share process parameters # across time series. To do that in Pyro, we'll declare the shared random variables # outside of the "origin" plate: drift_stability = pyro.sample("drift_stability", dist.Uniform(1, 2)) drift_scale = pyro.sample("drift_scale", dist.LogNormal(-20, 5)) with pyro.plate("origin", data_dim, dim=-2): # Now inside of the origin plate we sample drift and seasonal components. # All the time series inside the "origin" plate are independent, # given the drift parameters above. with self.time_plate: # We combine two different reparameterizers: the inner SymmetricStableReparam # is needed for the Stable site, and the outer LocScaleReparam is optional but # appears to improve inference. with poutine.reparam(config={"drift": LocScaleReparam()}): with poutine.reparam(config={"drift": SymmetricStableReparam()}): drift = pyro.sample("drift", dist.Stable(drift_stability, 0, drift_scale)) with pyro.plate("hour_of_week", 24 * 7, dim=-1): seasonal = pyro.sample("seasonal", dist.Normal(0, 5)) # Now outside of the time plate we can perform time-dependent operations like # integrating over time. This allows us to create a motion with slow drift. seasonal = periodic_repeat(seasonal, duration, dim=-1) motion = drift.cumsum(dim=-1) # A Levy stable motion to model shocks. prediction = motion + seasonal # Next we do some reshaping. Pyro's forecasting framework assumes all data is # multivariate of shape (duration, data_dim), but the above code uses an "origins" # plate that is left of the time_plate. Our prediction starts off with shape assert prediction.shape[-2:] == (data_dim, duration) # We need to swap those dimensions but keep the -2 dimension intact, in case Pyro # adds sample dimensions to the left of that. prediction = prediction.unsqueeze(-1).transpose(-1, -3) assert prediction.shape[-3:] == (1, duration, data_dim), prediction.shape # Finally we can construct a noise distribution. # We will share parameters across all time series. obs_scale = pyro.sample("obs_scale", dist.LogNormal(-5, 5)) noise_dist = dist.Normal(0, obs_scale.unsqueeze(-1)) self.predict(noise_dist, prediction)
def model(self, zero_data, covariates): data_dim = zero_data.size(-1) feature_dim = covariates.size(-1) bias = pyro.sample("bias", dist.Normal(5.0, 10.0).expand((data_dim,)).to_event(1)) weight = pyro.sample("weight", dist.Normal(0.0, 5).expand((feature_dim,)).to_event(1)) # We'll sample a time-global scale parameter outside the time plate, # then time-local iid noise inside the time plate. drift_scale = pyro.sample("drift_scale", dist.LogNormal(0, 5.0).expand((1,)).to_event(1)) with self.time_plate: # We'll use a reparameterizer to improve variational fit. The model would still be # correct if you removed this context manager, but the fit appears to be worse. with poutine.reparam(config={"drift": LocScaleReparam()}): drift = pyro.sample("drift", dist.Normal(zero_data.double(), drift_scale.double()).to_event(1)) # After we sample the iid "drift" noise we can combine it in any time-dependent way. # It is important to keep everything inside the plate independent and apply dependent # transforms outside the plate. motion = drift.cumsum(-2) # A Brownian motion. # The prediction now includes three terms. prediction = motion + bias + (weight * covariates).sum(-1, keepdim=True) assert prediction.shape[-2:] == zero_data.shape # The next part of the model creates a likelihood or noise distribution. # Again we'll be Bayesian and write this as a probabilistic program with # priors over parameters. stability = pyro.sample("noise_stability", dist.Uniform(1, 2).expand((1,)).to_event(1)) skew = pyro.sample("noise_skew", dist.Uniform(-1, 1).expand((1,)).to_event(1)) scale = pyro.sample("noise_scale", dist.LogNormal(-5, 5).expand((1,)).to_event(1)) noise_dist = dist.Stable(stability, skew, scale) # We need to use a reparameterizer to handle the Stable distribution. # Note "residual" is the name of Pyro's internal sample site in self.predict(). with poutine.reparam(config={"residual": StableReparam()}): self.predict(noise_dist, prediction)
def model(): stability = pyro.sample("stability", dist.Uniform(0., 2.)) skew = pyro.sample("skew", dist.Uniform(-1., 1.)) y = pyro.sample("z", dist.Stable(stability, skew)) pyro.sample("x", dist.Poisson(y.abs()), obs=torch.tensor(1.))
def random_stable(shape, stability, skew=None): if skew is None: skew = dist.Uniform(-1, 1).sample(shape) scale = torch.rand(shape).exp() loc = torch.randn(shape) return dist.Stable(stability, skew, scale, loc)
def model(): with pyro.plate_stack("plates", shape): with pyro.plate("particles", 200000): return pyro.sample("x", dist.Stable(stability, 0, scale, loc))
def model(): return pyro.sample("x", dist.Stable(stability, skew))
def model(): with poutine.reparam(config={"x": Reparam()}): with pyro.plate("plate", 10): return pyro.sample("x", dist.Stable(1.5, 0))
def test_normal(loc, scale): num_samples = 100000 expected = dist.Normal(loc, scale).sample([num_samples]) actual = dist.Stable(2, 0, scale * 0.5**0.5, loc).sample([num_samples]) assert_close(actual.mean(), expected.mean(), atol=0.01) assert_close(actual.std(), expected.std(), atol=0.01)
def random_stable(stability, skew_scale_loc_shape): skew = dist.Uniform(-1, 1).sample(skew_scale_loc_shape) scale = torch.rand(skew_scale_loc_shape).exp() loc = torch.randn(skew_scale_loc_shape) return dist.Stable(stability, skew, scale, loc)
def model(): with pyro.plate("particles", 20000): return pyro.sample("x", dist.Stable(stability, skew))