一個下午跑完六十組實驗,排行榜第一名就是最好的嗎?這篇用三個自己就能跑的模擬,帶你量出挑最高分到底會騙你多少!
三個模型、五套 Prompt、四種資料清洗方式,排列起來是六十組實驗。交給自動化流程,一個下午就跑完,排行榜第一名會自己浮出來。
問題從這裡開始:跑實驗變容易之後,誰來決定哪個結果值得相信?
這篇先回答一個更具體的問題:從六十組裡挑出來的第一名,分數裡有多少是運氣。答案可以用幾十行 numpy 算出來,結果比多數人以為的大。
要擋住運氣,靠的是一張在跑實驗之前就寫好的表,這篇叫它「Eval 設計卡」。Eval 是 evaluation 的簡稱,指用一批題目替模型或 AI 系統打分數。設計卡有五格:這次要推翻哪一句話、用哪些題目、怎麼評分、什麼時候停、結果怎麼報告。
每一格都先於結果寫下。它不保證每次決策都對,但能擋住最常見的錯:看了答案才出題。下面先從第一格講起,再用模擬實驗量出其他幾格各擋住了多少誤差。
快速回答 LLM Eval(用一批題目替模型或 AI 系統打分數)跑得越多組,第一名裡的運氣越多。在一組模擬裡,假設 60 組設定的真實準確率都是 70%,在同一批 200 題上挑出的第一名平均 77.3%,換 500 題新題目重測回到 70.0%。擋住運氣的方法,是跑之前寫好 Eval 設計卡:要推翻的假設、題目、評分、停止規則、報告五格,都在看到分數之前定下來,鎖起來的保留集留到最後才打開。
先寫下能被數據否定的具體假設,並在看到結果前鎖定比較條件與品質底線
假設一個團隊要改善文件問答。設計卡的第一格不要寫「新版比較好」,要寫一句可以被數據否定的話,例如:「對需要跨兩段文件整合的問題,新版能提高答案有來源支持的比例,且不增加沒有來源的斷言。」這句話同時寫下兩件事:要改善的對象,和不能犧牲的品質。
接著固定比較條件:同一批問題、同一份文件版本、同一套評分規則,只改這次要研究的因素。模型、Prompt、檢索和資料清洗一起變,分數上升也說不清是誰的功勞。想看因素之間的交互作用,可以做實驗設計,但要先決定哪些組合值得測、資源允許測到哪裡,不能事後把偶然的發現都當成原本的假設。
設計卡也要記錄「不改善什麼」。新版讓普通問題答得更快,卻讓資料不足時的拒答變少,團隊願不願意接受?這取決於產品風險,要在看到結果之前寫下來。同一個總分,對內部輔助工具和對外自動回答的客服,代表的決策可能完全不同。
三個模擬量出運氣成分:60 組挑最高分虛增 7.3%,反覆偷看 p 值讓假陽性飆至 27.4%
有了假設,接下來的三格(題目、停止規則、報告)各自在擋一種統計上的錯。我寫了三個模擬,把這三種錯各自量出來。程式只用 numpy,下面依序貼進同一個檔案就能跑,一般筆電幾秒鐘跑完。
想驗證的三件事
- 挑最高分會膨脹:候選組數越多,開發集上的第一名分數越高,就算每一組其實一樣好;換到沒看過的新題目,分數會掉回原形。
- 偷看會讓假陽性變多:每多幾題就看一次 p 值、顯著就停,宣稱「有差」的機率會遠高於表面上的 5%。
- 配對比較比較準:兩個模型答同一批題目時,用每一題的差異來算,標準誤會比把兩邊當成獨立樣本小,同樣的題數更容易看出真的差距。
跑出來的結果如果和這三點不同,上面的說法就要改。
前提假設
模擬的好處是答案已知:我知道每一組設定真正的準確率,才量得出偏差有多大。代價是所有設定都是我給的,所以先攤開:
- 1 到 200 組設定,真實準確率全都是 70%;開發集 200 題;第一名再拿 500 題新題目重測
- 每種組數 2,000 次
- A/A 測試:兩個完全相同的 Prompt,真實答對率都是 70%;每組每多 50 題看一次 p 值,p < 0.05 就停,最多 1,000 題
- 2,000 次
- 兩個模型答同一批 200 題,題目難易差很多;B 真的比 A 好,平均高約 6.6 個百分點
- 2,000 次
另外兩個前提:實驗 (1) 假設每一組設定答對與否彼此獨立;p 值用常態近似的雙尾檢定,這是比較兩個比例最常見的做法。
思路與程式碼
先放共用的設定:亂數種子固定為 16,所以你跑出來的數字會和文中一樣。p_value 把 z 分數換成雙尾 p 值。
import numpy as np
from math import erf, sqrt
rng = np.random.default_rng(16)
REPS = 2000 # repeat every setting 2,000 times
TRUE_ACC = 0.70 # every configuration is truly 70% accurate
def p_value(z: float) -> float:
"""Two-sided p-value under the normal approximation."""
return 1 - erf(abs(z) / sqrt(2))
實驗 (1) 模擬「k 組設定一起跑、挑開發集最高分」。每一組在 200 題上的答對題數,是一個二項分布的抽樣;max(axis=1) 就是排行榜第一名。最後再抽一份 500 題的新題目,代表第一名拿到保留集重測:因為每一組真實都是 70%,第一名換到新題目,就是另一次 70% 的抽樣。
N_DEV, N_HOLDOUT = 200, 500
for k in (1, 5, 10, 20, 60, 200):
dev = rng.binomial(N_DEV, TRUE_ACC, size=(REPS, k)) / N_DEV
best = dev.max(axis=1)
print(f"(1) {k:>3} candidates: best on dev {best.mean():.1%}, reaches 75% in {(best >= 0.75).mean():.0%} of runs")
holdout = rng.binomial(N_HOLDOUT, TRUE_ACC, size=REPS) / N_HOLDOUT
print(f" the winner on a fresh holdout: {holdout.mean():.1%}")
實驗 (2) 是 A/A 測試:兩邊完全一樣,任何「顯著差異」都是假陽性。每多 50 題就做一次兩比例檢定,第一次 p < 0.05 就停下來宣布有差;同時記下「只在 1,000 題時看一次」的結果當對照。
STEP, MAX_N = 50, 1000
peek_hits = fixed_hits = 0
for _ in range(REPS):
a = rng.random(MAX_N) < TRUE_ACC
b = rng.random(MAX_N) < TRUE_ACC
for n in range(STEP, MAX_N + 1, STEP):
pa, pb = a[:n].mean(), b[:n].mean()
pool = (pa + pb) / 2
if p_value((pa - pb) / sqrt(2 * pool * (1 - pool) / n)) < 0.05:
peek_hits += 1
break
pa, pb = a.mean(), b.mean()
pool = (pa + pb) / 2
fixed_hits += p_value((pa - pb) / sqrt(2 * pool * (1 - pool) / MAX_N)) < 0.05
print(f"(2) false positives: peek every {STEP} -> {peek_hits / REPS:.1%}, look once at {MAX_N} -> {fixed_hits / REPS:.1%}")
實驗 (3) 讓題目有難有易:每一題有一個難度值,兩個模型答對的機率都隨難度變化,B 在每一題都比 A 好一點。不配對的標準誤把兩邊當成兩群獨立的人來算;配對的標準誤先算每一題的「B 減 A」,再看這 200 個差值的波動。
N_Q = 200
se_unpaired, se_paired, gaps = [], [], []
win_unpaired = win_paired = 0
for _ in range(REPS):
difficulty = rng.normal(0, 2.0, N_Q)
a = rng.random(N_Q) < 1 / (1 + np.exp(-(1.0 + difficulty))) # model A
b = rng.random(N_Q) < 1 / (1 + np.exp(-(1.5 + difficulty))) # model B, truly a bit better
diff = b.astype(float) - a
su = sqrt(a.mean() * (1 - a.mean()) / N_Q + b.mean() * (1 - b.mean()) / N_Q)
sp = diff.std(ddof=1) / sqrt(N_Q)
gaps.append(diff.mean())
se_unpaired.append(su)
se_paired.append(sp)
win_unpaired += diff.mean() / su > 1.96
win_paired += diff.mean() / sp > 1.96
print(f"(3) average gap {np.mean(gaps):.1%}; SE unpaired {np.mean(se_unpaired):.2%}, paired {np.mean(se_paired):.2%}")
print(f" B shown better at 95%: unpaired {win_unpaired / REPS:.0%}, paired {win_paired / REPS:.0%}")
結果
先看實驗 (1)。把每種候選組數的第一名分數畫出來:
只有 1 組時,開發集分數平均 69.9%,就是真實水準。候選變成 60 組,第一名平均 77.3%,而且 2,000 次模擬裡有 99% 的第一名超過 75%。這 7 個百分點全部是運氣:第一名拿到 500 題新題目重測,平均 70.0%。
這個數字可以手算出來。200 題、答對率 70% 的分數,本身就有約 3.2 個百分點的標準誤(√(0.7 × 0.3 ÷ 200))。60 個獨立抽樣裡的最大值,平均落在中心之上約 2.3 個標準誤,70 + 2.3 × 3.2 ≈ 77.4,和模擬的 77.3% 幾乎一樣。候選越多,最大值離中心越遠,這就是挑最高分的膨脹。
實驗 (2):每 50 題偷看一次、顯著就停,A/A 測試有 27.4% 會宣布「兩個 Prompt 有差」;只在 1,000 題看一次,是 5.9%,接近名目上的 5%。每偷看一次,就多一次被雜訊騙到的機會;20 次查看彼此相關,所以沒有膨脹到 64%(1 − 0.95²⁰),但也從 5% 變成了五倍多。
實驗 (3):B 平均比 A 高 6.6 個百分點。不配對的標準誤是 4.63 個百分點,配對的是 3.67,小了約兩成。換成判斷力:在 95% 的水準下,不配對只有 25% 的模擬看得出 B 比較好,配對有 44%。原因是題目難易這份波動兩邊共享,相減之後就消掉了。但要注意,就算配對,200 題也只有不到一半的機會看出 6.6 個百分點的差距:題數該多少,要在跑之前算。
回到想驗證的三件事,三件都成立:
- 60 組的第一名:開發集平均 77.3%,保留集 70.0%
- 成立
- 每 50 題看一次 27.4%,只看一次 5.9%
- 成立
- 標準誤 4.63 → 3.67 個百分點;看出差距的機率 25% → 44%
- 成立
這是模擬,數字只在這組設定下成立。可以帶走的是三個方向,和量它們的方法。你可以把題數改成 1,000、把候選組數改成 6,看膨脹縮小多少。
OpenAI 停用 SWE-bench Verified:被反覆使用兩年的保留集,59.4% 難題有錯且已遭訓練污染
公開的 benchmark,本質上是整個產業共用的保留集。實驗 (1) 講的事,在 2026 年 2 月發生在最有名的那一份上。
SWE-bench Verified 是 OpenAI 在 2024 年 8 月整理的 500 題真實 GitHub issue,請模型修 bug,再用專案原本的測試判定修對沒有。發表後,它成了各家發表新模型時必報的分數。2026 年 2 月 23 日,OpenAI 宣布不再回報這項成績,理由有兩個,剛好對上設計卡的兩格。
第一個是評分本身有問題。最佳成績在六個月內只從 74.9% 進到 80.9%,OpenAI 於是去查剩下的題目:挑出 o3 跑 64 次都沒能穩定解出的 138 題,每題至少六位資深工程師獨立審查,發現 59.4% 的題目在測試設計或題目描述上有實質問題,有的測試會擋下功能正確的解法,有的測試檢查了題目根本沒提到的功能。分數卡住的地方,有一大半是考卷出錯。
第二個是保留集已經不再保留。題目來自公開的開源專案,各家都可能拿來訓練;OpenAI 測試的前沿模型都能重現部分題目的標準答案或題目細節,而看過題目的模型比較容易過關。OpenAI 的結論是,這份 benchmark 上的進步,越來越只反映模型在訓練時看過多少題目,不代表真實的寫程式能力。
一份被全產業反覆使用兩年的保留集,最後量到的是大家看過它幾次。團隊內部的測試集也會走到同一步,只是沒有人會替你發公告。
實務落地五個決定:分層抽樣、鎖定保留集、校正評審偏誤、定好停止規則與交代配對差異
把上面的機制落到一次真的 LLM Eval,大致是五個要事先決定的事,順序就是設計卡的順序。
決定一:題目怎麼抽
測試集要從產品真正會遇到的問題抽出來,而且要分層。以文件問答為例,先列出題型:簡單問答、跨文件整合、資料不足應該拒答、需要引用原文位置。每一層各抽一定題數,讓小題型也有足夠的題目可以單獨看;總分再依實際流量的比例加權。
只從過去答得好的案例抽樣,分數會很漂亮,但回答不了上線後最棘手的問題。拒答題尤其容易被漏掉,因為它在日誌裡看起來像「沒有答案」。
決定二:開發集和保留集怎麼切、多久換
開發集讓團隊每天看、每天改 Prompt;保留集鎖起來,只在要做決定時打開,例如上線前,而且每打開一次都記下來。Dwork 等人 2015 年在《Science》的研究說明了原因:同一份保留集被反覆查詢,選出來的方案會開始貼合保留集本身。
保留集也要輪替。定期從最新的產品日誌補進新題目、汰換舊題目,避免它和實驗 (1) 的開發集一樣,被看熟了。保留集一旦被拿來挑方案,那一輪的結論就降級成探索性結果,下一輪再用新題目確認。
決定三:誰來評分
能用規則判定的就用規則,例如有標準答案的題目或單元測試;但規則本身也要查,SWE-bench Verified 就是測試寫錯的例子。開放式的答案,越來越多團隊用另一個 LLM 當評審。Zheng 等人 2023 年的研究發現,GPT-4 當評審時和人類的一致率超過 80%,和人與人之間的一致程度差不多;同一篇研究也列出它的偏誤:偏好排在前面的答案、偏好比較長的答案、偏好自己產生的答案。
- 有明確對錯的題目
- 規則本身可能寫錯,或比題目要求得更多
- 建立基準、開放式答案
- 慢又貴,標註者之間也會不一致
- 大量開放式答案
- 位置、長度、自家模型三種偏誤
- 多數團隊的預設
- 抽查比例要固定,評審模型換了就重算一致率
實務上的做法:兩兩比較時把答案順序對調再評一次,兩次一致才算數;評審不要和受測模型同一家;定期抽一批題目給人工標註,算評審和人的一致率,一致率不夠就先別相信評審給的排名。人工標註也一樣,先給標註者相同的判準,再抽一部分重複標,看分歧集中在哪些題型。評分者自己都判不一致時,精確到小數點的排名只是看起來精確。
決定四:什麼時候停
實驗 (2) 說明了「看到顯著就停」的代價。停止規則有幾種寫法:
- 事先估要測幾題才看得出想要的差距,測完才看
- 最乾淨,但要先估差距多大
- 實驗 (2) 的做法
- 假陽性從約 5% 變成 27%
- 用專為反覆查看設計的方法,例如 Johari 等人提出的 always-valid p 值
- 可以提早停,但方法要事先選好
候選組數也算在停止規則裡:事先寫下要比幾組,跑到一半想加新組合,就記下來,報告時一起交代。實驗 (1) 已經量過,組數本身就會把第一名推高。
決定五:報告怎麼寫
報告的目的是讓讀者判斷這次比較能支持多強的說法。Anthropic 2024 年整理的模型評估建議是同一個方向:分數旁附標準誤;比較兩個模型時,用同一批題目算配對差異;用檢定力分析決定需要幾題。一份 Eval 報告至少交代:
- 總分與 95% 信賴區間,以及和基準版本的配對差異
- 各層的分數與題數,退步的層要列出來
- 一共試了幾組才挑出這一組
- 評分方式,以及評審和人工抽查的一致率
- 保留集這次是第幾次打開
Anthropic 那篇建議用一張圖說明「分數旁附標準誤」的理由:Eval 的題目只是所有可能題目裡抽出來的一批,換一批,分數就會跟著動。
切片看到落差時,小切片樣本少、波動大,先回頭檢查題目與不確定性,不急著宣稱某群使用者受益或受害。
避開「分岔小徑」的多重比較風險:看資料才發現的切片標為探索,留給新題目驗證
Gelman 與 Loken 把一種常見的處境叫做「分岔小徑」:就算只做了一次分析,只要分析方式是看過資料才決定的,結論就帶著多重比較的風險。
所以探索時才發現的新切片,不用假裝它不存在,也不能當成原本的假設。把它標成「後來發現」,記下為什麼值得追,下一輪用新題目驗證。團隊照樣能從意外裡學到東西,又不會把意外包裝成早就預測到的勝利。
設計卡也有不必寫得那麼重的時候。探索期每天改 Prompt、看開發集,本來就是在偷看,只要結論標成探索、不拿去做上線決定就好。差距大到二十個百分點、怎麼看都看得出來時,統計細節的影響也小。真正需要整張卡的,是那些差距只有幾個百分點、又要拿去做上線或採購決定的比較。
結論:實驗跑得越便宜第一名裡的運氣越多,跑之前先定好標準才不會把雜訊當進步
六十組實驗,一個下午跑得完。實驗 (1) 已經算給你看:所有組其實一樣好,第一名在開發集上平均還是多出 7 個百分點。跑得越便宜,這份運氣越常被當成進步。
實驗跑得越多,第一名裡的運氣就越多。
回頭看你們現在的測試集:它被拿來挑過幾次方案?如果你正在考慮換模型,可以接著讀〈新模型出來了要不要換?AI 功能答錯時,換模型多半沒用(2026 除錯懶人包)〉。
常見問題
用 LLM 當評審(LLM-as-a-judge)可靠嗎?
可以用,但要防三種偏誤。Zheng 等人 2023 年的研究發現,GPT-4 當評審和人類的一致率超過 80%,和人與人之間差不多;它也偏好排在前面、比較長、自己產生的答案。實務上,兩兩比較時把順序對調再評一次,評審不要和受測模型同一家,定期抽題給人工標註、算一致率。
A/B 測試為什麼不能每跑一批就看一次 p 值?
每看一次,就多一次被雜訊騙到的機會。在一組模擬裡,兩個完全相同的 Prompt(真實答對率都是 70%)每 50 題看一次 p 值、顯著就停,有 27.4% 會宣布「有差」;只在 1,000 題時看一次是 5.9%。要嘛事先估好題數、測完才看,要嘛改用序列檢定,它在設計時就把反覆查看算進誤判機率。
LLM Eval 的保留集(holdout)要怎麼用?
開發集讓團隊每天看、每天改 Prompt;保留集鎖起來,只在上線前這類要做決定的時候打開,每打開一次都記下來。Dwork 等人 2015 年在《Science》的研究說明,同一份保留集被反覆查詢,選出來的方案會開始貼合它本身。保留集要定期從最新日誌補新題、汰換舊題。
OpenAI 為什麼不再回報 SWE-bench Verified 的成績?
SWE-bench Verified 是 OpenAI 2024 年整理的 500 題真實 GitHub issue。2026 年 2 月 OpenAI 宣布不再回報這項成績,理由有兩個:審查 138 道難題後,59.4% 在測試設計或題目描述上有實質問題;題目來自公開專案,前沿模型能重現部分標準答案,分數越來越反映看過多少題。
延伸閱讀
- 新模型出來了要不要換?AI 功能答錯時,換模型多半沒用(2026 除錯懶人包)
- AI 會寫 SQL 了,還需要學 Data Science 嗎?(2026 完整解析)
- AI 寫的測試全過,為什麼功能還是錯的?驗證閉環完整懶人包(2026)
- 賭場怎麼抓 AI 老千?異常偵測三層做法一次看懂(2026 完整解析)
參考資料
- OpenAI, Why we no longer evaluate SWE-bench Verified(2026-02-23) https://openai.com/index/why-we-no-longer-evaluate-swe-bench-verified/
- Cynthia Dwork, Vitaly Feldman, Moritz Hardt, Toniann Pitassi, Omer Reingold, Aaron Roth, The reusable holdout: Preserving validity in adaptive data analysis(Science 349(6248), 2015) https://www.science.org/doi/10.1126/science.aaa9375
- Ramesh Johari, Pete Koomen, Leonid Pekelis, David Walsh, Peeking at A/B Tests: Why it matters, and what to do about it(KDD '17, 2017) https://dl.acm.org/doi/10.1145/3097983.3097992
- Andrew Gelman, Eric Loken, The garden of forking paths: Why multiple comparisons can be a problem, even when there is no "fishing expedition" or "p-hacking"(Columbia University working paper, 2013) https://sites.stat.columbia.edu/gelman/research/unpublished/p_hacking.pdf
- Anthropic, A statistical approach to model evaluations(2024-11-19;論文 Adding Error Bars to Evals, arXiv:2411.00640) https://www.anthropic.com/research/statistical-approach-to-model-evals
- Lianmin Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena(NeurIPS 2023 Datasets and Benchmarks, arXiv:2306.05685) https://arxiv.org/abs/2306.05685
留言0
登入後就能留言。
登入