动态

详情 返回 返回

sklearn 特徵選擇實戰:用 RFE 找到最優特徵組合 - 动态 详情

特徵越多模型效果就越好?這個想法在實踐中往往站不住腳,因為過多的特徵反而會帶來過擬合、訓練時間過長、模型難以解釋等一堆麻煩。遞歸特徵消除(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

user avatar infodator 头像 lab4ai 头像 pulangte 头像 thomas_59b947425fa95 头像
点赞 4 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.