Kaggle の Feedback Prize - Predicting Effective Arguments コンペでソロ参加銀メダル(43位)だった
本日終了したKaggleのコンペ、Feedback Prize - Predicting Effective Argumentsにソロで参加し、1566チーム中43位銀メダルを獲得した。暫定順位なので、確定順位はちょっと変わる可能性がある。
前回始めて参加したコンペではチームメンバーに恵まれ、たまたま金メダルだった。その中でKaggleの面白さを知って、次も参加したい、ただ個人だとモチベーションが無くなりそうなのでできたらチームで、と思っていたのだけど、今回はチーム組まず(というか知り合いが少ないので組めず、が正しいが…)でのコンペスタート。チーム参加だと実力がなくても場合によって金メダルも採れてしまう(前回の自分の成績)ことがわかったので、ソロ参加でどれぐらいの成績が残せるか、ということにもチャレンジ。
当初はモチベーションが続かないのではと懸念があったけど、最初に作ったベースラインが銀メダル圏だったので「これは金圏ワンチャンあるのでは!?」とモチベーションが高い状態でスタートして完走することができた。結果としては金圏には全く届かない感じで自分の実力不足や引き出しの狭さを認識できたのだけど、ソロ参加はソロ参加で色々なものを得られた。チームと違い自分ですべてのコードを書くので、コードの意図をちゃんと理解しなくてはならないとか、類似コンペで良いとされる手法を一通り試してみたりとか、全部一人で行うので知識の幅が広がる。反面、チーム参加のときのような一体感がなかったり、全く知らない知識がチームメンバーからもたらされたり、チーム参加で得られたものは得られなかったりもするのだけど、これは一長一短かな。
また、前回参加したPPPMコンペでは作る機械学習モデルはTransformersのエンコーダモデルで、今回のコンペも同様に自然言語処理のTransformersのエンコーダモデルを作る(であろう)コンペだったので、前回参加した知識もかなりの部分活かせた、というのも最初から右も左も分からない場所から取り組み始めたのではなく、最低限の知識はあった状態からスタートできた、というのもソロ参加でモチベーションが続いた要因の一つだったと思う。
なおコンペ終盤のチームマージ締め切り時に、複数のチームからチームマージのお声がけをいただき、なるほどソロで参加しているとこういう事が起きるのだな~という体験ができた。途中からソロ参加で成績を残すことも目標になってきたため、ありがたいお誘いなのだが今回は辞退させてもらったのだけど。
Feedback Prize - Predicting Effective Arguments はどんなコンペか
このコンペは、以前行われたFeedback Prize - Evaluating Student Writingの亜種的なコンペで、今回のお題は米国の学生が書いたエッセイの一部分がIneffective,Adequate,Adequateどれに属するかを当てるという、クラス分類問題。
実際のデータはこんな感じの3.7万行ぐらいのデータで、essay_id には別途紐づいたエッセイの長いテキスト情報が用意されていて、discourse_textはそのエッセイの一部分を取り上げたもの。公開ベースラインでは、discourse_type + discourse_text + [SEP] + essay_text
的なデータを作って学習するものがほとんど。この場合、essay_text の重複が起きまくるのであっという間に過学習になってしまうのが課題であった。また、同じエッセイは同一の生徒が書いたようで、エッセイごとにIneffective,Adequate,Adequateの偏り発生していて(そりゃ良いエッセイを書いた生徒の文章は全体的に良いであろう)、それもうまく活かせそうな感じ。
解法
金圏でもないし、あんまり参考にならないかもしれないが、自分の中でやった解法、効いた手法や学習効率化手法、うまく効かなかった手法などを。
行単位でなくエッセイ単位で見る
問題を行で見るのではなく、essay_id単位に見る。行単位だと3.7万行あるが、essay_id単位だと 約4200行。約1/8ぐらいとなり、学習速度も向上する。エッセイの一部分に必ずdiscourse_textが含まれるので、一文に複数出現する特定の部分のクラス分類問題として考えることができる。この場合、NER(固有表現抽出)と同じようなモデルで分類が可能になる。
先程のデータ例なら、こんな感じで[TAR_START]
と[TAR_END]
というspecial_tokenで挟んだエッセイのテキストを作って、このspetical_token間のクラス分類問題にできる。
[TAR_START]Lead so you want to take all the cars out of the city ok cool[TAR_END]. [TAR_START]Claim this will save the pepol like 1,000,000 dolers a year[TAR_END] and will [TAR_START]Claim reduce polution[TAR_END] and stuf i dont know. [TAR_START]Counterclaim i gess its a good idea but on the other hand nah i mean lookif pepal want to ruin the world with gas thats there choce man[TAR_END]. but i just relised that the thing seid to agrewith the pasige or whatever so yah. ummmmmmm i dont know [TAR_START]Position the eirth is cool so why distroit with gasis or something[TAR_END]. look to be honist...
クラス分類時に使う情報は、[TAR_START]
と[TAR_END]
の間の平均、[TAR_START]
と[TAR_END]
のtokenのみ、[TAR_START]
のみ等々を試したが、[TAR_START]
のみの情報が一番良かった。またspetial_tokenは使わずCLSとSEPのtokenのみや、TAR_ENDを使わない、SEPに置き換える等々もやったが、それらよりも自分で定義した[TAR_START][TAR_END]
のspetial_tokenのほうが良い結果となった。
トークナイザーのmax_lengthは1024で、この長さでも一部のエッセイがはみ出してしまうので、その場合は適当にdiscourse_textを詰める処理行った。
最終的にはdeberta-v3-largeを使ってCV0.5911
,LB0.585
,Private-LB0.586
の4foldsモデルと、devert-largeでCV0.6034
,LB0.595
,Private-LB0.597
のモデルを作り、他に初期に作った行単位で見るモデル(deverta-v3-large, CV0.6179
,LB0.608
,Private-LB0.604
)等々でアンサンブル。結果はLB0.582
でプライベートLB0.583
となった。
効いた手法
- AWP(Adversarial Weight Perturbation)
- スコア0.05~0.1ぐらい向上する
- AWPは摂動幅と範囲がパラメータなのだけど、エポックごとに摂動幅を増やすことでちょっとスコアが向上した。時間があればスケジューラを使った変更もやってみたかったな。
- テキスト処理で小文字化・記号系の削除
- スコア 0.03 ぐらい向上
- 再翻訳 augumentation
- 再翻訳したデータをモデルにかけて、正答率が90%を越えるエッセイのみを対象とした。
- スコア 0.03 ぐらい向上。
- 再翻訳しただけだと過学習になってしまったので、20%ぐらい
[MASK]
したデータを使った所、安定して学習が進んだ。
うまくいかなかった手法
あくまで自分がうまくできなかっただけで、別の知識を持った人がやればうまくいくことも多そうなのだけど。
- 最終出力前に LSTM や Bi-LSTM
- 過去のFeedback Prize - Evaluating Student Writingを用いた Pseudo Labelling
- torch.nn.CrossEntropyLoss の weight 追加
- データに偏りがあるので weight 追加したがうまくいかず
- エッセイに含まれるdiscourse_id が少ないものを削除
- 1エッセイ1discourse_id的なデータをなくしてしまう
- deberta-xlager, xxlarge
- 学習進まず
- allenai/longformer-large-4096
- 勾配がnanになって進まず
- allenai/longformer-base-4096 は進むがスコア悪い
学習効率化手法
今回はテキストがそこそこの長さなので、どう高速に学習させるかや省メモリで学習させることができるかもポイントだったと思う。Optimization approaches for Transformers にまとめられいて、詳しくはリンク先の記事を読んだほうが良いが、ざっくりまとめを。
- 8-bit Optimizers
- Optimiezer を 16bit ではなく更に省メモリの8bitで行う
- 具体的にはbitsandbytesを使って、AdamWと置き換えるだけですんなり動く。
- 試した感じ、スコア低下は感じられなく、実際に省メモリになった
- torch.nn.Embeddingからbnb.nn.StableEmbedding の置き換えがすんなり行かなかったで、うまく置き換えられればさらに良い結果になったかも。
- Gradient Checkpointing
- 不要な勾配を削除しながら計算する。削除したものが再度必要になれば逆勾配グラフを再構築するため、速度が落ちる。
- transformers なら
model.gradient_checkpointing_enable()
するだけで使える - スコアはほぼ変わらず、学習スピードは落ちるが大幅に省メモリに
- Automatic Mixed Precision (AMP)
- 問題ない部分はfp32からfp16としてメモリ配置、計算してくれる。
- 勾配オーバーフローが起きないために、GradScalerと使うのがポイント
- Gradient Accumulation
- メモリが少ないとバッチサイズが小さくなってしまうが、loss の適用を分割することで、バッチサイズを大きくとった時と同じような学習ができるようにする
- Freezing
- 入力レイヤに近い部分の学習は、学習率が減衰しているし、この部分はむしろ学習しないほうが良い結果になることもある
- そのため、その辺のレイヤをフリーズしてしまう
- Fast Tokenizers
- transformers の tokenizer を rust で書かれたものに変更する
- 最近は標準で rust 実装があれば使われるので、意識しなくても良い
- Uniform Dynamic Padding
- Dynamic Padding ではバッチごとの最長の token length に引っ張られるが、事前にtoken_lengthでソートすると、いびつな最長 token length が発生しない
- 学習時には通常ランダムで学習させたいから使うことが難しいが、推論時には高速化が可能
コンペを終えて
最初は「金圏ワンチャンあるのでは!?」とも思ったが、結果は銀圏の中盤で金圏に手が全く届かず、実力不足と自分の立ち位置を再確認できた。ちまちました改善だと金圏スコアに到達することは無理で、ドラスティックな改善が必要であったであろう。これから上位解法を観るのが楽しみだ。
前回参加したコンペと同等の自然言語処理コンペで使うモデルもTransformersのエンコーダモデルと同じような物にも関わらず今回銀メダル圏中盤の成績だったので、別のトピックを扱うKaggleコンペではメダル取れるかも微妙である。
チームでなく個人でやってみて、一人でも一応の成績は残せたし、自分一人でもモチベーションを持って楽しく取り組め良かった。あと銀メダル一個で Kaggle Competitions Master なので、引き続き興味が出そうなコンペはチームでもソロでも取り組んでいきたい。というか大体のコンペはやったことのない課題だと思うので、どれをやってもメダルは取れずとも知識として得るものは大きそうだ。