決定木

所要時間

200分

学ぶコト

・決定木の概要
・決定木をPythonで実装

決定木を実装してみよう!

決定木の概要について理解できたところで、実際に手を動かしながら決定木を実装していきましょう!

決定木の実装には分類問題によく使われるirisというアヤメの花のデータを使っていきます。

それでは実際に手を動かしながら見ていきましょう!

まず、以下のようにライブラリをインポートしていきます。

# ライブラリの読み込み
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

決定木を使うためにscikit-learn(サイキットラーン)というライブラリをインポートしています。

scikit-learnはPythonのオープンソース機械学習ライブラリであり、呼び出す時はsklearnと記述します。

ここでは、DecisionTreeClassifier(決定木モデルを構築するため)、plot_tree(決定木分岐を描画するため)、train_test_split(学習データと予測データに分けるため)、confusion_matrix(予測値と実測値がどれだけ合致しているかを測る混合行列を使うため)をインポートしています。

それでは、早速irisデータをインポートしていきましょう!

# irisデータの読み込み
df = sns.load_dataset("iris")
df
決定木1

それぞれ、

sepal_lengthsepal_widthpetal_lengthpetal_width

花のがくの長さ、がくの幅、花弁の長さ、花弁の幅を表しています。

それぞれの花の特徴からどのアヤメの種類(species)なのかを判別していきます。

アヤメの種類は何種類あるのでしょうか?

そんな時は以下のように記述してあげましょう。

df["species"].unique()

array([‘setosa’, ‘versicolor’, ‘virginica’], dtype=object)

setosaとversicolorとvirginicaの3種類のタイプがあることが分かりました。

この3種類を分類していきます。

まずは、データフレームを目的変数と説明変数に分けていきます。

# 説明変数と目的変数に分ける
df_x = df.drop('species', axis=1)
df_y = df['species']

続いてデータを学習データとテストデータに分けていきましょう!

#学習データとテストデータに分割
train_x, test_x, train_y, test_y = train_test_split(df_x, df_y, test_size=0.5)

これで説明変数と目的変数をそれぞれ学習データとテストデータに分けることが出来ました。

続いて決定木でモデル構築をおこなっていきます。

#モデル学習
model = DecisionTreeClassifier()
model.fit(train_x, train_y)

model.fit(説明変数, 目的変数)とすることでモデルを作成することができます。

ちなみに今回デフォルトでの実装を行いますが決定木は以下のようにたくさんのパラメータを持っています。

DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion=’gini’, max_depth=None, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=’deprecated’, random_state=None, splitter=’best’)

決定木手法のアルゴリズムには変化させることのできるパラメータがいくつかあり、それらを調整することで精度を改善することができるのです。

それでは、このモデルで予測値を算出してみましょう!

# 予測値を算出
pred_y = model.predict(test_x)
pred_y

array([‘versicolor’, ‘virginica’, ‘versicolor’, ‘virginica’, ‘setosa’, ‘virginica’, ‘setosa’, ‘versicolor’, ‘virginica’, ‘versicolor’, ‘virginica’, ‘virginica’, ‘virginica’, ‘versicolor’, ‘versicolor’, ‘versicolor’, ‘setosa’, ‘versicolor’, ‘virginica’, ‘versicolor’, ‘virginica’, ‘versicolor’, ‘versicolor’, ‘versicolor’, ‘setosa’, ‘versicolor’, ‘virginica’, ‘setosa’, ‘versicolor’, ‘versicolor’, ‘setosa’, ‘virginica’, ‘versicolor’, ‘setosa’, ‘virginica’, ‘versicolor’, ‘setosa’, ‘versicolor’, ‘virginica’, ‘versicolor’, ‘virginica’, ‘virginica’, ‘versicolor’, ‘setosa’, ‘versicolor’, ‘setosa’, ‘setosa’, ‘setosa’, ‘versicolor’, ‘virginica’, ‘setosa’, ‘setosa’, ‘versicolor’, ‘versicolor’, ‘versicolor’, ‘versicolor’, ‘virginica’, ‘virginica’, ‘virginica’, ‘setosa’, ‘setosa’, ‘virginica’, ‘versicolor’, ‘virginica’, ‘virginica’, ‘virginica’, ‘virginica’, ‘setosa’, ‘setosa’, ‘virginica’, ‘versicolor’, ‘setosa’, ‘setosa’, ‘versicolor’, ‘setosa’], dtype=object)

こんな形で3つの種のうちどれであるかという予測値が配列で返ってきているのが分かります。

では、実際の値とどれだけ合致しているのでしょうか?

実は正解率を見るためには以下のように記述してあげれば問題ありません。

# 正解率を表示
model.score(test_x, test_y)

0.96

たったこれだけで、実際の値と予測したい値の正解率がどのくらいなのか算出することが分かります。

今回は150個を学習データとテストデータで半分に分けているので75個分の判別になります。

96%ということは75個中72個が正解していることが分かります。

では、実際に3つの種のうちどの種を当てることが出来ているのかマトリクスで見てみましょう。

# 混合行列を算出
confusion_matrix(pred_y, test_y)

array([[21, 0, 0],
   [ 0, 28, 1],
   [ 0, 2, 23]])

結果は学習データとテストデータの分け方で変わるのでこちらに出力されている結果とご自身で実装した場合の結果は異なる可能性があります

このように予実の組み合わせた行列を混合行列(Confusion matrix)と呼びます。

ちょっと分かりにくいのですが、それぞれの行列は[“setosa”, “versicolor”, “virginica”]の順番になっています。

そして行方向がpred_yすなわち予測値を表していて、列方向がtest_yすなわち実測値を表しています。

setosaは21個のうち全てを正解することができ、versicolorは30個のうち2個をvirginicaと間違えてしまっていることが分かります。

またvirginicaは24個のうち1つをversicolorと間違えてしまっています。

名前からもイメージが湧きますが、versicolorとvirginicaは判別にしにくいのかもしれないなという仮説が立ちますね!

それでは、続いて決定木の分岐プロットを見てみましょう。

決定木では特徴量によって分岐されていく様子を分かりやすく図示することが出来るので解釈容易性が高く現状の構造を把握するのに向いています。

## ツリーマップ
plt.figure(figsize=(20,10))
plot_tree(model, feature_names=df_x.columns, filled=True, proportion=True, class_names=["setosa", "versicolor", "virginica"])
plt.show()

plot_treeの中身が少し複雑なので見ていきましょう。

まずは、この決定木のモデル(model)を指定しています。

それ以外は実は必要不可欠ではないのですが、あったほうが間違いなく見やすいので覚えておきましょう!

feature_namesを引数にそれぞれの説明変数を指定することで、分岐の際の説明変数名を表示してくれます。

filled=Trueにすることで、プロットに色が付きます。

proportion=Trueにすることで、それぞれのノードにサンプル数の割合が表示されます。

class_namesを指定することでそれぞれのノードの最も多いクラスを表示してくれます。

決定木2

こんな決定木のプロットが表示されました。

非常に見やすくて分かりやすいですね。

どの説明変数で分類されているのかが一目で分かります。

例えば、最初の分岐でpetal_widthが0.75以下のサンプルは全てsetosaに分類されています。

setosaを見分けるのは簡単そうですね。

最後に決定木モデルのパラメータについて見ていきましょう!

最初のモデル構築の際に以下のようにモデルのパラメータが出力されました。

#モデル学習
model = DecisionTreeClassifier()
model.fit(train_x, train_y)

DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion=’gini’, max_depth=None, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=’deprecated’, random_state=None, splitter=’best’)

これは決定木内のパラメータを表していて、特に指定しなければデフォルトで決まってしまうのですが、手動でパラメータを指定してあげることも可能です。

例えば、max_depthは葉の深さを表します。

現状はNoneになっているので深さの制限はありませんが、あまりにも深いと分岐の図が見にくくなったり、学習データに過度にフィッティングしすぎてしまう過学習という問題を起こす可能性があります。

そのためmax_depthを指定して葉の深さを制限することがあります。

それでは試しにmax_depthを指定してもう一度分岐の図を描画してみましょう!

# 説明変数と目的変数に分ける
df_x = df.drop('species', axis=1)
df_y = df['species']

#学習データをテストデータを分割
train_x, test_x, train_y, test_y = train_test_split(df_x, df_y, test_size=0.5)

#モデル学習
model = DecisionTreeClassifier(max_depth=3)
model.fit(train_x, train_y)

## ツリーマップ
plt.figure(figsize=(10,5))
plot_tree(model, feature_names=df_x.columns, filled=True, proportion=True, class_names=["setosa", "versicolor", "virginica"])
plt.show()
決定木3

ちゃんと葉の深さが3で止まっていることが分かります。

他にもたくさんの決定木のパラメータがあるので以下の公式リファレンスを読みながらパラメータを動かしてみて理解を深めましょう!

sklearn.tree.DecisionTreeClassifier

ここまでで決定木について見てきました。

決定木は現状の可視化や基礎分析によく使うのでぜひ使えるようになっておきましょう。

課題

・パラメータや使う変数を変化させて描画される決定木が変わることを確認してみましょう!