はじめに
前回はLightGBMを使用するときの手順を学びました。
このとき、Cross Validation(交差検証)に関しては、詳しい説明は省いたので、今回あらたに取り上げたいと思います。Scikitlearnを利用することで簡単に交差検証をおこなうことができます。但し、データセットを分割するときに注意する点もあります。この辺もとりあげることにしましょう。
Cross Validation(交差検証)
Cross Validation(交差検証)とは、異なる方法で複数のデータセットの分割をおこない、それぞれでホールドアウト検証を実行する方法です。そのスコアの平均を確認することで、1回のホールドアウト検証で生じる偏りを抑え、汎化性能を高めることが期待できます。。
単純なホールドアウト検証は、scikitlearnのtrain_test_split( ) で実装できます。そのため、train_test_split( )を複数回用いれば Cross Validation(交差検証) を実装できそうです。ただ、scikitlearnにはもっと便利なKFoldというクラスが用意されています。これを使った例をみていきましょう。
ライブラリのインポート
まずは利用するライブラリのインポートをします。train_test_splitはデータを分割する前処理用のクラスです。KFoldが交差検証用のクラス、lightgbmはLightGBMのライブラリになります。
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
import lightgbm as lgb
データの読み込み
train=pd.read_csv('data/train.csv')
test=pd.read_csv('data/test.csv')
data = pd.concat([train, test], sort=False)
特徴量エンジニアリング
Titanicのデータはこれまで何度か扱ったことがあり、その中で探索的データ解析をおこないました。以下のコードの前半部分は、その探索的データ解析の結果を反映させています。探索的データ解析については、下の投稿に記しています。
# 特徴量エンジニアリング
data['Sex'].replace(['male', 'female'], [0, 1], inplace=True)
data['Embarked'].fillna(('S'), inplace=True)
data['Embarked'] = data['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
data['Fare'].fillna(np.mean(data['Fare']), inplace=True)
data['Age'].fillna(data['Age'].median(), inplace=True)
data['FamilySize'] = data['Parch'] + data['SibSp'] + 1
data['IsAlone'] = 0
data.loc[data['FamilySize'] == 1, 'IsAlone'] = 1
# 特徴量選択
delete_columns = ['Name', 'PassengerId', 'Ticket', 'Cabin']
data.drop(delete_columns, axis=1, inplace=True)
train = data[:len(train)]
test = data[len(train):]
y_train = train['Survived']
X_train = train.drop('Survived', axis=1)
X_test = test.drop('Survived', axis=1)
交差検証
ここで扱うKFoldは、データセットをK個のグループ(フォールド)に分割し、そのうちの1グループ(フォールド)をテストデータ、他のグループ(フォールド)を訓練データとし一つのモデルを構築します。これをK回グループ間でテストデータをローテーションすることでK個のモデルを作ります。最終的な予測値は殆どの場合K個のモデルの予測値の平均で決めます。
OOF予測値とはOut Of Fold (Prediction)から来ており、文字通りフォールドから出てきた予測値という意味で頻繁に使われます。
ここからKFoldを使った交差検証を実装していきましょう。
# 各分割でのX_testに対する予測値を格納するリスト
y_preds = []
# 各分割で学習したモデルを格納するリスト
models = []
# 各分割での検証用データセット(X_val)に対する予測値を格納するndarray
oof_train = np.zeros((len(X_train),))
# 分割方法の指定
cv = KFold(n_splits=5, shuffle=True, random_state=0)
categorical_features = ['Embarked', 'Pclass', 'Sex']
params = {
'objective': 'binary',
'max_bin': 300,
'learning_rate': 0.05,
'num_leaves': 40
}
# 設定した分割方法に基づいて、各分割での学習用・検証用データセットに対応するindexを取得
for train_index, valid_index in cv.split(X_train):
# 取得したindexに基づいて、データセットを分割
X_tr = X_train.loc[train_index, :]
X_val = X_train.loc[valid_index, :]
y_tr = y_train[train_index]
y_val = y_train[valid_index]
lgb_train = lgb.Dataset(X_tr, y_tr,categorical_feature=categorical_features)
lgb_eval = lgb.Dataset(X_val, y_val,reference=lgb_train,categorical_feature=categorical_features)
model = lgb.train(params, lgb_train,
valid_sets=[lgb_train, lgb_eval],
verbose_eval=10,
num_boost_round=1000,
early_stopping_rounds=10)
# 各分割での検証用のデータセット(X_val)に対する予測値を格納(oof_trainに格納)
oof_train[valid_index] = model.predict(X_val, num_iteration=model.best_iteration)
# 各分割でのX_testに対する予測値を、先に作成したy_predsというリストに格納
y_pred = model.predict(X_test, num_iteration=model.best_iteration)
y_preds.append(y_pred)
# 各分割で学習したモデルをmodelsというリストに格納
models.append(model)
評価
scores = [
m.best_score['valid_1']['binary_logloss'] for m in models
]
score = sum(scores) / len(scores)
print('===CV scores===')
print(scores)
print(score)
from sklearn.metrics import accuracy_score
y_pred_oof = (oof_train > 0.5).astype(int)
accuracy_score(y_train, y_pred_oof)
まとめ
いかがでしょうか?何度かやっているうちに、だんだん理解できたりすることもあります。まずは、自分でコードを書いてみて、わからなかったところを調べて写経するなどしているうちに、何故か、すーっと理解できる瞬間があったります。あきらめず頑張りましょう!
コメント