2. Pythonで綴る多変量解析　7-1. 決定木(scikit-learn)

*   決定木（けってい-ぎ）はデシジョン・ツリー(Decision Tree)の名でも知られています。
*   ある目的に到達するために、データの各属性（説明変数）を条件として、分岐をくり返してクラス分けをする方法です。そのプロセスが全体として樹形図に示されることにちなんで、意思決定(decision)のための木(tree)という名がついています。
*   クラス分けのターゲットとなる目的変数が、カテゴリデータの場合を**分類木**(classification tree)、数値データの場合を**回帰木**(regression tree)といいます。

**ここでは、まず分類木の例を一通りやってみます。**

###⑴ ライブラリのインポート
```python
# 決定木モデルを構築するクラス
from sklearn.tree import DecisionTreeClassifier
# 決定木モデルをベースとするモジュール
from sklearn import tree

# 機械学習用データセットのパッケージ
from sklearn import datasets
# データを分割するためのユーティリティ
from sklearn.model_selection import train_test_split

# Notebook内に画像を表示させるモジュール
from IPython.display import Image  
# 決定木モデルを可視化するためのモジュール
import pydotplus
```

###⑵ データの取得と読み込み
```python
iris = datasets.load_iris()
```
*   あまりに有名なデータセットで今更のようですが、３種類の花菖蒲（アイリス）の｢花びら｣と｢がく｣の長さ・幅をそれぞれ計測した４つの特徴量が格納されたデータセットです。
*   ３種類は、セトーサ(Setosa)、ヴァーシカーラ(Versicolour)、ヴァジーニカ(Virginica)というカテゴリデータで、種類ごとに50サンプルずつ、計150サンプルとなっています。
*   scikit-learn公式の解説はこちら、https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html

||変数名|意味|注記|データ型|
|---|---|---|---|---|
|1|sepal length|がく片の長さ|連続量(cm)|float64|
|2|sepal width|がく片の幅|連続量(cm)|float64|
|3|petal length|花びらの長さ|連連続量(cm)|float64|
|4|petal width|花びらの幅|連続量(cm)|float64|
|5|species|種別|Setosa=1, Versicolour=2, Virginica=3|int64|

*   irisデータセットの内容は、説明変数（特徴量）のラベルとデータ、目的変数（種別）のラベルとデータ、さらにデータの概要という５つの部分から構成されています。
*   念の為、データがどのように入っているかを確認しておきます。

```python
# 説明変数のラベル
print(iris.feature_names)

# 説明変数の形状
print(iris.data.shape)

# 説明変数の先頭5行を表示
iris.data[0:5, :]
```

*   ４つの計測値を説明変数として、全部で150サンプルとなっています。

![2_7_1_01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/06f557cc-abd9-9463-53d7-8ad9c5be79c7.png)

*   次いで目的変数は、３種類が[0, 1, 2]というカテゴリ変数として格納されています。

```python
# 目的変数のラベル
print(iris.target_names)

# 目的変数の形状
print(iris.target.shape)

# 目的変数を表示
iris.target
```

![2_7_1_02.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/cfa37598-1b07-331f-e820-53545dd9d681.png)

###⑶ データの前処理
```python
# 説明変数・目的変数をそれぞれ格納
X = iris.data
y = iris.target

# 訓練用・テスト用にそれぞれ分割
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
```

*   特に配分比を指定しなければ、デフォルトで訓練用75％、テスト用25％の比率でランダムに分割されます。
*   引数に`random_state=0`を指定することで、分割を何度くり返しても最初に分割した状態が再現されます。

###⑷ 決定木のモデル構築と評価
```python
# 決定木モデルを構築するクラスを初期化
clf = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=0)

# 決定木モデルを生成
model = clf.fit(X_train, y_train)

# 訓練・テストそれぞれの正解率を算出
print('正解率(train):{:.3f}'.format(model.score(X_train, y_train)))
print('正解率(test):{:.3f}'.format(model.score(X_test, y_test)))
```

![2_7_1_03.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/eb537ea1-b292-2c08-6556-14fc089cc9fe.png)

*   決定木が生成される過程でその基準となるのは、カテゴリ識別の**不純度**という指標です。どれだけ不純物が混じっているか、それが 0.0 であれば純粋に分類できていることになります。
*   引数に`criterion='gini'`とあるのがそれで、ここでは明示的に**ジニ不純度(Gini impurity)**を指定しています。（デフォルトがジニ不純度なので特に記述の必要はありません）
*   また、**条件分岐の階層の深さ**は、この例では`max_depth=3`として多くとも３階層までと指定しています。階層数を増やして｢深い木｣にすると、正解率は高くなりますが、過学習のリスクも増すことになり得ます。
*   生成されたモデルに対し、`score()`関数で訓練・テストそれぞれの正解率を算出します。訓練データでは 0.982 と非常に高く、テストデータでは若干それを下回りますが、いずれも 1.0 に近く高い水準となっています。

###⑸ 樹形図(tree diagram)の描画
*   ダイアグラムの描画は、次の３ステップです。

1.   **決定木モデルをDOTデータに変換**
2.   **DOTデータからダイアグラムを描画**
3.   **`png`に変換してNotebook内に表示**


```python
# 決定木モデルをDOTデータに変換
dot_data = tree.export_graphviz(model,                              # 決定木モデルを指定
                                out_file = None,                    # 出力ファイルではなく文字列を返すように指定
                                feature_names = iris.feature_names, # 特徴量の表示名を指定
                                class_names = iris.target_names,    # クラス分類の表示名を指定
                                filled = True)                      # 多数派クラスでノードを色付け

# ダイアグラムを描画
graph = pydotplus.graph_from_dot_data(dot_data)  

# ダイアグラムを表示
Image(graph.create_png())
```

*   **DOTデータ**とは、DOT言語で記述されたデータのことです。**DOT言語**は、グラフ構造（ノードとエッジから成るネットワーク構造）を記述するための言語です。ノードは結節部(□)、エッジは連結線(↓)を意味します。
*   そのようなグラフ構造を描画するためのツールが**Graphviz**です。sklearnのtreeモジュールに属する`export_graphviz()`関数が、決定木モデルをDOT形式に変換してくれます。その際に引数として、描画の仕様や表示名などの細かな指定をしています。
*   次いで、PythonでDOT言語を扱うためのモジュール**pydotplus**の`graph_from_dot_data()`関数をつかってグラフを描画します。
*   さらに、Notebook内にグラフを表示させるために、グラフを`png`に変換して**IPython.display**モジュールの`Image()`メソッドを実行しています。

![2_7_1_04.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/41f1af5d-2b84-4e6f-e545-184efcf40f38.png)

## 樹形図(tree diagram)の見方

![2_7_1_05.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/fa012035-d2a8-280c-0d96-32e120f305b7.png)

*   決定木は、上から見ていきます。まず第一に有効とされた分類条件➀、ここからスタートします。
*   条件式の`petal width (cm) <= 0.8`は「花びらの幅が0.8以下」を意味し、あてはまるなら`True`、それ以外は`False`の矢印を進みます。
*   `True`の矢印を下りてきたノードは、**ジニ不純度が 0.0** を示し、37サンプル全てがsetosa種ということで純粋に分類できています。これが一つ目のゴールです。
*   かたや`False`側のノードは、新たな分類条件➁によって、さらにTrueかFalseに分岐します。このように**ジニ不純度が 0.0 となるゴール**を目指して、分岐しながら階層を下りていくことになります。

###補記
*   以上、google colaboratory上に実装していますので、樹形図をローカルPCに取り込むには次のように行います。

```python
# pngファイルに書き出す
graph.write_png("iris.png")

# google colaboratoryからダウンロード
from google.colab import files
files.download('iris.png')
```

![2_7_1_06.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/19f0b9a0-851a-612b-0e1e-f9f2284f850c.png)