テーブルデータのモデル作成手順を、ボストン住宅価格のデータセットを用いて紹介していきます。
この記事を読むことで、テーブルデータに必要なデータの前処理から機械学習モデルの構築手順を理解することができますので、ぜひ最後までご覧ください。
使用するデータセット
使用するデータセットは、ボストンにおける住宅価格のデータセットを使用します。このデータセットは数値データからカテゴリカル変数まで、様々な説明変数が含まれているため、サンプルとして頻繁に使用されています。
サンプルデータはこちらの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
ランダムフォレストモデルの構築
データの前処理について紹介したので、モデルを構築していきます。
データの前処理
ここまでの内容を元に、学習データ、テストデータの前処理を行います。
# 欠損値の削除/置換
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]
今回は説明用に部分的なデータで学習しましたが精度を上げたい場合は、データ分析を行い説明変数の追加やハイパーパラメータのチューニングを行えば向上すると思います。
終わりに
テーブルデータを用いて、ランダムフォレストの学習モデルを構築する手順を紹介してきました。本番ではデータ分析に応じて様々な前処理を試し、モデルも複数構築してそれぞれ評価して最適な手法を探索することになります。他のモデルの構築方法やハイパーパラメータチューニングについても執筆予定ですので、そちらも参考にしていただければと思います。
コメント