3. 多项式特征扩展:PolynomialFeatures原理、过拟合风险、交互特征构造

好,咱们今天聊聊多项式特征扩展。说白了,就是给原始特征加上平方项、立方项,还有特征之间的乘积项。为什么要这么做?因为现实世界里的关系,很少是纯线性的。

我刚开始做量化因子挖掘那会儿,就踩过这个坑。当时用了一堆线性模型去预测股价,结果回测曲线漂亮得不行,一上实盘就崩。后来才发现,很多因子之间的关系是弯的,不是直的。你想想看,市盈率和收益率之间,哪能是简单的正比关系?

3.1 为什么需要多项式特征?

线性模型有个硬伤——它假设特征和目标之间是直线关系。但真实市场里,这种假设太天真了。

  • 边际效应递减:比如市值因子,小市值股票确实有超额收益,但市值越小收益越明显?其实不是。市值从10亿到20亿的收益变化,和从100亿到110亿的收益变化,完全不是一个量级。
  • 阈值效应:有些因子只在特定区间有效。比如换手率,太低说明流动性差,太高说明投机过度,中间段才是黄金区域。
  • 交互作用:两个因子单独看可能不显著,但组合起来威力巨大。比如「低波动+高动量」,这个组合在A股市场我就见过不少次。

多项式特征,就是帮我们把这些非线性关系「塞」进线性模型里。嗯,这里要注意,它并不是真的让模型变非线性了,而是通过构造新特征,让线性模型能拟合曲线。

3.2 PolynomialFeatures 原理

Scikit-learn 里的 PolynomialFeatures 用起来很简单,但背后逻辑你得搞明白。

假设你有两个特征:x1x2。设置 degree=2 后,它会生成:

原始特征: x1, x2
二阶扩展: x1², x2², x1*x2
再加上偏置项: 1(常数项)

一共就是 1 + 2 + 3 = 6 个特征。公式长这样:

y = w0 + w1*x1 + w2*x2 + w3*x1² + w4*x2² + w5*x1*x2

你看,虽然模型还是线性的(对参数 w 而言),但特征空间已经能表达曲线和交互了。

核心要点:PolynomialFeatures 的本质是「特征工程」,不是「模型升级」。它把原始特征映射到高维空间,让线性模型在这个空间里找到非线性决策边界。

我个人习惯,在构造因子时先用 degree=2 试试水。为什么?因为 degree 每增加1,特征数量会爆炸式增长。具体来说:

n 个特征,degree = d
特征总数 = C(n+d, d) = (n+d)! / (n! * d!)

举个例子,10个特征,degree=3,特征数就变成 C(13,3) = 286 个。这还只是3阶,要是搞到5阶...你想想看,模型还没训练,内存先爆了。

3.3 过拟合风险——我踩过的坑

说到过拟合,我得好好聊聊。我在项目中遇到过最惨的一次,就是用多项式特征做因子挖掘,回测夏普比干到3.5,结果实盘直接变负的。

为什么会这样?因为多项式特征太容易「记住」噪声了。

阶数 训练集 R² 测试集 R² 结论
1(线性) 0.12 0.10 欠拟合,没学到啥
2 0.35 0.28 不错,有提升
3 0.58 0.31 开始过拟合了
5 0.89 0.15 严重过拟合

你看这个表,degree=5 时训练集 R² 高达 0.89,但测试集只有 0.15。说白了,模型把训练数据里的随机波动都当成了规律。

避坑指南:我曾经在月度调仓的因子模型里,把 degree 设到 4,结果模型完美拟合了过去3年的所有异常波动。但一到新数据,那些「规律」全消失了。后来我养成了一个习惯:每次用多项式特征,必须做交叉验证,而且至少用3年的滚动窗口来检验稳定性。

3.4 交互特征构造——真正的价值所在

说实话,多项式特征里最有价值的,不是那些平方项、立方项,而是交互特征。也就是 x1*x2x1*x2*x3 这种。

为什么?因为金融市场的因子之间,天然存在交互作用。举个我实际用过的例子:

  • 估值 × 动量:低估值股票加上强动量,往往意味着基本面反转。这个组合因子在A股市场效果很好。
  • 波动率 × 换手率:高波动+高换手,通常是游资炒作,持续性差;低波动+低换手,可能是机构锁仓。
  • 市值 × 盈利质量:小市值公司里,盈利质量高的更容易被市场认可。

代码实现很简单:

from sklearn.preprocessing import PolynomialFeatures

# 假设你有两个因子:pe_ratio(市盈率)和 momentum(动量)
X = df[['pe_ratio', 'momentum']].values

# 只生成交互特征,不要平方项
poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
X_interact = poly.fit_transform(X)

# 看看生成的特征
print(poly.get_feature_names_out(['pe_ratio', 'momentum']))
# 输出: ['pe_ratio', 'momentum', 'pe_ratio momentum']

注意 interaction_only=True 这个参数。它告诉模型:我只想要交互项,不要平方项。这在因子数量多的时候特别有用,能有效控制特征膨胀。

我的小技巧:在构造交互特征前,先把原始因子做标准化。为什么?因为不同因子的量纲差异太大,比如市盈率可能是几十,而动量可能是0.几。不做标准化,交互项会被量纲大的因子主导,模型学不到均衡的信息。

3.5 实战中的取舍

讲了这么多,你可能会问:到底该用多少阶?交互项要不要全加?

我的建议是:

  1. 从 degree=2 开始,只加交互项,不加平方项。这样特征数量可控,而且交互项往往比平方项更有业务含义。
  2. 用 L1 正则化(Lasso)来筛选特征。Lasso 会自动把不重要的多项式系数压到0,帮你做特征选择。
  3. 业务逻辑优先。别盲目堆特征。比如你知道「估值」和「成长性」可能有交互,那就手动构造这个交互项,而不是让模型去猜。

我记得有一次做多因子模型,原始特征只有8个,用 degree=3 后特征膨胀到165个。结果 Lasso 一跑,最后只保留了12个有效特征,其中7个是交互项。你看,真正有用的东西,其实没那么多。

好了,多项式特征这块就聊到这儿。记住一句话:多项式扩展是把双刃剑,用好了能捕捉非线性关系,用不好就是过拟合的温床。关键还是得结合业务理解,别让模型替你瞎猜。

多项式特征扩展核心逻辑 原始特征 x1, x2, ..., xn PolynomialFeatures degree=2 或 3 interaction_only 参数 平方/高次项 x1², x1³, ... 交互特征 x1*x2, x1*x3, ... 高维特征空间 ⚠ 过拟合风险 特征数量爆炸 → 模型记住噪声 → 训练集R²高但测试集R²低 解决方案:交叉验证 + L1正则化 + 业务逻辑筛选

专注资料整理