# 確信度としきい値

すべての `Decision` には `confidence` があります。`choice` の確率で、0 から 1 の値です。訓練したヘッドではこれが較正されているので、判断に使えます。確信度の低い答えを人に回す、ユーザーに確認する、「どれでもない」と答えるなどです。

## 較正の意味 {#what-calibrated-means}

ヘッドが確信度 0.9 前後で返した答えをすべて集めます。その約 90% が正しければ、ヘッドは 0.9 で較正されています。全域で較正されているとは、どの `p` でも確信度 `p` の答えが約 `p` の割合で正しいということです。

誤差は ECE（expected calibration error、期待較正誤差）で測ります。答えを確信度でグループに分け、グループの平均確信度と精度を比べ、その差をグループの大きさで重み付けして平均します。0 が完璧です。デフォルトのモデルをラベル名なしで使ったテスト分割の結果です（`eval/results-bge-small.json`）。

| ラベルごとの例 | 5 | 10 | 25 | すべて |
|---|---|---|---|---|
| ECE、BANKING77 | 0.050 | 0.015 | 0.013 | 0.013 |
| ECE、CLINC150 | 0.020 | 0.015 | 0.016 | 0.008 |

これは 0.2、ラベル名ありの値です（`eval/results-h14.json`）。0.2 より前（すべての規模で SGD、ラベル名なし、`eval/results-bge-small.json`）：

| ラベルごとの例 | 5 | 10 | 25 | すべて |
|---|---|---|---|---|
| ECE、BANKING77 | 0.071 | 0.029 | 0.011 | 0.017 |
| ECE、CLINC150 | 0.028 | 0.010 | 0.015 | 0.008 |

較正が最も緩むのは、近いラベルで例が少ないときです（ラベルごとに 5 例の BANKING77）。2 から 10 ラベルの小さなタスク（all-MiniLM-L6-v2 で計測、`eval/tiny.ts`）では、ECE はラベルごとに 1 例で 0.020、5 例で 0.008 でした。

較正は正確さとは別です。80% の割合で正しく、0.8 と言うヘッドは較正されていますが、5 回に 1 回は間違えます。較正で手に入るのは、どの答えを信じるべきかを数字が教えてくれることです。

## 温度の当てはめ方 {#how-the-temperature-is-fitted}

ヘッドは各ラベルのスコアを出し、softmax がスコアを確率にします。softmax の前にスコアを温度で割ると、どのラベルが勝つかを変えずに、確率を鋭く（1 未満）または平らに（1 超）できます。`train()` は、訓練に使わなかった例でヘッドが正解する頻度に確率が合うように温度を選びます。

1. 例を取り分けます。デフォルトは 10%（`holdout`）です。それが 200 例未満なら代わりに 5 分割を使い、すべての例を 1 回ずつ取り分けます。
2. 残りで訓練し、取り分けた例のスコアを出します。その分割で訓練例が残らないラベルの例は飛ばします。そのラベルは選ばれようがなく、外れとして数えると温度が高くなりすぎるからです。
3. 0.2 から 5 までの温度を 0.05 刻みで試し、取り分けた答えで負の対数尤度が最小のものを残します。
4. 取り分けた答えが 50 未満なら、下限（bge-small は 0.5、他のモデルは 1）より下げません。そうしないと、少数の例での満点がすべての答えを確信度 1.0 に押し上げます。下限は `eval/small-floor.ts` で計測しました。0.5 では、ラベルごとに 1 から 5 例で訓練した bge-small のヘッドの較正誤差は 0.009 から 0.017 でした（1 では 0.087 から 0.133 で、確信度が低すぎました）。リリース前のビルドはすべてのモデルで 1 を使っていました。
5. すべての例で再訓練し、温度はそのまま残します。

ヘッドはこれを `head.temperature` に保存します。データが少ないときは 5 分割のステップが効きます。all-MiniLM-L6-v2 でラベルごとに 1 例のとき、ECE は 0.459 から 0.020 になりました。

## 自分のデータでしきい値を選ぶ {#pick-a-threshold-on-your-own-data}

`minConfidence` は確信度の低い答えを `null` に変えます。

```ts
const r = await d.decide(text, head, { minConfidence: 0.9 });
if (r.choice === null) askAPerson(text);
```

適切なしきい値は、ラベル、データ、間違いのコストで変わります。計測してください。ラベル付きの例の一部を訓練から外し、しきい値ごとに、保留になる答えの数と、残りの答えのうち正しい数を数えます。

```ts
import { createDecider, type Example } from "decidekit";

const d = await createDecider();

// Hold out a fifth of the examples. Shuffle first if they're sorted by label.
const cut = Math.floor(examples.length * 0.8);
const train = examples.slice(0, cut), test = examples.slice(cut);

const head = await d.train(train);
const rs = await d.decideMany(test.map((e) => e.text), head);

for (const t of [0, 0.5, 0.7, 0.8, 0.9, 0.95]) {
  const answered = rs.flatMap((r, i) => (r.confidence >= t ? [r.choice === test[i].label] : []));
  const right = answered.filter(Boolean).length;
  console.log(
    `threshold ${t}: null ${(100 * (1 - answered.length / rs.length)).toFixed(1)}%,`,
    `right when answered ${(100 * right / Math.max(1, answered.length)).toFixed(1)}%`,
  );
}
```

"right when answered" が用途に十分な最も低いしきい値を選び、null の割合が許容できるか確かめてください。そのあとで配布用のヘッドをすべての例で訓練し、しきい値はそのまま使います。

取り分けるセットには十分な大きさが必要です。テスト例が 20 なら、1 つの答えで結果が 5 ポイント動きます。どのラベルにも合わないテキストも想定するなら、その一部をテストセットに入れてください（ヘッドのラベルにない名前を付ければ、答えたときは必ず誤りと数えられます）。そしてそのどれだけが null で返るかを確かめます。

## ゼロショットの確信度 {#zero-shot-confidence}

ゼロショットの `decide()`（ラベルの配列か説明のレコード）には、温度を当てはめる例がありません。モデルごとに固定の温度（`createDecider({ temperature })`）を使います。デフォルトの bge-small は 0.03 です（説明付きラベルはスコアが高くなるので 0.025。ECE は BANKING77 で 0.042、CLINC150 で 0.020）。all-MiniLM-L6-v2 と他のモデルは 0.05 です。0.1.0 より前はすべてのモデルで 0.05 でした。BANKING77 と CLINC150 の dev 分割では、これらはほぼ較正された状態になりました。bge-small で ECE 0.03-0.04 です（0.05 の MiniLM は 2 から 150 の選択肢で 0.005-0.045）。ラベルに説明を付けると精度も上がります。[仕組み](/ja/decide/docs/how-it-works#describe-your-labels)を参照してください。これは 2 つのインテントのデータセットだけの計測です。あなたのラベルや説明では、スコアがもっと鋭くも平らにもなりえます。しきい値を置く前に、自分のラベル付きテキストでゼロショットの確信度を確かめるか、ヘッドを訓練してください。

## 「どれでもない」 {#none-of-these}

どのラベルにも合わないテキストの扱いは 2 通りあります。

**しきい値。** 本当のラベルだけで訓練し、`minConfidence` を設定します。話題外のテキストはどのラベルでもスコアが低くなりがちなので、`null` で返ります。範囲内の例だけで訓練した CLINC150 で、検証分割で選んだしきい値 0.9 を使いました。bge-small は、範囲外のテストクエリ 1,000 件の 87.0% に `null` を返しました（`eval/results-bge-small.json`）。その代償に、範囲内のクエリで答えてかつ正しいものは 84.7% でした。しきい値なしの正解は 94.4% です。正しい答えの一部も 0.9 を下回るからです。all-MiniLM-L6-v2 のしきい値 0.93 では（`eval/results.json`）、86.3%、82.9%、94.0% でした。ここまでは 0.2 より前の値です。0.2 で同じ方法で選んだしきい値は 0.98 で、92.8%、82.0%、96.3% です（`eval/results-h14.json`）。古いヘッドで選んだしきい値はそのまま使えないので、選び直してください。

意味を持たせるには、ラベルごとに十分な例が必要です。ラベルごとに 3 例のヘッドは "hello" に確信度 0.89 を付けました。CLINC150 にはラベルごとに 100 例あります。

**`other` ラベル。** 目にする話題外のテキスト（あいさつ、スパム、別の話題の質問）の例を集め、独自のラベルとして訓練します。話題外のテキストにはっきりした形があるときに効き、小さなヘッドでも高いしきい値なしで「どれでもない」と言えます。捕まえられるのは、例を与えた種類の話題外テキストだけです。`labelNames: false` で訓練するか、内容のわかる名前を付けてください。"other" という単語はヒントとしてほとんど役に立ちません。

2 つは組み合わせられます。わかっている話題外テキストには `other` ラベル、残りにはしきい値です。