🐈

Act 22. Pythonでランダムフォレストを試す

2024/11/23に公開

はじめに

Act 01. AIで外国為替を自動売買するまでの道のりをベースに学習を進めて行く。

前回はランダムフォレストの概要について学習した。
なので今回は実際にpythonでランダムフォレストを使った分析を行う。

データセットが必要になるため、お馴染みのpythonで提供されているIrisデータセットを使用する。
ほとんど決定木と同じような内容だが、補足の内容は少し面白いので是非読んでほしい。

ランダムフォレスト

結論

とりあえず実行させてくれや!って人のためにコードを載せておく。

Act22.py
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt


# Irisデータセットをロード
iris = load_iris()
X = iris.data
y = iris.target

# 訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# モデルの構築
model = RandomForestClassifier()
model.fit(X_train, y_train)

# 予測
y_pred = model.predict(X_test)

# 結果
accuracy = accuracy_score(y_test, y_pred)
print(f"accuracy: {accuracy:.2f}")

print(classification_report(y_test, y_pred))

# 決定木の個数
print(f"estimator: {len(model.estimators_)}")

# プロット
tree_1 = model.estimators_[0]
tree_2 = model.estimators_[1]

fig, axes = plt.subplots(1, 2, figsize=(32, 16))
plot_tree(tree_1, feature_names=iris.feature_names, class_names=iris.target_names, filled=True, ax=axes[0])
plot_tree(tree_2, feature_names=iris.feature_names, class_names=iris.target_names, filled=True, ax=axes[1])

plt.show()

出力は以下の通り。
まあ今回も安定の100%ですよと。

accuracy: 1.00
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        10
           1       1.00      1.00      1.00         9
           2       1.00      1.00      1.00        11

    accuracy                           1.00        30
   macro avg       1.00      1.00      1.00        30
weighted avg       1.00      1.00      1.00        30

estimator: 100

プロットの結果は以下の通り。
以下に関しては違う結果になるため参考程度に。

コードの説明

まあ、やっていることは決定木とほぼ同じだから、被っている個所は割愛ということで。
気になる人はAct 20. Pythonで決定木を試すを見てほしい。

モデル

今回使用するモデルはRandomForestClassifierとなる。
もう一つ、RandomForestRegressorというクラスがあるのだが、こちらは回帰分析用のクラス。

プロット

前回とはプロットの内容が異なる。

# プロット
tree_1 = model.estimators_[0]
tree_2 = model.estimators_[1]

fig, axes = plt.subplots(1, 2, figsize=(32, 16))
plot_tree(tree_1, feature_names=iris.feature_names, class_names=iris.target_names, filled=True, ax=axes[0])
plot_tree(tree_2, feature_names=iris.feature_names, class_names=iris.target_names, filled=True, ax=axes[1])

plt.show()

ランダムフォレストでは、サンプリングを行い複数の決定木を構築することで、データ予測の精度を向上している。

決定木はmodel.estimators_[0]の様に指定することで、0番目の決定木を取得することが可能。
ちなみにIrisのデータセットの場合は100個の決定木が作られていた。

後はfig, axes = plt.subplots(1, 2, figsize=(32, 16))の様に、以前も登場したsubplotsメソッドを使用して複数のデータを1つの表にプロットしている。

そしてそれを出力することで以下の図が表示されると。

補足

特徴量の偏り

前回の記事で、決定木はランダムフォレストに比べて特徴量の偏りが強いと記載した。
それについて解説してみようと思う。

まず、全体的なコードは以下の通り。
同じデータセットで決定木とランダムフォレストの分析を行っている。
※画面が小さい人は全体が表示されないごめんなさい。

Act22-2.py
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt
import numpy as np


# Irisデータセットをロード
iris = load_iris()
X = iris.data
y = iris.target

# 訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ランダムフォレストモデルの構築
rfc = RandomForestClassifier(random_state=42)
rfc.fit(X_train, y_train)

# 決定木モデルの構築
dtc = DecisionTreeClassifier(random_state=42)
dtc.fit(X_train, y_train)

# 予測
rfc_y_pred = rfc.predict(X_test)
dtc_y_pred = dtc.predict(X_test)

# 特徴量の重要度を取得
rfc_feature_importances = rfc.feature_importances_
dtc_feature_importances = dtc.feature_importances_

# プロット
fig, axes = plt.subplots(1, 2, figsize=(30, 16))
rfc_indices = np.argsort(rfc_feature_importances)[::-1]  # 重要度を降順でソート
dtc_indices = np.argsort(dtc_feature_importances)[::-1]  # 重要度を降順でソート
# ランダムフォレスト
axes[0].bar(range(X.shape[1]), rfc_feature_importances[rfc_indices], align="center")
axes[0].set_xlabel("Features", fontsize=24)
axes[0].set_ylabel("Importance", fontsize=24)
axes[0].set_title("RandomForestClassifier", fontsize=24)
axes[0].set_xticks(range(X.shape[1]), np.array(iris.feature_names)[rfc_indices], fontsize=16, rotation=30)
# 決定木
axes[1].bar(range(X.shape[1]), dtc_feature_importances[dtc_indices], align="center")
axes[1].set_xlabel("Features", fontsize=24)
axes[1].set_ylabel("Importance", fontsize=24)
axes[1].set_title("DecisionTreeClassifier", fontsize=24)
axes[1].set_xticks(range(X.shape[1]), np.array(iris.feature_names)[dtc_indices], fontsize=16, rotation=30)
plt.show()

出力は以下の通り。
左はランダムフォレストの特徴量の偏りで、右が決定木の特徴量の偏り。

こんなに違うんかーい!っていうのが率直な感想。
確かにこれではまともな分析ができないかもしれないね…。

コードについてもめちゃくちゃ適当だけど解説しておく。

# axes[0]の図に棒グラフ(バーチャート)を設定している
axes[0].bar(range(X.shape[1]), rfc_feature_importances[rfc_indices], align="center")

# 上の内容を "出力 : 説明" の形式で記述しているよ
X.shape  # (150, 4) : Xの行列を取得。今回の場合は150行4列
X.shape[1]  # 4 : (150, 4)の1番目の要素を取得している
range(X.shape[1])  # range(0, 4) : 0, 1, 2, 3のレンジ
rfc_feature_importances[rfc_indices]  # [0.43999397 0.42152159 0.10809762 0.03038681] : 降順にソートした順に内容を取得している

# axes[0]の図に各棒グラフの説明を設定している
axes[0].set_xticks(range(X.shape[1]), np.array(iris.feature_names)[rfc_indices], fontsize=16, rotation=30)

# 上の内容を "出力 : 説明" の形式で記述しているよ
np.array(iris.feature_names)  # ['sepal length (cm)' 'sepal width (cm)' 'petal length (cm)' 'petal width (cm)'] : Irisデータセットの説明変数の名前をnumpyの配列にしている
np.array(iris.feature_names)[rfc_indices]  # ['petal length (cm)' 'petal width (cm)' 'sepal length (cm)' 'sepal width (cm)'] : 1行上の配列をソートした順に切り替えている

正解率

どちらの精度が高いのかも比べてみたい。
例えば以下のようなコードを実行してみる。

Act22-3.py
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# Irisデータセットをロード
iris = load_iris()
X = iris.data
y = iris.target

# 訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ランダムフォレストモデルの構築
rfc = RandomForestClassifier(random_state=42)
rfc.fit(X_train, y_train)

# 決定木モデルの構築
dtc = DecisionTreeClassifier(random_state=42)
dtc.fit(X_train, y_train)

# 予測
rfc_y_pred = rfc.predict(X_test)
dtc_y_pred = dtc.predict(X_test)

# 評価
rfc_accuracy = accuracy_score(y_test, rfc_y_pred)
dtc_accuracy = accuracy_score(y_test, dtc_y_pred)

print(f"ランダムフォレストの正解率: {rfc_accuracy: .2f}")
print(f"決定木の正解率: {dtc_accuracy: .2f}")

出力は以下の通り。
ここまではいつもの通りだから特に問題ない。

ランダムフォレストの正解率:  1.00
決定木の正解率:  1.00

どちらも精度が高いってことだね。めでたしめでたし。
とは行かない。

実はこれ、random_state=42でデータの分割方法を固定しているため、誰が何回実行しても同じ結果になるようになっている。
つまり、正解率100%になるようなrandom_stateの値を指定しているだけ。

試しにrandom_stateを消して実行してみる。
ついでにtrain_test_splittest_size0.1にしてみた。

出力は以下の通り。
これは人によって結果が異なるので参考程度に。

ランダムフォレストの正解率:  1.00
決定木の正解率:  0.93

1回では精度が分からないため、複数回実施してみようと思う。

Act22-3.py
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
import numpy as np

# Irisデータセットをロード
iris = load_iris()
X = iris.data
y = iris.target

# 50回分の正解率を格納するリスト
rfc_accuracy_list = []
dtc_accuracy_list = []

# 50回ループ
for i in range(50):
    # 訓練データとテストデータに分割
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)

    # ランダムフォレストモデルの構築
    rfc = RandomForestClassifier()
    rfc.fit(X_train, y_train)

    # 決定木モデルの構築
    dtc = DecisionTreeClassifier()
    dtc.fit(X_train, y_train)

    # 予測
    rfc_y_pred = rfc.predict(X_test)
    dtc_y_pred = dtc.predict(X_test)

    # 評価
    rfc_accuracy = accuracy_score(y_test, rfc_y_pred)
    dtc_accuracy = accuracy_score(y_test, dtc_y_pred)

    # リストに追加
    rfc_accuracy_list.append(rfc_accuracy)
    dtc_accuracy_list.append(dtc_accuracy)

# リストをNumPy配列に変換
rfc_accuracy_list = np.array(rfc_accuracy_list)
dtc_accuracy_list = np.array(dtc_accuracy_list)

# 結果を表示
print(f"ランダムフォレスト: {rfc_accuracy_list.mean() * 100: .2f}%")
print(f"決定木: {dtc_accuracy_list.mean() * 100: .2f}%")

出力は以下の通り。
ランダムな分割方法で50回の分析を行い、平均値を算出している。
わずかだがランダムフォレストの方が正解率が高かった。

ランダムフォレスト:  95.07%
決定木:  94.00%

さいごに

決定木よりランダムフォレストの方が優れてるね!
とても良い勉強になった。

これで教師あり学習についてはいったん終了となる。
2024/10/19からこのシリーズの学習を始めて、今が2024/11/23なので約1か月。
時間が経つのは一瞬だね。

最近は寒くなってきたから体調に気を付けながら引き続き勉強を頑張ろう。
次回からは教師なし学習について。

ではまた

Discussion