kaggle | Machine Learning for Insights Challenge
來自專欄 kaggle玩中學26 人贊了文章
1 前言
最近比較忙,這次不是比賽分享了,是來分享一個「挑戰」,kaggle上用詞說是Challenge,在我理解這個是某專家管理員建立了一個簡短的course,介紹了某主題的玩法,然後讓大家做做簡單的練習,一起討論交流一下。
這次學習的是一個關於模型洞察力的主題,原文鏈接https://www.kaggle.com/ml-for-insights-signup, 本文這裡主要是將我看到的有用的內容按照自己的理解翻譯,記錄下來。這個ML for insights,字面直譯為模型洞察力了,在我理解來則是模型可解釋性分析。
知乎這個寫文章,沒有目錄的啊,看稍微長一點的文章就會覺得沒有段落和層次哎,這個文章我同步發在我博客了https://yyqing.me/post/2018/2018-09-25-kaggle-model-insights,那裡有目錄,看起來舒服一點。
1.1 My Insights
我個人在玩kaggle和工作中用到最多的主要是樹類模型(lgb,xgb)和神經網路(cnn, rnn),確實很少去思考其中的含義和解釋性,如果讓我自己回答這個問題,我理解到用決策樹的信息熵計算統計概率得出葉子節點的重要性,再加上擬合殘差的思路就是xgb類的演算法了。而神經網路方面,我則簡單的理解為求導擬合。高中課本里的一次函數二次函數的求導大家都會,神經網路只是用鏈式法則給若干個矩陣求導罷了,思路還是朝著目標去擬合。
如此之粗淺,見笑了,下面看看別人的insights玩法。
2 Use Cases for Model Insights
2.1 一個模型的哪些東西是可解釋的
許多人認為機器學習模型是黑盒子,在他們認為模型可以做出很好的預測,但是大家無法理解這些預測背後的邏輯。確實是,很多數據科學家不知道如何用模型來解釋數據的實際意義。這裡的文章將會從這麼幾個方面來討論:
- 哪些特徵在模型看到是最重要的?
- 關於某一條記錄的預測,每一個特徵是如何影響到最終的預測結果的?
- 從大量的記錄整體來考慮,每一個特徵如何影響模型的預測的?
2.2 為什麼這些解釋信息是有價值的
- 調試模型用
- 指導工程師做特徵工程
- 指導數據採集的方向
- 指導人們做決策
- 建立模型和人之間的信任
調試模型用
一般的真實業務場景會有很多不可信賴的,沒有組織好的臟數據。你在預處理數據時就有可能加進來了潛在的錯誤,或者不小心泄露了預測目標的信息等,考慮各種潛在的災難性後果,debug的思路就尤其重要了。當你遇到了用現有業務知識無法解釋的數據的時候,了解模型預測的模式,可以幫助你快速定位問題,balabala
指導特徵工程
特徵工程通常是提升模型準確率最有效的方法。特徵工程通常涉及到到反覆的操作原始數據(或者之前的簡單特徵),用不同的方法來得到新的特徵。
有時候你完成FE的過程只用到了自己的直覺。這其實還不夠,當你有上百個原始特徵的時候,或者當你缺乏業務背景知識的時候,你將會需要更多的指導方向。
這個預測貸款結果的kaggle競賽就是一個比較極端的例子,這個比賽有上百個原始特徵。並且因為隱私原因,特徵的名稱都是f1, f2, f3
等等而不是普通的英文單詞來描述。這就模擬了一個場景,你沒有任何業務方面直覺的場景。有一位參賽者發現了某兩個特徵相減f527, f528
可以創建出特別有用的新特徵。擁有這個新特徵的模型比沒有這個特徵的模型優秀很多。但是當你面對幾百個特徵時,你如何創造出這樣優秀的特徵呢?
在這門課程里,你將會學習到找到最重要的特徵的方法,並且可以發現兩個特別相關的特徵,當面對越來越多的特徵的時候,這種方法就會很重要啦。
指導未來數據收集
對於網上下載的數據集你完全控制不了。不過很多公司和機構用數據科學來指導他們從更多方面收集數據。一般來說,收集新數據很可能花費比較高或者不是很容易,所以大家很想要知道哪些數據是值得收集的。基於模型的洞察力分析可以教你很好的理解已有的特徵,這將會幫助你推斷什麼樣子的新特徵是有用的
指導人們決策
一些決策是模型自動做出來的,雖然亞馬遜不會用人工來決定展示給你網頁上的商品,但是很多重要的決策是由人來做出的,而對於這些決定,模型的洞察力會比模型的預測結果更有價值。
建立信任
很多人在做重要決策的時候不會輕易的相信模型,除非他們驗證過模型的一些基本特性,這當然是合理的。實際上,把模型的可解釋性展示出來,如果可以匹配上人們對問題的理解,那麼這將會建立起大家對模型的信任,即使是在那些沒有數據科學知識的人群中。
3 Permuation Importance
一個最基本的問題大概會是什麼特徵對我模型預測的影響最大呢? 這個東西就叫做「feature importance」即特徵重要性。anyway,字面意思看這個東東就很重要啦。我們有很多方法來衡量特徵的重要性,這裡呢,將會介紹一種方法:排列重要性。這種方法和其他方法比起來,優勢有:
- 計算速度快
- 廣泛使用和理解
- Consistent with properties we would want a feature importance measure to have
3.1 工作原理
排列重要性,一定是在model訓練完成後,才可以計算的。簡單來說,就是改變數據表格中某一列的數據的排列,看其對預測準確性的影響有多大。大概三個步驟:
- 訓練好模型
- 拿某一個feature column, 然後隨機打亂順序。然後用模型來重新預測一遍,看看自己的metric或者loss function變化了多少
- 把上一個步驟中打亂的column復原,換下一個column重複上一個步驟,直到所有column都算一遍
3.2 代碼示例
這個case是利用FIFA 18很多場的足球比賽的數據統計,來預測"Winner of The Game"
代碼片段1,這個算是把模型訓練完畢
import numpy as npimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierdata = pd.read_csv(../input/fifa-2018-match-statistics/FIFA 2018 Statistics.csv)y = (data[Man of the Match] == "Yes") # Convert from string "Yes"/"No" to binaryfeature_names = [i for i in data.columns if data[i].dtype in [np.int64]]X = data[feature_names]train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)my_model = RandomForestClassifier(random_state=0).fit(train_X, train_y)
代碼片段2,用eli5庫,計算排列重要性
import eli5from eli5.sklearn import PermutationImportanceperm = PermutationImportance(my_model, random_state=1).fit(val_X, val_y)eli5.show_weights(perm, feature_names = val_X.columns.tolist())
3.3 解讀permutation importance
在上圖中,頂部的是對結果影響最大的,底部的是影響最小的。因為是隨機的打亂排列計算出來的,所以多做了幾次這個操作,對metric的影響大小,用的是mean加減std這兩個數字來表示的。
偶爾會看到重要性的值是負數,這意味著那一列特徵被打亂後比打亂前的預測準確率還要高。這意味著這列數據基本沒啥用。在較小的數據集里會更容易出現這種情況。
這裡例子里,最重要的特徵是「Goals scored」, 這看起來是合理的。足球運動員對這些特徵的重要性的順序會有自己的見解。
4 Partial Dependence Plots
特徵重要性可以告訴你哪些特徵是最重要的或者是不重要的,而partial dependence圖可以告訴你一個特徵是如何影響預測的。
這個圖特別適合用來回答類似這樣的問題:
- 在所有的房屋的特徵中,經度和緯度是如何影響房價的?或者說,在不同的地區同樣面積的房屋價格有多少相似呢?
- 預測人們健康狀況時,飲食結構的不同會帶來多大的影響?還是有其他更重要的影響因素?
如果你對線性回歸或者邏輯回歸比較熟悉,那麼partial dependence可以被類比為這兩類模型中的「係數」。並且partial dependence在複雜模型中的作用比在簡單模型中更大,抓出更複雜的特性。
4.1 工作原理
和permutation importance一樣,partial dependence plots是需要在模型訓練完畢後才能計算出來。
同樣還是用FIFA2018的數據集,不同的球隊在各個方面都是不一樣的。比如傳球數,射門數,射進數等等。一眼看過去,很難區分這些特徵對結果的影響有多大。為了清晰的分析,我們還是先只拿出某一行數據,比如說這一行數據里,有控球時間50%,100次傳球,10次射門和一個進球。我們將會用已有模型來預測結果,將這一行的某一個變數,反覆的進行修改和重新預測,比如將控球時間修改從50%修改為60%,等等。持續觀察預測結果,在不同的控球率時有什麼樣的變化。
這裡的例子,只用到了一行數據。特徵之間的相互作用關係通過這一行來觀察可能不太妥當,那麼考慮用多行數據來進行試驗,然後根據平均值畫出圖像來。
4.2 代碼示例
代碼片段3,還是先訓練好一個決策樹模型
import numpy as npimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.tree import DecisionTreeClassifierdata = pd.read_csv(../input/fifa-2018-match-statistics/FIFA 2018 Statistics.csv)y = (data[Man of the Match] == "Yes") # Convert from string "Yes"/"No" to binaryfeature_names = [i for i in data.columns if data[i].dtype in [np.int64]]X = data[feature_names]train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)tree_model = DecisionTreeClassifier(random_state=0, max_depth=5, min_samples_split=5).fit(train_X, train_y)
一般是為了解釋起來比較方便,所以這裡用的是決策樹,自己實際玩的時候可以用更複雜的模型。
代碼片段4,決策樹結構
from sklearn import treeimport graphviztree_graph = tree.export_graphviz(tree_model, out_file=None, feature_names=feature_names)graphviz.Source(tree_graph)
這個圖大意如下:1. 擁有child節點的節點裡的信息,第一行標記的是如何決策拆分的;2.每個節點下面幾行的數據,都是表示該節點上的已經確定的value
代碼片段5,用PDPBox庫來畫出Partial Dependence Plot
from matplotlib import pyplot as pltfrom pdpbox import pdp, get_dataset, info_plots# 創建好畫圖所需的數據pdp_goals = pdp.pdp_isolate(model=tree_model, dataset=val_X, model_features=feature_names, feature=Goal Scored)# 畫出「Goal Scored」這一特徵的partial dependence plotpdp.pdp_plot(pdp_goals, Goal Scored)plt.show()
在解讀這個圖的時候,有幾點是值得注意的:第一, y軸是預測結果的變化量,第二,藍色陰影區域代表了信心的大小。從這幅圖可以看出,進球數的增加肯定可以增加贏得MVP的概率,但是進更多的球的時候,對這個概率影響不大了。
代碼片段6,再來看另外一個特徵:
feature_to_plot = Distance Covered (Kms)pdp_dist = pdp.pdp_isolate(model=tree_model, dataset=val_X, model_features=feature_names, feature=feature_to_plot)pdp.pdp_plot(pdp_dist, feature_to_plot)plt.show()
這幅圖看起來更單調一點。因為模型是用的比較簡單。你可以從決策樹的圖中看出這個圖和決策樹上的數據一一匹配的樣子。(咦?這個是咋看出來的?我咋沒看出來)
代碼片段7,如果換一個模型,用隨機森林的話,圖會略有不同
# Build Random Forest modelrf_model = RandomForestClassifier(random_state=0).fit(train_X, train_y)pdp_dist = pdp.pdp_isolate(model=rf_model, dataset=val_X, model_features=feature_names, feature=feature_to_plot)pdp.pdp_plot(pdp_dist, feature_to_plot)plt.show()
隨機森林認為差不多跑動距離大約100KM左右是最容易獲得MVP的,跑動距離多了概率會下降一點。
總的來說,這個曲線的平滑程度看上去比決策樹更加合理一點。在處理小數據集的時候,解讀模型要十分小心。
4.3 2D Partial Dependence Plots
如果你對特徵之間的相互關係感興趣的話,那麼2D PDP圖肯定用得上了。
代碼片段8,還是用決策樹模型,來創建一個簡單的圖,看看是否匹配之前的樹結構。
# Similar to previous PDP plot except we use pdp_interact instead of pdp_isolate and pdp_interact_plot instead of pdp_isolate_plotfeatures_to_plot = [Goal Scored, Distance Covered (Kms)]inter1 = pdp.pdp_interact(model=tree_model, dataset=val_X, model_features=feature_names, features=features_to_plot)pdp.pdp_interact_plot(pdp_interact_out=inter1, feature_names=features_to_plot, plot_type=contour)plt.show()
上圖展示了進球數和跑動距離的各種組合情況下的預測結果。例如,我們看到了最高的預測值來自於當一個球隊進了一個球並且跑動距離接近於100km的時候。當進球數為0的時候,跑動距離無論多少都沒啥關係了。你能不能從決策樹的圖中分析出當進球數為0時的這種情況呢?
這個圖能清晰的顯示出,跑動距離在有進球的時候才會影響預測結果。
5 SHAP Values
前面已經介紹了兩種技術,現在將要介紹的技術可以讓你觀察到某一個item預測中各個特徵對預測結果產生的影響:SHAP(是SHapley Additive exPlanations的縮寫) values。這個技術應用場景可以是:1. 一個模型說銀行不可以貸款給你,但是在法律上銀行需要基本的解釋為什麼拒絕了你的貸款申請;2. 一個醫療保健中心想要知道在某一類疾病患者中,每一個患者的最大的病因是什麼,然後醫療中心可以對症下藥
5.1 How They Work
原文這一段略扯啊,沒有直接說工作原理,而是舉例說明如何工作的。原文也註明了說,關於真正原理性的解釋,請參考這篇博客
這一句話我愣是沒看懂說了啥工作原理,有人看到可以請幫幫我。SHAP values interpret the impact of having a certain value for a given feature in comparison to the prediction wed make if that feature took some baseline value.
這裡還是用FIFA 18足球比賽的數據集來舉例,SHAP值的計算原理如下:
sum(SHAP values for all features) = pred_for_team - pred_for_baseline_values
他有一個baseline value,然後呢,他給出所有特徵基於這個baseline的值是多少,當然也會有最後預測值是多少。可以看到如下圖所示:
如何解讀這張圖呢,我們預測值是0.7,基準值是0.4979,增加預測值的特徵顏色是紅色,長條的面積代表了數值的大小。降低預測值的特徵顏色為藍色。可以看到最大的增加預測值的特徵是「進球數」為2。但是我們可以看到控球率這個還比較有意義的特徵在這裡是降低了預測值。
它用到了複雜的技術來保證了那個公式,baseline值加上各個特徵的影響值等於最後的預測值(聽起來不是那麼直觀到底是為什麼)。這裡我們不繼續探討細節,這對於使用SHAP values不是critical的。偏理論一點的解釋文章,參考這個博客鏈接。
5.2 代碼示例
這裡我們用到的庫是宇宙第一無與倫比的Shap(github link),快去給作者們點個贊。
代碼片段9:還是先訓練好一個隨機森林
import numpy as npimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierdata = pd.read_csv(../input/fifa-2018-match-statistics/FIFA 2018 Statistics.csv)y = (data[Man of the Match] == "Yes") # Convert from string "Yes"/"No" to binaryfeature_names = [i for i in data.columns if data[i].dtype in [np.int64, np.int64]]X = data[feature_names]train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)my_model = RandomForestClassifier(random_state=0).fit(train_X, train_y)
代碼片段10:計算出單次預測的SHAP值
import shap # package used to calculate Shap valuesrow_to_show = 5data_for_prediction = val_X.iloc[row_to_show] # use 1 row of data here. Could use multiple rows if desireddata_for_prediction_array = data_for_prediction.values.reshape(1, -1)# Create object that can calculate shap valuesexplainer = shap.TreeExplainer(my_model)# Calculate Shap valuesshap_values = explainer.shap_values(data_for_prediction)
上面計算出來的shap_values里有兩個數組。第一個數組裡存放的是給預測值負面影響的數字,第二個數組裡存著正面影響的數值。直接查看兩個數組的數字可能會有點蠢,所以shap library上場來一個漂亮的可視化。可視化之前,我們先看一預測結果,my_model.predict_proba(data_for_prediction_array)
的輸出是array([[0.3, 0.7]])
,可以看到這個隊伍有70%的概率有成員可以獲得最終的MVP。
代碼片段11:shap可視化
shap.initjs()shap.force_plot(explainer.expected_value[1], shap_values[1], data_for_prediction)
如果你仔細觀察我們創建SHAP values時的代碼,你回注意到我們用的是一個TreeExplainer。shap庫其實對其他的模型也有對應的解釋器(explainers): * shap.DeepExplainer 是用於解釋深度學習模型的 * shap.KernelExplainer 可用於解釋所有的模型,儘管它運行時間比其他解釋器時間長一點,但是它能夠給出一個近似的的Shap values.
代碼片段12:KernelExplainer的使用,可以和treeExplainer給出同樣的結果
# use Kernel SHAP to explain test set predictionsk_explainer = shap.KernelExplainer(my_model.predict_proba, train_X)k_shap_values = k_explainer.shap_values(data_for_prediction)shap.force_plot(explainer.expected_value[1], shap_values[1], data_for_prediction)
6 SHAP Value進階
前情提要:
Shap values可以告訴我們一個特徵對我們的一條預測結果產生了多大的影響。
Shap library 是個不錯的工具,可以告訴我們具體的shap values是多少並且有不錯的可視化來展現
6.1 Summary Plots
Permutation importance很不錯,因為它用很簡單的數字就可以衡量特徵對模型的重要性。但是它不能handle這麼一種情況:當一個feature有中等的permutation importance的時候,這可能意味著這麼兩種情況:1:對少量的預測有很大的影響,但是整體來說影響較小;2:對所有的預測都有中等程度的影響。
這個時候,SHAP summary plot上場了,它可以對特徵重要性來一個鳥瞰圖,看是FIFA足球的數據集,如下圖:
這個圖上有很多點,對吧,每一個點都有三個含義:
- 豎直坐標是說明它屬於哪個特徵
- 顏色代表了這個特徵在某一行數據里的數值是高還是低
- 水平位置代表了這個特徵在某一行數據里是提高預測值還是降低預測值
比如左上角的那個藍點的意思就是,進球數特別少,給預測值降低了0.25。 其他幾個很容易觀察出來的方面包括:
- 這個模型幾乎忽略了Red and Yellow和Red這兩個特徵
- Yellow Card這個特徵,通常不會有大的影響,但是也有幾個極端的例子大幅降低了預測值
- Goal Scored這個特徵,大體上和預測值有正相關性
如果你認真觀察這個圖,可以抓到更多有效信息。
6.2 代碼示例: Summary Plots
代碼片段13,建立隨機深林模型
import numpy as npimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierdata = pd.read_csv(../input/fifa-2018-match-statistics/FIFA 2018 Statistics.csv)y = (data[Man of the Match] == "Yes") # Convert from string "Yes"/"No" to binaryfeature_names = [i for i in data.columns if data[i].dtype in [np.int64, np.int64]]X = data[feature_names]train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)my_model = RandomForestClassifier(random_state=0).fit(train_X, train_y)
代碼片段14,畫圖summary plots
import shap # package used to calculate Shap values# Create object that can calculate shap valuesexplainer = shap.TreeExplainer(my_model)# calculate shap values. This is what we will plot.# Calculate shap_values for all of val_X rather than a single row, to have more data for plot.shap_values = explainer.shap_values(val_X)# Make plot. Index of [1] is explained in text below.shap.summary_plot(shap_values[1], val_X)
這段代碼不是很複雜,但是有幾點需要注意的: * 當畫圖的時候,我們用變數shap_values[1]
,如果是分類問題,那麼這變數是一個array,每一個元素里都是某一個類別的SHAP values。在這個例子里,這裡是存放的為True的概率 * 計算SHAP values可能會比較慢。在這裡不會是大問題,因為這個數據集比較小。不過將來你用的時候得小心使用的數據集不要太大。不過有一個例外:當使用xgboost的時候,SHAP會有一些優化來加速。
上面這個圖給了一個不錯的overview,但是想要深入了解某一個特徵的話,看下面的dependence contributions plots吧。
6.3 SHAP Dependence Contribution Plots
之前我們用Partial Dependence Plots來展示過某一個特徵對模型的影響。那是非常有用的洞察力工具,並且還可以解釋給非技術類的朋友聽。
但是,有很多信息PDP無法展示。例如,某一特徵的影響分布是怎樣的,這種影響是一個常量,還是大多依賴於其他特徵的值。SHAP dependence contribuition plots可以畫出一種類似PDP的圖,但是有更多的細節。
我們先關注形狀,之後再來看顏色。圖中的每一個點都代表了一行數據。橫坐標是控球率(直接來自特徵Ball Posssession),縱坐標表示的是這個特徵的這個值對最後的預測結果有什麼影響。從大體圖像上的斜坡形狀可以看出控球率越高就會給預測值帶來越多正效益。
我們再來看下圖中標記出來的兩個點,有幾乎相同的控球率,但是SHAP value卻差距很大。這裡看不出有什麼不同,覺著吧,如果和一起其他的重要特徵一起看看,估計能發現什麼。
而下面這個圖中,可以看到,儘管控球率很高,但是由於進球數不夠,所以也是預測值會降低,說明當一個球隊長期持球而不進球,那麼也是屬於表現不好。
6.4 代碼示例:SHAP Dependence Contribution Plots
代碼片段15,畫圖SHAP DCP
import shap # package used to calculate Shap values# Create object that can calculate shap valuesexplainer = shap.TreeExplainer(my_model)# calculate shap values. This is what we will plot.shap_values = explainer.shap_values(X)# make plot.shap.dependence_plot(Ball Possession %, shap_values[1], X, interaction_index="Goal Scored")
如果interaction_index
那裡不指定參數的話,Shapley會根據演算法自動選擇一個比較有趣的來。幾行代碼就可以畫好這個圖,更重要的是洞察出有意義的信息。
6. End
暫時就介紹到這裡啦!原文每一個part都有編程練習,難度也不大,有興趣的朋友可以去做一做。
我是通過這個ML insights學到了不少東西,相信這個知識在今後的無論是工作中還是比賽中都會大有益處。
推薦閱讀: