Kaggle技巧總結
Introduction
首先簡單介紹一些關於 Kaggle 比賽的知識:
?不同比賽有不同的任務,分類、回歸、推薦、排序等。比賽開始後訓練集和測試集就會開放下載。
?比賽通常持續 2 ~ 3 個月,每個隊伍每天可以提交的次數有限,通常為 5 次。
?比賽結束前一周是一個 Deadline,在這之後不能再組隊,也不能再新加入比賽。所以想要參加比賽請務必在這一 Deadline 之前有過至少一次有效的提交。
?一般情況下在提交後會立刻得到得分的反饋。不同比賽會采取不同的評分基準,可以在分數欄最上方看到使用的評分方法。
?反饋的分數是基於測試集的一部分計算的,剩下的另一部分會被用於計算最終的結果。所以最後排名會變動。
?自己做的 Cross Validation 得到的分數一般稱為?CV?或是?Local CV。一般來說?CV?的結果比?LB?要可靠。
Data preprocessing
數據預處理,就是將數據處理下,為模型輸入做準備,其中包括:
?處理missing value:主要有以下幾種方法
1.如果missing value占總體的比例非常小,那麽直接填入平均值或者眾數
2.如果missing value所占比例不算小也不算大,那麽可以考慮它跟其他特征的關系,如果關系明顯,那麽直接根據其他特征填入;也可以建立簡單的模型,比如線性回歸,隨機森林等。
?處理Outlier:
1.通過畫圖,找出異常值
2.通過聚類等找出異常值
?處理categorical feature:一般就是通過dummy variable的方式解決,也叫one hot encode,可以通過pandas.get_dummies()或者 sklearn中preprocessing.OneHotEncoder(),
另外在處理categorical feature有兩點值得註意:
1.如果特征中包含大量需要做dummy variable處理的,那麽很可能導致得到一個稀疏的dataframe,這時候最好用下PCA做降維處理。
?近期在kaggle成功的案例中發現,對於類別特征,在模型中加入tf-idf(term frequency–inverse document frequency)總是有效果的。
?還有個方法叫“Leave-one-out” encoding,也可以處理類別特征種類過多的問題,實測效果不錯。
?有些 Float 變量可能是從未知的 Int 變量轉換得到的,這個過程中發生精度損失會在數據中產生不必要的?Noise,即兩個數值原本是相同的卻在小數點後某一位開始有不同。這對 Model 可能會產生很負面的影響,需要設法去除或者減弱 Noise。
下面給兩個例子:
Outlier
Dummy Variables
對於 Categorical Variable,常用的做法就是?One-hot encoding。即對這一變量創建一組新的偽變量,對應其所有可能的取值。這些變量中只有這條數據對應的取值為 1,其他都為 0。
如下,將原本有 7 種可能取值的?Weekdays?變量轉換成 7 個 Dummy Variables。
要註意,當變量可能取值的範圍很大(比如一共有成百上千類)時,這種簡單的方法就不太適用了。
Feature Engineering
有人總結 Kaggle 比賽是?“Feature 為主,調參和 Ensemble 為輔”,說的很有道理。Feature Engineering 能做到什麽程度,取決於對數據領域的了解程度。比如在數據包含大量文本的比賽中,常用的 NLP 特征就是必須的。怎麽構造有用的 Feature,是一個不斷學習和提高的過程。
一般來說,當一個變量從直覺上來說對所要完成的目標有幫助,就可以將其作為 Feature。至於它是否有效,最簡單的方式就是通過圖表來直觀感受。比如:
在對一些基礎的特征進行生成之後,測試的思路我後來發現並不是很好,因為是通過新增加一個或幾個feature,如果cv分數上去了,就增加這個feature,如果cv分數沒有上去,就舍棄這個feature,也就是相當於貪心驗證。這樣做的弊處在於,如果之前被舍棄的feature和之後被舍棄的feature聯合在一起才會有正面影響,就相當於你錯過了兩個比較好的feature。因此特征的選擇和聯合顯得非常關鍵。
總結一下比賽中有哪些“好”的feature,為以後的比賽提供靈感和經驗。
?數值型feature的簡單加減乘除
這個乍一看仿佛沒有道理可言,但是事實上卻能挖掘出幾個feature之間的內在聯系,比如在比賽Two Sigma Connect: Rental Listing Inquiries中提供了bathrooms和bedrooms的數量,以及價格price,合租用戶可能會更關心每個臥室的價格,即bathrooms / price,也會關心是不是每個房間都會有一個衛生間bathrooms / price ,這些數值型feature之間通過算數的手段建立了聯系,從而挖掘出了feature內部的一些價值,分數也就相應地上去了。
?高勢集類別(High Categorical)進行經驗貝葉斯轉換成數值feature
什麽是High Categorical的特征呢?一個簡單的例子就是郵編,有100個城市就會有好幾百個郵編,有些房子坐落在同一個郵編下面。很顯然隨著郵編的數量增多,如果用簡單的one-hot編碼顯然效果不太好,因此有人就用一些統計學思想(經驗貝葉斯)將這些類別數據進行一個map,得到的結果是數值數據。在這場比賽中有人分享了一篇paper裏面就提到了具體的算法。詳細就不仔細講了,用了這個encoding之後,的確效果提升了很多。那麽這場比賽中哪些數據可以進行這樣的encoding呢,只要滿足下面幾點:1. 會重復,2. 根據相同的值分組會分出超過一定數量(比如100)的組。
?時間特征
針對於時間數據來講,提取年、月、日、星期等可能還是不夠的,有另外一些points可以去思考,用戶的興趣跟發布時間的久遠是否有關系?可以構造如下的feature來進行測試:
data["latest"] = (data["created"]- data["created"].min())
data["passed"] = (data["created"].max()- data["created"])
另外針對於時間特征還可以用可視化的方式來與其他特征建立聯系,比如我們觀察其他字段與時間變化到底有怎樣的聯系
?地理位置特征
想到地理位置,就會想到聚類,一個簡單的方式將每個位置數據劃分到同一塊區域中去;除了聚類以外,算出幾個中心點坐標,計算曼哈頓距離或者歐式距離可能都會有神奇的效果。
?文本特征
文本類數據主要包括提取關鍵詞、情感分析、word embedding聚類之類等方法,也可以采用深度網絡方法進行特征提取,分析文本特征與目標字段之間的關系。
?圖片特征
圖片類數據特征還是需要通過CNN方式進行提取。
?稀疏特征集
其實就相當於一系列標簽,不同標簽的個數也是挺多的,本次比賽我只是簡單地采用了counterEncoding的方式進行one-hot編碼。值得一提的是,有些標簽是可以合並的,比如cat allowed 和 dog allowed可以合並成為 pet allowed,我在這場比賽中手工地合並了一些feature數據,最終結果略微有所提升。
?特征重要程度(feature importance)
在樹結構的分類器比如randomforest、xgboost中最後能夠對每個特征在分類上面的重要程度進行一個評估。這時候如果已經選定了一些feature進行訓練了之後,查看feature importance的反饋是非常重要的,比如本場比賽制勝的關鍵是運用manager_id這個feature,而它的feature importance反饋結果也是非常高。通過對重要特征的重新再提取特征,能夠發現很多有意思的新特征,這才是用FE打好一場比賽的關鍵所在。
Feature Selection
總的來說,我們應該生成盡量多的 Feature,相信 Model 能夠挑出最有用的 Feature。但有時先做一遍 Feature Selection 也能帶來一些好處:
?Feature 越少,訓練越快。
?有些 Feature 之間可能存在線性關系,影響 Model 的性能。
?通過挑選出最重要的 Feature,可以將它們之間進行各種運算和操作的結果作為新的 Feature,可能帶來意外的提高。
Feature Selection 最實用的方法也就是看 Random Forest 訓練完以後得到的?Feature Importance?了。其他有一些更復雜的算法在理論上更加 Robust,但是缺乏實用高效的實現,比如這個。從原理上來講,增加 Random Forest 中樹的數量可以在一定程度上加強其對於 Noisy Data 的 Robustness。
看 Feature Importance 對於某些數據經過脫敏處理的比賽尤其重要。這可以免得你浪費大把時間在琢磨一個不重要的變量的意義上。
Feature Encoding
這裏用一個例子來說明在一些情況下 Raw Feature 可能需要經過一些轉換才能起到比較好的效果。
假設有一個 Categorical Variable 一共有幾萬個取值可能,那麽創建 Dummy Variables 的方法就不可行了。這時一個比較好的方法是根據 Feature Importance 或是這些取值本身在數據中的出現頻率,為最重要(比如說前 95% 的 Importance)那些取值(有很大可能只有幾個或是十幾個)創建 Dummy Variables,而所有其他取值都歸到一個“其他”類裏面。
Model Selection
準備好 Feature 以後,就可以開始選用一些常見的模型進行訓練了。Kaggle 上最常用的模型基本都是基於樹的模型:
?Gradient Boosting
?Random Forest
?Extra Randomized Trees
以下模型往往在性能上稍遜一籌,但是很適合作為 Ensemble 的 Base Model。SVM
?Linear Regression
?Logistic Regression
?Neural Networks
以上這些模型基本都可以通過?sklearn?來使用。
當然,這裏不能不提一下?Xgboost。Gradient Boosting?本身優秀的性能加上?Xgboost?高效的實現,使得它在 Kaggle 上廣為使用。幾乎每場比賽的獲獎者都會用?Xgboost?作為最終 Model 的重要組成部分。在實戰中,我們往往會以 Xgboost 為主來建立我們的模型並且驗證 Feature 的有效性。
Model Training
在訓練時,我們主要希望通過調整參數來得到一個性能不錯的模型。一個模型往往有很多參數,但其中比較重要的一般不會太多。比如對?sklearn?的?RandomForestClassifier?來說,比較重要的就是隨機森林中樹的數量?n_estimators?以及在訓練每棵樹時最多選擇的特征數量?max_features。所以我們需要對自己使用的模型有足夠的了解,知道每個參數對性能的影響是怎樣的。
通常我們會通過一個叫做?Grid Search?的過程來確定一組最佳的參數。其實這個過程說白了就是根據給定的參數候選對所有的組合進行暴力搜索。
param_grid = {‘n_estimators‘: [300, 500], ‘max_features‘: [10, 12, 14]}
model = grid_search.GridSearchCV(estimator=rfr, param_grid=param_grid, n_jobs=1, cv=10, verbose=20, scoring=RMSE)
model.fit(X_train, y_train)
順帶一提,Random Forest 一般在?max_features?設為 Feature 數量的平方根附近得到最佳結果。
這裏要重點講一下 Xgboost 的調參。通常認為對它性能影響較大的參數有:
oeta:每次叠代完成後更新權重時的步長。越小訓練越慢。
onum_round:總共叠代的次數。
osubsample:訓練每棵樹時用來訓練的數據占全部的比例。用於防止 Overfitting。
ocolsample_bytree:訓練每棵樹時用來訓練的特征的比例,類似?RandomForestClassifier?的?max_features。
omax_depth:每棵樹的最大深度限制。與 Random Forest 不同,Gradient Boosting 如果不對深度加以限制,最終是會 Overfit 的。
oearly_stopping_rounds:用於控制在 Out Of Sample 的驗證集上連續多少個叠代的分數都沒有提高後就提前終止訓練。用於防止 Overfitting。
Hyperparameter
?網格搜索
網格搜索可能是應用最廣泛的超參數搜索算法了,因為它確實很簡單。網格搜索通過查找搜索範圍內的所有的點,來確定最優值。它返回目標函數的最大值或損失函數的最小值。給出較大的搜索範圍,以及較小的步長,網格搜索是一定可以找到全局最大值或最小值的。但是,網格搜索一個比較大的問題是,它十分消耗計算資源,特別是,需要調優的超參數比較多的時候(例如,隨機森林裏有8個左右
?隨機搜索
隨機搜索的思想和網格搜索比較相似,只是不再測試上界和下界之間的所有值,只是在搜索範圍中隨機取樣本點。它的理論依據是,如果隨即樣本點集足夠大,那麽也可以找到全局的最大或最小值,或它們的近似值。通過對搜索範圍的隨機取樣,隨機搜索一般會比網格搜索要快一些。但是和網格搜索的快速版(非自動版)相似,結果也是沒法保證的
?基於梯度的優化
?貝葉斯優化
貝葉斯優化尋找使全局達到最值的參數時,使用了和網格搜索、隨機搜索完全不同的方法。網格搜索和隨機搜索在測試一個新的點時,會忽略前一個點的信息。而貝葉斯優化充分利用了這個信息。貝葉斯優化的工作方式是通過對目標函數形狀的學習,找到使結果向全局最大值提升的參數。它學習目標函數形狀的方法是,根據先驗分布,假設一個搜集函數。在每一次使用新的采樣點來測試目標函數時,它使用這個信息來更新目標函數的先驗分布。然後,算法測試由後驗分布給出的,全局最值最可能出現的位置的點。
對於貝葉斯優化,一個主要需要註意的地方,是一旦它找到了一個局部最大值或最小值,它會在這個區域不斷采樣,所以它很容易陷入局部最值。為了減輕這個問題,貝葉斯優化算法會在勘探和開采(exploration and exploitation)中找到一個平衡點。
Cross Validation
Cross Validation?是非常重要的一個環節。它讓你知道你的 Model 有沒有 Overfit,是不是真的能夠 Generalize 到測試集上。在很多比賽中?Public LB?都會因為這樣那樣的原因而不可靠。當你改進了 Feature 或是 Model 得到了一個更高的?CV?結果,提交之後得到的?LB?結果卻變差了,一般認為這時應該相信 CV 的結果。當然,最理想的情況是多種不同的?CV?方法得到的結果和?LB?同時提高,但這樣的比賽並不是太多。
在數據的分布比較隨機均衡的情況下,5-Fold CV?一般就足夠了。如果不放心,可以提到?10-Fold。但是 Fold 越多訓練也就會越慢,需要根據實際情況進行取舍。
很多時候簡單的?CV?得到的分數會不大靠譜,Kaggle 上也有很多關於如何做?CV的討論。比如這個。但總的來說,靠譜的?CV?方法是 Case By Case 的,需要在實際比賽中進行嘗試和學習,這裏就不再(也不能)敘述了。
Ensemble Generation
Ensemble Learning?是指將多個不同的 Base Model 組合成一個 Ensemble Model 的方法。它可以同時降低最終模型的 Bias 和 Variance(證明可以參考這篇論文,我最近在研究類似的理論,可能之後會寫新文章詳述),從而在提高分數的同時又降低 Overfitting 的風險。在現在的 Kaggle 比賽中要不用 Ensemble 就拿到獎金幾乎是不可能的。
常見的 Ensemble 方法有這麽幾種:
oBagging:使用訓練數據的不同隨機子集來訓練每個 Base Model,最後進行每個 Base Model 權重相同的 Vote。也即 Random Forest 的原理。
oBoosting:叠代地訓練 Base Model,每次根據上一個叠代中預測錯誤的情況修改訓練樣本的權重。也即 Gradient Boosting 的原理。比 Bagging 效果好,但更容易 Overfit。
oBlending:用不相交的數據訓練不同的 Base Model,將它們的輸出取(加權)平均。實現簡單,但對訓練數據利用少了。
oStacking:接下來會詳細介紹。
從理論上講,Ensemble 要成功,有兩個要素:
oBase Model 之間的相關性要盡可能的小。這就是為什麽非 Tree-based Model 往往表現不是最好但還是要將它們包括在 Ensemble 裏面的原因。Ensemble 的 Diversity 越大,最終 Model 的 Bias 就越低。
oBase Model 之間的性能表現不能差距太大。這其實是一個?Trade-off,在實際中很有可能表現相近的 Model 只有寥寥幾個而且它們之間相關性還不低。但是實踐告訴我們即使在這種情況下 Ensemble 還是能大幅提高成績。
詳細ensemble內容參考該博客https://mlwave.com/kaggle-ensembling-guide/
Stacking
相比 Blending,Stacking 能更好地利用訓練數據。以 5-Fold Stacking 為例,它的基本原理如圖所示:
整個過程很像 Cross Validation。首先將訓練數據分為 5 份,接下來一共 5 個叠代,每次叠代時,將 4 份數據作為 Training Set 對每個 Base Model 進行訓練,然後在剩下一份 Hold-out Set 上進行預測。同時也要將其在測試數據上的預測保存下來。這樣,每個 Base Model 在每次叠代時會對訓練數據的其中 1 份做出預測,對測試數據的全部做出預測。5 個叠代都完成以後我們就獲得了一個?#訓練數據行數 x #Base Model 數量?的矩陣,這個矩陣接下來就作為第二層的 Model 的訓練數據。當第二層的 Model 訓練完以後,將之前保存的 Base Model 對測試數據的預測(因為每個 Base Model 被訓練了 5 次,對測試數據的全體做了 5 次預測,所以對這 5 次求一個平均值,從而得到一個形狀與第二層訓練數據相同的矩陣)拿出來讓它進行預測,就得到最後的輸出。
這裏是參考代碼:
class Ensemble(object):
def __init__(self, n_folds, stacker, base_models):
self.n_folds = n_folds
self.stacker = stacker
self.base_models = base_models
def fit_predict(self, X, y, T):
X = np.array(X)
y = np.array(y)
T = np.array(T)
folds = list(KFold(len(y), n_folds=self.n_folds, shuffle=True, random_state=2016))
S_train = np.zeros((X.shape[0], len(self.base_models)))
S_test = np.zeros((T.shape[0], len(self.base_models)))
for i, clf in enumerate(self.base_models):
S_test_i = np.zeros((T.shape[0], len(folds)))
for j, (train_idx, test_idx) in enumerate(folds):
X_train = X[train_idx]
y_train = y[train_idx]
X_holdout = X[test_idx]
# y_holdout = y[test_idx]
clf.fit(X_train, y_train)
y_pred = clf.predict(X_holdout)[:]
S_train[test_idx, i] = y_pred
S_test_i[:, j] = clf.predict(T)[:]
S_test[:, i] = S_test_i.mean(1)
self.stacker.fit(S_train, y)
y_pred = self.stacker.predict(S_test)[:]
return y_pred
獲獎選手往往會使用比這復雜得多的 Ensemble,會出現三層、四層甚至五層,不同的層數之間有各種交互,還有將經過不同的 Preprocessing 和不同的 Feature Engineering 的數據用 Ensemble 組合起來的做法。但對於新手來說,穩穩當當地實現一個正確的 5-Fold Stacking 已經足夠了。
*Pipeline
可以看出 Kaggle 比賽的 Workflow 還是比較復雜的。尤其是 Model Selection 和 Ensemble。理想情況下,我們需要搭建一個高自動化的 Pipeline,它可以做到:
?模塊化 Feature Transform,只需寫很少的代碼就能將新的 Feature 更新到訓練集中。
?自動化 Grid Search,只要預先設定好使用的 Model 和參數的候選,就能自動搜索並記錄最佳的 Model。
?自動化 Ensemble Generation,每個一段時間將現有最好的 K 個 Model 拿來做 Ensemble。
第一點可能意義還不是太大,因為 Feature 的數量總是人腦管理的過來的;第三點問題也不大,因為往往就是在最後做幾次 Ensemble。但是第二點還是很有意義的,手工記錄每個 Model 的表現不僅浪費時間而且容易產生混亂。
可參考Crowdflower Search Results Relevance?的第一名獲得者?Chenglong Chen?公開的pipeline代碼https://github.com/ChenglongChen/Kaggle_CrowdFlower
Takeaways
1.比較早的時候就開始做 Ensemble 是對的,這次比賽到倒數第三天我還在糾結 Feature。
2.很有必要搭建一個 Pipeline,至少要能夠自動訓練並記錄最佳參數。
3.Feature 為王。我花在 Feature 上的時間還是太少。
4.可能的話,多花點時間去手動查看原始數據中的 Pattern。
Issues Raised
比賽中遇到的一些問題是很有研究價值的:
1.在數據分布並不 i.i.d. 甚至有 Dependency 時如何做靠譜的?CV。
2.如何量化 Ensemble 中?Diversity vs. Accuracy?的 Trade-off。
3.如何處理 Feature 之間互相影響導致性能反而下降。
SWOT分析法
SWOT分析法讓我們可以讓我們思路更清晰,專註於利用我們最大的優勢(Strengths),了解我們的弱點(Weaknesses),利用現有的機會(Opportunities),警惕潛在的威脅(Threats)。?
SWOT方法使我們可以在正確的方向前進,避開很多令人頭疼的事兒。
優勢(Strengths):利用我們已有的優勢
?利用堆疊技術和Agile過程的經驗
?協作團隊的經驗和技能可以相互補充
?吸取之前的Kaggle挑戰的經驗和教訓
?使用Agile過程處理並行工作的經驗
弱勢(weaknesses):我們需要提升的領域
?時間有限,限制了探索的深度和廣度
?對新工具和模型的不熟悉,大大降低了我們的戰鬥力
?邊做邊學,拖慢了整個進程
?對於所使用的新技術,相關的資源十分稀少
機會(opportunities):可以利用的機會,及實踐得到的經驗
?了解如何制定策略,調整模型、算法和參數,來達到對違約幾率的最佳預測
?在使用貝葉斯優化算法時,獲得實時的經驗
?嘗試使用深度學習(Theano/Keras)來進行違約預測
威脅(threats):我們需要減輕和控制的危機
?數據集比較小,這對模型的泛化有較大的挑戰。這影響了模型的構造,最終影響了預測的精度。
?對AUC的公差嚴格到了小數點後10,000位
?前5%的目標分數太高了-簡直不可行
Agile過程
我們使用‘Agile過程’作為本項目的流程,這是由Bernard發明的。它特別為了機器學習優化過,因為他需要整合很大一塊內容,包括數據是如何被處理的,建模、測試的,以及很多更傳統的開發生命周期。?
Agile過程利用了‘分塊’的概念,以一種快速的方式來完成工作任務。它整合了很多並行的任務,很快地失敗,又很快地叠代。它發揮了強大的作用,使產出最大化。
Kaggle技巧總結