AIの信頼度0.9とは?basal miniで見る校正とECE
AIの信頼度0.9は正答率90%を意味するのでしょうか。3モデル・計50件の実測結果をもとに、校正、ECE、自動化のしきい値の確かめ方を解説します。
👁 116閲覧数
信頼度0.9は、一見すると「10回中9回は正解する」という約束のように見えます。しかし、この数字だけでは何も証明できません。アプリで使うデータに近いデータを用意し、正解と照らし合わせる必要があります。ここでは、校正の意味を説明し、3モデルの保存済み結果を使った計算と、簡単なPythonスクリプトを紹介します。特に重要なのは、信頼度のしきい値と、許容できる誤り率は別のものだという点です。
要点(2026年10月19日)
- 校正では、モデルが示す信頼度と実際の正答率が一致しているかを確かめます。
- 保存済みのテストでは、basal miniは信頼度0.9以上の回答40件中35件が正解でした。
- データは50件 בלבדです。これは説明のための例であり、自動化のしきい値を証明するものではありません。
- しきい値は調整用データで選び、事前に分けておいた別のサンプルで検証してください。
AIの信頼度0.9が本当に意味すること
校正は、複数の予測を対象にした性質です。信頼度が0.9に近い回答を集めたとき、適切に校正されたモデルなら、その約90%が正解になるはずです。これは個々の回答が検証済みだという意味ではありません。また、正答率が低くても、誤りが多いことを正直に示すモデルなら、校正が良好な場合があります。
Guoらは、2017年のニューラルネットワークの校正に関する論文で、この違いを説明しています。正答率を上げるだけでは、確率が信頼できるものになるとは限りません。アプリでは、正解を選ぶことと、確認が必要なケースを信頼度で見分けられることの両方が重要です。
チャットボットが信頼度を表示しない理由
判定モデルでは、回答候補があらかじめ決められています。モデルは候補ごとの確率分布を返せるため、選択した回答の確率を、その選択の信頼度として扱うのが一般的です。正解ラベルと比べられる、具体的な値が得られます。
言語モデルも確率を扱っています。ただし、一部のインターフェースで利用できるlogprobsが示すのはトークンの確率であり、文全体が真実である確率ではありません。Structured outputを使えば、たとえばカテゴリ名を含むJSONのように、回答形式を指定できます。しかし、形式が整っているだけで信頼度が校正されるわけではありません。チャットボットにconfidenceフィールドを追加させる場合も、検証が必要です。チャットボットが信頼度を出せないというより、その値が何を意味し、どう測られているかが問題です。
実測データで見るAIの校正
2026年10月19日、3つの出力ファイルを使い、katとgold_katの一致率、および平均confを計算しました。basal-omni-1.0-mini Q8_0は10月19日にローカル環境で、Jev 1.13とPerplexity Decider v1.1 27Bは10月13日にAPI経由でテストしました。各モデルについて、難しいケースも除外せず、50件すべてを集計しています。ツール選択に関する別タスクは含めていません。この評価は保存された正解ラベルに基づいており、曖昧さがあり得る報告も同じ基準で扱っています。テストに使った報告はポーランド語です。以下に示す集計値はポーランド語で行ったテストの結果であり、日本語で再テストしたものではありません。
| モデル | 信頼度の範囲 | 平均信頼度 | 正答率 |
|---|---|---|---|
| basal mini | 0.5-0.7 | 60.9% | 2/2 = 100% |
| basal mini | 0.7-0.9 | 83.3% | 5/8 = 62.5% |
| basal mini | 0.9-1 | 97.7% | 35/40 = 87.5% |
| Jev 1.13 | 0.5-0.7 | 61.5% | 0/2 = 0% |
| Jev 1.13 | 0.7-0.9 | 79.2% | 4/9 = 44.4% |
| Jev 1.13 | 0.9-1 | 99.4% | 37/39 = 94.9% |
| Decider v1.1 | 0-0.5 | 47.3% | 1/1 = 100% |
| Decider v1.1 | 0.5-0.7 | 62.9% | 2/3 = 66.7% |
| Decider v1.1 | 0.7-0.9 | 81.5% | 2/5 = 40% |
| Decider v1.1 | 0.9-1 | 99.0% | 39/41 = 95.1% |
範囲の下限は含み、上限は含みません。ただし最後の範囲は1も含みます。該当するデータがない範囲は省略しています。全体の正答率は、それぞれ42/50、41/50、44/50です。分母の異なるこれらの数値を、basalのローカルテストで説明した47件の結果と混同しないでください。
校正プロットでは、横軸がグループの平均信頼度、縦軸が正答率です。理想的な点は対角線上に並びます。点が線より下なら過信、上なら信頼度が控えめです。basalの最も高い信頼度の範囲は、座標が97.7%、87.5%となり、対角線からのずれがはっきり見られます。
ECEとは?回答数で重み付けするずれの指標
ECEは、グループごとの正答率と信頼度の絶対差を、グループの件数で重み付けして平均した値です。basalで回答40件のグループは、全50件のうち40/50の重みを持ちます。このグループの差は約10.17ポイントで、ECEには約8.14ポイント分が加わります。他のグループも加えた結果は0.1302、つまり13.02ポイントです。
同じ範囲分けを使うと、JevのECEは0.1224、Deciderは0.0861です。値が低いほど、測定したずれは小さいことを示しますが、別のデータでも優れている証明にはなりません。ECEはグループの分け方に左右され、観測件数が少ないと値も不安定です。また、誤答率でも、別のベンチマークにおけるCalibrationのスコアでもありません。
自動化のしきい値は別のデータで検証する
まず、自動で処理する判断の許容誤り率を、たとえば5%または1%のように決めます。次に、しきい値を調整用データで選び、独立したデータで評価する前に固定します。測定するのは2つです。自動処理された判断の中での誤り率と、全件のうち自動処理できた割合であるカバレッジです。
しきい値を0.9にすると、basalは50件中40件を受け入れますが、そのうち5/40、つまり12.5%を誤ります。Deciderは50件中41件を受け入れ、2/41、約4.88%を誤ります。後者は少数サンプルでの観測としては5%を下回りますが、目標リスクを統計的に確認できたことにはなりません。
- 同じ例の別バージョンが調整用データとテストデータの両方に入らないよう、関連するケースはまとめて分割してください。
- 重要なカテゴリは個別に確認してください。全体の成績が良くても、出現頻度の低いクラスの成績が悪いことがあります。
- モデル、指示文、量子化、データの種類を変更したら、測定をやり直してください。
検証には何件のデータが必要?
50件あれば問題を見つけられることはありますが、誤り率1%を確認するには足りません。しきい値を事前に固定し、観測が独立していて、誤りが0件の場合、信頼水準95%での誤り率の片側上限は1 - 0.05**(1/n)です。上限5%には受け入れた判断が少なくとも59件、1%には299件必要です。数えるのは集めた例の総数ではなく、受け入れた判断の数です。誤りがあれば、必要なサンプル数は増えます。
basalのドキュメントでも、調整と評価を分け、統計的不確実性を考慮しています。具体的なツールの使い方はbasal-calibrateのガイドを参照してください。ここで重要なのは、どのモデルでも共通する原則です。しきい値の効果を正当化するために使う回答と、しきい値を選ぶ回答を同じにしないでください。
Pythonでファイルから校正を計算する
以下のコードをcheck_calibration.pyとして保存してください。標準ライブラリだけを使い、ここで説明した出力ファイルの形式に対応しています。python check_calibration.py results.json 0.9を実行すると、グループごとの値、ECE、カバレッジ、観測された誤り率が表示されます。このスクリプトは校正を行うものでも、しきい値を認定するものでもありません。
import json
import math
import sys
with open(sys.argv[1], encoding="utf-8") as f:
rows = json.load(f)["wyniki"]
rows = [r for r in rows if r.get("typ") == "zgl"]
if not rows:
raise ValueError("報告データがありません")
for r in rows:
c = r["conf"]
if not math.isfinite(c) or not 0 <= c <= 1:
raise ValueError("信頼度が範囲外です")
r["ok"] = r["kat"] == r["gold_kat"]
edges = [0, 0.5, 0.7, 0.9, 1]
ece = 0
for lo, hi in zip(edges, edges[1:]):
group = [r for r in rows
if lo <= r["conf"] < hi
or hi == 1 and r["conf"] == 1]
n = len(group)
if not n:
continue
conf = sum(r["conf"] for r in group) / n
acc = sum(r["ok"] for r in group) / n
ece += n / len(rows) * abs(conf - acc)
print(lo, hi, n, round(conf, 4), round(acc, 4))
print("ECE:", round(ece, 4))
threshold = float(sys.argv[2])
if not 0 <= threshold <= 1:
raise ValueError("しきい値が範囲外です")
accepted = [r for r in rows
if r["conf"] >= threshold]
print("カバレッジ:", len(accepted) / len(rows))
if accepted:
errors = sum(not r["ok"] for r in accepted)
print("誤り率:", errors / len(accepted))
else:
print("誤り率: 受け入れた判断がありません")
まずは各グループの件数を確認してください。2件中2件正解の「100%」から分かることはごくわずかです。モデルが返す数字を判断に使えるかどうかは、独立したサンプルで繰り返し確かめる必要があります。
計算元:ローカルファイルwyniki_basal_mini_q8.json、wyniki_jev113.json、wyniki_decider11.json。2026年10月19日に再計算。


