ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

特征不是越多越好:相关性去重与RFE递归特征消除实战

特征不是越多越好:相关性去重与RFE递归特征消除实战 特征不是越多越好相关性去重 RFE 递归特征消除实战做量化的人大概都有过这么一段“特征收集癖”的阶段看见什么数据都觉得能成因子成交量、持仓量、技术指标、舆情情绪、宏观数据……一股脑全塞进数据集。之前我做Python量化数据工程系列到第七篇终于想聊聊这个事儿。相关性去重和RFE递归特征消除这两个方法就是我用来给特征空间“瘦身”的主要工具。先说结论特征数量并不等于模型能力冗余特征不仅拖慢训练速度还会放大过拟合风险尤其在样本量有限的金融数据里多并不代表好。这篇文章就把我实际跑量化策略时怎么用这两招清理特征、踩过哪些坑、最终效果如何完整给你捋一遍。适合正在做特征工程、准备上模型的Python量化新手也适合那些特征堆到上百个、训练越来越慢、回测越来越不稳的老手。1. 先把问题想清楚特征冗余到底危害在哪里很多人在特征工程阶段陷入一个误区总觉得特征越多模型看得越全预测就越准。这个想法放在数据量无限的场景里或许还有几分道理但在量化交易里基本是错的。原因有三点我一个个讲。1.1 维度诅咒样本量撑不起特征量金融数据的样本量本身就是受限的。日线级别的数据哪怕你有十年的历史也就两千多个交易日分钟级别稍多一些但也经不起随意挥霍。当你把特征维度推到几百个的时候每个样本在高维空间里都被“稀释”了模型的泛化能力会迅速下降。直白点说特征空间太大会让模型在训练集上记得住、在测试集上认不出。我在一次实盘策略回测中就有过深刻教训当时加了大概120个特征进去训练集上准确率能到0.83看起来相当漂亮但一到样本外测试直接掉到0.51跟抛硬币没什么区别。后来排查才发现绝大部分特征之间存在严重的多重共线性模型被冗余信息带偏了。1.2 计算资源训练时间随维度膨胀特征维度直接决定了矩阵运算的规模。以线性模型为例训练时间复杂度通常与特征维度呈线性关系但对于树模型、SVM这类非线性模型特征维度的影响会被放大。我在同样的数据集上对比过80个特征跑一次XGBoost大约需要4分钟压缩到20个特征后只需要1分钟不到。这个时间差距在参数调优阶段会被放大几十上百倍因为你可能要跑几十组参数组合。更麻烦的是特征多了以后你在每个特征上的数据预处理、分箱、标准化步骤也会跟着变多整个数据管线的复杂度和出错概率都会上升。量化项目最怕的不是模型效果差而是数据链路出了bug你却查不出来。1.3 解释性崩塌策略出问题后无法复盘量化和纯机器学习竞赛最大的区别在于你必须要能复盘。策略亏钱了你得能回答“为什么亏”“哪个因子失效了”“市场结构是不是变了”。如果模型里有上百个特征这些问题的答案会被埋在巨大的特征空间里你几乎无从下手。而把特征压缩到10到20个核心维度后每个特征背后都有清晰的经济学直觉复盘就变得容易很多。2. 第一步清理相关性去重把“复读机”特征拿掉特征冗余最典型的表现就是高相关性。两个特征可能来源不同但反映的信息高度重叠比如“当日涨跌幅”和“当日收盘价相对开盘价的变动比例”这两个几乎是在描述同一件事。把它们同时放进模型模型并不会因此获得更多信息反而可能因为共线性问题导致权重分配不稳定。2.1 相关性矩阵怎么看我的习惯是先算一个完整的相关系数矩阵。对于大部分金融数据用Pearson相关系数就够用但如果你处理的是收益率、波动率这类带有明显厚尾分布的特征建议同时算一下Spearman秩相关系数它基于排名计算对异常值不敏感能避免个别极端行情导致的相关性误判。import pandas as pd import numpy as np # 假设 df 是已经处理好、对齐了时间索引的特征表 # 每一列是一个特征每一行是一个交易日 corr_matrix df.corr(methodspearman) # 用spearman更稳健 # 找到高相关的特征对 upper_tri corr_matrix.where( np.triu(np.ones(corr_matrix.shape), k1).astype(bool) ) high_corr_pairs [] for col in upper_tri.columns: high_corr upper_tri.index[upper_tri[col].abs() 0.85].tolist() for row in high_corr: high_corr_pairs.append((col, row, upper_tri.loc[row, col])) print(f发现 {len(high_corr_pairs)} 对高相关特征)这里有几个细节需要注意。第一相关系数阈值取0.85还是0.9没有绝对标准我的经验是日频数据取0.85高频数据可以放宽到0.9以上因为高频数据的噪音更多相关系数天然偏低。第二只看系数绝对值还不够最好把散点图也打出来扫一眼因为相关系数只能捕捉线性关系两个特征如果是明显的非线性关系但线性相关系数很低它们依然可能是冗余的。2.2 高相关特征对里保留谁确定哪些特征对需要处理之后下一步就是决定每一对里保留哪一个。我不建议单纯看哪个特征与目标变量的相关性更高就选哪个因为那很容易让你选到“幸存者偏差”式的特征。我的做法是综合三点特征的经济学含义是否清晰、缺失率是否更低、在不同市场状态下的稳定性如何。举个实际例子。我当时在构建一个分钟级短线策略候选特征里有两个一个是“过去5分钟成交量相对于过去20分钟均量的比值”另一个是“过去5分钟成交额相对于过去20分钟均额的比值”。两者的相关系数高达0.92明显冗余。从经济学含义上看成交量受价格水平影响成交额则是量价综合理论上前者更适合作为纯量能因子但实际回测中后者在上涨行情中的区分度更高。最终我保留了成交额比值因为它在极端行情下的稳定性更好。这个决定过程中我把每个候选特征的IC均值、IC标准差、ICIR这几个指标都打印了出来。ICIR是IC均值除以IC标准差这个指标能很好地反映特征有效性的稳定性比单纯看IC均值要可靠。def evaluate_feature(feature_series, forward_return): # 计算IC信息系数这里用spearman相关系数 ic feature_series.corr(forward_return, methodspearman) return ic # 对一组候选特征批量评估 candidates [vol_ratio, amount_ratio, price_position] ic_results {} for feat in candidates: # forward_return 是未来N期的收益序列 ic_results[feat] evaluate_feature(df[feat], df[fwd_ret]) print(f{feat}: IC {ic_results[feat]:.4f})2.3 正交化替代方案这里补充一个很多人不知道的进阶操作如果两个特征相关性很高但又都觉得它们各自带了独特信息除了“删一个”之外还可以做正交化处理。把特征B对特征A做回归取残差作为新的特征这个残差就是B中“剔除A影响后”的剩余信息。from sklearn.linear_model import LinearRegression # B对A正交化取残差 reg LinearRegression().fit(df[[feat_a]], df[feat_b]) residual df[feat_b] - reg.predict(df[[feat_a]])这种方法在因子组合里用得比较多但有个问题残差的经济学含义往往变得含糊不好解释。我的态度是能不正交化就不正交化实在两个都想留再考虑但使用前要想清楚你能否承担解释性下降的成本。3. 第二步深挖RFE递归特征消除让模型自己选相关性去重处理的是“特征之间的冗余”但还有一个问题它解决不了有些特征和别的特征相关性不高但对预测目标毫无贡献甚至是在引入噪音。这时候就需要用到RFE。3.1 RFE的核心思想RFE即递归特征消除它的逻辑很直观训练一个模型看每个特征的重要性排序把最不重要的那个特征删掉然后用剩下的特征重新训练再删掉此时最不重要的特征如此反复直到特征数量符合你的预期。为什么叫“递归”因为特征的重要性不是一次就能定死的。某个特征单独看可能不太重要但和另外几个特征组合在一起时可能贡献很大。一次性删掉最不重要的特征可能误伤这种组合型特征。递归逐步删除每次基于当前特征子集重新评估能最大程度保留特征间的协同信息。我用一个生活化的类比想象你有一个团队要裁员但不是一次性按照某张固定排名表从头裁到尾而是每裁一个人之后重新评估一次排名因为某个人的价值可能依赖于他身边特定几个同事的配合。这种动态评估虽然开销更大结果却更可靠。3.2 选择合适的重要性评估器RFE本身是一个框架它需要“重要性评估器”来给特征打分。经典的做法是用线性模型或树模型。我用下来比较顺手的是两种组合第一逻辑回归配合L1正则化。系数绝对值可以作为特征重要性的代理指标L1正则化本身就有特征选择效果会倾向于把不重要特征的系数压缩到0。这种组合适合交易方向预测这类二分类问题。第二随机森林或梯度提升树自带的feature_importances_属性。树模型的重要性基于分裂时带来的不纯度减少量比如Gini不纯度能捕捉非线性关系适合特征与目标之间关系复杂的场景。我的经验是先用相关性去重清理一遍再用带L1的逻辑回归跑RFE得到一个粗选结果最后拿树模型做精修。这样兼顾了效率和准确性因为线性模型跑得快可以快速缩小候选范围而树模型的最终效果往往更优。from sklearn.feature_selection import RFE from sklearn.linear_model import LogisticRegression # X 是过滤完高相关特征之后的自变量矩阵 # y 是二分类标签比如未来收益率是否大于0 estimator LogisticRegression(penaltyl1, solverliblinear, C0.1, max_iter1000) selector RFE(estimator, n_features_to_select20, step1) selector.fit(X, y) # 查看哪些特征被选中 selected_mask selector.support_ selected_features X.columns[selected_mask] print(RFE选中的特征, selected_features.tolist()) # 查看特征排名1为最优 ranking selector.ranking_ for feat, rank in zip(X.columns, ranking): print(f{feat}: 排名 {rank})这里我特别提醒一下step参数的设置。step表示每轮递归要删掉几个特征默认是1。如果你特征总数不多建议保持step1宁可多花点时间也不要一次性删太多。如果特征数量很大比如几百个可以先用step5快速缩小范围到了最后二三十个的时候再改成step1做精细化选择。3.3 消除交叉验证法防止过拟合RFE有个臭名昭著的坑它是在整个训练集上做特征选择然后你再用同一批数据评估模型效果这会带来严重的“选择偏差”也就是你可能把那些恰好在这个数据集上表现好的特征留了下来但它们并没有跨样本的稳定性。解决方法是用消除交叉验证法英文缩写叫RFCV。它的思路是把数据分成K折在每一折上分别跑RFE看特征在不同折上被选中的一致性。特征在所有折上都被选中说明它真的是稳定有效的特征只在某一折被选中大概率是那个折特有的噪音模式要谨慎对待。from sklearn.feature_selection import RFECV from sklearn.linear_model import LogisticRegression # 使用5折交叉验证自动确定最优特征数量 estimator LogisticRegression(penaltyl1, solverliblinear, C0.1, max_iter1000) rfecv RFECV(estimator, step1, cv5, scoringaccuracy, min_features_to_select5) rfecv.fit(X, y) print(f最优特征数量{rfecv.n_features_}) print(f各特征是否选中{rfecv.support_}) cv_scores rfecv.cv_results_[mean_test_score] optimal_feature_count rfecv.n_features_ print(f特征数量从 {len(X.columns)} 降到 {optimal_feature_count})交叉验证的折数选择我建议样本量足够的情况下用5折样本少就用3折。另外评分标准不要默认用准确率。如果你的策略本身是少数交易日赚大钱、大多数时间小幅波动准确率会严重失真。我一般会改用自定义的评分函数比如“未来N日收益方向的AUC值”或者“分层收益单调性”甚至直接把ICIR作为评分依据。3.4 特征数量到底选多少这是被问最多的问题之一RFE最后到底留多少特征合适没有一个万能答案但有几个经验法则可以参考。特征数量不能超过样本量的5%到10%否则过拟合风险急剧上升。比如你有2000个样本特征数量最好控制在100到200之间但这只是个粗框架实际还是要结合模型复杂度来定。更实用的方法是画一条“特征数量vs交叉验证得分”的曲线。RFCV会自动帮你做这件事但我也常手动跑一组实验分别设置5、10、15、20、30、50个特征记录每一组的样本外表现找到边际收益开始趋平的那个点。很多时候你会发现从30个特征增加到50个模型效果几乎没提升那30就是比较合适的截断点。4. 完整实操流程从100个特征压缩到21个前面讲了不少原理和方法这一节我把一个完整案例走一遍。这是我实际做过的A股日频选股策略的特征处理流程时间跨度和具体标的在这里不展开但流程具有很强的通用性你可以直接套用。4.1 数据准备与初步清洗我首先从行情、财务、资金流三个数据源里拼出约100个原始特征。这里有一个重要提醒相关性去重和RFE都必须在数据预处理之后进行顺序不能乱。标准做法是先去极值、再标准化、最后做特征选择。去极值我用的是MAD法也就是中位数绝对偏差法比简单的百分位截尾要稳健。标准化则根据模型需求决定线性模型用Z-score标准化没问题树模型不那么在意特征尺度可以跳过这一步但如果后续还要做因子组合、计算IC均值标准化之后统一量纲会更方便。def mad_winsorize(series, n5): 基于MAD的去极值处理 median series.median() mad (series - median).abs().median() upper median n * 1.4826 * mad lower median - n * 1.4826 * mad return series.clip(lower, upper) # 对所有特征列做去极值 for col in feature_cols: df[col] mad_winsorize(df[col])4.2 相关性去重结果对去极值后的数据计算Spearman相关系数矩阵阈值设为0.85。这一步删掉了大约17对高度冗余的特征特征数量从100降到76。我把几个典型的高相关特征对列出来感受一下这个过程的直观程度保留特征被删除特征相关系数保留理由5日动量10日动量0.915日动量反应更灵敏10日可通过组合生成量比指标换手率指标0.87量比在极端行情下区分度更高20日波动率60日波动率0.8920日波动率对短期风险定价更敏感你可能注意到像“5日动量”和“10日动量”这种在逻辑上明显有重叠的特征相关性高是很自然的。但问题在于很多特征不会像这个例子这么明显它们来源不同、命名不同唯独数值高度相关这时候相关性矩阵才能派上用场。4.3 RFE递归消除结果在76个特征的基础上跑RFCV使用带L1惩罚的逻辑回归作为评估器5折交叉验证。最终的optimal数量是21个特征。看各折特征一致性时发现一个有意思的现象将近一半的特征只在某一折中表现突出但在其他折中排名靠后这些特征虽然通过了L1筛选但稳定性太差我最终也把它们手动剔除了。这一步相当于在算法自动选择的基础上又叠加了一层人工审核。最终选定的21个特征分布在以下几类动量类5个、波动率类4个、量能类4个、资金流类3个、估值类3个、技术形态类2个。这个分布很合理说明特征没有被某一类因子垄断策略的收益来源相对多元。4.4 前后效果对比特征压缩前后的对比数据我没有细化到具体收益率数字不同策略差异太大说了反而误导你但从几个维度看提升是很明确的训练时间缩短了约70%。原来76个特征训练一轮XGBoost需要接近6分钟压缩到21个之后只需要1分多钟后续网格搜索几十组参数省下的时间非常可观。过拟合程度明显下降。特征压缩前训练集AUC大约是0.87验证集只有0.58压缩后训练集AUC降到0.74验证集反而升到0.62。这个差距收窄说明模型不再死记硬背训练集的噪音模式。换手率降低约15%。这个变化比较隐蔽我分析原因是被删除的特征中有不少对短期波动噪音特别敏感它们在实盘里容易引发频繁换仓。特征简化后信号本身更稳定交易成本也跟着降下来了。5. 常见问题与避坑指南最后这部分把我踩过的一些坑集中列一下这些都是文档里不会写、但实战中极易踩中的细节。5.1 相关性去重里的“幸存者偏差”相关性去重时经常会碰到一对特征一个覆盖时间长、一个覆盖时间短。比如一个因子从2018年就开始计算另一个从2022年才开始它们在同一段时间里相关性很高。如果你直接保留覆盖时间长的那个短期因子的独特信息可能就被误删了。我的处理方式是在删除之前先检查两个特征的缺失率如果某个特征缺失率明显更高优先考虑是不是该保留它因为它可能包含了另一段时期特有的信息。5.2 RFE对特征顺序高度敏感这是一个很多人没意识到的问题。树模型的feature_importances_受到随机种子影响逻辑回归的特征选择结果受到特征标准化和样本顺序影响。你会发现换了顺序之后有些中性特征时而被选中时而被剔除。应对方法是多跑几次用不同的随机种子统计特征被选中的频率而不是依赖单次运行的结果。这也是我在4.3里强调“跨折一致性检查”的原因。单次RFE的结果不值得完全信任多次运行的稳定性才是更可靠的信号。5.3 先做特征选择还是先做交叉验证一定要先做特征选择再做交叉验证。很多人把顺序反过来先切训练集测试集再在训练集内做特征选择这会导致信息泄露。正确的流程是先把全量特征传到特征选择器里选好特征子集后再切分数据集进行模型训练和验证。如果你用的是RFCV它在内部已经帮你处理了这个问题因为它每折只基于当前折的训练部分做特征选择这是它比普通RFE更可靠的原因之一。5.4 特征选择不是一劳永逸市场和数据都在变隔几个月重新跑一次相关性去重和RFE是非常必要的。特别是当你在原有基础上新增了数据源比如上了分钟级数据、引入了另类数据新旧特征之间很可能产生新的共线性问题。我一般会每季度做一次全量特征评估把最新数据拉进来重新过一遍这个流程。另外实盘跑起来之后建议记录每个特征在线上的IC表现。那些在特征选择时有效、但线上持续失效的特征要么是市场风格切换了要么是当时的选择本身就过拟合了。及时清理掉这些“过期因子”策略才能保持竞争力。我个人在实际操作中的体会是特征选择这个环节花几天时间反复打磨长期来看回报率极高。它不直接产生预测能力但它能帮你把模型从“看起来很厉害”变成“真正能稳定赚钱”。如果你现在正被一堆特征压得喘不过气回测怎么调都过拟合建议先停下来做一次减法。
返回列表