ランダムフォレストの簡単な実装

ランダムフォレストは機械学習の中でも感覚的に理解しやすい手法です。
ここでは備忘録を兼ねて、簡単な実装と解析のコードを紹介します。
# ランダムフォレストとは
決定木を複数作りその出力の平均を最終出力とする手法です。
これは決定木を並列に繋いでいるものと解釈でき、このような方法はバギングと呼ばれます。これと対照的な手法にはブースティングと呼ばれる方法があり、こちらでは一個目の決定木の出力を二個目の決定木の入力として用い、以降は予測値が収束するまでn番目の出力を入力としてn+1番目の出力をし続ける手法です。（以下、自作のイメージ図）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2603842/34c2282e-9aa6-3468-eb75-c6ac5966fa36.png)


# 実装の流れ
1. データ確認
2. 説明変数と目的変数の設定
3. ランダムフォレストのインスタンスの作成
4. 学習
5. 評価
6. 解釈

## データ確認
今回はsklearnのサンプルデータセットである「乳がんの診断データ」([Breast Cancer](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_breast_cancer.html#sklearn.datasets.load_breast_cancer))のデータを用いてランダムフォレストで分類モデルを作っていきます。簡単な説明ですが、このデータでは腫瘍の形状の情報と、その腫瘍が悪性(1)か、良性(0)かデータとなっています。のそのまずはデータ構造を見ていきましょう。対応関係がわかりやすいように必要なモジュールはそれを使用する直前でインポートしています。(環境構築に関しては省略します)
```Python
# 実行環境 Python3.10.10

# がんの診断結果のデータを読み込み
from sklearn.datasets import load_breast_cancer

# データを読み込む
cancer = load_breast_cancer()

# 説明変数をXに、目的変数をyに格納
X = cancer.data
y = cancer.target

# DataFrameを作成
import pandas as pd
df = pd.DataFrame(X, columns=cancer.feature_names)  
df['target'] = y
```
```Python
# target=0（良性）,target=1 (悪性) のデータを抽出して特徴量のバープロットを作成
import matplotlib.pyplot as plt
df_0 = df[df['target']==0]
df_1 = df[df['target']==1]

# 陽性サンプルと陰性サンプルの数を表示
print("陰性サンプル：%d"%len(df_0), ",","陽性サンプル：%d"%len(df_1))
print("特徴量数：%d"%(len(df_0.columns)-1))

for i in range(30):
    # 30個の図を10行3列にプロット
    plt.subplots_adjust(wspace=0.5, hspace=0.5)
    plt.title(df_0.columns[i])
    df_0.iloc[:,i].plot(kind='hist', bins=30, color='blue', alpha=0.5)
    df_1.iloc[:,i].plot(kind='hist', bins=30, color='red', alpha=0.5) 
    plt.show()
```
実行結果（一部抜粋）
```
陽性サンプル：212,陰性サンプル：357,
特徴量数：30
```
サンプル数に偏りがあると学習に影響が出ることがあります。
また、特徴量数が多すぎると解釈が難しくなることも覚えておきましょう。
![15380196-1c60-4d1d-b179-fda53b33b0c0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2603842/14da833d-0f9e-45d6-ec87-b36ce1a0cdb2.png)
赤色が陽性サンプルを、青色が陰性サンプルを表します。
この特徴量「mean radius」は2つのクラスで大きく分布が異なり、予測に大きく寄与することが予想されます。
![725e8ca2-0847-4519-99dd-82c08957c2cf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2603842/5e479995-ef7c-d9b6-c343-7029d2a5ba7f.png)

一方、別の特徴量である「symmetry error」では陰性、陽性サンプルで似たような分布であるため、この特徴量は学習に寄与しないと予想されます。

# 2. 説明変数と目的変数の設定
今回は1で色分けに使用した、「陽性」「陰性」のラベルを目的変数とし、他の30個のデータを説明変数（特徴量）に設定した。
```Python
# dfは上で定義したものと同じ

# df_xは説明変数(全体からtaget列を削除する)
df_x = df.drop("target",axis=1)

# df_yは目的変数(target列を指定する)
df_y = df["target"]
```
# 3. ランダムフォレストのインスタンスの作成
ハイパーパラメータについての説明は省略しますが、気になる方は[こちら](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html)を参照してください。今回は結果を固定するために乱数シードのみ指定します。
```Python
# sklearnからRandomForestClassiferモデルをインポート
from sklearn.ensemble import RandomForestClassifier as RFC

# モデルのインスタンスを作る
model = RFC(random_state=0)
```
# 4. 学習
先程定義した説明変数と目的変数を使いモデルに学習させます。
一般に機械学習では「学習のためのデータ」、「検証用データ」、「テストデータ」を用いて以下の手順で学習を進めます。
1. 学習用データでモデルにデータを学習させる
2. 評価用データを使い、モデルに目的変数を予測させる
3. 2.で予測した値が実際の値とどれくらい一致するか検証する
4. テストデータを使い、その目的変数を予測する

最後に示したテストデータでの予測が最終目的となります。
今回は練習なのでテストデータはないものとして考えています。
```Python
# データを指定の割合に分けるモジュール
from sklearn.model_selection import train_test_split

# データを学習用、検証用に分割する(乱数シードを0、テストデータの割合を0.3に指定)
x_train,x_val,y_train,y_val = train_test_split(df_x, df_y, test_size=0.3, random_state=0)

# fitメソッドで学習((説明変数,目的変数)を指定する)
model.fit(x_train,y_train)
```
# 5. 評価
評価として、適合率(precision),再現率(recall),F値を各クラスごとに求めたclassfication_reportと、予測結果の混同行列を出力します。これらの用語についてまとめた[記事](https://qiita.com/ameshikou/items/895cf8c1ad09c61ca172)があるのでよろしければ参照ください！
```Python
# 検証用データで予測値を生成する
y_pred = model.predict(x_val)

# 検証用データで予測した結果のclassfication_reportを表示する
# 必要なモジュールをインポート
from sklearn.metrics import classification_report
# classfication_reportを表示
print(classification_report(y_val,y_pred))

# 混同行列を作成してseabornで表示する
# 必要なモジュールをインポート
from sklearn.metrics import confusion_matrix
import seaborn as sns
# 混同行列を表示
cm = confusion_matrix(y_val,y_pred)
sns.heatmap(cm, annot=True, cmap='Blues')
```
以下の上側の図がclassification_report、下側が混同行列です。どちらを見ても高精度な予測ができていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2603842/a3f31608-1f29-28b7-1e74-44157f9c7e0e.png)

# 6. 解釈
最後にモデルの特徴量重要度というものを算出し図示します。これはジニ係数という
値に基づき算出される値で（公式ドキュメントを参照してください）、訓練中にどの特徴量によってどれだけサンプルがうまく分けられているのかを相対的な大小で判断できます。
```Python
# モデルの特徴量の重要度を図示する
importances = model.feature_importances_
plt.figure(figsize=(10,10))
plt.barh(df_x.columns, importances)
plt.show()
```
結果は以下の図のようになります。
先に上げたmean_radiusが意外と分類に寄与していないことがわかり、またsymmetry errorについては予想通り予測にあまり寄与していないということがわかります。他の特徴量についても検討してみると面白いです！
今回は特徴量の意味がわからない状態で学習を行いましたが、あらかじめ予測に関わらなそうな特徴量を削除しておくことも精度の向上に有用な場合があります。（特徴量選択のモジュールにBorutaやRFEなどがあります）
![115de5ba-cfb0-4c7e-af5d-d40d4a767937.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2603842/43ad1469-76ad-8186-3854-38dbfb3e3093.png)

# まとめ
今回はランダムフォレストの簡単な実装について解説しました！
わかりにくいところも多々あったと思いますが、ご容赦ください。

この実装ができれば、他の学習モデルの適用や評価指標のカスタマイズなど様々な拡張を行えるかと思います。

ここまでお付き合いいただきありがとうございました！！