7.10 特征工程避坑:过拟合、数据穿越、共线性
避坑这一篇是踩坑清单。下面每一条都是自建系统最常见的失败原因。建议动手前先过一遍。
一、陷阱一:数据穿越(最致命)
表现
回测结果异常优秀(IC > 0.15),但一上真实使用就失效。
常见成因
| 成因 | 说明 |
|---|---|
| 用整赛季均值计算赛季初的指标 | 用了未来数据 |
| 用赛季末的积分榜排名 | 当时还没有这个排名 |
| 随机切分训练/测试集 | 时序数据必须按时间切 |
| 标准化时用了全量数据 | scaler 只能 fit 在训练集上 |
| 用「最终」阵容数据 | 赛前不知道最终阵容 |
| 数据数值用了赛后收盘价 | 赛前不知道收盘价 |
正确做法
# ✗ 错误:用全量数据做标准化
# scaler = StandardScaler()
# X_all = scaler.fit_transform(X) # 泄露了未来信息
# ✓ 正确:只用训练集 fit,测试集只 transform
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train) # 训练集:fit + transform
X_test = scaler.transform(X_test) # 测试集:只 transform
自查方法:如果回测表现好得不像真的(IC > 0.15),几乎可以确定存在数据穿越。
足球数据的真实 IC 通常不会那么高。
二、陷阱二:过拟合
表现
训练集表现优秀,样本外表现平平甚至为负。
成因与对策
| 成因 | 对策 |
|---|---|
| 特征太多、样本太少 | 特征数应远小于样本数(经验:样本至少是特征数的 20 倍) |
| 反复调参直到回测好看 | 保留一个从不参与调参的「最终验证集」 |
| 没有正则化 | 用 Ridge / Lasso / 逻辑回归的 C 参数 |
| 挑最好的时间段展示 | 报告完整周期的表现,不做选择性展示 |
# 三重切分:训练 / 验证(调参用)/ 测试(只测一次)
# 训练集:拟合模型
# 验证集:调参、选特征
# 测试集:最终只跑一次,绝不回头调参
# 时间顺序切分(不是随机切分)
n = len(df)
i1, i2 = int(n * 0.6), int(n * 0.8)
train, valid, test = df.iloc[:i1], df.iloc[i1:i2], df.iloc[i2:]
三、陷阱三:多重共线性
表现
回归系数剧烈波动,每次重新训练权重差异巨大,无法解释。
成因
足球特征天然高度相关:
| 特征对 | 相关性来源 |
|---|---|
| 进攻强度 ↔ 近期状态 | 状态好通常进攻也好 |
| 防守强度 ↔ 门将表现 | 门将强则失球少 |
| 控球率 ↔ 传球成功率 | 同属技术层面 |
| 射门数 ↔ 射正数 | 同一进攻过程的两个阶段 |
检测
import pandas as pd
def check_collinearity(df, feature_cols, threshold=0.8):
"""检查特征之间的相关性,标记高相关组合"""
corr = df[feature_cols].corr().abs()
high_pairs = []
for i in range(len(feature_cols)):
for j in range(i + 1, len(feature_cols)):
c = corr.iloc[i, j]
if c >= threshold:
high_pairs.append({
'pair': f'{feature_cols[i]} ↔ {feature_cols[j]}',
'corr': round(float(c), 3),
})
return sorted(high_pairs, key=lambda x: -x['corr'])
对策
| 对策 | 说明 |
|---|---|
| 删除冗余特征 | 相关性 > 0.9 的两个特征保留一个 |
| 合并为综合指标 | 把高度相关的几个特征合成一个(这正是加权评分的用途) |
| 用正则化 | Ridge 能缓解但不解决 |
| 用 PCA 降维 | 谨慎:会失去可解释性 |
四、陷阱四:样本偏差
表现
模型只在特定类型的比赛上有效,换个联赛就失效。
常见偏差来源
| 偏差 | 说明 |
|---|---|
| 联赛偏差 | 只用英超数据训练,用到意甲就失效 |
| 时期偏差 | 只用了疫情期数据,规则已变 |
| 强弱偏差 | 只覆盖强队比赛,缺少中下游样本 |
| 覆盖偏差 | 小联赛数据不全,被排除后样本不平衡 |
对策
- 按联赛分别标定:不同联赛参数不同,不要强行统一;
- 定期重标定:赛季变化、规则变化后需要重新拟合;
- 检查样本分布:确认各类型比赛都有足够样本。
五、陷阱五:幸存者偏差
表现
你的指标「看起来很准」,因为你只保留了有效的版本。
典型场景
你调了 20 组参数,发现第 13 组回测最好,于是采用它。
但你可能只是找到了在历史数据上恰好表现好的参数,而不是真正有效的参数。
这在样本量小时尤其危险。
对策
- 记录所有尝试,包括失败的参数组合;
- 用样本外数据验证,而不是回测最优值;
- 参数越少越好:每增加一个可调参数,过拟合风险就上升一次。
六、陷阱六:忽略基本面对比基准
你的模型准确率 58%,听起来不错。但基线是多少?
| 基线策略 | 准确率 |
|---|---|
| 全部猜主胜 | 约 45%~48% |
| 全部猜强队获胜 | 约 52%~56% |
| 跟随市场最低数据数值 | 约 53%~58% |
如果你的模型 58%,而「跟随市场」也有 57%,那你的增量只有 1%。
七、避坑检查清单
| 检查项 | 状态 |
|---|---|
| 标准化 scaler 只 fit 在训练集 | ☐ |
| 训练/测试严格按时间切分 | ☐ |
| 没有使用任何赛后信息 | ☐ |
| 检查过特征相关性 | ☐ |
| 保留了一个从不参与调参的测试集 | ☐ |
| 与基线策略做过对比 | ☐ |
| 记录了所有参数尝试(不只保留成功的) | ☐ |
| 样本量至少是特征数的 20 倍 | ☐ |
下一步该看什么
- 搭建完整系统 → 7.11 自建系统架构
- 用回测检验策略 → 7.14 回测验证
足球赛事前瞻 | AI 智能分析 | 足球数据解读 - 球小策