1. 程式人生 > >決策樹和隨機森林用 python treeinterpreter實現

決策樹和隨機森林用 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

歡迎關注