見出し画像

scikit-learn機械学習⑫決定木(Decision Tree)実践編

前回は、決定木(Decision Tree)の理論的な側面を解説しました。今回は、scikit-learnを使って実験を行います。

データセットとしては、scikit-learn.datasets からアイリス(iris、アヤメ)を使用します。タスクは、このデータセットに含まれる花の特徴から花の種類を予測することです。ナイーブ・ベイズの実践編でも使ったので、結果を比較できます。

まずは、データセットを訓練用とテスト用に分割します。次に、訓練用のデータセットから決定木を作成します。そして、学習を終えたモデルをテストセットで評価します。

決定木の良いところは、モデルがどのような判断をしているのかが理解できることです。ブラックボックスではなく、決定木のグラフを生成したり、どの特徴が重要視されているのかを表示することができるので、実践していきます。


Python環境の設定

ここはいつも同様なので、前回の実装と同じ環境を使う方は、飛ばしてください。

まず、仮想環境を作ってscikit-learnなど必要なライブラリをインストールします。

mkdir decision_tree
cd decision_tree

# 仮想環境を作る
python3 -m venv venv
source venv/bin/activate

# pip をアップグレードしておく
pip install --upgrade pip

# 必要なライブラリをインストール
pip install scikit-learn jupyter matplotlib pandas

Jupyterノートブックを立ち上げてPython3のノートブックを作成してください。Jupyterノートブックに関しては、こちらを参照してください。
あるいは、VSCode(Visual Studio Code)を使ってJupyterノートブックを作成しても構いません。私は、どちらかというとVSCodeをよく使います。これについてもこちらで簡単に解説しています。

データの読み込み

ここも前回と同様ですが、便利のため再掲します。

アイリスのデータセットは、scikit-learn から次のように読み込みます。

from sklearn.datasets import load_iris

# データセットの読み込み
data = load_iris(as_frame=True)

as_frame=True と指定しているのは、データを pandas のデータフレームとして読み込むためです。

なお、データセットの説明は、次のように確認できます。

print(data.DESCR)

説明は長いので一部のみをここに掲載します。

.. _iris_dataset:

Iris plants dataset
--------------------

**Data Set Characteristics:**

:Number of Instances: 150 (50 in each of three classes)
:Number of Attributes: 4 numeric, predictive attributes and the class
:Attribute Information:
    - sepal length in cm
    - sepal width in cm
    - petal length in cm
    - petal width in cm
    - class:
            - Iris-Setosa
            - Iris-Versicolour
            - Iris-Virginica

:Summary Statistics:

============== ==== ==== ======= ===== ====================
                Min  Max   Mean    SD   Class Correlation
============== ==== ==== ======= ===== ====================
sepal length:   4.3  7.9   5.84   0.83    0.7826
sepal width:    2.0  4.4   3.05   0.43   -0.4194
petal length:   1.0  6.9   3.76   1.76    0.9490  (high!)
petal width:    0.1  2.5   1.20   0.76    0.9565  (high!)
============== ==== ==== ======= ===== ====================

データには、それぞれの花について以下の4つの特徴が測定されています。

  • sepal length:がく片の長さ(cm)

  • sepal width:がく片の幅(cm)

  • petal length:花弁の長さ(cm)

  • petal width:花弁の幅(cm)

そして、ターゲットのクラスは次の3種類のアヤメの花です。

  • Iris-Setosa:セトサ

  • Iris-Versicolour:バージカラー

  • Iris-Virginica:バージニカ

pandas を使ってデータを少し見てみます。

data.frame.head()

ターゲットの0は何でしょうか。ターゲットの名前を見てみます。

data.target_names

ターゲット値の0は setosa (セトサ)であるのがわかります。

データの分割

まず、訓練用とテスト用にデータを分割します。

from sklearn.model_selection import train_test_split

# データセットを訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=0)

test_size = 0.2 と指定して、80%を訓練用、20%をテスト用にしました。random_state = 0 として再現性の確保のために乱数シードを固定しました。

特徴量の値の範囲などを訓練データから確認します。

X_train.describe()

最小値や最大値を平均や標準偏差と比較しても、極端な外れ値(Outliers)も見当たりません。

なお、決定木ではデータの正規化は必要ありません。決定木モデルにおけるデータの分割は、各ノードにおいてデータの値分割のための条件値比較だけを基に行われるため、特徴量の大小に依存しません。よって、標準化(Standardization )を行う必要がありません。

決定木を訓練データにフィットする

次のようにして、訓練用のデータから決定木の条件を抽出します。

# モデルの訓練
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(X_train, y_train)

そして、テスト用のデータセットで評価します。

from sklearn.metrics import accuracy_score

# テストデータに対する予測
y_pred = model.predict(X_test)

# 予測精度の評価
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy:.2f}')

97%の正解度なので、かなり良い正解率です。また、ナイーブ・ベイズの時と同じ条件でデータ分割し実験した時と同等の結果になりました。

クラスごとの評価も確認してみましょう。

from sklearn.metrics import classification_report

print(classification_report(y_test, y_pred))

これもナイーブ・ベイズの時と同じ結果です。

なお、これらの評価値についての詳細はこちらでも解説しています。

決定木を可視化する

次に決定木の可視化を行います。

from sklearn.tree import plot_tree
import matplotlib.pyplot as plt

# 決定木の可視化
plt.figure(figsize=(12,8))
plot_tree(model, 
          feature_names=data.feature_names, 
          class_names=data.target_names, 
          filled=True)
plt.title(f"Decision Tree for Iris Dataset (Accuracy: {accuracy*100:.2f}%)")
plt.show()

この図については前回の記事で解説しました。scikit-learnを使うと非常に簡単に決定機の可視化が行えます。

このグラフを見ると、花弁の長さ(petal length)と花弁の幅(petal width)のみを条件として使っています。

確認するために、決定木のモデルの属性(model.feature_importances_)を使って、どの特徴が重要なのかを表示しましょう。

import pandas as pd
import matplotlib.pyplot as plt

# モデルの特徴量の重要度と特徴量名を組み合わせてDataFrameを作成
feature_importances_df = pd.DataFrame({
    'Feature': data.feature_names,
    'Importance': model.feature_importances_
})

# DataFrameを表示
print(feature_importances_df)

# 棒グラフで特徴量の重要度を表示
feature_importances_df.plot(kind='bar', x='Feature', y='Importance', color='blue')
plt.title('Feature Importances')
plt.xlabel('Feature')
plt.ylabel('Importance')
plt.show()

こうしてみると、モデルが重要視しているのは、花弁の長さ(petal length)と花弁の幅(petal width)のみであるのが確認できます。

決定木の深さを変えてみる

今度は、決定木をさらに深くしてみましょう。

# より深い決定木を作成
model2 = DecisionTreeClassifier(max_depth=5, random_state=42)
model2.fit(X_train, y_train)

max_depth = 5 として深さは最大5までに設定し、訓練データにフィットします。

次に、テストデータで評価します。

# テストデータに対する予測
y_pred2 = model2.predict(X_test)

# 予測精度の評価
accuracy2 = accuracy_score(y_test, y_pred2)
print(f'Accuracy: {accuracy2:.2f}')

正解率が100%になりました。

クラスごとの評価も確認してみましょう。

print(classification_report(y_test, y_pred2))

全て100%です。

決定木は過学習しやすいので深さを増やしすぎるとテストデータでの評価が悪くなる可能性がありますが、ここではテストデータでの正解率が100%なので過学習とは言えません。

そもそものデータセットが小さいので、決定木が臨機応変にフィットできたのと、訓練データとテストデータでデータの分布に大きな差がないためでしょう。

決定木を可視化します。

# 決定木の可視化
plt.figure(figsize=(12,8))
plot_tree(model2, 
          feature_names=data.feature_names, 
          class_names=data.target_names, 
          filled=True)
plt.title(f"Decision Tree for Iris Dataset (Accuracy: {accuracy2*100:.2f}%)")
plt.show()

小さくてわかりづらいですが、全ての葉ノードで ジニ不純度(gini) が0になっています。つまり、これらの葉ノードにはクラスが混合していません。

また、花弁の長さ(petal length)と花弁の幅(petal width)だけでなく、がく片の長さ(sepal length)も条件として使われているのがわかります(白い箱を見てください)。

重要視されている特徴を確認します。

# モデルの特徴量の重要度と特徴量名を組み合わせてDataFrameを作成
feature_importances_df2 = pd.DataFrame({
    'Feature': data.feature_names,
    'Importance': model2.feature_importances_
})

# DataFrameを表示
print(feature_importances_df2)

# 棒グラフで特徴量の重要度を表示
feature_importances_df2.plot(kind='bar', x='Feature', y='Importance', color='blue')
plt.title('Feature Importances')
plt.xlabel('Feature')
plt.ylabel('Importance')
plt.show()

やはり、がく片の長さ(sepal length)が少しですが重要視されているのがわかります。

次回予告

たくさんの木が集まると森になりますが、決定木が集まったモデルの一つとしてランダム・フォレストがあります。

次回は、ランダム・フォレストを紹介します。

お楽しみに!

いいなと思ったら応援しよう!