Pythonの機械学習ライブラリであるscikit-learnを使って決定木(DecisionTree)とランダムフォレスト(RandomForest)を使用する方法について解説します。
Contents
決定木(Decision Tree)
決定木(Decision Tree)とは
決定木(DecisionTree)は、分析対象データの要素の一つ一つに着目して、要素をある値に基づいて分割していくことでデータが属するクラスを決めていく手法です。
木と言っているのは、以下のようにある値によって対象のデータを分割していくのが木構造になっているためです。以下図のx1は、例えば「ある日の気温」等のようなデータを説明する要素で対象データにより様々考えられます。
決定木のモデルで重要なハイパーパラメーターは深さ(depth)です。深さをいくつにするかで結果が変わり、必ずしも深くすればよいというものでもありません。
本記事では、Pythonのscikit-learnを用いて決定木を実装する方法例について紹介していきます。
ハイパーパラメーターとは、機械学習のモデルが持つパラメーターで使用者が調整しなければならないパラメータのことです。ハイパーパラメーターを調整することをチューニングと言います。
scikit-learnの決定木(DecisionTreeClassifier)の使用方法
scikit-learnの決定木(DecisionTreeClassifier)の使用方法を簡単な2次元データを使った例で紹介していきます。
import matplotlib.pyplot as plt import numpy as np from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier def main(): """メイン関数""" np.random.seed(0) # 決定木の深さ設定 max_depth = 2 # テスト用の分類用データセットの作成 data, target = make_blobs(n_samples=100, centers=3, cluster_std=1.0) plt.scatter(data[:, 0], data[:, 1], c=target, cmap="cividis") # 訓練データとテストデータの分離 train_data, test_data, train_target, test_target = train_test_split(data, target) # 決定木の作成 model = DecisionTreeClassifier(max_depth=max_depth) # モデルの学習 model.fit(train_data, train_target) # 正解率の計算 print(model.score(test_data, test_target)) # モデルの予測結果を表示 # 描画領域を計算(x1:横軸、x2:縦軸) min_x1, max_x1 = data[:, 0].min() - 1, data[:, 0].max() + 1 min_x2, max_x2 = data[:, 1].min() - 1, data[:, 1].max() + 1 xx1, xx2 = np.meshgrid( np.arange(min_x1, max_x1, 0.01), np.arange(min_x2, max_x2, 0.01) ) input_data = np.array([xx1.ravel(), xx2.ravel()]).T # 学習したモデルで予測 pred = model.predict(input_data) plt.contourf(xx1, xx2, pred.reshape(xx1.shape), alpha=0.3) plt.show() if __name__ == "__main__": main()
【実行結果】 0.92
【表示結果(max_depth=2)】
以降でプログラムの細部について部分ごとに説明をしていきます。
決定木を使用するには、sklearn.treeからDecisionTreeClassifierをimportします。他にも分析データ操作や可視化のためにnumpyやmatplotlib、サンプルデータ生成用にmake_blobs、学習用データと検証データの分割のためにtrain_test_splitといったものもimportしています。
import matplotlib.pyplot as plt import numpy as np from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier
以下の部分では、ハイパーパラメータとなる情報を定義しています。乱数は実行毎に結果が変わらないように状態を固定しています。また、max_depthは決定木の重要な深さパラメータで、この値を変えると結果は変わります。
np.random.seed(0) # 決定木の深さ設定 max_depth = 2
以下の部分は分類用のデータセットを作成している部分です。今回は、サンプル数(n_samples) が100で、データ集合の中心(centers)が3つ、各データ集合の標準偏差(cluster_std)が1.0のデータを生成しています。
また、訓練用のデータとテスト用のデータとしてtrain_test_splitでデータを分離しています。
# テスト用の分類用データセットの作成 data, target = make_blobs(n_samples=100, centers=3, cluster_std=1.0) plt.scatter(data[:, 0], data[:, 1], c=target, cmap="cividis") # 訓練データとテストデータの分離 train_data, test_data, train_target, test_target = train_test_split(data, target)
さて、本題の決定木の作成や学習をしている部分は以下の部分になります。
# 決定木の作成 model = DecisionTreeClassifier(max_depth=max_depth) # モデルの学習 model.fit(train_data, train_target) # 正解率の計算 print(model.score(test_data, test_target))
決定木の作成は簡単でDecisionTreeClassifierにハイパーパラメータとして用意した引数(max_depth)を渡すだけです。
モデルの学習ではfitメソッドを使用します。また、正解率の計算はscoreメソッドにテスト用データを渡すことで計算することができます。今回の例では0.92という正解率でした。
以下の部分は決定木の予測結果を可視化するための部分です。
# モデルの予測結果を表示 # 描画領域を計算(x1:横軸、x2:縦軸) min_x1, max_x1 = data[:, 0].min() - 1, data[:, 0].max() + 1 min_x2, max_x2 = data[:, 1].min() - 1, data[:, 1].max() + 1 xx1, xx2 = np.meshgrid( np.arange(min_x1, max_x1, 0.01), np.arange(min_x2, max_x2, 0.01) ) input_data = np.array([xx1.ravel(), xx2.ravel()]).T # 学習したモデルで予測 pred = model.predict(input_data) plt.contourf(xx1, xx2, pred.reshape(xx1.shape), alpha=0.3) plt.show()
meshgridでデータ点を作成し各点の分類結果をpredictメソッド予測して表示しています。このように決定木でモデルを学習しておけば新しいデータに対してどういった結果となるかを予測することができます。
【表示結果(再掲)】
実行結果を見てみると、それぞれのデータ点のエリアに分類できる決定木モデルが作成できています。上記のようにデータを分割していくため境界線はまっすぐに分けられることも決定木の特徴です。
ハイパーパラメータ max_depthの変更
決定木のハイパーパラメータとしてmax_depthがありますが、この値を変えたときにどのようになるか少し見てみましょう。
【max_depth=1の場合】
【max_depth=4の場合】
max_depth=1にするとただの2分割であるため、データ点をうまく分割することができません。一方で、max_depth=4まであげると黄色の細い領域ができてしまっています。この部分は本来濃い青のデータ領域と考えられるため、この領域のデータを黄色と予測するのには違和感があり、モデルがデータに過剰に適合してしまっている状況と見ることができます。
こういったハイパーパラメータのチューニングは手作業で実施するのは大変です。パラメータを実行しながら精度の高いハイパーパラメータセットを見つける方法としてグリッドサーチやランダムサーチといった方法があります。
グリッドサーチとランダムサーチについては、「グリッドサーチとランダムサーチでハイパーパラメータをチューニングする方法(GridSearchCV, RandomizedSearchCV)」でまとめていますので興味があれば参考にしてください。
sklearn.tree.DecisionTreeClassifierの公式ドキュメントの説明はこちらを参考にしてください。
ランダムフォレスト
ランダムフォレストとは
ランダムフォレスト(RandomForest)とは、上記で説明してきた決定木を複数作り、分類結果をモデルの多数決で決めていく手法です。学習したモデルを組み合わせて機械学習の汎化性能を向上させる方法のことをアンサンブル学習と言います。
決定木では、すべての説明変数を使用するのに対して、ランダムフォレストでは、複数作成する決定木の一つ一つはランダムに決められる少ない説明変数だけを使用してデータの属するクラスを決めます。このようにして作成した複数の決定木モデルを使って予測されるクラスのうち多数決で最も多いクラスを結果として出力することになります。
ランダムフォレストは、線形分離可能ではないようなデータ集合にも使用することができるのが特徴です。
アンサンブル学習としては「バギング」と「ブースティング」という方法があります。ランダムフォレストはバギングの手法の一種です。
バギングは複数のモデルを並列的に学習し、結果をまとめることで最終的な予測としますが、ブースティングは1つ前のモデルが予測を誤ったデータを優先的に改善するように直列的に学習します。
scikit-learnのランダムフォレスト(RandomForestClassifier)の使用方法
scikit-learnのランダムフォレスト(RandomForestClassifier)の使用方法を、決定木で見てきた例と同じ例を用いて紹介していきます。
import matplotlib.pyplot as plt import numpy as np from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier def main(): """メイン関数""" np.random.seed(0) # 決定木の深さ設定 max_depth = 2 # 決定木の数 n_estimators = 10 # テスト用の分類用データセットの作成 data, target = make_blobs(n_samples=100, centers=3, cluster_std=1.0) plt.scatter(data[:, 0], data[:, 1], c=target, cmap="cividis") # 訓練データとテストデータの分離 train_data, test_data, train_target, test_target = train_test_split(data, target) # ランダムフォレストの構築 model = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth) # モデルの学習 model.fit(train_data, train_target) # 正解率の検出 print(model.score(test_data, test_target)) # モデルの予測結果を表示 # 描画領域を計算(x1:横軸、x2:縦軸) min_x1, max_x1 = data[:, 0].min() - 1, data[:, 0].max() + 1 min_x2, max_x2 = data[:, 1].min() - 1, data[:, 1].max() + 1 xx1, xx2 = np.meshgrid( np.arange(min_x1, max_x1, 0.01), np.arange(min_x2, max_x2, 0.01) ) input_data = np.array([xx1.ravel(), xx2.ravel()]).T # 学習したモデルで予測 pred = model.predict(input_data) plt.contourf(xx1, xx2, pred.reshape(xx1.shape), alpha=0.3) plt.show() if __name__ == "__main__": main()
【実行結果】 0.96
【表示結果(max_depth=2, n_estimators=10)】
以降でプログラムの細部について部分ごとに説明をしていきますが、決定木の説明と重複する部分は割愛しますので、決定木の説明をご確認ください。
ランダムフォレストを使用するには、sklearn.ensembleからRandomForestClassifierをimportします。
from sklearn.ensemble import RandomForestClassifier
まず、ハイパーパラメータとして決定木より増えている項目があります。ランダムフォレストは複数の決定木の多数決で結果が決定されます。この決定木の数を決めるのが、n_estimatorsです。今回の例では10としてみています。
np.random.seed(0) # 決定木の深さ設定 max_depth = 2 # 決定木の数 n_estimators = 10
ランダムフォレストモデルの使い方も決定木とほとんど同じです。
# ランダムフォレストの構築 model = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth) # モデルの学習 model.fit(train_data, train_target) # 正解率の検出 print(model.score(test_data, test_target))
RandomForestClassifierには、ハイパーパラメータとして決定木の時のパラメータ(max_depth)に加えてn_estimatorsを渡します。
モデルの学習では同様にfitメソッドを使用し、また、正解率の計算はscoreメソッドにテスト用データを渡すことで計算することができます。
今回の例では0.96という正解率でした。決定木では同様のmax_depth=2で0.92という正解率でしたが、ランダムフォレストで正解率が向上していることが分かります。
predictを用いた予測の表示結果を見ると境界面についても決定木よりも複雑な境界面を表現できていることが分かります。
sklearn.ensemble.RandomForestClassifierの公式ドキュメントの説明はこちらを参考にしてください。
上記で紹介しているソースコードについてはgithubにて公開しています。参考にしていただければと思います。