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 pandasJupyterノートブックを立ち上げて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)が少しですが重要視されているのがわかります。
次回予告
たくさんの木が集まると森になりますが、決定木が集まったモデルの一つとしてランダム・フォレストがあります。
次回は、ランダム・フォレストを紹介します。
お楽しみに!
