LightGBM/XGBoostのtree構造可視化メモ

# はじめに

LightGBMとXGboostのtree構造を可視化したいと思いdtreevizとplot_treeを試したときの備忘録。

# 環境

実行した環境は以下の通り。

```bash

$sw_vers
ProductName:	Mac OS X
ProductVersion:	10.13.6
BuildVersion:	17G14042
```

Jupyter Notebookを使用しました。

```
The version of the notebook server is: 5.7.8
The server is running on this version of Python:
Python 3.7.3 (default, Mar 27 2019, 16:54:48) 
[Clang 4.0.1 (tags/RELEASE_401/final)]
```

# モデル構築

tree構造の可視化が目的なので、データはscikit-learnのbostonデータセットを使用しました。  506行13列のデータで、すべてNullがなくfloat型なので、特徴量エンジニアリングなどはせず、そのままモデルを構築します。

```py
import pandas as pd
import sklearn.datasets as skd

data = skd.load_boston()

df_X = pd.DataFrame(data.data, columns=data.feature_names)
df_y = pd.DataFrame(data.target, columns=['y'])

df_X.info()
#<class 'pandas.core.frame.DataFrame'>
#RangeIndex: 506 entries, 0 to 505
#Data columns (total 13 columns):
#CRIM       506 non-null float64
#ZN         506 non-null float64
#INDUS      506 non-null float64
#CHAS       506 non-null float64
#NOX        506 non-null float64
#RM         506 non-null float64
#AGE        506 non-null float64
#DIS        506 non-null float64
#RAD        506 non-null float64
#TAX        506 non-null float64
#PTRATIO    506 non-null float64
#B          506 non-null float64
#LSTAT      506 non-null float64
#dtypes: float64(13)
#memory usage: 51.5 KB
```

LightGBMのモデル構築。ハイパーパラメータなどはほぼデフォルトを使用します。

```py

import lightgbm as lgb
from sklearn.model_selection import train_test_split

df_X_train, df_X_test, df_y_train, df_y_test = train_test_split(df_X, df_y, test_size=0.2, random_state=4)

lgb_train = lgb.Dataset(df_X_train, df_y_train)
lgb_eval = lgb.Dataset(df_X_test, df_y_test)

params = {
    'seed':4,
    'metric':'rmse'}

lgbm = lgb.train(params,
                lgb_train,
                valid_sets=lgb_eval,
                num_boost_round=200,
                early_stopping_rounds=20,
                verbose_eval=50)

#Training until validation scores don't improve for 20 rounds
#[50]	valid_0's rmse: 3.58803
#[100]	valid_0's rmse: 3.39545
#[150]	valid_0's rmse: 3.31867
#[200]	valid_0's rmse: 3.28222
#Did not meet early stopping. Best iteration is:
#[192]	valid_0's rmse: 3.27283
```

XGBoostのモデル構築。

```py
import xgboost as xgb

dtrain = xgb.DMatrix(df_X_train, label=df_y_train)
dtest = xgb.DMatrix(df_X_test, label=df_y_test)

params = {
    'objective': 'reg:squarederror',
    'eval_metric': 'rmse',
}

evals = [(dtrain, 'train'), (dtest, 'eval')]
evals_result = {}

xgbm = xgb.train(params,
                dtrain,
                num_boost_round=200,
                early_stopping_rounds=20,
                evals=evals,
                evals_result=evals_result
                )

#(中略)
#[80]	train-rmse:0.05752	eval-rmse:3.96890
#[81]	train-rmse:0.05430	eval-rmse:3.96797
#[82]	train-rmse:0.05194	eval-rmse:3.96835
```

# dtreeviz

決定木アルゴリズムの決定木を可視化するツール。
[公式のGithub](https://github.com/parrt/dtreeviz)にインストール方法が記載されていますが、```pip install dtreeviz```を実行した際に、内部で```xgboost```をインストールするようになっていて、エラーとなってしまいインストールができませんでした。
そこで、[こちら](https://qiita.com/TakuyaToda/items/b0f91617c253cd79da8e)を参考にしたら、```xgboost```を無事にインストールできました。

```xgboost```のインストール後に、```pip install dtreeviz```を再度実行して、無事にdtreevizのインストール完了。

LightGBMのモデルに対してdtreevizを使用しようとすると、

```py
from dtreeviz.trees import *

viz = dtreeviz(lgbm,
               x_data=df_X_train,
               y_data=df_y_train['y'],
               target_name='y',
               feature_names=df_X_train.columns.tolist(),
              tree_index=0)
              
viz.save('./lgb_tree.svg')    
 
#ValueError: Tree model must be in (DecisionTreeRegressor, DecisionTreeClassifier, xgboost.core.Booster, but was Booster      
```

ということで、そもそもdtreevizはLightGBMに対して使えないことが分かりました。

気を取り直して、XGBoostのモデルに対して可視化してみます。

```py
from dtreeviz.trees import *

viz = dtreeviz(xgbm,
               x_data=df_X_train,      # x_dataはDataFrame
               y_data=df_y_train['y'], # y_dataはSeries
               target_name='y',
               feature_names=df_X_train.columns.tolist(), # StringのList
              tree_index=0 ) # アンサンブルモデルを使用するときはindexを指定しないとエラーになる
              

viz.save('./xgb_tree.svg')   
```

dtreevizでは出力がsvg形式なので、一度Inkscapeを起動してpdfへ変換しました。
Inkscapeはversion 1.0.1を使用しています。インストールは[ここ](https://enbarqment.jp/inkscape-18)を参考にしました。下図のような結果を得ました。

![xgb_tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/177e08c5-3d3a-a1c7-c4ee-ff0ace477cb7.png)


# plot_tree (graphviz)

LightGBMとXGBoostにplot_treeという関数が用意されていて、これでtree構造を可視化できます。
内部でgraphvizを使用するので、インストールが必要となります。
インストール方法は[こちら](https://graphviz.org/download/)に記載されているように、
```brew install graphviz```でOKのはずですが、自分の環境だと

```
Error: graphviz: no bottle available!
You can try to install from source with e.g.
  brew install --build-from-source graphviz
Please note building from source is unsupported. You will encounter build
failures with some formulae. If you experience any issues please create pull
requests instead of asking for help on Homebrew's GitHub, Twitter or any other
official channels.
```

エラーが出てインストールできなかったので、エラーメッセージにあるように

```bash

$brew install --build-from-source graphviz
```

を実行。これでインストールできました。
まずはLightGBMのtree構造を可視化します。

```py

ax = lgb.plot_tree(gbm, tree_index=0, figsize=(20, 20), show_info=['split_gain'])
plt.show()
graph = lgb.create_tree_digraph(gbm, tree_index=0, format='png', name='Tree')
graph.render(view=True)
```

得られた出力が下図。

![Tree.gv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/b5463ba2-d2a9-0f09-012b-f39a6031ea04.png)

続いてXGBoostの可視化もしてみます。

```py
ax = xgb.plot_tree(xgbm, num_trees=0, figsize=(20, 20))
plt.show()
graph = xgb.to_graphviz(xgbm, num_trees=0)
graph.render(view=True,format='png')
```

実行すると下図を得ます。

![Source.gv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/b36c1902-0b54-cbfa-68cd-2924626da00d.png)

# まとめ

LightGBMとXGBoostに対して、dtreevizとplot_treeを試してみました。
dtreevizだと散布図が表示されるので、イメージしやすいと思いました。
ただ、使えるアルゴリズムが限られているので、ちょっと不便。