0
本文作者: AI研習(xí)社 | 2017-03-17 19:31 |
雷鋒網(wǎng)按:本文作者章凌豪,復(fù)旦大學(xué)計(jì)算機(jī)科學(xué)專(zhuān)業(yè)。有興趣的同學(xué)可以移步他的個(gè)人主頁(yè):Linghao Zhang
Kaggle 是目前最大的 Data Scientist 聚集地。很多公司會(huì)拿出自家的數(shù)據(jù)并提供獎(jiǎng)金,在 Kaggle 上組織數(shù)據(jù)競(jìng)賽。我最近完成了第一次比賽,在 2125 個(gè)參賽隊(duì)伍中排名第 98 位(~ 5%)。因?yàn)槭堑谝淮螀①?,所以?duì)這個(gè)成績(jī)我已經(jīng)很滿(mǎn)意了。在 Kaggle 上一次比賽的結(jié)果除了排名以外,還會(huì)顯示的就是 Prize Winner,10% 或是 25% 這三檔。所以剛剛接觸 Kaggle 的人很多都會(huì)以 25% 或是 10% 為目標(biāo)。在本文中,我試圖根據(jù)自己第一次比賽的經(jīng)驗(yàn)和從其他 Kaggler 那里學(xué)到的知識(shí),為剛剛聽(tīng)說(shuō) Kaggle 想要參賽的新手提供一些切實(shí)可行的沖刺 10% 的指導(dǎo)。
本文的英文版見(jiàn)這里。
Kaggler 絕大多數(shù)都是用 Python 和 R 這兩門(mén)語(yǔ)言的。因?yàn)槲抑饕褂?Python,所以本文提到的例子都會(huì)根據(jù) Python 來(lái)。不過(guò) R 的用戶(hù)應(yīng)該也能不費(fèi)力地了解到工具背后的思想。
首先簡(jiǎn)單介紹一些關(guān)于 Kaggle 比賽的知識(shí):
不同比賽有不同的任務(wù),分類(lèi)、回歸、推薦、排序等。比賽開(kāi)始后訓(xùn)練集和測(cè)試集就會(huì)開(kāi)放下載。
比賽通常持續(xù) 2 ~ 3 個(gè)月,每個(gè)隊(duì)伍每天可以提交的次數(shù)有限,通常為 5 次。
比賽結(jié)束前一周是一個(gè) Deadline,在這之后不能再組隊(duì),也不能再新加入比賽。所以想要參加比賽請(qǐng)務(wù)必在這一 Deadline 之前有過(guò)至少一次有效的提交。
一般情況下在提交后會(huì)立刻得到得分的反饋。不同比賽會(huì)采取不同的評(píng)分基準(zhǔn),可以在分?jǐn)?shù)欄最上方看到使用的評(píng)分方法。
反饋的分?jǐn)?shù)是基于測(cè)試集的一部分計(jì)算的,剩下的另一部分會(huì)被用于計(jì)算最終的結(jié)果。所以最后排名會(huì)變動(dòng)。
LB 指的就是在 Leaderboard 得到的分?jǐn)?shù),由上,有 Public LB 和 Private LB 之分。
自己做的 Cross Validation 得到的分?jǐn)?shù)一般稱(chēng)為 CV 或是 Local CV。一般來(lái)說(shuō) CV 的結(jié)果比 LB 要可靠。
新手可以從比賽的 Forum 和 Scripts 中找到許多有用的經(jīng)驗(yàn)和洞見(jiàn)。不要吝嗇提問(wèn),Kaggler 都很熱情。
那么就開(kāi)始吧!
P.S. 本文假設(shè)讀者對(duì) Machine Learning 的基本概念和常見(jiàn)模型已經(jīng)有一定了解。 Enjoy Reading!
在這一節(jié)中我會(huì)講述一次 Kaggle 比賽的大致流程。
Data Exploration
在這一步要做的基本就是 EDA (Exploratory Data Analysis),也就是對(duì)數(shù)據(jù)進(jìn)行探索性的分析,從而為之后的處理和建模提供必要的結(jié)論。
通常我們會(huì)用 pandas 來(lái)載入數(shù)據(jù),并做一些簡(jiǎn)單的可視化來(lái)理解數(shù)據(jù)。
Visualization
通常來(lái)說(shuō) matplotlib 和 seaborn 提供的繪圖功能就可以滿(mǎn)足需求了。
比較常用的圖表有:
查看目標(biāo)變量的分布。當(dāng)分布不平衡時(shí),根據(jù)評(píng)分標(biāo)準(zhǔn)和具體模型的使用不同,可能會(huì)嚴(yán)重影響性能。
對(duì) Numerical Variable,可以用 Box Plot 來(lái)直觀地查看它的分布。
對(duì)于坐標(biāo)類(lèi)數(shù)據(jù),可以用 Scatter Plot 來(lái)查看它們的分布趨勢(shì)和是否有離群點(diǎn)的存在。
對(duì)于分類(lèi)問(wèn)題,將數(shù)據(jù)根據(jù) Label 的不同著不同的顏色繪制出來(lái),這對(duì) Feature 的構(gòu)造很有幫助。
繪制變量之間兩兩的分布和相關(guān)度圖表。
這里有一個(gè)在著名的 Iris 數(shù)據(jù)集上做了一系列可視化的例子,非常有啟發(fā)性。
Statistical Tests
我們可以對(duì)數(shù)據(jù)進(jìn)行一些統(tǒng)計(jì)上的測(cè)試來(lái)驗(yàn)證一些假設(shè)的顯著性。雖然大部分情況下靠可視化就能得到比較明確的結(jié)論,但有一些定量結(jié)果總是更理想的。不過(guò),在實(shí)際數(shù)據(jù)中經(jīng)常會(huì)遇到非 i.i.d. 的分布。所以要注意測(cè)試類(lèi)型的的選擇和對(duì)顯著性的解釋。
在某些比賽中,由于數(shù)據(jù)分布比較奇葩或是噪聲過(guò)強(qiáng),Public LB 的分?jǐn)?shù)可能會(huì)跟Local CV 的結(jié)果相去甚遠(yuǎn)??梢愿鶕?jù)一些統(tǒng)計(jì)測(cè)試的結(jié)果來(lái)粗略地建立一個(gè)閾值,用來(lái)衡量一次分?jǐn)?shù)的提高究竟是實(shí)質(zhì)的提高還是由于數(shù)據(jù)的隨機(jī)性導(dǎo)致的。
Data Preprocessing
大部分情況下,在構(gòu)造 Feature 之前,我們需要對(duì)比賽提供的數(shù)據(jù)集進(jìn)行一些處理。通常的步驟有:
有時(shí)數(shù)據(jù)會(huì)分散在幾個(gè)不同的文件中,需要 Join 起來(lái)。
處理 Missing Data。
處理 Outlier。
必要時(shí)轉(zhuǎn)換某些 Categorical Variable 的表示方式。
有些 Float 變量可能是從未知的 Int 變量轉(zhuǎn)換得到的,這個(gè)過(guò)程中發(fā)生精度損失會(huì)在數(shù)據(jù)中產(chǎn)生不必要的 Noise,即兩個(gè)數(shù)值原本是相同的卻在小數(shù)點(diǎn)后某一位開(kāi)始有不同。這對(duì) Model 可能會(huì)產(chǎn)生很負(fù)面的影響,需要設(shè)法去除或者減弱 Noise。
這一部分的處理策略多半依賴(lài)于在前一步中探索數(shù)據(jù)集所得到的結(jié)論以及創(chuàng)建的可視化圖表。在實(shí)踐中,我建議使用 iPython Notebook 進(jìn)行對(duì)數(shù)據(jù)的操作,并熟練掌握常用的 pandas 函數(shù)。這樣做的好處是可以隨時(shí)得到結(jié)果的反饋和進(jìn)行修改,也方便跟其他人進(jìn)行交流(在 Data Science 中 Reproducible Results 是很重要的)。
下面給兩個(gè)例子。
Outlier
這是經(jīng)過(guò) Scaling 的坐標(biāo)數(shù)據(jù)??梢园l(fā)現(xiàn)右上角存在一些離群點(diǎn),去除以后分布比較正常。
Dummy Variables
對(duì)于 Categorical Variable,常用的做法就是 One-hot encoding。即對(duì)這一變量創(chuàng)建一組新的偽變量,對(duì)應(yīng)其所有可能的取值。這些變量中只有這條數(shù)據(jù)對(duì)應(yīng)的取值為 1,其他都為 0。
如下,將原本有 7 種可能取值的 Weekdays 變量轉(zhuǎn)換成 7 個(gè) Dummy Variables。
要注意,當(dāng)變量可能取值的范圍很大(比如一共有成百上千類(lèi))時(shí),這種簡(jiǎn)單的方法就不太適用了。這時(shí)沒(méi)有有一個(gè)普適的方法,但我會(huì)在下一小節(jié)描述其中一種。
Feature Engineering
有人總結(jié) Kaggle 比賽是 “Feature 為主,調(diào)參和 Ensemble 為輔”,我覺(jué)得很有道理。Feature Engineering 能做到什么程度,取決于對(duì)數(shù)據(jù)領(lǐng)域的了解程度。比如在數(shù)據(jù)包含大量文本的比賽中,常用的 NLP 特征就是必須的。怎么構(gòu)造有用的 Feature,是一個(gè)不斷學(xué)習(xí)和提高的過(guò)程。
一般來(lái)說(shuō),當(dāng)一個(gè)變量從直覺(jué)上來(lái)說(shuō)對(duì)所要完成的目標(biāo)有幫助,就可以將其作為 Feature。至于它是否有效,最簡(jiǎn)單的方式就是通過(guò)圖表來(lái)直觀感受。比如:
Feature Selection
總的來(lái)說(shuō),我們應(yīng)該生成盡量多的 Feature,相信 Model 能夠挑出最有用的 Feature。但有時(shí)先做一遍 Feature Selection 也能帶來(lái)一些好處:
Feature 越少,訓(xùn)練越快。
有些 Feature 之間可能存在線性關(guān)系,影響 Model 的性能。
通過(guò)挑選出最重要的 Feature,可以將它們之間進(jìn)行各種運(yùn)算和操作的結(jié)果作為新的 Feature,可能帶來(lái)意外的提高。
Feature Selection 最實(shí)用的方法也就是看 Random Forest 訓(xùn)練完以后得到的Feature Importance 了。其他有一些更復(fù)雜的算法在理論上更加 Robust,但是缺乏實(shí)用高效的實(shí)現(xiàn),比如這個(gè)。從原理上來(lái)講,增加 Random Forest 中樹(shù)的數(shù)量可以在一定程度上加強(qiáng)其對(duì)于 Noisy Data 的 Robustness。
看 Feature Importance 對(duì)于某些數(shù)據(jù)經(jīng)過(guò)脫敏處理的比賽尤其重要。這可以免得你浪費(fèi)大把時(shí)間在琢磨一個(gè)不重要的變量的意義上。
Feature Encoding
這里用一個(gè)例子來(lái)說(shuō)明在一些情況下 Raw Feature 可能需要經(jīng)過(guò)一些轉(zhuǎn)換才能起到比較好的效果。
假設(shè)有一個(gè) Categorical Variable 一共有幾萬(wàn)個(gè)取值可能,那么創(chuàng)建 Dummy Variables 的方法就不可行了。這時(shí)一個(gè)比較好的方法是根據(jù) Feature Importance 或是這些取值本身在數(shù)據(jù)中的出現(xiàn)頻率,為最重要(比如說(shuō)前 95% 的 Importance)那些取值(有很大可能只有幾個(gè)或是十幾個(gè))創(chuàng)建 Dummy Variables,而所有其他取值都?xì)w到一個(gè)“其他”類(lèi)里面。
Model Selection
準(zhǔn)備好 Feature 以后,就可以開(kāi)始選用一些常見(jiàn)的模型進(jìn)行訓(xùn)練了。Kaggle 上最常用的模型基本都是基于樹(shù)的模型:
Gradient Boosting
Random Forest
Extra Randomized Trees
以下模型往往在性能上稍遜一籌,但是很適合作為 Ensemble 的 Base Model。這一點(diǎn)之后再詳細(xì)解釋。(當(dāng)然,在跟圖像有關(guān)的比賽中神經(jīng)網(wǎng)絡(luò)的重要性還是不能小覷的。)
SVM
Linear Regression
Logistic Regression
Neural Networks
以上這些模型基本都可以通過(guò) sklearn 來(lái)使用。
當(dāng)然,這里不能不提一下 Xgboost。Gradient Boosting 本身優(yōu)秀的性能加上Xgboost 高效的實(shí)現(xiàn),使得它在 Kaggle 上廣為使用。幾乎每場(chǎng)比賽的獲獎(jiǎng)?wù)叨紩?huì)用 Xgboost 作為最終 Model 的重要組成部分。在實(shí)戰(zhàn)中,我們往往會(huì)以 Xgboost 為主來(lái)建立我們的模型并且驗(yàn)證 Feature 的有效性。順帶一提,在 Windows 上安裝 Xgboost 很容易遇到問(wèn)題,目前已知最簡(jiǎn)單、成功率最高的方案可以參考我在這篇帖子中的描述。
Model Training
在訓(xùn)練時(shí),我們主要希望通過(guò)調(diào)整參數(shù)來(lái)得到一個(gè)性能不錯(cuò)的模型。一個(gè)模型往往有很多參數(shù),但其中比較重要的一般不會(huì)太多。比如對(duì) sklearn 的 RandomForestClassifier 來(lái)說(shuō),比較重要的就是隨機(jī)森林中樹(shù)的數(shù)量 n_estimators 以及在訓(xùn)練每棵樹(shù)時(shí)最多選擇的特征數(shù)量 max_features。所以我們需要對(duì)自己使用的模型有足夠的了解,知道每個(gè)參數(shù)對(duì)性能的影響是怎樣的。
通常我們會(huì)通過(guò)一個(gè)叫做 Grid Search 的過(guò)程來(lái)確定一組最佳的參數(shù)。其實(shí)這個(gè)過(guò)程說(shuō)白了就是根據(jù)給定的參數(shù)候選對(duì)所有的組合進(jìn)行暴力搜索。
1 param_grid = {'n_estimators': [300, 500], 'max_features': [10, 12, 14]}
2 model = grid_search.GridSearchCV(estimator=rfr, param_grid=param_grid, n_jobs=1, cv=10, verbose=20, scoring=RMSE)
3 model.fit(X_train, y_train)
順帶一提,Random Forest 一般在 max_features 設(shè)為 Feature 數(shù)量的平方根附近得到最佳結(jié)果。
這里要重點(diǎn)講一下 Xgboost 的調(diào)參。通常認(rèn)為對(duì)它性能影響較大的參數(shù)有:
eta:每次迭代完成后更新權(quán)重時(shí)的步長(zhǎng)。越小訓(xùn)練越慢。
num_round:總共迭代的次數(shù)。
subsample:訓(xùn)練每棵樹(shù)時(shí)用來(lái)訓(xùn)練的數(shù)據(jù)占全部的比例。用于防止 Overfitting。
colsample_bytree:訓(xùn)練每棵樹(shù)時(shí)用來(lái)訓(xùn)練的特征的比例,類(lèi)似 RandomForestClassifier 的 max_features。
max_depth:每棵樹(shù)的最大深度限制。與 Random Forest 不同,Gradient Boosting 如果不對(duì)深度加以限制,最終是會(huì) Overfit 的。
early_stopping_rounds:用于控制在 Out Of Sample 的驗(yàn)證集上連續(xù)多少個(gè)迭代的分?jǐn)?shù)都沒(méi)有提高后就提前終止訓(xùn)練。用于防止 Overfitting。
一般的調(diào)參步驟是:
將訓(xùn)練數(shù)據(jù)的一部分劃出來(lái)作為驗(yàn)證集。
先將 eta 設(shè)得比較高(比如 0.1),num_round 設(shè)為 300 ~ 500。
用 Grid Search 對(duì)其他參數(shù)進(jìn)行搜索
逐步將 eta 降低,找到最佳值。
以驗(yàn)證集為 watchlist,用找到的最佳參數(shù)組合重新在訓(xùn)練集上訓(xùn)練。注意觀察算法的輸出,看每次迭代后在驗(yàn)證集上分?jǐn)?shù)的變化情況,從而得到最佳的early_stopping_rounds。
1 X_dtrain, X_deval, y_dtrain, y_deval = cross_validation.train_test_split(X_train, y_train, random_state=1026, test_size=0.3)
2 dtrain = xgb.DMatrix(X_dtrain, y_dtrain)
3 deval = xgb.DMatrix(X_deval, y_deval)
4 watchlist = [(deval, 'eval')]
5 params = {
6 'booster': 'gbtree',
7 'objective': 'reg:linear',
8 'subsample': 0.8,
9 'colsample_bytree': 0.85,
10 'eta': 0.05,
11 'max_depth': 7,
12 'seed': 2016,
13 'silent': 0,
14 'eval_metric': 'rmse'
15 }
16 clf = xgb.train(params, dtrain, 500, watchlist, early_stopping_rounds=50)
17 pred = clf.predict(xgb.DMatrix(df_test))
最后要提一點(diǎn),所有具有隨機(jī)性的 Model 一般都會(huì)有一個(gè) seed 或是 random_state 參數(shù)用于控制隨機(jī)種子。得到一個(gè)好的 Model 后,在記錄參數(shù)時(shí)務(wù)必也記錄下這個(gè)值,從而能夠在之后重現(xiàn) Model。
Cross Validation
Cross Validation 是非常重要的一個(gè)環(huán)節(jié)。它讓你知道你的 Model 有沒(méi)有 Overfit,是不是真的能夠 Generalize 到測(cè)試集上。在很多比賽中 Public LB 都會(huì)因?yàn)檫@樣那樣的原因而不可靠。當(dāng)你改進(jìn)了 Feature 或是 Model 得到了一個(gè)更高的 CV 結(jié)果,提交之后得到的 LB 結(jié)果卻變差了,一般認(rèn)為這時(shí)應(yīng)該相信 CV 的結(jié)果。當(dāng)然,最理想的情況是多種不同的 CV 方法得到的結(jié)果和 LB 同時(shí)提高,但這樣的比賽并不是太多。
在數(shù)據(jù)的分布比較隨機(jī)均衡的情況下,5-Fold CV 一般就足夠了。如果不放心,可以提到 10-Fold。但是 Fold 越多訓(xùn)練也就會(huì)越慢,需要根據(jù)實(shí)際情況進(jìn)行取舍。
很多時(shí)候簡(jiǎn)單的 CV 得到的分?jǐn)?shù)會(huì)不大靠譜,Kaggle 上也有很多關(guān)于如何做 CV 的討論。比如這個(gè)。但總的來(lái)說(shuō),靠譜的 CV 方法是 Case By Case 的,需要在實(shí)際比賽中進(jìn)行嘗試和學(xué)習(xí),這里就不再(也不能)敘述了。
Ensemble Generation
Ensemble Learning 是指將多個(gè)不同的 Base Model 組合成一個(gè) Ensemble Model 的方法。它可以同時(shí)降低最終模型的 Bias 和 Variance(證明可以參考這篇論文,我最近在研究類(lèi)似的理論,可能之后會(huì)寫(xiě)新文章詳述),從而在提高分?jǐn)?shù)的同時(shí)又降低 Overfitting 的風(fēng)險(xiǎn)。在現(xiàn)在的 Kaggle 比賽中要不用 Ensemble 就拿到獎(jiǎng)金幾乎是不可能的。
常見(jiàn)的 Ensemble 方法有這么幾種:
Bagging:使用訓(xùn)練數(shù)據(jù)的不同隨機(jī)子集來(lái)訓(xùn)練每個(gè) Base Model,最后進(jìn)行每個(gè) Base Model 權(quán)重相同的 Vote。也即 Random Forest 的原理。
Boosting:迭代地訓(xùn)練 Base Model,每次根據(jù)上一個(gè)迭代中預(yù)測(cè)錯(cuò)誤的情況修改訓(xùn)練樣本的權(quán)重。也即 Gradient Boosting 的原理。比 Bagging 效果好,但更容易 Overfit。
Blending:用不相交的數(shù)據(jù)訓(xùn)練不同的 Base Model,將它們的輸出?。訖?quán))平均。實(shí)現(xiàn)簡(jiǎn)單,但對(duì)訓(xùn)練數(shù)據(jù)利用少了。
Stacking:接下來(lái)會(huì)詳細(xì)介紹。
從理論上講,Ensemble 要成功,有兩個(gè)要素:
Base Model 之間的相關(guān)性要盡可能的小。這就是為什么非 Tree-based Model 往往表現(xiàn)不是最好但還是要將它們包括在 Ensemble 里面的原因。Ensemble 的 Diversity 越大,最終 Model 的 Bias 就越低。
Base Model 之間的性能表現(xiàn)不能差距太大。這其實(shí)是一個(gè) Trade-off,在實(shí)際中很有可能表現(xiàn)相近的 Model 只有寥寥幾個(gè)而且它們之間相關(guān)性還不低。但是實(shí)踐告訴我們即使在這種情況下 Ensemble 還是能大幅提高成績(jī)。
Stacking
相比 Blending,Stacking 能更好地利用訓(xùn)練數(shù)據(jù)。以 5-Fold Stacking 為例,它的基本原理如圖所示:
整個(gè)過(guò)程很像 Cross Validation。首先將訓(xùn)練數(shù)據(jù)分為 5 份,接下來(lái)一共 5 個(gè)迭代,每次迭代時(shí),將 4 份數(shù)據(jù)作為 Training Set 對(duì)每個(gè) Base Model 進(jìn)行訓(xùn)練,然后在剩下一份 Hold-out Set 上進(jìn)行預(yù)測(cè)。同時(shí)也要將其在測(cè)試數(shù)據(jù)上的預(yù)測(cè)保存下來(lái)。這樣,每個(gè) Base Model 在每次迭代時(shí)會(huì)對(duì)訓(xùn)練數(shù)據(jù)的其中 1 份做出預(yù)測(cè),對(duì)測(cè)試數(shù)據(jù)的全部做出預(yù)測(cè)。5 個(gè)迭代都完成以后我們就獲得了一個(gè) #訓(xùn)練數(shù)據(jù)行數(shù) x #Base Model 數(shù)量 的矩陣,這個(gè)矩陣接下來(lái)就作為第二層的 Model 的訓(xùn)練數(shù)據(jù)。當(dāng)?shù)诙拥?Model 訓(xùn)練完以后,將之前保存的 Base Model 對(duì)測(cè)試數(shù)據(jù)的預(yù)測(cè)(因?yàn)槊總€(gè) Base Model 被訓(xùn)練了 5 次,對(duì)測(cè)試數(shù)據(jù)的全體做了 5 次預(yù)測(cè),所以對(duì)這 5 次求一個(gè)平均值,從而得到一個(gè)形狀與第二層訓(xùn)練數(shù)據(jù)相同的矩陣)拿出來(lái)讓它進(jìn)行預(yù)測(cè),就得到最后的輸出。
這里給出我的實(shí)現(xiàn)代碼:
1 class Ensemble(object):
2 def __init__(self, n_folds, stacker, base_models):
3 self.n_folds = n_folds
4 self.stacker = stacker
5 self.base_models = base_models
6 def fit_predict(self, X, y, T):
7 X = np.array(X)
8 y = np.array(y)
9 T = np.array(T)
10 folds = list(KFold(len(y), n_folds=self.n_folds, shuffle=True, random_state=2016))
11 S_train = np.zeros((X.shape[0], len(self.base_models)))
12 S_test = np.zeros((T.shape[0], len(self.base_models)))
13 for i, clf in enumerate(self.base_models):
14 S_test_i = np.zeros((T.shape[0], len(folds)))
15 for j, (train_idx, test_idx) in enumerate(folds):
16 X_train = X[train_idx]
17 y_train = y[train_idx]
18 X_holdout = X[test_idx]
19 # y_holdout = y[test_idx]
20 clf.fit(X_train, y_train)
21 y_pred = clf.predict(X_holdout)[:]
22 S_train[test_idx, i] = y_pred
23 S_test_i[:, j] = clf.predict(T)[:]
24 S_test[:, i] = S_test_i.mean(1)
25 self.stacker.fit(S_train, y)
26 y_pred = self.stacker.predict(S_test)[:]
27 return y_pred
獲獎(jiǎng)選手往往會(huì)使用比這復(fù)雜得多的 Ensemble,會(huì)出現(xiàn)三層、四層甚至五層,不同的層數(shù)之間有各種交互,還有將經(jīng)過(guò)不同的 Preprocessing 和不同的 Feature Engineering 的數(shù)據(jù)用 Ensemble 組合起來(lái)的做法。但對(duì)于新手來(lái)說(shuō),穩(wěn)穩(wěn)當(dāng)當(dāng)?shù)貙?shí)現(xiàn)一個(gè)正確的 5-Fold Stacking 已經(jīng)足夠了。
*Pipeline
可以看出 Kaggle 比賽的 Workflow 還是比較復(fù)雜的。尤其是 Model Selection 和 Ensemble。理想情況下,我們需要搭建一個(gè)高自動(dòng)化的 Pipeline,它可以做到:
模塊化 Feature Transform,只需寫(xiě)很少的代碼就能將新的 Feature 更新到訓(xùn)練集中。
自動(dòng)化 Grid Search,只要預(yù)先設(shè)定好使用的 Model 和參數(shù)的候選,就能自動(dòng)搜索并記錄最佳的 Model。
自動(dòng)化 Ensemble Generation,每個(gè)一段時(shí)間將現(xiàn)有最好的 K 個(gè) Model 拿來(lái)做 Ensemble。
對(duì)新手來(lái)說(shuō),第一點(diǎn)可能意義還不是太大,因?yàn)?Feature 的數(shù)量總是人腦管理的過(guò)來(lái)的;第三點(diǎn)問(wèn)題也不大,因?yàn)橥褪窃谧詈笞鰩状?Ensemble。但是第二點(diǎn)還是很有意義的,手工記錄每個(gè) Model 的表現(xiàn)不僅浪費(fèi)時(shí)間而且容易產(chǎn)生混亂。
Crowdflower Search Results Relevance 的第一名獲得者 Chenglong Chen 將他在比賽中使用的 Pipeline 公開(kāi)了,非常具有參考和借鑒意義。只不過(guò)看懂他的代碼并將其中的邏輯抽離出來(lái)搭建這樣一個(gè)框架,還是比較困難的一件事??赡茉趨⒓舆^(guò)幾次比賽以后專(zhuān)門(mén)抽時(shí)間出來(lái)做會(huì)比較好。
Home Depot Search Relevance
在這一節(jié)中我會(huì)具體分享我在 Home Depot Search Relevance 比賽中是怎么做的,以及比賽結(jié)束后從排名靠前的隊(duì)伍那邊學(xué)到的做法。
首先簡(jiǎn)單介紹這個(gè)比賽。Task 是判斷用戶(hù)搜索的關(guān)鍵詞和網(wǎng)站返回的結(jié)果之間的相關(guān)度有多高。相關(guān)度是由 3 個(gè)人類(lèi)打分取平均得到的,每個(gè)人可能打 1 ~ 3 分,所以這是一個(gè)回歸問(wèn)題。數(shù)據(jù)中包含用戶(hù)的搜索詞,返回的產(chǎn)品的標(biāo)題和介紹,以及產(chǎn)品相關(guān)的一些屬性比如品牌、尺寸、顏色等。使用的評(píng)分基準(zhǔn)是 RMSE。
這個(gè)比賽非常像 Crowdflower Search Results Relevance 那場(chǎng)比賽。不過(guò)那邊用的評(píng)分基準(zhǔn)是 Quadratic Weighted Kappa,把 1 誤判成 4 的懲罰會(huì)比把 1 判成 2 的懲罰大得多,所以在最后 Decode Prediction 的時(shí)候會(huì)更麻煩一點(diǎn)。除此以外那次比賽沒(méi)有提供產(chǎn)品的屬性。
EDA
由于加入比賽比較晚,當(dāng)時(shí)已經(jīng)有相當(dāng)不錯(cuò)的 EDA 了。尤其是這個(gè)。從中我得到的啟發(fā)有:
同一個(gè)搜索詞/產(chǎn)品都出現(xiàn)了多次,數(shù)據(jù)分布顯然不 i.i.d.。
文本之間的相似度很有用。
產(chǎn)品中有相當(dāng)大一部分缺失屬性,要考慮這會(huì)不會(huì)使得從屬性中得到的 Feature 反而難以利用。
產(chǎn)品的 ID 對(duì)預(yù)測(cè)相關(guān)度很有幫助,但是考慮到訓(xùn)練集和測(cè)試集之間的重疊度并不太高,利用它會(huì)不會(huì)導(dǎo)致 Overfitting?
Preprocessing
這次比賽中我的 Preprocessing 和 Feature Engineering 的具體做法都可以在這里看到。我只簡(jiǎn)單總結(jié)一下和指出重要的點(diǎn)。
利用 Forum 上的 Typo Dictionary 修正搜索詞中的錯(cuò)誤。
統(tǒng)計(jì)屬性的出現(xiàn)次數(shù),將其中出現(xiàn)次數(shù)多又容易利用的記錄下來(lái)。
將訓(xùn)練集和測(cè)試集合并,并與產(chǎn)品描述和屬性 Join 起來(lái)。這是考慮到后面有一系列操作,如果不合并的話就要重復(fù)寫(xiě)兩次了。
對(duì)所有文本能做 Stemming 和 Tokenizing,同時(shí)手工做了一部分格式統(tǒng)一化(比如涉及到數(shù)字和單位的)和同義詞替換。
Feature
*Attribute Features
是否包含某個(gè)特定的屬性(品牌、尺寸、顏色、重量、內(nèi)用/外用、是否有能源之星認(rèn)證等)
這個(gè)特定的屬性是否匹配
Meta Features
各個(gè)文本域的長(zhǎng)度
是否包含屬性域
品牌(將所有的品牌做數(shù)值離散化)
產(chǎn)品 ID
簡(jiǎn)單匹配
搜索詞是否在產(chǎn)品標(biāo)題、產(chǎn)品介紹或是產(chǎn)品屬性中出現(xiàn)
搜索詞在產(chǎn)品標(biāo)題、產(chǎn)品介紹或是產(chǎn)品屬性中出現(xiàn)的數(shù)量和比例
*搜索詞中的第 i 個(gè)詞是否在產(chǎn)品標(biāo)題、產(chǎn)品介紹或是產(chǎn)品屬性中出現(xiàn)
搜索詞和產(chǎn)品標(biāo)題、產(chǎn)品介紹以及產(chǎn)品屬性之間的文本相似度
Latent Semantic Indexing:通過(guò)將 BOW/TF-IDF Vectorization 得到的矩陣進(jìn)行 SVD 分解,我們可以得到不同搜索詞/產(chǎn)品組合的 Latent 標(biāo)識(shí)。這個(gè) Feature 使得 Model 能夠在一定程度上對(duì)不同的組合做出區(qū)別,從而解決某些產(chǎn)品缺失某些 Feature 的問(wèn)題。
值得一提的是,上面打了 * 的 Feature 都是我在最后一批加上去的。問(wèn)題是,使用這批 Feature 訓(xùn)練得到的 Model 反而比之前的要差,而且還差不少。我一開(kāi)始是以為因?yàn)?Feature 的數(shù)量變多了所以一些參數(shù)需要重新調(diào)優(yōu),但在浪費(fèi)了很多時(shí)間做 Grid Search 以后卻發(fā)現(xiàn)還是沒(méi)法超過(guò)之前的分?jǐn)?shù)。這可能就是之前提到的 Feature 之間的相互作用導(dǎo)致的問(wèn)題。當(dāng)時(shí)我設(shè)想過(guò)一個(gè)看到過(guò)好幾次的解決方案,就是將使用不同版本 Feature 的 Model 通過(guò) Ensemble 組合起來(lái)。但最終因?yàn)闀r(shí)間關(guān)系沒(méi)有實(shí)現(xiàn)。事實(shí)上排名靠前的隊(duì)伍分享的解法里面基本都提到了將不同的 Preprocessing 和 Feature Engineering 做 Ensemble 是獲勝的關(guān)鍵。
我一開(kāi)始用的是 RandomForestRegressor,后來(lái)在 Windows 上折騰Xgboost 成功了就開(kāi)始用 XGBRegressor。XGB 的優(yōu)勢(shì)非常明顯,同樣的數(shù)據(jù)它只需要不到一半的時(shí)間就能跑完,節(jié)約了很多時(shí)間。
比賽中后期我基本上就是一邊臺(tái)式機(jī)上跑 Grid Search,一邊在筆記本上繼續(xù)研究 Feature。
這次比賽數(shù)據(jù)分布很不獨(dú)立,所以期間多次遇到改進(jìn)的 Feature 或是 Grid Search新得到的參數(shù)訓(xùn)練出來(lái)的模型反而 LB 分?jǐn)?shù)下降了。由于被很多前輩教導(dǎo)過(guò)要相信自己的 CV,我的決定是將 5-Fold 提到 10-Fold,然后以 CV 為標(biāo)準(zhǔn)繼續(xù)前進(jìn)。
最終我的 Ensemble 的 Base Model 有以下四個(gè):
RandomForestRegressor
ExtraTreesRegressor
GradientBoostingRegressor
XGBRegressor
第二層的 Model 還是用的 XGB。
因?yàn)?nbsp;Base Model 之間的相關(guān)都都太高了(最低的一對(duì)也有 0.9),我原本還想引入使用 gblinear 的 XGBRegressor 以及 SVR,但前者的 RMSE 比其他幾個(gè) Model 高了 0.02(這在 LB 上有幾百名的差距),而后者的訓(xùn)練實(shí)在太慢了。最后還是只用了這四個(gè)。
值得一提的是,在開(kāi)始做 Stacking 以后,我的 CV 和 LB 成績(jī)的提高就是完全同步的了。
在比賽最后兩天,因?yàn)樯硇钠v加上想不到還能有什么顯著的改進(jìn),我做了一件事情:用 20 個(gè)不同的隨機(jī)種子來(lái)生成 Ensemble,最后取 Weighted Average。這個(gè)其實(shí)算是一種變相的 Bagging。其意義在于按我實(shí)現(xiàn) Stacking 的方式,我在訓(xùn)練 Base Model 時(shí)只用了 80% 的訓(xùn)練數(shù)據(jù),而訓(xùn)練第二層的 Model 時(shí)用了 100% 的數(shù)據(jù),這在一定程度上增大了 Overfitting 的風(fēng)險(xiǎn)。而每次更改隨機(jī)種子可以確保每次用的是不同的 80%,這樣在多次訓(xùn)練取平均以后就相當(dāng)于逼近了使用 100% 數(shù)據(jù)的效果。這給我?guī)?lái)了大約 0.0004 的提高,也很難受說(shuō)是真的有效還是隨機(jī)性了。
比賽結(jié)束后我發(fā)現(xiàn)我最好的單個(gè) Model 在 Private LB 上的得分是 0.46378,而最終 Stacking 的得分是 0.45849。這是 174 名和 98 名的差距。也就是說(shuō),我單靠 Feature 和調(diào)參進(jìn)到了 前 10%,而 Stacking 使我進(jìn)入了前 5%。
Lessons Learned
比賽結(jié)束后一些隊(duì)伍分享了他們的解法,從中我學(xué)到了一些我沒(méi)有做或是做的不夠好的地方:
產(chǎn)品標(biāo)題的組織方式是有 Pattern 的,比如一個(gè)產(chǎn)品是否帶有某附件一定會(huì)用With/Without XXX 的格式放在標(biāo)題最后。
使用外部數(shù)據(jù),比如 WordNet,Reddit 評(píng)論數(shù)據(jù)集等來(lái)訓(xùn)練同義詞和上位詞(在一定程度上替代 Word2Vec)詞典。
基于字母而不是單詞的 NLP Feature。這一點(diǎn)我讓我十分費(fèi)解,但請(qǐng)教以后發(fā)現(xiàn)非常有道理。舉例說(shuō),排名第三的隊(duì)伍在計(jì)算匹配度時(shí),將搜索詞和內(nèi)容中相匹配的單詞的長(zhǎng)度也考慮進(jìn)去了。這是因?yàn)樗麄儼l(fā)現(xiàn)越長(zhǎng)的單詞約具體,所以越容易被用戶(hù)認(rèn)為相關(guān)度高。此外他們還使用了逐字符的序列比較(difflib.SequenceMatcher),因?yàn)檫@個(gè)相似度能夠衡量視覺(jué)上的相似度。像這樣的 Feature 的確不是每個(gè)人都能想到的。
標(biāo)注單詞的詞性,找出中心詞,計(jì)算基于中心詞的各種匹配度和距離。這一點(diǎn)我想到了,但沒(méi)有時(shí)間嘗試。
將產(chǎn)品標(biāo)題/介紹中 TF-IDF 最高的一些 Trigram 拿出來(lái),計(jì)算搜索詞中出現(xiàn)在這些 Trigram 中的比例;反過(guò)來(lái)以搜索詞為基底也做一遍。這相當(dāng)于是從另一個(gè)角度抽取了一些 Latent 標(biāo)識(shí)。
一些新穎的距離尺度,比如 Word Movers Distance
除了 SVD 以外還可以用上 NMF。
最重要的 Feature 之間的 Pairwise Polynomial Interaction。
針對(duì)數(shù)據(jù)不 i.i.d. 的問(wèn)題,在 CV 時(shí)手動(dòng)構(gòu)造測(cè)試集與驗(yàn)證集之間產(chǎn)品 ID 不重疊和重疊的兩種不同分割,并以與實(shí)際訓(xùn)練集/測(cè)試集的分割相同的比例來(lái)做 CV 以逼近 LB 的得分分布。
至于 Ensemble 的方法,我暫時(shí)還沒(méi)有辦法學(xué)到什么,因?yàn)樽约褐挥凶詈?jiǎn)單的 Stacking 經(jīng)驗(yàn)。
Summary
Takeaways
比較早的時(shí)候就開(kāi)始做 Ensemble 是對(duì)的,這次比賽到倒數(shù)第三天我還在糾結(jié) Feature。
很有必要搭建一個(gè) Pipeline,至少要能夠自動(dòng)訓(xùn)練并記錄最佳參數(shù)。
Feature 為王。我花在 Feature 上的時(shí)間還是太少。
可能的話,多花點(diǎn)時(shí)間去手動(dòng)查看原始數(shù)據(jù)中的 Pattern。
Issues Raised
我認(rèn)為在這次比賽中遇到的一些問(wèn)題是很有研究?jī)r(jià)值的:
在數(shù)據(jù)分布并不 i.i.d. 甚至有 Dependency 時(shí)如何做靠譜的 CV。
如何量化 Ensemble 中 Diversity vs. Accuracy 的 Trade-off。
如何處理 Feature 之間互相影響導(dǎo)致性能反而下降。
Beginner Tips
給新手的一些建議:
選擇一個(gè)感興趣的比賽。如果你對(duì)相關(guān)領(lǐng)域原本就有一些洞見(jiàn)那就更理想了。
根據(jù)我描述的方法開(kāi)始探索、理解數(shù)據(jù)并進(jìn)行建模。
通過(guò) Forum 和 Scripts 學(xué)習(xí)其他人對(duì)數(shù)據(jù)的理解和構(gòu)建 Feature 的方式。
如果之前有過(guò)類(lèi)似的比賽,可以去找當(dāng)時(shí)獲獎(jiǎng)?wù)叩?Interview 和 Blog Post 作為參考,往往很有用。
在得到一個(gè)比較不錯(cuò)的 LB 分?jǐn)?shù)(比如已經(jīng)接近前 10%)以后可以開(kāi)始嘗試做 Ensemble。
如果覺(jué)得自己有希望拿到獎(jiǎng)金,開(kāi)始找人組隊(duì)吧!
到比賽結(jié)束為止要繃緊一口氣不能斷,盡量每天做一些新嘗試。
比賽結(jié)束后學(xué)習(xí)排名靠前的隊(duì)伍的方法,思考自己這次比賽中的不足和發(fā)現(xiàn)的問(wèn)題,可能的話再花點(diǎn)時(shí)間將學(xué)到的新東西用實(shí)驗(yàn)進(jìn)行確認(rèn),為下一次比賽做準(zhǔn)備。
好好休息!
Solution for Prudential Life Insurance Assessment - Nutastray
Search Results Relevance Winner’s Interview: 1st place, Chenglong Chen
雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見(jiàn)轉(zhuǎn)載須知。