戰(zhàn) + 4 個最常見的誤讀)
SHAP 可解釋性入門模型為什么這么判Python 實(shí)戰(zhàn) 4 個最常見的誤讀關(guān)鍵詞前置SHAP、可解釋性、Shapley 值、模型歸因、Python 實(shí)戰(zhàn)模型準(zhǔn)確率做到 0.93業(yè)務(wù)方第一個問題往往不是還能不能再高一點(diǎn)而是**“這一單為什么被拒了”**。這時候你會發(fā)現(xiàn)一個尷尬的事實(shí)XGBoost / LightGBM / CatBoost 能給你一個預(yù)測值卻說不清這個預(yù)測值是怎么來的。特征重要性feature_importances_只告訴你這個特征整體上有用回答不了對這一個樣本來說哪個特征把它推向了這個結(jié)果。SHAP 解決的就是這個問題。它不是另一種模型而是一套把預(yù)測值拆開分給每個特征的數(shù)學(xué)方法。一、SHAP 到底在算什么一場分蛋糕的博弈SHAP 的全稱是 SHapley Additive exPlanations核心來自博弈論里的Shapley 值。原始問題很樸素幾個人合作完成一件事產(chǎn)出了一塊蛋糕這塊蛋糕該怎么分才公平公平分法要滿足四條效率性分完的總和 蛋糕總量不多不少對稱性貢獻(xiàn)一樣的人拿一樣多無效性沒貢獻(xiàn)的人拿 0可加性兩件事一起干分到的等于分開干之和。把這四條翻譯成模型語言就是模型輸出 基準(zhǔn)值(所有樣本平均預(yù)測) 各特征的 SHAP 值之和用公式寫就是# 對任意一個樣本 x# base_value → 訓(xùn)練集上的平均預(yù)測對數(shù)幾率空間# shap_values → 每個特征對這個樣本的貢獻(xiàn)增量assertabs(base_valueshap_values.sum()-model_output)1e-6這條等式是 SHAP 最值錢的地方它不是重要度打分而是真的把預(yù)測值拆成了可加的幾份每一份都能指名道姓地說是這個特征推高了 0.32。這就是為什么 SHAP 能解釋單樣本而feature_importances_不能——后者算的是全局的、分裂增益的累計(jì)值跟某個具體樣本的預(yù)測沒有加法關(guān)系。二、三分鐘跑通TreeExplainer 三張圖SHAP 對樹模型有專門的TreeExplainer復(fù)雜度是多項(xiàng)式的不用遍歷所有特征子集速度非??臁mportshapimportpandasaspdfromsklearn.model_selectionimporttrain_test_splitfromsklearn.datasetsimportload_breast_cancerfromxgboostimportXGBClassifier# 1. 準(zhǔn)備數(shù)據(jù)dataload_breast_cancer(as_frameTrue)X,ydata.data,data.target X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,random_state42,stratifyy)# 2. 訓(xùn)練模型modelXGBClassifier(n_estimators300,max_depth4,learning_rate0.05,eval_metriclogloss)model.fit(X_train,y_train)# 3. 解釋explainershap.TreeExplainer(model)shap_valuesexplainer(X_test)# shap 新版返回 Explanation 對象print(shap_values.shape)# (114, 30) —— 114 個樣本 × 30 個特征圖 1全局特征重要性蜂群圖shap.summary_plot(shap_values,X_test)這張圖里每個點(diǎn)是一個樣本橫軸是 SHAP 值對模型輸出的貢獻(xiàn)顏色是該特征本身的大小。點(diǎn)越靠右說明這個特征把預(yù)測往正類推得越狠。它比feature_importances_多了一層信息能看出方向。同樣是重要特征有的特征值越大越推高預(yù)測紅色在右有的正好相反紅色在左。圖 2單樣本解釋瀑布圖shap.plots.waterfall(shap_values[0])從上往下讀E[f(X)]是基線f(x)是這個樣本的最終輸出中間每一行是一個特征的貢獻(xiàn)。紅黃藍(lán)三色分別代表推高、微弱、拉低。這張圖就是為什么這一單被拒的標(biāo)準(zhǔn)答案。圖 3特征依賴散點(diǎn)圖shap.plots.scatter(shap_values[:,worst area])看單個特征取值和它 SHAP 值的關(guān)系能看出模型學(xué)到的是不是線性、有沒有閾值效應(yīng)。三、4 個最常見的誤讀面試和匯報都愛考誤讀 1把 SHAP 值當(dāng)因果關(guān)系錯。SHAP 值回答的是模型認(rèn)為這個特征貢獻(xiàn)了多少不是改變這個特征結(jié)果會變多少。如果模型學(xué)到的是逾期次數(shù)和是否被拒的相關(guān)性而真實(shí)因果鏈?zhǔn)鞘杖氲?→ 逾期多 → 被拒那 SHAP 會把貢獻(xiàn)算在逾期次數(shù)頭上。你把逾期次數(shù)改小模型輸出會變但真實(shí)世界不會跟著變。判斷方法拿 SHAP 找出候選特征后還得做 A/B 或因果推斷驗(yàn)證不能直接拿它做決策依據(jù)。誤讀 2忽略基線值只看 SHAP 值大小SHAP 值是相對基線的增量。base_value會隨訓(xùn)練數(shù)據(jù)分布變化——同樣一個模型、同樣一個樣本換一批訓(xùn)練集base_value 變了所有 SHAP 值都會跟著變。所以匯報時一定要帶上基線print(f基線對數(shù)幾率:{explainer.expected_value:.3f})print(f該樣本輸出:{explainer.expected_valueshap_values[0].values.sum():.3f})脫離基線談這個特征貢獻(xiàn)了 0.8是沒有意義的。誤讀 3相關(guān)特征的貢獻(xiàn)會被攤薄兩個高度相關(guān)的特征比如總面積和房間數(shù)Shapley 值的定義要求遍歷所有特征子集所以它們會分?jǐn)偙緛韺儆诠餐畔⒌哪遣糠重暙I(xiàn)。結(jié)果就是單看每個特征的 SHAP 值都不大但它們其實(shí)聯(lián)合起來非常重要。處理辦法建模前先做相關(guān)性剪枝相關(guān)系數(shù) 0.9 的留一個或者用shap.plots.bar看整體不要盯著單個特征下結(jié)論。誤讀 4分類任務(wù)拿錯了輸出維度二分類模型調(diào)用explainer.shap_values(X)時老版本返回的是shape (n_samples, n_features)正類的值但有些模型/版本會返回(n_samples, n_features, n_classes)。一旦搞錯維度圖會畫反結(jié)論也跟著反。svexplainer(X_test).valuesprint(sv.shape)# (114, 30) 是正類(114, 30, 2) 是兩類# 若是三維取正類ifsv.ndim3:svsv[...,1]新版 shap0.40推薦用explainer(X)返回Explanation對象用.values取值能少踩這個坑。四、工程落地的 3 個性能坑坑 1大數(shù)據(jù)集上慢到不可用TreeExplainer已經(jīng)很快但 10 萬行 × 100 特征照樣要跑幾分鐘。辦法# 用背景數(shù)據(jù)采樣代替全量KernelExplainer 必做TreeExplainer 可選backgroundshap.sample(X_train,100)explainershap.TreeExplainer(model,databackground)對非樹模型神經(jīng)網(wǎng)絡(luò)、線性模型加交互KernelExplainer是通用的但極慢樣本數(shù)一定要控制在 100200???2類別特征要先編碼SHAP 作用于特征矩陣不是原始列。CatBoost 的cat_features、pandas 的category類型都要先轉(zhuǎn)數(shù)值X_encodedpd.get_dummies(X,drop_firstTrue)# 或者用 OrdinalEncoder但要注意樹模型能接受坑 3線上實(shí)時解釋要做緩存每次預(yù)測都算一遍 SHAP 會拖慢接口。實(shí)際做法是離線批量算好高頻樣本的 SHAP 值存進(jìn)特征庫線上只對命中風(fēng)控規(guī)則的少量樣本實(shí)時算用model.predict(X, output_marginTrue)拿到對數(shù)幾率空間的原始輸出和 SHAP 的base_value對齊默認(rèn)是 margin 空間不是概率空間。最后這點(diǎn)最容易出錯base_value shap_values.sum()等于的是logit要經(jīng)過sigmoid才是概率。直接拿去和predict_proba比會發(fā)現(xiàn)對不上。importnumpyasnp logitexplainer.expected_valueshap_values[0].values.sum()prob1/(1np.exp(-logit))assertabs(prob-model.predict_proba(X_test.iloc[[0]])[0,1])1e-4五、什么時候該用、什么時候別用該用需要向業(yè)務(wù)/風(fēng)控/監(jiān)管解釋單個決策信貸拒單、內(nèi)容打標(biāo)、醫(yī)療輔助想檢查模型是不是學(xué)到了泄漏特征某個未來信息特征 SHAP 值異常高八成是泄漏做特征篩選SHAP 值長期接近 0 的特征可以直接刪。別用想要因果結(jié)論去做因果推斷SHAP 只是相關(guān)性拆解特征高度共線又沒做處理貢獻(xiàn)會被攤薄解釋失真把 SHAP 圖直接貼給用戶——非技術(shù)讀者看不懂瀑布圖要做成因?yàn)槟?X 指標(biāo)偏高這種話術(shù)。小結(jié)記住三句話就夠了SHAP 值是可加的增量基線 所有特征貢獻(xiàn) 模型輸出這條等式是它能解釋單樣本的根本原因它解釋的是模型不是世界SHAP 高不等于因果別直接拿它做干預(yù)決策先看基線再看貢獻(xiàn)先剪共線再看單特征90% 的誤讀都出在這兩步。把 SHAP 接進(jìn)你的訓(xùn)練腳本只需要三行代碼但它能幫你在下一次被問為什么的時候給出一個有數(shù)字、有方向的答案。延伸閱讀XGBoost / LightGBM / CatBoost 三篇入門指南在我的專欄里配合本文一起看從訓(xùn)練到解釋是一條完整鏈路。你在用 SHAP 時踩過哪些坑評論區(qū)聊聊。