【テーブルデータ】ランダムフォレストで学ぶモデル構築手順

table_data_ml 機械学習

テーブルデータのモデル作成手順を、ボストン住宅価格のデータセットを用いて紹介していきます。
この記事を読むことで、テーブルデータに必要なデータの前処理から機械学習モデルの構築手順を理解することができますので、ぜひ最後までご覧ください。

この記事で学べる内容
  • テーブルデータにおけるモデル構築手順
    • テーブルデータの前処理(欠損値の扱い、ダミー変数変換)
    • ランダムフォレストのモデル構築方法

使用するデータセット

使用するデータセットは、ボストンにおける住宅価格のデータセットを使用します。このデータセットは数値データからカテゴリカル変数まで、様々な説明変数が含まれているため、サンプルとして頻繁に使用されています。
サンプルデータはこちらのkaggleの公式サイトからダウンロードできます。

このデータはライブラリscikit-learnから取得することもできますが、実作業に近い形でcsvファイルから読み込んでいきます。

データの読み込み

kaggleからダウンロードしたデータ(train.csv, test.csv)を読み込んでいきます。ここでは、データはカレントディレクトリのdataディレクトリ配下にある想定です。

import pandas as pd

train_all = pd.read_csv('data/train.csv') # 学習用データ
test_all = pd.read_csv('data/test.csv') # テスト用データ

train_all.shape # (1460, 81)
test_all.shape # (1459, 80)

テスト用データは目的変数の住宅価格(SalePrice)がないため、カラムが1つ少なくなっています。
この記事では、見やすさを重視してカラムを絞って用います。

# この記事で使用するカラム名
xt_var = ['SalePrice', 'OverallQual', 'GrLivArea', 'FireplaceQu', 
          'BsmtQual', 'BsmtCond', 'PoolQC']

# カテゴリカル変数のカラム名
categorical_var = ['FireplaceQu', 'BsmtQual']

train = train_all[xt_var]
test = test_all[xt_var[1:]]

print(train[:5]) # 先頭5行を表示
#    SalePrice  OverallQual  GrLivArea FireplaceQu BsmtQual PoolQC
# 0     208500            7       1710         NaN       Gd    NaN
# 1     181500            6       1262          TA       Gd    NaN
# 2     223500            7       1786          TA       Gd    NaN
# 3     140000            7       1717          Gd       TA    NaN
# 4     250000            8       2198          TA       Gd    NaN

このカラムは一通りのパターンを満たすように、カテゴリカル変数や、欠損値が含むカラム、目的変数との相互相関が高いカラムを選択しています。
ちなみに、目的変数(SalePrice)との相互相関が高いカラム、は以下のコードで取得しています。

corr_var = train_all.corr()["SalePrice"].sort_values(ascending=False).head(3).keys().tolist()

corr()メソッドで相関を計算し、降順で並べ替え、上位のキーを取得しています。

前処理

モデル構築に向けてデータの前処理を行います。
ここでは、欠損値の対応カテゴリ変数のダミー変数化について順番に紹介します。

欠損値

データに欠損があるとモデル学習/推論において支障が出るため対処が必要です。対処法としては、欠損値を含むカラムやインデックスを削除するか、適当な値で欠損値を置換する方法があります。
対処法を決めるために、欠損値の有無から確認します。

欠損値の確認

欠損値を確認するには、isnull()メソッドを用います。各要素に対して欠損値NaNの判定を行い、欠損値の場合はTrue、そうでない場合はFalseが返ります。

print(train[:5].isnull())

#    SalePrice  OverallQual  GrLivArea  FireplaceQu  BsmtQual  PoolQC
# 0      False        False      False         True     False    True
# 1      False        False      False        False     False    True
# 2      False        False      False        False     False    True
# 3      False        False      False        False     False    True
# 4      False        False      False        False     False    True

これを利用することで、カラム毎の欠損率を確認できます。

# カラム毎の欠損率
print((train.isnull().sum()/len(train)).sort_values(ascending=False)[:5])

# PoolQC         0.995205
# FireplaceQu    0.472603
# BsmtQual       0.025342
# SalePrice      0.000000
# OverallQual    0.000000
# dtype: float64

欠損値の削除

欠損値の削除にはdropna()メソッドを使います。引数を指定しない場合、欠損値が含まれる行が全て削除されます。

print(train[:5].dropna()) # カラム"PoolQC"に欠損値が含まれるため、行が削除される。

# Empty DataFrame

引数axis=1とすることで、欠損値を含む列を削除するように変更できます。(デフォルトでaxis=0)

print(train.dropna(axis=1))

#   SalePrice  OverallQual  GrLivArea
# 0     208500            7       1710
# 1     181500            6       1262
# 2     223500            7       1786
# 3     140000            7       1717
# 4     250000            8       2198

引数how='all'とすることで、行もしくは列(axisで指定)が全て欠損値だった場合に、その行もしくは列が削除されます。(デフォルトでhow='any')

print(train.dropna(how='all')[:5]) # 全て欠損値の行を削除

引数threshを指定することで、欠損値ではない要素の数に応じて指定したaxisを削除することができます。

dropna_rate = 0.05
print(train.dropna(thresh=len(train)*(1-dropna_rate), axis=1)[:5])

#    SalePrice  OverallQual  GrLivArea BsmtQual
# 0     208500            7       1710       Gd
# 1     181500            6       1262       Gd
# 2     223500            7       1786       Gd
# 3     140000            7       1717       TA
# 4     250000            8       2198       Gd

欠損値の置換

fillna()メソッドを用いることで、欠損値を全て同じ値で置換できます。
カムラごとに置換する値を変更したい場合は、カラム名をキーとした辞書で指定します。その場合、指定されなかったカラムの欠損値は残ります。

print(train.fillna({'FireplaceQu':'TA', 'PoolQC':0})[:5])

#    SalePrice  OverallQual  GrLivArea FireplaceQu BsmtQual PoolQC
# 0     208500            7       1710          TA       Gd      0
# 1     181500            6       1262          TA       Gd      0
# 2     223500            7       1786          TA       Gd      0
# 3     140000            7       1717          Gd       TA      0
# 4     250000            8       2198          TA       Gd      0

平均値や中央値といった統計値で置換したい場合は、DataFrameで得られる統計値を与えれば良いです。

train.fillna(train.median()) # 中央値で置換する例

ダミー変数化

性別や血液型のように数値ではなく、文字列で表現されているデータをカテゴリカルデータと呼びます。ここでは、このカテゴリカルデータを数値(この変換された数値をダミー変数と呼ぶ)に変換していきます。
注意点として、機械学習に用いるデータの場合、テストデータのカラム名は学習データのカラム名と合わせる必要がある点に注意が要ります。

カテゴリの指定: pd.Categorical

学習データとテストデータで同じカラム名になるように、カテゴリを指定します。

for var in ['FireplaceQu', 'BsmtQual']:
    categories = train[var].dropna().unique().tolist() # カテゴリデータの種類を取得
    train[var] = pd.Categorical(train[var], categories=categories)

ダミー変数化

get_dummies()メソッドを用いることで、カテゴリカルデータをダミー変数にできます。

print(pd.get_dummies(train))

# 出力結果(抜粋)
#    SalePrice  OverallQual  GrLivArea  FireplaceQu_TA  FireplaceQu_Gd  \
# 0     208500            7       1710               0               0   
# 1     181500            6       1262               1               0   
# 2     223500            7       1786               1               0   
# 3     140000            7       1717               0               1   
# 4     250000            8       2198               1               0  

#    FireplaceQu_Fa  FireplaceQu_Ex  FireplaceQu_Po  BsmtQual_Gd  BsmtQual_TA  \
# 0               0               0               0            1            0   
# 1               0               0               0            1            0   
# 2               0               0               0            1            0   
# 3               0               0               0            0            1   
# 4               0               0               0            1            0  

ランダムフォレストモデルの構築

データの前処理について紹介したので、モデルを構築していきます。

  1. データの前処理:前章で紹介した前処理を適用します
  2. 検証用データの取得:学習データの一部を検証用のデータとして分離します
  3. 学習モデルの構築:ハイパーパラメータをチューニングし、学習モデルを構築します
  4. 検証データの推論:学習モデルを用い検証データの推論を行います

データの前処理

ここまでの内容を元に、学習データ、テストデータの前処理を行います。

# 欠損値の削除/置換
dropna_rate = 0.05
train_dropna = train.dropna(thresh=len(train)*(1-dropna_rate), axis=1).dropna()
test_dropna = test.dropna(thresh=len(test)*(1-dropna_rate), axis=1).dropna()

# カテゴリの指定
for var in categorical_var:
    categories = train_dropna[var].dropna().unique().tolist()
    train_dropna[var] = pd.Categorical(train_dropna[var], categories=categories)
    test_dropna[var] = pd.Categorical(test_dropna[var], categories=categories)

# ダミー変数化
train_dummy = pd.get_dummies(train_dropna)
test_dummy = pd.get_dummies(test_dropna)

train_test_split:学習データと検証データの分離

sklearn.model_selection.train_test_splitを用い、学習データの一部を検証データとして分離します。

from sklearn.model_selection import train_test_split
train_sp, valid_sp = train_test_split(train_dummy)

print(train_sp.shape, valid_sp.shape)

# (1095, 7) (365, 7)

学習モデルの構築(ハイパーパラメータチューニングなし)

ランダムフォレストsklearn.ensemble.RandomForestClassifierのモデルを構築します。

  • 学習/教師データの作成:目的変数(SalePrice)を分離します
  • モデル定義:ハイパーパラメータを指定し、モデルを定義します
  • モデル学習:fit()メソッドを用いて、モデルを学習します
# ランダムフォレストのImport
from sklearn.ensemble import RandomForestClassifier

xs = train_sp.drop('SalePrice',axis=1) # 学習データ
ts = train_sp['SalePrice'] # 教師データ
xs_valid = valid_sp.drop('SalePrice',axis=1) # 検証データ(説明変数)
ts_valid = valid_sp['SalePrice'] # 検証データ(目的変数)

# モデル定義(ハイパーパラメータの設定)
model = RandomForestClassifier(max_depth=20, random_state=1000)

# モデル学習
model.fit(xs, ts) 

ここではハイパーパラメータ(max_depthなど)を決め打ちでモデルは学習していますが、実際はoptunaなどを用いて適切なパラメータを探索していきます。

学習モデルの構築:(ハイパーパラメータチューニングあり)

先ほどはハイパーパラメータを決め打ちでモデル学習まで行いました。ここでは、ハイパーパラメータ探索の定番フレームワークoptunaを使ってチューニングを行なった学習モデルを構築します。

import optuna
from sklearn.metrics import mean_absolute_error as mae

# 評価関数の作成
def objective(trial, xs, ts, xs_valid, ts_valid):
    param = {}
    param['n_estimators'] = trial.suggest_int('n_estimators', 1, 100, log=True)
    param['max_depth'] = trial.suggest_int('max_depth', 1, 100, log=True)

    model = RandomForestClassifier(**param)
    model.fit(xs, ts)
    
    ys =  model.predict(xs_valid)
    score = mae(ys, ts_valid)

    return score

# ハイパーパラメータのチューニング
study = optuna.create_study() # ハイパラチューニングのインスタンスを生成
study.optimize(lambda trial: objective(trial, xs, ts, xs_valid, ts_valid), n_trials=100) # 最適なパラメータの探索
print(f'Best parameter:{study.best_params}') # チューニングで得られたパラメータ

# Best parameter:{'n_estimators': 37, 'max_depth': 5}

# モデル定義(ハイパーパラメータの設定)
model = RandomForestClassifier(**study.best_params)

# モデル学習
model.fit(xs, ts)

これでOptunaを用いたハイパーパラメータの探索ができました。
より具体的な解説は別記事として公開していく予定です。

検証データの予測

モデルが構築できたので、predict()メソッドで検証用データを予測していきます。

ys = model.predict(valid_sp.drop('SalePrice',axis=1))

結果を確認します。

print(ys[:10]) # 検証データの予測結果
# [176500 200000 264561 157900 109900 173900 177000 264561 157900  88000]

print(valid_sp['SalePrice'][:10].values) # 検証データの正解
# [183000 189000 239000 142000 135000 187500 222000 262500 185000 110000]

今回は説明用に部分的なデータで学習しましたが精度を上げたい場合は、データ分析を行い説明変数の追加やハイパーパラメータのチューニングを行えば向上すると思います。

終わりに

テーブルデータを用いて、ランダムフォレストの学習モデルを構築する手順を紹介してきました。本番ではデータ分析に応じて様々な前処理を試し、モデルも複数構築してそれぞれ評価して最適な手法を探索することになります。他のモデルの構築方法やハイパーパラメータチューニングについても執筆予定ですので、そちらも参考にしていただければと思います。

コメント

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