N-BEATS: neural basis expansion analysis for time series forecasting.
Classes
N-BEATS: doubly-residual stacks of fully connected blocks.
N-BEATS forecasts a series from a lookback window using nothing but dense layers — no recurrence, no convolution, no attention — and still beat the winner of the M4 competition. Its one structural idea is the double residual: every block emits both a backcast (its reconstruction of the input it saw) and a forecast. The backcast is subtracted from the block's input before the next block runs, so each block only ever works on the part of the signal its predecessors could not explain, and the final forecast is the plain sum of the block forecasts.
Two basis configurations are available. The generic one learns the backcast and forecast vectors directly and is the stronger forecaster. The interpretable one constrains the first stack to a low-order polynomial (trend) and the second to a Fourier series (seasonality), which costs a little accuracy and buys a decomposition you can plot — see decompose.
__init__( self, lookback: int = 24, horizon: int = 8, stack_type: str = 'generic', n_stacks: int = 2, n_blocks: int = 2, n_layers: int = 2, hidden_size: int = 64, trend_polynomial_degree: int = 2, n_harmonics: int = 4, backcast_loss_weight: float = 0.5, n_epochs: int = 100, batch_size: int = 32, learning_rate: float = 0.001, patience: int = 15, device: str = 'cpu', random_state: Optional[int] = None, )
Overview
-
Slide a
(lookback, horizon)window over the series to build a
- Normalise each window by its own mean and standard deviation.
- Pass the window through the first block: a stack of ReLU layers
- Subtract the backcast from the block input and hand the residual to the
- Denormalise the summed forecast with the window's own statistics.
Theory
Block \ell receives residual x_\ell and produces
where \theta_\ell = g_\ell(x_\ell) is the output of the fully connected trunk and V^b, V^f are the backcast and forecast bases. The residual recursion and the forecast aggregation are
For the generic configuration V^b and V^f are identity — the trunk emits the vectors themselves. For the interpretable configuration the trend stack uses a polynomial basis of order p,
and the seasonality stack a Fourier basis with fundamental period H,
The constant harmonic k = 0 is omitted so the seasonal component has exactly zero mean and cannot absorb the level.
Parameters
lookback
horizon
stack_type
"generic" learns the bases; "interpretable" uses a trend stack and a seasonality stack and enables decompose.
n_stacks
stack_type="interpretable", which always has exactly two.
n_blocks
n_layers
hidden_size
trend_polynomial_degree
n_harmonics
backcast_loss_weight
n_epochs
batch_size
learning_rate
patience
device
"cpu" because it is the only setting that gives bit-identical forecasts across machines; use "auto" when speed matters more than exact reproducibility.
random_state
device="cpu" the forecast is reproducible.
Attributes
module_
lookback_
horizon_
n_windows_
offset_
scale_
series_
loss_curve_
n_epochs_run_
device_
Notes
Requires PyTorch: pip install 'tuiml[torch]'. The class imports, constructs, registers and reports its schema without torch; only fit needs it.
Complexity:
- Training: O(E \cdot W \cdot L \cdot B \cdot h) for E
- Prediction: O(\lceil s / H \rceil \cdot L \cdot B \cdot h)
When to use NBEATSForecaster:
- Univariate series with a few hundred points or more, where a classical
- When you want a strong forecaster with no feature engineering.
- When an explicit trend/seasonality split is wanted, via
stack_type="interpretable".
- Not for very short series: with fewer than roughly a hundred points,
ExponentialSmoothing or ARIMA will usually win.References
See Also
>>> import numpy as np
>>> from tuiml.algorithms.timeseries.deep import NBEATSForecaster
>>> from tuiml.utils.torch_backend import has_torch
>>> model = NBEATSForecaster(lookback=24, horizon=6, random_state=0)
>>> sorted(model.get_parameter_schema())[:3]
['backcast_loss_weight', 'batch_size', 'device']
>>> if has_torch():
... y = np.sin(np.arange(200) / 5.0)
... _ = model.fit(y)
... print(model.predict(steps=6).shape)
... else:
... print("(6,)")
(6,)
Methods
decompose
(self, steps: int=1) -> Dict[str, np.ndarray]
decompose
(self, steps: int=1) -> Dict[str, np.ndarray]
Split the forecast into its trend and seasonality components.
Parameters
steps
Returns
components
"trend", "seasonality" and "forecast", each of shape (steps,) and in the units of the training series.
Raises
RuntimeError
fit.
ValueError
Functions
Build the polynomial basis N-BEATS uses for its trend stack.
degree — a deliberately low-order, slowly varying function.Parameters
degree
degree + 1 rows.
length
Returns
basis
>>> from tuiml.algorithms.timeseries.deep.nbeats import polynomial_basis
>>> polynomial_basis(1, 4).round(2)
array([[1. , 1. , 1. , 1. ],
[0. , 0.25, 0.5 , 0.75]])
Build the Fourier basis N-BEATS uses for its seasonality stack.
n_harmonics over a fundamental period of exactly length. The zeroth (constant) harmonic is deliberately omitted: it would let the seasonality stack absorb the level, blurring the split against the trend stack. Because it is omitted, every row — and so every linear combination of them — has exactly zero mean over one period and repeats with period length.Parameters
n_harmonics
2 * n_harmonics rows, a cosine and a sine per harmonic.
length
Returns
basis
>>> import numpy as np
>>> from tuiml.algorithms.timeseries.deep.nbeats import fourier_basis
>>> basis = fourier_basis(2, 8)
>>> basis.shape
(4, 8)
>>> bool(np.allclose(basis.mean(axis=1), 0.0))
True