【Python】ランダムフォレストを実装してみる

# はじめに
本記事ではPythonでランダムフォレストの実装を行います。データはscikit-learnのサンプルで登録されているものを使用しています。

# ランダムフォレストとは
ランダムフォレストは教師あり学習の1種です。複数の決定木を学習させて多数決をとる手法となっており、アンサンブル学習に含まれます。なお、分類・回帰の両方で使用することができます。

- `アンサンブル学習`
複数のアルゴリズムを用いた学習
- `分類`
データがどのグループに属するかを分類する手法
- `回帰`
連続した数値を予測する手法

## 決定木とは
決定木とはデータを基に樹形図を作成し、予測を行うアルゴリズムです。

- 分類で使用する場合のイメージ

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/c562ddfd-d2ae-3d94-805b-7972489d60e2.png)
　
- 回帰で使用する場合のイメージ

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/06e3f43a-cdff-10dd-0259-7388360ea7e2.png)

## ランダムフォレストの流れ
ランダムフォレストは以下の流れで行います。

1. 学習用データからn個のブートストラップデータを作成する（学習用データから重複を許してランダム抽出したデータをn個作成する）
2. n個のブートストラップデータからn個の決定木を作成する
3. n個の決定木からn個のモデルを作成する
4. テストデータをn個のモデルに学習させて予測を行う
5. n個の予測結果から、分類の場合は多数決、回帰の場合は平均値をとったものを最終結果とする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/8ebe8d5a-43ab-64e1-de44-f83fe5c7e4d5.png)

# Python実行環境
python 3.10.4
Windows 11 Home

# ランダムフォレスト（分類）の実装
乳がんのデータセット「load_breast_cancer」を使用してランダムフォレストの分類を行います。

## 使用するデータの構造
使用するデータの説明変数と目的変数は以下の通りとなっています。

- 説明変数
`load_breast_cancer`の`dataset.data`を使用します。
データの中身は以下で確認することができます。（乳房塊の微細針吸引物のデジタル画像に含まれる細胞核の特徴が入っている模様）

    ```python:説明変数に使うデータの確認
    import pandas as pd
    from sklearn.datasets import load_breast_cancer
    
    dataset = load_breast_cancer()
    
    # 1.データの行数、列数を確認する
    print(dataset.data.shape)
    
    # 2.データのカラムを確認する
    print(dataset.feature_names)
    
    # 3.データのサンプルを表示する
    sanple_df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
    print(sanple_df.head(10))
    ```
    
    ```:実行結果
    「1.データの行数、列数を確認する」の実行結果（569行30列）
    (569, 30)
    
    「2.データのカラムを確認する」の実行結果
    ['mean radius' 'mean texture' 'mean perimeter' 'mean area'
     'mean smoothness' 'mean compactness' 'mean concavity'
     'mean concave points' 'mean symmetry' 'mean fractal dimension'
     'radius error' 'texture error' 'perimeter error' 'area error'
     'smoothness error' 'compactness error' 'concavity error'
     'concave points error' 'symmetry error' 'fractal dimension error'
     'worst radius' 'worst texture' 'worst perimeter' 'worst area'
     'worst smoothness' 'worst compactness' 'worst concavity'
     'worst concave points' 'worst symmetry' 'worst fractal dimension']
    
    「3.データのサンプルを表示する」の実行結果
    mean radius  mean texture  ...  worst symmetry  worst fractal dimension
    0        17.99         10.38  ...          0.4601                  0.11890
    1        20.57         17.77  ...          0.2750                  0.08902
    2        19.69         21.25  ...          0.3613                  0.08758
    3        11.42         20.38  ...          0.6638                  0.17300
    4        20.29         14.34  ...          0.2364                  0.07678
    5        12.45         15.70  ...          0.3985                  0.12440
    6        18.25         19.98  ...          0.3063                  0.08368
    7        13.71         20.83  ...          0.3196                  0.11510
    8        13.00         21.82  ...          0.4378                  0.10720
    9        12.46         24.04  ...          0.4366                  0.20750
    
    ```
　
- 目的変数
    `load_breast_cancer`の`dataset.target`を使用します。
    データの中身は以下で確認することができます。（悪性か良性かの情報が入っている）
    
    ```python:目的変数に使うデータの確認
    import pandas as pd
    from sklearn.datasets import load_breast_cancer
    
    dataset = load_breast_cancer()
    
    # 1.データの行数、列数を確認する
    print(dataset.target.shape)
    
    # 2.データの対応を確認する
    print(dataset.target_names)
    
    # 3.データのサンプルを表示する(先頭10件は全て0だったため、末尾を表示)
    sanple_df = pd.DataFrame(dataset.target, columns=['乳がん診断結果'])
    print(sanple_df.tail(10))
    ```
    
    ```:実行結果
    「1.目的変数データの行数、列数を確認する」の実行結果（569行1列）
    (569,)
    
    「2.目的変数データの対応を確認する」の実行結果（0が悪性、1が良性）
    ['malignant' 'benign']
    
    「3.目的変数データのサンプルを表示する」の実行結果
         乳がん診断結果
    559        1
    560        1
    561        1
    562        0
    563        0
    564        0
    565        0
    566        0
    567        0
    568        1
    ```

## 作成プログラム
Pythonでランダムフォレストの分類を実装したプログラムは以下になります。

```python
from sklearn.datasets import load_breast_cancer 
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import pandas as pd

def main():
    dataset = load_breast_cancer() #乳がんのデータセットを読み込む
    x_train, x_test, y_train, y_test = split_data_set(dataset)
    calculate_accuracy(dataset, x_train, x_test, y_train, y_test)

#乳がんのデータを学習用とテスト用に分割する
def split_data_set(dataset):
    x = dataset.data #説明変数
    y = dataset.target #目的変数

    #学習用データ（train）を8割、テストデータ（test）を2割に分ける
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, stratify = y)
    
    return x_train, x_test, y_train, y_test 

#ランダムフォレストを使用し、テストデータの正解率を算出する
def calculate_accuracy(dataset, x_train, x_test, y_train, y_test):
    #ランダムフォレスト（分類）を実行しモデルを作る
    model_rf = RandomForestClassifier(max_depth=3)
    model_rf.fit(x_train, y_train)

    #どの説明変数が重要だったかを出力する
    output_feature_importances(model_rf, dataset)

    #テストデータにモデルを適用し、悪性か良性かを予測する
    pred = model_rf.predict(x_test) 

    #正解データと予測データを比較して正解率を算出する
    score = accuracy_score(y_test, pred)
    print("テストデータの正解率：" + str(score))

#どの説明変数が重要だったかを出力する
def output_feature_importances(model_rf, dataset):
    df = pd.DataFrame({"説明変数":dataset.feature_names, "重要度":model_rf.feature_importances_})
    df = df.sort_values("重要度", ascending=False)
    print(df)

if __name__ == '__main__':
    main()
```

## 実行結果

- テストデータの正解率
    ```
    テストデータの正解率：0.956140350877193
    ```
    約96％は正解（悪性のデータを正しく悪性、良性のデータを正しく良性と算出）となりました。よって今回作成したモデルの精度は高いと言えます。
　
- どの説明変数が重要だったかを出力した結果

    | 説明変数 | 重要度  |
    | ---- | ---- |
    | worst perimeter | 0.194244 |
    | worst radius | 0.123658 |
    | worst concave points | 0.121415 |
    | mean concave points | 0.100320 |
    | worst area | 0.091693 |
    | 以降説明変数が続く | 以降重要度が続く |

    `worst perimeter`（細胞核の外周の長さの最悪値のようです）が悪性、良性の予測に最も影響していることが分かります。

※`train_test_split`で作られる学習用データとテストデータは毎回変わるため、テストデータの正解率、説明変数の重要度も毎回変化します


# ランダムフォレスト（回帰）の実装
カリフォルニア住宅価格のデータセット「fetch_california_housing」を使用してランダムフォレストの回帰を行います。

## 使用するデータの構造
使用するデータの説明変数と目的変数は以下の通りとなっています。

- 説明変数
`fetch_california_housing`の`dataset.data`を使用します。
データの中身は以下で確認することができます。（地域をブロックごとに分けて算出した情報が入っている）

    ```python:説明変数に使うデータの確認
    import pandas as pd
    from sklearn.datasets import fetch_california_housing
    
    dataset = fetch_california_housing()
    
    # 1.データの行数、列数を確認する
    print(dataset.data.shape)
    
    # 2.データのカラムを確認する
    print(dataset.feature_names)
    
    # 3.データのサンプルを表示する
    sanple_df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
    print(sanple_df.head(10))
    ```
    
    ```:実行結果
    「1.データの行数、列数を確認する」の実行結果（20640行8列）
    (20640, 8)
    
    「2.データのカラムを確認する」の実行結果
    ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']
    
    「3.データのサンプルを表示する」の実行結果
       MedInc  HouseAge  AveRooms  AveBedrms  Population  AveOccup  Latitude  Longitude
    0  8.3252      41.0  6.984127   1.023810       322.0  2.555556     37.88    -122.23
    1  8.3014      21.0  6.238137   0.971880      2401.0  2.109842     37.86    -122.22
    2  7.2574      52.0  8.288136   1.073446       496.0  2.802260     37.85    -122.24
    3  5.6431      52.0  5.817352   1.073059       558.0  2.547945     37.85    -122.25
    4  3.8462      52.0  6.281853   1.081081       565.0  2.181467     37.85    -122.25
    5  4.0368      52.0  4.761658   1.103627       413.0  2.139896     37.85    -122.25
    6  3.6591      52.0  4.931907   0.951362      1094.0  2.128405     37.84    -122.25
    7  3.1200      52.0  4.797527   1.061824      1157.0  1.788253     37.84    -122.25
    8  2.0804      42.0  4.294118   1.117647      1206.0  2.026891     37.84    -122.26
    9  3.6912      52.0  4.970588   0.990196      1551.0  2.172269     37.84    -122.25
    
    ```
　
- 目的変数
    `fetch_california_housing`の`dataset.target`を使用します。
    データの中身は以下で確認することができます。（住宅価格の中央値が入っている）
    
    ```python:目的変数に使うデータの確認
    import pandas as pd
    from sklearn.datasets import fetch_california_housing
    
    dataset = fetch_california_housing()

    # 1.データの行数、列数を確認する
    print(dataset.target.shape)
    
    # 2.データの対応を確認する
    print(dataset.target_names)
    
    # 3.データのサンプルを表示する
    sanple_df = pd.DataFrame(dataset.target, columns=['住宅価格の中央値'])
    print(sanple_df.head(10))
    ```
    
    ```:実行結果
    「1.データの行数、列数を確認する」の実行結果（20640行1列）
    (20640,)
    
    「2.データの対応を確認する」の実行結果
    ['MedHouseVal']
    
    「3.データのサンプルを表示する」の実行結果
         住宅価格の中央値
    0       4.526
    1       3.585
    2       3.521
    3       3.413
    4       3.422
    5       2.697
    6       2.992
    7       2.414
    8       2.267
    9       2.611
    ```

## 作成プログラム
Pythonでランダムフォレストの回帰を実装したプログラムは以下になります。

```python
from sklearn.datasets import fetch_california_housing 
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
import pandas as pd

def main():
    dataset = fetch_california_housing() #カリフォルニア住宅価格のデータセットを読み込む
    x_train, x_test, y_train, y_test = split_data_set(dataset)
    check_model_accuracy(dataset, x_train, x_test, y_train, y_test)

#カリフォルニア住宅価格のデータを学習用とテスト用に分割する
def split_data_set(dataset):
    x = dataset.data #説明変数
    y = dataset.target #目的変数

    #学習用データ（train）を8割、テストデータ（test）を2割に分ける
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
    
    return x_train, x_test, y_train, y_test 

#ランダムフォレストで作成したモデルの精度を確認する
def check_model_accuracy(dataset, x_train, x_test, y_train, y_test):
    #ランダムフォレストを実行しモデルを作る
    model_rf = RandomForestRegressor(max_depth=8)
    model_rf.fit(x_train, y_train)

    #どの説明変数が重要だったかを出力する
    output_feature_importances(model_rf, dataset)

    #テストデータにモデルを適用し、住宅価格を予測する
    pred = model_rf.predict(x_test) 

    #正解データと予測データを比較（平均絶対誤差）してモデルの精度を確認する
    score = mean_absolute_error(y_test, pred)
    print("平均絶対誤差：" + str(score))

#どの説明変数が重要だったかを出力する
def output_feature_importances(model_rf, dataset):
    df = pd.DataFrame({"説明変数":dataset.feature_names, "重要度":model_rf.feature_importances_})
    df = df.sort_values("重要度", ascending=False)
    print(df)

if __name__ == '__main__':
    main()
```

## 実行結果

- 平均絶対誤差
    ```
    平均絶対誤差：0.3892159206320577
    ```
    平均絶対誤差は約0.39となりました。 平均絶対誤差は0に近いほど正解データと予測データの誤差が小さくモデルの精度が高いと言えます。そのため今回作成したモデルの精度は高いと言えそうです。
　
- どの説明変数が重要だったかを出力した結果

    | 説明変数 | 重要度  |
    | ---- | ---- |
    | MedInc | 0.644024 |
    | AveOccup | 0.138845 |
    | Latitude | 0.065285 |
    | Longitude | 0.061197 |
    | HouseAge | 0.044450 |
    | 以降説明変数が続く | 以降重要度が続く |

    `MedInc`（ブロックの所得中央値）が住宅価格の予測に最も影響していることが分かります。

※`train_test_split`で作られる学習用データとテストデータは毎回変わるため、平均絶対誤差、説明変数の重要度も毎回変化します

# 終わりに
Pythonでランダムフォレストを実装しました。
今回はパラメータの設定、評価指標の選択までは考慮しなかったため、別途勉強をし、モデルの精度を高めたいと思いました。

# 参考
この記事は以下の動画・記事を参考にして執筆しました。

・[Pythonでランダムフォレストを作ってみよう【Python機械学習#8】](https://youtu.be/odqb8oXcJMY?si=yHxGGnidxHEZK9n5)

・[Pythonによるあたらしいデータ分析の教科書（寺田 学,辻 真吾,鈴木 たかのり,福島 真太朗）](https://amzn.asia/d/6FHJD2e)

・[ランダムフォレストとは？基本の仕組みから活用事例までまとめて解説](https://aismiley.co.jp/ai_news/random-forests/)

・[Pythonオンライン学習サービス PyQ（パイキュー）_ランダムフォレスト ](https://docs.pyq.jp/python/machine_learning/model/randomforest.html)

・[決定木分析（ディシジョンツリー）とは？概要や活用方法、ランダムフォレストも解説](https://cacoo.com/ja/blog/what-is-decision-tree/)