Digital Reactor
AIエージェント統計・確率

評価データセットは何件必要か:合格率の幅と、改善を検出できる件数

評価データセットは何件必要か:合格率の幅と、改善を検出できる件数

はじめに

評価データセットを作るとき、最初に決まらないのが件数です。何十件という目安はよく共有されますが、その件数で何が言えるのかはあまり議論されません。合格率が90%と出たとき、それは「85%より上だ」と言える根拠になるのか。翌週プロンプトを直して92%になったとき、改善したと言ってよいのか。この2つは、同じ件数では答えられません。

現状を報告するだけなら、件数はそれほど要りません。難しいのは改善のほうで、5ポイントの改善を統計的に主張するには1400件ほど必要になります。ただしこれは、新旧を別々のケースで比べた場合の数字です。同じケースを両方のバージョンに通す設計に変えるだけで、必要件数は数分の1に下がります。このとき件数を左右するのは改善の大きさもありますが、それと同じくらい、2つのバージョンで結果が食い違うケースの割合が効きます。前者は誰でも見積もるのに、後者は測られていません。件数を決める前に、測るべきものが残っています。

対象読者:

  • エージェントやLLMアプリの評価データセットを設計する立場の方
  • 「合格率が上がった」を根拠に本番へ出す判断をしている方
  • 評価件数を増やす工数を、どこまでかけるべきか見積もりたい方

記事のポイント:

  • 二項比率の3つの区間(Wald・Wilson・Clopper-Pearson)のカバレッジを厳密に計算し、合格率が高い領域で正規近似が破綻することを示します
  • 改善を検出するのに必要な件数を検出力から逆算し、ケースを分ける設計と同じケースを使う設計で比べます
  • 必要件数が改善幅と食い違う割合の両方で決まることを示し、後者を予備調査で先に測る手順を出します

合格率だけを報告しても幅が分からない

100件のうち90件が合格したとします。合格率は0.90です。この数字だけでは、真の合格率がどのあたりにあるのかが分かりません。区間をつけて初めて「85%より上と言えるか」に答えられます。

区間の作り方は1つではありません。実務でよく使われる3つを並べます。nn 件のうち kk 件が合格したとして、p^=k/n\hat{p} = k/nzz は標準正規分布の分位点です。

Wald:p^±zα/2p^(1p^)n\text{Wald:}\quad \hat{p} \pm z_{\alpha/2}\sqrt{\frac{\hat{p}(1-\hat{p})}{n}} Wilson:p^+z22n±zp^(1p^)n+z24n21+z2n\text{Wilson:}\quad \frac{\hat{p} + \dfrac{z^2}{2n} \pm z\sqrt{\dfrac{\hat{p}(1-\hat{p})}{n} + \dfrac{z^2}{4n^2}}}{1 + \dfrac{z^2}{n}} Clopper-Pearson:[Beta1(α/2; k, nk+1),  Beta1(1α/2; k+1, nk)]\text{Clopper-Pearson:}\quad \Bigl[\, \mathrm{Beta}^{-1}\bigl(\alpha/2;\ k,\ n-k+1\bigr),\ \ \mathrm{Beta}^{-1}\bigl(1-\alpha/2;\ k+1,\ n-k\bigr) \Bigr]

Waldは正規近似をそのまま当てたもの、Wilsonは同じ近似を pp について解き直したもの、Clopper-Pearsonは二項分布から直接作る正確な区間です。

def wilson_ci(k, n, alpha=0.05):
    """Wilson のスコア区間。p̂ が0や1に寄っても幅がつぶれない。"""
    z = norm.ppf(1 - alpha / 2)
    p = k / n
    denom = 1 + z**2 / n
    center = (p + z**2 / (2 * n)) / denom
    half = z * np.sqrt(p * (1 - p) / n + z**2 / (4 * n**2)) / denom
    return np.clip(center - half, 0, 1), np.clip(center + half, 0, 1)


def clopper_pearson_ci(k, n, alpha=0.05):
    """Clopper-Pearson(正確)区間。ベータ分布の分位点から作る。"""
    lo = np.where(k == 0, 0.0, beta_dist.ppf(alpha / 2, k, n - k + 1))
    hi = np.where(k == n, 1.0, beta_dist.ppf(1 - alpha / 2, k + 1, n - k))
    return lo, hi

100件中90件合格のとき、Waldは [0.841,0.959][0.841, 0.959]、Wilsonは [0.826,0.945][0.826, 0.945]、Clopper-Pearsonは [0.824,0.951][0.824, 0.951] になりました。どれを使っても「85%より上」とは言い切れません。下限が0.85を下回っているからです。100件で分かるのは、真の合格率がおおよそ0.82から0.95のあいだにあるということまでです。

正規近似は合格率が高いところで壊れる

3つの幅は近いので、どれでもよさそうに見えます。区間の善し悪しは、幅よりもカバレッジで判断します。名目95%の区間が、実際に真の値を含む確率です。二項分布は離散なので、これは乱数を使わずに厳密に計算できます。kk について足し上げるだけです。

def exact_coverage(ci_fn, n, p, alpha=0.05):
    """名目 1-alpha 区間が真の p を含む確率。二項分布の総和で厳密に計算する。"""
    k = np.arange(n + 1)
    lo, hi = ci_fn(k, n, alpha)
    return float(np.sum(binom.pmf(k, n, p) * ((lo <= p) & (p <= hi))))

真の合格率に対するカバレッジ。50件でも200件でも、Wald区間は合格率0.95を超えたあたりから急落し、50件では最悪0.22まで下がる。WilsonとClopper-Pearsonは名目の95%付近を保つ。

のこぎり状に揺れるのは、二項分布が離散だからです。それを踏まえても、Waldの曲線は合格率0.95を超えたところで崖のように落ちます。50件のときの最小値は0.222、200件でも0.632でした。名目95%と書いてある区間が、実際には5回に1回も真の値を含まない領域があるということです。同じ条件でWilsonの最小値は0.896と0.920、Clopper-Pearsonは0.951と0.950で、名目の水準を保っています。

問題は、この崖がエージェント評価でいちばん使う領域にあることです。合格率が0.70の状態で本番に出す判断はしません。実際に見るのは0.90から0.98あたりで、そこはWaldが壊れている場所です。極端な場合、全件合格すると p^=1\hat{p}=1 で標準誤差が0になり、区間は点になります。100件全部通ったから合格率は100%だと報告するのと同じで、これは区間として意味を持ちません。

件数と95%区間の幅。合格率0.95ではWald区間がほかの2つより狭くなり、件数が少ないほど差が開く。

幅を見ると、合格率0.95のあたりでWaldはむしろ他より狭くなります。狭いのに当たっていないという性質が、いちばん扱いにくいところです。狭い区間が出ると精度が高いと読んでしまうので、報告を受けた側は気づけません。合格率が高い側を測るなら、区間はWilsonかClopper-Pearsonで出します。Clopper-Pearsonは常に名目以上を保つ代わりに幅が広めに出るので、安全側に振りたいときに選びます。

Wilsonで区間幅を0.10以下にするのに必要な件数は、真の合格率0.70で319件、0.85で194件、0.95で80件でした。合格率が高いほど少ない件数で足ります。分散 p(1p)p(1-p) が小さくなるからです。逆に言えば、まだ合格率が7割程度の初期段階こそ件数が要ります。

「改善した」と言うには何件必要か

ここからが本題です。区間をつけて現状を報告するのと、2つのバージョンを比べて改善を主張するのは、必要件数がまったく違います。

まず素朴な設計から考えます。評価ケースを2つに分け、片方をバージョンA、もう片方をバージョンBで評価する。あるいは新しいケースを追加してBを測り、以前Aで測った数字と比べる。どちらも2つの独立した標本を比べていることになります。

必要件数は2標本比率の検定から逆算できます。有意水準5%、検出力80%として、

n[zα/22pˉ(1pˉ)+zβpA(1pA)+pB(1pB)]2(pBpA)2n \approx \frac{\left[z_{\alpha/2}\sqrt{2\bar{p}(1-\bar{p})} + z_{\beta}\sqrt{p_A(1-p_A)+p_B(1-p_B)}\right]^2}{(p_B-p_A)^2}

が片群あたりの件数です。近似式なので、同じ件数でシミュレーションして検出力を確かめました。

比較片群あたり両群の合計実測した検出力
0.85 → 0.90(5ポイント)686件1,372件0.805
0.85 → 0.95(10ポイント)141件282件0.813
0.70 → 0.80(10ポイント)294件588件0.803
0.90 → 0.93(3ポイント)1,356件2,712件0.802

近似式の値でおおよそ80%の検出力が出ているので、式はそのまま使えます。

左:改善幅と必要件数。2ポイントの改善には9,448件、5ポイントで1,372件、10ポイントで282件。右:同じ改善幅でも、同じケースで両方を走らせれば191件から775件で足りる。

必要件数は改善幅の2乗に反比例するので、小さな改善を主張しようとすると急に増えます。2ポイントの改善なら9,448件です。評価ケースは正解を人が用意して初めて使えるものなので、この規模を作るのは現実的ではありません。数十件から百件規模の評価データセットで「2ポイント改善した」と報告されているとき、その数字はほぼ誤差の中にあります。

同じケースで両方を走らせると件数が減る

素朴な設計には無駄があります。ケースを分けると、AとBが別の問題を解いていることになり、ケースの難しさのばらつきがそのまま比較のノイズになります。同じケースを両方のバージョンに通せば、このばらつきは打ち消せます。

このとき見るのは合格率の差ではなく、食い違ったケースです。Aは通ったがBは落ちた件数を bb、Aは落ちたがBは通った件数を cc とすると、2つのバージョンが同じ結果を出したケースは差の情報を持っていません。bbcc だけを取り出し、食い違いのうちBが勝った割合が半分より多いかを検定します。McNemar検定と呼ばれる形で、実際には食い違い b+cb+c 件のうち cc 件が成功という二項検定になります。

def cells(p_a, p_b, disagree):
    """4象限の確率。disagree = b + c(食い違う割合)から b, c を決める。

    b = P(Aは正解, Bは誤り), c = P(Aは誤り, Bは正解), c - b = p_b - p_a。
    """
    delta = p_b - p_a
    c = (disagree + delta) / 2
    b = (disagree - delta) / 2
    return np.array([p_a - b, b, c, 1 - p_a - c])   # 両方正解, Aのみ, Bのみ, 両方誤り

合格率を0.85から0.90に上げるという条件は同じにして、食い違う割合だけを変えます。

食い違う割合必要な評価ケース数実行回数
6%191件382回
9%305件610回
14%466件932回
19%622件1,244回
24%775件1,550回
(ケースを分ける場合)1,372件1,372回

食い違いが9%なら305件で足ります。ケースを分ける設計の1,372件に対して4.5分の1です。実行回数は1件を2回走らせるので610回になり、それでも2.2分の1です。評価ケースを作るコストが実行のコストより高い現場では、同じケースを両方に通す設計を選ばない理由がありません。

用意した評価ケース数に対する検出力。同じケースで両方を走らせると300件で80%に届くが、ケースを半分に分ける設計では1,400件でようやく届く。50件以下では逆転する。

改善幅を5ポイントに固定したまま食い違いを6%から24%に動かすと、必要件数は191件から775件へ4.1倍になります。では逆に、食い違いを9%に固定して改善幅を動かすとどうなるかも見ておきます。

改善幅必要な評価ケース数
3ポイント840件
5ポイント305件
7ポイント148件
9ポイント86件

こちらは9.8倍動きました。必要件数は改善幅と食い違う割合の両方で決まり、効き方はどちらも同じ桁です。 改善幅のほうがやや強いくらいです。

対応のある比較で検出力が上がるという性質そのものは、乱数シードを変えるとスコアが動く話でも触れています。あちらは同じシード集合で連続値のスコアを比べる設定で、そこでは共通の変動が打ち消されるという同じ理屈が働きます。今回は単位が評価ケースで、合否という二値なので、打ち消したあとに残るのが食い違い件数という数えられる量になります。この量が測れることが、件数設計を計算に落とせる理由です。

件数が少ないと、そもそも有意になり得ない

図の左端では順序が逆転しています。50件以下では、同じケースを使う設計のほうが検出力が低いのです。理由は検定の仕組みにあります。両側5%で棄却するには、食い違いが6件以上あり、しかもその全部が同じ向きに出ている必要があります。5件では全部が同じ向きでも p=0.0625p = 0.0625 で、5%に届きません。

食い違いが9%の設定なら、25件で食い違いが6件以上になる確率は0.021、50件で0.293、100件で0.895です。25件では、結果がどう出ても改善を有意と言える組み合わせがほとんど存在しません。この状態で「有意差なし」と報告するのは、差がないことを示したのではなく、測れていないことを示しています。評価件数が数十件のうちは、検定の結論を出さず、区間の幅で語るのが限界といえます。

何件で何ができるか

ここまでの計算をまとめると、件数ごとに言えることが分かれます。

件数できることできないこと
20〜30件明らかな破綻の検出、評価の手順と合否基準を固めること合格率の報告(幅が0.3前後)、改善の検定
100件前後合格率を幅0.12〜0.14で報告、10ポイント規模の劣化の検出5ポイント以下の改善の判定
300件前後同じケースで比べ、食い違いが1割程度なら5ポイントの改善を検出ケースを分ける設計での5ポイントの検出
1,000件以上ケースを分ける設計での5ポイントの検出2ポイント規模の改善の判定

数十件の評価データセットは無意味ではありません。何を合格とみなすかを決め、入力の型を揃え、手順を回せる状態にするのは、件数ではなく設計の仕事です。そこが固まっていなければ、件数を増やしても数字は安定しません。ただし数十件で「改善した」と言うことはできないので、報告の書き方をそこに合わせます。

この計算が外れるとき

前提を置いています。緩む方向が分かれば、結論をどちらに寄せて読むべきかは判断できます。

各ケースの結果が独立という前提が最初のものです。同じ入力を2回流して結果が変わるなら、合否は1回の観測では決まりません。再実行のばらつきがある場合、1件あたり複数回走らせた多数決なり平均なりを合否とみなすか、ばらつき自体を分散の成分として分けて扱う必要があります。ここを無視して1回だけ走らせた結果で区間を作ると、区間は実際より狭く出ます。

評価が人手や別のモデルによる判定を含む場合、判定側の揺れも同じように入ってきます。判定者が変われば合否が変わるケースがあるなら、合格率の不確かさはケース数だけでは決まりません。判定の一致率を先に測っておくと、どこまで件数を増やす価値があるかが見えます。

ケースの代表性は件数では埋められません。本番の入力分布から外れたケースを1,000件集めても、区間は狭くなりますが中心が的を外します。件数を増やす工数と、ケースを本番の分布に近づける工数は別で、後者を先に片づけたほうが効くことが多いです。

評価を増やしながら結果を何度も見る場合、検定の性質が変わります。件数を増やしては有意かどうかを確認するという運用は、そのたびに検定を繰り返しているので偽陽性が積み上がります。この扱いはA/Bテストを毎日覗く場合の記事と同じで、事前に件数を決めるか、逐次的に見ることを前提とした手順に切り替えます。

最後に、ここでの必要件数は検出力80%での値です。80%は慣習にすぎません。見落としたときの損失が大きい判断なら90%を、探索的な比較なら60%でも構いません。必要件数は検出力を上げるほど増えるので、この選択も件数の議論に含めます。

まとめ

合格率をひとつ報告するのと、改善を主張するのは、別の計算です。前者は100件規模で幅0.12から0.14の区間が出せますが、後者はケースを分ける設計だと5ポイントの改善に1,372件、2ポイントなら9,448件を要します。同じケースを両方のバージョンに通す設計に変えると、食い違いが9%のとき305件まで下がりました。この設計での必要件数は、改善幅と食い違う割合の両方でほぼ同じ強さで動きます。前者は事前に見当がつくのに対し、後者は走らせないと分かりません。合格率が高い側を測るなら、区間はWilsonかClopper-Pearsonにします。Wald区間は0.95を超えたあたりでカバレッジが0.6や0.2まで落ち、しかも幅は狭く出るので、報告を受けた側が誤りに気づけません。

いま評価データセットの件数を決めようとしているなら、まず手元の数十件で新旧の両方を走らせ、結果が食い違ったケースを数えてください。その割合を上の表に当てれば、必要件数がすぐ出ます。同時に、いまの件数でWilson区間を引いてみて、その幅が意思決定に使える広さかを見ます。幅が広すぎるなら、増やすべきは件数か、それともケースの代表性かを分けて考えます。件数を増やす前に、同じケースで両方を走らせる形に評価の回し方を変えるほうが、多くの場合は先に効きます。

関連記事

← 技術ブログ一覧へ