1. Time Series
This matters because forecasting code fails when it leaks future information into training. Focus on sliding windows, chronological splits, and making the prediction target explicit.
1.1. Build windows from a signal
A forecasting model does not see the whole series at once. It sees a context window and predicts the next value or the next horizon.
[ ]:
import os
import math
import torch
from torch import nn
torch.manual_seed(59)
check_mode = os.getenv('PYTORCH_INTRO_CHECK_MODE') == '1'
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
steps = torch.arange(0, 240, dtype=torch.float32)
series = torch.sin(steps / 12) + 0.1 * torch.cos(steps / 3)
window = 16
features = torch.stack([series[i:i + window] for i in range(len(series) - window)])
targets = torch.stack([series[i + window] for i in range(len(series) - window)])
train_size = int(0.8 * len(features))
x_train, x_valid = features[:train_size].to(device), features[train_size:].to(device)
y_train, y_valid = targets[:train_size].unsqueeze(1).to(device), targets[train_size:].unsqueeze(1).to(device)
print(x_train.shape, x_valid.shape)
assert x_train.shape[1] == window
1.2. Train a simple forecaster
A small multilayer perceptron is enough to show the mechanics: input window in, next-step forecast out.
[ ]:
model = nn.Sequential(nn.Linear(window, 32), nn.ReLU(), nn.Linear(32, 1)).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.02)
criterion = nn.MSELoss()
epochs = 5 if check_mode else 80
for epoch in range(epochs):
optimizer.zero_grad(set_to_none=True)
prediction = model(x_train)
loss = criterion(prediction, y_train)
loss.backward()
optimizer.step()
if epoch in {0, epochs - 1}:
valid_loss = criterion(model(x_valid), y_valid).item()
print(epoch, round(loss.item(), 4), round(valid_loss, 4))
with torch.inference_mode():
forecast = model(x_valid[:4])
print(forecast.squeeze(1))
assert forecast.shape == (4, 1)