コンペ用 LightGBM の実装 (Classification)

Posted on 2020/11/27 in 機械学習 , Updated on: 2020/11/27

はじめに

kaggle などのコンペで使用される LightGBM の 2値分類問題における使い方。汎用できるように、交差検証や特徴量重要度の可視化まで行う。

  • データセット : scikit-learn を使った自作データ
  • 交差検証 : StratifiedKFold k=5
  • 評価指標 : Binary Logarithm loss

インポート

In [140]:
import numpy as np
import pandas as pd

# グラフ描画用
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
sns.set_style('darkgrid')

import lightgbm as lgb

from sklearn.datasets import make_classification
from sklearn.metrics import roc_auc_score, roc_curve, accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold

データセットの作成

make_classification を使って、2値分類用のデータセットを作成する。参考
作成したデータを、DataFrame にした上で、8:2 の割合で train/test に分割する。

In [147]:
# データセット作成
X, y = make_classification(n_samples=6000,
                           n_features=300,
                           n_informative=10,
                           n_redundant=0,
                           n_classes=2,
                           n_clusters_per_class=1,
                           class_sep=0.1,
                           random_state=42)

# DataFrame の作成。特徴量名は Feature_~ にする。
df = pd.DataFrame(X, columns=["Feature_{}".format(i+1) for i in range(X.shape[1])])

# train/test に分割
X_train, X_test, y_train, y_test = train_test_split(
                                        df, y, test_size=0.2,
                                        shuffle=True, random_state=42
                                    )
X_train = X_train.reset_index(drop=True)
X_test = X_test.reset_index(drop=True)

print("Training data shape :", X_train.shape)
print("Test data shape :", X_test.shape)
X_train.head()
Training data shape : (4800, 300)
Test data shape : (1200, 300)
Out[147]:
Feature_1 Feature_2 Feature_3 Feature_4 Feature_5 Feature_6 Feature_7 Feature_8 Feature_9 Feature_10 ... Feature_291 Feature_292 Feature_293 Feature_294 Feature_295 Feature_296 Feature_297 Feature_298 Feature_299 Feature_300
0 1.180723 -0.910884 -0.057152 -0.252932 -2.105539 -0.393988 -1.269655 0.999651 1.196060 -1.330411 ... -0.064076 -0.501613 -1.534005 -1.131345 -0.032086 -0.203505 1.565069 1.371163 -0.868586 0.423831
1 0.999401 0.035357 -0.192271 -0.923003 -0.535823 -0.710071 1.561174 0.921455 -0.900954 1.340095 ... 1.040523 -0.893182 0.298837 -1.117109 -0.595703 0.379988 0.027215 -0.771336 -1.311806 -0.824764
2 0.372522 -0.790324 1.086492 1.574006 0.401104 -1.069710 0.926690 -1.015784 -0.551172 1.722592 ... -1.337947 -0.656622 -0.514128 1.075478 0.382057 -0.270041 0.017649 1.286371 -0.198724 -0.151523
3 0.230054 1.132656 0.417253 1.319316 0.235799 -0.492621 -0.877917 0.053437 1.042794 -0.167608 ... 0.589167 -0.326233 0.952869 0.291304 2.380726 -0.488720 1.600328 0.467231 -0.844646 0.196435
4 -1.799978 -1.464917 -0.462669 0.761324 -1.086268 -0.098241 -0.031997 -0.452804 -0.945452 -0.354523 ... 0.950559 -1.677762 0.244088 1.333380 0.275513 -0.182026 -0.544353 -0.464670 1.622610 -0.193263

5 rows × 300 columns

これで、train/test それぞれ 40,000/10,000 サンプルづつの、特徴量 500個のデータセットができた。

学習

今回のデータは、すべて float 型の数値データなので、このまま学習に用いる。
※ カテゴリデータが含まれる場合は、ラベルエンコードなどの処理必要 : 参考

学習は、k=5StratifiedKFold を実施。各fold ごとにモデルの精度を確認。early_stopping でロスの減少が止まったところで学習を止める。止めた round のモデルで、各 fold の validation データに対して、予測値を出し、ROC-AUC スコアを表示する。後の推論用にすべてのモデルをリストに保管する。

In [148]:
# 学習用のパラメータ
params = {
    "objective": "binary",      # 2値分類
    "boosting" : "gbdt",        # default
    "learning_rate": 0.1,       # 学習率
    "metric": "binary_logloss", # モデルの評価指標
    "seed": 42
}

# 交差検証設定
skf = StratifiedKFold(n_splits=5, random_state=12, shuffle=True)

# 学習記録用の入れ物を準備
oof = pd.DataFrame()                 # Out-of-Fold 結果
models = []                          # 各 fold のモデル
scores = 0.0                         # Validation データでのスコア

# 交差検証
for fold, (trn_idx, val_idx) in enumerate(skf.split(X_train, y_train)):

    
    print("Fold :", fold+1)
    
    # train/validation 用に lightGBM 用データセットを作成
    X_trn, y_trn = X_train.loc[trn_idx], y_train[trn_idx]
    X_val, y_val = X_train.loc[val_idx], y_train[val_idx]
    
    lgb_train = lgb.Dataset(X_trn, y_trn, weight=None)
    lgb_eval = lgb.Dataset(X_val, y_val, weight=None)
    
    # model の作成、学習
    model = lgb.train(params=params,
                      train_set=lgb_train,
                      valid_sets=[lgb_train, lgb_eval],
                      num_boost_round=10000,           # 10000 round まで実行
                      early_stopping_rounds=100,        # 100 round ごとに los を確認。改善なければ stop
                      verbose_eval=100                  # 100 round ごとの los を表示。
                     )
    
    # validation データでの予測結果
    val_pred = model.predict(X_val)
    score = roc_auc_score(y_true=y_val, y_score=val_pred)
    print(f"Validation ROC-AUC score : {score:.4f}")

    scores += score / 5
    models.append(model)
    print("*" * 100)

# 各 fold における Validation スコアの平均
print(f"All fold average score : {scores:.4f}")
Fold : 1
Training until validation scores don't improve for 100 rounds
[100]	training's binary_logloss: 0.0655846	valid_1's binary_logloss: 0.295541
[200]	training's binary_logloss: 0.0109665	valid_1's binary_logloss: 0.250766
[300]	training's binary_logloss: 0.00191411	valid_1's binary_logloss: 0.25155
Early stopping, best iteration is:
[232]	training's binary_logloss: 0.00620919	valid_1's binary_logloss: 0.247921
Validation ROC-AUC score : 0.9632
****************************************************************************************************
Fold : 2
Training until validation scores don't improve for 100 rounds
[100]	training's binary_logloss: 0.0662993	valid_1's binary_logloss: 0.265093
[200]	training's binary_logloss: 0.0119891	valid_1's binary_logloss: 0.229463
[300]	training's binary_logloss: 0.00224233	valid_1's binary_logloss: 0.216391
[400]	training's binary_logloss: 0.000393244	valid_1's binary_logloss: 0.219121
Early stopping, best iteration is:
[314]	training's binary_logloss: 0.00173829	valid_1's binary_logloss: 0.214377
Validation ROC-AUC score : 0.9725
****************************************************************************************************
Fold : 3
Training until validation scores don't improve for 100 rounds
[100]	training's binary_logloss: 0.0704351	valid_1's binary_logloss: 0.272947
[200]	training's binary_logloss: 0.0128042	valid_1's binary_logloss: 0.239922
[300]	training's binary_logloss: 0.00240497	valid_1's binary_logloss: 0.227304
[400]	training's binary_logloss: 0.000432678	valid_1's binary_logloss: 0.224824
Early stopping, best iteration is:
[392]	training's binary_logloss: 0.000494855	valid_1's binary_logloss: 0.223912
Validation ROC-AUC score : 0.9713
****************************************************************************************************
Fold : 4
Training until validation scores don't improve for 100 rounds
[100]	training's binary_logloss: 0.0673661	valid_1's binary_logloss: 0.284575
[200]	training's binary_logloss: 0.0119856	valid_1's binary_logloss: 0.246237
[300]	training's binary_logloss: 0.00214653	valid_1's binary_logloss: 0.242272
Early stopping, best iteration is:
[251]	training's binary_logloss: 0.00489218	valid_1's binary_logloss: 0.240168
Validation ROC-AUC score : 0.9651
****************************************************************************************************
Fold : 5
Training until validation scores don't improve for 100 rounds
[100]	training's binary_logloss: 0.0773569	valid_1's binary_logloss: 0.296821
[200]	training's binary_logloss: 0.0135572	valid_1's binary_logloss: 0.250549
[300]	training's binary_logloss: 0.00240434	valid_1's binary_logloss: 0.240423
Early stopping, best iteration is:
[238]	training's binary_logloss: 0.0068639	valid_1's binary_logloss: 0.239155
Validation ROC-AUC score : 0.9658
****************************************************************************************************
All fold average score : 0.9676

各 fold での score が確認できた。models には、各 fold で作成されたモデルが保管されているので、それぞれを呼び出して、テストデータの推論や特徴量の重要度の確認を行う。

推論

各モデルで、最初に分割しておいた test データに対する推論を行う。各モデルでそれぞれ予測して、結果を平均するだけなので、実際には、上記の学習ループの中に組み込むのが効率的。

In [150]:
# test データと同じ長さですべての要素が 0 の配列を用意
preds = np.zeros(len(X_test))

# 各モデルで推論
for model in models:
    pred = model.predict(X_test, num_iteration=model.best_iteration)
    preds += pred / len(models)
    
# スコア
print(f"Test data evaluation socre : {roc_auc_score(y_test, preds):.4f}")
Test data evaluation socre : 0.9664

特徴量重要度

各 model には、学習時の目的変数に対する特徴量の重要度が保持されている。上記の推論時と同様に、各モデルの特徴量重要度をまとめて平均し、重要度の高い順に上位50個だけ並べて、可視化する。
ここで、今回用いたデータセットを作成時に、make_classification の引数 n_informative を 10 と設定していたので、すべての特徴量 300個のうち 10個が突出して重要な特徴量として可視化されるはずである。

In [160]:
# 特徴量重要度を保管する dataframe を用意
feature_importances = pd.DataFrame()

for fold, model in enumerate(models):

    tmp = pd.DataFrame()
    tmp['feature'] = model.feature_name()
    tmp['importance'] = model.feature_importance(importance_type='gain')
    tmp['fold'] = fold

    feature_importances = feature_importances.append(tmp)

# 各特徴量で集約して、重要度の平均を算出。上位50個だけ抜き出す
order = list(feature_importances.groupby("feature")["importance"].mean().sort_values(ascending=False).index)[:50]

# 可視化
plt.figure(figsize=(10, 10))
sns.barplot(x='importance', y='feature', data=feature_importances, order=order)
plt.title('LGBM importance')
plt.tight_layout()
plt.show()

グラフから、モデルは重要な 10個の特徴量をうまく用いることができていたことがわかる。