NeurIPS 2024上位解法まとめ

# はじめに
初めて真面目に取り組んだKaggleコンペの上位解法を復習がてらまとめます。

# 1. 参加したコンペ
NeurIPS 2024 - Predict New Medicines with BELKA Predict small molecule-protein interactions using the Big Encoded Library for Chemical Assessment (BELKA)

https://www.kaggle.com/competitions/leash-BELKA

SMILESという文字列形式で表された化合物が特定のタンパク質3種類に反応するかどうか予測するテーブルコンペです。

# 2. データセット
コンペで与えられたデータの概要をさらっと紹介します。詳しくは本家のHPをご覧ください。
## 2.1 構造
至ってシンプルで、id ＋ smiles + protein nameだけです。学習データは3億行、テストデータは250万行ほどです。Kaggle Notebook上でtrain.csvを全部読み込もうとすると行が多すぎてクラッシュします。

## 2.2 building blockとcore
与えられた化合物はブロック（buildingblock:以下BB）の組合せになっています。
そしてBBはcoreと呼ばれる物質に合計3つ付いています。trainデータの化合物は全てtriazineという物質をcoreとしています。数式チックに表すと次式のとおりです。
```
smiles = BB1 + BB2 + BB3 + triazine core
```
この組み合わせが1億通りあります
1億の物質それぞれが3種のタンパク質に反応するか否かのデータなので3億行、と言うことです。

## 2.3 課題
どう下記課題に対処するかが重要なコンペでした。詳しくは3.2モデル選択にて書きます。
・testにはあるがtrainにはないBBを含む物質がtestの7割近くを占める
・しかもそのうちの半分はtriazine coreですらない

# 3. 解法
この記事のメインです。
Solitionは色々公開されていましたが、個人的に一番読みやすいと感じた以下のSolutionを紹介します。リソースがあればやりたかったことが書かれていたのも選んだ理由です。

https://www.kaggle.com/competitions/leash-BELKA/discussion/518951

## 3.1 前処理
smiles文字列から大きく分けて2種類の特徴量を生成しています。

## ECFP (Extended-Connectivity Fingerprints)
SMILES文字列を1024or2048ビットのデータに置き換えた"指紋"です。
指紋というくらいなので分子ごとに一意です。1024ビットでやってる方もいましたが、衝突（カブり）の恐れがあったからか2048ビットで処理してる人が多い印象でした。

## トークナイズ→エンコード→埋め込み行列
いわゆる自然言語処理です。一連の処理ではChemBERTaを使います。
ChemBERTaとはBERTという言語モデルの一種です。BERTとはGoogle発の現状最強の言語モデルです。Hugging Faceのtransformersライブラリを利用してBERTを使うことができます。Hugging Faceは自然言語処理分野でツールやコミュニティを提供する企業です。
このBERTをsmiles文字列の処理に特化させたものがChemBERTaということです。smiles文字列で事前学習させています。10Mというモデルと77Mというモデルが公開されていて、それぞれ1000万データ、7700万データで学習させたよーって意味です。上位陣は皆さんChemBERTa-77M-MTRを使用されてました。
#### ・トークナイズ
SMILES文字列をトークン化し、埋め込み行列を生成します。
トークナイザーによって文字列を意味のあるカタマリ（サブワード）に分けることができます。
ちなみに、、、
なぜサブワードに分けるのか。例えばunhappyという文字列。１文字１文字分けて文字同士の関係を学習する場合と、un/happyというサブワードに分けて学習する場合を比較します。新しく"un"fairという単語の意味を予測する際、あるいは"happi"nessという単語の意味を予測する際、サブワードを頼りに類推すると、なんとなくunfairは否定的な意味を持ち、happinessはプラスの感情っぽい意味を持つかもなって予想できる気がしますよね。サブワードごとに意味（特徴）を学習することで未知語への対応能力が上がるのです。これがサブワードに分ける理由です。サブワードに分けることをトークン化と言います。

#### ・エンコード
トークンに対してIDを付与します。これで機械が認識できるデータになります。

#### ・埋め込み行列
各トークン同士の関係性を表す特徴量を追加する、というイメージです。
埋め込み行列の解説はこちら↓

https://qiita.com/iamtaro/items/5fc250925144e6279a16

## 3.2 モデル選択
2.3課題で書いたように、testにはあるがtrainには無いBBで構成されるデータが半分以上を占めます。
trainに含まれるデータをshared、trainに含まれないデータをnonsharedと表すと、データは次の3種類が存在します。

1. shared BB + triazine core
2. nonshared BB + trinazine core
3. nonshared BB + non-triazine core

trainデータはすべて1です。testデータは半分ちょいが1で、あとは2か3です。配点は1,2,3の種類のデータに対して均等に行うというルールでした。つまりtrainっぽいデータしか予測できないモデルはあんまり意味をなさないということです。

上位陣は傾向が異なるshared/nonsharedのデータに対してそれぞれ別のモデルを適用していました。
### Nonshared
##### モデル：ChemBERTa
###### パラメータ：
・optimizer：AdamW
・Epoch数：0.01（小さ!!）

###### CVをどうするか問題：
5-fold CVをします。が、validation dataのBBが含まれるtrain dataは除外します。
これによりtrainingデータにないデータに対しての予測性能を評価できます。
聞けば簡単な方法ですし、汎化性能を評価する方法はこれ以外ないなって思うんですが、何で自分はやろうと思わなかったんだろう。

###### 小さなEpoch数：
上記CVを頼りに学習を進めたところすぐにOverfitした、ということでEpoch数を0.01という非常に小さな値に設定して各Epochのcheckpointを保存。その中で極端にスコアが小さなものを除外して、全てのモデルの平均を取っています。

### Shared
##### モデル：1DCNN
こちらの公開Notebookを皆さん使われてました。ちなみにこれ単体でPrivate Score = 0.26でSilver Prizeでした、、、笑

https://www.kaggle.com/code/ahmedelfazouan/belka-1dcnn-starter-with-all-data/notebook

## 3.3 おわりに
信頼できるCV手法を確立した段階でコンペの半分以上は終わったと言っても過言ではない、という言葉の意味を身をもって実感しました。LBで順位が上がっていくのが楽しく、modelの良し悪しはpublic scoreで判断してしまいました。次からはbaseline作成→CV手法の確立→feature engineeringやパラメータ調整、という基本に立ち返ってコンペに臨もうと思います。
間違いがあれば教えてください！！

## 追記：Solutionでは触れてなかったがDiscussionで書いてたこと
いくつかDiscussionで書かれていた内容についてメモします。上位SolutionのNotebookに書いてませんでしたが、皆さん当たり前にやってるから書くまでもないよってことなのでしょうかね。それともこれやらないほうがスコアが良かったのか、、、
### 異性体の扱い
本コンペで与えられたSMILESデータは立体異性体情報が含まれません。つまり、同じ物質だったとしても違う並びのSMILES文字列として表記される可能性があるということです。実際そういったデータが多く含まれていました。自然言語処理チックにSMILES文字列を処理する場合、異性体を放置してると精度が下がります。
そこで「SMILES」→「分子オブジェクト」→「SMILES」という変換を行うことで、同じ分子オブジェクトを持つ物質＝同じ構造を持つ物質＝同じ物質は同じSMILES文字列に変換されます。
これをやるかやらないかでLBスコアが0.01程度変わります。

### [Dy]の扱い
##### ・前提
ラベルデータ（タンパク質に反応したか否か）は実際に化学実験をすることで得られたものです。
ではどのように実験をしているのか。タンパク質に反応するか一分子ずつ試すと時間がかかりすぎるため、目的のタンパク質に対して50,000-500,000個の分子を一緒に入れて、どれが反応したかを一気に確認します。これをハイスループットスクリーニングといいます。
しかし、反応した物質を特定するために、BBなんなのか、coreはなんなのか確認し、その組み合わせの化合物は何なのか追跡するのは面倒です。バーコードのようなものがあればBBやcoreを調べずとも一瞬で分かるので楽ちんです。
そのバーコードとしての役割を果たすのがDNAです。全実験対象分子に対して一意となるようなDNAを分子にくっつけておけば、どの物質なのか後で追跡しやすいというわけです。

##### ・じゃあ[Dy]は何？
SMILESデータには[Dy]という文字列が必ず含まれていたのですが、これはここに上記DNAがくっついているよーっている意味です。しかしSMILESにおいて[Dy]は"ジスプロシウム"という金属を表します。本当は含まれない物質を含むデータとなり、このまま学習モデルに突っ込むと精度が低下します。

##### ・どう処理したのか
この場合Cheminformaticsの分野では炭化水素を使うそうです。周りに干渉しずらいかつ構造が単純であるという理由です。今回はエチレングリコール誘導体リンカー、というもので置き換えました。Discussion読んでてもこれが一番良さそうな雰囲気がありました。CとOとHのみからなる単純な物質（炭化水素）かつ、柔軟性が高いため、元のDNAリンカーの動きを十分再現できるというメリットがあります。
他の候補としてメチル基（Cのみ）があります。単純な構造で小さな物質なので、エチグリ誘導体リンカーと比較して計算負荷が減り、分子構造全体に影響を与えにくいという長所があります。しかし柔軟性が低いためDNAリンカーの結合を十分に再現できるかは微妙です。