4. 特征工程(下):交叉特征、因子正交化、特征选择与降维
好,咱们接着聊特征工程的下半场。上一章我们把单因子特征梳理清楚了,但说实话,在真实交易里,单个因子往往不够用。你想想看,市场是多维的,一个因子只能反映一个侧面。所以这一章,我们重点解决三个问题:怎么把因子组合起来产生化学反应?怎么去掉因子之间的冗余信息?以及,当特征多到爆炸时,怎么优雅地降维?
核心观点:特征工程的下半场,本质是从「有什么特征」走向「用什么特征」。交叉特征产生新信息,正交化去除冗余,降维保留精华。这三步走完,你的模型输入才算真正准备好。
4.1 交叉特征:让因子之间产生化学反应
交叉特征,说白了就是把两个或多个因子揉在一起,生成新的特征。为什么要这么做?因为市场是非线性的。举个例子,单独看市盈率(PE)和市值(Size),可能效果一般。但如果你把低PE和小市值组合起来,往往能抓到更强的alpha。
我个人习惯把交叉特征分成两类:
- 显式交叉:加减乘除、取对数、取最大值等数学运算。比如:PE / PB、成交量 * 价格变化、换手率 * 波动率。
- 隐式交叉:通过树模型或神经网络自动学习特征之间的交互。比如XGBoost的树分裂天然会做特征组合,但缺点是解释性差。
我在项目中遇到过一个问题:两个因子单独看都不错,但交叉之后反而效果变差。后来发现,是因为这两个因子高度相关,交叉特征只是放大了噪声。所以,做交叉之前,先看一眼相关性矩阵,别盲目组合。
我的小技巧:做交叉特征时,优先选择逻辑上有关联的因子。比如「动量因子」和「波动率因子」交叉,比「动量因子」和「天气因子」交叉有意义得多。别为了堆特征而堆特征。
4.2 因子正交化:去掉冗余信息
你想想看,如果两个因子相关性高达0.9,那它们本质上是在描述同一个东西。模型输入两个几乎一样的特征,不仅浪费计算资源,还会导致多重共线性,让模型不稳定。
因子正交化,就是通过数学变换,把一组相关的因子转换成一组互不相关的因子。最常用的方法是施密特正交化(Gram-Schmidt)和PCA正交化。
嗯,这里要注意:正交化不是万能的。它虽然去掉了线性相关性,但非线性相关性依然存在。而且正交化后的因子,解释性会变差——你很难说清楚「正交因子1」到底代表什么。
我曾经在CTA策略里吃过亏:把动量因子和期限结构因子做了正交化,结果回测曲线漂亮得不行,但实盘一跑就崩。后来发现,正交化把两个因子的共同信息去掉了,而那个共同信息恰恰是市场最核心的驱动因素。所以,正交化要谨慎,别把婴儿和洗澡水一起倒掉。
避坑指南:正交化之前,先问自己三个问题:1)这两个因子真的冗余吗?2)去掉共同信息后,剩下的信息还有意义吗?3)正交化后的因子,我能解释它的逻辑吗?如果三个答案都是「是」,再动手。
4.3 特征选择:从100个里挑出10个
特征选择,就是在一堆特征里,选出对预测最有用的那部分。方法很多,我按实用程度排个序:
| 方法 | 原理 | 适用场景 | 我的评价 |
|---|---|---|---|
| 单因子IC测试 | 计算每个因子与未来收益的相关系数 | 快速筛选,初步判断 | 简单粗暴,但容易漏掉组合效应 |
| L1正则化(Lasso) | 让不重要的特征系数变为0 | 特征数量远大于样本量时 | 好用,但要注意调参 |
| 树模型特征重要性 | 基于分裂次数或信息增益排序 | 非线性关系明显时 | 直观,但容易过拟合 |
| 递归特征消除(RFE) | 反复训练模型,剔除最不重要的特征 | 特征数量中等时 | 计算量大,但效果好 |
我个人习惯先用IC测试做一轮粗筛,把IC绝对值低于0.02的扔掉。然后用Lasso做第二轮精筛,最后用树模型的重要性排序做最终确认。三步走下来,基本能留下最精华的10-20个特征。
记住:特征选择不是越多越好。我见过有人用200个特征训练模型,结果过拟合得一塌糊涂。少即是多,尤其在金融数据里,信噪比极低,特征越多,噪声越大。
4.4 降维:PCA与Autoencoder
降维和特征选择不一样。特征选择是「挑」,降维是「压缩」。前者保留原始特征,后者生成新的低维表示。
4.4.1 PCA:经典中的经典
PCA(主成分分析)的原理很简单:找到数据方差最大的方向,把数据投影上去。说白了,就是保留最重要的信息,扔掉次要的噪声。
在量化里,PCA常用于处理因子之间的共线性。比如你有50个动量因子,它们高度相关,用PCA可以压缩成3-5个主成分,每个主成分代表一类动量模式。
但PCA有个硬伤:它是线性变换。如果因子之间的关系是非线性的,PCA就抓不住。而且PCA的主成分很难解释——你能说清楚「第3主成分」是什么吗?反正我不能。
# 一个简单的PCA示例
from sklearn.decomposition import PCA
import numpy as np
# 假设我们有100个样本,50个因子
X = np.random.randn(100, 50)
# 保留95%的方差
pca = PCA(n_components=0.95)
X_pca = pca.fit_transform(X)
print(f"原始特征数: {X.shape[1]}")
print(f"降维后特征数: {X_pca.shape[1]}")
print(f"解释方差比: {pca.explained_variance_ratio_}")
4.4.2 Autoencoder:深度学习的降维利器
Autoencoder(自编码器)是神经网络版的PCA。它通过编码器把高维数据压缩到低维,再通过解码器还原。如果还原误差很小,说明低维表示保留了足够的信息。
Autoencoder最大的优势是能捕捉非线性关系。我在做高频因子降维时,PCA只能解释60%的方差,但Autoencoder能解释85%以上。差距很明显。
不过,Autoencoder也有坑:训练不稳定,容易过拟合,而且超参数多(层数、节点数、学习率等)。我建议先用PCA做个基线,如果PCA效果够好,就别折腾Autoencoder了。
# 一个简单的Autoencoder示例(PyTorch风格)
import torch
import torch.nn as nn
class Autoencoder(nn.Module):
def __init__(self, input_dim, encoding_dim):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, 64),
nn.ReLU(),
nn.Linear(64, encoding_dim)
)
self.decoder = nn.Sequential(
nn.Linear(encoding_dim, 64),
nn.ReLU(),
nn.Linear(64, input_dim)
)
def forward(self, x):
encoded = self.encoder(x)
decoded = self.decoder(encoded)
return decoded
# 使用示例
model = Autoencoder(input_dim=50, encoding_dim=10)
# 训练过程略...
我的建议:如果特征数量在50以下,优先用PCA。如果特征数量超过100,或者你怀疑存在非线性关系,试试Autoencoder。但记住,Autoencoder需要大量数据,样本量少于1000时别用。
4.5 本章知识体系总览
为了让你更直观地理解这一章的逻辑,我画了一张流程图。它展示了从原始因子到最终模型输入的完整路径:
这张图展示了我们这一章的核心流程。你可以看到,交叉特征、正交化、特征选择是三个并行分支,最终汇聚到降维环节。我个人习惯先做交叉特征,因为这一步能产生新信息;然后做正交化,去掉冗余;最后用PCA或Autoencoder做降维,把维度压缩到可控范围。特征选择可以穿插在任何一步之后,作为质量把关。
好了,这一章的内容就到这里。特征工程是深度学习择时策略的地基,地基不牢,模型再花哨也没用。下一章我们会进入模型构建环节,到时候这些特征就要派上大用场了。