6th Place Solution

はじめに

こんにちは、なぽなぽと申します。 一年ほど前から機械学習の勉強を始め、最近データ分析コンペを始めました。
説明が至らない部分があるかもしれませんが、大目に見ていただけると幸いです。
スプラトゥーンに関しては1のほうはかなりやりこんでましたが、2はあまりプレイしてません。

追加した外部データ

以下のトピックに記載されているデータを使用させて頂きました。

モデル

LightGBMのみ。
10分割のCVでそれぞれ予測し、平均した結果を1, 0で割り振るようにしました。
(NNなどを含めて、Stackingなどもやりたかったですが、時間の都合で間に合わず...)

特徴量

自分で新たに作成した特徴量としてはないです。
カテゴリ変数は以下のようにしました。

  • weapon, specialは使用頻度の高い順に数値を割り振る。
  • rank, category2はそのままカテゴリとして扱う。
  • 欠損値は-999で置換。

それ以外で、一部の特徴量(mode, stageなど)は削除しました。

データ拡張

A1~A4とB1~B4を入れ替えて、ラベルを反転させたデータを追加しました。
上記のデータをtrainとvalidに分けて、trainだけデータを水増しました。
水増しには以下のような関数で実装しました。(N=17で実行)

import pandas as pd
import itertools
import random

def expand_dataset(N: int, df: pd.DataFrame, random_state=42):
    """データを拡張する"""

    if N == 0:
        return df

    # 実際には列名のリストが格納されている
    a1 = ['A1-weapon', 'A1-rank', 'A1-level', …]
    a2 = []
    a3 = []
    a4 = []
    b1 = []
    b2 = []
    b3 = []
    b4 = []

    train = df.copy(deep=True)
    train_temp = df.copy(deep=True)
    all_train = df.copy(deep=True)

    a_team_list = [a1, a2, a3, a4]
    b_team_list = [b1, b2, b3, b4]

    a_team_combination = list(itertools.permutations(a_team_list))
    b_team_combination = list(itertools.permutations(b_team_list))

    a_and_b_combination = list(itertools.product(a_team_combination, b_team_combination))


    if N > len(a_and_b_combination):
        pattern_list = a_and_b_combination
    else:
        random.seed(random_state)
        pattern_list = random.sample(a_and_b_combination, N)

    for pattern in pattern_list:
        train[pattern[0][0]] = train_temp[a1]
        train[pattern[0][1]] = train_temp[a2]
        train[pattern[0][2]] = train_temp[a3]
        train[pattern[0][3]] = train_temp[a4]
        train[pattern[1][0]] = train_temp[b1]
        train[pattern[1][1]] = train_temp[b2]
        train[pattern[1][2]] = train_temp[b3]
        train[pattern[1][3]] = train_temp[b4]

        all_train = pd.concat([all_train, train], axis=0)
    return all_train

ハイパーパラメータチューニング

optunaを使ってみましたが、実行するたびにパラメータの数値が右往左往するので、最終的には自分で調整しました。
以下が一番良かったLightGBMのモデルのハイパーパラメータです。

# LightGBMのハイパーパラメータ
params = {
    'objective': 'binary',
    'metric': 'binary_logloss',
    'early_stopping_rounds': 100,
    'num_iterations': 50000, 
    'lambda_l1': 0.004,
    'lambda_l2': 0.0000006, 
    'num_leaves': 24,
    'feature_fraction': 0.5,
    'bagging_fraction': 0.724624897380167,
    'bagging_freq': 2,
    'min_child_samples': 5,
    'learning_rate': 0.1
}

スコア

  • CV : 0.560052
  • Private : 0.560339
  • Public : 0.566549

まとめ

ほかにチームの統計量などを追加してみましたが、見事に過学習してしまいました。
統計量でなく個人の特徴量を増やしていけば、スコア向上が見込めたかな~とか思っています。
あとはStackingなど、他のモデルを合わせてのアンサンブル学習なども試したかったですね...

Aws4 request&x amz signedheaders=host&x amz signature=0beadb5650af3807b1b4eba42dea0fa0179348a87f20dbc9a165fcc0592386ed
nash

今回データ拡張はかなり有効そうですね
私はvalidも水増しして過学習しました...
勉強になります!

Aws4 request&x amz signedheaders=host&x amz signature=613b4922f5cd65bd38a639620f231f0ffb529b40dfd78f1c4c9f3cce78d0ea92
NapoNapo

やはりvalidはリークを避けるために、testと同じような扱いをしないとダメみたいですね。 私も最初はvalidごと水増しした際は過学習したので、上記のような方法に変えました。

Aws4 request&x amz signedheaders=host&x amz signature=45b70ce7d11fe83f5dbba862c9c0752d5c3d73c8b38ea5de66c0ea22656e89cd
cha_kabu

解法投稿ありがとうございます!新たに特徴を加えていないということで、とてもショッキングな解法です…山ほど加えた特徴量たちは何だったのか…

非常に初歩的な内容で申し訳ないのですが、質問させてください。 「validごと水増しすると過学習した」とのことですが、自分の理解では過学習するのはtrainに対してで、validに対してはリークはしても過学習はしないと認識しているのですが、こちらは特徴量作成やパラメータ調整においてリークしているvalidに過剰適合するように調整してしまうという意味合いでしょうか? それともそもそもの認識が根本的に違いますでしょうか…

Aws4 request&x amz signedheaders=host&x amz signature=613b4922f5cd65bd38a639620f231f0ffb529b40dfd78f1c4c9f3cce78d0ea92
NapoNapo

私も特徴量を追加してモデルを作ってみたりもしたんですが、結局この方法を超えるモデルは作成できませんでした。下手に特徴量を増やすよりも、データ数が多い方がより精度の高い予測ができるという、新たな知見も得られたので今回のコンペは勉強になりました。

過学習に関してですが、今回はearly stoppingを使用しているので、validごと水増しした場合にはちょうどよいiterationで止まらなかったことが原因かと思われます。(loss curveなども詳しくは見ていないので、あくまで憶測ですが...)
なので認識としてはtrainに対して過剰適合しているという認識で合ってると思いますね。validがリークしてるので、trainに過剰適合してるとも知らずに学習を続けてたっていうイメージです。

Favicon
new user
コメントするには 新規登録 もしくは ログイン が必要です。