まだタイトルない

アウトプット用です

【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というフォームがあるので他チームのルール違反、自チームが消されたときのやってませんの報告はこちらから行う
    • 英語はちゃんと読みましょう。やったのにやってないと問い合わせると重くなるみたい

それではまた次の機会で