reparam.time_reparam()
Reparameterize every in-sample time latent of model along the time axis.
Usage
reparam.time_reparam(
model,
transform,
)Port of the time_reparam argument of Pyro’s Forecaster and HMCForecaster (forecaster.py). The returned model is numpyro.handlers.reparam(model, config) with a config that targets every non-observed continuous sample site under the time plate opened by innovations(), exactly as Pyro’s time_reparam_haar targets every site inside its time plate. Each targeted site name becomes a deterministic site of the same shape, computed from a new sample site f"{name}_haar" or f"{name}_dct" whose event shape owns the time axis and every plate axis to its right (plate axes to the left of time stay batch axes). The transform is orthonormal and acts on the unconstrained value of each site, so the posterior and the unconstrained potential energy that NUTS and AutoNormal target are the same function of the rotated coordinates; only the geometry inference sees changes. For a site with a constrained support (positive or bounded), the numpyro.infer.util.log_density values and the AutoDelta MAP estimates are not those of the plain model: see the Notes.
Parameters
model: ForecastModel-
A forecasting model
(covariates, data=None) -> None. transform: TimeTransform-
"haar"fornumpyro.distributions.transforms.HaarTransform(a multi-resolution average/difference basis, suited to blocky or multi-scale dynamics) or"dct"fornumpyro.distributions.transforms.DiscreteCosineTransform(a cosine frequency basis, close to the Karhunen-Loeve basis of first-order Markov processes). Note that Pyro’s own string mapping is swapped: its"haar"runs a DCT and its"dct"runs a Haar transform.
Returns
ForecastModel-
The wrapped model. It is a
numpyro.handlers.reparamhandler and is the single object to hand to the guide,SVI/MCMC/ blackjax, forecast(), predict_in_sample(), to_datatree() and themodel_fnof backtest().
Raises
ValueError-
If
Also if an in-sample time latent is under a subsampled plate when the model is traced: a plate opened withmodelalready contains the result of time_reparam(). Nesting is not supported: the inner handler would turn the site into a deterministic before the outer one sees it, so the outer transform would silently be a no-op. The check runs when wrapping and looks through any NumPyro handlers aroundmodel(scope,seed, …). A nesting behind a plain function or afunctools.partial()raises when the model is first traced (insideSVI,MCMC, forecast(), …), if the outer transform finds no in-sample time latent of its own. A composite model whose body calls a time_reparam() component next to its own innovations time latents is valid: the outer transform applies to those latents. A composite whose own time latents are never targeted (markov_series or ssoe sites, observed sites) also raises, because the outer transform does nothing there either.subsample_sizesmaller than its size, or a full model plate that the guide subsamples throughcreate_plates. See the Notes.
Notes
- Hand the wrapped model, not the original, to the guide, the sampler and the drivers: only the wrapped model has the auxiliary site that the posterior holds. Creating it once is a convenience, not a requirement: the drivers key nothing on the model object, so a rewrapped model gives the same draws.
- Apply it innermost.
scope(time_reparam(model), prefix="a")yieldsa/drift_haar;time_reparam(scope(model, "a"))yieldsa/a/drift_haarbecause the auxiliary sample passes throughscopea second time (generic NumPyro reparam-under-scope behavior). To export with to_datatree(), which rejects"/"in a posterior key, scope a latent component withdivider="_"and call predict outside every scope (scope(time_reparam(trend), prefix="a", divider="_")yieldsa_drift_haaranda_drift); see theposteriorparameter of to_datatree(). - It composes with the per-block
reparam=hook: afterinnovations(..., reparam=LocScaleReparam(0))the site under the plate isdrift_decentered, so the auxiliary site isdrift_decentered_haarand bothdrift_decenteredanddriftbecome deterministic. This matches Pyro, whose config applies to every site in the plate. - Untouched sites: the
_futuresuffix sites (they stay prior-drawn undertime_future, so forecast() is unaffected), the scan sites of markov_series(), the error sites of ssoe(), observed sites and discrete sites. - Constrained supports. The rotation
Tacts on the unconstrained valueu = biject_to(support).inv(x)of a targeted sitex, so the auxiliary site holdsT(u). The potential energy in the unconstrained space is the same function of the rotated coordinates for every support: NUTS and the guides that work in that space (AutoNormal, for example) target the same density, only in the rotated basis. A mean-field guide such asAutoNormalis not rotation invariant, so its fit and its ELBO can change; that is the point of the feature. For a site with real support,uisxand the value ofnumpyro.infer.util.log_densityis unchanged too. For a site with a constrained support, the value ofnumpyro.infer.util.log_densityof the wrapped model at the auxiliary valueT(u)is the value of the plain model atxplus the log absolute Jacobian determinant ofbiject_to(support)atu:sum(log(x))for aLogNormalsite andsum(log(x) + log(1 - x))for aBetasite. Values ofnumpyro.infer.util.log_densityandAutoDeltaMAP estimates are therefore not comparable with those of the plain model (NumPyro’sAutoDeltadoes MAP in constrained space, so the plain model’s estimate is the mode inx; the wrapped model’s auxiliary site is real-valued, so its estimate is the mode inu, which is whatAutoLaplaceApproximationgives for the plain model). To keep them comparable, sample the innovation on the real line, for exampleinnovations(h, "log_growth", dist.Normal(0.0, 0.5)), and applyjnp.expto it (to its cumulative sum for a multiplicative random walk); the MAP is then the mode in the log for both models. Pyro’s time_reparam and NumPyro’sUnitJacobianReparambehave the same way. - Time latents under a subsampled plate are not supported. The auxiliary site is sampled outside every enclosing plate, so a subsampled plate would apply no
size / subsample_sizescale to it and anAutoGuidewould size its parameters to the minibatch. A time latent under such a plate therefore raisesValueError, also when the guide subsamples the plate throughcreate_plates. Time latents outside the subsampled plate (for example one level shared by all series) and a plate withsubsample_sizeequal to its size are accepted. - Posterior dictionaries from draw_posterior() and
mcmc.get_samples()contain bothdrift(deterministic) anddrift_haar;Predictivesubstitutes only the latter and recomputes the former.init_to_valuemust therefore targetdrift_haar. to_datatree() raisesValueErrorfor a posterior key that misses its site (drift_haarwithoutdriftwith the plain model, ora_drift_haarwith a model still scoped with"/"), while forecast() and predict_in_sample() ignore such a key and draw the site from the prior. - Measured on random-walk level models: mean-field
AutoNormalreaches a better ELBO for both transforms (DCT slightly ahead of Haar), but an optimizer schedule tuned for the original coordinates does not transfer as is. The rotated posterior is better conditioned, so it tolerates and may need a larger learning rate to converge within the same step budget; a schedule that is too small stalls with part of the intercept still held by the level. NUTS trajectories become cheaper (fewer leapfrog steps per iteration) while the effective sample size per draw is model dependent.
Examples
model_dct = time_reparam(seasonal_model, "dct")
guide = AutoNormal(model_dct)
svi = SVI(model_dct, guide, Adam(0.01), Trace_ELBO())
svi_result = svi.run(key_fit, 1_500, covariates[:t_obs], data, progress_bar=False)
posterior = draw_posterior(key_post, guide, svi_result.params, num_samples=100)
samples = forecast(key_pred, model_dct, posterior, data, covariates)