Opportunities and Applications
Scan
in NumPyro😅 …
for
loopsdef scan(f, init, xs):
"""Pure Python implementation of scan.
Parameters
----------
f : A a Python function to be scanned.
init : An initial loop carry value
xs : The value over which to scan along the leading axis.
"""
carry = init
ys = []
for x in xs:
carry, y = f(carry, x)
ys.append(y)
return carry, np.stack(ys)
We need recursive relationships! \(y_t \longmapsto y_{t+1}\)
\[ \begin{align*} \hat{y}_{t+h|t} = & \: l_t \\ l_t = & \: \alpha y_t + (1 - \alpha)l_{t-1} \end{align*} \]
\[ \begin{align*} \hat{y}_{t+h|t} = & \: l_t \\ l_t = & \: \alpha y_t + (1 - \alpha)l_{t-1} \end{align*} \]
def level_model(y: ArrayLike, future: int = 0) -> None:
# Get time series length
t_max = y.shape[0]
# --- Priors ---
## Level
level_smoothing = numpyro.sample(
"level_smoothing", dist.Beta(concentration1=1, concentration0=1)
)
level_init = numpyro.sample("level_init", dist.Normal(loc=0, scale=1))
## Noise
noise = numpyro.sample("noise", dist.HalfNormal(scale=1))
# --- Transition Function ---
def transition_fn(carry, t):
. . .
# --- Run Scan ---
with numpyro.handlers.condition(data={"pred": y}):
_, preds = scan(
transition_fn,
level_init,
jnp.arange(t_max + future),
)
# --- Forecast ---
if future > 0:
numpyro.deterministic("y_forecast", preds[-future:])
Scalability: \(~40\)K time-series can be fitted in less than \(10\) minutes in a GPU.
The method is based on the idea of separating the demand size \(z_t\) and the demand interval \(p_t\), and then forecasting them separately using simple exponential smoothing.
\[ \hat{y}_{t+h} = \frac{\hat{z}_{t+h}}{\hat{p}_{t+h}} \]
def croston_model(z: ArrayLike, p_inv: ArrayLike, future: int = 0) -> None:
z_forecast = scope(level_model, "demand")(z, future)
p_inv_forecast = scope(level_model, "period_inv")(p_inv, future)
if future > 0:
numpyro.deterministic("z_forecast", z_forecast)
numpyro.deterministic("p_inv_forecast", p_inv_forecast)
numpyro.deterministic("forecast", z_forecast * p_inv_forecast)
- The TSB method is similar to the Croston’s method: constructs two different time series out of the original one and then forecast each of them separately, so that the final forecast is generated by combining the forecasts of the two time series.
- The main difference between the two methods is that the TSB method uses the demand probability instead of the demand periods.
🧪 We can modify the TSB model to include zero-inflation by using a Zero-Inflated Negative Binomial Distribution.
def censored_normal(loc, scale, y, censored):
distribution = dist.Normal(loc=loc, scale=scale)
ccdf = 1 - distribution.cdf(y)
numpyro.sample(
"censored_label",
dist.Bernoulli(probs=ccdf).mask(censored == 1),
obs=censored
)
return numpyro.sample("pred", distribution.mask(censored != 1))
Change likelihood distribution in a time-series model:
Use a hierarchical structure to regularize the demand elasticity parameters.
Let us assume that we know from domain knowledge that the effect of temperature on demand over 32°C is somehow stable at around a value of 0.13.