4、交叉验证的艺术:K折交叉验证、滚动交叉验证在时间序列数据中的正确打开方式
聊到过拟合控制,交叉验证是个绕不开的话题。但说实话,我见过太多人在时间序列数据上把交叉验证用错了——直接用标准的K折去切时间序列,结果呢?模型用未来的数据去预测过去,这跟作弊没什么两样。
今天咱们就把这事掰扯清楚。时间序列数据的交叉验证,到底该怎么玩?
为什么标准K折在时间序列上会翻车?
先看一个简单的例子。假设你有2019到2023年的日频收益率数据,想做5折交叉验证。标准做法是把数据随机打乱,分成5份,轮流拿4份训练、1份验证。
问题来了——随机打乱之后,2023年的数据可能出现在训练集里,2020年的数据反而在验证集里。这意味着什么?模型用未来的信息去预测过去,这叫"未来信息泄露"。你想想看,这在真实交易中根本不可能发生。
我在项目中遇到过一位同事,他用标准K折在股票数据上做特征筛选,结果回测曲线漂亮得不行,一上实盘就崩。后来一查,就是交叉验证切分方式出了问题。
K折交叉验证的正确打开方式
其实K折在时间序列上也不是完全不能用,关键是要做"时间感知"的切分。说白了,就是按时间顺序切,不随机打乱。
具体做法是这样的:
- 把数据按时间排序(这是前提)
- 按顺序切成K个连续的时间段
- 第i次迭代时,用前i个时间段训练,第i+1个时间段验证
嗯,这里要注意:不是轮流用K-1份训练、1份验证,而是训练集永远是历史数据,验证集永远是未来数据。
我个人的习惯是,在金融数据上通常用5折或8折。折数太少,验证集不够稳定;折数太多,计算量上去了,收益却不大。
滚动交叉验证:时间序列的标配
滚动交叉验证(Rolling Cross-Validation),也叫前向链验证(Forward Chaining),是时间序列场景下我最常用的方法。
它的逻辑很直观:
- 设定一个固定长度的训练窗口
- 每次把窗口向前滚动一个步长
- 用窗口内的数据训练,预测窗口外紧邻的下一个时间段
这样做的好处是,每次验证都模拟了真实的交易场景——你只有过去的数据,要预测未来的表现。
- 训练窗口长度:通常取1-3年,取决于策略的换手率和市场环境
- 滚动步长:可以是1天、1周或1个月,步长越小验证越密集
- 验证集长度:一般与滚动步长一致,或者固定为某个周期
下面是我在项目中常用的滚动交叉验证流程图,帮你直观理解这个过程:
代码实现:Python中的滚动交叉验证
光说不练假把式。下面是我在项目中常用的实现方式,用scikit-learn的TimeSeriesSplit就能搞定:
from sklearn.model_selection import TimeSeriesSplit
import numpy as np
# 假设你有5年的日频数据
n_samples = 1250 # 5年 * 250个交易日
X = np.random.randn(n_samples, 10)
y = np.random.randn(n_samples)
# 创建滚动交叉验证对象
tscv = TimeSeriesSplit(
n_splits=8, # 做8次滚动验证
test_size=63, # 验证集大小:约3个月
gap=0 # 训练集和验证集之间不留间隔
)
# 执行交叉验证
for fold, (train_idx, val_idx) in enumerate(tscv.split(X)):
X_train, X_val = X[train_idx], X[val_idx]
y_train, y_val = y[train_idx], y[val_idx]
# 训练模型并评估
# model.fit(X_train, y_train)
# score = model.score(X_val, y_val)
print(f"第{fold+1}次验证: 训练集 {len(train_idx)} 条, 验证集 {len(val_idx)} 条")
避坑指南:我曾经踩过的三个坑
这些年做量化策略,在交叉验证上栽过不少跟头。分享几个典型的:
- 坑一:验证集太小——我曾经用1周的验证集去评估策略,结果波动极大,选出来的模型过拟合严重。后来我规定验证集至少要有1个月的数据。
- 坑二:滚动步长太大——步长设成半年,一年只做两次验证。这样得到的评估结果太粗糙,无法捕捉策略在不同市场环境下的表现。
- 坑三:忽略了数据预处理——标准化、缺失值填充这些操作,必须在每次交叉验证的循环内部重新做。如果在整个数据集上先做预处理再切分,又会产生未来信息泄露。
什么时候用K折,什么时候用滚动?
我个人的经验是:
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 数据量很大(>10年日频) | 滚动交叉验证 | 能模拟真实的交易节奏,评估更可靠 |
| 数据量中等(3-5年) | 时间感知K折 | 在保持时间顺序的前提下,充分利用有限数据 |
| 数据量很小(<1年) | 留一法或扩展窗口 | 数据太少,任何交叉验证都不太可靠,建议先积累数据 |
| 策略换手率很高 | 滚动交叉验证(小步长) | 高频策略对市场变化敏感,需要更密集的验证 |
说白了,交叉验证不是越多越好,也不是越复杂越好。关键是让你的验证方式尽可能接近真实的交易环境。你想想看,如果回测时用滚动交叉验证表现稳定,实盘时大概率也不会差太多。
嗯,关于交叉验证的艺术,今天就聊到这里。记住一句话:在时间序列的世界里,尊重时间顺序,就是尊重真相。