def test_end_to_end_prediction_works_and_is_deterministic( self, dtype, use_xla, use_spike_and_slab): if not tf.executing_eagerly(): return seed = test_util.test_seed(sampler_type='stateless') model, observed_time_series, is_missing = self._build_test_model( num_timesteps=5, batch_shape=[3], prior_class=gibbs_sampler.XLACompilableInverseGamma, sparse_weights_nonzero_prob=0.5 if use_spike_and_slab else None, dtype=dtype) @tf.function(jit_compile=use_xla) def do_sampling(observed_time_series, is_missing): return gibbs_sampler.fit_with_gibbs_sampling( model, tfp.sts.MaskedTimeSeries( observed_time_series, is_missing), num_results=4, num_warmup_steps=1, seed=seed) samples = do_sampling(observed_time_series[..., tf.newaxis], is_missing) predictive_dist = gibbs_sampler.one_step_predictive( model, samples, thin_every=1) # Test that the seeded calculation gives the same result on multiple runs. samples2 = do_sampling(observed_time_series[..., tf.newaxis], is_missing) predictive_dist2 = gibbs_sampler.one_step_predictive( model, samples2, thin_every=1) (predictive_mean_, predictive_stddev_, predictive_mean2_, predictive_stddev2_) = self.evaluate(( predictive_dist.mean(), predictive_dist.stddev(), predictive_dist2.mean(), predictive_dist2.stddev())) self.assertAllEqual(predictive_mean_, predictive_mean2_) self.assertAllEqual(predictive_stddev_, predictive_stddev2_)
def test_forecasts_are_sane(self): seed = test_util.test_seed() num_observed_steps = 5 num_forecast_steps = 3 model, observed_time_series, is_missing = self._build_test_model( num_timesteps=num_observed_steps + num_forecast_steps, batch_shape=[3]) samples = gibbs_sampler.fit_with_gibbs_sampling( model, tfp.sts.MaskedTimeSeries( observed_time_series[..., :num_observed_steps, tf.newaxis], is_missing[..., :num_observed_steps]), num_results=5, num_warmup_steps=10, seed=seed, compile_steps_with_xla=False) predictive_dist = gibbs_sampler.one_step_predictive( model, samples, num_forecast_steps=num_forecast_steps, thin_every=1) predictive_mean, predictive_stddev = self.evaluate( (predictive_dist.mean(), predictive_dist.stddev())) self.assertAllEqual(predictive_mean.shape, [3, num_observed_steps + num_forecast_steps]) self.assertAllEqual(predictive_stddev.shape, [3, num_observed_steps + num_forecast_steps]) # Uncertainty should increase over the forecast period. self.assertTrue( np.all(predictive_stddev[..., num_observed_steps + 1:] > predictive_stddev[..., num_observed_steps:-1]))
def _detect_anomalies_inner(observed_time_series, seasonal_structure, anomaly_threshold=0.01, num_warmup_steps=50, num_samples=100, use_gibbs_predictive_dist=True, seed=None): """Helper function for `detect_anomalies` to cache `tf.function` traces.""" with tf.name_scope('build_default_model_for_gibbs_sampling'): observed_mean, observed_stddev, _ = sts_util.empirical_statistics( observed_time_series) # Center the series to have mean 0 and stddev 1. Alternately, we could # rescale the priors to match the series, but this is simpler. observed_mean = observed_mean[..., tf.newaxis] # Broadcast with num_steps. observed_stddev = observed_stddev[..., tf.newaxis] observed_time_series = observed_time_series._replace( time_series=(observed_time_series.time_series - observed_mean[..., tf.newaxis]) / observed_stddev[..., tf.newaxis]) model, posterior_samples = _fit_seasonal_model_with_gibbs_sampling( observed_time_series, seasonal_structure=seasonal_structure, num_results=num_samples, num_warmup_steps=num_warmup_steps, seed=seed) parameter_samples = _parameter_samples_from_gibbs_posterior( model, posterior_samples) if use_gibbs_predictive_dist: predictive_dist = gibbs_sampler.one_step_predictive( model, posterior_samples) else: # Rebuild the model using appropriate Seasonal components, throwing away # the seasonal effects fit by the Gibbs sampler. Instead, the predictive # model incorporates the seasonal effects in its state space, so their # posterior is tracked exactly (rather than by sampling) and is updated # with each step of the series. This avoids the learning-from-the-future # behavior of the Gibbs-estimated effects. seasonal_model = ( model.components[0] + # LocalLinearTrend component. default_model.model_from_seasonal_structure( seasonal_structure, observed_time_series, # Gibbs sampling didn't fit a drift scale(s). allow_drift=False)) predictive_dist = one_step_predictive( seasonal_model, observed_time_series, timesteps_are_event_shape=False, parameter_samples=parameter_samples) prob_lower = predictive_dist.cdf(observed_time_series.time_series[..., 0]) tail_probabilities = 2 * tf.minimum(prob_lower, 1 - prob_lower) lower_limit, upper_limit, predictive_mean = compute_predictive_bounds( predictive_dist, anomaly_threshold=anomaly_threshold) restore_scale = lambda x: x * observed_stddev + observed_mean return (restore_scale(lower_limit), restore_scale(upper_limit), restore_scale(predictive_mean), tail_probabilities)
def test_end_to_end_prediction_works_and_is_deterministic( self, dtype, use_xla): if not tf.executing_eagerly(): return seed = test_util.test_seed() model, observed_time_series, is_missing = self._build_test_model( num_timesteps=5, batch_shape=[3]) samples = gibbs_sampler.fit_with_gibbs_sampling( model, tfp.sts.MaskedTimeSeries(observed_time_series[..., tf.newaxis], is_missing), num_results=4, num_warmup_steps=1, seed=seed, compile_steps_with_xla=use_xla) predictive_dist = gibbs_sampler.one_step_predictive(model, samples, thin_every=1) # Test that the seeded calculation gives the same result on multiple runs. samples2 = gibbs_sampler.fit_with_gibbs_sampling( model, tfp.sts.MaskedTimeSeries(observed_time_series, is_missing), num_results=4, num_warmup_steps=1, seed=seed, compile_steps_with_xla=use_xla) predictive_dist2 = gibbs_sampler.one_step_predictive(model, samples2, thin_every=1) (predictive_mean_, predictive_stddev_, predictive_mean2_, predictive_stddev2_) = self.evaluate( (predictive_dist.mean(), predictive_dist.stddev(), predictive_dist2.mean(), predictive_dist2.stddev())) self.assertAllEqual(predictive_mean_, predictive_mean2_) self.assertAllEqual(predictive_stddev_, predictive_stddev2_)
def test_forecasts_match_reference(self, use_slope, num_chains, time_series_shift, use_zero_step_prediction=False): seed = test_util.test_seed() num_observed_steps = 5 num_forecast_steps = 4 num_results = 10000 # Dividing the number of results with number of chains so we sample the same # total number of MCMC samples. if not tf.nest.is_nested(num_chains): num_results = num_results // num_chains model, observed_time_series, is_missing = self._build_test_model( num_timesteps=num_observed_steps + num_forecast_steps, true_slope_scale=0.5 if use_slope else None, batch_shape=[3], time_series_shift=time_series_shift) @tf.function(autograph=False) def do_sampling(): return gibbs_sampler.fit_with_gibbs_sampling( model, tfp.sts.MaskedTimeSeries( observed_time_series[..., :num_observed_steps, tf.newaxis], is_missing[..., :num_observed_steps]), num_chains=num_chains, num_results=num_results, num_warmup_steps=100, seed=seed) samples = self.evaluate(do_sampling()) def reshape_chain_and_sample(x): if np.ndim(x) > 2: return np.reshape(x, [x.shape[0] * x.shape[1], *x.shape[2:]]) return x if not tf.nest.is_nested(num_chains): samples = tf.nest.map_structure(reshape_chain_and_sample, samples) predictive_dist = gibbs_sampler.one_step_predictive( model, samples, num_forecast_steps=num_forecast_steps, thin_every=1, use_zero_step_prediction=use_zero_step_prediction) predictive_mean, predictive_stddev = self.evaluate(( predictive_dist.mean(), predictive_dist.stddev())) self.assertAllEqual(predictive_mean.shape, [3, num_observed_steps + num_forecast_steps]) self.assertAllEqual(predictive_stddev.shape, [3, num_observed_steps + num_forecast_steps]) # big tolerance, but makes sure the predictive mean initializes near # the initial time series value self.assertAllClose(tf.reduce_mean(predictive_mean[:, 0]), observed_time_series[0, 0], atol=10.) if use_slope: parameter_samples = (samples.observation_noise_scale, samples.level_scale, samples.slope_scale, samples.weights) else: parameter_samples = (samples.observation_noise_scale, samples.level_scale, samples.weights) # Note that although we expect the Gibbs-sampled forecasts to match a # reference implementation, we *don't* expect the one-step predictions to # match `tfp.sts.one_step_predictive`, because that makes predictions using # a filtered posterior (i.e., given only previous observations) whereas the # Gibbs-sampled latent `level`s will incorporate some information from # future observations. reference_forecast_dist = tfp.sts.forecast( model, observed_time_series=observed_time_series[..., :num_observed_steps], parameter_samples=parameter_samples, num_steps_forecast=num_forecast_steps) reference_forecast_mean, reference_forecast_stddev = self.evaluate(( reference_forecast_dist.mean()[..., 0], reference_forecast_dist.stddev()[..., 0])) self.assertAllClose(predictive_mean[..., -num_forecast_steps:], reference_forecast_mean, atol=1.0 if use_slope else 0.3) self.assertAllClose(predictive_stddev[..., -num_forecast_steps:], reference_forecast_stddev, atol=2.0 if use_slope else 1.0)
def test_forecasts_match_reference(self, use_slope): seed = test_util.test_seed() num_observed_steps = 5 num_forecast_steps = 4 model, observed_time_series, is_missing = self._build_test_model( num_timesteps=num_observed_steps + num_forecast_steps, true_slope_scale=0.5 if use_slope else None, batch_shape=[3]) samples = tf.function(lambda: gibbs_sampler.fit_with_gibbs_sampling( # pylint: disable=g-long-lambda model, tfp.sts.MaskedTimeSeries( observed_time_series[..., :num_observed_steps, tf.newaxis], is_missing[..., :num_observed_steps]), num_results=10000, num_warmup_steps=100, seed=seed))() predictive_dist = gibbs_sampler.one_step_predictive( model, samples, num_forecast_steps=num_forecast_steps, thin_every=1) predictive_mean, predictive_stddev = self.evaluate( (predictive_dist.mean(), predictive_dist.stddev())) self.assertAllEqual(predictive_mean.shape, [3, num_observed_steps + num_forecast_steps]) self.assertAllEqual(predictive_stddev.shape, [3, num_observed_steps + num_forecast_steps]) if use_slope: parameter_samples = (samples.observation_noise_scale, samples.level_scale, samples.slope_scale, samples.weights) else: parameter_samples = (samples.observation_noise_scale, samples.level_scale, samples.weights) # Note that although we expect the Gibbs-sampled forecasts to match a # reference implementation, we *don't* expect the one-step predictions to # match `tfp.sts.one_step_predictive`, because that makes predictions using # a filtered posterior (i.e., given only previous observations) whereas the # Gibbs-sampled latent `level`s will incorporate some information from # future observations. reference_forecast_dist = tfp.sts.forecast( model, observed_time_series=observed_time_series[ ..., :num_observed_steps], parameter_samples=parameter_samples, num_steps_forecast=num_forecast_steps) reference_forecast_mean = self.evaluate( reference_forecast_dist.mean()[..., 0]) reference_forecast_stddev = self.evaluate( reference_forecast_dist.stddev()[..., 0]) self.assertAllClose(predictive_mean[..., -num_forecast_steps:], reference_forecast_mean, atol=1.0 if use_slope else 0.3) self.assertAllClose(predictive_stddev[..., -num_forecast_steps:], reference_forecast_stddev, atol=2.0 if use_slope else 1.0)