この記事で分かること
強いモデルの出力を教師データにして小さいモデルを追加学習 (蒸留) すると、形式はすぐ真似できるのに、JSON の後ろの方にあるリスト欄 (注意点・未解明点) だけが教師の半分以下しか出ない、という壁に当たりました。学習の設定を変えると全体は良くなりましたが、この欄だけはどう振っても動かず、最後は学習ではなく後処理で対処しました。同じ種類のタスク (長い構造化 JSON を出す蒸留) をやる方の参考になるよう、実測をそのまま書きます。
前提知識
- 蒸留 (SFT): 商用の強いモデル (教師) に同じ入力で JSON を書かせ、その「入力 → 出力」の対でローカルの小さいモデル (生徒) を教師あり微調整 (SFT) すること。
-
LoRA: モデル全体ではなく、小さな追加行列だけを学習する省メモリの微調整法。
rankはその行列の大きさで、大きいほど学習容量が増えます。 - mlx-lm: Apple Silicon 上で LoRA 学習ができる Apple のライブラリ。
- epoch: 学習データを 1 周すること。学習率 (lr): 1 回の更新でどれだけ動かすか。cosine: 学習率を途中から滑らかに下げていく方式。
- 以下の数値は、固定した評価用の入力 39 件に対して生徒に JSON を書かせ、各リスト欄の件数を平均したものです。同じ文の繰り返しは 1 件に数えます。
設定
- 生徒: Gemma 4 26B (MoE、4bit)、LoRA、mlx-lm、Apple Silicon 128GB
- 教師: 商用フロンティアモデルの出力 682 対 (同じ入力・同じ JSON schema)
- 出力 JSON の欄 (この順): 見出し → 要点 → 事実 (facts) → 記事間の相違点 (discrepancies) → 注意点 (caveats) → 未解明点 (unknowns)
1. 学習設定の見直しは効いた
初版は rank 8 / 学習率 1e-5 固定 / 1 epoch でした。これを rank 32 / 学習率 3e-5 (最初の 5% で上げてから cosine で下げる) / 勾配を 4 回分まとめて更新 / 2 epoch に変えた結果:
| facts | discrepancies | caveats | unknowns | 出力の文字数 | |
|---|---|---|---|---|---|
| 教師 | 10.95 | 2.05 | 3.03 | 6.49 | 3,524 |
| 初版 | 6.13 | 0.97 | 1.51 | 2.44 | 1,800 |
| 見直し版 | 9.51 | 1.69 | 1.74 | 2.64 | 2,955 |
39 件を 1 件ずつ比べると、discrepancies は 23 勝 4 敗、facts は 28 勝 3 敗でした。ただし初版は同じ設定で 4 回学習したうち一番良かった 1 本で (回によって caveats は 0.85〜1.54 まで散ります)、1 回ずつの比較は慎重に読む必要があります。
mlx-lm 固有の注意: LoRA の scale (既定 20) は他ライブラリの α/r とは違い、出力に直接掛かる係数です。Adam で学習すると実効的な 1 ステップの大きさは scale × 学習率になるので、「LoRA の学習率は 1e-4〜2e-4」という一般則をそのまま入れると 20 倍過剰になります。
2. epoch は 3 で頂点、4 以上は暗記
5 epoch 分を 1 回で学習し、1 epoch ごとに保存した途中経過を評価しました。
| epoch | 検証データの損失 | caveats | discrepancies | 文字数 | 同じ文を繰り返した件数 |
|---|---|---|---|---|---|
| 1 | 0.845 | 1.21 | 0.95 | 2,208 | 2 |
| 2 | 0.789 | 1.64 | 0.97 | 2,308 | 4 |
| 3 | 0.956 | 1.97 | 1.26 | 3,302 | 8 |
| 4 | 1.351 | 1.69 | 0.97 | 2,589 | 3 |
| 5 | 1.748 | 1.51 | 0.92 | 2,848 | 4 |
- 検証データの損失が最小なのは epoch 2、出力の質の頂点は epoch 3 で、ずれます。少量データの微調整では損失と品質が一致しない、という LIMA 論文の報告と同じでした。損失の最小点で学習を止めない方がよいです。
- ただし注意点があります。1 回の学習の途中経過を比べると、学習率の下がり具合が混ざります。学習率を最後まで下げ切った 2 epoch 版 (上の見直し版) は、5 epoch 予定の途中にある epoch 2 時点 (まだ学習率が高い) を大きく上回りました (discrepancies 1.69 対 0.97)。epoch 数を比べるなら、それぞれ学習率を下げ切った状態同士で比べる必要があります。
3. unknowns は何をしても動かない
上の全設定で unknowns は 2.6〜2.9 件 (教師 6.49) でした。学習率・rank・epoch・学習率の下げ方のどれにも反応しません。
考えている理由: JSON の末尾のリストでは、「もう 1 件足すか、閉じるか」の判断が , か ] のわずか数トークンに乗ります。学習の損失はトークンごとの平均なので、長文の欄が損失の大半を占め、この数トークンの判断はほとんど学習されません。推論モデルの蒸留で「終了トークンの判断が学習されにくい」という報告 (arXiv:2505.07961) と同じ構図です。JSON のリスト件数について直接調べた論文は見つけられませんでした。
4. 学習で解けない部分は後処理で受ける
- 最小件数チェック: caveats / unknowns が 2 件未満なら、同じプロンプトで最大 2 回生成し直し、一番件数の多い候補を採用します。指示文に「必ず書け」と足しても埋まらなかったものが、運用初日は 17 本中 4 本でこの再生成が動き、4 本とも 1 回で埋まりました。
- 繰り返しの畳み込み: 見直し版の設定は、同じ相違点を 36 回列挙するような繰り返しを出すことがあります。教師の出力に重複は 0 件なので、重複は常に誤りとして、保存前に同じ文を 1 件にまとめます。
5. 「良かったモデルから続きを学習する」は効かなかった
初版のうち一番良かった 1 本を起点に、低い学習率 (5e-6 固定) で元データも混ぜて続きを学習する方式を 2 回試しましたが、2 回とも初版より悪化し、新しく足したタスクの出力もまったく変わりませんでした。続きを学習するときは学習率をいったん上げ直さないと新しいデータに適応しない、という継続学習の報告 (arXiv:2308.04014, arXiv:2403.08763) と整合します。
追記: 動かなかったのは「8 層の中では」だった
上の実験はすべて LoRA を最後の 8 層 (30 層中) にだけ入れていました。その後、MLX の別の壁 (Metal の buffer 個数上限、MLX_DISABLE_COMPILE=1 で回避) を越えて全 30 層に LoRA を入れて 1 epoch 学習したところ、同じデータ・同じ学習率で次のように変わりました。
| 教師 | 8 層の最良 (3 epoch) | 30 層 (1 epoch) | |
|---|---|---|---|
| caveats | 3.03 | 1.79 | 2.58 |
| unknowns | 6.49 | 2.68 | 5.74 |
| 出力の文字数 | 3,524 | 2,872 | 3,068 |
| 同じ文の繰り返し / 3 欄すべて空 | 0 / 0 | 3 / 2 | 0 / 0 |
「何を振っても動かない」と書いた unknowns が、層数だけで教師の 9 割に届きました。epoch や学習率の調整は容量不足の中での最適化で、伸び代が小さかったことになります。LoRA の文献で「全層に入れる、特に MLP 側が鍵」とされている通りでした。後処理 (最小件数チェック・繰り返しの畳み込み) は安全網として残しています。
限界
- モデルは 1 系統、各設定 1 回ずつ、評価 39 件です。件数は品質そのものではありません (4 件を読み比べた範囲では、増えた項目はおおむね根拠のある内容でしたが、欄の取り違えや欄をまたいだ重複はありました)。
- 教師の API からトークンごとの確率が取れないため、確率を直接合わせる型の蒸留 (GKD など) は試していません。