2. 多变量时间序列预测入门

多变量时间序列预测入门 学习卡片

任务说明

本案例使用Transformer模型对多变量气象时间序列进行预测。输入为过去多个时间步的多项气象指标(温度、湿度、气压、风速),输出为未来一个时间步的温度值。通过该案例掌握多变量序列如何组织为Transformer所需的序列-特征格式,以及如何在解码端生成预测。

数据准备

使用开源数据集Jena Climate Dataset(每10分钟采样,共14个气象指标)。为简化演示,选取其中4个变量:T (温度)、p (气压)、rh (相对湿度)、wv (风速)。取连续2000个时间步,前1600步训练,后400步验证。输入窗口长度设为72(即过去12小时),预测未来1步温度。

模型结构

模型采用编码器-解码器架构:编码器接收形状为(batch, seq_len, num_features)的多变量输入,经位置编码后送入标准TransformerEncoder;解码器仅使用一个可学习的起始令牌(start )作为查询,通过TransformerDecoder交叉注意力输出预测值。该设计将多变量历史信息压缩为上下文向量,再映射为单步温度预测。

多变量Transformer预测流程 输入张量 (batch, 72, 4) t-71 … t-1 时刻 4个气象变量 温度 气压 湿度 风速 数值标准化 滑动窗口切片 Transformer编码器 位置编码 + 输入投影 自注意力层 ×2 前馈网络 输出: 上下文特征 (batch, 72, d_model) Transformer解码器 起始令牌 (1, d_model) 交叉注意力 线性输出层 输出: 温度预测 (batch, 1) T_pred 未来1步

代码实现

import torch
import torch.nn as nn
import numpy as np
from torch.utils.data import DataLoader, TensorDataset

# ---------- 1. 数据加载与预处理 ----------
# 模拟Jena数据格式:每行 [温度, 气压, 湿度, 风速]
# 实际使用时替换为真实数据加载
def generate_synthetic_data(n=2000):
    t = np.arange(n) * 0.1
    temp = 20 + 10*np.sin(t/5) + 0.5*np.random.randn(n)
    pressure = 1013 + 5*np.sin(t/20) + 0.3*np.random.randn(n)
    humidity = 60 + 15*np.cos(t/8) + 1.0*np.random.randn(n)
    wind = 5 + 2*np.sin(t/3) + 0.8*np.random.randn(n)
    return np.stack([temp, pressure, humidity, wind], axis=1)

data = generate_synthetic_data()
# 标准化
mean = data.mean(axis=0)
std = data.std(axis=0)
data_norm = (data - mean) / std

# 构造窗口样本
def create_sequences(data, input_len=72, pred_len=1):
    X, y = [], []
    for i in range(len(data) - input_len - pred_len + 1):
        X.append(data[i:i+input_len])
        y.append(data[i+input_len:i+input_len+pred_len, 0])  # 只预测温度
    return np.array(X), np.array(y)

X, y = create_sequences(data_norm)
X = X.astype(np.float32)
y = y.astype(np.float32)

# 划分训练/验证
split = int(len(X) * 0.8)
X_train, X_val = X[:split], X[split:]
y_train, y_val = y[:split], y[split:]

train_dataset = TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train))
val_dataset = TensorDataset(torch.from_numpy(X_val), torch.from_numpy(y_val))
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=64)

# ---------- 2. 定义Transformer模型 ----------
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1).float()
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-np.log(10000.0)/d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe.unsqueeze(0))

    def forward(self, x):
        return x + self.pe[:, :x.size(1)]

class MultiVarTransformer(nn.Module):
    def __init__(self, num_features, d_model=64, nhead=4, num_layers=2, dropout=0.1):
        super().__init__()
        self.input_proj = nn.Linear(num_features, d_model)
        self.pos_encoder = PositionalEncoding(d_model)
        encoder_layer = nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward=128, dropout=dropout, batch_first=True)
        self.encoder = nn.TransformerEncoder(encoder_layer, num_layers)
        decoder_layer = nn.TransformerDecoderLayer(d_model, nhead, dim_feedforward=128, dropout=dropout, batch_first=True)
        self.decoder = nn.TransformerDecoder(decoder_layer, num_layers)
        self.start_ = nn.Parameter(torch.randn(1, 1, d_model))
        self.output_proj = nn.Linear(d_model, 1)

    def forward(self, x):
        # x: (batch, seq_len, num_features)
        src = self.pos_encoder(self.input_proj(x))
        memory = self.encoder(src)
        # 解码器输入:起始令牌 (batch, 1, d_model)
        tgt = self.start_.repeat(x.size(0), 1, 1)
        out = self.decoder(tgt, memory)
        return self.output_proj(out).squeeze(1)  # (batch, 1)

model = MultiVarTransformer(num_features=4)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

# ---------- 3. 训练 ----------
epochs = 30
for epoch in range(epochs):
    model.train()
    train_loss = 0.0
    for X_batch, y_batch in train_loader:
        optimizer.zero_grad()
        pred = model(X_batch)
        loss = criterion(pred, y_batch)
        loss.backward()
        optimizer.step()
        train_loss += loss.item() * X_batch.size(0)
    train_loss /= len(train_dataset)

    # 验证
    model.eval()
    val_loss = 0.0
    with torch.no_grad():
        for X_batch, y_batch in val_loader:
            pred = model(X_batch)
            loss = criterion(pred, y_batch)
            val_loss += loss.item() * X_batch.size(0)
    val_loss /= len(val_dataset)
    if (epoch+1) % 5 == 0:
        print(f'Epoch {epoch+1:3d} | Train Loss: {train_loss:.6f} | Val Loss: {val_loss:.6f}')

# ---------- 4. 预测与反标准化 ----------
model.eval()
with torch.no_grad():
    sample_idx = 0
    sample_x = torch.from_numpy(X_val[sample_idx:sample_idx+1])
    sample_y = y_val[sample_idx]
    pred_norm = model(sample_x).item()
    pred = pred_norm * std[0] + mean[0]
    true = sample_y * std[0] + mean[0]
    print(f'预测温度: {pred:.2f}°C, 真实温度: {true:.2f}°C')

运行与验证

运行方式:安装torch和numpy后直接执行上述脚本。训练过程约需1分钟(CPU即可)。

验证方法:观察训练损失与验证损失是否同步下降并收敛。最终打印的预测值与真实值应处于同一温度范围(示例数据中温度约在10~30°C之间)。

判定标准:验证集MSE低于0.01(标准化后),且预测温度与真实温度误差小于2°C,即视为模型有效学习了多变量到温度的映射关系。