まだタイトルない

アウトプット用です

【Kaggle挑戦記】シミュレーションコンペ Lux AIに参加してきました。【#12】

f:id:teyoblog:20211212220002j:plain:w500

こんにちは。

2021年8月16日から2021年12月6日まで開催されていた Lux AI Challengeに参加してきました。

結果についてはこれからの2週間の評価期間を経て確定となりますが、初めてのシミュレーションコンペということでいけて銅メダルか?というラインなので今回は解法振り返りというより取り組みを振り返るようなポエムを書いていこうと思います。

参加した動機

まずはこちらの実際のゲームの動画?を見てみてください。

かわいいですね。

なので参加することにしました。

概要

  • 昼30ターン、夜10ターンを9サイクル360ターンが試合時間
  • 最後に街をたくさん残していたほうが勝ち
  • ワーカーは資源を使って街を作る
  • 街は燃料を保持し、夜のターンに燃料を消費する
    • 燃料が足りなければ街もワーカーも消滅する

こんな感じです。実際にルールベースなコードを書いたりする際にはクールタイムなど詳細なルールがありますので詳しく理解する必要があります。

ルールベースで取り組む

このコンペは Halite コンペに似ているということがDiscussionにあったためどんなソリューションがかったの軽く調べてみました。

この記事を見て上位陣のagentがなんターンくらいで研究レベルを50/200にしているかを調べたりしてみましたが特に収穫はありませんでした。

ルールベースでも戦えることがわかったことと、公開ノートにルールベースのノートがあったのでまずはコレで勉強。

https://www.kaggle.com/huikang/lux-ai-working-title-bot

解読にとても時間がかかったのですが初めてガッツリとオブジェクト指向を活用したコードを読んだのでとても勉強になりました。

頭と実力が足りず特に新しいルールを実装する事はできませんでした。(というか本当によくできた神ノートすぎて・・・

石炭やウランが研究レベルが50/200にってからしか採集市に行かないようになってたのでそこをフライングして採集に行くようにしたもののうまく行かず・・・

本を買ってモチベーションを上げる

今回はこの3冊を買って読みました。どの本もこのコンペに落とし込むには似たような公開ノートもなく気合が必要に感じましたが、2冊目のCNNを使ったタスクに落とし込む発送とかは後述する模倣学習の理解に助かったように思います。後単純日本は面白かった

ちなみにこの本ゲーム好きな人にはおすすめです。(アフィちゃうで

圧力コンペに浮気

短期間のテーブルデータコンペが来ていたのでそっちに1ヶ月位浮気してました。
並行でいけるかと思ったのですが全く並行して取り組めませんでした・・・

Lux AI再開

圧力に行く前に投稿しててルールベースのagentがかなり低い位置に居たので他の手法を試すことにしました。

Reinforcement Learning(強化学習)とImitation Learning(模倣学習)があったのですが、今回は模倣学習の考え方がとっつきやすかったのでそっちをやることにしました

こちらのノートLux AI with Imitation Learning を参考にしました。

マップ状況を 20ch,h32,w32で表現して入力データに、とった行動をラベルにして学習する仕組みです。

学習データ増やしてLrスケジュールするともっと良くなるよっていうノートやアンサンブルのノートも公開されたのでとりあえずそれをサブしたら調子がいいと銀圏付近まで伸びることを観測しました。

しかしみんなそれを投稿しちゃうのでだんだん順位は下がります...

自分なりのアレンジをする

まず単純に学習データはLB1位のチームが対戦するたびに増えるのでどんどん学習データを増やすことで公開されてるノートより上に立てることは容易に想像できるのでスクレイピングして収集します。

その他模倣学習のモデルをゴニョゴニョて上を目指しました

  • alphazeroっぽいアーキテクチャを作る
    • resblock19層、filter数128はおそすぎてタイム・アウトしちゃいました。
  • poolingにGeMpoolingを使ってみる
    • loss自体は改善されました
  • 公開ノートのresblockのレイヤー数を増やす
  • CVを同エピソードでtrain valid別れないようにする
  • 序盤だけルールベースにする
  • アンサンブル
    • ユニット数に応じてシングルモデルに切り替える
  • targetsizeを下げた学習(Discussion
    • 結局うまく推論させることができませんでした...

f:id:teyoblog:20211212220351p:plain

手元で学習させてたalpha風モデル(植えず茶色い方)はval scoreはいいのにLB弱すぎてずっと泣いてました

f:id:teyoblog:20211212220419p:plain

Agentが馬鹿な問題を深堀り

コンペ終盤3日くらいのところでagentが全く建築しないので本番想定でlogitを眺めることにしました

(おそすぎる・・・・)

oof
f:id:teyoblog:20211212220428p:plain

本番想定
f:id:teyoblog:20211212220436p:plain

桁が違いすぎる・・・

batchnormalizationがだめ?

学習時はvalidもbatch size大きいのに対して本番はbatch size1で推論してるからそこでノーマライズ効かなくておかしくなってる?

いいえ違いました

pytorch:BatchNorm1dでのaffine=True or False,track_running_stats=True or False の設定の違い

batch normalizationのtrack_running_statsはdefaultでTrueです。Trueだと推論時は学習サンプル集合の平均と分散を使用して,正規化。Falseだと学習時と同様に入力データの平均と分散を使用して正規化。とのことなので問題はないです

ではなぜ・・・・・

torch.jit.trace()がうまくできてなかった

このコンペは推論時間に制限があるためjit.traceして事前にコンパイルすることで推論速度の高速化を公開ノートがしていたので自分もそうしていました。

しかし、ローカルで保存したものをkaggleで読み込んでjit.traceしても正常な予測結果にならないことがわかりました。この問題はまだ解決していなくて、結果的にmodelのインスタンスを作成してweightを読み込んでjit.traceすることにしました。コレでagentがアホ問題は解決されました。コレが最終日前日夜のことでした笑

タイム・アウトしないようにアンサンブルして提出

後はアンサンブルを作って提出して終わりになりました。

www.kaggle.com

12/11現在最終日のサブが自己ベストまで来ているので粘ってよかったなと思います。

f:id:teyoblog:20211212220449p:plain

次はもうちょっと分かるコンペでマスター目指していきたいです!!!

【Kaggle】Ventilatorコンペ上位解法から学ぶ

f:id:teyoblog:20211205001615p:plain

こんにちは!

Lux AI何もわからなくて辛いてょです。まんまと見た目の可愛さに釣られました。

本記事はKaggle Advent Calendar 20215日目の記事です。タイトル通り先日終了したkaggleのGoogle Brain - Ventilator Pressure Predictionコンペの上位解法をまとめます。

内容は下記です

  • Brainコンペの上位解法をまとめる
  • 実際に動かしてみてコンペ期間の自分のベストモデルとの比較をする
  • やらないこと
    • PIDコントローラーの逆解析については今回は扱いません

1位

①simple LSTM(LB0.1209)

  • Task - regression
  • Model - 4層LSTM(input 9, hidden512)
    • SiLU
  • Features - u_in,u_out, time_step, RとCのOHEの計9個
    • 学習してるうちに派手な特徴量はいらないのではないかと気づいた
  • Optimizer - AdamW
    • lr: 2e-3 # or 3e-3
    • weight_decay: 0.1
  • Scheduler - ReduceLROnPleateau
    • patience : 30
    • factor : 0.6
    • min_lr : 1e-05
    • 500-600epochでearlystoppingで終了する
    • CosineAnnealingはoverfittingになりやすい
  • Loss - dual regression loss
  • batch_size: 512
  • Score
    • CV 0.1507
    • public 0.1503
    • private 0.1555

伸びすぎてコンペ中何してたんだろうという気分になりました

②LSTM CNN transformer

f:id:teyoblog:20211204233726p:plain 引用元:https://towardsdatascience.com/winning-the-kaggle-google-brain-ventilator-pressure-prediction-2d4c90d831ec

  • Task - regression
  • Model - LSTM CNN transformer
    • LSTM
      • 目標値が過去の時点大きく依存しているため必要
    • 1D conbolutions, transformers
      • グローバルな依存関係をモデル化するに適した組合せ
      • transformersがlocalな相互作用をとらえられないことを補っている
    • 層が深く、勾配の問題もあるため残差接続を追加
  • Features - 公開ノートで広く使われている基本的な特徴量のみ
    • originalデータに加えて、いくつかのラグや差分の特徴量、u_inの累積和、OHEされたR&Cの特徴を使用する
  • Optimizer - Ranger optimizer
  • Scheduler - CosineAnnealing 150epoch
    • 100epochくらいで下げきり、そこから一定のlr
  • Scaler - RobustScaler
  • batch size 128

2位

PIDコントローラーの解析

  • pulp-fiction(公開ノート)を7つ学習しbetter than medianでアンサンブルする
  • 生成PIDコントローラを決定し,可能な限り予測値を逆PIDコントローラの出力で置き換える
    • 4024000個の値のうち915437個を置き換えた

3位

  • Task - regression
  • validation - RCでStratified k-fold
    • そこまで有効ではなかった
  • Model - Conv1d + Stacked LSTM
    • オリジナル、カーネルサイズ2,3,4のConv1dを通したものをConcat(bs,L,4n)をLSTMに通す
    • LSTM(4N,1024,bidirectional=True,dropout=.1)
    • LSTM(1024*2, 512 bidirectional=True,dropout=.1)
    • LSTM(512*2, 256,bidirectional=True,dropout=.1)
    • LSTM(256*2, 128,bidirectional=True,dropout=.1)
    • FC
  • Features
    • オリジナル: u_in, u_out, R(one-hot), C(one-hot)
    • 追加特徴量: u_in_min, u_in_diff, inhaled_air, time_diff
    • inhaled_air???
    • u_inのcumsum はu_inが減少した時点での圧力の予測に支障が出ると考え使用をやめた
    • conv1dで特徴量を作るのでrollingの特徴量は使用しなかった
    • 初期圧力の予測が重要だと考える⇒初期圧力が異常に低いものがあった⇒u_inの最小値が非常に小さいことが判明⇒u_in_minという特徴量を作成
  • Optimizer - AdamW
  • Scheduler - Cosine
  • Data augmentation
    • R、Cをランダムにマスク
    • sequenceの順番をランダムにシャッフル
    • 2つのsequenceをmixup
      • RとCはonehot状態を*0.5して和をとった
      • LB0.117から0.1104になった
  • mixup(p=0.4)⇒shuffling(p=0.2)⇒masking(p=0.2)
  • Loss 2種類使用
    • pressureに対するMAE
    • Difference of pressure
      • sequence間のpressureのdiffもラベルとして学習した
      • LBが0.14x から 0.127xに向上
  • ensemble
    • Seedアンサンブル(median)
    • 1st Pseudo labeling(0.948)
    • 2nd Pseudo labeling(0.0942)
    • single model 0.1004
    • ensemble + post-processingで0.963

4th

  • 30%のデータは逆PIDコントローラーの値を使用

5th

  • Task - classification
  • seq長を80でなく40にした
    • u_out==0が最大でも35までだったため
  • Validation -
  • Model - transformers
    • encoder 20層
    • head 128
    • dropout(0.8)
    • multihead
  • Features
    • 基本的に公開ノートで広く使われているもので、アーキテクチャのほうが精度に貢献している
    • original特徴量とConv1dの特徴量を結合
    • 作成した特徴量をカーネルサイズ(2,3,5,7,11,15)のconv1Dで16chanにしてconcatすることで96次元の特徴量に変換
  • Optimizer - AdamW
  • Loss - Cross Entropy
  • 1000epoch
  • 15fold
  • 15seed

f:id:teyoblog:20211204233610p:plain

6th

  • Task - regression
  • Validation - 全データで学習 30seed
  • Model - nn.LSTM(input_dim, 512, batch_first=True, bidirectional=True, num_layers=4, dropout=0.1)
  • Features - raw_features, u_in_cumsum, u_in_log, [time_step, u_in, u_in_log]のdiff1-4
  • Optimizer - AdamW
    • lr - 3e-4
  • Scheduler - CosineAnnealingWarmRestarts(3restarts
  • Scaler -
  • Loss -
  • Batch size - 128
    • 大きいと性能が悪くなった
  • Score
  • 280epoch
  • 近隣学習とマルチタスク学習による性能向上
    • RとCでグループ
    • u_inのMAEでソート
    • trainセット内の上位5つの近傍breathからランダムで1つ選択
    • uin, uin_diff1-2, pressure, pressure_diff1-2, neighbour_uin-self_uinを特徴量に追加
    • TTAではtop5つそれぞれで予測し5つの推測値のアンサンブル
  • マルチターゲットで学習すると収束が早くなった
    • diff of pressure
    • diff2 of pressure
    • pressure - neighbour's pressure
    • classification label (950 classes)
  • pseudo-label

9th

  • ベースはnakama-sanの公開ノート
  • loss - likelihood loss
  • 10fold
  • 10seed
  • pseudo label
  • 誤差をラプラス分布と仮定したL1ノルムの最小化をした
  • sequence noごとに誤差の分散が異なると過程
    • modelからは予測値xと予測分散bを出力
    • スコアが0.01以上改善

10th

  • Task - regression
  • Model - 4層LSTM layers + Dense layers + Output Layer with skip connections
  • Features - 一番重要
    • 7つのMagic特徴量で0.017-0.018改善
      • R,C,整数に丸めたu_in,rankを結合したカテゴリを作成 (trainだけでunique 22422)
      • カテゴリのcount
      • カテゴリの基礎統計量(mean min max)
      • u_inと基礎統計量3種との差
    • seqを逆順にして学習
      • 正順で作られた特徴量も加える
      • seq80は1個目と80個目のu_inが特徴としてある状態
      • 正順LSTMよりも0.002ほど良い
    • 特徴は誤分類されるbreathの情報とそれを校正することを目的に考えた
  • 正順と逆順をアンサンブルして0.004のブーストがかかった
  • Score(public:0.1093,private 0.1133)

マジック特徴量の実装は公開ノート

11th

  • Task - regression と classification 両方
  • Pseudoラベルは有効だった
    • 実ラベルと擬似ラベル予測の層を分けたりすることも有効だった

Regression Part

  • Model
    • u_inとRCをConv1dを通してsignal hidden unitへ(このsignal hidden unit がよくわからない
    • signal hidden unit; time embedding(time stepから作成?) -> Transformer with Disentangled Self Attention -> LSTM
      • Disentangled Self Attentionの実装は、NLPのDeBERTa Modelを参考に位置の埋め込みを時間ステップの埋め込みに置き換えた
      • https://arxiv.org/abs/2006.03654
  • Optimizer - AdamP
    • lr 2e-4
  • Scheduler StepLR
    • Decay 0.95/5epoch
  • Loss - u_outの値ごとのマルチタスク
  • Score - 0.1160

Classification Part

  • Model - 4層 conformer
  • Optimizer - Ranger
    • lr 5e-3
  • Scheduler - CosineAnnealingLR
    • 最初の100エポック
    • そこからReduceLROnPlateau
  • Loss - BCE
    • u_out==1は学習しない
  • RCの組み合わせが異なると、挙動がかなり異なることを発見した
    • すべてのデータから一般的な学習をしたあと異なるRCに対してfine-tuneをした(0.002改善
  • Score - LB0.1167

Ensemble

  • 回帰と分類のモデルのアンサンブルで0.1112
  • 上記から得られるpseudo labelでモデルを再学習してensembleに加えて0.1074までboost

13th

Transformer(regression)

  • Model - skip connectionを使用したtransformer encoder を12層
  • Features - 特徴量は公開ノートのもの
  • Scheduler - cosine with restarts (+0.020
  • Target - pressure.diff()も予測してlossに使用(+0.015
  • Train - 全データで学習
  • https://www.kaggle.com/cdeotte/tensorflow-transformer-0-112
  • Score -CV:0.133,LB0.112

bidirectional LSTM(regression)

  • Model - 4層LSTM
  • Features 20個
    • u_inの対数
    • u_inのcumsumを200で割った値
    • time_step, u_in, log_u_inのdiff(1-4)
    • 詳細はdiscussion
  • Target - pressure, pressure.diff(), pressure.cumsum()を予測してMAE
  • Train - 全データで学習
  • Score - CV 0.135 LB 0.118

CNN-LSTM(classification)

  • Model - todaさんの公開ノートベース
    • LSTMの前にCNNを追加
  • lr - 2.5e-4
  • Scheduler - cosine
  • Loss - u_out=0だけで計算
  • Batch size - 32
  • Train - 全データで学習
  • 70epoch
  • Score - CV 0.145 LB 0.125

LSTM-GRU(regression

  • Model - Pulp Fictionベース
    • LSTMの前にCNN
  • Features - u_in, u_out, time_step,RとCのdummyの18個
  • lr - 2.5e-4
  • Scheduler - cosine
  • Batch size - 32
  • 70epoch
  • Score - CV 0.150 LB 0.127

Ensemble

  • 72モデルを最適化を用いて加重中央値を計算し、アンサンブル
    • CV 0.117 LB 0.116
  • 上記4種*32model+72の200モデルでアンサンブル
  • 200modelのアンサンブルから作られたpseudo labelを使ってCNN-LSTMで再学習
    • 全データ(trainもtest)で32seed分学習してLB 0.110
    • 20%のcutmix
    • Loss pressure, pressure.diff(), pressure.cumsum()
    • LSTMの前にCausalCNN blockを使用
      • time_stepから8つの新しい特徴量を作成するもの
      • 作られた8つの特徴量を他の特徴量と結合してLSTMに入力

14th

  • Features - 公開ノートを参考
    • u_inの対数(u_inオリジナルも使用)
  • Scaler - Robust Scale qunatile-range(10:90)
  • Target - pressure_diff(1個前と1個後)の追加で0.010改善した
  • Loss MAE
  • Model(keras)
    • 4層 bidirectional LSTM
    • CNNの追加
    • 活性化関数 最後はseluでなくlinear
      • seluは-1.75~の値をとり、圧力の差分を目的変数として使用するのに適していない
    • fold 10~16
    • Score - cv 0.134 public 0.1157
  • Model(pytorch)
    • 5層 bidirectional LSTM
    • マルチタスクにすることでkerasよりもスコアがよくなった
    • Scheduler - CosineAnnealingWarmupRestarts
    • Score - cv 0.135 public 0.1153
  • Ensemble
    • 15モデルの出力の中央値
    • CV 0.1117 / Public 0.1103 / Private 0.1107
  • Stacking
    • 15モデルの出力のスタッキング、および8つのスタッキングの中央値
    • CV 0.1134 / Public 0.1100 / Private 0.1111*
  • breath_idに対して最適なモデルを選択するモデルを作ったがまったくうまくいかなかった

まとめた感想

今回は金圏まででご勘弁...

本コンペで私は3090に乗るだけbatch_size上げてたのですが上位はほとんど共通してバッチサイズは小さい方がいいとなっていますね。なんででしょうか・・・? 特徴量に関してはガッツリと作り込む場合や、作りすぎると収束が早くなるので殆ど作らない、Conv1dに任せるなどが見られました。 CV戦略についてはデータがかなり多かったのでランダムでも十分LBとの相関が取れ詰めるポイントではなかったようです。(全データで学習してるチームも多くありました。 後は多くのモデルでアンサンブルも重要ではありそうでした。

自分で金圏のスコアを出したい

今まで復習すらあまりしてなかったのですが今回はソリューション(コード)を見ながら自分で金券を目指してみようと思います

まずはコンペ中のベストモデル

  • Model - 4層LSTM dual regression head
  • Features - ['Base','Area','U_in_cumsum_mean','U_in_Lag','RC_OHE','U_out_stat','U_in_Lag_Diff','U_in_Rolling','U_in_delta','Area_cumsum','U_in_stat','U_in_half_stat','U_in_first_last','U_in_diff_maxmean','FFT',]
  • Optimizer - RAdam
    • lr - 5e-3
    • weight_decay - 1e-5
  • Epochs - 300
  • Scheduler - CosineAnnealingLR
    • min_lr - 1e-5
  • Batch size - 1024
  • Scaling - RobustScaler -> StandardScaler
  • Score
    • CV 0.16745
    • public0.1708
    • private0.174

1位のSimpleLSTM

  • singleFold
    • CV 0.1507403004
    • public 0.1503
    • private 0.1555

じっくり学習するだけでここまでのビルとは思いませんでした

  • 15fold
    • CV 0.1450459443
    • public 0.1253
    • private 0.1282(38th相当)

1位のLSTM CNN transformer

  • singleFold
    • CV 0.1397898606
    • public 0.122
    • private 0.146
  • 15fold
    • CV 0.1417298052
    • public 0.1255
    • private 0.1285(41st相当)

上記2モデルのアンサンブル

  • public 0.1182
  • private 0.1212(25th相当)

十分強いですけどまだ銀色なのでもう少し他のモデルを作ります

10th

まずはアーキテクチャだけ真似してみます

  • CV 0.1360389739

十分強い

マジック特徴量使用

  • singleFold
    • CV 0.1235325308
    • public 0.1265
    • private 0.131
  • 15fold
    • CV 0.1290646463
    • public 0.1117
    • private 0.1159(19位相当)

強すぎた

結果

上記の1位のモデル2種と、10位のモデルを2種アンサンブルした結果金圏スコアを出すことができました!

f:id:teyoblog:20211204233627p:plain

f:id:teyoblog:20211204233635p:plain

無事ソリューションの復習と自分で金圏スコアを出すことに成功しました。めでたしめでたし。

使用コード

おわりに

今回アドベントカレンダーという機会を活用して参加したコンペをいつも以上にじっくり復習させていただきました。自分は学生の頃から復習が苦手で模試とかも受けっぱなしなタイプでしたし、kaggleでもコンペも終わったらダラダラしてしまうことが多かったのですが、やはり復習はすべきだなと再認できました。今回の復習は結構ダラダラ時間をかけすぎてしまったのでそこは課題として今後も復習はしっかりしていきたいと思います。

【Kaggle挑戦記】肺の圧力コンペ銀メダル相当振り返り【#11】

こんにちは

kaggleで9/23から11/4の期間で開催されていた『Google Brain -Ventilator Pressure Prediction』というコンペティションに参加してきました。4名の方とチームを組み、結果としては2,659チーム中76位、銀メダル相当のスコアを出すことができました。

今回はコンペ期間中に学べたことや取り組んだこと書いていきます。

コンペ概要/背景

  • 人工呼吸器のシミュレーションデータから肺にかかる圧力を予測するというタスク
  • 人工呼吸は臨床医が必要になる。COVID-19のパンデミック時にはその数に限界が来ていた
  • 人工呼吸器を制御する新しい方法を開発するには莫大な費用がかかる
  • 機械学習により様々な特性を持つ肺に一般化できるシミュレータを作りたい

データ

  • シンプルなテーブルデー
  • 説明変数は R, C, time_step, u_in, u_out
  • 目的変数がpressure
  • 呼吸単位でbreath_idが設定されており、連続した80行が1つのbreathのデータとなっている
    • 時系列も上から下に時間が経過されるように並んでいる
  • 各変数についてより明確にイメージするための図がDiscussionに投稿されていました

評価指標

MAE

  • 提出したデータのうち、u_outが0の部分のみ評価される
  • CVは同様にu_outが0の行のみのmaeを取ることで比較的LBと高い相関が取ることができた

f:id:teyoblog:20211109184533p:plain [引用]https://www.kaggle.com/artgor/ventilator-pressure-prediction-eda-fe-and-models

実際にデータをプロットすると画像のようになります。この前半が評価対象です。

取り組み

コンペ中にトライしたことや、こんなモデルがあったということを書いていきます

テーブルデータなので初手LightGBM

直前のMLBコンペでは表形式の時系列データでLightGBMで戦えていたこともありまずはLightGBMで様子を見ることにしました。スコアは1.4くらいでこの時点で公開されていたLSTMのスコアには遠く及ばない値だったためチューニングすることなくLSTMの勉強に切り替えました。

LSTM

torch.nn.LSTM/GRU

ということでここからLSTMで取り組みました。これまで使ったことがなかったため挙動を理解するいい機会になりました。自分用メモとして図に残します。

f:id:teyoblog:20211109184554p:plain

  • 入力サイズは (N, L, Hin) (バッチサイズ、シーケンス長、input_size)
  • データを与えると動的にLSTM blockが生成されるあら便利
  • GRUに変えればすぐにGRUが使える
  • Bi-directionのときは出力のサイズが2倍になっている

特徴量

特徴量はディスカッションや公開ノートから採用しました。

管理方法は『Kaggleで使えるFeather形式を利用した特徴量管理法』を初めて使ってみました。

前処理

公開ノートは完全にRobustScalerでスケーリングを取る流れに支配されていました。 私もその流れに乗っていたのですが、ある特徴量を追加すると、lossがnanになってしまったので分布を確認すると入力値がmax5000くらいになっていました。そこで、RobustScalerをしたあとにStandardScalerを通すことにしました。また、MoAのときお世話になったRankGauss(+standardscaler)も試してみましたが本コンペではRobustScalerのほうが若干いいスコアが出せていたように思います

f:id:teyoblog:20211109184621p:plain
Scaler毎の分布の違い

f:id:teyoblog:20211109184633p:plain
StandardScalerを通すとどうなるかの確認

CV

上に書いたとおり比較的LBと相関が高かったように思います。その結果密なLBでしたがshakeも小さかったです。

fold

RとCの組み合わせが均等になるようなFoldの切り方をしました。データが多すぎるためかそこまでスコアの向上には繋がりませんでしたが、fold間のスコアの分散は小さくなった気がします...

pytorchで公開noteの再現が取れない問題

序盤はkerasのハイスコアノートが多く、pytorchに移植してもスコアが悪くなってしまうという状態でDiscussionも盛り上がりを見せていました。 この問題はPyTorch LSTM with TensorFlow-like initializationというnotebookによってpytorchユーザーでもハイスコアが狙えるようになりました。原因としてはnoteにて説明がありますが初期値がKerasとpytorchでは違うよということです。

余談ですが私がその頃使っていたモデルにはもう一つ精度が出ない要因がありました。モデルの構成はこちらのnotebookを参考にLSTMへ入力する前に2層NNを通して分散表現を得るようにしていたのですが、このNNが私の場合は制度を悪化させていました。(後述しますが1DCNNだといい感じなのに不思議なものです。

いろんなモデルを試す

分類タスク

このコンペの目的変数は連続地のように見えて値のユニーク数が950個であることが指摘されていて、分類タスクとして扱うこともできました。実際に分類で解くnotebookも公開されていました。

dualheadなモデル

コンペ後半はケロッピ先生がdiscussionで紹介していたdualheadなモデルを使用しました。

f:id:teyoblog:20211109184705p:plain

u_outが0と1それぞれに対してLinearを学習させることができ、u_out=1の部分も学習できるため時系列としてスタッキングをすることもできるようになるため採用しました。ちなみにout側(図の右側)のlossのweightを減らして加算することはあまり効果がなかったです。

Conv1dで特徴量を作る

同じdiscussionにオリジナルの特徴量5個から32次元の特徴量をconv1d作るというアプローチもありとても勉強になりました。 f:id:teyoblog:20211109184714p:plain 時系列に畳み込みをしつつ7次元から32次元に増やします。前述したLSTM前のNNは不発でしたがこのLSTM前のConv1dは使用する特徴量がもともとの5個でもかなり高いスコアを出していました。

公開ノートに屈する

残り期間が2週間を切って、公開ノートのアンサンブルしたbestスコアノートがどんどん更新されていきました。その頃私達はそのスコアより下にいる上に、ベストシングルモデルも公開されているものに負けていました。このコンペはアンサンブルが効くことはわかっていましたので、公開されている一番スコアの良いノートをみんなで回し始めました。5人分TPUのquotaがありますからね・・・

f:id:teyoblog:20211109184731p:plain

これがDatasetです...

f:id:teyoblog:20211109184737p:plain

これがそのmodelなのですが、正直このmodelのお気持ちはよくわからないです。

スタッキング

LightGBM

シンプルにモデルの予測値を特徴量にしてLightGBMにぶち込みます。u_out=1の行だけで学習もできるので、u_out=1部分の学習をしてないチームメンバーのモデルなどを使うことができました。

CVは0.1442とそこまで良くなかったですが最後のアンサンブルには含まれました。

1DCNN

やってみたものの採用はされず、ただ実装の勉強になりました・^・ CV0.1546,LB0.1359くらい。

LSTM

1st stageの予測結果も時系列なのでそのまま各モデルを特徴量にしてLSTMにもう一回入れます。(u_out=1のデータも学習してる必要があるはず)通常のLSTMもやりましたが、こちらのnoteを参考に数種類のkernel sizeのConv1dを使って時系列を畳み込んで特徴にしたLSTMが最終的に一番良かったです。

f:id:teyoblog:20211109184754p:plain

CVはだいたい0.13957

ちなみに pulp function12個でmedian取ると0.1384でました・・・。

アンサンブル

結局最後までチームとして優れたシングルモデルは作れませんでした。

手元にある約50のモデル(コピペモデル含む)でmedianをとった結果がCV0.1344,LB0.1318で最終日public銅圏漏れprivateギリ銅くらい。そこから1つづつ省くとCVが上がるものを、何を省いても上がらなくなるまで抜き続けた結果CV0.1320,LB0.1295まで上がり最終的にコレが汚いsolution図且つ銀メダルsolutionとなります。

better than medianという混ぜ方も提唱されていましたが、モデルが多すぎたのか単純にmedian取るのが結局良かったです。

f:id:teyoblog:20211109184806p:plain

学習速度改善の話

コンペ序盤に他の参加者が1epoch10秒未満と言ってるにも関わらず手元のRTX3090で1分くらいかかるという事象が起きてました。当時のコードが下記コード1で600万行ほどのdataをDataFrameで渡していたことが原因でした。コレを下記コード2のようにnumpy配列で渡すようにした所同じように10秒/epochで学習ができるようになりました。コンペ期間中どれだけ速度向上に時間を割くかというところではありますが、こういったインデクシング速度の差についてはある程度理解が必要だと痛感しました。教えてくださったフォロワー様ありがとうございます。

※下記コードのもっと良い書き方例あればご指摘ください。

# コード1
class TrainDataset(Dataset):
    def __init__(self, df, transform=None):
        self.df = df
        self.groups =df.groupby('breath_id').groups
        self.keys = list(self.groups.keys())
        
    def __len__(self):
        return len(self.groups)
    
    def __getitem__(self, idx):
        indexes = self.groups[self.keys[idx]]
        df = self.df.iloc[indexes]
        cate_seq_x = torch.LongTensor(df[CFG.cate_seq_cols].values)
        cont_seq_x = torch.FloatTensor(df[CFG.cont_seq_cols].values)
        u_out = torch.LongTensor(df['u_out'].values)
        label = torch.FloatTensor(df['pressure'].values)
        return cate_seq_x, cont_seq_x, u_out, label
# コード2
class TrainDataset(Dataset):
    def __init__(self, X, y, u_out):
        self.X = X
        self.y = y
        self.u_out = u_out
        
    def __len__(self):
        return len(self.X)
    
    def __getitem__(self, idx):
        x = torch.FloatTensor(self.X[idx])
        u_out = torch.LongTensor(self.u_out[idx])
        label = torch.FloatTensor(self.y[idx]).squeeze(1)
        return x, u_out, label

X = np.float32(df_train[CFG.feature_cols]).reshape(-1, 80, len(CFG.feature_cols))
U_out = np.float32(df_train[["u_out"]]).reshape(-1, 80)
y = np.float32(df_train["pressure"]).reshape(-1, 80, 1)

反省点

今回始めてLSTMやGRUといったモデルを使ってどんな感じにデータを与えてやればいいのかを理解することができたのは良い収穫でした。また、弱識別機をアンサンブルさせて何とか銀に行けたこともいい経験ではありますが、やはり自分で良いシングルモデルを作ることができなかった点は反省と悔しさが残ります。

アドベントカレンダーで上位ソリューションをまとめると宣言したのでそこでまた深く反省したいと思います。

最後に

11/4のprivateLB公開時には76位の銀メダル圏におりましたが、11/9の朝順位が確定する前のLB更新にて我々のチームはLBから削除されました。理由はチーム5人の内で規約違反が発生していたためだと考えられます。ルールとマナーを守って楽しく競い合うコミュニティでこのようなことになってしまい申し訳ありません。

discussion

幸いアカウントとこれまでの実績は現状残してもらっているのでまた気持ち新たにコンペティションマスターを目指していきたいと思います。(金メダル味わいたい...

なかなか起きないことだと思うので軽くどんな感じになったのか共有させてください。

  • LBから自チームが消える
  • kaggleのマイページのCompetitionsタブから該当コンペがなくなる
  • 該当コンペのMysubmissionsはScore含め全部残っている
  • Teamも残っている
  • 特にkaggle側からメールとかはこない
  • Kaggle Complianceというフォームがあるので他チームのルール違反、自チームが消されたときのやってませんの報告はこちらから行う
    • 英語はちゃんと読みましょう。やったのにやってないと問い合わせると重くなるみたい

それではまた次の機会で

【Kaggle挑戦記】MLB コンペ 31th 銀メダルでした【#10】

f:id:teyoblog:20210915211906p:plain

こんにちは

本日MLBコンペのprivateリーダーボードが確定されて無事初めての銀メダルを手にすることができました。

銀メダルはコンペティションマスターになる条件の1枚ですのでやっと一歩目を踏み出せたなという思いです。

取組内容

取組内容に関してはすでに記事になっているので下記記事を参考にしてください。

teyoblog.hatenablog.com

ちょっとだけ考察

本コンペは締切が7/31で、8/1以降のデータをprivate testデータとしてhostが頑張ってデータを作って評価を行う形式でした。

実際には10日おきくらいでそれまでの10日分、20日分、30日分のデータでスコアの計算が実施され、都度自分の提出サブ2つのスコアの遷移を確認することができました。(メモは取り忘れました。

私が提出したモデルは学習方法、特徴量、モデル等はほとんど同じで、2021年シーズンだけで学習したもの以下①と、全シーズンで学習したもの以下②を提出しました。2回目の再計算までは①が優位だったのですが最終的な結果では②が逆転していました。LBでも2回目実行時60位くらいから31位まで上がったためこの期間のデータはちょっと異質だったのかな?って思います。(確認はできません・・・)

銀メダルとれた要因

大きく2つあるのかなと思ってます。

  • 再計算で半分くらいのチームが脱落した
    852チームサブを提出していたのですがprivate testデータで動いたチームが434チームです。その中でも1サブは死んでるというチームも多く見られました。(public testデータで rosterになぜかnanがあることはわかっていて、サブミッションエラーも出たためそこら編は対策できてるはずなのでどこでサブミッションが死んだのかは見当がつきません・・・
  • 今シーズンのデータを学習に使った
    コンペ開催当初から与えられていたデータには今シーズンのデータがかなり少ないです。公開ノートでも2021年3月までのデータをtrainとして2021年4月をvalidとして学習しているため、今シーズンのデータは学習に使えていない状態です。7月下旬に追加で配布された5/1-7/17までのデータと、コンペ締め切り後に更に追加される7/31までのデータを学習に使うような構成にしておくことは必須だったと思います。(lag特徴量を使うなら特に)
    もう確認できないpublic LB様子的に今シーズン5/1-7-31のデータ学習に使ってないチームがある程度いたのではないかと思います。その点で少し手法が劣っていても学習データ分でのし上がれた気がします。

あとはラベルの統計データもなるべく直近のラベルを使うようにしたことも影響があったかもしれません。

金メダルに足りなかったもの

まだソリューションを詳しく読めてないですが、ラグ特徴量やモデルのバリエーション部分は必須なのかなと感じました。

おわりに

今回kaggleを初めて約1年3ヶ月でやっと1枚目の銀メダルを手に入れることができました。エキスパートまでは比較的スムーズに行けた気がするのですが、そこから銀メダルを取るということは遠かったです。冒頭に書いたとおりこれで目標であるコンペマスターへの道がやっと一歩進んだなと思ってます。引き続き努力を続けていきたいと思います。

どんな人が1年位で銀メダルとったの?ときになる方は下記をご参考ください。 teyoblog.hatenablog.com

【30日チャレンジ】早起き【2021年8月】

f:id:teyoblog:20210831195455p:plain

こんにちは、以前から気になってた30日チャレンジというものを8月2日から今日までの30日でやってみました。

30日チャレンジとは

www.ted.com

動画は3分くらいなので見たほうが早いですが、30日だけなにかに挑戦してみれば30日チャレンジです。

半年くらい前ツイッターで見かけてずっと気になっていて、ふとこれ挑戦したいなと思うことがあったので始めてみました。

何をしたか

早起きをしました。Youtubeで早起きに関する動画*1 を見たのがきっかけです。副効果も大きいと思ったのでこれに決めました。

余談ですが動画の女の子の目と声が結構好き。

取り組み方

目標時間は5時半に起きる(そのために22時半就寝目標)と設定して、達成具合の管理をするためにnotionに専用のページを用意しました。

f:id:teyoblog:20210831193831p:plain

Notionでテンプレートを作成する方法を参考に、起きたらその日のページを作って、記録をつけます。

f:id:teyoblog:20210831194029p:plain

チェックボックスと、スマート睡眠パッド*2の記録を貼る簡易ページです。チャレンジと言いつつ5時半頃に起きれなければそれは失敗なのでアイコンでマルバツをつけるようにしました。

結果

f:id:teyoblog:20210831194316p:plain

18/30日の早起き成功でした。とはいえ6時までには起きれることが多かったので、7月までと比べて早起きができるようにはなったと思います。

反省点としては

  • 瞑想がよくわからん
  • 寝る時間がまだ遅く睡眠時間としては短い傾向にある

ことです。

今後の予定

早起きに関してはデメリットは特に感じてないし、もっとよくできると思うので今後もゆるゆると続けていこうと思います!(本当にやってよかった。

 

9月は面白そうなKaggleのコンペ(Lux AI)が生えてるので全くの未経験分野ですが挑戦してみようと思ってます。現行コンペということで進捗を報告できないのが不安要素・・・

 

【PyTorch Lightning】checkpointをロードするのに躓いたことメモ

f:id:teyoblog:20210826122249p:plain

こんにちは

最近PyTorch Lightningで学習をし始めてcallbackなどの活用で任意の時点でのチェックポイントを保存できるようになりました。

save_weights_only=Trueと設定したの今まで通りpure pythonで学習済み重みをLoadして推論できると思っていたのですが、どうもその認識はあっていなかったようで苦労しました。今回は学習済みの重みで予測するところまで進めようと思います。

※ちなみにsave_weights_onlyのTrueとFalseは全く違う設定のようです

結論

結論としては下記の2つの方法ができそうだなと思いました。

  1. 学習時のLightningModuleインスタンスを作ってload_from_checkpointで読み込む
  2. 学習が終わったらtorch.save(model.model.state_dict(), outpath)で保存する

まずは保存

保存の手順についても軽く触れておきます。
まずはModelCheckpointのインスタンスを作ります。

loss_checkpoint = ModelCheckpoint(
    dirpath=OUTPUT_DIR,
    filename=f"best_loss_fold{fold}",
    monitor="val_loss",
    save_last=True,
    save_top_k=1,
    save_weights_only=True,
    mode="min",
)
auc_checkpoint = ModelCheckpoint(
    dirpath=OUTPUT_DIR,
    filename=f"best_auc_fold{fold}",
    monitor="val_score",
    save_top_k=1,
    save_weights_only=True,
    mode="max",
)

あとはTrainerに渡して学習するだけ。

trainer = pl.Trainer(
    logger=wandb_logger,
    callbacks=[loss_checkpoint, auc_checkpoint, lr_monitor],
    default_root_dir=OUTPUT_DIR,
    gpus=1,
    progress_bar_refresh_rate=1,
    accumulate_grad_batches=CFG.grad_acc,
    max_epochs=CFG.epochs,
    precision=CFG.precision,
    benchmark=False,
    deterministic=True,
)
model = Trainer(CFG)
trainer.fit(model, data_module)

f:id:teyoblog:20210826122100p:plain

指定したフォルダに2つのModelCheckpoint分のチェックポイントと、lastの計3つのチェックポイントが生成されます。このckptファイルを使って推論をできるようにします。

もちろん公式にチェックポイントの保存と読み取りに関するドキュメントは用意されています。

チェックポイントを読み込んで推論する

とりあえずチェックポイントをtorch.load()で読み込んで中身を見る。

checkpoint = "../input/hoge/best_loss_fold0.ckpt"
state_dict = torch.load(checkpoint)
state_dict

f:id:teyoblog:20210826122116p:plain

state_dictに重みが入っていそう。

state_dict.keys()

>> dict_keys(['epoch', 'global_step', 'pytorch-lightning_version', 'state_dict'])

キー一覧を見ると、state_dict以外にも3つ項目が含まれていることがわかります。 state_dict["state_dict"]load_state_dict()したらいけそう?

timmで同じ構成のmodelをつくって読み込みます。

checkpoint = "../input/hoge/best_loss_fold0.ckpt"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = get_model(CFG)
model.to(device)
model.eval()
state_dict = torch.load(checkpoint, map_location=device)
model.load_state_dict(state_dict["state_dict"])

f:id:teyoblog:20210826122127p:plain

とても怒られた(´・ω・`)

ググって参考な記事を見つけたので試してみます。 + PyTorch LightningのckptファイルをLoadするのにはまった話 + PyTorch LightningのckptファイルをLoadするのにはまった話のその後

strict=Falseってやつをやってみる。

model.load_state_dict(state_dict["state_dict"],strict=False)

f:id:teyoblog:20210826122153p:plain

エラーは出ませんが互換性がないキーが大量に出てきます

load_state_dict()前後で値も変わっていないのでちょっとダメそう。ヤンチャすぎましたか。

model = get_model(CFG)
print(model.state_dict()['model.conv1.0.weight'][0])
model.to(device)
model.eval()
state_dict = torch.load(checkpoint, map_location=device)
model.load_state_dict(state_dict["state_dict"],strict=False)
print(model.state_dict()['model.conv1.0.weight'][0])

>>
tensor([[[-0.0417,  0.0072, -0.1121],
         [ 0.1637, -0.0598, -0.0470],
         [ 0.0556,  0.1053, -0.0689]],

        [[-0.0978, -0.0325, -0.1222],
         [-0.0573,  0.0169,  0.1032],
         [-0.0287, -0.0851, -0.0106]],

        [[ 0.0569,  0.0524,  0.0678],
         [-0.0426, -0.0429,  0.0857],
         [-0.0206,  0.0479,  0.1518]]])
tensor([[[-0.0417,  0.0072, -0.1121],
         [ 0.1637, -0.0598, -0.0470],
         [ 0.0556,  0.1053, -0.0689]],

        [[-0.0978, -0.0325, -0.1222],
         [-0.0573,  0.0169,  0.1032],
         [-0.0287, -0.0851, -0.0106]],

        [[ 0.0569,  0.0524,  0.0678],
         [-0.0426, -0.0429,  0.0857],
         [-0.0206,  0.0479,  0.1518]]], device='cuda:0')

諦めてLightningModule.load_from_checkpoint()する

ヤンチャしないで諦めてLightningModule宣言してload_from_checkpointでチェックポイントを読み込みます

model = Trainer(CFG)
checkpoint = "../input/hoge/best_loss_fold0.ckpt"
model = model.load_from_checkpoint(checkpoint)

>> __init__() missing 1 required positional argument: 'cfg'

が、cfgがないと怒られます。

それならとcfgを引数に入れたら出来ました。

CKPT_PATH = "../input/glrmodel/best_loss_fold0.ckpt"
print("保存されてるweights")
state_dict = torch.load(CKPT_PATH)
print(state_dict["state_dict"]['model.model.conv1.0.weight'][0][0])
model = Trainer(CFG)
print("モデル宣言時のweights")
print(model.model.state_dict()['model.conv1.0.weight'][0][0])
model = model.load_from_checkpoint(checkpoint_path=CKPT_PATH,cfg=CFG)
print("checkpointロード後のweights")
print(model.model.state_dict()['model.conv1.0.weight'][0][0])

>>
保存されてるweights
tensor([[ 0.0780,  0.0557, -0.8499],
        [ 0.1131,  0.8607,  0.5181],
        [-0.6028,  0.4215, -0.3620]], device='cuda:0')
モデル宣言時のweights
tensor([[-0.1406,  0.0140, -0.1002],
        [-0.1681,  0.1721, -0.0279],
        [-0.0425, -0.1278,  0.1192]])
checkpointロード後のweights
tensor([[ 0.0780,  0.0557, -0.8499],
        [ 0.1131,  0.8607,  0.5181],
        [-0.6028,  0.4215, -0.3620]])

値がロードされていることも確認できました。
よく見るとチェックポイント内のウェイトはmodel.modelの中に入ってるんですね。ここを何とかするといろいろできるかもしれないです。

cfgが必要な理由としては私のLightningModuleが下記のようになっているためです。

class Trainer(pl.LightningModule):
    def __init__(self, cfg):

おまけ

とあるコンペのsolutionを読んでいて、最後にtorch.save(model.model.state_dict(), outpath)しているコードがあったので試してみました。

これで保存したものはmodel.load_state_dict(state_dict)でロードできました。ご参考までに。

model = Trainer(CFG)
trainer.fit(model, data_module)

torch.save(model.model.state_dict(),OUTPUT_DIR + '/' + f'{CFG.exp_name}_fold{fold}.pth')

データサイエンス100本ノックを完走した【Google Colab】

こんにちは

 

コツコツと進めてたデータサイエンス100本ノックですが遂に完走できました。

今回は下記サイトを参考に(というか提供されてるnoteをもらって)Google Colabで全て行いました。興味ある方はノートパソコンでも全てできます。一部ライブラリのバージョンの差か何かでdatetimeの処理が違っていた気がしますがそこら編は私のnoteを貼っておくのでご参考ください。

qiita.com

下記私が100問解いたノートです。一部コメントなどが入ってます。

colab.research.google.com

 

感想というのも大げさですがこの100本ノックのおかげでMLBコンペでdataframeをつかってEDAするスピードが上がったと実感があります。

学びの多い100問だったと思います。