決策樹和隨機森林用 python treeinterpreter實現
示例說明
這個部落格深入到決策樹和隨機森林的基礎上,以便更好地解釋它們。
在過去的幾年中,隨機森林是一種新興的機器學習技術。它是一種基於非線性樹的模型,可以提供精確的結果。然而,大多是黑箱,通常很難解釋和充分理解。在本文中,我們將深入瞭解隨機森林的基本知識,以便更好地掌握它們。首先從決策樹開始。
編譯環境是 jupyter notebook, 可以通過安裝 Anaconda,匯入 scikit-learn 庫(sklearn 安裝)可以很容易實現,另外要用到 treeinterpreter ,用到的資料集為 abalone 資料集,本文的 github示例程式碼。其中程式碼為 Decision_Tree_and_Random_Forest.ipynb
tree_interp_functions.py
中有很多 plot 函式。
概述
Decision_Tree_and_Random_Forest.ipynb
程式碼中主要分為兩個部分
* 決策樹
* 隨機森林
用到的資料集 abalone,其中 Rings
是預測值:要麼作為連續值,要麼作為分類值。具體為:
名稱 | 資料型別 | 單位 | 描述 |
---|---|---|---|
Sex | nominal | – | M, F, and I (infant) |
Length | continuous | mm | Longest shell measurement |
Diameter(直徑) | continuous | mm | perpendicular to length |
Height | continuous | mm | with meat in shell |
Whole weight | continuous | grams | whole abalone |
Shucked weight(去殼重) | continuous | grams | weight of meat |
Viscera weight(內臟重) | continuous | grams | gut weight (after bleeding) |
Shell weight(殼重) | continuous | grams | after being dried |
Rings(預測值) | integer | – | +1.5 gives the age in years |
首先 import 各種庫
from __future__ import division
from IPython.display import Image, display
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.datasets import make_moons
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor,\
export_graphviz
from treeinterpreter import treeinterpreter as ti
import pydotplus
from tree_interp_functions import *
設定 matplotlib,設定 seaborn 顏色
# Set default matplotlib settings
plt.rcParams['figure.figsize'] = (10, 7)
plt.rcParams['lines.linewidth'] = 3
plt.rcParams['figure.titlesize'] = 26
plt.rcParams['axes.labelsize'] = 18
plt.rcParams['axes.titlesize'] = 22
plt.rcParams['xtick.labelsize'] = 14
plt.rcParams['ytick.labelsize'] = 14
plt.rcParams['legend.fontsize'] = 16
# Set seaborn colours
sns.set_style('darkgrid')
sns.set_palette('colorblind')
blue, green, red, purple, yellow, cyan = sns.color_palette('colorblind')
Section 1: 決策樹
決策樹通過以貪婪的方式迭代地將資料分成不同的子集。對於迴歸樹,是最小化所有子集中的 MSE(均方誤差)或 MAE(平均絕對誤差)來選擇。對於分類樹,通過最小化生成子集中的熵或 Gini 來決定分類。由此產生的分類器將特徵空間分隔成不同的子集。分別設定深度為 1,2,5,10.
light_blue, dark_blue, light_green, dark_green, light_red, dark_red = sns.color_palette('Paired')
x, z = make_moons(noise=0.20, random_state=5)
df = pd.DataFrame({'z': z,
'x': x[:, 0],
'y': x[:, 1]
})
md_list = [1, 2, 5, 10]
fig, ax = plt.subplots(2, 2)
fig.set_figheight(10)
fig.set_figwidth(10)
for i in xrange(len(md_list)):
md = md_list[i]
ix_0 = int(np.floor(i/2))
ix_1 = i%2
circle_dt_clf = DecisionTreeClassifier(max_depth=md, random_state=0)
circle_dt_clf.fit(df[['x', 'y']], df['z'])
xx, yy = np.meshgrid(np.linspace(df.x.min() - 0.5, df.x.max() + 0.5, 50),
np.linspace(df.y.min() - 0.5, df.y.max() + 0.5, 50))
z_pred = circle_dt_clf.predict(zip(xx.reshape(-1), yy.reshape(-1)))
z_pred = np.array([int(j) for j in z_pred.reshape(-1)])\
.reshape(len(xx), len(yy))
ax[ix_0, ix_1].contourf(xx, yy, z_pred, cmap=plt.get_cmap('GnBu'))
df.query('z == 0').plot('x', 'y', kind='scatter',
s=40, c=green, ax=ax[ix_0, ix_1])
df.query('z == 1').plot('x', 'y', kind='scatter',
s=40, c=light_blue, ax=ax[ix_0, ix_1])
ax[ix_0, ix_1].set_title('Max Depth: {}'.format(md))
ax[ix_0, ix_1].set_xticks([], [])
ax[ix_0, ix_1].set_yticks([], [])
ax[ix_0, ix_1].set_xlabel('')
ax[ix_0, ix_1].set_ylabel('')
plt.tight_layout()
plt.savefig('plots/dt_iterations.png')
匯入 abalone 資料。 展示決策樹和隨機森林迴歸以及分類如何工作的。我們使用 Rings
變數作為連續變數,並從中建立一個二進位制變數來表示 Rings
。
column_names = ["sex", "length", "diameter", "height", "whole weight",
"shucked weight", "viscera weight", "shell weight", "rings"]
abalone_df = pd.read_csv('abalone.csv', names=column_names)
abalone_df['sex'] = abalone_df['sex'].map({'F': 0, 'M': 1, 'I': 2})
abalone_df['y'] = abalone_df.rings.map(lambda x: 1 if x > 9 else 0)
abalone_df.head()
將資料集分為 train 和 test。
abalone_train, abalone_test = train_test_split(abalone_df, test_size=0.2,
random_state=0)
X_train = abalone_train.drop(['sex', 'rings', 'y'], axis=1)
y_train_bin_clf = abalone_train.y
y_train_multi_clf = abalone_train.sex
y_train_reg = abalone_train.rings
X_test = abalone_test.drop(['sex', 'rings', 'y'], axis=1)
y_test_bin_clf = abalone_test.y
y_test_multi_clf = abalone_test.sex
y_test_reg = abalone_test.rings
X_train = X_train.copy().reset_index(drop=True)
y_train_bin_clf = y_train_bin_clf.copy().reset_index(drop=True)
y_train_multi_clf = y_train_multi_clf.copy().reset_index(drop=True)
y_train_reg = y_train_reg.copy().reset_index(drop=True)
X_test = X_test.copy().reset_index(drop=True)
y_test_bin_clf = y_test_bin_clf.copy().reset_index(drop=True)
y_test_multi_clf = y_test_multi_clf.copy().reset_index(drop=True)
y_test_reg = y_test_reg.copy().reset_index(drop=True)
建立簡單的決策樹和隨機森林模型,可以設定決策樹的深度來看 interpretation 的用法。
dt_bin_clf = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=0)
dt_bin_clf.fit(X_train, y_train_bin_clf)
dt_multi_clf = DecisionTreeClassifier(criterion='entropy', max_depth=2, random_state=0)
dt_multi_clf.fit(X_train, y_train_multi_clf)
dt_reg = DecisionTreeRegressor(criterion='mse', max_depth=3, random_state=0)
dt_reg.fit(X_train, y_train_reg)
rf_bin_clf = RandomForestClassifier(criterion='entropy', max_depth=10, n_estimators=100, random_state=0)
rf_bin_clf.fit(X_train, y_train_bin_clf)
rf_multi_clf = RandomForestClassifier(criterion='entropy', max_depth=10, n_estimators=100, random_state=0)
rf_multi_clf.fit(X_train, y_train_multi_clf)
rf_reg = RandomForestRegressor(criterion='mse', max_depth=10, n_estimators=100, random_state=0)
rf_reg.fit(X_train, y_train_reg)
建立特徵貢獻值,用 ti.predict
可以得到預測值,偏差項和貢獻值. 貢獻值矩陣是一個 3D 陣列,由每個樣本的貢獻值,特徵和分類標籤組成。
dt_bin_clf_pred, dt_bin_clf_bias, dt_bin_clf_contrib = ti.predict(dt_bin_clf, X_test)
rf_bin_clf_pred, rf_bin_clf_bias, rf_bin_clf_contrib = ti.predict(rf_bin_clf, X_test)
dt_multi_clf_pred, dt_multi_clf_bias, dt_multi_clf_contrib = ti.predict(dt_multi_clf, X_test)
rf_multi_clf_pred, rf_multi_clf_bias, rf_multi_clf_contrib = ti.predict(rf_multi_clf, X_test)
dt_reg_pred, dt_reg_bias, dt_reg_contrib = ti.predict(dt_reg, X_test)
rf_reg_pred, rf_reg_bias, rf_reg_contrib = ti.predict(rf_reg, X_test)
視覺化決策樹,利用 graphviz
視覺化決策樹。可以顯示到每個葉子節點的路徑以及每個節點分類的比例。
reg_dot_data = export_graphviz(dt_reg,
out_file=None,
feature_names=X_train.columns
)
reg_graph = pydotplus.graph_from_dot_data(reg_dot_data)
reg_graph.write_png('plots/reg_dt_path.png')
Image(reg_graph.create_png())
為了預測鮑魚的年輪數,決策樹將沿著樹向下移動,直到它到達一片樹葉。每一步將當前子集拆分為兩個子集。對於每次分裂,Rings
均值變化定義為變數的貢獻值,決定怎麼分裂。
變數 dt_reg
是 sklearn 分類器目標值,x_test
表示 Pandas 或 NumPy 陣列,包含我們希望得到的預測和貢獻值的特徵變數。貢獻值變數
dt_reg_contrib
是一個 2D NumPy陣列(n_obs,n_features),其中 n_obs
觀測數,n_features
是特徵的數量。繪製一個給定鮑魚的各特徵的貢獻值,看看哪些特徵最影響其預測值。從下面的圖中可以看出,這種特定的鮑魚的重量和長度值對其預測的 Rings
有負面影響。
# Find abalones that are in the left-most leaf
X_test[(X_test['shell weight'] <= 0.0587) & (X_test['length'] <= 0.2625)].head()
df, true_label, pred = plot_obs_feature_contrib(dt_reg,
dt_reg_contrib,
X_test,
y_test_reg,
3,
order_by='contribution'
)
plt.tight_layout()
plt.savefig('plots/contribution_plot_dt_reg.png')
可以用 violin 畫出這個特定鮑魚與整個種群的各變數貢獻值比較,下圖中,可以看出這個特定鮑魚殼重相較其他相比異常低,事實上,大部分鮑魚的殼重對應一個正的貢獻值。
df, true_label, score = plot_obs_feature_contrib(dt_reg,
dt_reg_contrib,
X_test,
y_test_reg,
3,
order_by='contribution',
violin=True
)
plt.tight_layout()
plt.savefig('plots/contribution_plot_violin_dt_reg.png')
以上的描述並沒有對一個特定的變數如何影響鮑魚的 Rings
有一個全面的解釋。因此,我們可以根據一個特定特徵的值繪製給它的貢獻值。如果把殼重與它的貢獻值進行比較,我們就可以看出隨著殼重增加其貢獻值增加。
plot_single_feat_contrib('shell weight', dt_reg_contrib, X_test, class_index=1)
plt.savefig('plots/shell_weight_contribution_dt.png')
再來看看去殼重這個變數的貢獻值具有非線性、非單調的特點。低的去殼重沒有貢獻,高的去殼重具有負的的貢獻,而在低和高之間具有正的貢獻。
plot_single_feat_contrib('shucked weight', dt_reg_contrib, X_test, class_index=1)
plt.savefig('plots/shucked_weight_contribution_dt.png')
Section 2: 擴充套件到隨機森林
以上的過程都可以擴充套件到隨機森林,看看變數在森林中所有樹的平均貢獻。
df, true_label, pred = plot_obs_feature_contrib(rf_reg,
rf_reg_contrib,
X_test,
y_test_reg,
3,
order_by='contribution'
)
plt.tight_layout()
plt.savefig('plots/contribution_plot_rf.png')
df = plot_obs_feature_contrib(rf_reg,
rf_reg_contrib,
X_test,
y_test_reg,
3,
order_by='contribution',
violin=True
)
plt.tight_layout()
plt.savefig('plots/contribution_plot_violin_rf.png')
由於隨機森林本質上是隨機的,對於給定的殼重下的貢獻具有可變性。然而,平滑的黑色趨勢線仍顯示出增長的趨勢。與決策樹一樣,我們看到殼重增加對應於較高的貢獻
plot_single_feat_contrib('shell weight', rf_reg_contrib, X_test,
class_index=1, add_smooth=True, frac=0.3)
plt.savefig('plots/shell_weight_contribution_rf.png')
再看看 直徑 這個變數,我們具有複雜的、非單調的特點。直徑似乎在貢獻約0.45下降,在0.3和0.6左右的貢獻高峰。除此之外,直徑與目標變數 Rings
似乎具有普遍的正相關關係。
plot_single_feat_contrib('diameter', rf_reg_contrib, X_test,
class_index=1, add_smooth=True, frac=0.3)
plt.savefig('plots/diameter_contribution_rf.png')
再看看其他變數
plot_single_feat_contrib('shucked weight', rf_reg_contrib, X_test,
class_index=1, add_smooth=True, frac=0.3)
plt.savefig('plots/shucked_weight_contribution_rf.png')
如果按照性別來分類 分為 male,female,infant,可以畫出每一類的貢獻,比如說 infant 這一類
df, true_label, scores = plot_obs_feature_contrib(dt_multi_clf,
dt_multi_clf_contrib,
X_test,
y_test_multi_clf,
3,
class_index=2,
order_by='contribution',
violin=True
)
true_value_list = ['Female', 'Male', 'Infant']
score_dict = zip(true_value_list, scores)
title = 'Contributions for Infant Class\nTrue Value: {}\nScores: {}'.format(true_value_list[true_label],
', '.join(['{} - {}'.format(i, j) for i, j in score_dict]))
plt.title(title)
plt.tight_layout()
plt.savefig('plots/contribution_plot_violin_multi_clf_dt.png')
像之前的一樣,我們還可以為每個類特徵和貢獻值的對應圖。對於是雌性的鮑魚,貢獻隨殼重的增加而增加,而對 infant 的鮑魚,其貢獻隨殼重增加而減小。對雄性來說,當殼重超過0.5時,貢獻開始增加,然後減少。
fig, ax = plt.subplots(1, 3, sharey=True)
fig.set_figwidth(20)
for i in xrange(3):
plot_single_feat_contrib('shell weight', rf_multi_clf_contrib, X_test,
class_index=i, class_name=class_names[i],
add_smooth=True, c=colours[i], ax=ax[i])
plt.tight_layout()
plt.savefig('plots/shell_weight_contribution_by_sex_rf.png')
關於隨機森林的總結:
隨機森林是一個並行的,典型的高效能的機器學習模型。為了滿足的客戶的業務需求,我們不僅要提供一個高度預測模型,而且要模型也可以解釋。也就是說,不是給他們一個黑盒子,不管模型表現得有多好。特別是對於政府或者金融界的客戶。
依賴的 packages
- matplotlib
- pandas
- numpy
- seaborn
- treeinterpreter