# ファインチューニングよりヘッドの訓練：BANKING77 で 93.1% {#better-head-training-beat-fine-tuning-93-1-on-banking77}

decidekit 0.2 は BANKING77 のテスト分割で 90.6% から 93.1% に、CLINC150 で 94.6% から 96.1% に上がりました。エンコーダーは変えていません。最初にファインチューニングを試しましたが、開発用分割で BANKING77 が 2.5 ポイント、CLINC150 が 1.2 ポイント下がりました。効果があったのは、その上の小さなヘッドを当てはめる最適化手法を、[jimothy](https://www.npmjs.com/package/jimothy) の設定を使ったフルバッチの Adam に置き換えることでした。`npm install decidekit` で新しい訓練を使えます。

| テスト精度、全訓練データ | 0.1 | 0.2 | 変化 | 0.1 だけが正解した数 | 0.2 だけが正解した数 |
|---|---|---|---|---|---|
| BANKING77（77 ラベル） | 90.62% | 93.12% | +2.50 | 41 | 118 |
| CLINC150（150 ラベル） | 94.62% | 96.13% | +1.51 | 39 | 107 |
| HWU64（64 ラベル、調整には一切不使用） | 90.15% | 91.17% | +1.02 | 18 | 29 |

シードは 1 つで、デフォルトモデル（bge-small-en-v1.5）の q8 埋め込みをキャッシュして計測しました。対応のある検定（McNemar のカイ二乗）は、上の 2 行で 36.3 と 30.7 です。HWU64 の 2.1 はこのサイズでは有意ではないので、この行は良い兆候という以上のものではありません。`train()` を通した計測は [`eval/results-h14.json`](https://github.com/debuffjp/decidekit/blob/main/eval/results-h14.json) にあり、BANKING77 が 93.05%、CLINC150 が 96.29% です。同じ方法で計測した 0.1 は 90.7% と 94.4% でした。

すべての実行、テストの確認記録とスクリプトは [`eval/spike`](https://github.com/debuffjp/decidekit/tree/main/eval/spike) にあります。ファインチューニングのスクリプトは残しておらず、あるのは結果だけです。

## 前提 {#the-setup}

判定は 2 つの部分からなります。34 MB のエンコーダーがテキストをベクトルに変え、ヘッド（softmax の層が 1 つ）がベクトルをラベルの確率に変えます。すべてのヘッドが 1 つのキャッシュ済みエンコーダーを共有するので、ヘッドは小さく（77 ラベルで 118,580 バイト）、ブラウザのタブで訓練できます。

作業を始める前に合格基準を決めました。BANKING77 と CLINC150 の平均で +0.5 ポイント以上、どちらのデータセットとラベルあたり 5 例と 10 例で低下は 0.2 以内とします。さらに、HWU64 が下がらないこと、ラベルあたり 10 例以上で較正誤差が 0.03 以下であることです。調整にはシードで固定した 20% の開発用分割を使いました。最終候補ごとにテスト分割を見たのは 1 回だけで、その後は何も変えていません。

## 1. ファインチューニングは逆効果でした {#_1-fine-tuning-went-backwards}

ファインチューニングには、報告に使うベンチマーク以外の意図データが必要です。これで思った以上に候補が減りました。SLURP の 13,514 件のうち 5,133 件は HWU64 にそのまま含まれています。使ったのは SNIPS（CC0、13,576 件、7 意図）と PAWS の言い換えで、合わせて 41,119 ペアです。MultipleNegativesRanking で 2 エポック訓練し、RTX 5060 で 271 秒かかりました。

訓練の課題そのものでは成功に見えました。取り分けておいた SNIPS での accuracy@1 は 0.949 から 0.985 に上がりました。しかし、本当に必要なデータでは、試した両方のサイズで下がりました。

| 開発用分割の精度 | ラベルあたり | 元のエンコーダー | ファインチューニング後 |
|---|---|---|---|
| BANKING77 | 全部 | 92.45% | 89.91% |
| BANKING77 | 10 | 85.92% | 81.48% |
| CLINC150 | 全部 | 96.63% | 95.40% |
| CLINC150 | 10 | 93.22% | 90.20% |

どちらの列も同じ ONNX 書き出しと q8 量子化を通しているので、差の原因は書き出しではありません。検証はしていませんが、原因は大まかな 7 意図と Wikipedia の言い換えだと推測しています。`card_arrival` と `card_delivery_estimate` のような細かい違いを、エンコーダーが見分けにくくなったのでしょう。否定できたのは、このデータの組み合わせとこの設定だけです。

## 2. 改善の大半はヘッドにありました {#_2-most-of-the-gain-was-in-the-head}

同じく小さな意図分類器である jimothy は、古いエンコーダー all-MiniLM-L6-v2 で BANKING77 の 92.4% を報告しています。実行してみると、それ自身の分割でレポートの値は 92.66% でした。つまり効いているのはエンコーダーより最適化手法だと考え、その設定を私たちのヘッドで試しました。

各行は開発用分割で全データを使った 1 回の実行です。

| ヘッドへの変更 | BANKING77 | CLINC150 |
|---|---|---|
| 0.1：ミニバッチ SGD、重み減衰 1e-4 | 88.46% | 95.43% |
| SGD、重み減衰なし | 90.55% | 96.30% |
| ゼロから Adam、lr 0.05、重み減衰なし、100 エポック | 92.35% | 96.67% |
| 同じ設定で、ラベルの重心とラベル名から開始 | 92.85% | 96.90% |
| ゼロから Adam、400 エポック | 92.95% | 96.57% |
| L-BFGS、試した中で最良の重み減衰（1e-5） | 92.75% | 96.67% |

BANKING77 では、重み減衰をなくしただけで改善の約半分になり、残りの大半は Adam によるものです。L-BFGS、400 エポック、ラベルスムージングの差は開発用データで 12 問以内でした。シードが 1 つではこの差で順位は付けられないので、100 エポックの実行を採用しました。decidekit がすでに計算している重心から始めると、少ないデータでの優位が保たれます。ゼロからの Adam は、CLINC150 のラベルあたり 5 例でこれを失いました（91.36% から 90.12%）。

## 3. 少ないデータでは SGD のままです {#_3-small-data-keeps-sgd}

重心から始めても、CLINC150 のラベルあたり 5 例で Adam は 0.29 ポイント下がり、基準を超えました。そこで 0.2 は、ラベルあたり平均 8 例以上で Adam を使い、それ未満では SGD のままにしています。計測したのは 5 例と 10 例で、境界そのものは計測していないので、8 は判断で決めた値です。

| テスト精度、3 シードの平均 | ラベルあたり | 0.1 | 0.2 |
|---|---|---|---|
| BANKING77 | 10 | 84.43% | 87.52% |
| CLINC150 | 10 | 92.16% | 92.50% |
| HWU64 | 10 | 85.50% | 85.38% |
| 3 つすべて | 5 | 変化なし | 変化なし |

## 代償 {#what-it-costs}

- **訓練時間：** Node でキャッシュ済み埋め込みを使うと、BANKING77 は 84 秒から 123 秒に、CLINC150 は 167 秒から 332 秒に増えました。計測中のマシンは他の作業でも負荷がかかっていました。
- **ページの停止：** 問題になるものはありません。Ryzen 7 260 のノート PC の Chrome で BANKING77 を最初から最後まで訓練すると 208 秒かかり、最長の停止は 19 ms でした。
- **サイズ：** 増えません。ヘッドの形は同じです。
- **較正：** 全データでは悪化しません。3 つのデータセットすべてで誤差は 0.019 以下です。
- **しきい値：** 再訓練したヘッドはスコアが変わるので、0.1 で選んだ `minConfidence` はそのまま使えません。CLINC150 の範囲外判定のしきい値は 0.9 から 0.98 に変わりました。保存済みのヘッドは変更なしで動きます。

## ここからは言えないこと {#what-this-doesn-t-show}

- **HWU64 はラベルあたり 10 例で下がりました。** 低下は 0.12 ポイントで、3 シード、各 1,076 問のうち 4 問分です。合格基準の「下がらないこと」は少ないデータも含むのかを決めていなかったので、私たちはこれを、受け入れた未達と見ています。ラベルあたり 10 例の HWU64 では、1 つのシードの較正誤差も 0.034 で、上限の 0.03 を超えました。0.1 の最悪のシードは 0.048 でした。
- **全データの行はシードが 1 つです。** 対応のある検定が見ているのはテストセットで、訓練の実行ごとのばらつきではありません。
- **報告するベンチマークで調整しています。** 開発用分割はテストと分けましたが、BANKING77 と CLINC150 が選択に影響したことは変わりません。関わっていないのは HWU64 だけで、その改善幅は最も小さいものです。
- **jimothy に勝ったわけではありません。** 93.1% と 92.66% では分割とエンコーダーが異なり、設定は jimothy のものです。jimothy は Node で訓練するので、事前の訓練に向いています。
- **ラベルあたり 5 例は変わりません。** 対照学習の層が役立つかもしれませんが、実行していません。ヘッドごとに約 590 KB（見積もり）増えるうえ、ヘッドの変更ですでに基準を超えていたからです。

自分のラベルで実行して、何が変わったかを [Discussions](https://github.com/debuffjp/decidekit/discussions) で教えてください。

```bash
npm install decidekit@latest
```