[翻譯]特徵選擇:比特徵本身重要麼?
[翻譯]特徵選擇:比特徵本身重要麼?
翻譯:Feature Selection: Beyond feature importance?
作者:Dor Amir
關鍵詞:特徵工程(feature selection)
在機器學習中,特徵選擇就是選擇對於你的預測任務最有用的特徵的過程。儘管這聽起來很簡單,但這是在建立一個新的機器學習模型過程中最複雜的問題之一。
在本文中,我將與你分享一些方法,這些方法是我在Fiverr公司領導的一個專案中的研究。
你將得到一些關於我嘗試的基本方法和更復雜方法的一些想法,這些方法得到了最好的結果,就是移除60%的特徵,同時保持準確性和實現了模型的更魯棒性。我也將分享我們對於該演算法的改進。
圖1 通過特徵選擇,只保留40%的特徵,可以達到相同的效果
為什麼進行特徵選擇如此重要
如果你建立一個機器學習模型,你應該知道辨別哪些特徵是重要的,哪些是噪聲是很困難的。
移除噪聲特徵將有助於記憶體、計算成本和你模型的準確性。另外,通過移除一些特徵,將幫忙你的模型避免過擬合。
有時,一個特徵有自己業務上的含義,但是,這不意味著這個特徵可以幫助你進行預測。你需要記得,特徵在一個演算法中有用(例如決策樹),可能在另個演算法中就代表性不足(例如迴歸模型)。並不是所有特徵都是一樣的。
不相關或者部分相關的特徵可能對於模型的效能有負面的影響。特徵選擇和資料清洗應該是設計模型中的第一步,也是最重要的一步。
特徵選擇的方法
儘管有許多特徵選擇的技術,例如後向消除(backward elimination), 套索迴歸( lasso regression). 在本文中,我將分享3種方法,這些方法在進行更好的特徵選擇中是最有用的,每一個方法有它自身的優勢。
“All But X”
“All But X” 方法的名字是在Fiverr時給的。這個技術簡單但是有用。
-
你可以在迭代中反覆訓練和評估
-
在每一次迭代中,移除一個特徵。
如果你有大量的特徵,你可以移除一組特徵 -- 在Fiver,我們通常用不同的時間聚合得到特徵,如30天點選次數,60天點選次數等等,這個就是一組特徵。
-
根據準則來檢查你的評估指標。
這個技術的目標是檢測哪一組特徵對評估結果不起作用,或者甚至移除這一組特徵可以提高評估結果。
圖2 “All But X”方法執行全流程的流程圖。在執行完全部迭代後,我們比較哪組特徵對於模型的準確性沒有影響。
這個方法的問題在於通過一次移除一個特徵,你不會了解特徵之間的作用(非線性作用)。也許特徵X和特徵Y的組合在產生噪聲,不僅僅是特徵X.
特徵重要性+隨機特徵
我們嘗試的另個方法是使用特徵重要性,特徵重要性是大部分機器學習模型API都有的功能。
我們所做的不僅僅是選取頭部N個(top N)重要的特徵。我們向資料中添加了3個隨機特徵:
- 二進位制隨機特徵(0或1)
- 0到1的均勻分佈的隨機特徵
- 整數隨機特徵
在特徵重要性列表中,我們只選擇重要性比3個隨機特徵更高的特徵。
隨機特徵選取不同的分佈是重要的,不同的分佈有不同的效果。
在樹模型中,模型“更喜歡”連續型特徵(因為劃分),所以這些特徵將位於層次結構中更高的位置。因此,你需要將每一個特徵和其均勻分佈的隨機特徵進行比較。
Boruta
Boruta是一種特徵排名和選擇的演算法,是由Warsaw大學開發的。該演算法基於隨機森林,但也可以使用XGBoost和其他樹演算法。
在Fiverr時,我使用該演算法,對於XGBoost排名和分類模型有一些改進,下面將簡要介紹。
該演算法是上面提到的兩個方法的一種組合。
- 為資料集中每一個特徵建立一個"shadow"特徵,有相同的特徵值,但是對行進行混洗(shuffled)
- 迴圈執行,直到有停止條件之一
- 沒有移除任何特徵
- 移除足夠多的特徵 -- 我們想要移除60%的特徵
- 執行N次迭代 -- 我們限制迭代次數來避免陷入死迴圈
- 執行X次迭代 -- 我們一般用5,來消除模式中的隨機性
- 使用常規特徵和shadow特徵來訓練模型
- 儲存每個特徵的平均特徵重要性得分
- 移除所有比其shadow特徵得分低的特徵
Boruta虛擬碼
def _create_shadow(x):
"""
Take all X variables, creating copies and randomly shuffling them
:param x: the dataframe to create shadow features on
:return: dataframe 2x width and the names of the shadows for removing later
"""
x_shadow = x.copy()
for c in x_shadow.columns:
np.random.shuffle(x_shadow[c].values) # shuffle the values of each feature to all the features
# rename the shadow
shadow_names = ["shadow_feature_" + str(i + 1) for i in range(x.shape[1])]
x_shadow.columns = shadow_names
# Combine to make one new dataframe
x_new = pd.concat([x, x_shadow], axis=1)
return x_new, shadow_names
# Set up the parameters for running the model in XGBoost
param = booster_params
df = pd.DataFrame() # initial empty dataframe
for i in range(1, n_iterations + 1):
# Create the shadow variables and run the model to obtain importances
new_x, shadow_names = _create_shadow(x)
bst, df = _run_model(new_x, y, group, weights, param, num_boost_round, early_stopping_rounds, i == 1, df)
df = _check_feature_importance(bst, df, i, importance_type)
df[MEAN_COLUMN] = df.mean(axis=1)
# Split them back out
real_vars = df[~df['feature'].isin(shadow_names)]
shadow_vars = df[df['feature'].isin(
)]
# Get mean value from the shadows
mean_shadow = shadow_vars[MEAN_COLUMN].mean() * (perc / 100)
real_vars = real_vars[(real_vars[MEAN_COLUMN] > mean_shadow)]
criteria = _check_stopping_crietria(delta, real_vars, x)
return criteria, real_vars['feature']
圖3 Boruta執行流程圖,從建立shadows -- 訓練 -- 比較 -- 移除特徵並返回繼續。
Boruta 2.0
這是本文最好的部分,我們對於Boruta的改進。
我用原始模型的“簡短版本”來執行Boruta。通過獲取資料的樣本和使用較少數量的樹(我們使用的XGBoost),在沒有降低準確性的條件下,我們改善了原始Boruta的執行時間。
另一個改進是,我們用前面提到的隨機特徵來執行演算法。可以看到我們移除了資料集中所有的隨機特徵,這是一個很好的停止條件。
有了這些改進,對於模型在準確性上也沒有影響,但是在執行時間上有改進。通過移除,我們能將特徵從200+轉變到少於70。我們能看到模型在樹的數量和不同訓練期間的的穩定性。
我們也能看到在訓練集和驗證集上,對於損失的提升。
改進的Boruta和Boruta的優勢在於你在執行模型。在這種情況下,發現的有問題的特徵對你的模型有問題而不是一個不同的演算法。
總結
在本文中,你看到了3種不同的技術,該技術是關於對資料集進行特徵選擇和如何構建一個有效的預測模型的技術。你看到Boruta的實現,在執行時間上的改進和新增隨機特徵來進行合理性測試(sanity checks).
通過這些改進,我們的模型能執行地更快,更魯棒以及保持同樣的準確性,只用35%的原始特徵。
選擇最適合你的技術。請記住,特徵選擇(Feature Selection)能幫助改進準確性(accuracy),魯棒性(stability)和執行時間(runtime),並避免過擬合。更重要的是,用更少的特徵使除錯和解釋性更容易。