【機械学習】決定木の仕組みと実装方法について|python
今回は、決定木(Decision Tree)によるモデル構築方法をご紹介します。
決定木は、ある目的に到達するためにデータの書く属性の条件分岐を繰り返してクラス分けする方法です。
数学的な原理に加え、コードも紹介していきます。
決定木(decision tree)
決定木はその名の通り、木のような形をしています。
ちなみに、目的変数がカテゴリの場合は分類木、数値の場合は回帰木と呼びます。
決定木系のアルゴリズムは、直感的でわかりやすく、データの尺度に左右されにくいことから様々な場面で応用されています。
データの尺度に左右されにくいとは、正規化が不要な場合が多いことを意味しています。
また、決定木を並列に並べたランダムフォレスト(random forest)モデルは、過学習に強いとされます。
ランダムフォレストは、こちらで扱っています。
【ランダムフォレスト】ブートストラップ法を決定木に応用|python
決定木のあるレコードiの特徴量の組をxiとすると、決定木に属する葉のウェイトは以下のように表されます。
$$w(x_{i})$$
つまりこれが予測値です。
2値分類の問題だと、活性化関数としてシグモイド関数を適用したものが最終的な予測確率(0~1)となります。
ちなみにGBDTのように、決定木が複数ある(M本)場合だと、予測値は以下のようになります。
$$\sum_{m=1}^Mw_{m}(x_{i})$$
さて、ゴールがわかったところで次は枝を分割していく基準の設定方法です。
キーワードは、情報利得と不純度です。
情報利得と不純度(information and impurity)
情報利得:一つのノードの中で、異なるサンプルのクラスが含まれる割合を表したもの。
情報利得が大きければ、ノードの中が乱雑なので、枝を伸ばす必要があり、情報利得が小さければ、これ以上枝を伸ばす必要はないと判断されます。
決定木のアルゴリズムでは、過学習を防ぐために情報利得が閾値を下回る場合は剪定しています。
文字通り、決定木の層をこれ以上下に伸ばさないという意味合いです。
$$IG(D_{p})=I(D_{p})-(\sum_{j=1}^c(\frac{N_{j}}{N_{p}}))I(D_{j})$$
さて、求め方ですが、親ノードから子ノードの不純度の差で表すことができます。
IG(Dp):あるノードの情報利得
I(Dp):あるノードの不純度
I(Dj):子ノードの不純度。子ノードは1~c個あるとしています。
N:サンプル数。Npがあるノードのサンプル数。Njは子ノードのサンプル数。
不純度に関しては、エントロピーとジニ不純度のいずれかが扱われます。両者とも似ている結果が出ることが多いです。
例えば、xgboost(勾配ブースティング決定木アルゴリズム)などでは、optimizerとしてentropyかginiをパラメータとして選択ができます。
今回は、ジニ不純度をご紹介します。
決定木のあるノードjに対し、ノード内のサンプルがN個、ノード内のクラスがC個のときを考えます。
このノードj内で、クラスiに属するサンプルの個数をNjとすると、クラスiに属するサンプルの割合p(i|j)は、
$$p(i|j)=(\frac{N_{j}}{N})$$
と表すことができます。
この時、あるノード\(j\)のジニ不純度は以下のように表すことができます。
$$I(D_{t})=1-(\sum_{I=1}^cp(I|t)^2)$$
決定木では各ノードでの分割の決定にこれらの指標を使用します。
特に、情報利得が最大になる属性に基づいてノードを分割することで、データセットを効果的にクラス別に分類し、モデルの予測精度を向上させることができます。
不純度観点で言えば「クラスに占めるサンプルの割合が増えるように」分割します。
同じクラスのサンプルしかない場合、第2項は1となり、不純度は0になりますね。
CODE
今回もscikitlearnの乳がんデータを扱います。
乳房塊の微細針吸引物(FNA)のデジタル化画像から計算されており、画像中に存在する細胞核の特徴を捉えたものです。
データセットの中では悪性(malignant)は0、良性(benign)は1で表されており、targetカラムで表されております。
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
import numpy as np
import pandas as pd
from sklearn import tree
import matplotlib.pyplot as plt
%matplotlib inline
data_breast_cancer = load_breast_cancer()
# Pandasによるデータの表示
df_target = pd.DataFrame(data_breast_cancer["target"], columns=["target"])
df_data = pd.DataFrame(data_breast_cancer["data"], columns=data_breast_cancer["feature_names"])
df = pd.concat([df_target, df_data], axis=1)
df.head()
y = df["target"]
X = df.loc[:, "mean radius":]
# 訓練データとテストデータに分ける
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=0)
必要なライブラリをインポートして、乳がんのデータを取得します。
目的関数は、df_targetとします。
target | mean radius | mean texture | mean perimeter | mean area | mean smoothness | mean compactness | mean concavity | mean concave points | mean symmetry | … | worst radius | worst texture | worst perimeter | worst area | worst smoothness | worst compactness | worst concavity | worst concave points | worst symmetry | worst fractal dimension | |
0 | 0 | 17.99 | 10.38 | 122.80 | 1001.0 | 0.11840 | 0.27760 | 0.3001 | 0.14710 | 0.2419 | … | 25.38 | 17.33 | 184.60 | 2019.0 | 0.1622 | 0.6656 | 0.7119 | 0.2654 | 0.4601 | 0.11890 |
1 | 0 | 20.57 | 17.77 | 132.90 | 1326.0 | 0.08474 | 0.07864 | 0.0869 | 0.07017 | 0.1812 | … | 24.99 | 23.41 | 158.80 | 1956.0 | 0.1238 | 0.1866 | 0.2416 | 0.1860 | 0.2750 | 0.08902 |
2 | 0 | 19.69 | 21.25 | 130.00 | 1203.0 | 0.10960 | 0.15990 | 0.1974 | 0.12790 | 0.2069 | … | 23.57 | 25.53 | 152.50 | 1709.0 | 0.1444 | 0.4245 | 0.4504 | 0.2430 | 0.3613 | 0.08758 |
3 | 0 | 11.42 | 20.38 | 77.58 | 386.1 | 0.14250 | 0.28390 | 0.2414 | 0.10520 | 0.2597 | … | 14.91 | 26.50 | 98.87 | 567.7 | 0.2098 | 0.8663 | 0.6869 | 0.2575 | 0.6638 | 0.17300 |
4 | 0 | 20.29 | 14.34 | 135.10 | 1297.0 | 0.10030 | 0.13280 | 0.1980 | 0.10430 | 0.1809 | … | 22.54 | 16.67 | 152.20 | 1575.0 | 0.1374 | 0.2050 | 0.4000 | 0.1625 | 0.2364 | 0.07678 |
上のようなデータフレームが出力されたと思います。
次に、train_test_splitを使って、データを訓練データとテストデータに分けます。
train_test_splitの引数である、test_sizeは全体のデータに対して、テストデータのサイズが何%に当たるかを示しています。
test_size=0.5なら、教師データとテストデータはともに同じデータ数です。
tree_reg = DecisionTreeRegressor(max_depth=4,min_samples_leaf=5,random_state = 0).fit(X_train,y_train)
plt.figure(figsize=(20,8))
tree.plot_tree(tree_reg,fontsize=10)
では、決定木モデルを作成します。
まとめてfitまで行っていますが、基本的にはインスタンスの作成→データと引数の設定→fit→predictです。
パラメータの種類(hyper parameter tuning)
今回注目すべきDecisionTreeRegressorの引数は、max_depthとmin_samples_leafです。
max_depth:決定木の層の最大の深さを示します。深ければ深いほど、教師データをよく学習するので、増やしすぎると過学習に陥り、テストデータのスコアがガタ落ちします。体感ですが、3,4,5,6,7,8あたりでパラメーターチューニングするのが良さそうです。
min_samples_leaf:リーフノードのサンプル数の最小値を決定するパラメータです。デフォルト値は1なので、max_depthを設定しない限り、リーフノードに落ちるサンプルが1つになるまで木を伸ばし続けることになります。これは過学習まっしぐらですね。
matplotlibを使って決定木を図示した結果が以下になります。
上のようになりました。
max_depth=5としたおかげで5層までで学習は止まっています。
各ノードに色々分岐条件や統計量が書かれております。
X[n] <= m:次のノードへの分岐条件
mse:mean squared error(平均2乗誤差)のことです。
samples:ノードに含まれるサンプル数です。min_samples_leafで最小値を設定できます。
value:ノードに含まれるデータの平均値
#評価作業
#MAE,MSE,RMSE,R2を採用。
def get_eval_score(y_true, y_pred):
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
mae = mean_absolute_error(y_true,y_pred)
mse = mean_squared_error(y_true,y_pred)
rmse = np.sqrt(mse)
r2score = r2_score(y_true, y_pred)
print(f" MAE = {mae}")
print(f" MSE = {mse}")
print(f" RMSE = {rmse}")
print(f" R2 = {r2score}")
print("教師データスコア")
get_eval_score(y_train,y_train_pred)
print("テストデータスコア")
get_eval_score(y_test,y_test_pred)
#0と1にラベリング
y_test_pred = np.where(y_test_pred > 0.5, 1, 0)
y_test_pred[:5]
では、予測値の評価作業をしましょう。
使いまわせるように、上のように関数を作っておきます。r2_scoreは決定係数のことです。
忘れた方は、決定係数とは?説明変数の確らしさを図る指標の一つ。をご覧ください。
結局予測した値とは、どのような形になっているのでしょうか?
当然乳がんデータのtargetカラムでした。2値分類問題でしたので、活性化関数としてシグモイド関数が適用されているので、1になる確率が格納されています。
シグモイド関数については、【分類タスク】ロジスティック回帰の使い方|pythonで少し触れています。
ここで、下のような処理を行い、0.5を閾値として0と1にラベリングを行います。
→array([[0],
[1],
[1],
[1],
[1]])
このような予測ができました。決定木は過学習に陥りやすいモデルですが、max_depthやmin_samples_leafなどのパラメータを丁寧にチューニングすることで、精度の良い予測値を出力してくれます。
max_depthやmin_samples_leafなどのパラメータを最適にすると、これよりも良い精度の予測値が出るかもしれません。
そのほか機械学習に興味がある方は、以下のコンテンツをご覧ください。
【周期性を掴もう】pythonでコレログラムを書いてみましょう
【共線性解決】pythonで主成分分析をやってみた【XGB】交差検証法を使った勾配ブースティング決定木の実装|python