毎年恒例のSanta2024に挑戦|Kaggleチャレンジ記録

毎年開催されているサンタコンペ(Santaコンペ)に今年も参加しました。結果は55位でした(55/1526)。最最初はLLMならではの効率の良い探索方法を考えるコンペかと思いましたが、結果としては普通に最適化問題を解くものでした。
Santa2024
- 例年開催のサンタコンペ。今回は少し早めに開始
- 今年は、順列の並べ替え問題
- 与えられた英単語(10〜100単語)を並べ替えて、最も評価値が小さいものを作れたら優勝
- 評価関数はPerplexity。この評価関数は、LLMの評価では有名
- 利用するLLMはgemma-9B
例年開催のサンタコンペ、今年も参加しました。今年は、単語の並べ替え(順列並べ替え)問題。与えられた単語数は(10〜100単語)で、最も評価値が小さい並びを探索するというものです。評価関数はLLMではお馴染みのPerplexityで、利用するLLMはgemma-9Bとなっています。
Gemma-9Bが結構ネックで、そのままだと16GB超えのメモリを消費するため、そこそこメモリを搭載したGPUが必要になります。なので、まずは、効率の良いアルゴリズムを探索する作業が必要になると考えました。
取り組み内容
概要
今年のサンタは、課題を理解するのは簡単でした。
まずは、gemma-9BをローカルのPCで動かし、Perplexityを計算できるようにしました。一応MacBook Proでメモリは潤沢にあるモデルを利用しているので、ローカルで動かすことはできました。ただ、T4x2のkaggleノートブックと比較すると2~3倍遅い!
とはいえ、ローカルで動くので、初期の探索プログラム作成&デバッグには大いに役に立ちました。
作成したコードは以下のようなものになります。
探索コード作成(1)
基本はアニーリング&ビームサーチ
最適化問題の王道の、シミュレーティッドアニーリングとビームサーチをベースに検討を始めた。問題なのは、次の状態を作成するアルゴリズム。
雛形を作成したあと、近傍探索(状態遷移)の方法を考える
2-opt
巡回セールマン問題で活躍する2-optを実装してみたがイマイチ。今回の問題ではABCの順番とCBAの順番で結果が大きく異なるので(巡回セールスマンでは等価)、当然と言えば当然
Swap
いくつかの単語を選択して、スワップする。最初は2つを選択してスワップするものを作成していたが、ある程度最適化が進むと効果が出なくなってきたので、n個をスワップするコードも追加
順列並べ替え
任意の単語からn単語を選んで順列入れ替えをする方法を検討。最初は全パターンをやっていたが、これだとn=3,4くらいまでしかできないので、m個のパターンをランダム選択するように修正
1単語を抜き出して、任意の位置に挿入
単語を1つ抜き出して、別の場所に挿入するという処理。後半のチューニングで効果が高かった方法の1つ
上記の4つを繰り返す
それぞれ、p=0.5などと設定して、一定確率で繰り返すようにしました。たとえば、p=0.5であれば、平均2回同じ操作を繰り返すことになります。これをやるようになって温度が高い時に他の状態に遷移しやすくなりました。

上記全てではなく、いくつかを選んだり、選択確率を個別に調整したり色々実験しながら処理を行いました。
アニーリングメインに
ビームサーチも試しましたが、こちらの方がうまく機能したので今回はこちらをメインにしました。
ここからは、アニーリング用のコードを整理し、パラメータチューニングや機能追加&結果確認がメインになります。
高速化の手法
Gemma-9Bのビット数を減らしたりすると高速化できるのですが、今回は評価値が変化するため実施しませんでした。実際、A100はfp16, MacBookではfp32にした(MacBookのtransformerがfp16をサポートしていなかったため)のですが、fp16だと異なる並びで同値がいくつかみられました。精度によるばらつきが結構大きい印象でした。
キャッシュ
高速化としては、キャッシュの導入だけにしています。一度計算したパターンに対する計算はキャッシュして、定期的にファイル保存する仕様にしています。次回はそれを読み出すことで計算をスキップできます。ただ、終盤はキャッシュが数ギガまで増加しました。
Google Colab Pro+の契約
KaggleのGPUでは全然足りなかったので契約しましたが、A100を使うとかなり高速に動かすことができることに気づきました。A100で動かすことで、コード改善などもはかどりました。また、kaggleやローカルでは試せないような重い探索も行うことができました。今回は、演算パワーを持っていることがかなり有利に働いたと思います。
その他
タブーリスト
こちらは、ChatGPTに教えてもらった手法ですが、アニーリングで同じ値の間を何度も行き来しないようにする手法で、直前の数十回の結果を覚えておいて、同じ値になるパターンは除外するという方法です。実装して、使っていましたがこれが効果的だったかどうかはわかりません。
近傍探索のパターンを色々変える
基本は上記でも書いた4パターンですが、近傍探索のパターンを減らしたりしながら何度も探索しました。ただ、あまり関係なさそうでした(結局、極端な遷移はスコアが大きく変化するため)
文字列の一部を探索対象から外す
例えば、「先頭から10文字だけ探索して、20文字は固定」などができるようにコードに追加を行い、さまざまなパターンで実施してみました。ID4とID5はこれで少しだけスコアを伸ばせました。ただ、単語列の一部だけ探索するようにししても単語列全体でperplexityを計算する必要があるため、スコア計算の時間が短くならないのは残念でした。
Google Colab Pro+を契約
GPU時間が足りない&ローカルのMacBookでは速度不足なので、Google Colab Pro+を契約してA100を使いました。500ユニットで50時間ちょっとしか利用できませんが、かなり高速です。

A100とMacBook Proは、FP32同士なら2〜3倍程度の差しかないのですが、A100ではFP16が選択できてFP16にすると圧倒的に速かったです。これのおかげで、Try&Errorをかなり行うことができました。今回はGPUリソースをどれだけ潤沢に使えたかも結果に大きく影響した気がします。
結果と考察
結果は55位の銀メダルでした
最初はLLMならではの探索方法を検討して、効率よく探索する問題かと思いましたが、結果としては普通の最適化問題でした。というのもgemma-9Bのperplexityは少しの変化で大きく変わるので、「部分的なperplexityを計算して結合する」「先頭からperplexityが小さいものを連結する」などの手法では全然スコアが向上しませんでした。perplexityの計算は複雑なので、スコアの変化を予測して探索に利用するのは難しいと初期の段階で感じ、普通の最適化問題として解くことにしました。
ここで問題となったのはgemma-9Bでスコアを計算する部分の速度です。これが結構時間がかかってしまうため、GPU時間をあっという間に消費します。
最初は、ローカル環境でプログラム作成&デバッグ、kaggleで探索を行っていましたが、あっという間にGPUを消費するので、Google Colab Pro+を契約して使いました。A100が高速で、これのおかげでかなりスコアUPができました。

今回は、GPUリソースを持っている人が圧倒的に有利だと感じました
ただ、ID3はランダムな状態からスタートしてアニーリングを何度もやりましたが、中途半端な回数でGPU時間がなくなってしまい中途半端な結果に終わりました。
とはいえ、とりあえず銀メダル圏内に残れてよかったです。