Tak's Notebook

Kaggle, Machine Learning, Engineering

Kaggle Data Science Bowl 2019 上位解法まとめ

f:id:takaishikawa42:20200126205045p:plain
https://www.kaggle.com/c/data-science-bowl-2019/

Data Science Bowl 2019

  • 子供向けの教育系ゲームアプリの行動ログから session_id の Assessment の正答率を0~3の4段階のどれになるかを予測するタスク
  • Public で使われるテストデータの評価される session_id のユニーク数が1000しかなく、CV と PublicLB が安定しにくかった
  • 評価指標もQWKと不安定になりやすいものだったので結果的に比較的大きなShakeが起きた


所感

上位陣に共通していたポイント

  • Validation, Feature Selection に注力しているチームが比較的多かった印象がある。
  • ランダム性やPublic/Privateの乖離のリスクから、ランダムに truncated した Validation データセットを結構な回数回して平均を取ることで安定したCVスコアを求めていた

異なる点

  • 閾値は OptimizeRounder を使うだけのチームがある一方、閾値の決定に工夫をこらしているチームがあった
  • 目的変数の決め方も比較的バリエーションが多かった印象がある
  • ログデータを系列として捉えてそれをうまくモデルに落とし込めたチームは少なかったが、一定数いた


1st Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127469

Summary

  • 5 fold の Lightgbm のシングルモデルを Seed Averaging
  • Private QWK: 0.568, Public QWK: 0.563
  • CV Weighted QWK: 0.591, CV Weighted RMSE: 1.009

Validation

初期にLBスコアは不安定でローカルCVと相関が低いことが分かったので、ローカルCVのみにフォーカスし、以下の2つのバリデーションを試した。

  1. GroupK CV

  2. installation_id ごとの 5fold の GroupKFold を5回回した。各回のGroupKFoldでSeedと列順を変えた。それでもQWKはローカルCVで不安定だったのでRMSEの重みに注意してQWKは無視した。

  3. Nested CV

  4. 上記のGroupKFoldのCVはうまくいったが、信頼度が低かったので学習データで Train-Test の分割をシミュレーションした。つまり、全履歴を持った1400ユーザーをランダムに選び学習データとし、ランダムに履歴を切り捨てた2200ユーザーをテストデータとした。これを50~100回繰り返して平均値をバリデーションスコアとした。

Feature

特徴量生成に特に時間を割いた。20000個の特徴量を作り、Null Importance で上位500個の特徴量を選択した。

  1. true attempt の割合・correct true の割合・correct feaedback の割合の統計量(mean/sum/last/std/max/slope)。同じAssessmentや似たGameに基づいた統計量の重要度が高かった。(各々のGameを似たAssessmentに紐付けた)
  2. 異なるTimespanで履歴データから特徴量を抽出した。(1) 全履歴データ (2) 直近5/12/48時間 (3) 直近のAssessmentから現在のAssessmentまで
  3. イベントの間隔の特徴量(次のイベントのタイムスタンプから現在のイベントのタイムスタンプ)。event_id/event_code で groupby したイベントの間隔の統計量(mean/last)。いくつかの特徴量が高い重要度を示した。
  4. ビデオスキップの発生頻度。Clipイベントの間隔、ホストによって提供されたClipの長さ。
  5. Event Data 特徴量。event_data × event_id/event_code の全ての数値特徴の統計量(mean/sum/last)。例)event_code2030_misses_mean

Feature Selection

  • 重複列の削除
  • Adversarial Validation で Adversarial AUC が0.5になるようにして、リークとコードエラーをなくした
  • Null Importance で上位500個の特徴量を選択

Model

  1. Data Augmentation: 全データで学習させた(学習データ + テストで利用可能なデータ)
  2. Loss: RMSEを学習に用いて、Validation のために重み付きの RMSE Loss を用いた
  3. Threshold: QWKの最適化に Opitmizer Rounder を用いた
  4. Ensemble: シンプルなBlendingを試した(0.8 * lightgbm + 0.2 * catboost)。CVスコアが改善しなかったのでFinal Submissionに選択しなかった。


2nd Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127388

Summary

  • Public 0.563 / Private 0.563

Validation

  • Validation セットには 1ユーザーあたり1サンプルをリサンプリングした
  • StratifiedGroupKFold, 5-fold

Feature

  • Word2Vec features of title series
    • 予測対象の Assessment までの title の系列を文書と見なして、Word2Vec で処理して得られたベクトルの統計量(mean/std/max/min)を計算した
  • Historical feature
    • 全体および World ごとに group by して (session, world, type, title, event_id, event_code) のカウントを historical data として取得した
    • (event_round, game_time, event_count) の count, mean, max を取得した
  • Decayed historical feature
    • 上記の hisotrical data に関して session 以外のものについて各セッションごとに半減するような時間減衰をかけたものを取得した
  • Density of historical feature
    • Density = (count) / (elapsed days from a first activated day)
  • Lagged Assessment
    • num_correct, num_incorrect, accuracy, accuracy_group の様々な統計量
    • 過去の Assessment との hours の差分
  • Meta Features
    • 「ある game_session がどれだけ後の assessment の結果に影響を与えたか」を表すために、"meta target features" を各 assessment title に対して生成した。 KFold の oof の平均値を用いた

f:id:takaishikawa42:20200126204806p:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127388

Feature Selection

  • 重複列の削除
  • 高い相関係数(0.99以上)を持つ列の削除
  • Null Importance で上位300個の特徴量を最終的に選択した

Model

  • Lightgbm, Catboost, NN の Random Seed Averaging (5 random seed)
  • Ensemble(0.5 * LGB + 0.2 * CB + 0.3 * NN)
  • CV の QWK を最適化する Threshold を設定した

Others


3rd Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127891

Summary

  • 特徴量生成よりもデータに適したNNモデルのアーキテクチャを見つけることに注力した。
  • Single TRANSFORMER を用いた

Validation

  • 言及なし

Feature

  • installation_id の系列をそのまま使うには長すぎるので game_session ごとの集計した。以下コード例。
df = train_df
event_code = pd.crosstab(df['game_session'], df['event_code'])
event_id = pd.crosstab(df['game_session'], df['event_id'])
...
agged_df = pd.concat([event_code, event_id, game_accuracy, max_round])
session_df = df.drop_duplicates('game_session', keep='last').reset_index(drop=True)
session_df = session_df.merge(agged_df, how='left', on='game_session')
  • LSTM や TRANSFORMER は単語(あるいは文章)の0列を入力データとして受けつけるので、game_session (あるいは installtion_id) の系列を入力データとして用いた

Feature Selection

  • 言及なし

Model

  • position-related な情報(position embedding)を用いると手元のCVスコアが落ちた
    • BERT, ALBERT, GPT2 のような poision embedding を用いたモデルは良くなかったので、poisiton embedding がない TRANSFORMER を用いた
  • モデルの構造は以下図の通り

f:id:takaishikawa42:20200129083338j:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127891

  • 重要なポイントは game_session からどのように embedding を作り出すかという点だった。
    • Categorical columns(event_code, title, word, etc.)は別々に embedding した。それから、embedding を concatenete することで categorical_vector を獲得した。つづく nn.linear 層は categorical_vector の次元削減のために用いられた。
    • また Continuous columns は linear 層を使って直接 embedding された。また、標準化のために np.log1p を用いた
self.categorical_proj = nn.Sequential(
    nn.Linear(cfg.emb_size*num_categorical_columns, cfg.hidden_size//2),
    nn.LayerNorm(cfg.hidden_size//2),
)
self.continuous_emb = nn.Sequential(                
    nn.Linear(num_continuous_columns, cfg.hidden_size//2),
    nn.LayerNorm(cfg.hidden_size//2),
)
  • ハイパーパラメータは以下の通り

    • optimizer: AdamW
    • schedular: WarmupLinearSchedule
    • learning_rate: 1e-04
    • dropout: 0.2
    • number of layers : 2
    • embedding_size: 100
    • hidden_size: 500
  • Loss Function は以下の通り

    • ディスカッションで言及したとおり、nun_incorrect の値の上限を2に制限することで、正答・誤答の数と accuracy_group の組み合わせが一意になる。これらを用いて accuracy_group, num_correct, num_incorrect を目的変数にしてモデルを学習させた予測結果から、accuracy_group を算出した。
# num_incorrect[num_incorrect > 2 ] = 2 # Constrained not to exceed 2.
new_accuracy_group = 3 * num_correct - num_incorrect

Others

  • Game の event_data から Assessment と同じように正答・誤答を求めて、事前学習ステップとして3エポックまで元の学習データに追加して学習させた
  • game_session 数が30以上ある installation_id の場合、最大50~60%までランダムに古いevent_dataを削除した


4th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127210 https://www.kaggle.com/c/data-science-bowl-2019/discussion/127312 https://www.kaggle.com/sergeifironov/bowl-stabilize-coefs-cntrs-all5

Summary

  • 最終サブミットは、一つはNNのブレンドで、もう一つは3段階のStacking(Public/Privateが、前者は0.572/0.561, 後者は0.566/0.560)

Validation

  • テストデータは学習データと同じ処理でラベル付け可能なので学習データとして利用できる
  • データ量が少なかったので、高スコアを出すことよりもスコアを安定させることが重要だった。
    • 列の順序を変えるだけでスコアが悪化した場合、何か間違ったことをしていたと言えるだろう。
  • Discussion にも色々書いたが、コンペ終盤でPublic LBと相関をもたせることを諦めた

Feature

  • events の系列(title + event_code + correctflag + incorrectflag)に対して TfIdf を行った
    • その後 installation_id の hisotrical data を tokens の系列と見なして、それらに対して TfIdf を行った(?)
  • clip や別の title の中には Assessment の正答率の予測に対して大変重要なものがあった。順番はおそらくそれほど重要でなかったが、RNNアーキテクチャがユーザー履歴内でのそれらの重要性を十分扱うことができた

Model

  • Tfidf features + RNN on title sequence (last 64) with some additional features:
  • Tree base models (Lightgbm, Xgboost, Catboost)
  • Stack
    • 0 level: NN, Lightgbm, Catboost
    • 1 level: MLP, Lightgbm
    • 2 level: Ridge

f:id:takaishikawa42:20200126212242p:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127312

Others

  • うまくいかなかったこととして以下を挙げている
  • event_id, title, title+accuracy_group などの予測に対する(?) Transfomers, GPT-2, BERT
  • Grapgh NN
  • events の系列に対する Transformer


7th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127213

Summary

  • Feature Engineering に注力した。最終的に51個の特徴量を用いた(151個から切り捨てた)
    • 最も効いた特徴量は各 Assessment ごとの分布だったが、これはどの人も使っていた
  • "Trick" としてはテストセットの Assessment を学習データとして用いたことである
    • データ量が少なかったので学習データ量を増やすことを模索した
  • Score
    • Truncated CV: 0.575
    • Private LB: 0.559
    • Public LB: 0.559

Validation

  • Truncate CV を見ていた
    • テストセットの構造を反映するために各 installation_id ごとにランダムに一つの Assessment を Validation として選択した

Feature

  • Francois Chollet の On the measure of intelligence にインスパイアされて、以下のような特徴量のグループを作った
    • 経験:どういうアクティビティに時間を使ったか
    • 正答率:どういう正答率で来たか
    • スキルの獲得過程:どれほど早く学習してきたか

Feature Selection

  • 150の特徴量まで減らす際、一つずつ個別に特徴量を落としたときにCVスコアを計算して削除した
  • 100の特徴量まで晴らす際、QWKスコアが0.0001以下の改善しか寄与しなかった特徴量をノイズと見なして削除した
  • その後、再度CVを計算してスコアを僅かにしか改善しない特徴量を削除した。

Model

  • 最終的なモデルはアンサンブルだった(0.3 LGB, 0.3 CATB, 0.4 NN)
  • 全モデルとも 20 Kfold で回した。NNは追加で3 seedsの平均を取った


8th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127285 https://www.kaggle.com/zgzjnbzl/dsb-mlp-216-feature

Summary

  • 最終サブミットは8時間前に提出して6時間実行にかかったので、締切の2時間前に何とか提出できたので諦めなくてよかった

Validation

  • 5 GroupKFold

Feature

  • numericな特徴量に対してログ変換や、Stdを取ったりした
  • 欠損値は0埋めで、欠損フラグを新たな特徴量とした
  • 主な特徴量は以下
    • type, title, event, event_code の Count Encoding
    • title_accuracy, title_accuracy_lasttime
    • title duration の max/mean/std(title duration を1000でクリッピングした), ミスした title duration の mean/std
    • ミスしたtitleラウンド数の平均をラウンドの時間で割ったもの(accuracy vs. speed の情報を反映)
    • title の nunique
    • world ごとの title の nunique
    • session・eventの合計数
    • gameの試行数をgameの全体数で割った値

Feature Selection

  • Null Importance で1100程度の特徴量を216まで減らした
  • その216の特徴量を100までに減らした方がCVスコアが良かったが、Validation Lossもは大きくなり過学習を引き起こした。加えて PublicLB のスコアも良かったので最終サブミットは両方とも216特徴量を使ったものにした

Model

  • シンプルな3層のMLP(256-256-256 + BN, Dropout=0.3, LeakyReLU)
    • 異なるseed、微妙に異なるepochで全データを使って9つのモデルを学習した
    • OptimizerはAdam, Batchsizeは128, LRは0.0003(cyclic decay)
  • 9つのモデル各々で2つのターゲットの18個の結果の平均値を出して、それをOptimizerに適用した
    • ランダムに閾値の初期値を決めて25回繰り返してローカルCVが最良の閾値を選択した


9th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127221 https://twitter.com/Trust_CV/status/1220537156692627462

Summary

  • コンペ終了9日前に800th->50th、前日に80th->23thに順位を上げてきた
  • 解法はシンプルで、特徴量生成(ほとんど集計値)・多様なモデルとスタッキング・ランダムサーチで閾値をチューニングをした

Validation

  • 言及なし

Feature

  • 多くのカーネルで用いられていた get_data 関数は特徴量を作ったり管理したりするのが難しかったので以下のようにして作った
train_gs_assess_dict = {}
for ins_id, user_sample in tqdm(train.groupby('installation_id')):
    assess_count = 0
    for gs, session in user_sample.groupby('game_session', sort=False):
        if session['type'].iloc[0] == 'Assessment':
            assess_count += 1
        train_gs_assess_dict[gs] = assess_count
train['assess_count'] = train['game_session'].map(train_gs_assess_dict)
  • これを用いて assessment 以前のユーザーアクティビティのサブセットのための集計系の特徴量を作った
  • 実行時間はかかるが、こちらの方が特徴量の実装と管理が容易である

Feature Selection

  • 言及なし

Model

  • first level は8つのモデルを用いた
    • LightGBM は type / target / eval metrics などを変えて7種類 + NN
    • accuracy group との相関係数・ケンドールの順位相関などを見た?
  • second level は accuracy group を目的変数として Ridge Regression を用いた

    • accuracy group と予測値の相関係数が最良のモデルの重みが0になってしまったが、結果的にこのスタッキングが効いた
  • 閾値の決定はこのコンペではかなり重要だった。最初は OptimizeRounder 関数を用いていて、これは初期値に大きく依存することが分かったので閾値をランダムサーチで決めることにした。このアプローチのおかげで800位ほど順位を上げた。

  • また adversial validation の結果 public と private のデータセットはとても似ていると考え、ランダムに100回選んだ truncated validation の QWK の平均値を最大化する閾値を選択した。


10th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127332

Summary

  • 1年前のMalwareコンペで2位 -> 1500位までのデカいshake downの経験からバリデーション戦略やPublic/Private分析に努力してきた結果、初めて金メダルを獲得できた

Validation

  • Stratified Group KFold (10-fold)
  • バリデーションスコアは全て truncated validation で計算した
    • 各 installation_id ごとにランダムに一つの Assessment を Validation データセットにした
  • 各foldでは51個の truncated validaiton セットを使用した

    • 1つを early stopping に
    • 残りの50個を手元のCVスコアに(QWKの平均値を取った)
  • publicは1000個のinstallation_idしかなかったのでValidationとしては不適切な分布に鳴っていると考え、truncatedされた学習データの oof prediciton の平均値を1000回計算した。すると、パブリックデータセットは分布のレアケースである事がわかったので、Trust CV(LBを無視)して、CVでQWKが最良となる閾値を用いた。

  • 最終的な閾値は500のtruncated oof validationの平均値から OptimizedRounder を用いて [1.04, 1.76, 2.18] となった。(パブリックカーネルで行われていたような学習データの分布に合わせる閾値の決め方は分布が合ってないので過学習を招くと考えている)

f:id:takaishikawa42:20200129083436p:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127332

Feature

  • 3000~5000程度の特徴量を全部で作ったが、Magic Features はなかった(最終的には300個くらいだけ使った)
  • 良い特徴量は以下の通りだった
    • 標準化した Accuracy の特徴量
    • title ごとの特徴量
    • 関連した特徴量
      • event_code_4200_count, event_code_4700_count, last_accuracy, all_accuracy_mean

Feature Selection

  • truncated なバリデーションに対する特徴量の効果を評価するために、truncated な学習データの特徴重要度を用いた
  • 各foldで、50個の truncated なデータセットを作り、init_modelパラメータを用いて5回のイテレーションごとにデータセットを変えた結果から、なんとなく上位300個の特徴量を用いた(詳しくはpaoさん本人のツイート参考)

Model

  • Lightgbm を使った
    • feature fraction を0.8から1.0に変更したらCVが0.005改善した
    • title が target value に対して大きな効果があるので全てのツリーに assessment_title を使った方が良い結果になったのではないか
  • game_session ごとのモデル
    • Transformer とは別に、Lightgbmをgame_sesionごとに作った
    • 直接は使わなかったが、game ごとに良い特徴量を素早く見つけるのに役立った
  • 学習にテストデータセットを用いた
    • 改善に役立ったかどうか分からない

Others

  • うまくいかなかったこと
    • MLP Regressor
    • NN EventCode Transformer
    • Word2Vec Feature
    • 標準化した accuracy group をターゲットとする
    • titleごとの残差エラーの学習


14th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127221 https://www.kaggle.com/khahuras/bowl-2201-a?scriptVersionId=27403894

Summary

  • Public/Private で金圏にとどまることが出来た3チームのうちの1チームであった
  • Public-Private の相関が謎で、それがコンペを面白くしていた
  • 4人チームだったのでモデルや特徴量の多様性を意識した(コンペ最終盤でチームマージするとソリューションの統合がとても大変)

Validation

  • テストデータで利用可能なものを学習データに加えて学習させた

Feature

  • パブリックカーネルの特徴量に加えて、以下のような特徴量を加えた
    • session における good action に対する全アクションの比率
    • ミスクリック/ミスドラッグ数に対する全アクション数(あるいはセッション時間)の比率
    • 前回のAssessmentからの特定のevent_codeの数
  • 各installation_idごとの予測対象と同じassessmentに関する統計量は効いた
    • ツリーモデルの収束を早め、Assesment titleの重要度を下げるのに約だった

Feature Selection

  • 言及なし

Model

  • 全AssessmentをTargetにしたモデル (Main Model) と、各Assessmentごとに別々のモデル (Separate Model) を作り結果を結合したものの2種類のモデルを作った
  • セパレートモデルをメインモデルに適切にブレンドするとスコアの上昇が確認され、test dataを学習データに利用する/しないのオプションと組み合わせることで、4つの異なる学習戦略を持つことが出来た

  • シンプルなOptimizerを利用してCVを最大化する閾値を見つけようとした。モデリングやアンサンブルに注力して、閾値探索には注意を払わなかった。

  • Assessmentの数を元に学習データに重みを与えて学習させた。CVは殆ど変わらなかったけどLBは上昇した。このアプローチには問題があり、0-prior-assessmentのサンプルサイズは学習データでは1/4、テストデータでは1/2だったので、それに対して単純に重みを与えることは難しかったので、結局えいやで思いを決めた。 [0-prior, 1-prior, 2-prior, 3-prior, 4-prior, and more-than-4-prior]: [1.65 , 1.09, 0.87, 0.77, 0.57, 0.47]

  • アンサンブルについて。メインモデルの結果とセパレートモデルの結果を結合する良い方法を見つけた。3つのシンプルな分類器を学習させた。すなわち、0/1, 1/2, 2/3 のクラスに分類する分類器を学習させ、図のような方法でmodel1とmodel2の2つの予測値をブレンディングした。(上リンク先参考)

  • スタッキングについて。スタッキングはCV, LBの両方に対して有効だった。結果的に、一つはブレンディングモデル、もう一つはスタッキングモデルを最終サブミットに採用した。スタッキングでは4つのstackerの平均値と、extra-tree regressorの2つのアプローチを試した。後者のほうがCVでも、結果的にPrivateLBでもより良い性能だったが選ばず、ブレンディングモデルとスタッキングモデルのブレンディングを選んだ。


15th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127500 https://www.kaggle.com/hidehisaarai1213/dsb2019-nn-ovr-reduce-90-val-60-percentile https://www.kaggle.com/c/data-science-bowl-2019/discussion/125001#726894


16th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127331


17th Place Solution

リンク

https://twitter.com/sakata_ryuji/status/1220151217650749441?s=20 https://twitter.com/sakata_ryuji/status/1220171672419885057?s=20 https://twitter.com/sakata_ryuji/status/1220241711542046722?s=20