本記事では、あるLLM開発コンペで10 位に入賞したので、その開発手順についてまとめたいと思います。
目次
1. コンペ概要
コンペでは定められた期間中に指定された事前学習済みLLM を事後学習し、学習後のスコアを競い合う形式でした。
事前学習モデルとして指定されたモデルは以下でした。
- LLM-jp-3 (1.8, 3.7, 13B)
- gemma-2 (2, 9, 27B)
学習後の評価としては、運営側がElyza-tasks-100 を基に作成した100 個のプロンプトを学習後のLLM に推論させ、その結果をGemini を用いて自動評価する形でした。この自動評価で全体の上位30 位が最終ステージに進み、人手評価により最終的な順位を決定します。ここで上位20 位に入れば賞をいただけます。私は最終ステージで10 位となりました。
2. 開発手順
2.1. 開発手順のまとめ
端的に言うと、1 番性能が良さそうなモデルをなるべく良いデータで学習した、ということです。
開発の流れは以下のように行いました。
- コンペ試験問題の元となるElyza-tasks-100 の分析
- 人手で合成データの元となるシードプロンプトの作成
- (おそらく)shibu-phys 独自のgeneration 方式で合成データの生成
- データクレンジング
- LLM に学習させる(モデル選定を含む)
下記では、1 つ1 つの手順について解説していきます。
2.2. コンペ試験問題の元となるElyza-tasks-100 の分析
彼を知り己を知れば百戦殆からず、ということでまずは試験問題の元となるデータセットであるElyza-tasks-100 の分析、具体的にはタスク分類から始めました。あまりしっかり構造化できていないのですが、以下のような分類でタグづけしました。タスクの特徴や偏り、変わったタスクがないかを調べられば良かったので、これくらいでざっくりやっています。
2.2.1. 分類別リスト
2.2.1.1. 提案関連
- 提案
- 提案/知識
2.2.1.2. 知識関連
- 知識
- 知識(RAG)
- 知識(数式表現)
- 知識/抽出
- 知識/指示追従性
- 知識/推論
2.2.1.3. 抽出・推論関連
- 抽出
- 抽出/推論
- 推論
- 推論/評価
- 疑問
2.2.1.4. 言語処理関連
- 言い換え
- 文の修正
- 修正
- 要約
- 穴あき接続詞
2.2.1.5. 創作関連
- 提案/創作
- 創作/推論
- 小説
- ロールプレイ/質問
- メール返信
- 広告制作
- 大喜利
2.2.1.6. その他
- IME
- 偏見
- 感情分析
- 解釈
- 計算
特殊だと思ったタスクはIME と大喜利です。1 問ずつしかないのですが、学習データをLLM に作らせにくく、スコアを上げるのが難しかったです。
2.3. 人手で合成データの元となるシードプロンプトの作成
Llama2 のpaper に"Quality Is All You Need." と書かれている[Hugo et al. ('23); "Llama 2: Open Foundation and Fine-Tuned Chat Models"] ように、LLM の指示学習では品質が命となります。
世の中に落ちている学習用データセットよりも自作した方が高品質のデータセットを作れるだろうということで、LLM を用いて所謂合成データを作ることにしました。合成データを作るにしてもシードプロンプトが必要になります。Elyza-tasks-100 の使用はライセンス的に不可のため自作しました。本当に手で作ったので、100 個のプロンプトを作るのに12 時間くらいかかりました。正直ここが一番辛かったですね。まずはトピックを作らせそれらに関連するプロンプトを作らせる、というような人手を介さずにLLM に合成データを作らせる方法がどこかの論文で提案されていた気がするものの、品質を担保するために最初の100 個は人手でしっかりと作りました。
2.4. (おそらく)shibu-phys 独自のgeneration 方式で合成データの生成
前工程で作成したシードプロンプト"shibu-tasks-100" を基にLlama-3.1-70B-instruct を使って、学習用のデータ(プロンプト、問題)を増やしていきます。
まずは100 件のデータの1 件1 件から10 件の新しいプロンプトを作ります。GPT-4o などであればjson mode を使って1 回の推論で10 件のプロンプトを一気に出せると思うのですが、長文プロンプトもあるためLlama-3.1 では素朴にtemperature を最大にして10 回推論を回すことにしました。どうしても似たようなプロンプトが出てくる場合(特に短いプロンプトで顕著に起こる模様)があるものの、そのようなプロンプトは後工程のデータクレンジングで除きます。その後も4000 件ほどまでは、生成した10 件の類似プロンプトから1 つをランダムに選び、それを10 件に拡張するというやり方で増やしていきました(ここは下記の図と違います。図では簡略化して書いています)。ただ、やはり1 件を10 件にするやり方だと同じようなプロンプトが出てしまうことが多く、4000 件以降は1 つのプロンプトを基に1 つの新しいプロンプトを生成していきました。こうすることでよりタスクの内容の幅が出たように感じます。このデータ生成工程を10 回ほど繰り返し、約1 万件ほど新しいプロンプトを生成しました(プロンプトに対する回答は後工程のデータクレンジング後に生成しました)。
このデータ生成方法の思想としては、回数(世代)を経るごとに中心となるshibu-task から内容の幅を広げていき、多様な内容のタスクを生成しようというものです。こうすることで元のタスクと似たような問題により上手く対応できるようになるはずです。今回のデータ生成方法を「進化的データ生成 (Evolutionary Data Generation)」と命名したいと思います笑
2.5. データクレンジング
前工程でLLM を用いて生成したプロンプトは、どうしても似たようなプロンプトや問題になっていないような不適当なプロンプトが生まれてしまいます。似たようなプロンプトを多数学習させると過学習の恐れがありますし、不適当なプロンプトを学習してしまうと他のタスクがうまく答えられなくなる可能性があります。前述の"Quality Is All You Need." を達成するためにも合成データのデータクレンジングは不可欠です。
データクレンジングでは、以下の3つに当てはまるかどうかを判定させました。
- 問題に指示がなく、問題として成立していない場合
- 問題だけでなく答えが書かれてしまっている場合
- それ以外の場合でも問題として不適切な場合
このどれかに当てはまるプロンプトについて、目で見て確認しつつ除きました。結果的に8000 件ほどが残りました。コンペ期間中にLlama3.3 が出たので、同様の方式で6 世代分、約4000 件を追加し、最終的に学習データは1 万2, 3 千件ほどになりました。
※今回の開発では、プロンプトのみデータクレンジング対象としています。推論結果についてもクレンジングすべきであるものの、時間がなかったため今回は割愛しました。
2.6. LLM に学習させる(モデル選定を含む)
データクレンジング後に各プロンプトに対する回答を生成し、これでやっと指示学習用の学習データが揃いました。
下記で簡単に内容を説明していきます。
2.6.1. 学習データの順序
下記の知識を用いて学習データの順序を決定しました。
- 似たようなタスクはまとめた方が良い
- 簡単なタスクから難しいタスクへ移るように学ばせると良い(カリキュラム学習 [Bruce et al. ('23); Instruction Tuning with Human Curriculum])
- 数学系のタスクは学習の初めに入れると、性能が上がりやすい [Preferred Elements ('24); "1,000億パラメータの独自LLM「PLaMo-100B」の事後学習が完了", Nvidia ('24); "Nemotron-4 340B Technical Report"]
似たようなタスクについては、shibu-tasks を作った時点で各タスクに分類を付与していたので、それを基に並べ替えを行いました。全プロンプトをランダムに並べた場合でも学習させてみたのですが、並べ替えた方が性能が良かったです。
2.6.2. 使用したモデル
今回試したモデルは下記の3 つです。
- llm-jp-3 13B
- gemma-2 9B
- gemma-2 27b
どれも基本的には4-bit 量子化しLoRA で指示学習を行いました(gemma-2 9B だけは量子化しない場合も試したもののほとんど変わらなかった)。
結論、gemma-2 27B が最も性能が良かったため、採用しました。
2.6.3. 使用したパラメータ
データを何回繰り返して学習させるかについては1 epoch(1 回)と2 epoch(2 回)を試し、2 epoch を採用しました。
推論時のtemperature でも違いが出るようで、0 よりも1 の方がスコアが良かったため、採用しました。
2.6.4. 学習後のモデル評価
学習後の評価としては、Elyza-tasks-100 を用いました。
3. こぼれ話
-
今回試したどのモデルでも、SFT のデータが1000 件程度でElyza-tasks-100 の平均スコアが3 を超え、最近の事前学習モデルは高性能だなと思うとともに、3 を超えなかった方達のコメントを読むと改めてデータの質が大事だなと感じました。結局、モデル、学習データ、学習方法くらいしか主要な変数がないので、これらを最大限高めるしかないですよね。
-
数千件ほどで指示学習した後に、ちゃんと課題分析してスコアを向上しようと思い、Elyza-tasks-100 で、点数が1 点となった10 件について分析してみました。タスクとしてはIMF や大喜利などです。単純に考えると1 点10 個を全て5 点にできれば、平均点が0.4 点上がる計算になるので、効果が大きいですよね。結論、そのようなタスクでは合成データに問題があり、不適切なデータは除去しました。しかし、それらのタスクはそもそも正解データを作ることが難しいためにそのような問題が起こっており、人手で10 件ほどは確保したものの、その程度ではスコアが上がってくれず、結局緻密な課題分析はせずに学習データを増やすというゴリ押しで進めました。データ増やすと、スコアがちゃんと上がるのが面白かったです。ちなみに、回答の質を高めるために数百件はnvidia のllama-3.1-nemotron-70b-instruct を使用しています。
-
今回のコンペでライセンスを把握することの重要性を学びました。gemma を使うときはElyza-tasks-100 を学習に使うことができないということが判明し、事前に作ったスケジュールが大幅に遅延しました。外部のモデルやデータを使用するときはライセンスと複合したときの衝突が起きないか確認することが大事ですね。ちなみに、llama-3.1 等を学習データに用いたので、ライセンスの要求から今回作成したモデル名には"llama" を付与しています。
-
今回DPO を試そうと学習データまでは作成したのですが、27B ではメモリ不足のためA100 でもチューニングができなかったため断念しました。
-
gemma-2 の論文に異なる学習パラメータのモデルをモデルマージした、と書いてあったので、試そうとしましたが、時間がなく断念しました。
-
Sakana AI が提案した進化的モデルマージをやってみたいと思い、shibu-tasks をターゲットにmargekit を使う方法でコードを書いてみたのですが、上手くいかずこちらも時間がなく断念しました。どこかで是非やりたいです。
-
上記のようにいろいろ試したものの、業務やプライベートとの兼ね合いから実質2 週間ほどしか取れず時間が足りませんでした。平日は普通に仕事しているので、寝不足の日が続きましたね。ただ、これでモデル開発の肌感は掴めたので、今後この経験を活かしていきたいと思います。
4. まとめ
今回LLM 開発コンペに初挑戦し、多くの学びを得ることができました。個人で指示学習をやったことはあったのですが、既存のデータセットを学習させるだけでした。今回コンペということで、他の参加者と競い合いながら開発を行いより実務に使い経験が積めたと感じています。学習データを自作したので、ちゃんとデータを作ればこんなに良い出力が出るのだと感動しました。ぜひ案件でもモデル開発を行い技術を磨いていきたいです。最後に、このような機会をくださった運営の方々に感謝いたします。