AI时间序列预测完全教程

教程简介

本教程全面讲解AI时间序列预测的核心技术,涵盖ARIMA/Prophet传统方法、LSTM/GRU/TCN深度学习、Informer/PatchTST Transformer预测、TimesFM/Chronos时序大模型、多变量预测、异常检测与突变点识别、特征工程、交叉验证等核心内容,通过电力负荷预测案例帮助开发者掌握时序预测。

AI时间序列预测完全教程

1. 时间序列预测概述与应用场景

时间序列是按时间顺序排列的数据点序列,其中每个数据点与特定时间戳关联。时间序列预测的核心目标是基于历史观测值推断未来的数据趋势。这一任务在工业界有着极为广泛的应用:

  • 能源领域:电力负荷预测、风电出力预测、光伏功率预测
  • 金融领域:股价走势、汇率波动、交易量预估
  • 供应链:库存需求预测、物流流量预估
  • 物联网:设备健康状态监测、传感器数据预测
  • 气象科学:温度、降水、空气质量预测

时间序列数据通常包含四个核心成分:趋势(Trend)季节性(Seasonality)周期性(Cyclicity)噪声(Noise)。理解这些成分对于选择合适的建模方法至关重要。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 生成一个包含多种成分的示例时间序列
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=1000, freq='D')
trend = np.linspace(10, 50, 1000)
seasonal = 10 * np.sin(2 * np.pi * np.arange(1000) / 365.25)
noise = np.random.normal(0, 2, 1000)
values = trend + seasonal + noise

ts = pd.Series(values, index=dates)
ts.plot(title='合成时间序列:趋势 + 季节性 + 噪声', figsize=(12, 4))
plt.ylabel('值')
plt.tight_layout()
plt.show()

2. 传统方法回顾

2.1 ARIMA 模型

ARIMA(自回归积分移动平均模型)是最经典的线性时间序列模型,由三部分组成:AR(p) 自回归项、I(d) 差分阶数、MA(q) 移动平均项。

from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.stattools import adfuller

# ADF平稳性检验
result = adfuller(ts.values)
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')

# 拟合ARIMA模型
model = ARIMA(ts.values, order=(2, 1, 2))
fitted = model.fit()
print(fitted.summary())

# 预测未来30天
forecast = fitted.forecast(steps=30)
print(f'未来30天预测值: {forecast[:5]}...')

2.2 Prophet

由 Meta 开发的 Prophet 擅长处理具有强季节性效应和节假日效应的商业时间序列。其核心思想是将时间序列分解为趋势、季节性和节假日三个可加成分。

from prophet import Prophet

# Prophet要求特定的列名格式
df_prophet = pd.DataFrame({
    'ds': ts.index,
    'y': ts.values
})

model = Prophet(
    yearly_seasonality=True,
    weekly_seasonality=False,
    daily_seasonality=False,
    changepoint_prior_scale=0.05
)
model.fit(df_prophet)

future = model.make_future_dataframe(periods=30)
forecast = model.predict(future)
model.plot(forecast)
plt.title('Prophet 预测结果')
plt.show()

2.3 ETS 模型

ETS(误差-趋势-季节性)模型基于指数平滑原理,通过加法或乘法方式组合各成分。

from statsmodels.tsa.holtwinters import ExponentialSmoothing

ets_model = ExponentialSmoothing(
    ts.values,
    trend='add',
    seasonal='add',
    seasonal_periods=365
).fit()

ets_forecast = ets_model.forecast(30)
print(f'ETS预测均值: {np.mean(ets_forecast):.2f}')

3. 深度学习方法

3.1 LSTM(长短期记忆网络)

LSTM 通过门控机制解决了标准 RNN 的梯度消失问题,能够捕捉长期依赖关系。其核心组件包括遗忘门、输入门和输出门。

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader

class TimeSeriesDataset(Dataset):
    """将时间序列转换为监督学习格式"""
    def __init__(self, series, seq_len, pred_len):
        self.series = torch.FloatTensor(series)
        self.seq_len = seq_len
        self.pred_len = pred_len

    def __len__(self):
        return len(self.series) - self.seq_len - self.pred_len + 1

    def __getitem__(self, idx):
        x = self.series[idx:idx + self.seq_len]
        y = self.series[idx + self.seq_len:idx + self.seq_len + self.pred_len]
        return x.unsqueeze(-1), y

class LSTMForecaster(nn.Module):
    def __init__(self, input_size=1, hidden_size=64, num_layers=2, pred_len=30):
        super().__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers,
                           batch_first=True, dropout=0.2)
        self.fc = nn.Sequential(
            nn.Linear(hidden_size, hidden_size // 2),
            nn.ReLU(),
            nn.Linear(hidden_size // 2, pred_len)
        )

    def forward(self, x):
        lstm_out, _ = self.lstm(x)
        last_hidden = lstm_out[:, -1, :]
        return self.fc(last_hidden)

# 数据准备
seq_len, pred_len = 60, 30
data_normalized = (ts.values - ts.values.mean()) / ts.values.std()

train_data = data_normalized[:800]
test_data = data_normalized[740:]  # 留出overlap

train_dataset = TimeSeriesDataset(train_data, seq_len, pred_len)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

# 训练
model = LSTMForecaster(pred_len=pred_len)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.MSELoss()

for epoch in range(50):
    total_loss = 0
    for x_batch, y_batch in train_loader:
        pred = model(x_batch)
        loss = criterion(pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    if (epoch + 1) % 10 == 0:
        print(f'Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.6f}')

3.2 GRU(门控循环单元)

GRU 是 LSTM 的简化变体,将遗忘门和输入门合并为更新门,参数更少、训练更快,在某些场景下性能与 LSTM 相当。

class GRUForecaster(nn.Module):
    def __init__(self, input_size=1, hidden_size=64, num_layers=2, pred_len=30):
        super().__init__()
        self.gru = nn.GRU(input_size, hidden_size, num_layers,
                          batch_first=True, dropout=0.2)
        self.fc = nn.Linear(hidden_size, pred_len)

    def forward(self, x):
        gru_out, _ = self.gru(x)
        return self.fc(gru_out[:, -1, :])

3.3 TCN(时间卷积网络)

TCN 使用因果膨胀卷积确保不泄露未来信息,同时通过膨胀机制实现大范围感受野,适合并行计算。

class CausalConv1d(nn.Module):
    """因果卷积:确保输出只依赖于当前和过去的时间步"""
    def __init__(self, in_channels, out_channels, kernel_size, dilation):
        super().__init__()
        self.padding = (kernel_size - 1) * dilation
        self.conv = nn.Conv1d(in_channels, out_channels, kernel_size,
                             dilation=dilation, padding=self.padding)

    def forward(self, x):
        out = self.conv(x)
        return out[:, :, :-self.padding] if self.padding > 0 else out

class TCNBlock(nn.Module):
    def __init__(self, in_ch, out_ch, kernel_size=3, dilation=1):
        super().__init__()
        self.conv1 = CausalConv1d(in_ch, out_ch, kernel_size, dilation)
        self.conv2 = CausalConv1d(out_ch, out_ch, kernel_size, dilation * 2)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.2)
        self.residual = nn.Conv1d(in_ch, out_ch, 1) if in_ch != out_ch else nn.Identity()

    def forward(self, x):
        residual = self.residual(x)
        out = self.dropout(self.relu(self.conv1(x)))
        out = self.dropout(self.relu(self.conv2(out)))
        return self.relu(out + residual)

4. Transformer 时间序列预测

4.1 Informer

Informer 针对长序列时间序列预测进行了三项关键优化:ProbSparse 自注意力机制(降低复杂度从 O(L²) 到 O(L·logL))、自注意力蒸馏(逐层减半序列长度)、生成式解码器(一次性输出多步预测)。

class ProbSparseAttention(nn.Module):
    """ProbSparse自注意力:只计算Top-u个最活跃的Query"""
    def __init__(self, d_model, n_heads, factor=5):
        super().__init__()
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        self.factor = factor
        self.W_Q = nn.Linear(d_model, d_model)
        self.W_K = nn.Linear(d_model, d_model)
        self.W_V = nn.Linear(d_model, d_model)
        self.out_proj = nn.Linear(d_model, d_model)

    def forward(self, x):
        B, L, D = x.shape
        Q = self.W_Q(x).view(B, L, self.n_heads, self.d_k).transpose(1, 2)
        K = self.W_K(x).view(B, L, self.n_heads, self.d_k).transpose(1, 2)
        V = self.W_V(x).view(B, L, self.n_heads, self.d_k).transpose(1, 2)

        # 计算采样数量
        u = max(1, int(self.factor * np.log(L)))

        # 计算每个Query的稀疏度度量
        K_mean = K.mean(dim=2, keepdim=True)
        scores = torch.matmul(Q, K.transpose(-2, -1)) / np.sqrt(self.d_k)
        sparsity = scores.max(dim=-1).values - scores.mean(dim=-1)

        # 选择Top-u个最活跃的Query
        _, top_indices = sparsity.topk(u, dim=-1)
        top_indices = top_indices.unsqueeze(-1).expand(-1, -1, -1, self.d_k)
        Q_top = Q.gather(2, top_indices)

        # 计算注意力
        attn = torch.matmul(Q_top, K.transpose(-2, -1)) / np.sqrt(self.d_k)
        attn = torch.softmax(attn, dim=-1)
        out_top = torch.matmul(attn, V)

        # 将结果散回原始位置
        out = V.clone()
        for b in range(B):
            for h in range(self.n_heads):
                out[b, h, top_indices[b, h, :, 0]] = out_top[b, h]

        out = out.transpose(1, 2).contiguous().view(B, L, D)
        return self.out_proj(out)

4.2 Autoformer

Autoformer 引入了自相关机制替代传统注意力,通过频域计算序列间的相似性,同时内嵌了序列分解模块(趋势-季节性分解),使模型能更清晰地捕捉不同频率成分。

4.3 PatchTST

PatchTST 将时间序列分割为固定长度的 patch(类似 ViT 的 token),然后对 patch 序列应用标准 Transformer。这种方法保留了局部语义信息,大幅降低了计算复杂度。

class PatchEmbedding(nn.Module):
    """将时间序列切分为Patch并嵌入"""
    def __init__(self, seq_len, patch_len, d_model):
        super().__init__()
        self.patch_len = patch_len
        self.num_patches = seq_len // patch_len
        self.proj = nn.Linear(patch_len, d_model)
        self.pos_embed = nn.Parameter(torch.randn(1, self.num_patches, d_model))

    def forward(self, x):
        # x: (B, seq_len, 1)
        B = x.shape[0]
        # 切分为patch: (B, num_patches, patch_len)
        patches = x.unfold(1, self.patch_len, self.patch_len).squeeze(-1)
        return self.proj(patches) + self.pos_embed

class PatchTST(nn.Module):
    def __init__(self, seq_len=96, patch_len=16, d_model=128, n_heads=8, n_layers=3, pred_len=30):
        super().__init__()
        self.patch_embed = PatchEmbedding(seq_len, patch_len, d_model)
        encoder_layer = nn.TransformerEncoderLayer(d_model, n_heads, dim_feedforward=d_model*4, batch_first=True)
        self.encoder = nn.TransformerEncoder(encoder_layer, n_layers)
        self.head = nn.Linear(d_model * (seq_len // patch_len), pred_len)

    def forward(self, x):
        patches = self.patch_embed(x)
        encoded = self.encoder(patches)
        return self.head(encoded.reshape(encoded.shape[0], -1))

5. 时序大模型

2024年以来,时间序列预测进入了基础模型时代。这些模型在海量时间序列数据上预训练,展现出强大的零样本和少样本预测能力。

5.1 TimesFM(Google)

TimesFM 采用 Decoder-only Transformer 架构,在超过 1000 亿个真实世界时间点上预训练。其核心创新在于将时间序列值离散化为 token,然后以自回归方式预测。

import timesfm

# 加载预训练的TimesFM模型
tfm = timesfm.TimesFm(
    hparams=timesfm.TimesFmHparams(
        backend='gpu',
        per_core_batch_size=32,
        horizon_len=128,
        num_layers=50,
        context_len=2048,
    ),
    checkpoint=timesfm.TimesFmCheckpoint(
        huggingface_repo_id='google/timesfm-1.0-200m'
    )
)

# 零样本预测
input_ts = [ts.values[:200].tolist()]
forecast = tfm.forecast(
    horizon=30,
    inputs=input_ts,
)
print(f'TimesFM预测结果 shape: {forecast.shape}')

5.2 Amazon Chronos

Chronos 基于 T5 架构,将时间序列值量化为离散 bin,以语言模型的方式进行概率预测。其独特之处在于能够输出完整的预测分布而非点估计。

from chronos import ChronosPipeline

pipeline = ChronosPipeline.from_pretrained(
    'amazon/chronos-t5-large',
    device_map='auto',
    torch_dtype=torch.float32
)

# 概率预测
context = torch.tensor(ts.values[:200]).unsqueeze(0)
forecast = pipeline.predict(
    context=context,
    prediction_length=30,
    num_samples=100,  # 采样100条轨迹
)

# forecast shape: (1, num_samples, prediction_length)
median = np.median(forecast[0].numpy(), axis=0)
lower = np.percentile(forecast[0].numpy(), 10, axis=0)
upper = np.percentile(forecast[0].numpy(), 90, axis=0)
print(f'中位数预测: {median[:5]}')

5.3 Salesforce Moirai

Moirai 采用统一的多变量时间序列 Transformer,支持任意数量的变量和不同频率的数据。它通过混合分布(Student-t、Normal、Negative Binomial)建模不同特征的数据分布。

6. 多变量时间序列预测

多变量时间序列包含多个相互关联的变量,预测时需要同时建模变量内部的时序依赖和变量间的交叉依赖。

class MultiVarTransformer(nn.Module):
    """多变量时间序列Transformer预测器"""
    def __init__(self, n_vars, seq_len, pred_len, d_model=128, n_heads=4, n_layers=2):
        super().__init__()
        self.var_embed = nn.Linear(n_vars, d_model)
        self.pos_embed = nn.Parameter(torch.randn(1, seq_len, d_model))
        encoder_layer = nn.TransformerEncoderLayer(
            d_model, n_heads, dim_feedforward=d_model*4, batch_first=True
        )
        self.encoder = nn.TransformerEncoder(encoder_layer, n_layers)
        self.head = nn.Linear(d_model * seq_len, pred_len * n_vars)
        self.n_vars = n_vars
        self.pred_len = pred_len

    def forward(self, x):
        # x: (B, seq_len, n_vars)
        x = self.var_embed(x) + self.pos_embed
        encoded = self.encoder(x)
        out = self.head(encoded.reshape(encoded.shape[0], -1))
        return out.reshape(-1, self.pred_len, self.n_vars)

# 示例:多变量预测
n_vars = 5
multi_data = np.random.randn(500, n_vars).astype(np.float32)
model = MultiVarTransformer(n_vars=n_vars, seq_len=60, pred_len=30)
x = torch.tensor(multi_data[:60]).unsqueeze(0)
pred = model(x)
print(f'多变量预测输出 shape: {pred.shape}')  # (1, 30, 5)

变量间关系建模可以采用图神经网络,将每个变量视为图节点,通过学习自适应邻接矩阵来捕捉变量间的动态相关性。

7. 异常检测与突变点识别

7.1 基于重建误差的异常检测

使用自编码器学习正常模式,重建误差大的时间点即为潜在异常。

class TSAutoEncoder(nn.Module):
    def __init__(self, seq_len, n_features, latent_dim=16):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Linear(seq_len * n_features, 64),
            nn.ReLU(),
            nn.Linear(64, latent_dim)
        )
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, 64),
            nn.ReLU(),
            nn.Linear(64, seq_len * n_features)
        )
        self.seq_len = seq_len
        self.n_features = n_features

    def forward(self, x):
        B = x.shape[0]
        z = self.encoder(x.reshape(B, -1))
        out = self.decoder(z)
        return out.reshape(B, self.seq_len, self.n_features)

# 异常检测流程
def detect_anomalies(model, data, seq_len, threshold_percentile=95):
    model.eval()
    errors = []
    with torch.no_grad():
        for i in range(len(data) - seq_len):
            x = torch.tensor(data[i:i+seq_len]).unsqueeze(0).float()
            recon = model(x)
            mse = ((x - recon) ** 2).mean().item()
            errors.append(mse)
    threshold = np.percentile(errors, threshold_percentile)
    anomalies = np.where(np.array(errors) > threshold)[0]
    return anomalies, errors, threshold

7.2 突变点检测

突变点(Changepepoint)是时间序列统计特性发生显著变化的时间点。常用方法包括 CUSUM、Bayesian Online Changepoint Detection(BOCPD)等。

def cusum_detect(data, threshold=5, drift=0):
    """CUSUM累积和突变点检测"""
    mean = np.mean(data)
    std = np.std(data)
    normalized = (data - mean) / std

    pos_cusum = np.zeros(len(data))
    neg_cusum = np.zeros(len(data))
    changepoints = []

    for i in range(1, len(data)):
        pos_cusum[i] = max(0, pos_cusum[i-1] + normalized[i] - drift)
        neg_cusum[i] = max(0, neg_cusum[i-1] - normalized[i] - drift)
        if pos_cusum[i] > threshold or neg_cusum[i] > threshold:
            changepoints.append(i)
            pos_cusum[i] = 0
            neg_cusum[i] = 0

    return changepoints

8. 特征工程与数据预处理

优秀的特征工程往往比模型选择更重要。以下是时间序列预测中常用的技术:

def create_time_features(df, freq='D'):
    """从时间索引中提取丰富的时间特征"""
    features = pd.DataFrame(index=df.index)
    features['hour'] = df.index.hour if hasattr(df.index, 'hour') else 0
    features['dayofweek'] = df.index.dayofweek
    features['dayofmonth'] = df.index.day
    features['month'] = df.index.month
    features['quarter'] = df.index.quarter
    features['year'] = df.index.year
    features['dayofyear'] = df.index.dayofyear
    # 周末标记
    features['is_weekend'] = (features['dayofweek'] >= 5).astype(int)
    # 正弦余弦编码(捕捉周期性)
    features['month_sin'] = np.sin(2 * np.pi * features['month'] / 12)
    features['month_cos'] = np.cos(2 * np.pi * features['month'] / 12)
    features['dow_sin'] = np.sin(2 * np.pi * features['dayofweek'] / 7)
    features['dow_cos'] = np.cos(2 * np.pi * features['dayofweek'] / 7)
    return features

def add_lag_features(series, lags=[1, 7, 14, 28]):
    """添加滞后特征"""
    df = pd.DataFrame({'value': series})
    for lag in lags:
        df[f'lag_{lag}'] = df['value'].shift(lag)
    # 滚动统计特征
    for window in [7, 14, 30]:
        df[f'rolling_mean_{window}'] = df['value'].rolling(window).mean()
        df[f'rolling_std_{window}'] = df['value'].rolling(window).std()
    return df.dropna()

关键预处理步骤

  • 缺失值处理:前向填充、线性插值、KNN 插值
  • 平稳化:差分、对数变换、Box-Cox 变换
  • 归一化:Z-score 标准化、Min-Max 归一化(注意要在训练集上 fit,避免数据泄露)
  • 异常值处理:基于 IQR 或 Z-score 的截断或替换

9. 模型评估与交叉验证

时间序列数据具有时间顺序,不能随机划分训练集和测试集。需要使用时间序列交叉验证(滚动窗口或扩展窗口)。

from sklearn.metrics import mean_absolute_error, mean_squared_error

def time_series_cross_validate(model_cls, data, n_splits=5,
                                train_ratio=0.6, seq_len=60, pred_len=30):
    """时间序列滚动窗口交叉验证"""
    n = len(data)
    step = (n - int(n * train_ratio)) // n_splits
    metrics = []

    for i in range(n_splits):
        train_end = int(n * train_ratio) + i * step
        test_start = train_end - seq_len
        test_end = min(train_end + pred_len, n)

        train_data = data[:train_end]
        test_data = data[test_start:test_end]

        # 训练与预测(简化示例)
        # model = model_cls(...)
        # model.fit(train_data)
        # pred = model.predict(test_data[-seq_len:])

        # 计算指标
        # mae = mean_absolute_error(actual, pred)
        # rmse = np.sqrt(mean_squared_error(actual, pred))
        # metrics.append({'fold': i, 'mae': mae, 'rmse': rmse})

    return metrics

def evaluate_forecast(actual, predicted):
    """全面的预测评估指标"""
    mae = mean_absolute_error(actual, predicted)
    rmse = np.sqrt(mean_squared_error(actual, predicted))
    mape = np.mean(np.abs((actual - predicted) / (actual + 1e-8))) * 100
    # 对称MAPE
    smape = np.mean(2 * np.abs(actual - predicted) / (np.abs(actual) + np.abs(predicted) + 1e-8)) * 100
    return {'MAE': mae, 'RMSE': rmse, 'MAPE': mape, 'SMAPE': smape}

常用评估指标

  • MAE:平均绝对误差,对异常值不敏感
  • RMSE:均方根误差,对大误差更敏感
  • MAPE:平均绝对百分比误差,直观但接近零值时不稳定
  • SMAPE:对称 MAPE,解决 MAPE 的不对称问题

10. 实战案例:电力负荷预测系统

以下构建一个完整的电力负荷预测系统,整合数据处理、特征工程、模型训练和评估。

import torch
import torch.nn as nn
import numpy as np
import pandas as pd
from torch.utils.data import Dataset, DataLoader

class PowerLoadPredictor(nn.Module):
    """结合LSTM和注意力机制的电力负荷预测模型"""
    def __init__(self, n_features, hidden_size=128, n_layers=2, pred_len=24):
        super().__init__()
        self.lstm = nn.LSTM(n_features, hidden_size, n_layers,
                           batch_first=True, dropout=0.3)
        self.attention = nn.MultiheadAttention(hidden_size, num_heads=4, batch_first=True)
        self.norm = nn.LayerNorm(hidden_size)
        self.fc = nn.Sequential(
            nn.Linear(hidden_size, 64),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(64, pred_len)
        )

    def forward(self, x):
        lstm_out, _ = self.lstm(x)
        attn_out, weights = self.attention(lstm_out, lstm_out, lstm_out)
        out = self.norm(lstm_out + attn_out)
        return self.fc(out[:, -1, :]), weights

class PowerLoadPipeline:
    """电力负荷预测完整流水线"""
    def __init__(self, seq_len=168, pred_len=24):  # 7天历史 -> 预测24小时
        self.seq_len = seq_len
        self.pred_len = pred_len
        self.model = None

    def preprocess(self, raw_data, weather_data=None):
        """数据预处理与特征构建"""
        df = pd.DataFrame({'load': raw_data})
        # 时间特征
        df['hour'] = df.index.hour
        df['dow'] = df.index.dayofweek
        df['is_weekend'] = (df['dow'] >= 5).astype(int)
        # 滞后特征
        for lag in [24, 48, 168]:
            df[f'lag_{lag}'] = df['load'].shift(lag)
        # 滚动特征
        df['rolling_24h_mean'] = df['load'].rolling(24).mean()
        df['rolling_24h_std'] = df['load'].rolling(24).std()
        if weather_data is not None:
            df = df.join(weather_data)
        return df.dropna()

    def train(self, train_data, epochs=100, lr=1e-3):
        """训练模型"""
        n_features = train_data.shape[1]
        self.model = PowerLoadPredictor(n_features, pred_len=self.pred_len)
        optimizer = torch.optim.AdamW(self.model.parameters(), lr=lr, weight_decay=1e-4)
        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
        criterion = nn.HuberLoss()

        for epoch in range(epochs):
            self.model.train()
            total_loss = 0
            for i in range(len(train_data) - self.seq_len - self.pred_len):
                x = torch.tensor(train_data[i:i+self.seq_len]).unsqueeze(0).float()
                y = torch.tensor(train_data[i+self.seq_len:i+self.seq_len+self.pred_len, 0]).unsqueeze(0).float()
                pred, _ = self.model(x)
                loss = criterion(pred, y)
                optimizer.zero_grad()
                loss.backward()
                torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
                optimizer.step()
                total_loss += loss.item()
            scheduler.step()
            if (epoch + 1) % 20 == 0:
                print(f'Epoch {epoch+1}, Avg Loss: {total_loss/(len(train_data)-self.seq_len-self.pred_len):.6f}')

    def predict(self, recent_data):
        """单次预测"""
        self.model.eval()
        with torch.no_grad():
            x = torch.tensor(recent_data[-self.seq_len:]).unsqueeze(0).float()
            pred, attn_weights = self.model(x)
        return pred.squeeze().numpy(), attn_weights

# 使用示例
# pipeline = PowerLoadPipeline(seq_len=168, pred_len=24)
# processed = pipeline.preprocess(hourly_load_series)
# pipeline.train(processed.values, epochs=100)
# forecast, weights = pipeline.predict(processed.values)

11. 生产部署与在线学习

11.1 模型服务化

from fastapi import FastAPI
from pydantic import BaseModel
import torch
import numpy as np

app = FastAPI()
model_cache = {}

class ForecastRequest(BaseModel):
    series: list[float]
    horizon: int = 24

class ForecastResponse(BaseModel):
    predictions: list[float]
    confidence_lower: list[float]
    confidence_upper: list[float]

@app.post('/forecast', response_model=ForecastResponse)
async def forecast(request: ForecastRequest):
    model = model_cache.get('power_load')
    series = np.array(request.series)

    # 预处理
    mean, std = series.mean(), series.std()
    normalized = (series - mean) / std

    # 推理
    x = torch.tensor(normalized).unsqueeze(0).unsqueeze(-1).float()
    with torch.no_grad():
        pred, _ = model(x)
    pred_np = pred.squeeze().numpy() * std + mean

    # 简单置信区间(基于历史残差)
    residual_std = np.std(series[-168:]) * 0.1
    return ForecastResponse(
        predictions=pred_np.tolist(),
        confidence_lower=(pred_np - 1.96 * residual_std).tolist(),
        confidence_upper=(pred_np + 1.96 * residual_std).tolist()
    )

11.2 在线学习策略

在生产环境中,数据分布会随时间漂移(concept drift),模型需要持续更新。推荐策略:

  • 滑动窗口重训练:定期(如每天)用最近 N 天数据全量重训练
  • 增量微调:以较小学习率在新数据上继续训练现有模型
  • 模型集成:维护多个不同时间窗口训练的模型,动态加权融合
  • 漂移检测:监控预测误差分布,当误差显著增大时触发重训练
class OnlineLearner:
    """在线学习管理器"""
    def __init__(self, model, lr=1e-4, drift_threshold=2.0):
        self.model = model
        self.optimizer = torch.optim.Adam(model.parameters(), lr=lr)
        self.drift_threshold = drift_threshold
        self.recent_errors = []

    def update(self, x, y_true):
        """用新数据增量更新模型"""
        self.model.train()
        pred, _ = self.model(x.unsqueeze(0))
        loss = nn.MSELoss()(pred, y_true.unsqueeze(0))
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
        return loss.item()

    def check_drift(self, error):
        """检测概念漂移"""
        self.recent_errors.append(error)
        if len(self.recent_errors) > 100:
            self.recent_errors.pop(0)
        if len(self.recent_errors) >= 50:
            baseline = np.mean(self.recent_errors[:len(self.recent_errors)//2])
            recent = np.mean(self.recent_errors[len(self.recent_errors)//2:])
            if recent > baseline * self.drift_threshold:
                return True
        return False

11.3 监控指标

生产部署需要监控的关键指标:

  • 预测精度滑动窗口:MAE/MAPE 的 24h/7d 滚动值
  • 预测延迟:P50/P95/P99 推理耗时
  • 数据质量:缺失率、异常值比例、数据新鲜度
  • 模型漂移:预测残差分布的变化

时间序列预测是一个不断演进的领域。从经典的统计方法到深度学习,再到如今的基础模型,每一代方法都有其适用场景。在实际项目中,建议先用传统方法建立基线,再逐步引入复杂模型,始终以业务指标(而非纯学术指标)衡量模型价值。

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录