自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

(その4-6) エイムズの住宅価格をランダムフォーレストで予測してみた パート2

Data Analytics
Data Analytics

今回は前回の続きでランダムフォーレスト(random forest)のパラメータチューニングをベイズ最適化(Bayesian Optimization)で行いたいと思います。

(その4-6) エイムズの住宅価格をランダムフォーレストで予測してみた パート1
今回はランダムフォーレスト(random forest)になります。 旧ブログでも割とアクセスがあった手法になります。 大学の授業や会社での勉強などでとりあえず使ってみたいという方は上から順に実行すれば動くはずですのでぜひお試しください。 ...

色々と調べている中、ベイズ最適化はグリッドサーチのように総当たりで最適なパラメータを発掘するのではなく、ある程度当たりをつけながら最適なパラメータを探索してくれる手法というイメージを持ちました。

ランダムフォーレストのベイズ最適化はこちらのコードを参考にしました。

それではやっていきます。

スポンサーリンク

Bayesian Optimizationライブラリのインストール

GitHubで公開してくださっている方がいるので活用させていただきます。

基本ReadMeの通りやっていけばOKです。

まずは、

If you are using scipy>1.8.x, please install directly from this branch: e.g. pip install git+https://github.com/fmfn/BayesianOptimization

という記載があるので、scipyのバージョンを確認します。

import scipy
scipy.__version__
Out[0]
'1.8.1'

1.8以上だったので、注意書きの通り直接GitHubのブランチからインストールします。

# BayesianOptimizationのインストール
pip install git+https://github.com/fmfn/BayesianOptimization
Out[0]

Collecting git+https://github.com/fmfn/BayesianOptimization
  Cloning https://github.com/fmfn/BayesianOptimization to /private/var/folders/n0/ft3172td6v70d6klgt216_3r0000gn/T/pip-req-build-la4bz5a3
  Running command git clone --filter=blob:none --quiet https://github.com/fmfn/BayesianOptimization /private/var/folders/n0/ft3172td6v70d6klgt216_3r0000gn/T/pip-req-build-la4bz5a3
  Resolved https://github.com/fmfn/BayesianOptimization to commit 1aaa255591aa4c5514bf3ea5ed3b0270f6cce575
  Preparing metadata (setup.py) ... done
・・・省略・・・
Successfully built bayesian-optimization
Installing collected packages: bayesian-optimization
Successfully installed bayesian-optimization-1.2.0

スポンサーリンク

評価指標

住宅IdごとのSalePrice(販売価格)を予測するコンペです。

評価指標は予測SalePriceと実測SalePriceの対数を取ったRoot-Mean-Squared-Error(RMSE)の値のようです。

House Prices - Advanced Regression Techniques | Kaggle
Predict sales prices and practice feature engineering, RFs, and gradient boosting
スポンサーリンク

ランダムフォーレスト分析

分析用データの準備

事前に欠損値処理や特徴量エンジニアリングを実施してデータをエクスポートしています。

本記事と同じ結果にするためには事前に下記記事を確認してデータを用意してください。

(その3-2) エイムズの住宅価格のデータセットのデータ加工①

(その3-3) エイムズの住宅価格のデータセットのデータ加工②

学習用データとスコア付与用データの読み込み

import pandas as pd
import numpy as np
# エイムズの住宅価格のデータセットの訓練データとテストデータを読み込む
df = pd.read_csv("/Users/hinomaruc/Desktop/blog/dataset/ames/ames_train.csv")
df_test = pd.read_csv("/Users/hinomaruc/Desktop/blog/dataset/ames/ames_test.csv")
df.head()
Out[0]

Id LotFrontage LotArea LotShape Utilities LandSlope OverallQual OverallCond MasVnrArea ExterCond ... SaleType_New SaleType_Oth SaleType_WD SaleCondition_Abnorml SaleCondition_AdjLand SaleCondition_Alloca SaleCondition_Family SaleCondition_Normal SaleCondition_Partial SalePrice
0 1 65.0 8450 3.0 3.0 2.0 7 5 196.0 2.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 208500
1 2 80.0 9600 3.0 3.0 2.0 6 8 0.0 2.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 181500
2 3 68.0 11250 2.0 3.0 2.0 7 5 162.0 2.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 223500
3 4 60.0 9550 2.0 3.0 2.0 7 5 0.0 2.0 ... 0.0 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 140000
4 5 84.0 14260 2.0 3.0 2.0 8 5 350.0 2.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 250000

5 rows × 335 columns

# 描画設定
from IPython.display import HTML
import seaborn as sns
from matplotlib import ticker
import matplotlib.pyplot as plt
sns.set_style("whitegrid")
from matplotlib import rcParams
rcParams['font.family'] = 'Hiragino Sans' # Macの場合
#rcParams['font.family'] = 'Meiryo' # Windowsの場合
#rcParams['font.family'] = 'VL PGothic' # Linuxの場合
rcParams['xtick.labelsize'] = 12       # x軸のラベルのフォントサイズ
rcParams['ytick.labelsize'] = 12       # y軸のラベルのフォントサイズ
rcParams['axes.labelsize'] = 18        # ラベルのフォントとサイズ
rcParams['figure.figsize'] = 18,8      # 画像サイズの変更(inch)

ランダムフォーレストに使用する変数を選ぶ

とりあえず全て突っ込んでみます

ランダムフォーレストで学習を実施 (ベイズ最適化によるパラメータチューニング)

# 説明変数と目的変数を指定

# 学習データ
X_train = df.drop(["Id","SalePrice"],axis=1)
Y_train = df["SalePrice"] # 販売価格

# テストデータ
X_test = df_test.drop(["Id"],axis=1)
# ベイズ最適化
from bayes_opt import BayesianOptimization

# ランダムフォーレスト
from sklearn.ensemble import RandomForestRegressor

# 評価指標 (どっちも試してよい結果を採用しようと思います。)
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_squared_log_error

# 交差検証 (https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_validate.html)
from sklearn.model_selection import cross_validate
"""
RandomForest(交差検証あり)
"""
def random_forest_cv(X, y, scoring, **kwargs):

    regr = RandomForestRegressor(random_state=1414, **kwargs)

    cv_results = cross_validate(regr, X, y
                                , scoring = scoring # "neg_mean_squared_error,neg_root_mean_squared_error,neg_mean_squared_log_errorで検証してみる
                                , cv = 5 # 交差検証数 5-fold
                                , verbose = 2 # ログレベル
                                , n_jobs = -1 # 並列実行数 -1:全プロセッサ利用          
                              )

    return cv_results['test_score'].mean()
random_forest_cv(X_train,Y_train,'neg_mean_squared_error')
Out[0]
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 4 concurrent workers.
[Parallel(n_jobs=-1)]: Done   5 out of   5 | elapsed:    6.4s finished

-882864106.3749046
"""
Wrapper of RandomForest cross validation.
"""
def bayesian_optimise_rf(X, y, n_iter = 100):

    def rf_crossval(n_estimators, max_features,min_samples_split):
        return random_forest_cv(
            X = X,
            y = y,
            scoring='neg_mean_squared_error',
            n_estimators = int(n_estimators),
            max_features = max(min(max_features, 0.999), 1e-3), # 
            min_samples_split = int(min_samples_split)
        )

    optimizer = BayesianOptimization(
        f = rf_crossval, # black box function (最適化したい関数)
        pbounds = {
            "n_estimators" : (10, 400),
            "max_features" : (0.1, 0.999),
            "min_samples_split" : (2,50)
        }
    )

    optimizer.maximize(n_iter = n_iter)

    print("Final result:", optimizer.max)
bayesian_optimise_rf(X_train,Y_train)
Out[0]
|   iter    |  target   | max_fe... | min_sa... | n_esti... |
-------------------------------------------------------------

[Parallel(n_jobs=-1)]: Using backend LokyBackend with 4 concurrent workers.
[Parallel(n_jobs=-1)]: Done   5 out of   5 | elapsed:   10.1s finished
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 4 concurrent workers.

| 1         | -9.827e+0 | 0.2725    | 38.92     | 354.3     |

[Parallel(n_jobs=-1)]: Done   5 out of   5 | elapsed:    5.4s finished
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 4 concurrent workers.

| 2         | -8.858e+0 | 0.4678    | 24.19     | 227.6     |

・・・省略・・・

Final result: {'target': -809969600.8095932, 'params': {'max_features': 0.3814608551637303, 'min_samples_split': 5.840239834845827, 'n_estimators': 193.56063863177957}}

[Parallel(n_jobs=-1)]: Done   5 out of   5 | elapsed:    3.1s finished

ベイズ最適化は少ない試行の中で最大のパフォーマンスを出そうとする手法のようなので、最終的にはグリッドサーチやランダムサーチで総当たりで最適化した結果と似てくるようです。

# https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html
from sklearn.ensemble import RandomForestRegressor

# モデル作成
regr = RandomForestRegressor(random_state=1414,max_features=0.3814608551637303,min_samples_split=6,n_estimators=193)

# フィット
regr.fit(X_train,Y_train)
Out[0]

# モデルパラメータ一覧
regr.get_params()
Out[0]

    {'bootstrap': True,
     'ccp_alpha': 0.0,
     'criterion': 'squared_error',
     'max_depth': None,
     'max_features': 0.3814608551637303,
     'max_leaf_nodes': None,
     'max_samples': None,
     'min_impurity_decrease': 0.0,
     'min_samples_leaf': 1,
     'min_samples_split': 6,
     'min_weight_fraction_leaf': 0.0,
     'n_estimators': 193,
     'n_jobs': None,
     'oob_score': False,
     'random_state': 1414,
     'verbose': 0,
     'warm_start': False}

# 訓練データに対して精度を確認
regr.score(X_train,Y_train)
Out[0]
0.974362579748016

デフォルトの設定より若干オーバーフィットが収まったようです。

### モデルを適用し、SalePriceの予測をする
df_test["SalePrice"] = regr.predict(X_test)
df_test[["Id","SalePrice"]]
Out[0]

Id SalePrice
0 1461 124800.371709
1 1462 162157.481550
2 1463 182165.348584
3 1464 180167.654303
4 1465 194464.745519
... ... ...
1454 2915 90772.046772
1455 2916 89611.494052
1456 2917 162726.773648
1457 2918 116347.859999
1458 2919 235138.240851

1459 rows × 2 columns

sns.histplot(df_test["SalePrice"],bins=20)
Out[0]

予測できていそうです。

Kaggleにスコア付与結果をアップロード

df_test[["Id","SalePrice"]].to_csv("ames_submission.csv",index=False)
!/Users/hinomaruc/Desktop/blog/my-venv/bin/kaggle competitions submit -c house-prices-advanced-regression-techniques -f ames_submission.csv -m "#6 random forest bo"
Out[0]
100%|██████████████████████████████████████| 33.7k/33.7k [00:03<00:00, 8.87kB/s]
Successfully submitted to House Prices - Advanced Regression Techniques

#6 random forest bo
Score: 0.14587

デフォルトの設定より微妙に向上しました。

スポンサーリンク

使用ライブラリのバージョン

pandas Version: 1.4.3
numpy Version: 1.22.4
scikit-learn Version: 1.1.1
seaborn Version: 0.11.2
matplotlib Version: 3.5.2

スポンサーリンク

まとめ

ベイズ最適化(Bayesian Optimization)によるランダムフォーレストのパラメーターチューニングをしてみましたが、色々と関数を作り込むなど大変そうです。

パラメータは連続値しか試せませんでしたが、離散値や文字列なども対応しているか更なる調査は必要になりそうです。

次回は私が一番大好きなXGBoostを試してみたいと思います。

スポンサーリンク

参考

https://www.kdnuggets.com/2019/07/xgboost-random-forest-bayesian-optimisation.html

タイトルとURLをコピーしました