Kaggleコンペ「AI4Code」振り返り

# はじめに
本記事は[Kaggle Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kaggle)の19日目の記事です。
この記事では今年の中ごろにKaggle上で行われていた[AI4Codeコンペ](https://www.kaggle.com/competitions/AI4Code)の紹介と，参加者としての振り返りをしていきます．

[自分のモデルの解説](https://www.kaggle.com/competitions/AI4Code/discussion/367600)はすでにディスカッションにあげてますし，最終的な上位手法の具体的な処理を知りたいのならディスカッションで本人による個別の解説ページを見ていった方がわかりやすいでしょう．自分も自分の手法以外はその解説以上の情報を知らないわけですし．
ですからここでは具体的なモデル中の処理の話とかよりかは，参加者の間でコンペ中に議論がどういう流れで進んでいったか，最終モデルにつながる話がどのタイミングで生えてきたのかをメインに振り返ろうと思います．
記憶を頼りに書いている部分もあるので，ここ間違ってるよってところを見つけた方がいましたら教えてください．

# そもそもAI4Codeコンペとは
### タスク：Jupyter Notebookのマークダウンセルの位置復元問題
Jupyter Notebookはプログラムとドキュメンテーションを融合させたウェブベースのプログラミング環境です．コードやドキュメントはセルというまとまりごとにブロック分けできて，読みやすいプログラムを書くことができます．

セルにはプログラムを実装するコードセルと，ドキュメンテーションのためのマークダウンセルの二種類があり，マークダウンセルではその名の通りマークダウン記法が使えます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058126/ee69f6a6-8cea-2412-dff2-e520d30a7be3.png)

AI4CodeコンペではこのJupyter Notebookを題材に，バラバラな順序になったセルを元の順番に復元できるかという問題に取り組みます．
実際には全てのセルを並び替えるのではなく，コードセルだけでの順序は正しい並びが与えられており，コンペ参加者はマークダウンセルのみを正しい位置に戻せばよいことになっています．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058126/51ff8668-3900-1074-11aa-18b55b7d745b.png)
後ほどまた述べますが，上位で使われていた主なアプローチはモデル対象の観点から大きく分けて二つに分類できます．
* 上の図のように，マークダウンセルがどのコードセルの間に挟まるかの分類問題として解くアプローチ (Quick Thoughtsライクな手法)
* マークダウンセルのノートブック全体における絶対位置を予測する回帰問題として解くアプローチ

ちなみに，[ホスト側から与えられていたベースラインモデル](https://www.kaggle.com/code/ryanholbrook/getting-started-with-ai4code)や[1位解法](https://www.kaggle.com/competitions/AI4Code/discussion/360501) (Private score=0.9232) は回帰，[2位](https://www.kaggle.com/competitions/AI4Code/discussion/343659) (0.9220) や[私の3位解法](https://www.kaggle.com/competitions/AI4Code/discussion/367600) (0.9213) は分類，[4位](https://www.kaggle.com/competitions/AI4Code/discussion/343595) (0.9170) と[5位](https://www.kaggle.com/competitions/AI4Code/discussion/365587) (0.9158) は回帰と分類の複合でした．

### データセット
データセットはKaggleの公開ノートブックです．
訓練データとして約140K個のノートブック，Public LB用に約20K個のノートブックが収集されています．
Output情報（コードセルの実行結果）やメタデータはあらかじめ除去されており，純粋なマークダウンセルとコードセルのテキスト情報の羅列になっています．

Public LBの20Kノートブックはコンペ開始前に収集が終わっているわけで，したがって自前で公開ノートブックをクロールしてくるとPublic LBの解答となるノートブックまで手に入ってしまいリークしまくりです．実際コンペ最終盤はPublic LBスコアにいくつかとんでもなく高い値が出ていました (私もそうでした) ．
一方で最終順位を決めるPrivate LBについては2nd stage制のコンペになっており，Public用の20KノートブックはPrivate LBのスコア計測に用いられず，サブミッション締め切り後に新たにKaggle上に投稿されたノートブックを3か月にわたり再収集して，これらのみでPrivate LBのスコアが計測されるようになっていました．
そのためこのコンペではPriate LBのスコアに対してはリークの心配がほぼなく，自前でクロールしているチームはその扱いを間違えればむしろ大幅に順位が悪化する可能性がありました．

公開情報として，Public LB用の20Kノートブックもまた3か月の期間で収集されたものであり，提出締め切り前の時点ではPrivate LBノートブックの具体的な数は当然わからないものの，大体同じぐらいの数になるだろうとされていました．

### スコア
Kendall's tauと呼ばれる順位相関係数が使われます．
この値は+1～-1の範囲で動き，比較対象の二つの順序列（一方が正解の順序列でもう一方が予測の順序列に対応）が全く同じとき+1，真逆なら-1，相関がみられなければ0になります．高いほど良いです．
計測時はコードセルも含めた順序列同士の相関係数を測る設定になっているので，ランダムにマークダウンセルを配置してもコードセルが絶対正しい分で0.4程度のスコアは出ます．最終的な1位のPrivate LBは0.9232でした．


### コンペスケジュール
日本時間2022年5月12日にスタート，8月12日に提出締め切りでその後3か月かけてPrivate LB用の新規テストデータ収集が行われ，11月11日に最終結果が発表されました．

# AI4Codeのここがすごい！
### 大規模なデータセット！！
訓練データで140Kノートブックです．140Kという数字だけだとそこまで多くないような気がするかもしれませんが，1ノートブックに平均46セル程度あるのでセル数に換算すると約7Mセル！未圧縮で2.3Gありました．すげえ！
ちなみに大きすぎてKaggle上の計算資源だと訓練が終わらず絶望している参加者が見受けられました．

Public/Private LBも約20Kノートブックとなかなかな量があるので，計算時間の都合からそこまでアンサンブルを増やしたりすることもできません．なんならアプローチによっては軽量なモデルを使わないと1回分の推論すら間に合いません．1位の人も使ったのは1チェックポイントだけでアンサンブルは一切なしだったくらいです．

### 元データはApache 2.0ライセンス！！
データセットの元はKaggleの公開ノートブックであり，これらはすべてApache 2.0ライセンス，つまりだいたい何でもできるライセンスです．このコンペで得た知見は，手法だけでなくデータそのものを含めてよりダイレクトに他の場面で活用できます！すげえ！！

注意点として，”AI4Codeのコンペデータセット”のライセンスはApache 2.0ではないので，データそのものを流用したいときはクローラーを用意して自前でデータセットを構築する必要があります．しかしこの部分は[コンペ開催より前からの先人がいる](https://github.com/collab-uniba/KGTorrent)ので，そこまで大変ではありません．

### 賞金がすごい！！
Kaggleのコンペの賞金は1位で1万ドルとか1.25万ドルぐらいがありがちなのですが，AI4Codeは1位5万ドル！5位でも1万ドル！！すげえ！！！
しかしその割に参加者は比較的少なかった印象です・・・データセットが大きすぎて少し敷居が高かったのでしょうか？実際上位者はA100を普通に使ってる人だらけで，計算パワーがないとその時点でかなり不利な戦いになっている感は否めませんでした．

# すごく大雑把な解法の分類
本コンペで用いられた手法は，モデル対象の観点と，一度に入出力するマークダウンセル数の観点からいくつかに分けることができます．

### モデル対象の観点
この観点では，以下の二つに大別できます．
* **マークダウンセルがどのコードセルの間に挟まるかの分類問題として解くアプローチ (Quick Thoughtsライクな手法)**
分類問題として解くアプローチでは，あるマークダウンセルがどのコードセル2つの間に挟まるかという (コード, マークダウン, コード) の3つ組の他に，どのコードセルの下や前に来るかという (コード, マークダウン) の2つ組の形で解くタイプもあります．

* **マークダウンセルのノートブック全体における絶対位置を予測する回帰問題として解くアプローチ**
回帰問題として解くアプローチでは，絶対位置の値は0～1の範囲に正規化されることが多いです．
絶対位置はコードセル・マークダウンセルを合わせた全体の位置であるケースと，マークダウンセルだけ，コードセルだけに限るケースがあります．
なおコンペのホストから提供されていたベースラインモデルは，XGBoostにより正規化されていない絶対位置を予測する回帰モデルでした．

コードセルは正しい順序が与えられている都合上，コンペ的にはマークダウンの位置さえ当てればいいのですが，性能のためにコードセルの位置予測を学習時に行う手法もありました．例えば最終Private 1位の回帰モデルによる解法でもコードセルの位置学習を行っています．

### 一度に入出力するマークダウンセル数の観点
この観点では以下の4つに分けられます．
* **point-wise**
point-wiseな手法は必ず回帰モデルです．この手法では入出力としてどれか一つのセルのみに注目し，そのセルの絶対位置を予測します．どこかのポイント一か所に注目するのでpoint-wiseです．
典型的には，注目するマークダウンセルと，文脈情報としてコードセル情報をまとめて入力とし，注目マークダウンセルの絶対位置を出力するモデルになります．

* **pair-wise / triplet-wise**
これらは必ず分類モデルです．それぞれ (コード, マークダウン) の2つ組と (コード, マークダウン, コード) の3つ組に対応します．
なおtriplet-wiseはコンペ中で3つ組による手法がろくにシェアされなかったためそもそも名前が付けられておらず，ここで自分が勝手につけています．例えば2位の解法はpair-wiseとtriplet-wise両方を使う分類モデルでした．

* **list-wise**
出力を一度にドカッとまとめて出すモデルです．ドカッとまとめて出すかどうかがlist-wiseかどうかのポイントなので，実のところ分類モデルに対してはpair-wise/triplet-wiseに対して排他的ではなく，pair-wiseかつlist-wiseな分類モデルというのもありえます．実際2位は{pair-wise + triplet-wise}かつlist-wiseですし，私の手法もlist-wiseかつtriplet-wiseです．
分類モデルでは例えばマークダウンセルとコードセルを個別にエンコードし，それらの間のcos類似度でpair-wiseな分類を行うという構造のモデルが，いちいちペアごとにコードセルの表現を作り直す必要がないことからlist-wiseであると呼ばれていました．
一方でlist-wiseでない分類モデルは，例えばpair-wiseならマークダウンセル1つとコードセル1つをつなげてBERTに入力し，出力としてその組み合わせが正しい順序かどうかを二値分類するというのが典型的です．
回帰モデルでは一度の入力で複数セル分の予測をまとめて出すモデルはlist-wise，1つしか出てこないならpoint-wiseと呼び分けられていました．

### 上位解法概観
|  解法  |  Private スコア  |  モデル対象  |  入出力  |
| :----: | :----: | :----: | :----: |
|  [Private 1位](https://www.kaggle.com/competitions/AI4Code/discussion/360501)  |  0.9232  |  回帰  |  list-wise  |
|  [2位](https://www.kaggle.com/competitions/AI4Code/discussion/343659)  |  0.9220  |  分類  |  list-pair-wise + list-triplet-wise  |
|  [3位](https://www.kaggle.com/competitions/AI4Code/discussion/367600)  |  0.9213  |  分類  |  list-triplet-wise  |
|  [4位](https://www.kaggle.com/competitions/AI4Code/discussion/343595)  |  0.9170  |  分類+回帰  |  list-pair-wise (分類) + list-wise (回帰) <br> + CatBoostによるpoint-wiseな回帰 (アンサンブル用)  |
|  [5位](https://www.kaggle.com/competitions/AI4Code/discussion/365587)  |  0.9158  |  分類+回帰  |  list-triplet-wise (分類) + list-wise (回帰)  |


# どういう流れでコンペが進んでいったか
### 開始直後（5/12～5/19）
このコンペでは[ホストが回帰によるpoint-wiseなベースラインモデル](https://www.kaggle.com/code/ryanholbrook/getting-started-with-ai4code)を提供していた関係上，開始直後にフォーラム上でシェアされたモデルも基本的にはそれに沿っていました．その一方で，この時点ですでに[cos類似度を使ったfasttextによるlist-wiseな分類モデル](https://www.kaggle.com/code/ilyaryabov/fasttext-sorting-with-cosine-distance-algo?scriptVersionId=95873394)も出てきています．
データ拡張もすでに議論されていて，Jupyter Notebookがデータであることから[コーディング支援ソフトを使ったり](https://www.kaggle.com/competitions/AI4Code/discussion/324545)や[githubのような外部サイトから追加データを持ってくる](https://www.kaggle.com/competitions/AI4Code/discussion/324554)案があがっていました（実際に上位で使った人は見受けられませんでしたが）．
最終2位や5位のモデルで使われることになったCodeBERTもすでにこのタイミングで[ディスカッションに上がっています](https://www.kaggle.com/competitions/AI4Code/discussion/324570)．

開始直後に立ったディスカッションのうち特に重要だったものに，[回帰モデルで解く際はMSE LossよりもMAE Lossの方がよかったという報告](https://www.kaggle.com/competitions/AI4Code/discussion/325272)があります．これは本コンペで用いられる評価指標 (Kendall's tau) の性質から確かにMAEの方が好ましそうで，1位解法を含む回帰モデルは基本的にMAEで学習されていましたし，2位や私の手法のpostprocessでもここで効いてくるKendall's tauの性質が利用されているなど，絶対に抑えておきたいポイントでした．

解法の議論の脇では[このコンペが現実世界でどう役に立つのか](https://www.kaggle.com/competitions/AI4Code/discussion/325205)というKaggleらしいスレッドも生えたりしてしました．

### 開始から二週間後まで (5/19～5/26)
このタイミングで重要そうだったのは次の三つです．
* 具体的な実装を伴った[yuanzhe zhouさんによるpair-wiseな分類モデルによるアプローチ](https://www.kaggle.com/competitions/AI4Code/discussion/326473)がディスカッション上に生えてきました．
* Kaggle上の公開ノートブックをクロールしたKGTorrentデータセットが[ディスカッション上で話題に上がりました](https://www.kaggle.com/competitions/AI4Code/discussion/326520)
  * このデータは少し古いので直接使うのはあまりうまくいかなそうだったのですが，githubのレポジトリ上にクローラーが含まれており，自前でデータをクロールする機運が高まっていきました（個人の感想）．
* [Khoi Nguyenさんによるかなり強いpoint-wiseな回帰モデルがシェアされました](https://www.kaggle.com/competitions/AI4Code/discussion/326970)．
  * ホストによるベースラインモデルがPublic LBで0.6弱，上のpair-wiseモデルが0.8171 (この時点では十分強い) でこれが0.837なので相当なジャンプです．1位のモデルも入力の形式はこれを元にしていたので，優勝できうるモデルのベースがこのタイミングで出ていたことになります．
  * しかしながらこのモデルの学習にはかなりの計算リソースが必要で，その後も定期的に「計算リソースが足りない😭」スレッドが立つことになります．

回帰ベースのモデルに関してはKhoi Nguyenさんのモデルがデファクトスタンダードになり，これは最後まで変わりませんでした．分類ベースのモデルも結局最後までここで出てきたpair-wiseな分類モデルを超えるまともなベースラインが現れず，list-wiseな手法に至っては最初の1週間で出てきたfasttextベースのもののみという状況で，強いモデルは各自で考えて構築しなければならないという流れになっていました．

**そして恐ろしいことに，モデル構築に有用な情報はこの開始二週間でほぼ出尽くしました．**

### 開始から2か月後まで (5/26～7/7)
かなり飛びますが，実際この時期はディスカッションではそこまで大きな動きはあまりなく，[まったく同じマークダウンが複数回登場してしまう問題についての議論](https://www.kaggle.com/competitions/AI4Code/discussion/328324)や[英語以外の言語をどうするか](https://www.kaggle.com/competitions/AI4Code/discussion/329783)といった，本コンペspecificでスコア改善もそこまでなさそうな議論が生えたり生えなかったりでした．結局あまり使っている人を見ませんでしたが，[rankingベースの手法が何度か議論に上がったり](https://www.kaggle.com/competitions/AI4Code/discussion/328905)もしていました．
面白いところでは[コードセル中の関数や変数名をtokenizeライブラリで抽出し特徴量として使う案](https://www.kaggle.com/competitions/AI4Code/discussion/330417)が出ていました．自分も試してみようと思いましたが，解析に結構時間がかかるのと，文法が間違ってたりするコードは解析できないといった部分でいい解決策が思い浮かばず，結局使わずじまいでした．

一方でこの時期のPublic LBはというと，上位勢が83%～84%付近のスコアで戦っているところ，一人突然 (たしか) 86%程度のスコアを出した人が現れました．この時点で自分はKaggleノートブックのクロール作業を開始しており，きっと彼もクロールしていてリークしているだけでは？と疑っていました．その後のPrivate LBの推移的にも実際その通りだったようでしたが，当時の私はリークだと疑いつつもそうじゃない可能性も考えてそこそこの焦りがありました．仮にスコア的に無意味でも精神攻撃のためにリークは使い得です．
その後外部データを使わずともモデルの改善で彼を超えることができたのですが，周りのスコアは全然動く気配がなく，自分も1か月ほどサブしなくなりました (サブしないだけで裏で1か月ずっとDeberta-v3-largeのMLMの学習を回しつつクロールを続けていましたが)．

* LBの値は記憶を頼りに書いています．確認しようとしましたがmeta kaggleにすら2nd stage以降の情報しかなく確認できませんでした．

### 締め切り3週間ぐらい前まで (～7/23)
7月の頭ぐらいからようやく上位層のPublic LBが動き始め，89%越えのスコアがぽつぽつ出てきました．
その一方でディスカッションには[BERTのvariant](https://www.kaggle.com/competitions/AI4Code/discussion/336761)ぐらいしか気軽に使えるようなアイディアが出なくなり，[焦りのようなスレッド](https://www.kaggle.com/competitions/AI4Code/discussion/338333)や[LB Probingの話題](https://www.kaggle.com/competitions/AI4Code/discussion/336774)のようなコンペ末期の様相がすでに見受けられました．

一応[外部データがうまくいったか聞いてるディスカッションがあったり](https://www.kaggle.com/competitions/AI4Code/discussion/338863)はしましたが，この時点ではまだ試している人が本当に少なかったのか情報を隠しているだけなのか，試したと言っている人は現れませんでした．まあ自分は隠してましたが・・・

そしてちょうど締め切り3週間前のタイミングで，公開Codeとしては最高スコアになるアンサンブルノートブックが現れました．しかしここで使われているモデルはコンペ開始2週間で出てきたKhoi Nguyenさんとyuanzhe zhouさんのモデルのアンサンブルでスコアも85%程度と，ちょっといじって金ガチャ回すには無理なコンペでした．

### 締め切り1週間前まで (～8/5)
上位陣は猛烈にスコアが上がり，90%台のスコアが増えていきました．この時点で91%に届いた参加者もいたはずです．一方ディスカッションはバグ取りの話ばかりでスコア向上に使えそうな話はありませんでした．

ちょうど締め切り1週間前のタイミングで，再度クロールしたデータによるリークの結果とみられるPublic LB=93%のスコアが飛び出しました．私はそれまでクロールしたデータによるモデルはサブせずに隠していましたが，もしサブすれば同じくらいのスコアになるだろうと予想していたので，このタイミングで手法がバレたところでクロールも学習も間に合わないし，もう隠す必要もないと判断し同じタイミングでサブしました．そして案の定[他の参加者にクロールしていることがすぐバレました](https://www.kaggle.com/competitions/AI4Code/discussion/341920#1886552)．

### 締め切りまで (～8/12)
LBはデッドヒートが起こり，リークなしで92%に到達する人すら現れました．リークの結果とみられる93%越えは自分を含めて3チームいました．
モデル改善に有用なディスカッションはありません．己との戦いです．

### 締め切り後，およびPrivate LBの結果

このコンペではFeedback 3コンペと同じく，上位50位までの参加者はDiscussionに自分の手法の解説をあげることでKaggle swagをもらえる試みが実施されているため，結構な数の上位解法がDiscussionに転がっています．
また，賞金圏のチームのwinner's callもディスカッション上での手法の公開とInferenceコードのPublic化なので，少なくともInference部分については上位5チーム分全員公開されています（・・・と思っていましたがなぜか5位だけ見つかりません・・・）．

Private LBのスコアについては[Public LBからPrivate LBのスコアの変遷をまとめてくれた方](https://www.kaggle.com/competitions/AI4Code/discussion/363132)がいるので気になる方はそちらもご覧ください．簡単にまとめると以下の三点になります．
* 本コンペのスコアはかなり安定しており，特に上位ではリークの影響を除けばPublic/Privateでの大きなshakeは起きませんでした．
* リークらしい他2チームはやはりリークだったようで，自分を含めPrivate LBではスコアを落としています（それでも3チームとも金圏には残っています）．
* 他にも何チームかリークだったっぽい動きになっているチームがいました．

# 結局このコンペでは何が大事だったか？ (ポエム)

### 自分でモデルを組む力
個人的にはコンペ締め切り1か月前～2週間前というと，ここから参加し始める人もいたりして大抵のコンペではディスカッションが再度白熱する印象があったのですが，今回は有益な情報が全然増えませんでした．
おそらくですがデータセットが大きすぎて，ディスカッションで気軽に手法をさらけ出せないぐらい各人の実験コストが大きくなってしまったのではないのでしょうか．
そのため自分でモデル構造から考えて組む力がないと厳しいところがあったと感じました．

もちろん，モデルを組む力が試されることはすごくいいことです．みんながみんな同じモデル構造を使ってちょっとしたスコアの差を競うだけよりはるかに健全です．実際今回の上位手法はNLPコンペにしてはバリエーションに富んでたんじゃないんでしょうか．
ただディスカッションはディスカッションで終盤も多少は活気があった方がプラットフォームの雰囲気として明らかに好ましいです．そもそもディスカッションでどこまで自分の手法をさらすかは，他者からのフィードバックを期待しての打算込みの話になってしかるべきですし，出さない人がいても全然いいとも思いますが，それにしたって今回は本当に終盤のディスカッションが死にすぎでした．

ちなみに具体的な手法としては，計算時間の削減とcontextualizeのためにlist-wiseにすること (最重要)，quick-thoughtsライクな分類モデルならLSTMやattentionで他のセル情報をやりとりさせること，そしてBERTへの入力長をできる限り長くすることが重要だったようです．
それとMLMの追加学習は今回も初手レベルでした．自分の場合は1か月のdeberta-v3-largeのMLMの追加学習はやればやるほどスコアが伸びました．計算リソースの都合で止めただけでもっとやりたかったです．

### 計算力
残念ながら，本コンペでは計算力が如実にスコアに反映されたと言わざるを得ないです．
上位手法を見ればわかりますがmax_seq_len=1024のdeberta-v3-largeでこのデータセットを学習とか，BERTをセル数分並べてそれぞれエンコードして出てきた値をアテンションとか・・・
A100とは言わないまでも，3090程度はないと相当苦しい戦いだったんじゃないでしょうか．自分も3090をトータル2か月くらい回し続けたと思います．
もちろん計算力だけで勝てるわけではありませんが・・・

また，計算に回せる時間を最大化するために，コンペに早いうちに参加する必要もありました．今回上位に入った人たちは最初からコツコツ実験を積み重ねていた人ばかりなんじゃないでしょうか．逆に遅くから来た人の立場としてGMのCPMPさんも[訓練に時間がかかりすぎるコンペに直前に参加するべきではなかった](https://www.kaggle.com/competitions/AI4Code/discussion/343243#1893432)と発言していたりします（それでも金圏のすぐそばまで来ていて驚異的なのですが）．

# おわりに
いかがでしたか？最後の方は愚痴話が多くなってしまいましたが，個人的にはAI4Codeはトータルかなりの良コンペだったと思っています．特にモデルを組む力を要求されるコンペというところがよかったですね．データセットをApache 2.0ライセンスで再構築できうるというのも相当に魅力的なポイントでした．Private LBでリークしなそうという安心感も素晴らしいです．こういうのでいいんだよ．

Feedback 3にもあった上位50チームにswagを見返りに手法を書かせる試みは書く側としても読む側としてもかなり印象がいいです．どこからこの分のお金が来てるのかはわかりませんが，今後もぜひ続けてほしいところです．

計算力格差については，最近は巨大なデータセットを使ったコンペもよく生えてる印象ですし，今後プレイヤー同士で格差がますます開いていくんじゃないかという気もします．なんかいい感じの解決策ってないんでしょうか．いいの思いついた人はぜひKaggle運営にディスカッション投げてみてください．