特征越多模型效果就越好?这个想法在实践中往往站不住脚,因为过多的特征反而会带来过拟合、训练时间过长、模型难以解释等一堆麻烦。递归特征消除(RFE)就是用来解决这类问题的,算是特征选择里面比较靠谱的方法之一。
本文会详细介绍RFE 的工作原理,然后用 scikit-learn 跑一个完整的例子。
RFE 是什么
递归特征消除本质上是个反向筛选过程。它会先用全部特征训练模型,然后根据模型给出的重要性评分把最不重要的特征踢掉,接着用剩下的特征重新训练,如此反复直到达到设定的特征数量。
这可以理解成雕刻的过程,一点点削掉不重要的部分,最后留下对预测真正有用的核心特征。
RFE 比单变量特征选择高明的地方在于,它考虑了特征之间的交互关系。每次删掉特征后都会重新训练,这样能捕捉到特征组合的效果。
RFE 适用范围比较广,只要模型能给出特征重要性就能用。它会自己考虑特征之间怎么配合,这点很关键。删掉无关特征后模型泛化能力会更好,不容易过拟合。特征少了模型自然更好理解,训练和预测的速度也快。
实际操作
我们这次用 sklearn 自带的葡萄酒数据集。这个数据集有 13 个化学特征,任务是预测葡萄酒的类别。先把需要的库导入:
# importing necessary libraries
import numpy as np
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import RFE, RFECV
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
import seaborn as sns
# loading the wine dataset
wine = load_wine()
X = pd.DataFrame(wine.data, columns=wine.feature_names)
y = wine.target
print(f"Dataset shape: {X.shape}")
print(f"Number of classes: {len(np.unique(y))}")
print(f"\nFeature names:")
print('*'*15)
for col in X.columns:
print(col)
看下输出:

数据集有 178 个样本,13 个特征,3 个类别。接下来我们做数据准备和基线测试:
# train-test splitting
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# scaling the features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# baseline model including all features
baseline_model = LogisticRegression(max_iter=1000, random_state=42)
baseline_model.fit(X_train_scaled, y_train)
baseline_score = baseline_model.score(X_test_scaled, y_test)
print(f"Baseline accuracy with all {X.shape[1]} features: {baseline_score:.3f}")

用全部特征训练的逻辑回归准确率是 0.981,这个作为基准线。
固定特征数量的 RFE
现在用 RFE 筛选 5 个最重要的特征:
# initializing the estimator
estimator = LogisticRegression(max_iter=1000, random_state=42)
# creating and fitting the RFE object to select best 5 features
rfe = RFE(estimator, n_features_to_select=5, step=1)
rfe.fit(X_train_scaled, y_train)
# view the selected features
selected_features = X.columns[rfe.support_].tolist()
print(f"\nSelected features: {selected_features}")
# we can also get the feature rankings (where 1 is best)
feature_ranking = pd.DataFrame({
'feature': X.columns,
'ranking': rfe.ranking_
}).sort_values('ranking')
print("\nFeature Rankings:")
display(feature_ranking)
# model with selected features
X_train_rfe = rfe.transform(X_train_scaled)
X_test_rfe = rfe.transform(X_test_scaled)
rfe_model = LogisticRegression(max_iter=1000, random_state=69)
rfe_model.fit(X_train_rfe, y_train)
rfe_score = rfe_model.score(X_test_rfe, y_test)
print(f"\nRFE accuracy with {rfe.n_features_} features: {rfe_score:.4f}")
print(f"Accuracy difference: {rfe_score - baseline_score:.3f}")
结果如下:

选出了 5 个特征后准确率达到 0.9815,和用全部特征几乎没差别。也就是说特征数量直接砍掉了 60% 多,但模型性能基本没损失。
不同模型的表现
RFE 可以配合各种模型使用。比如说随机森林,看看选出来的特征有什么区别:
# RFE with Random Forest
rf_estimator = RandomForestClassifier(n_estimators=200, random_state=69)
rfe_rf = RFE(rf_estimator, n_features_to_select=5, step=1)
rfe_rf.fit(X_train_scaled, y_train)
# comparing Logistic Regression RFE and Random Forest RFE
lr_features = set(X.columns[rfe.support_])
rf_features = set(X.columns[rfe_rf.support_])
print("Features selected by Logistic Regression:")
print(lr_features)
print("\nFeatures selected by Random Forest:")
print(rf_features)
print("\nCommon features:")
print(lr_features.intersection(rf_features))
print("\nDifferent features:")
print(lr_features.symmetric_difference(rf_features))

两个模型选出来的特征有重叠也有差异。这很正常,因为不同模型看重的东西本来就不一样。逻辑回归更关注线性关系,而随机森林能捕捉更复杂的非线性模式。
特征排名可视化
再看看两个模型对特征的评价:
# creating a dataframe for easy plotting
comparison_df = pd.DataFrame({
'Feature': X.columns,
'LR_Ranking': rfe.ranking_,
'RF_Ranking': rfe_rf.ranking_,
'Selected_by_LR': rfe.support_,
'Selected_by_RF': rfe_rf.support_
})
# plot 1: feature rankings comparison
comparison_melted=comparison_df.melt(id_vars='Feature', value_vars=['LR_Ranking', 'RF_Ranking'],
var_name='Model', value_name='Ranking')
sns.barplot(data=comparison_melted,x='Feature', y='Ranking', hue='Model', palette=['darkblue','yellow'])
plt.grid(True, axis='y')
plt.xticks(rotation=90)
plt.yticks(np.arange(1, comparison_melted['Ranking'].max()+1))
plt.xlabel('Features')
plt.ylabel('Ranking (lower is better)')
plt.title('Feature Rankings by Model')
plt.show()
# Plot 2: selected features visualization using heatmap
selected_data = comparison_df[['Feature', 'Selected_by_LR', 'Selected_by_RF']].set_index('Feature').T
sns.heatmap(selected_data, annot=False, cmap='RdYlGn', cbar=False)
plt.title('Selected Features Matrix', fontsize=12, fontweight='bold')
plt.xlabel('Features', fontsize=11)
plt.ylabel('Model', fontsize=11)
plt.show()


柱状图显示了各个特征在不同模型中的排名,热力图直观地展示了哪些特征被选中。可以看到 proline、flavanoids 等几个特征在两个模型中都比较重要。
RFECV 自动找最优特征数
前面都是手动指定要保留 5 个特征。实际应用中很难事先知道该留多少个。所以RFECV 用交叉验证自动确定最优数量:
# create and fit RFECV object
rfecv=RFECV(
estimator=LogisticRegression(max_iter=1000, random_state=42),
step=1,
cv=5, # 5-fold cross-validation
scoring='accuracy',
min_features_to_select=1
)
rfecv.fit(X_train_scaled, y_train)
print(f"Optimal number of features: {rfecv.n_features_} with a score of {rfecv.cv_results_['mean_test_score'].max():.4f}")
print('The selected features are:')
forcolinX.columns[rfecv.support_]:
print(f" -> {col}")
# plotting number of selected features vs. cross-validation scores
plt.figure(figsize=(8, 5))
y1=rfecv.cv_results_['mean_test_score']
x1=rfecv.cv_results_['n_features']
ax=sns.lineplot(x=x1, y=y1)
plt.xlabel("Number of Features Selected")
plt.ylabel("Mean CV Accuracy")
plt.title("RFECV: Number of Features vs. CV Accuracy")
ax.axhline(y=np.max(y1), color='r', linestyle=':', label='Max CV Accuracy')
plt.xticks(x1)
plt.yticks(np.arange(0.75,1.01,0.02))
plt.grid(True)
plt.legend()
plt.show()
# creating model with optimal features
X_train_rfecv=rfecv.transform(X_train_scaled)
X_test_rfecv=rfecv.transform(X_test_scaled)
rfecv_model=LogisticRegression(max_iter=1000, random_state=42)
rfecv_model.fit(X_train_rfecv, y_train)
rfecv_score=rfecv_model.score(X_test_rfecv, y_test)
print(f"\nModel accuracy with {rfecv.n_features_} features: {rfecv_score:.4f}")
输出:


交叉验证结果显示 10 个特征时准确率最高,达到 0.9839。曲线图能清楚看到随着特征数量增加,准确率的变化趋势。通过图标可视化,比拍脑袋决定保留几个特征靠谱多了。
注意事项
特征标准化很重要,尤其是用距离相关的算法时。选择合适的基模型也关键,得确保它能给出有意义的特征重要性分数。数据量大的时候 RFE 跑起来会比较慢,所以可以调大 step 参数一次删多个特征。
交叉验证能避免特征选择过程中的过拟合,RFECV 的一个主要功能就是解决这个问题。最终模型的评估一定要在独立的测试集上做。有时候领域知识比算法选择更管用,别完全依赖自动化方法。
也可以试试不同的基模型也有价值,线性模型和树模型关注的点不同,选出的特征也会有差别。
总结
RFE 在特征选择这块确实好用。需要降维但又不想损失太多性能的时候,或者想搞清楚哪些特征最重要的时候,都可以考虑用它。
RFE 根据模型评分系统地删除特征;RFECV 能自动找到最优特征数量;不同模型可能选出不同特征;测试集验证不能少。
下次碰到高维数据的时候可以试试 RFE,说不定会有惊喜。
https://avoid.overfit.cn/post/2ef37f6acc184f2dbf8ae46fba3377bf
作者:Prathik C