CommonLit - Evaluate Student Summaries振り返り [24位]
はじめに
先日CommonLit - Evaluate Student Summariesコンペが終了し、結果はpublic5位->private24位で残念ながら金メダル獲得には至りませんでした。ほぼベストsubを選べており金圏subはなかったのでそういう点での悔しさはありませんでしたが、他の上位入賞者のソリューションを見るに、私の手元のCVスコアで改善が見られなかった実験(後述)がキーだったっぽいのを見ると少し悔やまれる結果でした。
今回は解法の解説がメインの記事になります。ちなみに終了一ヶ月前に金圏マージした海外の方と2名で取り組みました。
- 問題設計:英文(prompt_text)と生徒の要約文(text)が与えられ、後者に対するwording(言葉遣い)とcontent(内容)の2つのスコアを予測
- 評価指標:macro-F1 score
- 最終順位:24位/2,106チーム(チーム銀)
サマリ
うまくいったこと、うまくいかなかったこと、やるべきことは次のとおりです。今回は赤字の部分のみ解説します。
うまくいったこと
- prompt_textとtextで共通の単語やフレーズ、N-gramなどをインプットに追加
- 各種プーリング結果を結合させたカスタムヘッド
- 逆翻訳結果に対するPseudo labeling
- リッジ回帰によるアンサンブルウェイト算出(intercept=0, alpha=500)
- Weighted Loss
- レイヤーごとに学習率を変更
- Dynamic Paddingを利用した高速化
うまくいかなかったこと
- 一部レイヤーの重み凍結
- AWP
- MLM
- 目的変数の回転
- 学習と推論とで異なるmax lengthの利用
- より大きなmax lengthの利用
やるべきだったこと(敗因)
- より大きなmax lengthの利用の試行錯誤
うまくいったこと
prompt_textとtextで共通の単語やフレーズ、N-gramなどをインプットに追加
今回のコンペでは、要約対象の原文である"prompt_text"とそれを生徒が要約した"text"が与えられました。採点対象である後者だけでなく、原文である前者も重要なインプットであるわけですが、トークン数が多く、特にtestデータでは5000トークンを超えるようなプロンプトもあったため、長いトークンにも対応した形で学習・推論させるかが鍵でした。
金圏を目指す上では後述のmax lengthを増やして原文のprompt_textをそのまま投入する方が筋が良かったわけですが、我々はprompt_textとtext間で共通の単語やN-gramなど複数のアプローチで抽出し、各々をインプットの末尾に加えた異なるモデルを作成しました。結果的にCVスコア、LBスコア共に飛躍的に向上したので、これ自体は上位銀を取る上ではかなり有効な取り組みでした。
各アプローチによる抽出イメージは次の通りです。
ペア文章
文章A:He bought a red pen yesterday.
文章B:She bought a red pen two years ago.
共通◯◯
①単語:bought | a | red | pen | .
②N-gram(2-gramの例):bought a | a red | red pen
③フレーズ(3トークン以上):bought a red pen
④キーフレーズ(Rake):red pen
②N-gram系は共通部分が多いフレーズが混じるとどうしても抽出量が多くなってしまうので、次のようなアプローチで抽出した③フレーズなどもバリエーションに加えました。若干競プロを思い出しました。
- 共通3-gramを抽出し、各々の開始位置をリストに格納&昇順ソート
- 1の1つ目の要素をフレーズの開始位置とする
- 次の要素を確認し、以下の場合分けに沿って処理を進め、フレーズの終了位置を特定する
- 差が1→更に次の要素の確認に進む
- 差が1以外→前回要素+2がフレーズの終了位置とし、今回要素が新フレーズの開始位置とする
- 上記の繰り返し
各種プーリング結果を結合させたカスタムヘッド
BERT系のbackboneで獲得したembeddingをヘッド部分でどのように集約させるかは様々な方法がありますが、今回は各プーリング方法(Mean, Max, GeM, Attention)により得られたものを組み合わせるカスタムヘッドを作成しました。
例えばGeM Pooling × AttentionPoolingの組み合わせだと次のような感じ。
def forward(self, inputs):
# 中略 #
gemtext_out = self.gempooler(lasthiddenstate, attention_mask) attpool_out = self.attpooler(lasthiddenstate, attention_mask) context_vector = torch.cat((gemtext_out, attpool_out), dim=-1)
要約(text)部分以外をmaskさせるattentionアプローチも少し頭によぎりましたが、残念ながら他のアプローチを優先。
逆翻訳結果に対するPseudo labeling
要約を英語→中国語→英語に逆翻訳したものに対して、チームメイトの互いのモデルで予測した数値をpseudo labelingとして用いデータセットに加えたところ精度が向上しました。リークを防ぐために、お互いのCVを合わせてOOFでラベルづけしました。
ちなみにpseudo labelingデータの使い方として、次のようなものも試しましたが、大差はありませんでした。
- pseudo labelingで事前学習→元のデータセットで学習
- pseudo labelingデータに対する重みを調整
- pseudo labelingデータの目的変数をオリジナルの目的変数との加重平均に変換
リッジ回帰によるアンサンブルウェイト算出(intercept=0, alpha=500)
スタッキングには一部LightGBMも使いましたが、最終層にはリッジ回帰を用いました。また単純にリッジ回帰を用いるのではなく、次のような手法でアンサンブルウェイトを算出することで学習データへのoverfitを防ぐことができ、CV・LBスコア共に普通のリッジ回帰や線型回帰よりも良いスコアが得られました。
- 大きなalpha(=500)を設定(特定のモデルのウェイトが大きくなることを回避)
- interceptを0に固定(アンサンブルウェイト算出のため)
- coefficientsの和が1になるように標準化(アンサンブルウェイト算出のため)
- 全データに対してfitさせるのではなく、OOFで行い、coefficientsの平均をアンサンブルウェイトとする(過学習回避)
やるべきだったこと(敗因)
敗因はずばりmax lengthの調整に時間をかけなかったことです。テストデータには学習データに含まれないような長いトークンのprompt_textが含まれていることはdiscussionのLB probing情報からわかっていました。大きなmax lengthを用いて学習させたり、trainとinferenceで異なるmax lengthを適用するアプローチは試していたのですが、CVスコアが悪化したのでサブミットすらしませんでした。
長いトークンのプロンプトもそこまで多くはないだろうとタカをくくって、テストデータの分布をLB probingにより調査することや、上記のサブミットをサボってしまったことが悔やまれます。
終えてみて
本腰入れて取り組んだNLPコンペは今回が初めてでした。前回のPSPコンペに引き続き単独でpublic金圏までたどり着くことができたり、結果的に上位銀メダルを取れたことは成長を感じる部分でもありましたが、どちらかというと金メダルを取り切れない自分の実力不足を痛感した感じです。金メダルを求めてまた次のコンペを探してみます。
Predict Student Performance from Game Play振り返り [3位]
はじめに
2023/06/29に終了したKaggleのPredict Student Performance from Game Playコンペ(通称PSPコンペ)で3位入賞した時の振り返り記事になります。データリークやAPIバグに伴う度重なるコンペ延長により5ヶ月近く開催していたコンペでしたが、無事人生二度目の金メダルゲットとなりました。
- 問題設計:教育ゲームにおいて、プレイヤーの行動ログから問題の正解・不正解を予測する二値分類タスク
- 評価指標:macro-F1 score
- 最終順位:3位/2,051チーム
弊チームの解法は以下kaggle discussion上でもまとめてます。
- https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/420235
- https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/420274
コンペ参加に至るまで
2年前にKaggle masterになってからしばらくはKaggleから遠のいており、参加するにしても勉強がてら少しだけサブミットして終わりといった状況が続いておりました。しかし仕事が定時で終わることも多くなり、久々に本腰入れて2枚目の金メダルでも狙ってみるかという気持ちになり、年始にRTX3090を購入&人生初自作PCに挑戦し、コンペに参加する準備を整えました。2月下旬頃にどのコンペに出ようかと探っていたところ、ちょうど良さげなテーブルコンペがあったので参加することにしました。個人的なコンペ選びの主な観点は次の通りです。
コンペ選びの主な観点
- 運ゲーでない
- テストデータの量が十分多い
- テストデータの切り方がまとも
- 評価指標がまとも
- リソース格差の影響が大きすぎない
- 学びが多い
- データの性質上、工夫の余地が多い
- 初めて取り組む技術領域である
正直今回のコンペはデータを見た感じ学びが多いものではなさそうだったのですが、金メダルを狙う上では問題なかったので、こちらのコンペに取り組むことにしました。
コンペ参加からデータリーク発覚まで
データリークが発覚するまでの2,3週間ほどでざっくりと以下を実施しました。
序盤にやったこと
- キャッチアップ
- 公開ノートブック・ディスカッション読み漁り
- 過去類似コンペ上位解法の読み漁り
- ゲームプレイ
- ゲームのソースコード漁り
- ベースライン作成(GBDT)
- アンサンブル効果の確認
- GBDT+NN(公開モデル)
- スタッキング
- 推論余地の確認
- メモリ
- 推論時間
- 特徴量エンジニアリング
- 大量の集約特徴量
- 過去クエスチョンに対する予測値を用いた特徴量
- メタ特徴量
- 全問正解&クリア時間が短いユーザー行動から得た
- 高速化&省メモリ化
- 特徴量選択
- polarsの利用
- ハイパラチューニング用コードの作成
GBDTモデルをメインで作成しており、NNは公開ノートブックのoof予測値を用いてアンサンブル効果を確認したところ芳しくなかったので、特に育ててませんでした。この時点で一気に金圏まで来たものの、正直やることがたくさんあるようなコンペでもなかったので、あとはNNを育てるぐらいしかやることないなみたいな感じでした。
その後、コンペのテストデータのリークが発覚したことがKaggle Staffから通達され、しばらく様子見することになりました。
データリーク発覚からチームマージまで
データリーク問題により以下の対応がなされました。
運営の対応
- コンペの延長
- テストデータの差し替え
- 学習データの追加
- 生のログデータの公開
- APIの仕様変更
やることがあまりない中コンペの延長というのもきつかったのですが、差し替え後のテストデータのボリュームがかなり小さくなっていることがすぐにわかり、次のような懸念点が生じました。
テストデータ差し替えに対する懸念点
- 運ゲーになる可能性が上がる
- Final submission選択に迷いが生じる
- 推論時間が大きく変わるので戦略が変わる
モチベも下がってしまいましたが、とはいえ金メダルも欲しかったので、とりあえず配られた生のログデータの活用を検討しました。ログデータに含まれるプレイヤーと学習データに含まれるプレイヤーを比較したところ、後者に含まれないユーザーが前者に含まれることがわかったので、いわゆるリバースエンジニアリングに取り組み、追加のデータセットを獲得しました。結局、自分のモデルではprivate LBスコアに対してはあまり効果が得られなかったのですが、後述のチームメイトのNNモデルの学習に貢献できたので無駄な取り組みではなかったようです。また他の上位入賞者も同様の取り組みが功を奏したという意見が多かったです。
そんなこんなでコンペ再開後も金圏をうろうろしつつ、コンペ終了2週間前というところで同じく金圏付近の3名チームからチームマージの声がかかり、せっかくだしということで人生で初めてチームを組むこととしました。
チームマージからコンペ終了まで
全員外国の方だったので、コミュニケーションは英語、媒体はdiscordでした。この時点での相手のモデルは同じくGBDTで、NNは試行錯誤中みたいな状況でした。とりあえずマージ後に以下を実施しました。
チームマージ後にやったこと
- 知見やソースコード、追加生成データの共有
- アンサンブルの実施
- 共有してもらった知見を活用した特徴量エンジニアリング
同じGBDTモデルといってもアーキテクチャや特徴量が異なるおかげで多様性が生まれ、CV/LBスコアともにアンサンブル効果がそこそこありました。チームを組むタイミングや取り組み方って様々だと思いますが、中盤以降に組んで、互いのモデルをアンサンブルみたいなのがサブミット上限や多様性の観点からメジャーなのかなという気はしますね。でも日頃から知ってる人と序盤からチームを組んで、ディスカッションしながら進めるのも楽しそうです。
チームマージ後もコンペの再延期等ありましたが、public1位から3位付近をうろちょろしつつ、コンペ終了の日を迎えました。Final submissionsとしては以下の3つを選択し、3番目のsubmissionのおかげで3位入賞が果たせました。
Final submissions
- Best CV(自分のGBDT + チームメイトのGBDT)
- Best Public LB(チームメイトのGBDT)
- その他(チームメイトのNN + チームメイトのGBDT)
ちなみにprivate LB公開当時はpublic2位からのprivate1位だったのですが、public1位のチームがBANされたみたいで、最終的にはpublic1位からのprivate3位への転落になってました。多くのチームがshakeしている中耐えた方だと思いますが、手元にはprivate1位か2位相当のsubmission(自分のGBDT + チームメイトのGBDT)があったので、ひょっとしたらpublic/private両方1位が達成できたかもしれないと思うと、少しだけ悔しいですね。
PSPコンペを踏まえて
久々にガッツリコンペに取り組んだ結果、運ゲーじゃない形で2枚目の金メダルがゲットできて嬉しかったです。また、チームを組んで取り組むのも初めてでしたが、ソロでやるよりもずっと精神的に良く、結果も伴いやすいように感じました。今後もチームマージをする上では以下が重要かなと思うので、意識的に取り組んでみようと思います。
チームマージをする上で重要に感じること
- ソロの段階でLB上で存在感を示すこと
- ノートブックやディスカッション上で活発に活動すること
- 信頼できる人と組むこと
GMまではあと金メダル3つですが、得意そうなコンペでは金メダルを狙いつつ、そうでないコンペでも学びのために参加しようと思います。