hiro_5656's blog

機械学習やクラウド技術について勉強したことを発信していきます!

【Python】決定木・ランダムフォレストの実装

今回はPythonを使って、決定木・ランダムフォレストを実装するやり方を紹介します!

まえがき

決定木・ランダムフォレストともにPythonの scikit-learn というライブラリで簡単に実装することができます!
今回は簡単のために、scikit-learn に内包されている iris というデータ(アヤメという花の花弁の長さや広さとそのアヤメの種類のデータ)を用いて実装します。

データ準備

irisのデータを読み込みます。

from sklearn.datasets import load_iris

iris = load_iris()

簡単ですね。これでアヤメのデータが読み込まれました。

これを訓練データとテストデータに分割しましょう。

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(iris['data'], iris['target'])

決定木

決定木を実装します。まずは深さに制限を設けずに学習をさせてみましょう。
criterion は枝分割時のデータのばらつき具合を測る指標を指定しています。
ここでは「ジニ不純度」にしています。

from sklearn.tree import DecisionTreeClassifier

tree = DecisionTreeClassifier(max_depth=None, criterion='gini')
tree.fit(X_train, y_train)

精度を確認します。

print(f'Train Accuracy: {tree.score(X_train, y_train)*100:.1f} %')
print(f'Test Accuracy: {tree.score(X_test, y_test)*100:.1f} %')
Train Accuracy: 100.0 %
Test Accuracy: 97.4 %

決定木の場合、深さに制限を設けないと訓練データを完全に分類しきるまで枝分かれしてしまうので訓練データでの精度は100%です。
実際に学習された木を可視化してみましょう。

import graphviz
from sklearn.tree import export_graphviz

dot_data = export_graphviz(tree, out_file=None, impurity=False, filled=True, 
                          feature_names=iris.feature_names, 
                          class_names=iris.target_names)

graph = graphviz.Source(dot_data)
graph

f:id:hiro_5656:20211229165526p:plain

ただしこれだと過学習になっている可能性が高いのである程度の深さのところで枝分かれを打ち切るよう設定する必要があります。
木の深さを指定するには、max_depth という引数を使います。

from sklearn.tree import DecisionTreeClassifier

tree = DecisionTreeClassifier(max_depth=5, criterion='gini')
tree.fit(X_train, y_train)

print(f'Train Accuracy: {tree.score(X_train, y_train)*100:.1f} %')
print(f'Test Accuracy: {tree.score(X_test, y_test)*100:.1f} %')
Train Accuracy: 99.1 %
Test Accuracy: 97.4 %

ちなみに木系の機械学習のメリットは特徴量の寄与率(重要度)を可視化できるところでした。
こちらも scikit-learn のモジュールで簡単に実装できます。

import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-darkgrid')

n_features = iris['data'].shape[1]
plt.title('Feature Importance')
plt.bar(range(n_features), tree.feature_importances_, align='center')
plt.xticks(range(n_features), iris.feature_names, rotation=90)
plt.xlim([-1, X_train.shape[1]])
plt.show()

f:id:hiro_5656:20211229170231p:plain

この場合だと、patal length がほぼほぼアヤメの種類を判断する要因になっていることがわかります。

ランダムフォレスト

ランダムフォレストを実装してみましょう。
ランダムフォレストも決定木同様 scikit-learn で簡単に実装できます。 各引数の意味はこちらです。

  • n_estimator: 木の数
  • max_features: 1つの木で用いることのできる特徴量の最大数
  • max_depth: 各木の最大深さ
  • criterion: 枝分割時のデータのばらつき具合を測る指標(ここではジニ不純度を使用)
from sklearn.ensemble import RandomForestClassifier

forest = RandomForestClassifier(n_estimators=3, max_features=3, max_depth=4, criterion='gini')
forest.fit(X_train, y_train)

print(f'Train Accuracy: {forest.score(X_train, y_train)*100:.1f} %')
print(f'Test Accuracy: {forest.score(X_test, y_test)*100:.1f} %')
Train Accuracy: 99.1 %
Test Accuracy: 100.0 %

決定木の際よりも学習精度が上がっていますね😁

ランダムフォレストでも特徴量の寄与度(重要度)を可視化できます。

import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-darkgrid')

n_features = iris['data'].shape[1]
plt.title('Feature Importance')
plt.bar(range(n_features), forest.feature_importances_, align='center')
plt.xticks(range(n_features), iris.feature_names, rotation=90)
plt.xlim([-1, X_train.shape[1]])
plt.show()

f:id:hiro_5656:20211229171642p:plain

決定木の際よりも重要となる特徴量が増えていますね。
これはランダムフォレストの特徴である「特徴量のランダム性」が効いているということです!

あとがき

いかがだったでしょうか?
決定木もランダムフォレストも scikit-learn を用いることで簡単に実装することができました。
これらは機械学習の手法の中でも、ユーザー側にとって中身が理解しやすい手法の1つなので、いろんなところで活用できそうです😁