☰
高相关性筛选法:从上千特征到精简清单的实战指南
2026/10/20 6:12:38 网站建设 项目流程

做机器学习项目做到第三个月,我终于明白一件事:很多时候瓶颈不在模型,而在喂给模型的那堆特征里。之前跑一个信贷风控的模拟项目,训练集里堆了1200多个特征,模型训一次要两个小时,调参基本靠等,特征工程做完连验证的耐心都快磨没了。后来我用高相关性筛选法做了一道减法,特征砍到不到200个,训练时间缩到十分钟以内,线上指标还涨了两个点。今天就把这套方法从原理到代码完整拆开讲一遍。

高相关性筛选法,简单说就是计算特征两两之间的相关系数,把那些高度相关的特征识别出来,然后成组剔除,只留下代表性特征。它不是什么新算法,也没有复杂的数学推导,但恰恰是这种朴素手段,在真实业务里反而最常用、最耐用。适合谁看?如果你正在做特征工程,手头特征动辄几百上千个,模型训练慢、过拟合明显,或者你想快速搭一个干净的基线模型,这篇文章可以直接照着抄。

1. 高相关性筛选法到底在解决什么问题

1.1 特征多了,模型为什么反而变差

很多人有个直觉误区,觉得特征越多,模型能用的信息越多,效果就一定更好。实际上特征数量一旦超过某个临界点,模型效果会先升后降,这就是典型的“维度灾难”。特征多了以后,样本在高维空间里会变得极其稀疏,每个维度上的有效样本量都不够,模型很难学到稳定的规律。

高相关性带来的问题更直接。比如你有“用户月收入”和“用户年收入”两个特征,这俩本质上是同一个信息,只是刻度不同。模型在训练时会对这两个特征分配权重,但它们携带的信息高度重叠,权重分配就变得不稳定。换一批训练数据,两个特征的权重可能剧烈波动,模型泛化能力直线下降。

还有个容易被忽略的问题——训练成本。特征从200个加到1200个,训练时间不是线性增长,往往是几倍甚至十几倍地涨。我在实际项目中体会很深,每次调参数要等模型跑完才能看到结果,一天下来根本调不了几次。先把特征数量降下来,等于把迭代速度提上去了,效率完全不是一个量级。

1.2 哪些项目最适合用这套方案

不是所有项目都需要做高相关性筛选。我总结下来,以下三类场景收益最大:

第一类是线性模型为主的场景,比如逻辑回归、线性回归、评分卡模型。这类模型对多重共线性非常敏感,特征之间高度相关会直接导致系数估计失真,符号都可能反了。做信贷评分卡的时候,业务方还会要求每个特征有明确的经济含义,高相关性筛选能让留下来的特征互相独立,解释起来也顺。

第二类是特征数量远大于样本量的场景,比如基因表达数据、用户画像大宽表。几百个样本、上千个特征,模型几乎没有收敛的可能。先做一遍相关性筛选,把维度降到一个合理范围,后续无论是建模还是可视化都顺畅很多。

第三类是快速搭建基线模型的场景。项目初期,你希望用最少的成本跑通一个能用的版本,高相关性筛选法不需要训练模型、不需要调参,几分钟就能出一份特征清单,非常适合作为第一步粗筛。

1.3 为什么不直接跑个模型让算法自己筛

你可能想问,现在有不少自动特征选择方法,比如L1正则化、树模型的特征重要性,为什么还要手动做相关性筛选?我的看法是:它们解决的是不同层次的问题。

树模型的特征重要性确实能告诉你哪些特征有用,但它看的是“单特征和目标变量的关系”,看不出特征之间的冗余。L1正则化能把不重要的特征权重压成0,但两个高度相关的特征,它可能随机留下其中一个,选谁带有偶然性,可解释性也差。

相关性筛选法最大的优势在于——它根本不看目标变量,只分析特征之间的内部结构。这意味着它可以放在建模之前,作为纯数据预处理步骤,完全独立于后续的模型选择。你先用相关性筛选把冗余去掉,再交给L1正则化或树模型继续精筛,两步配合,效果比单用任何一种都好。

2. 三个相关系数怎么选,别再默认皮尔逊

2.1 三种指标各自的适用边界

相关系数最常用的是三种:皮尔逊相关系数、斯皮尔曼秩相关系数、肯德尔秩相关系数。很多教程喜欢直接甩出df.corr()的默认参数,默认是皮尔逊,但实际场景里默认的往往不是最合适的。

皮尔逊相关系数衡量的是两个变量之间的线性相关程度,它要求两个变量都近似服从正态分布,并且关系是线性的。优点是计算快、解释直观,缺点是遇到非线性关系就抓瞎。比如一个特征随另一个特征呈抛物线变化,皮尔逊相关系数可能接近0,可它们明明高度相关。

斯皮尔曼相关系数是基于秩次计算的,它先把每个变量的数值转成排序后的序号,再计算这些序号的皮尔逊相关。因为用的是秩而不是原始值,它对异常值极不敏感,也能捕捉单调的非线性关系。只要两个特征的变化趋势一致、不一定成直线,斯皮尔曼就能识别出来。

肯德尔相关系数也是基于秩的,但计算方式不同,它对样本量小的数据更稳健。实际项目中用到它的场景不多,通常是在做显著性检验、样本量特别小的时候才会优先考虑。日常特征筛选,我更推荐皮尔逊和斯皮尔曼搭配着用。

2.2 相关系数的计算逻辑,几句话讲透

很多人拿计算器按了相关系数,但没搞明白公式背后的直觉。皮尔逊相关系数的公式是协方差除以两个标准差的乘积。协方差衡量的是两个变量是否同向变动,标准差是各自波动的幅度。除以标准差之后,结果就变成了一个无量纲的数,范围被锁定在-1到1之间。

举个直觉的例子。两个特征完全同向变化,一个涨另一个也涨,协方差为正,相关系数接近1;完全反向,一个涨另一个跌,相关系数接近-1;两者毫无规律,相关系数趋近0。-1到1之间的取值,直观地表达了线性相关的方向与强度。

斯皮尔曼的计算同样可以用这个思路理解,只不过把原始数值替换成了排名。有一点要注意:数据的量纲根本不影响相关系数。比如一个特征用元为单位、另一个用万元为单位,皮尔逊系数算出来完全一样,因为除以标准差已经标准化掉了量纲差异。

2.3 相关性阈值怎么定才科学

阈值的选择没有标准答案,但行业里有几个常用的经验值。以皮尔逊系数绝对值算,0.8以上属于高相关,0.6到0.8属于中等相关,0.4以下基本可以认为相关性较弱。做筛选的时候,我一般从0.8起步,观察剔除后特征数量和模型效果的变化,再往0.7甚至0.6调整。

一个常见的误区是纠结于单一阈值。实际上阈值应该结合业务场景动态调整。如果特征数量基数很大,比如从1000个里筛,阈值可以适当放宽到0.7,多剔除一些冗余特征;如果特征只有三五十个,本身不算多,阈值就要收紧到0.85以上,避免误删有区分度的特征。

还有一个细节:阈值校验时不要只看相关矩阵里的数字,要抽样看几个具体特征对的散点图。我见过相关系数0.85但散点图明显呈U型的特征,这种情况下皮尔逊系数失真了,需要改用斯皮尔曼,或者干脆手工判断取舍。盲目信任一个数字,踩过坑才后悔。

3. 从相关矩阵到特征清单的完整实操

3.1 前置处理:数值化、缺失值、标准化

高相关性筛选法第一步根本不是算相关系数,而是把数据整理成能算的状态。最容易翻车的就是数据类型。df.corr()计算时会自动忽略非数值列,如果你没注意,可能漏掉一批本该筛选的特征。

所有参与计算的列都必须是数值型。类别型特征要先做编码,最简单的做法是标签编码或者独热编码。不过独热编码会产生大量稀疏列,这些列之间的相关性计算意义不大,我一般先对原始类别特征做一个目标编码,再用编码后的数值参与筛选。

缺失值也要提前处理。pandas计算相关系数时会默认丢弃缺失值对,但如果某个特征缺失比例超过30%,计算出的相关系数可信度就很低。我的习惯是先做缺失率统计,对缺失过高的特征单独标记,不能简单丢进相关矩阵里凑数。

标准化这件事倒不急。前面说了,相关系数对量纲不敏感,归一化和标准化不会改变计算结果。但你如果后续要用VIF或正则化再做精筛,建议还是先标准化一遍,省得后面来回折腾。

3.2 算矩阵、画热力图,一眼找出问题特征

数据准备就绪后,算相关矩阵就是一个函数的事。我习惯把计算结果和可视化同时做,热力图比数字表格直观得多。

import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt df = pd.read_csv('features.csv') corr_matrix = df.corr(method='spearman') plt.figure(figsize=(14, 12)) sns.heatmap(corr_matrix, cmap='RdBu_r', center=0, xticklabels=True, yticklabels=True) plt.title('Feature Correlation Matrix') plt.show()

热力图上颜色越深红的区块,就是高相关特征对聚集的地方。看到大块深色区域,说明很多特征在互相重复,这时候不要急着一个个手动处理,先统计出所有高相关特征对,再整体规划剔除策略。

def find_high_corr_pairs(corr_matrix, threshold=0.8): pairs = [] cols = corr_matrix.columns for i in range(len(cols)): for j in range(i + 1, len(cols)): corr_value = corr_matrix.iloc[i, j] if abs(corr_value) >= threshold: pairs.append((cols[i], cols[j], round(corr_value, 4))) return pairs pairs = find_high_corr_pairs(corr_matrix, threshold=0.8) for feat_a, feat_b, corr_val in pairs[:20]: print(f'{feat_a} - {feat_b}: {corr_val}')

这个函数循环了所有特征对,把超过阈值的都揪出来。之所以用abs(),是因为负相关和正相关一样代表冗余,方向不同而已。这个逻辑后面还会再强调。

3.3 贪婪剔除策略:保住信息量,去掉冗余

拿到高相关特征对列表之后,下一步就是决定谁走谁留。基础思路是:每组高相关的特征里,挑一个做代表,其他全部剔除。关键在于怎么挑代表。

最简单的策略是随机留一个,比如保留出现顺序靠前的那个。代码实现也比较直接:

def greedy_drop_by_corr(corr_matrix, threshold=0.8): drop_set = set() cols = list(corr_matrix.columns) for i in range(len(cols)): for j in range(i + 1, len(cols)): if cols[j] in drop_set: continue if abs(corr_matrix.iloc[i, j]) >= threshold: drop_set.add(cols[j]) keep_cols = [c for c in cols if c not in drop_set] return keep_cols, drop_set keep_cols, drop_set = greedy_drop_by_corr(corr_matrix, 0.8) print(f'保留特征数: {len(keep_cols)}, 剔除特征数: {len(drop_set)}')

这个策略的优点是简单直观,缺点也很明显——它完全不看特征本身的价值。如果被保留的那个特征缺失率高、或者和目标变量的相关性更弱,整体效果就吃亏了。

更好的做法是引入两个辅助信息来决策。第一个是特征缺失率,缺失率高的优先剔除;第二个是特征与目标变量的相关度,和目标变量相关度高的优先保留。我实际项目中常用一个综合评分:对每个高相关组内的特征,按“与目标变量的相关系数×非缺失率”打分,取分高的作为代表。代码改动不大,效果提升却很明显。

3.4 结合目标变量做监督式筛选,效果更稳

高相关性筛选法本身是无监督的,它只考虑特征之间的结构,不看目标变量。但实际使用中,我强烈建议做一层监督式结合。

思路是这样的:先用相关性筛选剔除组内冗余特征,再把保留下来的特征逐一和目标变量计算相关系数,保留显著相关的,剔除和目标变量几乎无关的。这一步能解决一个常见问题——如果某个特征组整体和目标变量都无关,那么从中挑一个代表保留也是浪费维度。

实际操作时,我会在产线里跑两步:第一步是特征间的相关性筛选,阈值0.8;第二步是特征与目标变量的相关性筛选,阈值视业务而定,通常皮尔逊系数绝对值低于0.05的可以认为没有线性关系,直接排除。两步合在一起,基本就能得到一个干净、低冗余、和目标变量有明显关联的特征子集。

我试过在某个营销响应预测项目里,单用无监督筛选后特征还剩400个,加上与目标变量的相关性筛选后砍到180个,后续模型效果不降反升。原因很简单——那些和响应行为毫不相关的特征被清掉了,模型不再为它们浪费容量,也降低了过拟合风险。

4. 高相关性筛选法的高频坑与排查实录

4.1 正负相关都是冗余,必须加绝对值

这个坑我见过太多次了。很多初学者写代码循环找高相关特征时,只判断corr_value >= threshold,忘了加绝对值。结果就是:正相关的冗余特征被删了,负相关的冗余特征一个没动。

为什么负相关也是冗余?想象两个特征,一个随用户年龄增长而上升,另一个随年龄增长而下降。它们携带的信息都是“年龄”,只是方向反了,本质上是同一个信息源的两种表达。模型只需要其中一个,两个都留着只会带来多重共线性问题。

无论皮尔逊还是斯皮尔曼,判断相关性强度一律用绝对值。符号只代表方向,不代表强弱。这是我特别想强调的一点,代码里加一个abs(),比后面踩坑再排查节省无数时间。

4.2 分类特征和文本特征怎么参与计算

分类特征和文本特征不能直接丢进相关矩阵,很多人在这一步就会卡住。我常用的方案是有层次地处理。

类别型特征如果是有序的,比如学历、评级等级,直接标签编码成0、1、2就能参与相关性计算,排序信息被保留下来。如果是无序的,比如城市、渠道来源,标签编码就不合适了——编码数字的大小关系会产生虚假的相关性。这种情况我用目标编码或者频率编码,把类别映射成目标变量均值或出现频率,再参与计算。

文本特征的情况更复杂一些。先用TF-IDF或者词向量转成数值矩阵,再进行相关性筛选。但要注意,文本特征转出来的维度非常高,直接全量算相关矩阵非常慢。我的做法是先做一遍SVD或PCA降维,再对降维后的成分做相关性筛选。这样既保住了文本的主要信息,又避免了维度爆炸。

4.3 筛选完还要复核:和VIF、L1、树模型配合使用

高相关性筛选法还完不了事。它筛掉的是“明显冗余”,但特征之间可能还存在中等程度的共线性,需要进一步复核和精筛。

方差膨胀因子(VIF)是经典的多重共线性指标,VIF大于10的特征一般都认为存在严重共线性。相关性筛选之后,我习惯再用VIF过一遍,防止残留的中等相关特征影响线性模型的稳定性。代码用statsmodels库里的variance_inflation_factor就能算。

from statsmodels.stats.outliers_influence import variance_inflation_factor X = df[keep_cols].dropna() vif_data = pd.DataFrame() vif_data['feature'] = X.columns vif_data['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] print(vif_data.sort_values('VIF', ascending=False).head(20))

VIF偏高的特征继续剔除,直到全部低于10。再往后,可以结合L1正则化或者树模型的特征重要性做最后一轮精筛。相关性筛选负责去冗余,VIF负责查共线性,树模型负责挑重要性,各管一段,组合起来才是完整流程。

4.4 一个容易忽略的细节:先归一化还是先筛选

先说结论:做相关性筛选之前,不需要先做标准化或归一化。相关系数的公式里已经包含了除以标准差这一步,量纲不同不影响计算结果。

但有一种情况需要先处理——如果你的特征数量级差距特别悬殊,比如一个特征取值范围是0到1,另一个是0到100000,计算过程会出现数值精度问题。虽然数学上无关,但浮点数运算时可能丢失精度。稳妥起见,我会先把特征做一次标准化,再计算相关矩阵,代价可以忽略。

更重要的其实是缺失值处理顺序。必须先处理缺失值,再算相关系数。如果某个特征有大量缺失,计算相关性时pandas默认会丢弃含有缺失值的行对,导致参与计算的有效样本量减少,相关系数的可靠性大幅下降。我遇到过某特征缺失率30%,算出来和目标变量相关系数高达0.6,补上缺失值后掉到0.1——完全是假象。

4.5 高相关性筛选和业务可解释性的平衡

最后聊一个偏经验的话题:筛选特征不能只盯着统计指标,还要考虑业务可解释性。

有些特征虽然和目标变量的相关性一般,但它们在业务上是核心变量,是业务方一定会追问的。比如在风控场景里,“负债收入比”可能和“月收入”高度相关,按统计应该二选一。但业务方需要明确的负债维度,这时候就不能死板地用相关性筛选把它删掉。

我的处理方式是:先跑完统计筛选,再人工过一遍结果,把业务上必须保留的特征加回来。统计方法帮你缩小候选范围,但最终决策一定要结合领域知识。模型再先进,最终解释权还是在人手里。这也是为什么我在很多项目里坚持先用高相关性筛选法打底,再在保留清单上做人工微调——效率和可解释性都能兼顾。

我个人在实际操作中的体会是:高相关性筛选法看起来简单,但真正用好它,需要你对业务有足够的理解。特征筛选不是一道纯粹的数学题,它是数据理解和业务判断的交汇点。每次跑完筛选流程,我都会把被剔除的特征整理成一张清单,写清楚删除原因,一方面是为了自己后续复盘,另一方面,业务方来问的时候也能给出明确的交代。这套流程我已经用了很久,建议你也试一试,第一次跑通之后,后面再遇到大宽表,你就知道该怎么下手了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询