时序预测实战:从 ARIMA 到时序基础模型

时序预测是销量、流量、运维与金融风控的核心能力,方法栈从经典统计到深度学习不断演进。本文系统讲解时序预测实战全链路:经典统计方法(ARIMA/ETS)、分解与平稳性检验、机器学习方法(LightGBM/XGBoost 特征工程)、深度方法(LSTM/TCN/Transformer/时序基础模型)、多步与滚动预测策略、评估指标(MSE/MAPE/Pinball),以及在线推理与生产部署的完整案例。

时序预测是最古老也最实用的机器学习任务之一:从库存补货、销量预估到服务器容量规划,几乎每个业务都有「预测下一个周期」的需求。方法栈从统计模型一路演进到大模型,但真正决定效果的往往不是模型,而是对问题形态的理解与特征工程。

时序预测问题定义

时序预测的任务:给定历史观测 $y_1, y_2, \dots, y_t$,预测未来 $y_{t+1}, \dots, y_{t+h}$($h$ 为预测水平)。正确的建模第一步是定义问题,它决定了方法选型:

预测水平(Forecast Horizon, h):短期(小时/天)、中期(周/月)、长期(年)。统计模型擅长短期;长期预测依赖趋势分解与领域知识。

预测粒度(Frequency):分钟/小时/日/周/月,粒度影响特征工程与模型结构。

数据形态:单变量(只预测目标序列)vs 多变量(用多个相关序列互作特征);平稳 vs 带趋势/季节;是否含外生变量(促销、节假日、天气)。

问题形态典型场景推荐方法
单变量、短中期简单销量预测ARIMA / ETS
单变量、强季节日度零售季节分解 + ETS
多变量 + 外生销量 + 促销/天气LightGBM / XGBoost
长序列 + 复杂模式能源、流量LSTM / TCN / Transformer
大规模多序列全 SKU 预测全局模型(LightGBM 单模型)
import numpy as np
import pandas as pd

def make_series(n=365*2, period=7, trend=0.02, noise=1.0):
    """构造含趋势与周期的演示序列。"""
    t = np.arange(n)
    y = trend * t + 10 * np.sin(2 * np.pi * t / period)
    y += noise * np.random.randn(n)
    return pd.Series(y, index=pd.date_range("2024-01-01", periods=n, freq="D"))

series = make_series()
print(series.head())

起始铁律:先判断数据形态,再选模型。带强趋势/季节的数据用 ARIMA 之前必须先差分或做季节分解;多变量场景先想清楚「哪些外生变量对未来有预测力」——用不可预测的变量(如未来天气)做特征反而引入噪声。

经典统计方法:ARIMA 与 ETS

ARIMA(Autoregressive Integrated Moving Average) 是单变量时序的统计基准:由自回归项 AR(p)、差分 I(d)、移动平均 MA(q) 组成,记作 ARIMA(p,d,q)。带季节性的扩展是 SARIMA(p,d,q)(P,D,Q,s)。

$$y_t = c + \sum_{i=1}^{p} \phi_i y_{t-i} + \varepsilon_t + \sum_{j=1}^{q} \theta_j \varepsilon_{t-j}$$

ETS(Error-Trend-Seasonality,指数平滑三指数族):把序列分解为误差、趋势、季节三部分,用指数平滑递推。ETS 对趋势与季节的建模比 ARIMA 更直观,适合强季节、可解释优先的场景。

import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.seasonal import seasonal_decompose

# 季节分解:把序列拆成 trend / seasonal / resid
series = make_series()
result = seasonal_decompose(series, model="additive", period=7)
trend, seasonal, resid = result.trend, result.seasonal, result.resid

# ARIMA 拟合与预测
model = ARIMA(series, order=(1, 1, 1), seasonal_order=(1, 1, 1, 7))
fit = model.fit()
forecast = fit.forecast(steps=14)   # 预测未来 14 天
print(forecast)

参数选择:p/q/d 用 ACF/PACF 图目测或用 AIC / BIC 准则网格搜索;季节性周期 s 由业务周期决定(日数据 s=7,月数据 s=12)。auto_arima(pmdarima 库)是工程上快速定参的标准工具。

对比ARIMAETS
建模方式差分 + 自回归/移动平均误差/趋势/季节指数平滑
季节处理SARIMA 显式周期季节分量
可解释性中高
长期预测趋势外推、易发散相对稳健
适用平稳化后的单变量强季节单变量

统计模型的定位是基线与小数据场景:数据量小(<1 年)、单变量、要可解释时,ARIMA/ETS 仍是首选;数据量足够且模式复杂时,统计模型让位给 ML/DL。

分解与平稳性

时序建模前需要诊断两个性质:平稳性(Stationarity) 与季节性(Seasonality)。

平稳性要求序列的均值、方差不随时间变化。趋势与季节性都是非平稳的来源。处理手段:差分(Differencing) 消除趋势,季节差分 消除季节,或对原始序列做变换(log/Box-Cox)。

ADF 检验(Augmented Dickey-Fuller) 是判断平稳性的标准工具:p 值 < 0.05 拒绝非平稳原假设,即序列平稳。

from statsmodels.tsa.stattools import adfuller

def check_stationarity(series: pd.Series) -> bool:
    result = adfuller(series.dropna())
    print(f"ADF p-value = {result[1]:.4f}")
    return result[1] < 0.05   # True 表示平稳

print("原始序列平稳?", check_stationarity(series))
diff = series.diff().dropna()
print("一阶差分后平稳?", check_stationarity(diff))

分解方法论:经典的是 STL(Seasonal-Trend decomposition using Loess),把序列拆为趋势、季节、残差三部分,各部分可分别建模。分解的两个目的:

  1. 诊断:看清趋势强度、季节模式、异常点
  2. 建模:对趋势建模(线性/多项式回归),对季节分量建模(one-hot 或周期特征),对残差建模(平稳模型)
原始序列 ──STL──→ [趋势 Trend] + [季节 Seasonal] + [残差 Resid]
                     │              │              │
                 线性/多项式回归   周期哑变量     ARIMA/平滑
                     └──────────────┴──────────────┘
                             预测 = 三部分求和

分解是把复杂序列「拆成简单部分各击破」的核心思想,也是后续特征工程的基础。任何时序建模前都应先做分解诊断——它能告诉你模型该用多长的季节周期、趋势是否单调、以及是否存在异常点需要剔除。

机器学习方法:LightGBM 特征工程

当有外生变量、多序列、数据量大时,回归式监督学习成为主流:把时序预测改造成「用历史窗口特征预测未来目标」的表格回归问题。

核心是特征工程。LightGBM/XGBoost 这类树模型不需要归一化,但对特征的信息量极度敏感:

特征族特征示例作用
滞后特征(Lag)y_{t-1}, y_{t-7}, y_{t-14}捕获自相关
滚动统计(Rolling)7 日均值、30 日标准差、最大/最小捕获局部水平与波动
时间特征星期几、月份、小时、节假日捕获日历规律
外生变量促销力度、天气、竞品活动驱动变化的因素
差分特征y_t - y_{t-1},周环比捕获变化率
import lightgbm as lgb
import numpy as np
import pandas as pd

def build_features(df: pd.DataFrame, lags=[1, 7, 14], windows=[7, 30]):
    df = df.copy()
    df["dayofweek"] = df.index.dayofweek
    df["month"] = df.index.month
    df["is_holiday"] = df.index.isin(holidays)
    for lag in lags:
        df[f"lag_{lag}"] = df["y"].shift(lag)
    for w in windows:
        df[f"rolling_mean_{w}"] = df["y"].rolling(w).mean()
        df[f"rolling_std_{w}"] = df["y"].rolling(w).std()
    return df.dropna()

train = build_features(df_train)
X, y = train.drop("y", axis=1), train["y"]

model = lgb.LGBMRegressor(
    n_estimators=800, learning_rate=0.05, num_leaves=63,
    subsample=0.9, colsample_bytree=0.9, reg_alpha=0.1,
    random_state=42,
)
model.fit(X, y, eval_set=[(X_val, y_val)],
          callbacks=[lgb.early_stopping(50)])
print("特征重要度 TOP5:", model.feature_importances_)

防数据泄露(Label Leakage) 是时序特征工程的头号陷阱:

  1. 滚动统计必须只用历史数据(shift 后再 rolling,禁止用未来)
  2. 划分训练/验证集不能随机打乱,必须按时间顺序(TimeSeriesSplit)
  3. 特征计算的「窗口边界」要留出预测水平 h 的空隙(gap),否则训练时看到未来的标签
from sklearn.model_selection import TimeSeriesSplit

# 时序交叉验证:保证只学过去、不偷看未来
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, val_idx in tscv.split(X):
    X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
    y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
    # 训练 + 评估(绝不 shuffle)

LightGBM 路线是多数生产时序系统的首选:可解释(特征重要性)、抗过拟合(正则化)、支持外生变量、训练快。深度方法的优势在于长依赖与复杂模式,但训练成本高、可解释性差,通常作为进阶选项。

深度方法:LSTM 与 TCN

深度学习捕捉时序中的复杂非线性模式,适合长序列与多维特征。

LSTM:通过门控机制建模长程依赖。对时序而言通常用序列到序列结构:用过去 $w$ 步的特征窗口预测未来 $h$ 步。

TCN(Temporal Convolutional Network):基于因果卷积(causal convolution)+ 膨胀卷积(dilated convolution),感受野随层数指数增长,并行性好、梯度稳定,训练比 LSTM 快且内存占用低。

import torch
import torch.nn as nn

class TCNBlock(nn.Module):
    """膨胀因果卷积块:dilation 指数扩大感受野。"""
    def __init__(self, in_ch, out_ch, kernel_size=3, dilation=2):
        super().__init__()
        self.conv = nn.Conv1d(
            in_ch, out_ch, kernel_size, dilation=dilation,
            padding=(kernel_size - 1) * dilation,
        )
        self.chomp = nn.ConstantPad1d((0, -(kernel_size - 1) * dilation), 0)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.2)

    def forward(self, x):
        return self.dropout(self.relu(self.chomp(self.conv(x))))

# 3 层 TCN,dilation = 1,2,4 → 感受野 = 1 + 2*(2^0+2^1+2^2)*kernel
对比LSTMTCNTransformer
长程依赖门控记住膨胀感受野全局注意力
并行训练差(串行)好最好
训练速度慢快中
参数量中中高
适用中等长度长序列、效率优先超长序列、多变量

训练要点:时序深度学习必须先做归一化(StandardScaler/MinMaxScaler),且缩放器只用训练集统计量拟合,防止验证/测试泄露;预测输出是未来 $h$ 步时,损失函数用 MAE(对离群更稳健)或 Pinball Loss(分位数)。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_norm = scaler.fit_transform(X_train)   # 只用训练集 fit
X_val_norm = scaler.transform(X_val)           # 验证集复用同一缩放器
X_test_norm = scaler.transform(X_test)

深度方法上线前必须回答一个现实问题:它比 LightGBM 好在哪? 如果数据集不大、模式不复杂,LSTM 很可能打不过特征工程充分的 LightGBM。深度方法的价值场景是:多变量、长依赖、非线性强、数据量百万级以上。

Transformer 与时序基础模型

Transformer 把注意力机制带入时序,关键改进是**通道独立(Channel-Independent)与分块注意力(Patch)**设计——时序的每个变量单独建模,把序列切块后注意力在 patch 间计算,兼顾效率与长程依赖。代表工作包括 PatchTST、TimesNet、Autoformer。

时序基础模型(Time Series Foundation Models) 是 2023 年后的新方向:在大规模多领域时序语料上预训练,通过 Zero-shot / Few-shot 适配新任务。代表包括 TimeGPT、TimesFM、Moirai、Chronos。

# TimesFM(Google)时序基础模型:Zero-shot 预测
import torch
from transformers import TimesFMForPrediction

model = TimesFMForPrediction.from_pretrained("google/timesfm-2.0-500m")
context = torch.tensor([series_values], dtype=torch.float32)
output = model.generate(
    input_ids=context,          # 历史序列(context)
    num_return_sequences=1,
    max_new_tokens=14,          # 预测未来 14 步
    temperature=0.0,            # 确定性预测
)
print(output[0].tolist())
模型类型核心思想适用
LSTM循环门控长程记忆中小数据
TCN卷积膨胀因果卷积长序列高效
PatchTST注意力Patch + 通道独立多变量
TimesNet注意力周期内/周期间二维分解强周期数据
Chronos基础模型token 化时序 + LLM 架构Zero-shot
TimesFM基础模型大规模预训练Zero-shot 基线

时序基础模型的工程定位是零样本基线:接入成本极低、可解释性依赖上下文 prompt,但长期预测精度仍未必超过「充分特征工程 + LightGBM」或专门训练的深度模型。建议作为「快速原型与对照组」,再决定是否专门训练。

多步预测与滚动预测

预测未来 $h$ 步有三条主流策略,它们的偏差-方差权衡完全不同:

直接多步(Direct Multi-step):为每个步长训练一个模型(或一个模型多输出头)。无误差累积,但每个步长模型只能看到「固定窗口」,无法利用已预测的值。实现简单,是生产默认。

递归多步(Recursive / Iterated):用单步模型迭代——把上一步的预测当作下一步的特征。能利用自身预测,但误差会累积,长水平下方差放大。

直接递归混合(DirRec):前几步直接预测、后续递归,在两者间折中。

def recursive_forecast(model, history, steps, exog_future=None):
    """递归预测:上一步预测值作为下一步的滞后特征。"""
    preds = []
    for s in range(steps):
        features = build_last_window(history, s, exog_future)
        yhat = model.predict(features)[0]
        preds.append(yhat)
        history = np.append(history, yhat)   # 用预测值续历史
    return preds

滚动预测(Rolling Forecast) 是生产中最常见的部署形态:每经过一个周期(如每天),用最新数据重新训练/预测,保证模型紧跟最新趋势。评估也应采用滚动方式——回测(Backtesting):在历史区间上滚动训练 + 预测 + 计算误差,模拟真实部署。

策略误差累积模型数量工程复杂度推荐
直接多步无h 个中生产默认
递归有1 个低步长短时
直接+递归部分h 个中高折中
滚动重训取决于模型持续高上线形态

工程经验:短期预测(h ≤ 7)直接多步 + 单模型多输出头最简单;长期预测(h ≥ 30)优先「先拟合趋势 + 季节,再对残差滚动预测」的组合,可显著抑制误差累积。

评估指标

时序评估指标的选择直接影响模型选型方向,且不同业务对不同方向的误差敏感度不同。

指标公式特性适用
MSE$\frac{1}{n}\sum(y-\hat y)^2$放大异常大误差代价高
RMSE$\sqrt{MSE}$与 y 同量纲通用默认
MAE$\frac{1}{n}\sumy-\hat y$
MAPE$\frac{1}{n}\sum\frac{y-\hat y}{
SMAPE对称化 MAPE处理零值零值多的序列
Pinball分位数损失非对称业务决策/库存

MAPE 的陷阱:当真实值 $y$ 接近 0 时,MAPE 会爆炸(除以很小的数),所以零值多的序列用 SMAPE 或 MASE(用基准模型归一化的绝对误差)。

Pinball Loss 是分位数预测的损失函数,直接优化「第 q 分位预测」,让预测结果携带不确定性区间,适合库存(避免缺货与积压的权衡):

$$\text{Pinball}(y, \hat y_q) = \begin{cases} q(y - \hat y_q) & y \ge \hat y_q \ (q-1)(y - \hat y_q) & y < \hat y_q \end{cases}$$

import numpy as np

def pinball_loss(y_true, y_pred, q=0.8):
    diff = y_true - y_pred
    return np.mean(np.where(diff >= 0, q * diff, (q - 1) * diff))

# 输出 80% 分位预测:只关心「以 80% 概率不被超卖的库存量」
inventory_q80 = model.predict(x)   # 模型输出 q=0.8 分位
loss = pinball_loss(y_true, inventory_q80, q=0.8)

指标要与业务决策绑定:库存场景用 Pinball(不对称成本:缺货 vs 积压成本不同);容量规划用 P99(极端负载);销量预测用 MAPE/SMAPE(相对误差)。单一的 MSE 往往掩盖「系统性低估还是高估」的重要信息,建议同时看偏倚(Bias = mean(y - ŷ))。

生产部署与案例

时序模型的生产化比静态 ML 多了「时间维度」的复杂性。完整闭环:

在线推理:每周期滚动预测。常用 Scheduled Retraining(定时重训) 或 Retrain on Drift(漂移触发重训)。推理链路需要考虑模型更新期间的一致性。

# 生产滚动预测服务的简化实现(FastAPI)
from fastapi import FastAPI
import joblib

app = FastAPI()
model = joblib.load("forecast_model.pkl")

@app.post("/forecast")
def forecast(body: dict):
    """输入:最新历史序列 + 外生变量;输出:未来 14 步预测 + 分位数。"""
    history = body["history"]            # 最近窗口的观测值
    exog = body.get("exog", {})          # 促销、节假日等
    point = model.predict(history, exog)          # 点预测
    q_low = model.predict_quantile(history, exog, 0.1)   # 10% 分位
    q_high = model.predict_quantile(history, exog, 0.9)  # 90% 分位
    return {"forecast": point, "interval": [q_low, q_high]}

监控与漂移:时序模型天然会「过期」——趋势突变、季节模式改变(疫情、政策)都会让模型失效。监控体系应包含:

  1. 预测误差监控:实际值出来后对比预测,MAPE 突增告警
  2. 分布漂移:输入特征分布与训练期对比(PSI/KL)
  3. 回滚机制:保留多版本模型,劣化时回滚到最近的好版本
# 生产监控告警配置示例
monitoring:
  metrics:
    - mape_7d: { threshold: 0.25, action: "warn" }
    - mape_30d: { threshold: 0.35, action: "retrain" }
  drift:
    feature_psi: { threshold: 0.25, action: "investigate" }
  retraining:
    schedule: "daily"          # 每日滚动重训
    data_window: "90d"         # 用最近 90 天数据

案例:零售库存补货预测。业务目标:对 5000 个 SKU 预测未来 7 天销量,指导补货,降低缺货率与积压。

环节做法
数据2 年日度销量 + 促销日历 + 节假日 + 天气
方法LightGBM 全局模型(单模型学所有 SKU + SKU ID 特征)
特征滞后 1/7/14 天、7/30 日滚动均值、星期/月份/节假日
输出点预测 + q=0.8 分位(补货量取 80% 分位防缺货)
部署每日 6 点批量预测 5000 SKU,写入库存系统
监控MAPE、缺货率、积压率按月复盘

分位数实战意义:补货量用点预测容易在促销季缺货;用 80% 分位预测补货,虽然略增库存,但缺货损失(丢失顾客)远大于积压成本(仓储),整体收益更高——这就是 Pinball 指标与业务决策挂钩的典型体现。

生产铁律:预测的价值在决策,不在分数。一个 MAPE 稍高但能稳定输出可靠分位区间的模型,远胜一个「平均误差好看但关键时刻离谱」的模型。上线前务必把预测结果接到决策动作上验证收益。

总结

方法族代表方法数据需求优点局限
统计ARIMA / ETS小、单变量可解释、快外生/多变量弱
机器学习LightGBM / XGBoost中、多特征特征灵活、可解释长依赖弱
深度学习LSTM / TCN大、多变量长依赖、非线性训练贵、解释难
注意力PatchTST / TimesNet大全局依赖数据门槛高
基础模型TimesFM / ChronosZero-shot快速基线长期精度存疑

时序预测的方法选型应遵循「由简入繁」:先统计模型建基线,再上特征工程充分的 LightGBM,数据量大、模式复杂再考虑深度方法,最后用滚动回测与多指标评估决定最终方案。预测的成败不取决于模型多么先进,而取决于对业务周期、特征信息与误差结构的理解深度。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 模型压缩:量化、剪枝、蒸馏与部署优化实战
  2. 量化感知训练(QAT)与量化微调:伪量化、STE 与 QLoRA 实战
  3. 模型评估与基准:从分类指标到 LLM-as-Judge