在機器學習的世界中,我常看到許多開發者直接將所有可用特徵丟入模型,期待演算法能自動找出重要的模式。然而,這種做法常導致模型過度複雜、訓練時間延長,甚至降低預測準確度。特徵選擇技術讓我們能夠在訓練前就識別並保留最有價值的特徵,為模型減輕不必要的負擔。
特徵選擇不僅能提升模型效能,還能降低計算資源需求,同時提高模型的可解釋性。在本文中,我將分享幾種實用的特徵選擇技術,幫助你最佳化器學習流程。
使用方差閾值過濾低訊息量特徵
數值特徵的方差閾值過濾
方差閾值過濾是最簡單的特徵選擇方法之一。這種技術根據一個簡單的假設:如果特徵的方差很小,那麼它可能對預測結果貢獻不大。當特徵的所有值都非常接近時,這個特徵可能不會提供模型所需的區分能力。
以下是如何使用方差閾值來過濾低變異的特徵:
# 載入必要的函式庫
from sklearn.feature_selection import VarianceThreshold
import numpy as np
# 建立特徵矩陣
features = np.array([
[0, 2, 0, 3],
[0, 1, 4, 3],
[0, 1, 1, 3],
[1, 1, 0, 2],
[0, 1, 0, 1]
])
# 建立方差閾值選擇器
thresholder = VarianceThreshold(threshold=0.5)
# 套用選擇器並轉換特徵矩陣
features_var_threshold = thresholder.fit_transform(features)
# 檢視結果
print("原始特徵數量:", features.shape[1])
print("選擇後特徵數量:", features_var_threshold.shape[1])
print("選擇的特徵:", features_var_threshold)
這段程式碼示範瞭如何使用 scikit-learn 的 VarianceThreshold
類別來過濾低方差特徵。我們首先建立一個包含 5 個樣本、4 個特徵的矩陣,然後設定方差閾值為 0.5。這意味著任何方差低於 0.5 的特徵都會被移除。
執行後,我們可以檢視每個特徵的方差:
# 檢視各特徵的方差
thresholder.fit(features).variances_
# 輸出:array([ 0.68112222, 0.18675067, 3.09242489, 0.57853156])
從輸出可以看出,第二個特徵的方差僅為 0.18675067,低於我們設定的閾值 0.5,因此被過濾掉了。最終保留了三個方差較高的特徵。
在使用方差閾值技術時,有兩點需要特別注意:
方差並非中心化的,它使用特徵本身的單位的平方。這意味著當特徵集包含不同單位的特徵時(例如一個特徵以年為單位,而另一個以元為單位),方差閾值過濾可能不適用。
方差閾值需要手動選擇,這需要依靠我們的判斷或使用模型選擇技術來確定最佳閾值。
此外,如果特徵已經被標準化(均值為零,方差為一),那麼方差閾值過濾就失去了意義:
# 載入標準化工具
from sklearn.preprocessing import StandardScaler
# 標準化特徵矩陣
scaler = StandardScaler()
features_std = scaler.fit_transform(features)
# 計算標準化後各特徵的方差
selector = VarianceThreshold()
selector.fit(features_std).variances_
# 輸出:array([ 1., 1., 1., 1.])
標準化後,所有特徵的方差都變成了 1.0,這時方差閾值過濾就無法區分特徵的重要性了。
二元特徵的方差閾值過濾
對於二元特徵(即值只有 0 或 1 的特徵),我們可以使用伯努利隨機變數的方差公式來設定更有意義的閾值:
# 載入必要的函式庫rom sklearn.feature_selection import VarianceThreshold
# 建立特徵矩陣:
# 特徵 0:80% 為類別 0
# 特徵 1:80% 為類別 1
# 特徵 2:60% 為類別 0,40% 為類別 1
features = [[0, 1, 0],
[0, 1, 1],
[0, 1, 0],
[0, 1, 1],
[1, 0, 0]]
# 使用伯努利分佈的方差公式設定閾值
thresholder = VarianceThreshold(threshold=(.75 * (1 - .75)))
selected_features = thresholder.fit_transform(features)
print("選擇的特徵:", selected_features)
在處理二元特徵時,我們可以利用伯努利隨機變數的方差公式:Var(x) = p(1-p)
,其中 p 是類別 1 的比例。當絕大多數觀測值都屬於同一類別,該特徵的方差會非常低,可能不包含太多有用訊息。
在這個例子中,我們設定閾值為 0.75 * (1 - 0.75) = 0.1875
。這意味著如果一個二元特徵的 0 或 1 的比例超過 75%(或低於 25%),該特徵將被移除。這種方法能有效過濾那些大多數值都相同的二元特徵。
實際應用中,我發現這種方法在處理稀疏特徵(如文字的詞袋模型)時特別有用,因為這些特徵通常有大量的 0 值和少量的 1 值。
處理高度相關的特徵
機器學習中的另一個常見問題是特徵間的高度相關性。當兩個特徵高度相關時,它們包含的訊息很相似,同時保留兩者可能是多餘的。處理高度相關特徵的簡單解決方案是:移除其中一個。
以下是識別和處理高度相關特徵的方法:
# 載入必要的函式庫
import pandas as pd
import numpy as np
# 建立包含兩個高度相關特徵的特徵矩陣
features = np.array([[1, 1, 1],
[2, 2, 0],
[3, 3, 1],
[4, 4, 0],
[5, 5, 1],
[6, 6, 0],
[7, 7, 1],
[8, 7, 0],
[9, 7, 1]])
# 將特徵矩陣轉換為 DataFrame
dataframe = pd.DataFrame(features)
# 建立相關矩陣(使用絕對值)
corr_matrix = dataframe.corr().abs()
# 選取相關矩陣的上三角部分
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape),
k=1).astype(np.bool))
# 找出相關係數大於 0.95 的特徵欄位索引
to_drop = [column for column in upper.columns if any(upper[column] > 0.95)]
# 移除這些特徵
reduced_features = dataframe.drop(dataframe.columns[to_drop], axis=1)
print(reduced_features.head(3))
這段程式碼演示瞭如何處理高度相關的特徵。我們首先建立一個特徵矩陣,其中第 0 列和第 1 列高度相關。然後,我們計算特徵之間的相關係數矩陣,並使用絕對值來忽略正負相關的差異。
為了避免重複檢查同一對特徵,我們只選取相關矩陣的上三角部分。接著,我們找出相關係數大於 0.95 的特徵,並將其中一個從特徵集中移除。
檢查相關矩陣:
# 相關矩陣
dataframe.corr()
'''
輸出:
0 1 2
0 1.000000 0.976103 0.000000
1 0.976103 1.000000 -0.034503
2 0.000000 -0.034503 1.000000
'''
可以看出,特徵 0 和特徵 1 之間的相關係數為 0.976103,超過了 0.95 的閾值。因此,我們移除了其中一個特徵(在這個例子中是特徵 1)。
這種方法在處理高維資料時特別有用,例如在金融分析或生物訊息學中,經常會遇到大量潛在相關的變數。透過移除高度相關的特徵,我們可以降低模型的複雜度,同時保留大部分有用的訊息。
移除分類別題中的無關特徵
當我們處理分類別題時,並非所有特徵都對預測目標類別有幫助。識別和移除那些與目標變數關聯性較低的特徵,可以簡化模型並可能提高其效能。
使用卡方檢驗選擇類別特徵
對於類別特徵,我們可以使用卡方檢驗來評估特徵與目標變數之間的相關性:
# 載入必要的函式庫
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
# 載入資料
iris = load_iris()
features = iris.data
target = iris.target
# 將特徵轉換為整數型態(類別資料)
features = features.astype(int)
# 選擇卡方統計量最高的兩個特徵
chi2_selector = SelectKBest(chi2, k=2)
features_kbest = chi2_selector.fit_transform(features, target)
# 顯示結果
print("原始特徵數量:", features.shape[1])
print("選擇後特徵數量:", features_kbest.shape[1])
卡方檢驗是一種統計方法,用於檢驗兩個類別變數之間的獨立性。在特徵選擇中,我們計算每個特徵與目標變數之間的卡方統計量,然後選擇統計量最高的特徵。
卡方統計量的計算公式為:
χ² = Σ[(O₁ - E₁)² / E₁]
其中 O₁ 是類別 i 中觀測到的樣本數量,E₁ 是在特徵與目標變數獨立的假設下,預期在類別 i 中的樣本數量。
卡方統計量越高,表示特徵與目標變數之間的關聯性越強。在這個例子中,我們使用 SelectKBest
來選擇卡方統計量最高的兩個特徵。
需要注意的是,卡方檢驗只適用於類別特徵。在使用之前,我們需要確保特徵是型別的,或者將數值特徵轉換為類別特徵。此外,所有特徵值必須是非負的,這也是為什麼我們在範例中將特徵轉換為整數型態。
使用 F 值選擇數值特徵
對於數值特徵,我們可以使用 ANOVA F 值來評估特徵與目標變數之間的相關性:
# 載入必要的函式庫
from sklearn.feature_selection import f_classif
# 選擇 F 值最高的兩個特徵
fvalue_selector = SelectKBest(f_classif, k=2)
features_kbest = fvalue_selector.fit_transform(features, target)
# 顯示結果
print("原始特徵數量:", features.shape[1])
print("選擇後特徵數量:", features_kbest.shape[1])
ANOVA(方差分析)F 值是一種統計方法,用於比較不同組之間的均值差異。在特徵選擇中,我們計算每個特徵在不同目標類別下的 F 值,然後選擇 F 值最高的特徵。
F 值越高,表示該特徵在不同類別之間
特徵選擇與模型評估:提升機器學習效能的關鍵技術
在機器學習專案中,我常發現資料科學家過度關注複雜模型的建構,卻忽略了兩個能大幅提升效能的關鍵環節:特徵選擇與模型評估。在本文中,我將分享如何透過數值特徵的統計分析、遞迴特徵消除以及交叉驗證等技術,建立更精確、更高效的機器學習模型。
數值特徵的統計檢定:F值分析
當處理數值特徵時,我們需要判斷哪些特徵對於預測目標變數真正有幫助。F值統計(ANOVA F-value)是一種強大的工具,能檢測特徵與目標變數之間的關聯性。
F值分析的核心思想是檢測:當我們按目標變數分組時,各組的特徵平均值是否有顯著差異。舉例來說,假設我們有一個二元目標變數「性別」和一個數值特徵「測驗分數」,F值會告訴我們男性的平均測驗分數是否與女性的不同。如果沒有差異,那麼測驗分數對預測性別沒有幫助,這個特徵就不相關。
在實務中,我常使用scikit-learn的f_classif
函式來計算F值,特別是處理分類別題時。這個簡單的統計檢定能快速篩選出對預測有實質幫助的特徵。
遞迴特徵消除:自動選擇最佳特徵集
在建構模型時,我們常面臨一個問題:到底應該保留哪些特徵?遞迴特徵消除(Recursive Feature Elimination, RFE)提供了一個系統化的答案。
遞迴特徵消除的核心原理
遞迴特徵消除的運作方式相當直觀:
- 使用所有特徵訓練一個模型
- 找出最不重要的特徵並移除
- 用剩餘的特徵重新訓練模型
- 重複這個過程,直到模型效能開始下降
結合交叉驗證(Cross-Validation, CV)的遞迴特徵消除(RFECV)更為強大,它能自動找出最佳的特徵數量。
讓我們看如何在Python中實作這個技術:
# 載入必要的函式庫
import warnings
from sklearn.datasets import make_regression
from sklearn.feature_selection import RFECV
from sklearn import datasets, linear_model
# 忽略一些無害的警告
warnings.filterwarnings(action="ignore", module="scipy", message="^internal gelsd")
# 生成特徵矩陣、目標向量
features, target = make_regression(n_samples=10000,
n_features=100,
n_informative=2,
random_state=1)
# 建立線性迴歸模型
ols = linear_model.LinearRegression()
# 遞迴消除特徵
rfecv = RFECV(estimator=ols, step=1, scoring="neg_mean_squared_error")
rfecv.fit(features, target)
selected_features = rfecv.transform(features)
這段程式碼展示瞭如何使用RFECV進行特徵選擇。首先,我們生成了一個有100個特徵的人薪水料集,但其中只有2個特徵真正包含資訊。接著,我們建立一個線性迴歸模型作為評估器,並設定RFECV使用均方誤差作為評分標準。
step=1
引數表示每次迭代移除一個特徵。當RFECV完成後,我們可以使用transform()
方法取得經過選擇的特徵子集。
執行完上述程式碼後,我們可以檢視RFECV為我們選出了多少個特徵:
# 最佳特徵數量
rfecv.n_features_
# 輸出: 5
我們還可以檢視哪些特徵被保留下來:
# 哪些特徵最佳
rfecv.support_
甚至可以檢視特徵的重要性排名:
# 特徵排名,從最佳(1)到最差
rfecv.ranking_
為何遞迴特徵消除如此有效?
在我多年的機器學習實踐中,發現遞迴特徵消除特別適合處理以下場景:
- 高維特徵空間:當特徵數量龐大時,手動選擇特徵幾乎不可能
- 多重共線性問題:當特徵之間高度相關時,RFE能幫助選出最具代表性的特徵
- 計算資源有限:減少特徵數量能顯著降低模型訓練與推論時間
RFE的核心思想是透過模型的引數(或權重)來判斷特徵重要性。對於線性模型,係數的絕對值大小直接反映了特徵的重要性。每次迭代中,RFE移除係數最小的特徵,這個過程持續到找到最佳特徵子集為止。
值得注意的是,在使用RFE之前,我通常會先對特徵進行標準化處理,確保所有特徵在相同的尺度上比較。
模型評估:交叉驗證的藝術
建立機器學習模型相對容易,但評估其真實效能卻是一門藝術。在探討各種學習演算法前,我們必須先理解如何正確評估模型品質。
為什麼需要交叉驗證?
初學者常犯的錯誤是在訓練資料上評估模型效能。這種方法存在根本性缺陷:我們的目標不是評估模型在已知資料上的表現,而是評估它在未曾見過的資料上的預測能力。
交叉驗證(Cross-Validation)提供了一個強大的框架,用於評估模型在新資料上的泛化能力。
實作交叉驗證評估
以下是如何使用scikit-learn實作完整的交叉驗證流程:
# 載入必要的函式庫
from sklearn import datasets
from sklearn import metrics
from sklearn.model_selection import KFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
# 載入數字資料集
digits = datasets.load_digits()
# 建立特徵矩陣與目標向量
features = digits.data
target = digits.target
# 建立標準化器
standardizer = StandardScaler()
# 建立邏輯迴歸物件
logit = LogisticRegression()
# 建立一個先標準化再執行邏輯迴歸的管道
pipeline = make_pipeline(standardizer, logit)
# 建立k折交叉驗證
kf = KFold(n_splits=10, shuffle=True, random_state=1)
# 進行k折交叉驗證
cv_results = cross_val_score(pipeline, # 管道
features, # 特徵矩陣
target, # 目標向量
cv=kf, # 交叉驗證技術
scoring="accuracy", # 損失函式
n_jobs=-1) # 使用所有CPU核心
# 計算平均值
mean_accuracy = cv_results.mean()
print(f"交叉驗證平均準確率: {mean_accuracy:.4f}")
這段程式碼展示了一個完整的模型評估流程。我們使用了手寫數字識別資料集,並建立了一個處理管道(pipeline),該管道先對資料進行標準化,再應用邏輯迴歸模型。
交叉驗證的核心在於KFold
物件,它將資料集分割為10個折疊(folds)。在評估過程中,每次使用9個折疊訓練模型,然後在剩下的1個折疊上測試。這個過程重複10次,每個折疊都會作為測試集一次。
cross_val_score
函式自動執行整個交叉驗證過程,並回傳每次評估的效能指標(這裡使用準確率)。最後,我們計算這些指標的平均值,得到模型的整體效能評估。
交叉驗證的深層原理
交叉驗證的核心思想可以概括為:
- 資料分割:將資料集分成訓練集和測試集
- 模型訓練:使用訓練集訓練模型
- 模型評估:在測試集上評估模型效能
- 重複過程:多次重複上述步驟,使用不同的資料分割
這個過程有效模擬了模型在實際應用中的表現,因為測試資料對模型來說是「未見過」的。
在我的實踐中,交叉驗證不僅用於評估最終模型,還用於:
- 超引數調整:找出最佳的模型引數設定
- 特徵選擇:如上文的RFECV
- 模型比較:客觀比較不同演算法的效能
模型評估指標的選擇
在交叉驗證過程中,選擇合適的評估指標至關重要。不同的問題需要不同的評估標準:
- 分類別題:準確率(accuracy)、精確率(precision)、召回率(recall)、F1分數、AUC-ROC
- 迴歸問題:均方誤差(MSE)、平均絕對誤差(MAE)、R²分數
在上面的例子中,我們使用了準確率作為評估指標,這適合於平衡的分類別題。但對於不平衡的分類別題,準確率可能會產生誤導性結果,此時應考慮使用F1分數或AUC-ROC。
綜合應用:特徵選擇與模型評估的結合
在實際專案中,我經常將特徵選擇和交叉驗證結合使用,以建立最佳模型。以下是這種組合方法的基本流程:
- 使用交叉驗證評估基準模型效能
- 應用RFECV等技術進行特徵選擇
- 使用選定的特徵子集重新訓練模型
- 再次使用交叉驗證評估最佳化的模型
這種方法確保了我們的特徵選擇過程不會導致過擬合,同時也提供了可靠的模型效能評估。
特徵選擇與模型評估的進階技巧
嵌入式特徵選擇
除了遞迴特徵消除外,還有一些模型本身就包含了特徵選擇機制,如Lasso迴歸和決策樹。這些模型在訓練過程中自動執行特徵選擇,被稱為「嵌入式特徵選擇」。
巢狀交叉驗證
當我們需要同時進行模型選擇和評估時,巢狀交叉驗證(Nested Cross-Validation)是一個強大的技術。它包含內層交叉驗證(用於模型選擇和超引數調整)和外層交叉驗證(用於無偏估計模型效能)。
時間序列資料的特殊考量
對於時間序列資料,標準的k折交叉驗證可能不適用,因為它打破了資料的時間順序。在這種情況下,時間序列交叉驗證(Time Series Cross-Validation)是更適合的選擇,它確保測試資料總是在時間上晚於訓練資料。
特徵選擇和模型評估是機器學習工作流程中的關鍵環節,它們對於建立高效、準確的預測模型至關重要。透過使用F值統計、遞迴特徵消除和交叉驗證等技術,我們可以系統地最佳化型,提高其在實際應用中的效能。
記住,建立模型很容易,但建立高品質的模型卻需要深思熟慮的特徵選擇和嚴謹的評估方法。在實際專案中,這些技術往往比選擇哪種學習演算法更為重要,因為它們直接影響模型的泛化能力和實用價值。
透過本文介紹的方法,你應該能夠更有信心地建立和評估機器學習模型,確保它們在實際應用中表現出色。無論是預測客戶行為、識
模型評估的藝術:超越表面資料
在機器學習的世界中,建立模型只是旅程的一半;另一半是正確評估這些模型的效能。當我深入研究模型評估技術時,發現許多開發者往往過於關注模型的複雜性,而忽略了評估的嚴謹性。一個複雜但評估不當的模型,就像一座建在沙灘上的城堡,表面華麗卻經不起實際應用的考驗。
讓我們探討幾種評估模型的核心方法,這些方法在我多年的實踐中被證明是最可靠的。
交叉驗證:模型評估的金標準
驗證集的侷限性
最直觀的模型評估方法是將資料分割成訓練集和測試集。在這種方法中,我們將一部分資料保留作為測試集,使用其餘資料訓練模型,然後評估模型在測試集上的表現。
然而,這種簡單的驗證方法存在兩個主要缺陷:
- 模型效能高度依賴於哪些觀測值被選入測試集
- 模型既沒有使用所有可用資料進行訓練,也沒有在所有可用資料上進行評估
k折交叉驗證:更強大的評估策略
k折交叉驗證(K-Fold Cross-Validation,KFCV)是一種能夠克服上述缺點的策略。在這種方法中,我們將資料分成k個部分(稱為「折」)。模型使用k-1個折進行訓練,然後在剩下的一折上進行測試。這個過程重複k次,每次使用不同的折作為測試集。最後,我們取k次迭代的平均效能作為模型的整體評估。
以下是使用scikit-learn進行10折交叉驗證的範例:
# 使用10折交叉驗證並輸出評估分數
cv_results = cross_val_score(model, features, target, cv=10)
# 檢視所有10折的分數
print(cv_results)
# 輸出結果類別於:
# array([0.97222222, 0.97777778, 0.95555556, 0.95, 0.95555556,
# 0.98333333, 0.97777778, 0.96648045, 0.96089385, 0.94972067])
這段程式碼執行了10折交叉驗證並列印了每一折的評估分數。cross_val_score
函式接收模型、特徵矩陣、目標向量以及折數作為引數。輸出的陣列顯示了每一折的準確率,大多數折的準確率都超過了95%,表明模型表現相當穩定。
使用交叉驗證時的關鍵考量
在使用k折交叉驗證時,有三個重要的考慮因素:
獨立同分布假設:KFCV假設每個觀測值的建立獨立於其他觀測值。如果資料符合這一假設,在分配觀測值到不同折時進行隨機打亂是個好主意。在scikit-learn中,我們可以設定
shuffle=True
來實作這一點。分層抽樣:當評估分類別時,通常希望每個折包含大致相同比例的各個目標類別的觀測值(稱為分層k折)。例如,如果我們的目標向量包含性別,與80%的觀測值是男性,那麼每個折也應該包含80%的男性和20%的女性觀測值。在scikit-learn中,我們可以使用
StratifiedKFold
替代KFold
來實作分層k折交叉驗證。正確的資料預處理:使用驗證集或交叉驗證時,必須根據訓練集進行資料預處理,然後將這些轉換應用於訓練集和測試集。例如,當我們擬合標準化物件時,只計算訓練集的均值和方差,然後使用這些引數來轉換訓練集和測試集。
以下是正確的資料預處理範例:
# 匯入函式庫
from sklearn.model_selection import train_test_split
# 建立訓練集和測試集
features_train, features_test, target_train, target_test = train_test_split(
features, target, test_size=0.1, random_state=1)
# 只使用訓練集擬合標準化器
standardizer.fit(features_train)
# 將轉換應用於訓練集和測試集
features_train_std = standardizer.transform(features_train)
features_test_std = standardizer.transform(features_test)
這段程式碼展示了正確的資料預處理流程。首先,使用train_test_split
函式將資料分為訓練集和測試集,測試集佔總資料的10%。然後,僅使用訓練集來擬合標準化器(計算均值和標準差)。最後,使用這個擬合好的標準化器來轉換訓練集和測試集。這種方法避免了測試集訊息洩漏到訓練過程中。
使用Pipeline簡化交叉驗證
scikit-learn的pipeline包使得在使用交叉驗證技術時正確預處理資料變得容易。我們首先建立一個包含資料預處理步驟和模型訓練的管道:
# 建立管道
pipeline = make_pipeline(standardizer, logit)
# 執行k折交叉驗證
cv_results = cross_val_score(
pipeline, # 管道
features, # 特徵矩陣
target, # 目標向量
cv=kf, # 交叉驗證技術
scoring="accuracy", # 損失函式
n_jobs=-1) # 使用所有CPU核心
這段程式碼建立了一個處理管道,包含標準化器和邏輯迴歸模型。然後使用cross_val_score
函式執行交叉驗證,其中pipeline
是我們建立的管道,features
和target
是特徵矩陣和目標向量,kf
指定了交叉驗證的方法,scoring
引數定義了評估指標(這裡是準確率),n_jobs=-1
告訴scikit-learn使用所有可用的CPU核心來加速操作。
cross_val_score
還有一些值得注意的引數:
cv
引數確定我們的交叉驗證技術。k折是最常見的,但還有其他方法,如留一交叉驗證(Leave-One-Out Cross-Validation),其中折數k等於觀測值的數量。scoring
引數定義了我們的成功指標,本章其他部分會討論許多不同的指標。n_jobs=-1
告訴scikit-learn使用每一個可用的核心。例如,如果你的電腦有四個核心(筆記型電腦常見的數量),那麼scikit-learn將同時使用所有四個核心來加速操作。