时间序列基础:过去为何能预测未来


文档摘要

时间序列基础:过去为何能预测未来 本节摘要:过去的业绩确实能预测未来的结果——前提是你先检查平稳性。时间序列(Time Series)打破标准 ML 依赖的假设:样本不独立(今天的股价依赖昨天的),分布不恒定(三月和十二月的销量长得不一样)。你抓起标准 ML 工具箱——随机训练/测试切分、交叉验证——每一步都是错的。一个在随机交叉验证下拿 95% 准确率的模型,在恰当的按时间评估下可能只有 55%。这差别不是技术细节,而是「纸面上能跑」与「生产里能用」的差别。

时间序列基础:过去为何能预测未来

本节摘要:过去的业绩确实能预测未来的结果——前提是你先检查平稳性。时间序列(Time Series)打破标准 ML 依赖的假设:样本不独立(今天的股价依赖昨天的),分布不恒定(三月和十二月的销量长得不一样)。你抓起标准 ML 工具箱——随机训练/测试切分、交叉验证——每一步都是错的。一个在随机交叉验证下拿 95% 准确率的模型,在恰当的按时间评估下可能只有 55%。这差别不是技术细节,而是「纸面上能跑」与「生产里能用」的差别。本节讲透时间数据为何不同:时间序列是趋势、季节性、残差的组合;平稳性(Stationarity)为何是多数预测方法的前提,如何用差分(Differencing)修复;自相关如何告诉你该用多少滞后特征;滞后特征如何把单列时间序列变成标准监督学习问题;以及为何随机切分在时间序列上是非法的、必须用滚动向前验证(Walk-Forward Validation)。我们会从零实现滞后特征生成、滚动向前切分、自相关函数、简单自回归模型,并对比 ARIMA。

学习目标

阅读完本节,你应当能够:

  1. 把时间序列分解为趋势、季节性、残差分量,并检验平稳性。
  2. 实现滞后特征和滚动统计,把时间序列转成监督学习问题。
  3. 构建滚动向前验证框架,防止未来数据泄漏进训练。
  4. 解释为何随机训练/测试切分对时间序列非法,并演示它相对于恰当时间切分的性能差距。

一、问题与直觉

你有按时间排序的数据。日销量、小时温度、每分钟 CPU 使用率、周股价。你想预测下一个值、下一周、下一季度。

你抓起标准 ML 工具箱:随机训练/测试切分、交叉验证、特征矩阵输入、预测输出。每一步都是错的。

时间序列打破标准 ML 依赖的假设。样本不独立——今天的温度依赖昨天的。随机切分把未来信息泄漏进过去。在回测里好看的特在生产里失败,因为它们依赖随时间漂移的模式。

一个在随机交叉验证下拿 95% 准确率的模型,在恰当按时间评估下可能只有 55%。差别不是技术细节,而是「纸面能跑」与「生产能用」的差别。

时间序列为何不同

标准 ML 假设 i.i.d.——独立同分布。每个样本从同一分布独立抽取。时间序列两者都违反:

  • 不独立。 今天的股价依赖昨天的。本周销量与上周相关。
  • 不同分布。 分布随时间漂移。十二月销量长得跟三月不一样。

这些违反不是小事。它们改变你如何构造特征、如何评估模型、哪些算法有效。

标准 ML 里样本可互换,打乱它们什么也不改变。时间序列里顺序就是一切,打乱摧毁信号。

时间序列的分量

每个时间序列是以下成分的组合:

  • 趋势(Trend):长期方向。营收年增 10%。全球气温上升。
  • 季节性(Seasonality):固定间隔的重复模式。零售十二月销量飙升。空调使用七月达峰。
  • 残差(Residual):移除趋势和季节性后的剩余。若残差像白噪声,分解就抓住了信号。

平稳性

时间序列在统计性质(均值、方差、自相关)不随时间变化时是平稳的(Stationary)。多数预测方法假设平稳性。

为何重要: 非平稳序列的均值会漂移。一月数据训练的模型学到的均值,与二月将显示的不同。它会系统性出错。

如何检查: 在窗口上算滚动均值和滚动标准差。若它们漂移,序列非平稳。

如何修复: 差分(Differencing)。不建模原始值,建模相邻值之差:

diff[t] = value[t] - value[t-1]

若一轮差分未使序列平稳,再差分一次(二阶差分)。多数真实序列最多需要两轮。

例子:

原始序列:[100, 102, 106, 112, 120]
一阶差分:[2, 4, 6, 8](仍向上趋势)
二阶差分:[2, 2, 2](常数——平稳)

原始序列有二次趋势。一阶差分把它变线性。二阶差分把它变平。实践中很少需要超过两轮。

正式检验: 增广迪基-富勒检验(Augmented Dickey-Fuller, ADF)是平稳性的标准统计检验。零假设是「序列非平稳」。p 值低于 0.05 意味着你可以拒绝零假设、认定平稳。我们不从零实现 ADF(它需要渐近分布表),但代码里的滚动统计方法给了一个实用的可视化检查。

自相关

自相关(Autocorrelation)度量时间 t 的值与时间 t-k(k 步之前)的值相关性多大。自相关函数(ACF)对每个滞后 k 画出这个相关性。

ACF 告诉你:

  • 序列记得多远。若 ACF 在滞后 5 后降到零,5 步之前的值无关。
  • 是否有季节性。若 ACF 在滞后 12 处尖峰(月数据),存在年度季节性。
  • 该造多少滞后特征。用 ACF 变可忽略之前的滞后。

PACF(偏自相关函数) 移除间接相关。若今天与 3 天前相关只是因为两者都与昨天相关,则滞后 3 的 PACF 会是零而 ACF 不是。

滞后特征:把时间序列变监督学习

标准 ML 模型需要特征矩阵 X 和目标 y。时间序列给你一列值。桥梁是滞后特征(Lag Features)。

取序列 [10, 12, 14, 13, 15] 造滞后 1 和滞后 2 特征:

lag_2 lag_1 target
10 12 14
12 14 13
14 13 15

现在你有一个标准回归问题。任何 ML 模型(线性回归、随机森林、梯度提升)都能从滞后预测目标。

你还可以工程的其他特征:

  • 滚动统计:最近 k 个值的均值、标准差、最小、最大。
  • 日历特征:星期几、月份、是否节假日、是否周末。
  • 差分值:与前一步的变化。
  • 扩展统计:累计均值、累计和。
  • 比率特征:当前值 / 滚动均值(离近期均值多远)。
  • 交互特征:lag_1 * 星期几(星期对动量的影响)。

多少滞后? 用自相关函数。若 ACF 显著到滞后 10,至少用 10 个滞后。若有周季节性,包含滞后 7(可能还有 14)。更多滞后给模型更多历史,但也更多特征要拟合,增加过拟合风险。

目标对齐陷阱。 造滞后特征时,目标必须是时间 t 的值,所有特征必须用时间 t-1 或更早的值。如果你不小心把时间 t 的值当特征,你有了一个完美预测器——和一个完全无用的模型。这是时间序列特征工程最常见的 bug。

滚动向前验证

这是本节最重要的概念。标准 k 折交叉验证随机把样本分到训练和测试。对时间序列,这泄漏未来信息。

滚动向前验证:

  1. 用到时间 t 的数据训练。
  2. 在时间 t+1 预测(或多步预测 t+1 到 t+k)。
  3. 把窗口向前滑。
  4. 重复。

每个测试折只含所有训练数据之后的数据。无未来泄漏。这给你模型部署后表现的诚实估计。

扩展窗口(Expanding Window)用所有历史数据训练(窗口增长)。滑动窗口(Sliding Window)用固定大小训练窗口(窗口滑动)。当你相信老数据仍相关时用扩展;当世界变化、老数据有害时用滑动。

ARIMA 直觉

ARIMA 是经典时间序列模型。它有三个分量:

  • AR(自回归,Autoregressive):从过去值预测。AR(p) 用最近 p 个值。
  • I(积分,Integrated):差分以达平稳。I(d) 施加 d 轮差分。
  • MA(移动平均,Moving Average):从过去预测误差预测。MA(q) 用最近 q 个误差。

ARIMA(p, d, q) 组合三者。你基于 ACF/PACF 分析或自动搜索(auto-ARIMA)选 p、d、q。

我们不从零实现 ARIMA——它需要超出本课范围的数值优化。关键是理解每个分量的作用,这样你能解读 ARIMA 结果并知道何时用它。

各方法何时用

方法 最适合 处理季节性 处理外部特征
滞后特征 + ML 有许多外部特征的表格 配日历特征
ARIMA 单变量序列,短期 SARIMA 变体 否(ARIMAX 有限)
指数平滑 简单趋势 + 季节性 是(Holt-Winters)
Prophet 商业预测,节假日 是(傅里叶项) 有限
神经网络(LSTM、Transformer) 长序列,多序列 学习得到

对多数实际问题,滞后特征 + 梯度提升是最强起点。它天然处理外部特征、不要求平稳、易调试。

预测范围与策略

单步预测预测下一个时间步。多步预测预测多个步。有三种策略:

递归(迭代):预测下一步,把预测当输入再预测下下步。简单但误差累积——每个预测用前一个预测,错误叠加。

直接:为每个范围训一个单独模型。模型 1 预测 t+1,模型 5 预测 t+5。无误差累积,但每个模型训练样本更少且不共享信息。

多输出:训一个模型同时输出所有范围。跨范围共享信息,但需要支持多输出的模型(或自定义损失)。

对多数实际问题,短范围(1~5 步)从递归开始,长范围用直接。

时间序列常见错误

错误 为何发生 如何修
随机训练/测试切分 标准 ML 习惯 用滚动向前或时间切分
用未来特征 时间 t 的特征被误纳入 审计每个特征的时间对齐
过拟合季节性 模型背日历模式 测试集留出一整个季节循环
忽略尺度变化 营收翻倍但模式不变 建模百分比变化而非绝对值
滞后特征太多 「历史越多越好」 用 ACF 定相关滞后
不差分 「模型会搞定」 树模型能处理趋势;线性模型需平稳

二、从零实现

code/time_series.py 从零实现核心构件。

滞后特征生成器

def make_lag_features(series, n_lags): n = len(series) X = np.full((n, n_lags), np.nan) for lag in range(1, n_lags + 1): X[lag:, lag - 1] = series[:-lag] valid = ~np.isnan(X).any(axis=1) return X[valid], series[valid]

这把 1D 序列转成特征矩阵,每行有最近 n_lags 个值作为特征、当前值作为目标。

滚动向前交叉验证

def walk_forward_split(n_samples, n_splits=5, min_train=50): assert min_train < n_samples, "min_train 必须小于 n_samples" step = max(1, (n_samples - min_train) // n_splits) for i in range(n_splits): train_end = min_train + i * step test_end = min(train_end + step, n_samples) if train_end >= n_samples: break yield slice(0, train_end), slice(train_end, test_end)

每个切分确保训练数据严格在测试数据之前。训练窗口随每折扩展。

简单自回归模型

纯 AR 模型就是滞后特征上的线性回归:

class SimpleAR: def __init__(self, n_lags=5): self.n_lags = n_lags self.weights = None self.bias = None def fit(self, series): X, y = make_lag_features(series, self.n_lags) # 用正规方程求解 X_b = np.column_stack([np.ones(len(X)), X]) theta = np.linalg.lstsq(X_b, y, rcond=None)[0] self.bias = theta[0] self.weights = theta[1:] return self

这在概念上与第 02 节的线性回归完全相同,只是应用到同一变量的时间滞后版本上。

平稳性检查

代码算滚动统计以可视化和数值化评估平稳性:

def check_stationarity(series, window=50): rolling_mean = np.array([ series[max(0, i - window):i].mean() for i in range(1, len(series) + 1) ]) rolling_std = np.array([ series[max(0, i - window):i].std() for i in range(1, len(series) + 1) ]) return rolling_mean, rolling_std

若滚动均值漂移或滚动标准差变化,序列非平稳。施差分再检查。

代码还通过比较序列前半和后半检查平稳性。若均值差超过半个标准差,或方差比超过 2 倍,序列被标记为非平稳。

自相关

def autocorrelation(series, max_lag=20): n = len(series) mean = series.mean() var = series.var() acf = np.zeros(max_lag + 1) for k in range(max_lag + 1): cov = np.mean((series[:n-k] - mean) * (series[k:] - mean)) acf[k] = cov / var if var > 0 else 0 return acf

三、框架对比

用 sklearn,你直接把滞后特征喂给任意回归器:

from sklearn.linear_model import Ridge from sklearn.ensemble import GradientBoostingRegressor X, y = make_lag_features(series, n_lags=10) for train_idx, test_idx in walk_forward_split(len(X)): model = Ridge(alpha=1.0) model.fit(X[train_idx], y[train_idx]) predictions = model.predict(X[test_idx])

ARIMA 用 statsmodels

from statsmodels.tsa.arima.model import ARIMA model = ARIMA(train_series, order=(5, 1, 2)) fitted = model.fit() forecast = fitted.forecast(steps=30)

code/time_series.py 演示两种方法并用滚动向前验证对比。

sklearn TimeSeriesSplit

sklearn 提供 TimeSeriesSplit,实现滚动向前验证:

from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for train_index, test_index in tscv.split(X): X_train, X_test = X[train_index], X[test_index] y_train, y_test = y[train_index], y[test_index] model.fit(X_train, y_train) score = model.score(X_test, y_test)

这与我们从零的 walk_forward_split 等价,但集成进了 sklearn 的交叉验证框架。可与 cross_val_score 配合:

from sklearn.model_selection import cross_val_score scores = cross_val_score(model, X, y, cv=TimeSeriesSplit(n_splits=5)) print(f"平均分: {scores.mean():.4f} +/- {scores.std():.4f}")

评估指标

时间序列预测用回归指标,但带时间感知的语境:

  • MAE(平均绝对误差):|y_true - y_pred| 的平均。易在原始单位解读。「平均预测偏差 3.2 度。」
  • RMSE(均方根误差):均方误差的平方根。比 MAE 更惩罚大误差。当大误差比许多小误差更糟时用。
  • MAPE(平均绝对百分比误差):|误差 / 真值| * 100 的平均。尺度无关,适合跨序列比较。但真值为零时未定义。
  • 朴素基线对比:永远与简单基线比。季节性朴素基线预测一个周期前的值(昨天、上周)。若你的模型打不过朴素,有问题。

滚动特征

代码演示在滞后特征上加滚动统计(7 天和 14 天窗口的均值、标准差、最小、最大)。这些给模型近期趋势和波动信息,是滞后特征单独抓不到的。

例如,若滚动均值在升,提示上升趋势;若滚动标准差在增,提示波动加剧。这些是树模型能学、线性模型不能的模式。

维度 从零实现 sklearn / statsmodels
灵活 可看清 ACF、差分内部 TimeSeriesSplit 集成 CV 框架
模型 简单 AR(线性回归) 任意回归器 + ARIMA
适用 理解原理 生产

必须打败的基线

建模前先立基线:

  1. 末值(持久性):预测明天等于今天。对许多序列,这出奇难打败。
  2. 季节性朴素:预测今天等于上周同一天(或去年同一天)。若你的模型打不过这个,它没学到季节性之外的任何有用模式。
  3. 移动平均:预测最近 k 个值的平均。平滑噪声但抓不到突变。

若你的花哨 ML 模型输给季节性朴素基线,你有 bug。最常见原因:特征里的未来泄漏、错误的评估方法、或序列确实随机不可预测。

实战贴士

  1. 先画图。 建模前先画原始序列,看趋势、季节性、离群点、结构性断点(行为的突变)。30 秒目视检查常胜过一小时自动分析。

  2. 先差分,后建模。 若序列有明显趋势,造滞后特征前先差分。树模型能处理趋势,但线性模型不能,差分从无坏处。

  3. 至少留一整个季节循环。 若有周季节性,测试集至少一整周。月度则至少一整月。否则你无法评估模型是否抓到季节模式。

  4. 生产中监控。 时间序列模型随世界变化而退化。滚动跟踪预测误差。误差开始增长时,用近期数据重训。

  5. 当心制度变化(Regime Change)。 用疫情前数据训的模型预测不了疫情后行为。把已知制度变化的指示符加为特征,或用会遗忘老数据的滑动窗口。

  6. 对偏斜序列取对数。 营收、价格、计数常右偏。取对数稳定方差、把乘法模式变加法(线性模型能处理)。在对数空间预测,再指数化回原始单位。

四、可复用产物

本节产出:

  • outputs/prompt-time-series-advisor.md——一个帮你框定时间序列问题的提示词。
  • code/time_series.py——滞后特征、滚动向前验证、AR 模型、平稳性检查。

五、练习

  1. 平稳性实验:生成带线性趋势的序列,用滚动统计检查平稳性,施一阶差分再检查。二次趋势需要几轮差分?

  2. 滞后选择:在季节性序列(周期=7)上算 ACF。哪些滞后自相关最高?只用这些滞后造特征(不用连续滞后)。对比用滞后 1 到 7,准确率有提升吗?

  3. 滚动向前 vs 随机切分:在滞后特征上训 Ridge 回归。分别用随机 80/20 切分和滚动向前验证评估。随机切分高估了多少性能?

  4. 特征工程:在滞后特征上加滚动均值(窗口=7)、滚动标准差(窗口=7)、星期几特征。用滚动向前验证对比有无这些额外特征的准确率。

  5. 多步预测:修改 AR 模型预测 5 步而非 1 步。对比两种策略:(a) 预测一步、把预测当输入再预测下一步(递归);(b) 为每个范围训单独模型(直接)。哪个更准?

本节要点回顾

  1. 时间序列非 i.i.d.:样本不独立(今天依赖昨天)、分布不恒定(三月和十二月不同),这改变特征构造、模型评估、算法选择。
  2. 三大分量:趋势(长期方向)、季节性(固定间隔重复)、残差(移除前两者后的随机变化)。
  3. 平稳性是多数预测方法的前提:非平稳序列均值漂移、模型系统性出错;用差分 diff[t]=value[t]-value[t-1] 修复,多数序列最多两轮。
  4. 自相关告诉你用多少滞后:ACF 显示序列记得多远、是否有季节性(如月数据滞后 12 尖峰)、该造多少滞后特征。
  5. 滞后特征是通往监督学习的桥梁:把单列序列变成「最近 k 个值预测当前值」的标准回归问题。
  6. 目标对齐陷阱:目标必须是时间 t 的值,所有特征必须用 t-1 或更早,误纳入时间 t 的值就是完美预测器+无用模型。
  7. 随机切分非法、滚动向前才对:每个测试折只含所有训练数据之后的数据,无未来泄漏;扩展窗口用所有历史,滑动窗口用固定大小。
  8. 必须打败基线:末值(持久性)、季节性朴素、移动平均;输给季节性朴素意味着有 bug(未来泄漏、错误评估、或序列真随机)。
  9. ARIMA 三分量:AR(自回归)、I(差分)、MA(移动平均),由 ACF/PACF 或 auto-ARIMA 选 p、d、q。
  10. 多步预测三策略:递归(简单但误差累积)、直接(无累积但样本少)、多输出(共享信息但需多输出模型);短范围用递归、长范围用直接。

下一节,我们讲异常检测——为何要把问题框定为「建模正常」而非「分类异常」,以及 Z 分数、IQR、孤立森林如何找离群点。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U