kaggleの雑記

【超雑記】NFL Big Data Bowl にlightgbmで学習させているコードを読んでみる

記事概要:lightgbmの学習コードを読んでみる

https://www.kaggle.com/enzoamp/nfl-lightgbm

このNotebookのコードを解読していきます。コードの作成者はLorenzo Ampilさんです。

私はlightgbmを1回使ったことのある程度です。

本記事はメモ書き程度なので、あまりご参考になるかわかりませんが、NFLコンペにて、とある手法の日本語訳程度に思っていただければと思います。(かなりオーソドックスな使い方しかしていないかもしれませんが)

実際のコードを読んでいく

訓練データを目的変数とデータに分割

train_basetable = create_features(train, False)


X = train_basetable.copy()#.head(5000)
yards = X.Yards

create_featuresは特徴量生成関数で、その内容は

kaggleのコンペから方向と距離についての特徴量生成を学ぶ特徴量生成のコードを読み解く、その1 https://www.kaggle.com/enzoamp/nfl-lightgbm こ...

にて全6記事でまとめています。

特徴量を生成してできDataFrame.shapeは

(23171, 37)

です。

目的変数となるの入れもを用意する カラムのリストを作成する

y = np.zeros((yards.shape[0], 199))
for idx, target in enumerate(list(yards)):
    y[idx][99 + target] = 1
X.drop(['GameId','PlayId','Yards'], axis=1, inplace=True)
X_columns = X.columns.values.tolist()

y の作成方法はonehot encodingに近いものだろうか?199個のカラムを用意して、yardsの相当する位置に1を入れている。ただyardsは最大99、最小で−14なので、199の幅は必ずしも必要なのだろうか?ただ、最終的な提出物は、このYardsになる確率を0~1で表現したものになりそう。

Xを標準化

scaler = StandardScaler()
X = scaler.fit_transform(X)
X = pd.DataFrame(X, columns=X_columns)

カテゴリ変数を定義する

# Categorical variables if <= 50 unique values
# Warning: These have to be lists for lgbm training to work
cat_feats = X.columns[(X.nunique() <= 50)].values.tolist()
num_feats = X.columns[(X.nunique() > 50)].values.tolist()

ユニークな値が50個以下か50個より大きいかで、カラムをリストとして格納している?

あとでこのように使っている。

    trn_data = lgb.Dataset(X_train, label=y_train, categorical_feature=cat_feats)
    val_data = lgb.Dataset(X_val, label=y_val, categorical_feature=cat_feats)

ユニークな値が50以下のものをカテゴリ変数、それ以上を数値の特徴量としているようだ。lightgbmのデータセットを明示するときに、カテゴリ変数はどれか明示するような引数があるけれども、そのために利用すると予想される。

lightgbmのスーパーパラメータを決める

metric = "multi_logloss"
param = {'num_leaves': 50, #Original 50
         'min_data_in_leaf': 30, #Original 30
         'objective':'multiclass',
         'num_class': 199, # 199 possible places
         'max_depth': -1,
         'learning_rate': 0.01,
         "min_child_samples": 20,
         "boosting": "gbdt",
         "feature_fraction": 0.7, #0.9
         "bagging_freq": 1,
         "bagging_fraction": 0.9,
         "bagging_seed": 11,
         "metric": metric,
         "lambda_l1": 0.1,
         "verbosity": -1,
         "seed":1234}

それぞれのパラメータの意味を知りたければ、公式ドキュメントが一番。

交差検証するときの結果の入れ物を用意する

models = []
kf = KFold(n_splits=3, random_state=42)
score = []
feature_importance_df = pd.DataFrame()
best_validation_scores = []

交差検証は理屈は知っているものの、今だに実装したことはないので、もみじあめさんのこちらの記事を参考にさせていただきました。

KFoldは何分割にするかを引数にとります。今回は3分割で交差検証を行う設定でした。

yを1が入っているindexの列に直す

y = np.argmax(y, axis=1)

交差検証を用いたlightgmbの学習

for i, (tdx, vdx) in enumerate(kf.split(X, y)):
    print(f'Fold : {i}')
    X_train, X_val, y_train, y_val = X.iloc[tdx], X.iloc[vdx], y[tdx], y[vdx]
    trn_data = lgb.Dataset(X_train, label=y_train, categorical_feature=cat_feats)
    val_data = lgb.Dataset(X_val, label=y_val, categorical_feature=cat_feats)
    num_round = 10000
    model = lgb.train(param, trn_data, num_round, valid_sets = [trn_data, val_data], verbose_eval=100, early_stopping_rounds = 200)
    score_ = crps(np.expand_dims(y_val, axis=1), model.predict(X_val, num_iteration=model.best_iteration))
    print(score_)
    fold_importance_df = pd.DataFrame()
    fold_importance_df["feature"] = X_columns
    fold_importance_df["importance"] = model.feature_importance()
    fold_importance_df["fold"] = i + 1
    feature_importance_df = pd.concat([feature_importance_df, fold_importance_df], axis=0)
    best_validation_scores.append(model.best_score['valid_1'][metric])
    score.append(score_)
    models.append(model)
print(np.mean(score))

1~4行目

sklearnのkFoldを用いて、3分割することを明示していたので、for分は3回周り、3回の交差検証を行います。

訓練データと検証データを分割し、lightgbmのデータセットとします。

lightgbm.Datasetsのドキュメント

5, 6行目

実際の学習を進めるコードです。

lightgbm.trainのドキュメントから学ぶことが筋ですが、これに近いことはこちらの記事の「LightGBM モデル訓練」の章に書いてあります。

7行目

crpsという関数によってX_valを予測した結果と実際のy_valという答えに対してのどのくらい一致しているかをスコアとして返しています。

def crps(y_true, y_pred):
    y_true = np.clip(np.cumsum(y_true, axis=1), 0, 1)
    y_pred = np.clip(np.cumsum(y_pred, axis=1), 0, 1)
    return ((y_true - y_pred) ** 2).sum(axis=1).sum(axis=0) / (199 * y_true.shape[0]) 

9~14行目

lightgbm.inportanceを使うと学習済みのモデルから、どの特徴量が重要か(寄与が大きいか)を算出することができる。それをDataFrameの形式にしてからfeature_importance_dfというDataFrameに追加していっている。

15行目

この辺りまでくるとかなり理解が追いつかなくなってきた。。lgb.trainのときにX_trainに対する結果を保持していてい、自身のscoreを持ち合わせていると言うことだろうか?

8行目でprintしたscoreと何が違うのだろうか?

おわりに

一旦このNotebookをベースにして、自分のNotebook第一弾ができました。

https://www.kaggle.com/hirayukis/my-original-nfl?scriptVersionId=23975057

これに改良を加えてなんとかスコアをあげられるように頑張ってみたいと思います。

ABOUT ME
hirayuki
今年で社会人3年目になります。 日々体当たりで仕事を覚えています。 テーマはIT・教育です。 少しでも技術に親しんでもらえるよう、noteで4コマ漫画も書いています。 https://note.mu/hirayuki