一文帶您了解SHAP:機器學(xué)習(xí)的模型解釋
在機器學(xué)習(xí)和數(shù)據(jù)科學(xué)領(lǐng)域,模型的可解釋性一直是研究者和實踐者關(guān)注的焦點。隨著深度學(xué)習(xí)和集成方法等復(fù)雜模型的廣泛應(yīng)用,理解模型的決策過程變得尤為重要??山忉屓斯ぶ悄埽‥xplainable AI ,XAI)通過提高模型的透明度,幫助建立對機器學(xué)習(xí)模型的信任和信心。
XAI是一套工具和框架,用于理解和解釋機器學(xué)習(xí)模型如何做出決策。其中,Python中的SHAP(SHapley Additive exPlanations)庫是一個非常有用的工具。SHAP庫能夠量化特征對單個預(yù)測及整體預(yù)測的貢獻(xiàn),并提供美觀且易于使用的可視化功能。
接下來,我們將概括介紹下SHAP庫的基礎(chǔ)知識,以理解在Scikit-learn中構(gòu)建的回歸和分類模型的預(yù)測。
SHAP 和SHAP values
SHAP(SHapley Additive Explanations)是一種解釋任何機器學(xué)習(xí)模型輸出的博弈論方法。它利用經(jīng)典的博弈論Shapley值及其相關(guān)擴(kuò)展,將最優(yōu)的信用分配與局部解釋相結(jié)合(詳細(xì)信息和引用請參閱相關(guān)論文:https://github.com/shap/shap#citations)。
SHAP values幫助我們量化特征對預(yù)測的貢獻(xiàn)。SHAP值越接近于零,表示該特征對預(yù)測的貢獻(xiàn)越小;而SHAP值遠(yuǎn)離零,表示該特征對預(yù)測的貢獻(xiàn)越大。
安裝shap 包:
pip install shap -i https://pypi.tuna.tsinghua.edu.cn/simple
我們看下面的示例:如何在回歸問題中獲取特征的SHAP值。我們將從加載庫和示例數(shù)據(jù)開始,然后快速構(gòu)建一個模型來預(yù)測糖尿病的進(jìn)展情況:
import numpy as np
np.set_printoptions(formatter={'float':lambda x:"{:.4f}".format(x)})
import pandas as pd
pd.options.display.float_format = "{:.3f}".format
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style='darkgrid', context='talk', palette='rainbow')
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.ensemble import (RandomForestRegressor,
RandomForestClassifier)
import shap
shap.initjs()
# Import sample data
diabetes = load_diabetes(as_frame=True)
X = diabetes['data'].iloc[:, :4] # Select first 4 columns
y = diabetes['target']
# Partition data
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2,
random_state=1)
print(f"Training features shape: {X_train.shape}")
print(f"Training target shape: {y_train.shape}\n")
print(f"Test features shape: {X_test.shape}")
print(f"Test target shape: {y_test.shape}")
display(X_train.head())
# Train a simple model
model = RandomForestRegressor(random_state=42)
model.fit(X_train, y_train)
一種常見的獲取SHAP值的方法是使用Explainer對象。接下來創(chuàng)建一個Explainer對象,并為測試數(shù)據(jù)提取shap_test值:
explainer = shap.Explainer(model)
shap_test = explainer(X_test)
print(f"Shap values length: {len(shap_test)}\n")
print(f"Sample shap value:\n{shap_test[0]}")
shap_test的長度為89,因為它包含了每個測試實例的記錄。從查看第一個測試記錄中,我們可以看到它包含三個屬性:
shap_test[0].base_values:目標(biāo)的基準(zhǔn)值
shap_test[0].data:每個特征的值
shap_test[0].values:每個對象的SHAP值
- 基準(zhǔn)值:基準(zhǔn)值(shap_test.base_values),也稱為期望值(explainer.expected_value),是訓(xùn)練數(shù)據(jù)中目標(biāo)值的平均值。
print(f"Expected value: {explainer.expected_value[0]:.1f}")
print(f"Average target value (training data): {y_train.mean():.1f}")
print(f"Base value: {np.unique(shap_test.base_values)[0]:.1f}")
- shap_test.data 包含與 X_test 相同的值
(shap_test.data == X_test).describe()
- values:shap_test 最重要的屬性是 values 屬性,因為我們可以通過它訪問 SHAP 值。讓我們將 SHAP 值轉(zhuǎn)換為 DataFrame,以便于操作:
shap_df = pd.DataFrame(shap_test.values,
columns=shap_test.feature_names,
index=X_test.index)
shap_df
可以看到每條記錄中每個特征的 SHAP 值。如果將這些 SHAP 值加到期望值上,就會得到預(yù)測值:
np.isclose(model.predict(X_test),
explainer.expected_value[0] + shap_df.sum(axis=1))
現(xiàn)在我們已經(jīng)有了 SHAP 值,可以進(jìn)行自定義可視化,如下圖所示,以理解特征的貢獻(xiàn):
columns = shap_df.apply(np.abs).mean()\
.sort_values(ascending=False).index
fig, ax = plt.subplots(1, 2, figsize=(11,4))
sns.barplot(data=shap_df[columns].apply(np.abs), orient='h',
ax=ax[0])
ax[0].set_title("Mean absolute shap value")
sns.boxplot(data=shap_df[columns], orient='h', ax=ax[1])
ax[1].set_title("Distribution of shap values");
plt.show()
左側(cè)子圖顯示了每個特征的平均絕對 SHAP 值,而右側(cè)子圖顯示了各特征的 SHAP 值分布。從這些圖中可以看出,bmi 在所使用的4個特征中貢獻(xiàn)最大。
Shap 內(nèi)置圖表
雖然我們可以使用 SHAP 值構(gòu)建自己的可視化圖表,但 shap 包提供了內(nèi)置的華麗可視化圖表。在本節(jié)中,我們將熟悉其中幾種選擇的可視化圖表。我們將查看兩種主要類型的圖表:
- 全局:可視化特征的整體貢獻(xiàn)。這種類型的圖表顯示了特征在整個數(shù)據(jù)集上的匯總貢獻(xiàn)。
- 局部:顯示特定實例中特征貢獻(xiàn)的圖表。這有助于我們深入了解單個預(yù)測。
- 條形圖/全局:對于之前顯示的左側(cè)子圖,有一個等效的內(nèi)置函數(shù),只需幾個按鍵即可調(diào)用:
shap.plots.bar(shap_test)
這個簡單但有用的圖表顯示了特征貢獻(xiàn)的強度。該圖基于特征的平均絕對 SHAP 值而生成:shap_df.apply(np.abs).mean()。特征按照從上到下的順序排列,具有最高平均絕對 SHAP 值的特征顯示在頂部。
- 總結(jié)圖/全局:另一個有用的圖是總結(jié)圖:
shap.summary_plot(shap_test)
以下是解釋這張圖的指南:
- 圖的橫軸顯示了特征的 SHAP 值分布。每個點代表數(shù)據(jù)集中的一個記錄。例如,我們可以看到對于 BMI 特征,點的分布相當(dāng)散亂,幾乎沒有點位于 0 附近,而對于年齡特征,點更加集中地分布在 0 附近。
- 點的顏色顯示了特征值。這個額外的維度允許我們看到隨著特征值的變化,SHAP 值如何變化。換句話說,我們可以看到關(guān)系的方向。例如,我們可以看到當(dāng) BMI 較高時(由熱粉色點表示)SHAP 值傾向于較高,并且當(dāng) BMI 較低時(由藍(lán)色點表示)SHAP 值傾向于較低。還有一些紫色點散布在整個光譜中。
- 熱力圖/全局:熱力圖是另一種可視化 SHAP 值的方式。與將 SHAP 值聚合到平均值不同,我們看到以顏色編碼的個體值。特征繪制在 y 軸上,記錄繪制在 x 軸上:
shap.plots.heatmap(shap_test)
這個熱力圖的頂部還補充了每個記錄的預(yù)測值(即 f(x))的線圖。
- Force plot/全局:這個交互式圖表允許我們通過記錄查看 SHAP 值的構(gòu)成。
shap.initjs()
shap.force_plot(explainer.expected_value, shap_test.values,
X_test)
就像熱力圖一樣,x 軸顯示每個記錄。正的 SHAP 值顯示為紅色,負(fù)的 SHAP 值顯示為藍(lán)色。例如,由于第一個記錄的紅色貢獻(xiàn)比藍(lán)色貢獻(xiàn)多,因此該記錄的預(yù)測值將高于期望值。
交互性允許我們改變兩個軸。例如,y 軸顯示預(yù)測值 f(x),x 軸根據(jù)輸出(預(yù)測)值排序,如上面的快照所示。
- 條形圖/局部:現(xiàn)在我們將看一下用于理解個別案例預(yù)測的圖表。讓我們從一個條形圖開始:
shap.plots.bar(shap_test[0])
與“ 條形圖/全局 ”中完全相同,只是這次我們將數(shù)據(jù)切片為單個記錄。
- Force plot/局部:Force plot是單個記錄的強制圖。
shap.initjs()
shap.plots.force(shap_test[0])
分類模型的SHAP values/圖表
上面示例是回歸模型,下面我們以分類模型展示SHAP values及可視化:
import numpy as np
np.set_printoptions(formatter={'float':lambda x:"{:.4f}".format(x)})
import pandas as pd
pd.options.display.float_format = "{:.3f}".format
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style='darkgrid', context='talk', palette='rainbow')
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import shap
from sklearn.datasets import fetch_openml
# 加載 Titanic 數(shù)據(jù)集
titanic = fetch_openml('titanic', version=1, as_frame=True)
df = titanic.frame
# 選擇特征和目標(biāo)變量
features = ['pclass', 'age', 'sibsp', 'parch', 'fare']
df = df.dropna(subset=features + ['survived']) # 刪除包含缺失值的行
X = df[features]
y = df['survived']
# 分割數(shù)據(jù)集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 訓(xùn)練隨機森林分類器
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
和回歸模型一樣的,shap values 值也是包括base_values 和values 值:
explainer = shap.Explainer(model)
shap_test = explainer(X_test)
print(f"Length of shap_test: {len(shap_test)}\n")
print(f"Sample shap_test:\n{shap_test[0]}")
print(f"Expected value: {explainer.expected_value[1]:.2f}")
print(f"Average target value (training data): {y_train}")
print(f"Base value: {np.unique(shap_test.base_values)[0]:.2f}")
shap_df = pd.DataFrame(shap_test.values[:,:,1],
columns=shap_test.feature_names,
index=X_test.index)
shap_df
我們仔細(xì)檢查一下將 shap 值之和添加到預(yù)期概率是否會給出預(yù)測概率:
np.isclose(model.predict_proba(X_test)[:,1],
explainer.expected_value[1] + shap_df.sum(axis=1))
內(nèi)置圖與回歸模型是一致的,比如:
shap.plots.bar(shap_test[:,:,1])
或者瀑布圖如下:
shap.plots.waterfall(shap_test[:,:,1][0])
示例
看一個具體的用例。我們將找出模型對幸存者預(yù)測最不準(zhǔn)確的例子,并嘗試?yán)斫饽P蜑槭裁磿龀鲥e誤的預(yù)測:
test = pd.concat([X_test, y_test], axis=1)
test['probability'] = model.predict_proba(X_test)[:,1]
test['order'] = np.arange(len(test))
test.query("survived=='1'").nsmallest(5, 'probability')
生存概率為第一個記錄的746。讓我們看看各個特征是如何對這一預(yù)測結(jié)果產(chǎn)生貢獻(xiàn)的:
ind1 = test.query("survived=='1'")\
.nsmallest(1, 'probability')['order'].values[0]
shap.plots.waterfall(shap_test[:,:,1][ind1])
主要是客艙等級和年齡拉低了預(yù)測值。讓我們在訓(xùn)練數(shù)據(jù)中找到類似的例子:
pd.concat([X_train, y_train],
axis=1)[(X_train['pclass']==3) &
(X_train['age']==29) &
(X_train['fare'].between(7,8))]
所有類似的訓(xùn)練實例實際上都沒有幸存?,F(xiàn)在,這就說得通了!這是一個小的分析示例,展示了 SHAP 如何有助于揭示模型為何會做出錯誤預(yù)測。
在機器學(xué)習(xí)和數(shù)據(jù)科學(xué)中,模型的可解釋性一直備受關(guān)注。可解釋人工智能(XAI)通過提高模型透明度,增強對模型的信任。SHAP庫是一個重要工具,通過量化特征對預(yù)測的貢獻(xiàn),提供可視化功能。本文介紹了SHAP庫的基礎(chǔ)知識,以及如何使用它來理解回歸和分類模型的預(yù)測。通過具體用例,展示了SHAP如何幫助解釋模型錯誤預(yù)測。