N-BEATS › Example

Michael BrenndoerferAugust 20, 20259 min read
Machine Learning from ScratchAll chapters
Part 8 of 9Time Series
Chapter 60N-BEATS › Example
Reading section 4 of 8 · Example · 9 minSection 4 of 8 · 9 min

Book contents

Section 4 of 8 · 9 min read

N-BEATS › ExampleOne section · Sections in this chapter4 / 8 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Now that we understand the mathematical framework, let's see N-BEATS in action. We'll work through a concrete example that demonstrates how the formulas we've discussed translate into a working forecasting model.

The scenario: Suppose we have monthly sales data for a retail store over 24 months, and we want to forecast the next 6 months. This is a realistic forecasting problem where we need to separate the underlying growth trend from seasonal patterns.

What we'll learn: Through this example, you'll see:

  • How the decomposition formula yt=∑k=1Kgkt(xt)+∑k=1Kgks(xt)+ϵty_t = \sum_{k=1}^{K} g_k^t(x_t) + \sum_{k=1}^{K} g_k^s(x_t) + \epsilon_t is applied in practice
  • How the neural network learns the coefficients αk,h\alpha_{k,h}, βk,h\beta_{k,h}, and γk,h\gamma_{k,h} from data
  • How the basis functions combine to produce interpretable forecasts

Let's begin by creating synthetic data that mimics a real-world scenario with both trend and seasonality.

Step 1: Prepare the Input Data

First, we need to format our time series data for N-BEATS. The model typically takes a window of historical values as input. This is the xt\mathbf{x}_t in our formula. We'll create data that has both a linear growth trend and yearly seasonality, which will allow us to see how N-BEATS separates these components:

In[6]:
Code
import numpy as np
import pandas as pd

# Generate sample monthly sales data
np.random.seed(42)
months = 24
t = np.arange(months)

# Create trend + seasonality + noise
trend = 100 + 2 * t  # Linear growth
seasonal = 20 * np.sin(2 * np.pi * t / 12) + 10 * np.cos(2 * np.pi * t / 12)
noise = np.random.normal(0, 5, months)
sales = trend + seasonal + noise

# Create DataFrame
df = pd.DataFrame({"month": t + 1, "sales": sales})
Out[7]:
Console
Sample sales data:
   month       sales
0      1  112.483571
1      2  119.968933
2      3  129.558951
3      4  133.615149
4      5  119.149741
5      6  110.169061
6      7  109.896064
7      8   99.176920
8      9   91.332120
9     10  100.712800

The sales data shows monthly values with both trend and seasonal components. Notice how the values combine a steady upward trend (the linear growth) with periodic fluctuations (the seasonal pattern). The first 10 months demonstrate the underlying pattern that N-BEATS will learn to decompose and forecast.

What's happening mathematically: This data follows the decomposition formula we discussed: yt=trend+seasonal+ϵty_t = \text{trend} + \text{seasonal} + \epsilon_t. The trend component is 100+2t100 + 2t (linear growth), the seasonal component is 20sin⁡(2πt/12)+10cos⁡(2πt/12)20\sin(2\pi t/12) + 10\cos(2\pi t/12) (yearly cycle), and we've added noise ϵt\epsilon_t to make it realistic. N-BEATS will learn to separate these components automatically.

Step 2: Define the N-BEATS Architecture

Now let's implement a simplified version of N-BEATS to understand how it works. This implementation will demonstrate the key components we discussed in the Formula section:

  • The neural network that learns coefficients (θ=NN(xt)\boldsymbol{\theta} = \text{NN}(\mathbf{x}_t))
  • The trend basis functions (gktg_k^t with polynomial terms)
  • The seasonal basis functions (gksg_k^s with trigonometric terms)
  • The additive combination to produce forecasts
In[8]:
Code
import torch
import torch.nn as nn


class SimpleNBEATS(nn.Module):
    def __init__(self, input_size=12, hidden_size=64, output_size=6):
        super(SimpleNBEATS, self).__init__()

        # Neural network to learn coefficients
        self.network = nn.Sequential(
            nn.Linear(input_size, hidden_size),
            nn.ReLU(),
            nn.Linear(hidden_size, hidden_size),
            nn.ReLU(),
            nn.Linear(
                hidden_size, output_size * 4
            ),  # 4 coefficients per horizon
        )

        self.output_size = output_size

    def forward(self, x):
        # Get coefficients from neural network
        coeffs = self.network(x)

        # Reshape coefficients
        coeffs = coeffs.view(-1, self.output_size, 4)

        # Extract trend and seasonal coefficients
        trend_coeffs = coeffs[:, :, :2]  # Linear and quadratic
        seasonal_coeffs = coeffs[:, :, 2:]  # Sine and cosine

        # Generate forecasts
        forecasts = []
        for h in range(self.output_size):
            # Trend component
            trend_h = trend_coeffs[:, h, 0] * (h + 1) + trend_coeffs[
                :, h, 1
            ] * ((h + 1) ** 2)

            # Seasonal component
            seasonal_h = seasonal_coeffs[:, h, 0] * np.sin(
                2 * np.pi * (h + 1) / 12
            ) + seasonal_coeffs[:, h, 1] * np.cos(2 * np.pi * (h + 1) / 12)

            forecast_h = trend_h + seasonal_h
            forecasts.append(forecast_h)

        return torch.stack(forecasts, dim=1)


# Initialize model
model = SimpleNBEATS(input_size=12, hidden_size=64, output_size=6)
total_params = sum(p.numel() for p in model.parameters())
Out[9]:
Console
Model parameters: 6552

The model architecture has a moderate number of parameters, making it suitable for learning the trend and seasonal patterns in the sales data.

Step 3: Prepare Training Data

We need to create input-output pairs for training:

In[10]:
Code
def create_sequences(data, input_size=12, output_size=6):
    """Create input-output sequences for training"""
    X, y = [], []

    for i in range(len(data) - input_size - output_size + 1):
        X.append(data[i : i + input_size])
        y.append(data[i + input_size : i + input_size + output_size])

    return np.array(X), np.array(y)


# Create training sequences
X, y = create_sequences(sales, input_size=12, output_size=6)
Out[11]:
Console
Input shape: (7, 12)
Output shape: (7, 6)
Number of training samples: 7

The sliding window approach creates multiple training samples from the 24 months of data. Each sample uses 12 months as input (this is our xt\mathbf{x}_t) to predict the next 6 months (this is our forecast horizon HH). This approach allows the model to learn from different segments of the time series, seeing how historical patterns relate to future values.

Why this matters: By creating multiple training samples, we give the neural network many examples of how historical patterns (the input window) should translate into coefficient values (the learned αk,h\alpha_{k,h}, βk,h\beta_{k,h}, γk,h\gamma_{k,h}) that produce accurate forecasts.

Step 4: Train the Model

Now let's train our N-BEATS model. This is where the loss function we discussed comes into play. The model will learn the optimal coefficients by minimizing the mean squared error:

L=1T∑t=1T(yt−y^t)2\mathcal{L} = \frac{1}{T} \sum_{t=1}^{T} (y_t - \hat{y}_t)^2

During training, the network will:

  1. Make predictions using current coefficients
  2. Calculate the loss (how far predictions are from actual values)
  3. Adjust its weights to reduce the loss
  4. Repeat until the coefficients produce accurate forecasts
In[12]:
Code
import torch.optim as optim

# Convert to PyTorch tensors
X_tensor = torch.FloatTensor(X)
y_tensor = torch.FloatTensor(y)

# Training setup
optimizer = optim.Adam(model.parameters(), lr=0.001)
criterion = nn.MSELoss()

# Training loop
epochs = 1000
model.train()
training_losses = []

for epoch in range(epochs):
    optimizer.zero_grad()

    # Forward pass
    predictions = model(X_tensor)

    # Calculate loss
    loss = criterion(predictions, y_tensor)

    # Backward pass
    loss.backward()
    optimizer.step()

    training_losses.append(loss.item())
Out[13]:
Console
Epoch 0, Loss: 44180.6016
Epoch 200, Loss: 26.6763
Epoch 400, Loss: 17.0943
Epoch 600, Loss: 12.7652
Epoch 800, Loss: 10.0619
Final training loss: 8.4439

The training loss decreases over epochs, indicating that the model is learning to capture the underlying patterns in the sales data. The final loss value shows how well the model fits the training data, with lower values indicating better fit.

What's happening mathematically: As the loss decreases, the neural network is learning the optimal coefficients αk,h\alpha_{k,h}, βk,h\beta_{k,h}, and γk,h\gamma_{k,h} that minimize prediction error. The network is simultaneously learning:

  • How to separate trend from seasonality
  • Which polynomial degrees are needed for the trend
  • Which harmonics are needed for the seasonality
  • The appropriate coefficients for each basis function

Step 5: Make Predictions

Finally, let's use our trained model to make forecasts. This is where all the pieces come together:

  1. The neural network takes the last 12 months as input (xt\mathbf{x}_t)
  2. It outputs the learned coefficients (θ=NN(xt)\boldsymbol{\theta} = \text{NN}(\mathbf{x}_t))
  3. These coefficients are applied to the basis functions (gktg_k^t and gksg_k^s)
  4. The trend and seasonal components are combined additively to produce the forecast

This is the decomposition formula in action. For each forecast horizon hh, we compute:

y^t+h=trendh+seasonalh=∑k=1Kαk,h⋅(hH)k+∑k=1K[βk,h⋅sin⁡(2πkhP)+γk,h⋅cos⁡(2πkhP)]\begin{align*} \hat{y}_{t+h} &= \text{trend}_h + \text{seasonal}_h \\ &= \sum_{k=1}^{K} \alpha_{k,h} \cdot \left(\frac{h}{H}\right)^k \\ &\quad + \sum_{k=1}^{K} \left[ \beta_{k,h} \cdot \sin\left(\frac{2\pi k h}{P}\right) + \gamma_{k,h} \cdot \cos\left(\frac{2\pi k h}{P}\right) \right] \end{align*}
In[14]:
Code
# Prepare last 12 months for prediction
last_window = sales[-12:].reshape(1, -1)
last_window_tensor = torch.FloatTensor(last_window)

# Make prediction
model.eval()
with torch.no_grad():
    forecast = model(last_window_tensor)
    forecast_np = forecast.numpy().flatten()
Out[15]:
Console
N-BEATS Forecast for next 6 months:
Month 1: 157.85
Month 2: 155.43
Month 3: 167.36
Month 4: 161.56
Month 5: 172.31
Month 6: 162.45
Out[16]:
Visualization
Monthly sales time series with 24 training observations, six circular N-BEATS forecast markers, six dashed square actual markers, and a dotted vertical line separating history from the forecast period.
N-BEATS predictions for the example sales data. The plot shows 24 months of training data, the model's forecast for the next 6 months, and the generated future values used for comparison. The dotted vertical line marks the forecast boundary.

The forecast shows the predicted sales values for the next 6 months. These values combine the learned trend and seasonal patterns from the historical data. The model extrapolates the linear growth trend and applies the seasonal pattern learned from the 24 months of training data.

What we've accomplished: This example demonstrates the complete N-BEATS workflow:

  1. Decomposition: The model learned to separate the time series into trend and seasonal components, following the formula yt=∑k=1Kgkt(xt)+∑k=1Kgks(xt)+ϵty_t = \sum_{k=1}^{K} g_k^t(x_t) + \sum_{k=1}^{K} g_k^s(x_t) + \epsilon_t

  2. Coefficient learning: The neural network automatically learned the coefficients αk,h\alpha_{k,h}, βk,h\beta_{k,h}, and γk,h\gamma_{k,h} that best fit the data, without us having to specify them manually

  3. Basis function application: The learned coefficients were applied to polynomial trend functions and trigonometric seasonal functions, creating interpretable components

  4. Additive combination: The trend and seasonal components were combined additively to produce forecasts that we can understand and trust

This is the power of N-BEATS: it combines mathematical rigor (the basis expansion framework) with practical learning (the neural network) to create forecasts that are both accurate and interpretable.