Giáo trình đo lường model ngôn ngữ · quyển thứ ba

Lò Luyện Đo Lường

Bạn train được model. Câu hỏi khó hơn nhiều là: làm sao biết bản mới thật sự tốt hơn bản cũ? Quyển này dựng lại từ đầu toàn bộ bộ máy trả lời câu đó — metric, sai số, kiểm định, cỡ mẫu, người chấm, judge, golden set — bằng số cụ thể và phòng thí nghiệm kéo được bằng tay.

11 module nối liền mạch 11 phòng thí nghiệm tương tác (JS thuần, không cài gì) 2 khu 3D: đám mây tương quan, mặt power Không có metric nào được nhắc mà không tính tay một lần

Đã học 0/11 module

Đọc trang này thế nào cho có lãi

Hai quyển trước dạy bạn làm ra một model: Lò Luyện Mạng Nơ-ron mổ xẻ cơ chế bên trong, Lò Luyện Model dạy các quyết định khi train. Cả hai đều kết thúc ở chỗ bạn có một checkpoint trong tay. Quyển này bắt đầu đúng từ đó, với một câu hỏi duy nhất, hỏi đi hỏi lại ở các mức độ khắt khe tăng dần:

Con số bạn vừa đo được có ý nghĩa gì, sai bao nhiêu, và nó cho phép bạn kết luận điều gì?

Nghe có vẻ nhẹ nhàng. Thực tế nó là chỗ tốn tiền nhất trong mọi dự án ML: một thí nghiệm sai thiết kế không chỉ vô ích, nó còn dẫn bạn đi sai hướng trong nhiều tuần vì bạn tin vào một con số không có gì chống lưng.

Thứ bạn thật sự muốn tốt hơn Bộ test một mẫu hữu hạn Metric hàm chấm điểm Con số kèm sai số hay không? Quyết định ship · sửa · bỏ rò rỉ 1 · mẫu không đại diện rò rỉ 2 · metric mù rò rỉ 3 · bỏ qua sai số đo lại sau khi can thiệp — vòng lặp này mới là công việc thật Đường đi của một quyết định kỹ thuật Mỗi mũi tên là một chỗ có thể mất thông tin. Ba chỗ đỏ là ba chương của quyển này.
Bản đồ chủ đạo. Khi lạc ở bất kỳ đâu trong giáo trình, hãy tự hỏi: mình đang ở mũi tên nào? Phần I lo mũi tên "bộ test → metric". Phần II lo "metric → con số". Phần III lo trường hợp metric là con người hoặc một model khác. Phần IV lo cả vòng lặp đỏ.

Bốn phần, và thứ bạn cầm được sau mỗi phần

Phần 0 · Module 00

Đặt đúng câu hỏi

  • Mục tiêu thật vs proxy
  • Luật Goodhart
  • Bốn tầng eval

Sau phần này: bạn biết mình đang đo cho ai, để quyết định gì, và sai thì mất gì.

Phần I · Module 01–02

Metric

  • BLEU, chrF, TER tính tay
  • BERTScore, COMET
  • Đánh giá chính metric

Sau phần này: bạn đọc được mọi con số metric và nói ngay nó mù chỗ nào.

Phần II · Module 03–05

Bất định

  • Bootstrap & khoảng tin cậy
  • Kiểm định cặp, p-value
  • Cỡ mẫu và power

Sau phần này: mọi con số bạn báo cáo đều kèm sai số, và bạn biết bộ test của mình nhạy tới đâu.

Phần III · Module 06–07

Người & judge

  • Rubric và phiên chấm mù
  • Hệ số đồng thuận $\kappa$
  • Bốn thiên vị của judge

Sau phần này: bạn tổ chức được một phiên chấm cho ra số dùng được, và biết khi nào judge nói dối.

Phần IV · Module 08–10

Hệ eval

  • Golden set & nhiễm dữ liệu
  • Kỷ luật thí nghiệm
  • Bảng quyết định ship

Sau phần này: bạn có một hệ eval chạy được mỗi commit và một quy trình không tự lừa mình.

Cách học cho hiệu quả

  • Đừng bỏ phòng thí nghiệm. Thống kê là môn mà trực giác sai gần như mọi lần. Kéo núm và thấy tận mắt phân phối p-value phẳng lì khi hai model bằng nhau, hiệu quả gấp mười lần đọc định nghĩa.
  • Tính tay đúng một lần cho mỗi công thức. Mọi công thức trong quyển này đều có một ví dụ số nhỏ chạy được bằng máy tính bỏ túi. Làm theo, đừng chỉ đọc.
  • Mỗi module có một dòng "Nó ứng dụng vào đâu trong đời thật". Nếu bạn đang bận, đọc mục tiêu đầu module và dòng đó trước, rồi quay lại phần thân sau.
  • Quên thống kê rồi thì ôn 5 phút ở dưới là đủ để đi hết Phần II.

★ Bảng cứu nguy: ký hiệu thống kê sang ngôn ngữ lập trình

Thống kê dùng chữ Hy Lạp nhiều hơn cả deep learning, nhưng số ký hiệu thật sự cần thuộc thì rất ít. Đây là toàn bộ những gì xuất hiện trong quyển này. Đánh dấu trang, bạn sẽ quay lại nhiều lần.

Ký hiệuĐọc làNghĩaTrong code
$n$enCỡ mẫu — số câu trong bộ testlen(test)
$\bar{x}$ích ngangTrung bình mẫu — thứ bạn đo đượcnp.mean(s)
$\mu$muyTrung bình tổng thể — thứ bạn muốn biết mà không bao giờ biết—
$s$étĐộ lệch chuẩn mẫu — mức tản mát của điểm từng câunp.std(s, ddof=1)
$\sigma$xích-maĐộ lệch chuẩn tổng thể—
$\mathrm{SE}$sai số chuẩn$s/\sqrt{n}$ — sai số của trung bình, không phải của từng câus/np.sqrt(n)
$\hat{\theta}$tê-ta mũƯớc lượng. Cái mũ luôn có nghĩa "đo từ mẫu, nên có sai số"—
$\Delta$đen-taChênh lệch giữa hai hệ thốngnp.mean(a - b)
$d_i$đê iHiệu ghép cặp trên câu thứ $i$: điểm A trừ điểm Ba[i] - b[i]
$H_0$hát khôngGiả thuyết không — "hai hệ thống thật ra bằng nhau"—
$\alpha$an-phaMức ý nghĩa, thường 0.05 — tỉ lệ báo động giả bạn chấp nhận0.05
$\beta$bê-taXác suất bỏ sót. Power $= 1-\beta$—
$p$p-valueXác suất thấy chênh lệch cỡ này nếu $H_0$ đúng—
$B$bêSố vòng lấy mẫu lại của bootstrapB = 1000
$\mathrm{CI}$khoảng tin cậyKhoảng chứa giá trị thật với độ tin cậy đã chọnnp.percentile(bs, [2.5, 97.5])
$\Phi$phi hoaHàm phân phối tích luỹ chuẩnnorm.cdf(z)
$z_{\alpha/2}$dét an-pha đôiNgưỡng chuẩn hai phía; bằng 1.96 khi $\alpha = 0.05$norm.ppf(0.975)
$\kappa$kap-paHệ số đồng thuận Cohen, đã trừ phần trùng do may rủicohen_kappa_score(a, b)
$\rho$, $r_s$rôTương quan hạng Spearmanspearmanr(x, y)
$\tau$tauTương quan hạng Kendall — đếm cặp thuận nghịchkendalltau(x, y)

Một quy ước đọc rất đáng thuộc: chữ Hy Lạp là sự thật bạn không biết, chữ La-tinh là thứ bạn đo được. $\mu$ và $\sigma$ thuộc về tổng thể; $\bar{x}$ và $s$ là ước lượng của chúng từ mẫu. Cái mũ trên $\hat{\theta}$ nghĩa là "đo từ mẫu, nên kèm sai số".

Ôn thống kê trong 5 phút

Toàn bộ Phần II chỉ dựa trên đúng một ý: bạn đo trên một mẫu, không đo trên toàn bộ thực tế. Nếu lấy một mẫu khác, con số sẽ khác. Mức khác nhau ấy có tên là sai số chuẩn (standard error), và nó co lại theo $\sqrt{n}$ chứ không theo $n$ — muốn giảm sai số một nửa, bạn phải tăng bộ test lên gấp bốn.

Điều bất ngờ dễ chịu: dù phân phối điểm từng câu có lệch xấu đến đâu, phân phối của trung bình vẫn nhanh chóng thành hình chuông. Kéo thử ngay dưới đây rồi hãy đọc tiếp.

Khởi động

Ôn thống kê 5 phút

Kéo n theo đúng chuỗi 25 → 100 → 400 và chỉ nhìn một con số: sai số chuẩn
Mẫu vừa lấy — n câu rút ra từ bộ 4000 câu
Phân phối của các trung bình mẫu đã lấy
25
Đổi n là làm lại từ đầu: máy tự lấy 200 mẫu mới để dựng khung dưới. Mỗi lần gấp bốn n, nhìn ô "sai số chuẩn" tụt còn một nửa.
Trung bình mẫu lần này—
Sai số chuẩn lý thuyết σ/√n—
Độ lệch chuẩn thực nghiệm của các trung bình—
Đọc số

—

Điều cần thấy: ① Khung trên không hình chuông chút nào. Nền xám là cả 4000 câu của tổng thể: 19.5% số câu dưới 10 điểm, đuôi phải kéo tới 95.9, trung vị 21.54 thấp hơn hẳn trung bình $\mu = 25.45$, hệ số lệch $\gamma = 1.36$. Điểm từng câu trông đúng như thế trong đời thật, và nó phá mọi giả định "chuẩn" mà bạn sắp muốn dùng. ② Khung dưới thì lại hình chuông. Cùng lúc đó, với n = 25 mặc định, 200 trung bình mẫu bám khít đường xanh lục $\mathcal{N}(\mu,\ \sigma/\sqrt{n})$: độ lệch chuẩn thực nghiệm 4.10 so với công thức $\sigma/\sqrt{n} = 19.92/5 =$ 3.98, lệch 3%. Gốc lệch tới đâu không quan trọng — trung bình của nó vẫn chuẩn. Đó là định lý giới hạn trung tâm, và nó là lý do gần như mọi thứ trong quyển này chạy được. ③ Gấp bốn n để giảm một nửa sai số. Kéo n đúng chuỗi 25 → 100 → 400: sai số chuẩn đi 3.98 → 1.99 → 1.00, chính xác một nửa mỗi bước, và độ lệch chuẩn thực nghiệm bám theo 4.10 → 2.02 → 1.02. Bề rộng dải 95% trên khung dưới cũng co y hệt: ±7.81 → ±3.90 → ±1.95 điểm. Luật $1/\sqrt{n}$ đó là hoá đơn của mọi bộ test: muốn con số chính xác gấp đôi, trả gấp bốn số câu. ④ Bộ test nhỏ không cho số nhiễu, nó cho số vô nghĩa. Kéo n xuống đáy (n = 5): 200 trung bình trải từ 5.95 tới 53.20 — cùng một hệ thống, cùng một tổng thể, hai lần đo lệch nhau 47 điểm. Ở n = 400 cả 200 trung bình nằm gọn trong [22.66, 28.44], rộng 5.8 điểm. Để ý luôn vạch trục trái cùng ở n = 5: nó ghi −10.2, tức dải bốn sai số chuẩn đã thò sang vùng điểm âm — chỗ không câu nào tồn tại được. Xấp xỉ chuẩn ở n = 5 chưa tử tế; nó chỉ tử tế dần khi n lớn. Bấm ▶ Lấy một mẫu mới vài lần và để ý: ô stat thứ nhất nhảy loạn, hai ô kia đứng yên. Con số bạn báo cáo chỉ là một lần rút thăm từ khung dưới.

Phần 0 — Vì sao đo lường quyết định tất cả · Module 00

00 Đo lường là hệ điều hành của dự án

Cần biết trước: bạn đã từng train hoặc fine-tune ít nhất một model Dựng nên: cách đặt câu hỏi trước mọi phép đo Dẫn tới: Module 01 — BLEU và họ metric n-gram

Mục tiêu

  • Trả lời được ba câu hỏi bắt buộc — đo cho ai · quyết định gì · sai thì mất gì — trước khi gõ dòng lệnh eval đầu tiên.
  • Tách bạch mục tiêu thật khỏi proxy, và chỉ được ra chỗ proxy nói dối trong chính dự án của bạn.
  • Tính bằng tay một ca proxy tăng 4.85 điểm trong khi mục tiêu thật rơi từ 53.1% xuống 11.8%.
  • Xếp một câu hỏi vào đúng một trong bốn tầng eval, và nói ngay tầng đó mù chỗ nào.
  • Dùng bảy từ nền — metric, score, eval set, baseline, ablation, regression, guardrail — theo đúng một nghĩa với đồng đội.

1. Ba câu hỏi phải trả lời trước khi chạy phép đo đầu tiên

Bạn đã ra hàng chục quyết định kỹ thuật dựa trên một con số trần trụi. Chọn checkpoint nào để đem đi distil. Kết luận rằng lượng tử hoá int8 "không làm hỏng gì". Bỏ một nhánh kiến trúc vì nó thua vài điểm. Tin rằng phiên bản mới thắng vì bảng số nhìn đẹp hơn.

Mỗi lần như thế, bạn đã ngầm ký một giả định: con số kia ổn định. Rằng nếu đổi bộ test, đổi seed, đổi cách tokenize, nó vẫn thế. Giả định đó gần như luôn sai, và quyển này tồn tại để chỉ ra nó sai bao nhiêu.

Luận đề xuyên suốt gói trong một câu: một con số không kèm sai số là một tin đồn. Không phải nói dối — tin đồn, tức là thứ có thể đúng, nhưng bạn không có quyền hành động dựa trên nó. Module 03 sẽ dạy cách tính sai số. Module này dạy thứ đến trước cả sai số: hỏi cho đúng.

Trước mọi phép đo, viết ba dòng vào sổ. Ba dòng, không phải ba trang.

  1. Đo cho ai. Ai đọc con số này, và họ tin nó tới mức nào. Con số bạn tự nhìn để chọn hướng debug được phép ồn ào. Con số dán vào README cho người khác dùng thì không.
  2. Quyết định gì. Con số này sẽ đổi hành động nào của bạn. Nếu cả hai kết quả có thể xảy ra đều dẫn tới cùng một hành động, phép đo đó vô ích — đừng tốn GPU cho nó.
  3. Sai thì mất gì. Hai chiều sai lệch gần như không bao giờ tốn bằng nhau, và chính sự bất đối xứng đó — chứ không phải con số — quyết định ngưỡng của bạn.

Câu thứ ba là câu hay bị bỏ qua nhất, nên hãy làm nó bằng số ngay tại đây. Gọi $C_{\text{ship}}$ là cái mất khi bạn ship một bản thật ra tệ hơn, $C_{\text{miss}}$ là cái mất khi bạn giữ lại một bản thật ra tốt hơn, và $p$ là mức tin của bạn rằng bản mới tốt hơn — một con số chủ quan bạn tự đặt trước khi đo, không phải p-value mà Module 04 sẽ nói tới. Ship có lợi khi

$$(1-p)\,C_{\text{ship}} \;\le\; p\,C_{\text{miss}} \qquad\Longleftrightarrow\qquad p \;\ge\; \frac{C_{\text{ship}}}{C_{\text{ship}} + C_{\text{miss}}}$$

Giả sử ship nhầm tốn 30 ngày-người (phát hiện, rollback, dịch lại những chương đã đẩy đi), còn bỏ lỡ tốn 5 ngày-người (chờ thêm một chu kỳ). Nếu bạn tin ở mức $p = 0.6$: kỳ vọng mất khi ship là $0.4 \times 30 = 12$; khi không ship là $0.6 \times 5 = 3$. Không ship, dù bạn nghiêng về phía tin rằng nó tốt hơn.

Nói bằng lời

Ngưỡng để bấm nút không phải "hơn 50% khả năng tốt hơn". Nó là tỉ số giữa cái mất khi ship nhầm và tổng hai cái mất: $30/(30+5) = 6/7 \approx 0.857$. Bạn cần 85.7% chắc chắn, không phải 51%.

Đó là lý do câu hỏi "bộ test của tôi cho tôi được bao nhiêu phần chắc chắn" không phải câu hỏi học thuật. Nó là câu hỏi ngân sách. Nhưng chú ý ngay: kiểm định tần suất không cấp thẳng cho bạn $p$ — muốn đảo từ dữ liệu về mức tin thì phải có xác suất tiên nghiệm, đó là Bayes chứ không phải kiểm định (Module 04, mục 6). Thứ gần nhất Module 05 cấp được là chênh lệch nhỏ nhất mà bộ test nhìn thấy nổi (MDE) và số câu cần để thấy nó.

2. Mục tiêu thật và cái bóng của nó

Mục tiêu thật (true objective) là điều bạn thật sự muốn xảy ra ngoài đời. Proxy (proxy metric) là thứ bạn đo được thay cho nó. Trong dự án dịch web novel zh→vi, mục tiêu thật là độc giả đọc hết truyện rồi quay lại. Không ai đo được thứ đó lúc 2 giờ sáng khi job train vừa xong.

Nên bạn tụt xuống một bậc. Rồi một bậc nữa. Mỗi bậc rẻ hơn, nhanh hơn — và mù thêm một chút.

① Điều bạn thật sự muốn độc giả đọc hết truyện rồi quay lại chương sau chờ hàng tuần · cần lưu lượng thật mất: thói quen đọc dài, tốc độ tải, giá tiền, bìa truyện ② Chương không có lỗi làm độc giả khựng lại sai tên nhân vật, sai xưng hô, rớt một câu hàng ngày · cần biên tập viên đọc thật mất: hiệu ứng cả chương — một lỗi đủ hỏng 40 câu còn lại ③ Câu dịch đúng nghĩa, đúng giọng, đọc trôi phán xét trên từng câu rời hàng giờ · cần rubric và người chấm mất: lỗi nặng và lỗi nhẹ bị cộng vào cùng một trung bình ④ Một con số trên bộ test cố định chạy được ở mọi commit 0.7 phút · khoảng 118 đ một vòng 500 câu rẻ hơn · nhanh hơn · mù hơn
Thang bậc thay thế. Con số bạn theo dõi hằng ngày nằm ở bậc ④, cách mục tiêu thật ba lần thay thế. Mỗi mũi tên đi xuống là một lần bạn đánh đổi thông tin lấy tốc độ. Đánh đổi đó hợp lý — miễn là bạn biết mình đã đánh đổi cái gì, và có cách kiểm lại ở bậc trên định kỳ.

Điểm mấu chốt: tương quan giữa bậc ④ và bậc ① là một quan sát thực nghiệm trong vùng bạn đã đo, không phải một định luật. Nó đúng cho các model bạn từng thấy. Nó không hứa gì về model tiếp theo, nhất là model được sinh ra bằng cách tối ưu chính bậc ④.

3. Chạy tay: proxy lên 4.85 điểm, mục tiêu thật mất gần bốn phần năm

Trừu tượng đủ rồi. Đây là một ca cụ thể, tính hết bằng máy tính bỏ túi.

Chạy tay một lần

Bối cảnh (mọi con số dưới đây là giả định được nêu rõ, không phải số đo thật của dự án nào). Bộ test 200 câu, trong đó 30 câu có tên riêng nhân vật — đúng thứ độc giả web novel nhạy nhất. Điểm tự động chấm mỗi câu trên thang 0–100. Một câu dài khoảng 20 token, nên sai một tên riêng làm hỏng 1 token trên 20: metric trừ 5 điểm cho câu đó. Đó không phải lỗi của metric, đó là bản chất của việc đếm token.

Hai hệ thống. A là bản đang chạy. B là bản mới, được chỉnh để văn xuôi mượt hơn — mọi câu được cộng 5 điểm nền — nhưng cách chỉnh đó làm nó bịa tên riêng nhiều hơn.

  • A: điểm nền 60; sai tên ở 3/30 câu.
  • B: điểm nền 65; sai tên ở 9/30 câu.

Bước 1 — proxy của A. 197 câu được 60 điểm, 3 câu được 55 điểm:

$$\frac{197 \times 60 + 3 \times 55}{200} = \frac{11\,820 + 165}{200} = \frac{11\,985}{200} = 59.925$$

Bước 2 — proxy của B. 191 câu được 65 điểm, 9 câu được 60 điểm:

$$\frac{191 \times 65 + 9 \times 60}{200} = \frac{12\,415 + 540}{200} = \frac{12\,955}{200} = 64.775$$

Bước 3 — chênh lệch. $\Delta = 64.775 - 59.925 = +4.85$ điểm, tức tăng tương đối 8.09%. Trong bất kỳ bảng báo cáo nào, đây là một thắng lợi to. Bạn ship.

Bước 4 — mục tiêu thật. Tỉ lệ câu có tên riêng trong bộ test là $30/200 = 15\%$. Một chương 40 câu vậy chứa khoảng $40 \times 0.15 = 6$ câu có tên riêng. Độc giả bỏ chương nếu gặp ít nhất một lỗi tên. Với tỉ lệ sai tên $r$ và $k$ câu tên mỗi chương, xác suất chương sạch là $P = (1-r)^k$.

$$\begin{aligned} \text{A} &: \; r = 3/30 = 0.10 \;\Longrightarrow\; P = 0.9^{6} = 0.531441 \\ \text{B} &: \; r = 9/30 = 0.30 \;\Longrightarrow\; P = 0.7^{6} = 0.117649 \end{aligned}$$

Tức là 53.14% số chương của A trót lọt, so với 11.76% của B.

Kết quả. Proxy đi lên 4.85 điểm; tỉ lệ chương đọc trót lọt rơi 41.38 điểm phần trăm, chỉ còn 22.1% so với trước — mất gần bốn phần năm. Số lỗi tên kỳ vọng mỗi chương tăng từ 0.6 lên 1.8, đúng gấp ba.

Nói bằng lời

$P = (1-r)^k$ đọc là: "muốn cả chương sạch thì mọi câu có tên riêng đều phải đúng". Xác suất một câu đúng là $1-r$; sáu câu độc lập thì nhân sáu lần. Vì là phép nhân, một tỉ lệ lỗi chỉ tăng gấp ba (từ 0.10 lên 0.30) đã đủ kéo xác suất chương sạch xuống còn chưa tới một nửa của một nửa.

Metric của bạn thì lại cộng điểm từng câu rồi chia. Cộng và nhân không cùng phản ứng với đuôi phân bố. Đó là toàn bộ vấn đề, gói trong một dòng.

Phản xạ đầu tiên của hầu hết mọi người là "vậy phạt nặng hơn đi". Thử phạt nặng nhất có thể: cho hẳn 0 điểm cho mọi câu sai tên riêng.

$$\begin{aligned} \text{A} &: \; \frac{197 \times 60 + 3 \times 0}{200} = \frac{11\,820}{200} = 59.100 \\ \text{B} &: \; \frac{191 \times 65 + 9 \times 0}{200} = \frac{12\,415}{200} = 62.075 \end{aligned}$$

$\Delta = +2.975$. Vẫn dương. B vẫn thắng. Muốn $\Delta$ về 0, mức phạt phải là 166.7 điểm trên thang 0–100 — tức là không tồn tại mức phạt nào trong thang điểm cứu được phép trung bình này. Lý do rất đơn giản: 5 điểm nền cộng cho cả 200 câu là 1000 điểm-câu, trong khi hình phạt chỉ tạo ra chênh lệch trên $9 - 3 = 6$ câu mà B sai nhiều hơn A — phạt kịch trần 100 điểm cũng mới được 600 điểm-câu, vẫn thua 1000.

Bẫy thường gặp

  • Tưởng rằng chỉnh trọng số trong một trung bình là đủ. Vừa chứng minh bằng số là không. Một trung bình cộng trên $n$ câu về bản chất không diễn đạt nổi ý "một câu hỏng là cả chương hỏng". Thứ bạn cần là một guardrail: một chỉ số riêng, có ngưỡng cứng, vi phạm là chặn ship — bất kể điểm chính đẹp tới đâu. Ở đây: "tỉ lệ sai tên riêng trên bộ 30 câu không vượt 10%", và B (30%) bị chặn ngay.
  • Chỉ nhìn số tổng. Nếu bạn tách bảng theo tầng (câu thường / câu có tên riêng), B lộ ngay ở dòng thứ hai. Trung bình tổng là nơi thông tin đi chết. Module 08 dạy cách phân tầng bộ test.
  • Nhầm "metric tồi" với "metric bị dùng sai". Metric ở trên làm đúng việc của nó — đếm token khớp. Sai lầm nằm ở chỗ bạn giao cho nó một quyết định mà nó chưa bao giờ hứa trả lời.
Kiểm lại toàn bộ phép tính trên — chạy được, không cần thư viện
# Bộ test 200 câu, trong đó 30 câu có tên riêng nhân vật.
# Sai một tên riêng = hỏng 1 token trên khoảng 20 -> metric chỉ trừ 5 điểm.
N, NAME, PEN = 200, 30, 5

def proxy(diem_nen, so_cau_sai_ten):
    """Điểm tự động trung bình trên toàn bộ test."""
    return ((N - so_cau_sai_ten) * diem_nen
            + so_cau_sai_ten * (diem_nen - PEN)) / N

def chuong_sach(so_cau_sai_ten, cau_ten_moi_chuong=6):
    """Xác suất một chương 40 câu không dính lỗi tên riêng nào."""
    r = so_cau_sai_ten / NAME
    return (1 - r) ** cau_ten_moi_chuong

A = proxy(60, 3)                    # 59.925
B = proxy(65, 9)                    # 64.775
print(round(B - A, 3))              # 4.85     -> bảng báo cáo nói "ship!"
print(chuong_sach(3))               # 0.531441 -> 53.1% chương sạch
print(chuong_sach(9))               # 0.117649 -> 11.8% chương sạch
print(round(chuong_sach(9) / chuong_sach(3), 4))   # 0.2214   -> còn 22.1%

4. Luật Goodhart: metric tốt chết vì được chọn làm mục tiêu

Luật Goodhart (Goodhart's law), đặt theo tên nhà kinh tế Charles Goodhart, ở dạng phát biểu quen thuộc nhất: khi một chỉ số trở thành mục tiêu, nó thôi là một chỉ số tốt.

Cơ chế thì thẳng thừng. Proxy = phần trùng với mục tiêu thật + phần thừa. Trong vùng dữ liệu bạn từng quan sát, phần trùng chiếm ưu thế nên hai thứ đi cùng nhau. Tối ưu hoá là một cái máy tìm kiếm không có đạo đức: nó sẽ tìm ra hướng nào làm proxy tăng nhanh nhất, và hướng đó gần như luôn nằm trong phần thừa — vì phần thừa rẻ hơn để khai thác. Càng tối ưu mạnh, bạn càng bị đẩy ra khỏi vùng mà tương quan từng đúng.

Ca B ở mục 3 chính là cơ chế đó chạy đúng một bước. Phần thừa ở đây là điểm nền của mọi câu: cộng 5 điểm vào đó mua được ngay +4.85 điểm proxy, rẻ hơn hẳn việc dịch tên riêng cho đúng. Phần trùng — tỉ lệ chương đọc trót lọt — trả giá 41.38 điểm phần trăm. Hai đại lượng đã đi ngược chiều ngay ở bước tối ưu thứ nhất, không cần chờ tới bước thứ mười.

Trong đúng công việc bạn đang làm, Goodhart hiện ra ở ba chỗ, xếp theo mức khó nhận ra:

  1. Nhiễm dữ liệu. Câu test lọt vào tập train, dù chỉ ở dạng gần trùng. Proxy tăng, năng lực không đổi. Dễ nhận ra nhất, và cũng thường bị bỏ qua nhất. Module 08 đưa cách đo.
  2. Chỉnh decode để chiều metric. Sửa beam size, length penalty, ngưỡng lặp cho tới khi con số đẹp. Bạn không cải thiện model — bạn dịch chuyển bản dịch về phía dạng câu mà metric ưa. Điểm lên, người đọc không thấy khác.
  3. Lấy chính teacher đã distil ra student để chấm student. Đây là vòng lặp tự khen khép kín: bạn tối ưu student cho khớp teacher, rồi hỏi teacher xem student có tốt không. Câu trả lời được biết trước. Đây là một ca Goodhart, không phải một bug — Module 07 mổ xẻ.

Cách phòng vệ không phải là "tìm một metric hoàn hảo". Không có. Cách phòng vệ là: giữ ít nhất một tầng đo mà bạn không tối ưu vào, và ngó nó định kỳ. Bậc ① và ② trong hình trên tồn tại chính vì lý do đó. Ở ca mục 3, tầng không bị tối ưu vào chỉ cần nhỏ bằng bộ 30 câu tên riêng: nó nói ngay B sai 9/30 câu, tức 30%, trong khi điểm chính vẫn khoe +4.85.

Kéo thử ở phòng thí nghiệm dưới đây trước khi đọc tiếp: tối ưu proxy từng bước và xem chỗ mục tiêu thật quay đầu.

Phòng thí nghiệm 00

Tối ưu proxy, đánh mất mục tiêu thật

Bấm tối ưu vài vòng rồi xem đường xanh quay đầu ở vòng nào
Proxy đo được và mục tiêu thật, cùng một thang 0–100
1.00
Mỗi vòng bạn ép model bám điểm proxy mạnh tới đâu: chọn checkpoint theo proxy, thêm reward theo proxy, lọc data theo proxy.
0.55
Phần điểm proxy có thể kiếm được mà không cải thiện chất lượng thật. Bằng 0 nghĩa là proxy đo đúng thứ bạn muốn.
Proxy (BLEU)40.0
Mục tiêu thật40.0
Khoảng cách0.0
Vòng 0

Chưa tối ưu vòng nào.

Điều cần thấy: ① Để nguyên hai núm (sức ép 1.00, độ lệch 0.55) và chạy hết 24 vòng: proxy đi từ 40.0 lên 75.2 mà không sụt một vòng nào, còn chất lượng thật lên tới 49.6 ở vòng 7 rồi tụt về 42.6. Bảy vòng đầu là làm việc, mười bảy vòng sau là phá. ② Riêng đoạn từ vòng 7 tới vòng 24: proxy +10.2 điểm, chất lượng thật −7.0 điểm. Chỉ nhìn bảng điểm thì đây là bản tốt nhất từ trước tới nay. ③ Kéo độ lệch lên 1.00: đỉnh dời về vòng 3, và tới vòng 24 chất lượng thật còn 23.6 — thấp hơn 16.4 điểm so với lúc chưa tối ưu gì cả — trong khi proxy khoe 82.8. ④ Kéo độ lệch về 0.00: hai đường chồng khít, khoảng cách đứng yên ở 0.0, chất lượng thật tăng đều tới 65.9. Không có Goodhart khi proxy đo đúng thứ bạn muốn. ⑤ Giữ độ lệch 0.55 và kéo sức ép từ 0.40 lên 2.00: đỉnh chạy từ vòng 19 về vòng 4. Ép mạnh không làm bạn sai — nó làm bạn sai sớm hơn. ⑥ Bật giấu mục tiêu thật rồi chạy lại từ đầu: không có một dấu hiệu nào trên đường đỏ cho biết vòng nào là vòng đáng ra phải dừng.

5. Bốn tầng eval và chỗ đứng của từng tầng

Mọi câu hỏi về chất lượng đều trả lời được ở một trong bốn tầng. Việc của bạn là chọn tầng rẻ nhất còn trả lời được câu hỏi đang có — không phải tầng quen tay nhất.

Bảng dưới dùng một tình huống chuẩn để chi phí so sánh được: một vòng eval trên bộ 500 câu. Giả định, dùng chung đúng bộ đơn giá với Module 07 và Module 10: giải mã 12 câu/giây; GPU thuê 0.4 USD/giờ; tỉ giá 25 500 đ/USD; người chấm 45 giây/câu, hai người chấm độc lập, 204 000 đ/giờ (đúng 8 USD/giờ). Thêm một giả định nữa về lịch làm việc, vì chính nó quyết định cột độ trễ: mỗi người chỉ dành được 2 giờ mỗi ngày cho việc chấm, nên 6.25 giờ của mỗi người rải ra khoảng 3 ngày.

TầngĐo được gìChi phí một vòngĐộ trễMù chỗ nào
1 · Tự động offline
metric trên bộ test cố định
Chênh lệch bề mặt so với bản tham chiếu; hồi quy so với baseline; chạy được ở mọi commit. ≈ 118 đ
41.7 s GPU
≈ 0.7 phút Thứ không có trong bản tham chiếu. Lỗi nặng và lỗi nhẹ tính như nhau. Nhiễm dữ liệu. Sở thích thật của độc giả.
2 · Người offline
rubric, chấm mù
Lỗi nặng hay nhẹ, đúng nghĩa, đúng xưng hô, đúng giọng nhân vật; vì sao sai chứ không chỉ sai bao nhiêu. ≈ 2 550 000 đ
12.5 giờ người
≈ 3 ngày Hành vi thật của độc giả. Hiệu ứng đọc liền 50 chương. Tốc độ, giá, độ ổn định hạ tầng.
3 · Online
A/B trên lưu lượng thật
Chính mục tiêu thật: đọc hết chương, quay lại, khiếu nại, huỷ theo dõi truyện. ≈ 57 900 lượt
đọc hết chương bị mất, 14 ngày
1–3 tuần Vì sao. Bạn biết nhánh B tệ hơn nhưng không biết nó sai lỗi gì. Không chạy được khi chưa có người dùng.
4 · Giám sát sau ship
canary, drift, khiếu nại
Trôi theo thời gian; hồi quy do đổi dữ liệu hay đổi hạ tầng; ca lạ chưa từng có trong bộ test. hạ tầng log
trả liên tục
giờ — ngày
nhưng chạy 24/7
Không có đối chứng: bạn không biết bản chưa ship sẽ ra sao trong cùng điều kiện. Tín hiệu bẩn và trễ.

Con số 57 900 ở tầng 3 không rơi từ trên trời: nó lấy thẳng từ ca chạy tay ở mục 3. Giả sử 20 000 lượt đọc chương mỗi ngày, chia đôi cho hai nhánh, nhánh B kéo tỉ lệ chương sạch từ 53.14% xuống 11.76%. Mỗi ngày mất $10\,000 \times (0.531441 - 0.117649) = 4\,138$ lượt đọc trót lọt; chạy A/B đủ 14 ngày là 57 931 lượt. Đó là học phí thật của việc để tầng 1 tự quyết một mình.

Khoảng cách giữa hai tầng đầu đáng để cảm nhận bằng con số: với các giả định trên, một vòng tầng 2 đắt hơn một vòng tầng 1 khoảng 21 600 lần (2 550 000 đ so với 118 đ) và chậm hơn khoảng 6 200 lần (3 ngày so với 41.7 giây). Đó là lý do bạn không chấm người mỗi commit — và cũng là lý do bạn không được phép chỉ chạy tầng 1 suốt sáu tháng.

6. Vòng lặp cải tiến và khâu yếu nhất của nó

Toàn bộ công việc kỹ thuật của bạn là một vòng lặp bốn khâu, chạy đi chạy lại. Tốc độ tiến bộ của dự án bằng tốc độ vòng lặp này nhân với lượng thông tin thu được mỗi vòng. Hầu hết mọi người chỉ tối ưu vế đầu.

Một vòng chỉ dạy bạn được đúng thứ mà phép đo phân biệt nổi ① ĐO baseline, bộ test cố định 1 phút ② GIẢ THUYẾT nó hỏng ở đâu, sửa cái gì 30 phút ③ CAN THIỆP đổi đúng một thứ, train lại 6 giờ ④ ĐO LẠI cùng script, cùng bộ, cùng seed 1 phút vòng tiếp theo — một chu kỳ 392 phút ≈ 6.5 giờ · 80 giờ làm việc trong hai tuần ≈ 12.2 vòng Độ phân giải của cả vòng lặp nằm ở ① và ④ — không nằm ở ③, dù ③ tốn 92% thời gian
Vòng lặp đo → giả thuyết → can thiệp → đo lại. Mũi tên đỏ đứt là chỗ vòng lặp khép kín. Khâu ③ chiếm $360/392 = 91.8\%$ thời gian, nên bản năng là đi tối ưu nó. Nhưng nếu ① và ④ không phân biệt nổi mức chênh mà ③ tạo ra, rút ngắn ③ chỉ giúp bạn đi bộ ngẫu nhiên nhanh hơn.

Làm rõ ý "khâu yếu nhất" bằng số. Giả sử một can thiệp điển hình của bạn tạo ra chênh lệch thật khoảng 0.4 điểm, còn phép đo hiện tại có sai số ±1.1 điểm (Module 03 dạy cách tính con số đó, Module 05 dạy cách thu nhỏ nó). Tỉ số tín hiệu trên nhiễu là $0.4/1.1 = 0.36$ — tín hiệu chỉ bằng khoảng một phần ba nhiễu.

Trong hai tuần, bạn chạy 12.2 vòng như thế. Mười hai lần bạn nhìn một con số, mười hai lần bạn tin rằng mình vừa học được điều gì đó về model. Thực tế bạn vừa quan sát nhiễu mười hai lần và kể cho mình nghe mười hai câu chuyện. Đó không phải lỗi kỷ luật — đó là lỗi độ phân giải, và không có sự chăm chỉ nào chữa được nó.

7. Bảy từ cả đội phải hiểu giống nhau

Phần lớn tranh cãi về kết quả eval thật ra là tranh cãi về từ vựng. Bảng này chốt nghĩa cho cả quyển; từ đây trở đi các module dùng đúng những nghĩa này.

TừNghĩa dùng trong quyển nàyNhầm lẫn phổ biến
metric
chỉ số
Một hàm: nhận bản dịch cùng bản tham chiếu (hoặc câu nguồn), trả về một số. Là quy tắc, không phải giá trị.Gọi con số 34.2 là "metric". Nó là score. Metric là cái sinh ra nó.
score
điểm
Giá trị metric trả về trên một bộ dữ liệu cụ thể. Chỉ có nghĩa khi kèm bốn thứ: metric nào, bộ nào, cấu hình tokenize nào, sai số bao nhiêu.Báo "BLEU 34.2" trần trụi, rồi so với 34.2 của một bài viết khác.
eval set
bộ đánh giá
Tập cố định, không nằm trong train, và không dùng để chọn siêu tham số hay checkpoint — việc đó là của bộ dev.Dùng chung một bộ cho cả việc chọn checkpoint lẫn báo cáo cuối. Nhìn nhiều lần cũng là một dạng overfit (Module 08).
baseline
mốc so
Hệ thống bạn phải đánh bại, được đo lại bằng đúng script, đúng bộ, đúng phiên bản thư viện, trong cùng đợt chạy.Chép số baseline từ nguồn khác. Khác tokenizer là khác vài điểm, so sánh vô nghĩa.
ablation
cắt bỏ
Bỏ đúng một thành phần, giữ nguyên tuyệt đối mọi thứ còn lại, đo lại. Mục đích: quy công cho thành phần đó.Đổi ba thứ cùng lúc rồi gọi là ablation. Khi đó bạn không quy công được cho thứ nào.
regression
hồi quy chất lượng
Bản mới tệ hơn bản cũ ở một mặt nào đó — kể cả khi trung bình tổng đi lên. Chính là ca B ở mục 3.Lẫn với "hồi quy" trong thống kê (regression analysis). Hai thứ không liên quan.
guardrail
chỉ số lan can
Chỉ số phụ có ngưỡng cứng đặt trước. Vi phạm là chặn ship, không thương lượng, bất kể chỉ số chính đẹp bao nhiêu.Coi guardrail là "số phụ để tham khảo". Nếu nó thương lượng được thì nó không phải guardrail.

Bảy từ này cộng lại thành một câu duy nhất mà phần còn lại của quyển sẽ khai triển: một score, sinh bởi một metric đã nêu tên, trên một eval set không bị nhiễm, so với một baseline đo cùng đợt, kèm sai số, và không vi phạm guardrail nào. Thiếu bất kỳ mảnh nào, thứ bạn có vẫn chỉ là một tin đồn có dấu thập phân.

Nó ứng dụng vào đâu trong đời thật

Trong dự án dịch web novel zh→vi, module này đổi bốn quyết định rất cụ thể:

  • Trước lần chạy eval tới, viết ba dòng. Đo cho ai · quyết định gì · sai thì mất gì. Riêng dòng thứ ba: ship một bản dịch tệ ra hàng nghìn chương đã đẩy đi tốn hơn nhiều so với chờ thêm một tuần — nên ngưỡng chắc chắn của bạn phải nằm ở vùng 85%, không phải 51%.
  • Dựng ngay một guardrail tên riêng và xưng hô. Một bộ nhỏ khoảng 30 câu dày đặc tên nhân vật, ngưỡng cứng đặt trước. Bản B ở mục 3 vượt điểm chính 4.85 nhưng sai tên 30% — guardrail chặn nó trong 0.7 phút, trong khi tầng 3 phải trả 57 900 lượt đọc mới phát hiện.
  • Với các quyết định hạ tầng — int8 của CTranslate2, Q4 của GGUF, WebGPU — hãy hỏi tầng nào đang trả lời. "Điểm không đổi trên 200 câu" là câu trả lời của tầng 1, và tầng 1 mù đúng thứ lượng tử hoá hay làm hỏng: câu dài, tên riêng, con số. Kèm một vòng tầng 2 trên 100 câu khó trước khi kết luận.
  • Khi teacher chấm student mà điểm đẹp bất thường, gọi tên nó là Goodhart, không phải may mắn. Bạn tối ưu student cho khớp teacher rồi hỏi teacher — vòng khép kín. Module 07 đưa cách kiểm định judge trước khi tin nó.

Tự kiểm tra

1 · Ship nhầm tốn 30 ngày-người, bỏ lỡ một bản tốt tốn 5. Bạn cần tin ở mức nào mới nên ship, và vì sao "trên 50%" là sai?

Ngưỡng là $p \ge C_{\text{ship}}/(C_{\text{ship}}+C_{\text{miss}}) = 30/35 = 6/7 \approx 0.857$, tức 85.7%. "Trên 50%" chỉ đúng khi hai loại sai lầm tốn bằng nhau — tình huống gần như không tồn tại trong thực tế. Với $p = 0.6$: kỳ vọng mất khi ship là $0.4 \times 30 = 12$ ngày-người, khi không ship là $0.6 \times 5 = 3$. Chênh gấp bốn lần, nghiêng hẳn về phía không ship.

2 · Trong ca chạy tay, nếu bạn phạt tối đa — cho 0 điểm mọi câu sai tên riêng — thì B có còn thắng không? Tính ra số.

Vẫn thắng. A: $(197 \times 60)/200 = 11\,820/200 = 59.10$. B: $(191 \times 65)/200 = 12\,415/200 = 62.075$. $\Delta = +2.975$. Muốn $\Delta = 0$ thì mức phạt phải là 166.7 điểm, vượt ngoài thang 0–100 — nghĩa là không có mức phạt hợp lệ nào cứu được phép trung bình. Bài học: vấn đề không nằm ở trọng số mà ở dạng của phép gộp. Phải tách ra thành guardrail riêng.

3 · Bộ test 200 câu có 30 câu chứa tên riêng. Một chương dài 40 câu. Nếu tỉ lệ sai tên là 10%, xác suất một chương không dính lỗi nào là bao nhiêu?

Tỉ lệ câu có tên riêng là $30/200 = 0.15$, nên một chương 40 câu chứa khoảng $40 \times 0.15 = 6$ câu như thế. Xác suất cả 6 câu đều đúng là $0.9^6 = 0.531441$, tức 53.14%. Nếu tỉ lệ sai tăng lên 30%: $0.7^6 = 0.117649$ hay 11.76% — còn 22.1% so với trước. Chú ý phép nhân: đây là lý do một lỗi hiếm ở mức câu vẫn có thể là lỗi phổ biến ở mức chương.

4 · Câu hỏi "bản mới có làm độc giả đọc hết chương nhiều hơn không" thuộc tầng nào? Vì sao tầng 1 không trả lời được, dù nó rẻ hơn 21 600 lần?

Tầng 3 (online). Tầng 1 đo độ khớp với một bản tham chiếu cố định — nó không hề chứa khái niệm "độc giả", "chương", hay "đọc hết". Ca ở mục 3 cho thấy hai đại lượng này có thể đi ngược chiều: proxy +4.85 trong khi tỉ lệ chương sạch rơi từ 53.14% xuống 11.76%. Tầng 1 vẫn hữu ích — nó là cái sàng rẻ chạy mỗi commit — nhưng nó không có thẩm quyền kết luận về mục tiêu thật.

5 · Guardrail khác một "metric phụ để tham khảo" ở chỗ nào?

Ở chỗ ngưỡng được đặt trước khi nhìn kết quả, và vi phạm thì chặn ship — không thương lượng, không "lần này thôi". Một metric phụ mà bạn có thể bỏ qua khi nó bất tiện thì về mặt vận hành nó không tồn tại. Trong ca ở mục 3: guardrail "sai tên riêng không quá 10% trên bộ 30 câu" chặn bản B (30%) ngay lập tức, dù điểm chính của B cao hơn 4.85 điểm.

Phần I — Metric: từ n-gram tới neural · Module 01

01 Metric n-gram: BLEU, chrF, TER tính tay

Cần biết trước: Module 00 — ba câu hỏi trước mọi phép đo Dựng nên: khả năng đọc một con số BLEU và biết chính xác nó đo gì Dẫn tới: Module 02 — metric học được

Mục tiêu

  • Tính BLEU của một cặp câu bằng tay, không thư viện — liệt kê n-gram, clipping, brevity penalty, trung bình nhân — và ra đúng con số mà sacreBLEU in ra: 62.40.
  • Giải thích được vì sao trên cùng ba câu, corpus-BLEU là 55.63 còn trung bình sentence-BLEU là 45.82, và cái nào mới là con số được phép báo cáo.
  • Tính chrF2 bằng tay ở mức 2-gram ký tự (ra 44.80) và nói được vì sao nó ít gãy hơn BLEU trên tiếng Việt.
  • Đếm được số thao tác TER của một cặp câu, kể cả thao tác thứ tư mà nhiều người quên: shift.
  • Đọc một sacreBLEU signature và kết luận ngay hai con số có so được với nhau hay không.

1. Bài toán: nén một bản dịch thành một con số

Một câu nguồn tiếng Trung có vô số bản dịch tiếng Việt đúng. Bạn thì chỉ có một câu tham chiếu (reference) do người viết ra. Toàn bộ họ metric bề mặt sinh ra từ một thoả hiệp thô bạo: coi bản dịch là tốt nếu nó dùng lại nhiều đoạn chuỗi của tham chiếu.

Thoả hiệp đó phải trả giá, và bạn nên biết trước cái giá là gì. Nhưng nó mua được ba thứ mà không cách nào khác mua nổi: chi phí gần bằng không (chấm 800 câu mất dưới một giây), tất định (deterministic — chạy lại ra đúng số cũ), và không cần GPU. Đó là lý do metric n-gram vẫn sống sau hai mươi năm bị chê.

Cặp câu sau sẽ đi theo bạn suốt module. Ta cố tình chọn một câu web novel ngắn, đã tách token bằng khoảng trắng, đã viết thường, không dấu câu — để mọi phép đếm làm được bằng mắt.

Tham chiếu $y$ · hắn chậm rãi rút thanh kiếm ra khỏi vỏ · $r = 9$ token.
Bản dịch máy $\hat{y}$ · hắn rút thanh kiếm ra khỏi vỏ kiếm · $c = 8$ token.

Bản máy làm rơi chậm rãi, và thừa ra một chữ kiếm ở cuối — đúng kiểu lỗi lặp đuôi mà model NAR hay decode tham lam vẫn mắc. Hai lỗi đó sẽ bị hai cơ chế khác nhau của BLEU bắt: một cái bởi clipping, một cái bởi brevity penalty.

2. Precision n-gram, và bệnh "the the the"

Một n-gram là một cửa sổ $n$ token liên tiếp. Câu 8 token có 8 unigram, 7 bigram, 6 trigram, 5 four-gram — công thức là $\max(0,\, L - n + 1)$ với $L$ là độ dài câu.

Ý tưởng đầu tiên, ngây thơ: precision $=$ tỉ lệ n-gram của bản dịch có xuất hiện trong tham chiếu. Ý tưởng này hỏng ngay lập tức. Lấy tham chiếu con mèo ngồi trên tấm thảm và một bản dịch rác:

trên trên trên trên trên trên. Precision thô cho 6/6 = 100.0%. Precision có clipping cho 1/6 = 16.7%.

Sáu lần chữ trên đều "có trong tham chiếu", nên precision thô cho điểm tuyệt đối cho một câu vô nghĩa. Cách chữa gọi là clipping (cắt trần): mỗi n-gram chỉ được tính công tối đa bằng số lần nó xuất hiện trong tham chiếu. Tham chiếu có trên đúng một lần, nên năm lần còn lại là công cốc.

$$p_n = \frac{\sum_{g} \min\big(c_{\hat{y}}(g),\; c_{y}(g)\big)}{\sum_{g} c_{\hat{y}}(g)}$$

Nói bằng lời

Với mỗi n-gram $g$ mà bản dịch dùng: đếm nó xuất hiện bao nhiêu lần trong bản dịch ($c_{\hat{y}}$), đếm nó xuất hiện bao nhiêu lần trong tham chiếu ($c_{y}$), lấy số nhỏ hơn. Cộng hết lại, chia cho tổng số n-gram của bản dịch. Mẫu số luôn là bản dịch, không bao giờ là tham chiếu — đây chính là chỗ BLEU cố ý bỏ recall, ta quay lại ở mục 3.

Áp vào cặp câu chính. Bản máy có kiếm hai lần, tham chiếu có một lần, nên $\min(2,1) = 1$: chữ kiếm thứ hai bị clip. Bảy unigram còn lại đều khớp.

Tham chiếur = 9 token Bản dịchc = 8 token hắn chậm rãi rút thanh kiếm ra khỏi vỏ hắn thiếu thiếu rút thanh kiếm ra khỏi vỏ kiếm bị clip 6 token liên tiếp khớp nguyên khối → 3 four-gram khớp Hai chỗ thiếu kéo c xuống 8 → brevity penalty ăn 11.8%. Chữ thừa bị clipping chặn → không được cộng công, lại còn phình mẫu số.
Một chữ thừa bị phạt hai lần. Ô kiếm cuối câu không lấy được điểm nào (clipping), nhưng vẫn nằm trong mẫu số của cả bốn mức $p_n$ và vẫn tham gia bốn n-gram sai. Ngược lại, chậm rãi bị mất chỉ bị phạt gián tiếp qua brevity penalty — BLEU không có cơ chế nào khác để trách bạn vì bỏ sót.

3. Brevity penalty: vì sao BLEU cố tình không có recall

Nếu mẫu số luôn là bản dịch, thì cách gian lận hiển nhiên là dịch ít đi. Chỉ xuất ra kiếm — một token, chắc chắn có trong tham chiếu — và bạn được $p_1 = 100\%$.

Cách chữa "đúng" là thêm recall. BLEU không làm thế, vì thời nó ra đời người ta chấm với nhiều tham chiếu cùng lúc, và recall trên nhiều tham chiếu không có định nghĩa tự nhiên: bản dịch nên phủ tham chiếu nào? Thay vào đó BLEU dùng một hình phạt thô: nếu tổng độ dài bản dịch ngắn hơn tổng độ dài tham chiếu, nhân điểm với một hệ số nhỏ hơn 1.

$$\text{BP} = \begin{cases} 1 & \text{if } c > r \\[2pt] e^{\,1 - r/c} & \text{if } c \leq r \end{cases}$$
0 0.5 1.0 0.4 0.6 0.8 1.0 1.2 1.4 tỉ lệ độ dài c / r BP ví dụ của ta 8/9 = 0.889 → BP = 0.8825 ngắn hơn → bị phạt dài hơn → không phạt gì cả
Hình phạt bất đối xứng. Đường cong chỉ dốc về bên trái. Dịch dài lê thê không bị BP đụng tới một chút nào — nó chỉ bị các $p_n$ trừng phạt, vì mẫu số phình ra. Vế phải phẳng lì chính là lý do model có xu hướng "nói thêm cho chắc" vẫn giữ được BLEU tử tế.

Vài giá trị đáng thuộc lòng, để bạn ước lượng nhẩm được thiệt hại khi nhìn dòng ratio trong output của sacreBLEU:

$c/r$0.500.700.800.900.951.00
BP0.36790.65140.77880.89480.94871.0000

Bẫy thường gặp

  • BP được tính một lần trên toàn corpus, không phải từng câu. $c$ là tổng độ dài mọi bản dịch, $r$ là tổng độ dài mọi tham chiếu. Nghĩa là một câu bị dịch cụt có thể được bù bởi một câu khác dài dư — corpus-BLEU không thấy gì cả. Nếu model của bạn hay bỏ nửa câu, BLEU corpus là công cụ tệ để phát hiện; hãy nhìn phân bố $c_i/r_i$ theo câu.
  • BP phạt nặng hơn cảm giác. Mất đúng một token trong câu 20 token cho $\text{BP} = 0.9487$ — trừ thẳng 5.1% điểm cuối, kể cả khi 19 token còn lại hoàn hảo. Mất hai token: BP $= 0.8948$, trừ 10.5%.
  • Ratio hơi trên 1.0 là dấu hiệu tốt, không phải dấu hiệu xấu. Nhiều người thấy ratio = 1.04 rồi đi sửa cho về 1.00. Vô ích: BP đã bằng 1 rồi.

4. Ghép lại — chạy tay trọn vẹn một ví dụ

Công thức đầy đủ, với $N = 4$ và trọng số đều $w_n = 1/4$:

$$\text{BLEU} = \text{BP} \cdot \exp\!\left(\sum_{n=1}^{4} w_n \log p_n\right) = \text{BP} \cdot \left(p_1 p_2 p_3 p_4\right)^{1/4}$$

Nói bằng lời

Lấy trung bình nhân (geometric mean) của bốn tỉ lệ khớp, rồi nhân với hệ số phạt độ dài. Trung bình nhân chứ không phải trung bình cộng — và đó là một lựa chọn có hậu quả nặng: chỉ cần một mức $p_n$ bằng 0 là cả tích bằng 0, BLEU bằng 0, dù ba mức kia đẹp thế nào. Một câu 12 token dịch gần hoàn hảo nhưng không có nổi một four-gram trùng vẫn nhận điểm 0. Ta xử lý chuyện này ở mục 5.

Chạy tay một lần

Tham chiếu hắn chậm rãi rút thanh kiếm ra khỏi vỏ · Bản dịch hắn rút thanh kiếm ra khỏi vỏ kiếm

Mức 1 — unigram (8 cái). hắn ✓ · rút ✓ · thanh ✓ · kiếm ✓ · ra ✓ · khỏi ✓ · vỏ ✓ · kiếm ✗ (clip: tham chiếu chỉ có 1 chữ "kiếm") → $p_1 = 7/8 = 0.875$

Mức 2 — bigram (7 cái). hắn·rút ✗ · rút·thanh ✓ · thanh·kiếm ✓ · kiếm·ra ✓ · ra·khỏi ✓ · khỏi·vỏ ✓ · vỏ·kiếm ✗ → $p_2 = 5/7 \approx 0.7143$

Mức 3 — trigram (6 cái). hắn·rút·thanh ✗ · rút·thanh·kiếm ✓ · thanh·kiếm·ra ✓ · kiếm·ra·khỏi ✓ · ra·khỏi·vỏ ✓ · khỏi·vỏ·kiếm ✗ → $p_3 = 4/6 \approx 0.6667$

Mức 4 — four-gram (5 cái). hắn·rút·thanh·kiếm ✗ · rút·thanh·kiếm·ra ✓ · thanh·kiếm·ra·khỏi ✓ · kiếm·ra·khỏi·vỏ ✓ · ra·khỏi·vỏ·kiếm ✗ → $p_4 = 3/5 = 0.6$

Tích bốn precision. Ở đây các số hạng triệt tiêu đẹp đến mức tính nhẩm được:

$$p_1 p_2 p_3 p_4 = \frac{7}{8}\cdot\frac{5}{7}\cdot\frac{4}{6}\cdot\frac{3}{5} = \frac{7\cdot5\cdot4\cdot3}{8\cdot7\cdot6\cdot5} = \frac{420}{1680} = 0.25$$

Trung bình nhân. $\;(0.25)^{1/4} = 1/\sqrt{2} = 0.707107$

Brevity penalty. $c = 8 < r = 9$, nên $\text{BP} = e^{\,1 - 9/8} = e^{-0.125} = 0.882497$

Kết quả. $\;\text{BLEU} = 0.882497 \times 0.707107 = 0.624020$, tức 62.40 theo thang 100 điểm.

Đối chiếu: sacrebleu ref.vi -i hyp.vi -m bleu --tokenize none in ra BLEU = 62.4 · 87.5/71.4/66.7/60.0 · BP = 0.882 ratio = 0.889 hyp_len = 8 ref_len = 9. Bốn precision và BP khớp từng chữ số với phần tính tay ở trên.

Hãy nhìn kỹ bốn con số 87.5/71.4/66.7/60.0. Chúng luôn giảm dần khi $n$ tăng, và tốc độ giảm nói cho bạn biết nhiều hơn con số tổng: giảm chậm nghĩa là bản dịch giữ được cụm dài, giảm dốc nghĩa là nó chỉ nhặt đúng từ vựng mà đảo lộn trật tự.

Trước khi đọc tiếp, hãy tự gõ vài cặp câu vào bàn mổ dưới đây — đặc biệt là thử thêm/bớt một token ở cuối và xem bốn mức $p_n$ đổ theo kiểu gì.

Phòng thí nghiệm 01

Bàn mổ BLEU

Gõ thẳng vào hai ô — mọi con số tính lại ngay
Bản dịch người, coi như chuẩn vàng. Độ dài của nó là $r$.
Cái đang bị chấm. Độ dài của nó là $c$. Ba nút dưới đây sửa thẳng ô này.
Tách token thô: hạ chữ thường, tách dấu câu ra thành token riêng. Đây đã là một lựa chọn tokenization — đổi nó là BLEU nhảy vài điểm.
Dài c (dịch)—
Dài r (tham chiếu)—
Brevity penalty—
Trung bình nhân—
BLEU (thang 100)—
Nếu bỏ clipping—
Đọc con số

—

Cột nhạt: khớp thô · cột đậm: sau clipping · nét đứt: trung bình nhân của bốn precision
Mức Tổng n-gram trong câu dịch Khớp thô Khớp sau clipping Precision $p_n$
n = 1————
n-gram của câu dịch theo thứ tự — có màu: khớp và được tính · viền đỏ: khớp thô nhưng bị clipping cắt · xám: không khớp

Điều cần thấy: ① Cặp mặc định chỉ khác nhau đúng một chỗ ("một trận bất an" thành "một cảm giác bất an") mà BLEU đã tụt còn 76.25 — bốn precision lần lượt 0.882 / 0.813 / 0.733 / 0.643, $\mathrm{BP} = 1$ vì câu dịch dài hơn tham chiếu. ② Bấm Lặp một từ: câu dài ra 21 token, mức 1 khớp thô 19/21 = 0.905 nhưng sau clipping chỉ còn 15/21 = 0.714, BLEU 55.52 thay vì 58.90 nếu bỏ clipping. Clipping chính là thứ chặn trò spam một từ. ③ Bấm Cắt ngắn câu: còn 10 token, cả bốn precision đều tròn 1.0000 — hoàn hảo tuyệt đối — nhưng $\mathrm{BP} = \exp(1 - 16/10) = 0.5488$ kéo BLEU xuống 54.88. Bấm lần nữa: 6 token, $\mathrm{BP} = 0.1889$, BLEU 18.89. Lần thứ ba: 3 token, không tồn tại 4-gram nào (0/0), BLEU rơi thẳng về 0 — đúng chỗ sentence-BLEU bắt buộc phải smoothing. ④ Bấm Đổi từ đồng nghĩa sáu lần liên tiếp, nghĩa câu không đổi một li: BLEU đi 76.25 → 69.65 → 56.35 → 35.23 → 31.47 → 0 → 0. ⑤ Bấm Cặp câu khác: cặp đảo trật tự hai vế có $p_1 = 1.000$ (đúng từng chữ, chỉ đổi chỗ) vẫn được 75.05; cặp diễn đạt lại bằng từ Hán-Việt chỉ được 25.97; cặp dịch sót nguyên một vế có precision 1.000 / 0.900 / 0.889 / 0.875 nhưng $\mathrm{BP} = 0.6347$ ép xuống 58.06.

5. corpus-BLEU không phải trung bình của sentence-BLEU

Đây là chỗ nhiều người báo cáo sai mà không biết. corpus-BLEU gộp tử số và mẫu số của toàn bộ corpus trước, rồi mới chia một lần. Nó không phải là trung bình cộng của điểm từng câu. Ba câu ngắn sau cho thấy khoảng cách:

CâuTham chiếu / Bản dịchKhớp 1/2/3/4Tổng 1/2/3/4$c$$r$sentence-BLEU
1hắn chậm rãi rút thanh kiếm ra khỏi vỏ
hắn rút thanh kiếm ra khỏi vỏ kiếm
7 / 5 / 4 / 38 / 7 / 6 / 58962.40
2nàng khẽ mỉm cười rồi quay đi
nàng cười rồi bỏ đi
4 / 1 / 0 / 05 / 4 / 3 / 2570.00
3sư phụ gật đầu rồi quay người bỏ đi
sư phụ gật đầu rồi quay người rời đi
8 / 6 / 5 / 49 / 8 / 7 / 69975.06
Gộpcorpus19 / 12 / 9 / 722 / 19 / 16 / 13222555.63

Kiểm lại dòng cuối bằng tay: $p_1 = 19/22 = 0.8636$, $p_2 = 12/19 = 0.6316$, $p_3 = 9/16 = 0.5625$, $p_4 = 7/13 = 0.5385$. Trung bình nhân $= 0.6375$. $\text{BP} = e^{\,1-25/22} = 0.8725$. Tích $= 0.5563$, tức 55.63.

Trung bình cộng ba điểm câu thì ra $(62.40 + 0 + 75.06)/3 = \mathbf{45.82}$ — lệch gần 10 điểm. Thủ phạm là câu 2: nó nhận điểm 0 tuyệt đối chỉ vì không có nổi một trigram nào trùng, và trong trung bình cộng thì cái 0 đó đè nặng như một câu thật, trong khi ở corpus-BLEU nó chỉ đóng góp 5 trên tổng 22 unigram.

Vì thế mới cần smoothing khi bạn buộc phải chấm từng câu (để xếp hạng câu, để lọc data, để làm paired test ở Module 04). Phương pháp mặc định của sacreBLEU là exp: mức $n$ đầu tiên có tử số 0 thì thay $p_n$ bằng $\dfrac{1}{2^{k} \cdot \text{total}_n}$, với $k$ tăng dần 1, 2, 3… cho các mức 0 kế tiếp.

Áp cho câu 2: $p_3 = \dfrac{1}{2 \times 3} = 0.1667$ và $p_4 = \dfrac{1}{4 \times 2} = 0.125$. Trung bình nhân của $(0.8,\, 0.25,\, 0.1667,\, 0.125)$ là $0.2541$; nhân với $\text{BP} = e^{-0.4} = 0.6703$ ra 17.03. Trung bình cộng ba câu lúc này là 51.50 — vẫn khác 55.63, nhưng đã hết vô lý.

Bẫy thường gặp

  • Đừng bao giờ báo cáo trung bình sentence-BLEU rồi gọi nó là "BLEU". Hai con số khác nhau tới cả chục điểm trên cùng dữ liệu. Nếu bạn dùng sentence-BLEU, phải nói rõ là sentence-level và nói rõ smoothing nào.
  • Sentence-BLEU không có smoothing gần như vô dụng với câu dưới 10 token. Xác suất có ít nhất một four-gram trùng là thấp, mà thiếu là 0 ngay.
  • Smoothing không phải chuẩn. Chọn exp, floor, hay add-k cho ra ba bảng xếp hạng câu khác nhau. Ghi lại lựa chọn, đừng để nó là mặc định vô hình.

6. chrF: đếm n-gram ký tự thay vì n-gram từ

chrF đổi đơn vị đếm từ token sang ký tự, và khác BLEU ở ba điểm: nó bỏ hết khoảng trắng trước khi cắt n-gram, nó dùng tới 6 mức thay vì 4, và nó có recall. Điểm cuối quan trọng nhất: chrF là F-score, nên không cần brevity penalty.

$$\text{chrF}_\beta = (1 + \beta^2)\,\frac{P \cdot R}{\beta^2 P + R}, \qquad \beta = 2$$

Nói bằng lời

$P$ là trung bình cộng các precision ký tự qua các mức $n$, $R$ là trung bình cộng các recall. Với $\beta = 2$, recall được coi trọng gấp bốn lần precision (vì hệ số là $\beta^2$). Dịch sót bị phạt nặng hơn dịch thừa — ngược hẳn tinh thần của BLEU. Đó là lựa chọn có chủ đích: người đọc bản dịch tha thứ cho chữ thừa dễ hơn cho ý bị mất. Chú ý ký hiệu trùng: $\beta$ ở đây là trọng số F-score, không dính dáng gì tới $\beta$ — xác suất sai lầm loại II — dùng ở Module 05.

Chạy tay một lần — chrF2 ở mức 2-gram ký tự

Tham chiếu trường kiếm → bỏ khoảng trắng → trườngkiếm (10 ký tự)
Bản dịch thanh kiếm dài → thanhkiếmdài (12 ký tự)

Mức 1 — ký tự đơn. Tham chiếu: t r ư ờ n g k i ế m. Bản dịch: t h a n h k i ế m d à i. Khớp có clipping: t, n, k, i, ế, m → 6. Vậy $P_1 = 6/12 = 0.5$ và $R_1 = 6/10 = 0.6$.

Mức 2 — bigram ký tự. Tham chiếu (9 cái): tr · rư · ườ · ờn · ng · gk · ki · iế · ếm. Bản dịch (11 cái): th · ha · an · nh · hk · ki · iế · ếm · md · dà · ài. Khớp: ki, iế, ếm → 3. Vậy $P_2 = 3/11 = 0.2727$ và $R_2 = 3/9 = 0.3333$.

Chú ý bigram gk của tham chiếu: nó vắt qua chỗ khoảng trắng đã bị xoá. Nghĩa là chrF không nhìn thấy ranh giới từ chút nào — chữ ký của nó ghi space:no, và trường kiếm với trườngkiếm cho đúng cùng một điểm. Bù lại nó vẫn đếm được ngữ cảnh vắt qua chỗ nối, nên nó vẫn thấy được hai từ nào đang đứng liền nhau. Đó chính là lý do nó gần như miễn nhiễm với word segmentation — gạch đầu dòng thứ hai ở cuối mục này.

Trung bình qua hai mức. $P = (0.5 + 0.2727)/2 = 0.386364$ · $R = (0.6 + 0.3333)/2 = 0.466667$

F-beta với $\beta = 2$:

$$\text{chrF}_2 = \frac{5 \times 0.386364 \times 0.466667}{4 \times 0.386364 + 0.466667} = \frac{0.901515}{2.012121} = 0.448042$$

Tức 44.80. Đối chiếu sacrebleu --chrf-char-order 2: chrF2 = 44.80. Với mặc định 6 mức, cùng cặp câu này ra 21.16 — thấp hơn nhiều vì các mức 5 và 6 không khớp gì cả.

Còn BLEU của chính cặp câu này? 0.00. Không có nổi một bigram từ nào trùng.

Vì sao chrF hợp tiếng Việt hơn BLEU? Ba lý do rất cụ thể, minh hoạ bằng số:

Cặp câusentence-BLEU
(smooth: exp)
chrF2TER
hắn vung thanh kiếm sắc bén lên trời
hắn vung thanh kiêm sắc bén lên trời — sai một dấu thanh
50.0086.3212.50
hắn lập tức rời khỏi căn phòng
hắn ngay lập tức ra khỏi căn phòng — diễn đạt khác
27.0566.7128.57
Cặp câu chính của module này62.4069.5333.33

Cả ba hàng đều là sentence-BLEU với smoothing exp. Hàng 2 có $p_4 = 0/5$, nên tắt smoothing thì nó bằng đúng 0.00 — chính là trường hợp mục 5 vừa dựng. Hai hàng còn lại không có mức nào bằng 0, bật hay tắt smoothing cũng ra cùng con số.

  • Thanh điệu. Đổi kiếm thành kiêm là một lỗi rất nhỏ về mặt ký tự nhưng phá huỷ hoàn toàn một token. BLEU rơi từ 100 xuống 50.00, vì token đó nằm trong 1 unigram, 2 bigram, 3 trigram và 4 four-gram — bốn precision tụt còn 87.5/71.4/50.0/20.0. chrF2 chỉ xuống 86.32, vì kiếm và kiêm chung ba trong bốn ký tự.
  • Từ ghép và ranh giới từ. Tiếng Việt viết rời âm tiết, nên thanh kiếm và trường kiếm với BLEU là hai token hoàn toàn khác nhau, còn với chrF chúng chia sẻ chuỗi kiếm. Điều này cũng khiến chrF gần như miễn nhiễm với chuyện bạn có chạy word segmentation hay không — trong khi BLEU thì nhảy vài điểm, xem mục 8.
  • Hình vị Hán-Việt. Rất nhiều lỗi zh→vi là chọn sai một trong hai âm tiết Hán-Việt của một từ ghép. chrF cho điểm bán phần cho phần đúng; BLEU cho 0.

7. TER: đếm số thao tác sửa

TER (Translation Edit Rate) đo theo hướng ngược lại: một biên tập viên phải sửa bao nhiêu nhát để biến bản máy thành bản tham chiếu?

$$\text{TER} = \frac{E}{|y|}$$

với $E$ là số thao tác tối thiểu và $|y|$ là số token của tham chiếu. Điểm thấp là tốt — ngược chiều BLEU và chrF, một nguồn nhầm lẫn kinh điển khi đọc bảng kết quả.

Bốn loại thao tác, mỗi cái tính giá 1 bất kể dài ngắn:

  • Insertion — chèn một token còn thiếu. Trên cặp câu chính: chèn chậm, chèn rãi sau hắn → 2 thao tác.
  • Deletion — bỏ một token thừa: xoá kiếm ở cuối → 1 thao tác.
  • Substitution — đổi một token thành token khác. Không có trong ví dụ này.
  • Shift — dời nguyên một khối liền kề sang vị trí khác, giá vẫn là 1. Không có trong ví dụ này.

Vậy $E = 2 + 1 = 3$, tham chiếu có 9 token, $\text{TER} = 3/9 = 0.3333$ → 33.33. sacreBLEU cho đúng con số đó.

Thao tác shift là thứ khiến TER khác hẳn khoảng cách Levenshtein thông thường, và cũng là thứ khiến nó đắt để tính (tìm tổ hợp shift tối ưu là bài toán NP-khó, mọi cài đặt đều dùng heuristic tham lam). Hình dưới cho thấy sự khác biệt:

Bản dịch Tham chiếu ra khỏi vỏ hắn rút kiếm hắn rút kiếm ra khỏi vỏ 1 shift = 1 thao tác → TER = 1/6 = 16.67 Không có shift, khoảng cách sửa là 6 → TER = 6/6 = 100.00
Shift là lý do TER nhìn thấy được đảo trật tự. Cùng một cặp câu, có shift thì 16.67, không có shift thì 100.00 — chênh sáu lần. BLEU của cặp này bằng 0 dù 1-gram khớp 100% và 2-gram khớp 80% — vì không four-gram nào sống sót qua chỗ nối ($p_4 = 0/3$), mà trung bình nhân chỉ cần một thừa số 0 là bằng 0. Trật tự tự do của tiếng Việt khiến khoảng cách giữa hai con số đó xuất hiện thường xuyên hơn bạn tưởng.

8. Tokenization làm BLEU nhảy vài điểm — và sacreBLEU signature

BLEU đếm token. Ai định nghĩa token? Bạn. Và mọi lựa chọn đều cho ra một con số khác. Đây là cùng một cặp câu (lần này có dấu chấm và viết hoa), chấm bằng bốn cách tách token:

Cách tách tokenToken của tham chiếuBLEU
none — cắt theo khoảng trắng thô947.75
13a — mặc định sacreBLEU, tách dấu câu1056.48
word segmentation (kiểu VnCoreNLP) rồi --tokenize none856.23
char — mỗi ký tự là một token3169.54

Từ 47.75 tới 69.54 trên đúng một cặp câu, chỉ vì đổi cách cắt chữ. Chênh lệch giữa hai dòng đầu là 8.7 điểm — lớn hơn mọi cải tiến kiến trúc mà bạn từng vất vả giành được. Đây là lý do tuyệt đối không được so con số BLEU của bạn với con số trong một bài báo khác, trừ khi hai bên dùng cùng một pipeline chấm.

sacreBLEU tồn tại để chấm dứt chuyện đó: nó nhận vào bản dịch chưa tách token, tự tách theo một chuẩn cố định, và in kèm một signature mô tả toàn bộ cấu hình. Ba signature thật, lấy từ sacreBLEU 2.6.0:

Chữ ký in kèm mọi kết quả
BLEU   nrefs:1|case:mixed|eff:no|tok:13a|smooth:exp|version:2.6.0
chrF2  nrefs:1|case:mixed|eff:yes|nc:6|nw:0|space:no|version:2.6.0
TER    nrefs:1|case:lc|tok:tercom|norm:no|punct:yes|asian:no|version:2.6.0

Đọc từng trường của dòng BLEU: nrefs:1 — một tham chiếu; case:mixed — phân biệt hoa thường; eff:no — không dùng effective order (mức $n$ nào không có n-gram thì vẫn tính là 0, chứ không bỏ qua); tok:13a — tokenizer; smooth:exp — smoothing dùng khi cần. Hai chữ ký khác nhau ở bất kỳ trường nào cũng đủ để hai con số không so được.

Lệnh thật, chạy được ngay
# ref.vi và hyp.vi: mỗi dòng một câu, KHÔNG tách token trước, UTF-8
$ sacrebleu ref.vi -i hyp.vi -m bleu chrf ter --format text
        BLEU|nrefs:1|case:mixed|eff:no|tok:13a|smooth:exp|version:2.6.0 = 56.5 88.9/62.5/57.1/50.0 (BP = 0.895 ratio = 0.900 hyp_len = 9 ref_len = 10)
      chrF2|nrefs:1|case:mixed|eff:yes|nc:6|nw:0|space:no|version:2.6.0 = 67.6
TER|nrefs:1|case:lc|tok:tercom|norm:no|punct:yes|asian:no|version:2.6.0 = 44.4

# chỉ lấy con số, 2 chữ số thập phân, để nhét vào script CI.
# Một metric thì -b in số trần; nhiều metric cùng lúc thì nó in một danh sách có ngoặc vuông và dấu phẩy.
$ sacrebleu ref.vi -i hyp.vi -m bleu -b -w 2
56.48

# chrF++ : thêm 2-gram TỪ vào bên cạnh n-gram ký tự, thường bám điểm người sát hơn
$ sacrebleu ref.vi -i hyp.vi -m chrf --chrf-word-order 2 --format text

# JSON để log lại cùng commit hash — luôn lưu cả signature, không chỉ score
$ sacrebleu ref.vi -i hyp.vi -m bleu --format json

Bẫy thường gặp

  • Đừng tự tách token rồi mới đưa cho sacreBLEU. Nó sẽ tách thêm lần nữa. Nếu bạn buộc phải làm việc trên text đã tách (ví dụ output của CTranslate2 chưa detokenize), phải thêm --tokenize none và ghi rõ trong báo cáo — signature sẽ hiện tok:none.
  • 13a không biết gì về tiếng Việt. Nó chỉ tách dấu câu theo kiểu mteval-v13a. Với tiếng Việt điều đó là tốt: không có tokenizer "chuẩn" nào cho tiếng Việt, nên dùng cái đơn giản nhất và cố định nó lại còn hơn dùng cái thông minh mà mỗi phiên bản một khác.
  • Số phiên bản là một phần của chữ ký. Nâng cấp sacreBLEU giữa hai lần đo là đủ để làm lệch con số. Ghim version trong requirements.txt của bộ eval.
  • Chênh lệch 8.7 điểm ở bảng trên là chênh lệch của cách đo, không phải của model. Nếu bạn đổi tokenizer giữa baseline và bản mới, mọi kết luận đều vô nghĩa.

Nó ứng dụng vào đâu trong đời thật

  • Chọn metric chủ cho zh→vi. Bảng ở mục 6 cho thấy với lỗi thanh điệu, BLEU rơi 50 điểm còn chrF2 chỉ rơi 14. Nếu bộ test của bạn đầy tên riêng và từ Hán-Việt — đúng thể loại tiên hiệp — thì BLEU đang khuếch đại một loại lỗi và bỏ qua loại khác. Đặt chrF2 làm metric chủ của vòng lặp phát triển hằng ngày — mỗi commit, mỗi checkpoint — BLEU làm guardrail để dễ so với bên ngoài, TER để ước lượng công biên tập. Ở cổng ship thì metric chủ lại là COMET chứ không phải chrF2: ba tầng metric nằm ở Module 02, bảng quyết định ở Module 10.
  • Đọc lại các bảng so sánh cũ của bạn. Nếu có bảng nào ghi BLEU mà không kèm signature, con số đó không dùng lại được — kể cả để so với chính bạn ba tháng trước. Lệnh sinh lại chỉ mất vài giây; hãy chạy lại toàn bộ với một cấu hình duy nhất và lưu signature vào cùng file với score.
  • Bệnh lặp đuôi của decode. Clipping khiến các token lặp không lấy được điểm, nhưng nó không phạt mạnh: bỏ hẳn chữ kiếm thừa đi thì BLEU chỉ nhích từ 62.40 lên 63.19 (xem câu hỏi 1 phía dưới) — chưa tới 0.8 điểm. Nếu bạn đang debug lặp trong bản NAR/CTC, BLEU là công cụ quá tù; dùng TER (mỗi token thừa là một deletion, phạt tuyến tính) hoặc đếm thẳng tỉ lệ câu có n-gram lặp.
  • Bộ lọc data bằng sentence-BLEU. Rất hay dùng để lọc cặp song ngữ bẩn hoặc để chọn mẫu cho distillation. Bắt buộc bật smoothing, nếu không mọi câu ngắn đều nhận 0 và bạn sẽ lọc mất đúng phần data ngắn — vốn là phần hội thoại nhiều nhất trong web novel.
  • Điều module này chưa cho bạn: mọi con số ở trên đều là điểm trần trụi, không sai số. Chênh lệch 0.4 BLEU giữa hai checkpoint có thật hay không là câu hỏi của Module 03 và Module 04.

Tự kiểm tra

1 · Bản dịch hắn rút thanh kiếm ra khỏi vỏ (bỏ chữ kiếm thừa ở cuối) sẽ có BLEU cao hơn hay thấp hơn 62.40? Ước lượng bằng tay.

Cao hơn khá nhiều. Bỏ token thừa thì $c = 7$, các mẫu số giảm còn 7/6/5/4 trong khi tử số vẫn 7/5/4/3. Vậy $p_1 = 7/7 = 1$, $p_2 = 5/6$, $p_3 = 4/5$, $p_4 = 3/4$. Tích $= 0.5$, trung bình nhân $= 0.5^{1/4} = 0.8409$. Nhưng BP xấu đi: $c = 7$, $r = 9$ nên $\text{BP} = e^{\,1-9/7} = e^{-0.2857} = 0.7515$. Kết quả $0.7515 \times 0.8409 = 0.6319$, tức 63.19 — chỉ nhỉnh hơn 0.79 điểm. Bài học: BP gần như nuốt trọn phần thưởng của việc bỏ token rác. BLEU không cho bạn nhiều động lực sửa lỗi lặp.

2 · Một hệ thống dịch mọi câu đúng một token duy nhất, chọn token phổ biến nhất của tham chiếu. Trên corpus 800 câu, tham chiếu trung bình 18 token. BLEU corpus xấp xỉ bao nhiêu?

Xấp xỉ 0, và không phải vì precision. $p_1$ có thể rất cao (mỗi câu một token đúng → gần 100%). Nhưng câu một token thì không có bigram nào cả, nên $p_2 = p_3 = p_4 = 0$ và trung bình nhân bằng 0. Kể cả nếu ta bỏ qua chuyện đó: $c = 800$, $r = 800 \times 18 = 14400$, tỉ lệ $c/r = 1/18 = 0.0556$, cho $\text{BP} = e^{\,1-18} = e^{-17} \approx 4.1 \times 10^{-8}$. Hai cơ chế độc lập cùng chặn kiểu gian lận này — đó chính là lý do BLEU cần cả clipping lẫn BP chứ không chỉ một trong hai.

3 · Bạn đo được BLEU 34.2 với tok:13a. Đồng nghiệp đo lại cùng model, cùng bộ test, được 31.8 với tok:none. Ai sai?

Không ai sai — hai người đo hai thứ khác nhau. Bảng ở mục 8 cho thấy chỉ riêng chuyện tách dấu câu đã làm lệch 8.7 điểm trên một cặp câu; trên corpus thì lệch thường nhỏ hơn nhưng vài điểm là chuyện thường. tok:none gộp dấu câu vào từ cuối, tạo ra hàng loạt token duy nhất không bao giờ khớp, nên điểm thấp hơn là dự đoán được. Việc phải làm: thống nhất một signature cho cả nhóm, ghi vào file cấu hình eval, và đo lại cả hai hệ thống bằng cấu hình đó. Không được phép quy đổi 31.8 sang thang 13a bằng một hệ số.

4 · Vì sao chrF không cần brevity penalty còn BLEU thì cần?

Vì chrF là F-score, tức nó đã có recall trong công thức. Dịch cụt làm recall tụt ngay: nếu bản dịch chỉ chứa 4 trong 10 ký tự của tham chiếu thì $R_1 = 0.4$ dù precision là 1.0, và với $\beta = 2$ recall còn được nhân trọng số gấp bốn. BLEU thì mẫu số của mọi $p_n$ đều là bản dịch, nên rút ngắn bản dịch chỉ làm tăng precision — không có gì trong bốn số $p_n$ nhìn thấy phần tham chiếu bị bỏ sót. BP là miếng vá dán ngoài để bịt đúng lỗ hổng đó, và vì nó tính trên tổng độ dài toàn corpus nên nó là miếng vá khá thô.

5 · Trên ba câu ở mục 5, corpus-BLEU là 55.63 còn trung bình sentence-BLEU có smoothing là 51.50. Nếu bạn thêm vào corpus 20 câu rất ngắn (3 token) dịch hoàn hảo, con số nào thay đổi nhiều hơn?

Trung bình sentence-BLEU thay đổi mạnh hơn nhiều — nhưng đổi theo chiều nào thì lại phụ thuộc một quy ước ít ai để ý. Câu 3 token không có mức four-gram nào để mà khớp. Bật effective order (sacreBLEU khuyến nghị bật nó cho sentence-level, và in cảnh báo nếu bạn quên) thì mức 4 bị bỏ qua, mỗi câu đó được 100, trung bình vọt từ 51.50 lên 93.67. Giữ đúng eff:no như chữ ký ở mục 8 thì mức 4 tính là 0, mỗi câu đó được 0, và trung bình rơi xuống 6.72. Cả hai chiều đều động mạnh hơn corpus-BLEU: nó cộng dồn n-gram, 20 câu ba token chỉ thêm 60 unigram, 40 bigram, 20 trigram và 0 four-gram vào các tổng vốn đã là 22/19/16/13, nên nó chỉ nhích từ 55.63 lên 75.10. Đây chính là điểm mấu chốt: corpus-BLEU cân theo số n-gram, trung bình sentence-BLEU cân theo số câu. Bộ test nhiều câu ngắn sẽ đẩy hai con số xa nhau.

Phần I — Metric: từ n-gram tới neural · Module 02

02 Metric học được: BERTScore, COMET, BLEURT

Cần biết trước: Module 01, và khái niệm embedding (quyển Mạng Nơ-ron đã dựng) Dựng nên: tiêu chí chọn metric, và cách đo chất lượng của chính metric Dẫn tới: Module 03 — sai số của một con số

Mục tiêu

  • Chỉ ra được bốn loại lỗi mà mọi metric đếm n-gram không thể nhìn thấy, kèm ví dụ zh→vi cho từng loại.
  • Tính tay BERTScore precision / recall / F1 từ một ma trận cosine 3×3 bằng ghép tham lam (greedy matching), và giải thích được vì sao $P \neq R$.
  • Nói được COMET học cái gì, từ dữ liệu nào, và khác BERTScore ở chỗ nào về bản chất.
  • Tính tay $\rho$ (Spearman) và $\tau$ (Kendall) trên 6 cặp điểm, và biết chọn hệ số nào cho loại quyết định nào.
  • Đưa ra một bộ metric cụ thể cho dự án dịch web novel zh→vi, có lý do bằng số cho từng lựa chọn.

1. Bốn chỗ mù của n-gram

Module 01 để lại một khoảng trống rất lớn: BLEU chỉ biết đếm chuỗi ký tự trùng nhau. Nó không có khái niệm "nghĩa". Bốn hệ quả, kèm ví dụ thật của thể loại tiên hiệp:

  1. Đồng nghĩa. Tham chiếu hắn lạnh lùng mỉm cười, bản dịch gã cười nhạt một cách lạnh lẽo. Hai câu gần như cùng nghĩa. Số 1-gram trùng sau clipping: đúng 2 trên 7 token của bản dịch, tức precision 1-gram $= 2/7 = 0.286$. Số 2-gram trùng: 0. Vì BLEU lấy trung bình nhân qua 4 mức n, một mức bằng 0 kéo cả tích về 0. Một bản dịch tốt bị chấm 0 điểm.
  2. Trật tự tự do. Tiếng Việt cho phép đảo trạng ngữ: sáng hôm sau, hắn xuống núi và hắn xuống núi vào sáng hôm sau. Cùng nghĩa, cùng bộ 1-gram, nhưng bộ 3-gram và 4-gram lệch hẳn.
  3. Một câu có nhiều bản đúng. Bạn thường chỉ có một tham chiếu. Mọi cách diễn đạt đúng khác đều bị tính là sai. Đây là lỗi hệ thống, không phải nhiễu: nó luôn kéo điểm xuống, không bao giờ kéo lên.
  4. Lỗi nặng nhẹ như nhau. Dịch ba nghìn lạng bạc thành hai nghìn lạng bạc làm sai một token. Dịch hắn thành gã cũng sai một token. BLEU trừ điểm y hệt nhau, dù lỗi thứ nhất phá vỡ cốt truyện còn lỗi thứ hai thì người đọc không nhận ra.

Metric học được (learned metric) ra đời để vá ba lỗi đầu. Cần nói thẳng ngay từ đây: chúng gần như không vá được lỗi thứ tư — mục 5 sẽ tính ra con số cụ thể cho chuyện đó.

2. BERTScore: ghép tham lam trên ma trận cosine

Ý tưởng ngắn gọn tới mức có thể viết trong một câu: thay vì so token với token bằng phép bằng nhau, hãy so vector với vector bằng cosine, rồi mỗi token đi tìm bạn giống mình nhất ở phía bên kia.

Cụ thể, cho câu tham chiếu $r$ có $|r|$ token và bản dịch $\hat{c}$ có $|\hat{c}|$ token. Đưa cả hai qua một encoder (BERT, XLM-R, PhoBERT…) để lấy vector ngữ cảnh $v_i$ cho từng token. Rồi:

$$P = \frac{1}{|\hat{c}|}\sum_{j \in \hat{c}} \max_{i \in r} \cos(v_i, v_j), \qquad R = \frac{1}{|r|}\sum_{i \in r} \max_{j \in \hat{c}} \cos(v_i, v_j), \qquad F_1 = \frac{2PR}{P+R}$$

Nói bằng lời

Precision hỏi: "mỗi chữ tôi viết ra có chỗ dựa nào bên tham chiếu không?" — nó phạt chữ thừa, chữ bịa. Recall hỏi ngược lại: "mỗi chữ tham chiếu có ai bên bản dịch nhận không?" — nó phạt chữ bị bỏ sót. Hai câu hỏi khác nhau, nên hai con số khác nhau, và chênh lệch giữa chúng chính là thông tin.

Chữ tham lam (greedy) nằm ở chỗ: mỗi token tự chọn bạn tốt nhất của riêng mình, không ai điều phối. Kết quả không phải một song ánh: hai token bản dịch có thể cùng chọn một token tham chiếu, và một token tham chiếu có thể không được ai chọn.

① Chiều precision — token BẢN DỊCH đi tìm bản dịch (3 token) tham chiếu (3 token) gã mỉm cười hắn cười lạnh 0.860.710.95 không mũi tên nào tới P = (0.86 + 0.71 + 0.95) / 3 = 0.84 ② Chiều recall — token THAM CHIẾU đi tìm bản dịch (3 token) tham chiếu (3 token) gã mỉm cười hắn cười lạnh 0.860.950.20 R = (0.86 + 0.95 + 0.20) / 3 = 0.67 F1 = 2PR/(P+R) = 0.745 · hai token cùng chọn “cười”, còn “lạnh” chỉ vớt được 0.20 — recall là chỗ mất điểm
Ghép tham lam chạy hai lần theo hai chiều ngược nhau trên cùng một ma trận cosine. Chiều precision bỏ quên “lạnh” hoàn toàn (không mũi tên nào trỏ tới nó); chiều recall bắt buộc “lạnh” phải nhận một bạn, và bạn tốt nhất của nó chỉ đạt 0.20. Đó là toàn bộ lý do $P = 0.84$ mà $R = 0.67$.

Chạy tay một lần — BERTScore trên ma trận 3×3

Tham chiếu: hắn / cười / lạnh. Bản dịch máy: gã / mỉm / cười. Giả sử encoder cho ra ma trận cosine sau (hàng = token bản dịch, cột = token tham chiếu). Số in đậm là cực đại của hàng.

coshắncườilạnhmax hàng
gã0.860.220.140.86
mỉm0.180.710.200.71
cười0.210.950.170.95
max cột0.860.950.20—

Bước 1 — precision. Lấy cực đại từng hàng: $0.86 + 0.71 + 0.95 = 2.52$. Chia cho 3 token bản dịch: $P = 2.52/3 = \mathbf{0.84}$.

Bước 2 — recall. Lấy cực đại từng cột: $0.86 + 0.95 + 0.20 = 2.01$. Chia cho 3 token tham chiếu: $R = 2.01/3 = \mathbf{0.67}$.

Bước 3 — F1. $F_1 = 2 \cdot 0.84 \cdot 0.67 / (0.84 + 0.67) = 1.1256 / 1.51 = \mathbf{0.745}$.

Đọc kết quả. Chênh lệch $P - R = 0.17$ nói đúng một chuyện: bản dịch không bịa gì cả, nhưng nó đánh rơi một ý. Ý bị rơi là “lạnh” — cột lạnh có cực đại chỉ 0.20, tức không token nào trong gã mỉm cười mang được sắc thái lạnh lùng. Đây chính xác là kiểu lỗi mà một con số F1 duy nhất sẽ giấu đi, còn cặp $(P, R)$ thì chỉ thẳng vào.

Chú ý thêm hai chi tiết của ghép tham lam mà bạn nhìn thấy ngay trên bảng: cột “cười” được hai token bên bản dịch cùng chọn (0.71 và 0.95), còn hàng “mỉm” bị ép làm bạn của “lạnh” trong chiều recall dù nó chỉ đạt 0.20. Không có ràng buộc một-đối-một nào ở đây.

Toàn bộ BERTScore, một khi đã có ma trận cosine
# hàng j = token bản dịch, cột i = token tham chiếu
M = [[0.86, 0.22, 0.14],
     [0.18, 0.71, 0.20],
     [0.21, 0.95, 0.17]]

P  = sum(max(hang) for hang in M) / len(M)          # cực đại theo hàng
R  = sum(max(cot) for cot in zip(*M)) / len(M[0])   # cực đại theo cột
F1 = 2 * P * R / (P + R)
print(f"P={P:.3f}  R={R:.3f}  F1={F1:.3f}")         # P=0.840  R=0.670  F1=0.745

w = [0.9, 0.4, 1.7]                                 # idf của hắn / cười / lạnh
R_idf = sum(wi * max(cot) for wi, cot in zip(w, zip(*M))) / sum(w)
print(f"R_idf={R_idf:.3f}")                         # R_idf=0.498

Phần khó của BERTScore không nằm ở công thức — nó nằm ở việc chọn encoder nào và lấy vector ở tầng nào. Đó là hai siêu tham số ẩn, và đổi chúng là đổi luôn thang điểm.

Bẫy thường gặp

  • Trọng số idf đổi kết luận. Bản gốc cho phép nhân mỗi token với idf của nó. Với idf giả định hắn = 0.9, cười = 0.4, lạnh = 1.7 (từ hiếm thì idf cao), recall thành $R_{\text{idf}} = (0.9{\cdot}0.86 + 0.4{\cdot}0.95 + 1.7{\cdot}0.20)/3.0 = 1.494/3.0 = 0.498$ — tụt 0.172 so với 0.67. Cùng một bản dịch, hai con số cách nhau xa, chỉ vì bật một cờ. Ghi rõ bạn có bật idf hay không, luôn luôn.
  • Thang điểm bị nén. Vector ngữ cảnh của cùng một encoder bị dồn về một nón hẹp trong không gian, nên cosine giữa hai token thật sự hiếm khi xuống dưới 0.6 — ma trận 3×3 ở trên đã kéo giãn các ô thấp xuống dải 0.14–0.22 cho dễ nhìn, số thật sát nhau hơn nhiều. Hệ quả: BERTScore thô nằm gọn trong một dải hẹp và "0.87" trông đẹp một cách vô nghĩa. Bản gốc chữa bằng cách trừ đi một baseline $b$ đo trên các cặp câu ghép ngẫu nhiên: $\tilde{s} = (s-b)/(1-b)$. Với $b = 0.70$ giả định, $F_1 = 0.745$ trở thành $0.151$, và $R = 0.67$ trở thành $-0.10$ — âm, nghĩa là "còn tệ hơn ghép ngẫu nhiên". Rescale không đổi thứ hạng, nhưng đổi hoàn toàn cảm giác của người đọc báo cáo.
  • Không so được giữa hai cấu hình. BERTScore của bạn (encoder X, tầng 9, có idf, có rescale) và BERTScore trong một bài báo (encoder Y, tầng 17, không idf) là hai đại lượng khác nhau. Đúng như bài học sacreBLEU ở Module 01: chữ ký cấu hình là một phần của con số.

3. COMET: hồi quy thẳng lên điểm người

BERTScore vẫn là một công thức do người viết ra: nó giả định rằng cosine trung bình là thứ tương ứng với chất lượng. COMET bỏ hẳn giả định đó và hỏi một câu khác: nếu ta đã có hàng chục nghìn câu được người chấm điểm, sao không huấn luyện thẳng một model dự đoán điểm đó?

Đầu vào có ba phần chứ không phải hai: câu nguồn $s$ (tiếng Trung), bản dịch máy $h$, và bản tham chiếu $r$. Ba câu đi qua cùng một encoder đa ngữ, thành ba vector, rồi được ghép thành đặc trưng và đưa qua một MLP nhỏ cho ra một số thực. Đặc trưng đó chứa cả tích và hiệu của $h$ với $s$ chứ không chỉ với $r$ — đúng chỗ đó là thứ BERTScore không có: nó không bao giờ nhìn câu nguồn. Hàm mất mát là bình phương sai lệch so với điểm người:

$$\mathcal{L} = \frac{1}{N}\sum_{k=1}^{N}\big(\hat{y}_k - y_k\big)^2$$
src · câu Trung hyp · bản máy ref · bản người bản QE bỏ ô này Encoder đa ngữmột bộ trọng sốdùng chung cho cả ba s h r Ghép đặc trưng[ h ; r ; h⊙s ; h⊙r ;|h−s| ; |h−r| ]bản QE: bỏ các số hạng có r MLP hồi quy ŷ một số thực Huấn luyện: chỉnh trọng số để bình phương sai lệch với điểm người y (DA hoặc MQM) nhỏ nhất Suy ra: metric này chỉ đúng trong đúng cái miền mà người ta đã bỏ tiền ra chấm
COMET không phải một công thức, nó là một model đã được train. Điều đó đổi bản chất của mọi câu hỏi bạn đặt cho nó: không còn là "công thức này hợp lý không" mà là "nó đã thấy dữ liệu nào, và câu của tôi có giống dữ liệu đó không".

Điểm người $y$ đến từ hai nguồn quen thuộc: DA (direct assessment — người kéo thanh trượt 0–100) và MQM (multidimensional quality metrics — người đánh dấu từng lỗi kèm mức độ nặng nhẹ rồi quy ra điểm phạt). Module 06 sẽ dựng hai thang này tử tế; ở đây chỉ cần biết chúng là nhãn của bài toán hồi quy.

Bỏ ô ref đi thì được bản QE (quality estimation, ước lượng chất lượng không tham chiếu): chỉ cần câu nguồn và bản dịch. Đây là thứ duy nhất trong cả module này dùng được trên dữ liệu production — toàn bộ kho chương web novel bạn đã dịch ra và không hề có bản người dịch để đối chiếu (giả sử kho đó cỡ 40 nghìn chương). Đổi lại, nó dễ bị lừa bởi bản dịch trôi chảy nhưng sai nghĩa, vì không có gì neo nó vào nội dung đúng.

BLEURT đứng giữa hai họ trên: cũng là hồi quy lên điểm người, nhưng thêm một giai đoạn tiền huấn luyện trên hàng triệu cặp câu tổng hợp — lấy câu gốc rồi cố tình làm hỏng (xoá từ, đảo chữ, dịch vòng) và gán nhãn bằng chính BLEU/ROUGE. Nhờ đó nó bớt đói dữ liệu người. Về mặt sử dụng, bạn đối xử với BLEURT hệt như COMET: một model, có miền huấn luyện, có ngày hết hạn.

4. Đo chính metric: Pearson, Spearman, Kendall

Đây là mục quan trọng nhất module, và là mục hầu hết người dùng metric bỏ qua. Một metric học được không tự nhiên đúng. Nó là một ước lượng, và bạn phải kiểm nó y như kiểm một model: lấy một bộ câu đã có điểm người, chạy metric, rồi đo xem hai dãy số bám nhau tới đâu.

Có ba hệ số, và chúng trả lời ba câu hỏi khác nhau:

  • Pearson $r$ — hai dãy có quan hệ tuyến tính tới đâu. Nhạy với dạng hàm và với điểm ngoại lai.
  • Spearman $\rho$ — chính là Pearson tính trên hạng thay vì trên giá trị. Chỉ quan tâm thứ tự.
  • Kendall $\tau$ — đếm tỉ lệ cặp được xếp đúng chiều. Gần nhất với câu hỏi thực tế của bạn.
$$\rho = 1 - \frac{6\sum_i \delta_i^2}{n(n^2-1)}, \qquad \tau = \frac{C - D}{\binom{n}{2}}$$

Nói bằng lời

$\delta_i$ là hiệu hạng của câu thứ $i$ giữa hai bảng xếp hạng. Nếu hai bảng trùng khít thì mọi $\delta_i = 0$ và $\rho = 1$. Chú ý ký hiệu: $\delta_i$ ở đây là hiệu hạng, khác hẳn $d_i$ trong bảng ký hiệu đầu sách — cái đó là hiệu điểm ghép cặp giữa hai hệ thống, chuyện của Module 04. Phần $n(n^2-1)$ chỉ là hằng số chuẩn hoá để $\rho$ nằm trong $[-1, 1]$.

Với Kendall: lấy mọi cặp câu, hỏi "người nói câu A hơn câu B, metric có nói vậy không?". $C$ là số cặp đồng thuận, $D$ là số cặp ngược nhau. $\tau$ chỉ là hiệu chia tổng.

Chạy tay một lần — $\rho$ và $\tau$ trên 6 câu

Sáu câu zh→vi, mỗi câu có một điểm người (DA, thang 0–100) và một điểm metric. Hạng 1 = tốt nhất.

Câuđiểm ngườihạng ngườiđiểm metrichạng metric$\delta$$\delta^2$
s19210.81100
s27830.66300
s38520.74200
s44060.584+24
s56140.356−24
s65550.49500
Tổng————08

Spearman. $n = 6$ nên $n(n^2-1) = 6 \cdot 35 = 210$. Tử số $6 \sum \delta_i^2 = 6 \cdot 8 = 48$. Vậy

$$\rho = 1 - \frac{48}{210} = 1 - 0.2286 = \mathbf{0.7714}$$

Kendall. Có $\binom{6}{2} = 15$ cặp. Đi qua từng cặp và hỏi hai bảng có cùng chiều không. Chỉ ba cặp lệch chiều, và cả ba đều dính tới cụm s4–s5–s6:

  • (s4, s5): người nói s5 hơn (61 > 40), metric nói s4 hơn (0.58 > 0.35) → nghịch.
  • (s4, s6): người nói s6 hơn (55 > 40), metric nói s4 hơn (0.58 > 0.49) → nghịch.
  • (s5, s6): người nói s5 hơn (61 > 55), metric nói s6 hơn (0.49 > 0.35) → nghịch.

Vậy $D = 3$, $C = 15 - 3 = 12$, và

$$\tau = \frac{12 - 3}{15} = \frac{9}{15} = \mathbf{0.600}$$

Pearson không đi qua hạng, nó ăn thẳng vào giá trị. Gọi $x_i$ là điểm người, $y_i$ là điểm metric:

$$r = \frac{\sum_i (x_i-\bar{x})(y_i-\bar{y})}{\sqrt{\sum_i (x_i-\bar{x})^2}\,\sqrt{\sum_i (y_i-\bar{y})^2}}$$

Tử số hỏi hai dãy có cùng lệch khỏi trung bình của chính mình theo cùng một chiều hay không; mẫu số chỉ chuẩn hoá kết quả về $[-1, 1]$. Bấm máy trên đúng sáu dòng của bảng: $\bar{x} = 411/6 = 68.5$ và $\bar{y} = 3.63/6 = 0.605$. Tử số bằng $11.745$. Hai tổng bình phương ở mẫu: $\sum (x_i-\bar{x})^2 = 1965.5$ nên căn là $44.334$, và $\sum (y_i-\bar{y})^2 = 0.14215$ nên căn là $0.37703$. Vậy $r = 11.745/(44.334 \cdot 0.37703) = \mathbf{0.7027}$ — con số thứ ba, lại khác nữa.

Đọc ba con số. Chúng không so sánh được với nhau: $\tau = 0.600$ không "tệ hơn" $\rho = 0.7714$, chúng đo hai thứ khác nhau trên hai thang khác nhau. Với $n$ vừa phải và không có hạng bằng nhau, $\tau$ gần như luôn nhỏ hơn $\rho$; ở đây tỉ số là $0.600/0.7714 = 0.778$. Nếu bạn thấy ai đó khoe "tương quan 0.77" mà không nói hệ số nào, con số đó chưa dùng được.

$\tau$ dịch thẳng ra một câu tiếng Việt dùng được

Khi không có hạng bằng nhau, $C + D = \binom{n}{2}$, nên accuracy trên cặp $= C/(C+D) = (\tau+1)/2$. Ở ví dụ trên: $(0.600+1)/2 = 0.80$ — metric xếp đúng chiều 12 trên 15 cặp câu, tức 80%.

Đây là dạng phát biểu dễ mang đi họp nhất — nhưng phải đọc kèm cỡ mẫu. "Trên 6 câu này, nếu tôi lấy hai bản dịch bất kỳ và hỏi metric cái nào hơn, nó trả lời đúng 12 trên 15 lần." Bỏ mấy chữ "trên 6 câu" đi thì câu nói thành vô căn cứ: với $n = 6$, con số 80% chưa phân biệt được với chấm bừa — xem bẫy ngay dưới.

Và phải nói rõ nó đo cái gì: $\tau$ ở đây đo khả năng metric xếp đúng thứ tự hai câu bất kỳ. Nó không trả lời "checkpoint A hay checkpoint B" — câu đó là so hai trung bình trên cùng một bộ test, một bài toán khác hẳn, của Module 04. Cái $\tau$ cho bạn là quyền tin vào con số mà Module 04 sẽ đem đi so.

Ba hệ số phản ứng rất khác nhau trước cùng một thay đổi dữ liệu. Hai thí nghiệm trên đúng bộ 6 câu ở trên:

  • Biến đổi đơn điệu. Thay điểm metric bằng lập phương của nó (thứ tự giữ nguyên tuyệt đối): Pearson nhảy từ $0.7027$ lên $\mathbf{0.7966}$, còn $\rho$ và $\tau$ không đổi một chút nào. Tức là bạn có thể "cải thiện" Pearson bằng cách calibrate lại metric mà không làm nó thông minh hơn một tí nào.
  • Một điểm ngoại lai. Chỉ hạ điểm metric của s5 từ 0.35 xuống 0.05 (thứ tự vẫn nguyên): Pearson rơi từ $0.7027$ xuống $\mathbf{0.5189}$, mất $0.184$ — vì một câu. $\rho$ và $\tau$ lại không đổi.

Kết luận thực dụng: để chọn metric, dùng $\tau$ hoặc accuracy trên cặp. Chỉ dùng Pearson khi bạn thật sự quan tâm tới giá trị tuyệt đối của điểm, ví dụ khi đặt ngưỡng "COMET dưới 0.6 thì chuyển cho người biên tập". Nhắc lại phạm vi: $\tau$ giúp bạn chọn metric, còn chọn checkpoint thì vẫn phải chạy kiểm định ghép cặp ở Module 04 trên chính bộ test của bạn.

Kéo thử cụm điểm ba chiều dưới đây — xoay nó để thấy ba hệ số cùng thay đổi khi bạn kéo một câu ra khỏi đám mây, trước khi đọc tiếp.

Phòng thí nghiệm 02 · 3D

Metric có bám điểm người không

Kéo chuột để xoay khối · lăn chuột để phóng to
Mỗi chấm là một câu — x: điểm người · y: BLEU · z: COMET (cả ba chuẩn hoá về khoảng −1 … 1)
120
Kéo xuống 30 rồi bấm Mẫu câu khác vài lần: cùng một sự thật, ba hệ số nhảy loạn.
0.85
BLEU đếm n-gram trùng nhau nên rung mạnh quanh ý người. Về 0 thì chỉ còn méo phi tuyến.
0.35
Metric học được cũng chỉ là một cái thước có sai số. Kéo qua 1.0 là nó tụt xuống ngang BLEU.
Pearson · BLEU—
Spearman · BLEU—
Kendall · BLEU—
Cặp đúng · BLEU—
Pearson · COMET—
Spearman · COMET—
Kendall · COMET—
Cặp đúng · COMET—
Metric nào bám người hơn

—

Điều cần thấy: ① Ở mức mặc định (120 câu), BLEU được Pearson 0.701 còn COMET được 0.966. Đừng nhìn con số, nhìn hai cái bóng: bóng người × COMET trên vách sau là một dải mảnh ôm lấy đường hồi quy, còn bóng người × BLEU dưới đáy hộp là một đám bầu dục dày. Khoảng cách giữa 0.97 và 0.70 chính là bề dày ấy. ② Kéo nhiễu BLEU về 0 — chỉ còn lại méo phi tuyến. Spearman và Kendall của BLEU nhảy lên đúng 1.000, cặp đúng 100%, nhưng Pearson dừng ở 0.975. Tương quan hạng không quan tâm cái thước có cong hay không; Pearson thì có. Đây là lý do khi so metric người ta hay báo tương quan hạng chứ không chỉ báo Pearson. ③ Kéo số câu từ 300 xuống 30 rồi bấm ⟳ liên tục. Ở 30 câu, 95% số mẫu cho Pearson của BLEU rơi vào [0.46, 0.82] — rộng 0.36 điểm. Ở 300 câu bề rộng ấy còn 0.12. Cùng một BLEU, cùng một sự thật, chỉ khác cỡ mẫu. Cách đóng khung cái nhảy đó bằng một con số là việc của Module 03. ④ Kéo nhiễu COMET lên 1.2 (giữ nguyên 120 câu, nhiễu BLEU 0.85): Pearson chấm COMET cao hơn — 0.741 so với 0.701 — nhưng Spearman (0.705 so với 0.738) và tỉ lệ cặp đúng (75.7% so với 76.2%) chấm ngược lại. Ba hệ số không phải ba cách nói cùng một điều. Bạn phải chọn trước sẽ báo cái nào, trước khi nhìn số. ⑤ Ô Kendall và ô cặp đúng luôn khớp nhau theo công thức $\text{acc} = (1+\tau)/2$: mặc định $(1+0.523)/2 = 0.762$. Chúng là một con số nói theo hai kiểu — τ dùng để so, còn "cặp đúng" thì đọc lên hội nghị được: 91 trong 100 cặp câu, COMET xếp cùng chiều với người.

Bẫy thường gặp

  • Sáu câu thì con số nào cũng vô nghĩa. Xáo ngẫu nhiên hạng của metric có $6! = 720$ cách. Trong đó có đúng 37 cách cho $\rho \geq 0.7714$, tức 5.1%. Nói cách khác: một metric hoàn toàn mù, chấm bừa, vẫn đạt "tương quan 0.77" trên 6 câu trong khoảng 1 lần mỗi 20 lần thử. Kỹ thuật bootstrap ở Module 03 áp thẳng được cho hệ số tương quan — bốc lại chính các cặp (câu, điểm) rồi tính lại $\rho$ mỗi vòng — và Module 04 gọi tên chính xác con số 5.1% này: nó là p-value của một permutation test.
  • Tương quan mức câu và mức hệ thống là hai bài toán khác nhau. Mức câu: mỗi điểm là một câu. Mức hệ thống: mỗi điểm là một model, và bạn thường chỉ có 8 model, tức $\binom{8}{2} = 28$ cặp. Một metric có $\tau$ mức câu tầm thường vẫn có thể xếp hạng hệ thống rất tốt, vì trung bình trên hàng nghìn câu đã dập bớt nhiễu.
  • Hạng bằng nhau (ties) làm $\tau$ sai. Công thức $\tau_a$ ở trên giả định không có ties. Khi người chấm dùng thang Likert 1–5, ties đầy rẫy. Lúc đó phải dùng $\tau_b = \dfrac{C-D}{\sqrt{(n_0-t_x)(n_0-t_y)}}$ với $n_0 = \binom{n}{2}$ và $t_x, t_y$ là số cặp bằng nhau ở mỗi bên. Trên một ví dụ 5 phần tử với $C=9$, $D=0$, $t_x=1$, $n_0=10$: $\tau_a = 0.900$ nhưng $\tau_b = 0.949$. Chênh 0.049 chỉ vì một cặp bằng nhau.

5. Khi metric học được gãy

Bây giờ tới chỗ khó chịu. Hãy quay lại chỗ mù thứ tư của mục 1 và tính ra con số.

Chạy tay một lần — lỗi chí mạng rẻ hơn lỗi vô hại

Một câu 12 token, bản dịch đúng gần như tuyệt đối, mọi cosine bằng 1.00 → $P = 1.000$. Xét hai kịch bản hỏng:

  • Sai một con số. Dịch ba nghìn lạng thành hai nghìn lạng. Đúng một token đổi, và cosine giữa hai vector số đếm trong cùng ngữ cảnh rất cao — giả sử 0.75. Khi đó $P = (11 \cdot 1.00 + 0.75)/12 = 11.75/12 = 0.9792$. Mất 0.0208.
  • Ba từ đồng nghĩa hoàn toàn vô hại. Đổi hắn → gã, nói → bảo, rất → vô cùng. Ba token, cosine 0.88 mỗi cái. $P = (9 \cdot 1.00 + 3 \cdot 0.88)/12 = 11.64/12 = 0.9700$. Mất 0.0300.

Bản dịch bịa sai số tiền — lỗi mà một biên tập viên sẽ trả lại ngay — bị phạt 0.0208. Bản dịch chỉ thay ba từ đồng nghĩa, không ai phàn nàn, bị phạt 0.0300, tức nặng gấp 1.44 lần. Metric đã đảo ngược thứ tự ưu tiên của bạn.

Đây không phải lỗi cài đặt mà là hệ quả trực tiếp của thiết kế: BERTScore trung bình hoá trên token, nên mọi lỗi đều bị pha loãng theo $1/n$, và encoder được huấn luyện để đặt các con số gần nhau trong không gian vector. COMET đỡ hơn vì nó học từ MQM (thang MQM có severity, nên critical error bị phạt nặng trong nhãn), nhưng chỉ đỡ hơn theo mức mà dữ liệu huấn luyện của nó đã dạy.

Danh sách các chỗ metric học được thường gãy, xếp theo mức nguy hiểm với dự án web novel zh→vi:

  • Tên riêng và danh xưng. Diệp Phàm vs Diệp Phong — hai vector rất gần, người đọc thì mất mạch truyện ngay. Phải bắt bằng luật riêng, không giao cho metric.
  • Con số, đơn vị, ngày tháng. Như vừa tính: chi phí gần bằng 0.
  • Câu quá ngắn. Với câu 3 token, một token hỏng chiếm $1/3$ trọng số; phương sai của điểm câu tăng vọt. Đối thoại web novel đầy câu 2–4 âm tiết.
  • Ngoài miền huấn luyện. COMET được train chủ yếu trên tin tức và văn bản chung. Tiên hiệp có hệ thuật ngữ riêng (cảnh giới tu luyện, pháp bảo, xưng hô sư huynh/đạo hữu) mà model chấm chưa từng thấy trong dữ liệu DA.
  • Ngôn ngữ ít tài nguyên. Tiếng Việt có mặt trong các encoder đa ngữ nhưng với tỉ trọng nhỏ hơn hẳn tiếng Anh hay tiếng Đức. Chất lượng biểu diễn kém hơn kéo theo metric kém hơn — và bạn không nhìn thấy điều đó từ bên trong.
  • Không truy ngược được. BLEU sai thì bạn xem được n-gram nào trượt. COMET cho 0.42 thì bạn không biết vì sao. Debug bằng một số vô hướng là debug mù.

Bẫy nguy hiểm nhất: tối ưu thẳng vào metric học được

Nếu bạn dùng COMET làm reward để chọn bản dịch (reranking, MBR decoding, hay reward trong DPO), bạn đang tối ưu vào chính chỗ mù của nó. Model sẽ học cách viết những câu trông giống câu điểm cao — trôi chảy, đúng độ dài, đủ từ phổ biến — kể cả khi sai nghĩa. Đây đúng là luật Goodhart ở Module 00, chỉ khác là lần này metric có 500 triệu tham số nên bạn khó phát hiện hơn nhiều. Quy tắc an toàn: metric dùng để tối ưu và metric dùng để nghiệm thu phải khác nhau.

6. Chọn metric nào cho zh→vi

Bảng dưới đây tổng hợp bốn họ metric theo năm tiêu chí quyết định. Cột chi phí là con số minh hoạ, tính từ giả định thông lượng cho một bộ test 2000 câu: BLEU 2000 câu/s và chrF 1200 câu/s trên CPU, BERTScore 300 câu/s và COMET 40 câu/s trên một GPU, COMET 2.5 câu/s nếu chỉ có CPU. Máy của bạn sẽ khác, nhưng bậc độ lớn thì đúng.

MetricCần reference?Cần GPU?Bắt đồng nghĩa?Giải thích được?Chi phí / 2000 câu
BLEUCó, ít nhất 1KhôngKhông — chỉ khớp chuỗiCao — chỉ ra được n-gram nào trượt~1 s (CPU)
chrFCó, ít nhất 1KhôngMột phần — bắt được biến thể hình thái, không bắt được từ khác gốcCao — truy được n-gram ký tự~1.7 s (CPU)
BERTScoreCó, ít nhất 1Nên cóCóTrung bình — xem được cặp token nào ghép với nhau và cosine bao nhiêu~7 s (GPU)
COMET (có ref)Có, ít nhất 1CóCóThấp — một số vô hướng, không truy ngược~50 s (GPU)
~13 phút (CPU)
COMET-QEKhôngCóCó, nhưng dễ khen bản trôi chảy sai nghĩaThấp~50 s (GPU)

Đọc bảng theo cột chi phí: COMET trên GPU đắt hơn BLEU 50 lần, còn COMET trên CPU đắt hơn 800 lần. Con số 800 mới là con số quyết định kiến trúc: nó là ranh giới giữa "chạy được mỗi commit" và "chạy được mỗi tuần".

Từ đó ra một cách bố trí ba tầng, mỗi tầng một mục đích:

  1. Vòng lặp nhanh (mỗi commit, mỗi eval trong lúc train). chrF làm metric chủ — rẻ, không cần GPU, hợp tiếng Việt hơn BLEU vì bắt được thanh điệu và từ ghép. Thêm BLEU chỉ để so với số cũ trong sổ.
  2. Nghiệm thu một checkpoint ứng viên (vài lần mỗi tuần). COMET có reference trên bộ test cố định, cộng chrF. 50 giây GPU là chấp nhận được. Nếu hai metric mâu thuẫn nhau, đó là tín hiệu bắt buộc phải mở mắt người ra xem.
  3. Giám sát production (dữ liệu không có bản người dịch). COMET-QE trên một mẫu ngẫu nhiên các chương đã dịch, cộng một bộ luật cứng bắt tên riêng và con số. Metric ở tầng này không dùng để chấm điểm, nó dùng để xếp hàng đợi cho biên tập viên.

Và một quy tắc chung, đúng cho cả ba tầng: báo cáo ít nhất hai metric thuộc hai họ khác nhau — một metric bề mặt và một metric học được. Khi cả hai cùng lên, bạn có bằng chứng. Khi chúng lệch nhau, bạn có một câu hỏi hay, và câu hỏi hay đáng giá hơn một con số đẹp.

Nó ứng dụng vào đâu trong đời thật

  • Đọc lại kết quả distillation. Nếu student giữ được chrF nhưng tụt COMET, khả năng cao nó đã học được cách viết trôi chảy mà mất một phần nội dung — đúng dấu hiệu của cặp $(P, R)$ lệch nhau ở mục 2: precision giữ nguyên, recall rơi. Hãy tính riêng BERTScore precision và recall thay vì nhìn F1, con số sẽ chỉ thẳng vào chỗ mất.
  • Kiểm định trước khi tin. Trước khi để COMET quyết định checkpoint nào lên production, hãy tự chấm tay 60–100 câu tiên hiệp, tính $\tau$ giữa điểm bạn và điểm COMET. Nếu accuracy trên cặp không vượt được 75%, COMET đang không hiểu miền của bạn và bạn cần quay lại chrF cộng mắt người.
  • Đừng để metric học được vào vòng huấn luyện. Bạn đã trả giá một lần với DPO v1 khi tín hiệu ưu tiên quá yếu. Reward bằng COMET thì ngược lại: tín hiệu rất mạnh và rất dễ bị lách. Giữ COMET ở vai trọng tài, đừng đưa nó làm huấn luyện viên.
  • Bộ luật cứng vẫn là thứ rẻ nhất. Một regex bắt sai số đếm và một danh sách tên nhân vật cố định chạy trong mili-giây, và bắt đúng loại lỗi mà cả bốn metric trong bảng đều mù. Chi phí gần bằng 0, giá trị cao nhất trong toàn hệ.

Tự kiểm tra

1 · Một bản dịch có BERTScore $P = 0.91$ nhưng $R = 0.68$. Chuyện gì đang xảy ra, và bạn kiểm tra bằng cách nào?

Precision cao nghĩa là mọi thứ bản dịch viết ra đều có chỗ dựa bên tham chiếu — nó không bịa. Recall thấp nghĩa là nhiều token tham chiếu không tìm được ai bên bản dịch — nó bỏ sót. Nói cách khác: bản dịch quá ngắn hoặc đã lược mất một mệnh đề. $F_1 = 2 \cdot 0.91 \cdot 0.68/(0.91+0.68) = 1.2376/1.59 = 0.778$, và con số 0.778 này giấu hoàn toàn chuyện vừa nói. Cách kiểm: in ra cực đại của từng cột (chiều recall) và tìm cột nào thấp bất thường — như cột "lạnh" đạt 0.20 ở ví dụ mục 2. Đó chính là chữ bị đánh rơi. Với dịch web novel, thủ phạm thường gặp là model bỏ nguyên một vế câu ghép dài.

2 · Metric A có $\rho = 0.86$, $\tau = 0.68$. Metric B có $\rho = 0.84$, $\tau = 0.71$. Bạn cần chọn giữa hai checkpoint. Chọn metric nào?

Metric B. Việc bạn cần metric làm là một phép so cặp: đưa hai bản dịch, hỏi cái nào hơn. Đại lượng đo đúng việc đó là $\tau$, vì nó dịch thẳng ra accuracy trên cặp: A cho $(0.68+1)/2 = 0.840$, B cho $(0.71+1)/2 = 0.855$. Trên 1000 cặp, B xếp đúng nhiều hơn A khoảng 15 cặp. Chuyện A có $\rho$ nhỉnh hơn 0.02 nghĩa là A bám toàn cục bảng xếp hạng tốt hơn một chút, nhưng bạn không cần bảng xếp hạng toàn cục, bạn cần một phép so hai chiều. Hai cảnh báo kèm theo. Thứ nhất, chênh lệch 0.03 trên $\tau$ là rất nhỏ; trước khi kết luận, hãy hỏi hai con số đó đo trên bao nhiêu câu — Module 03 sẽ cho bạn công cụ. Thứ hai, chọn xong metric B thì bạn vẫn chưa chọn được checkpoint: so hai checkpoint là so hai trung bình trên cùng bộ test, phải chạy kiểm định ghép cặp ở Module 04.

3 · Vẫn sáu câu ở mục 4, nhưng lần này giả sử metric xếp hạng trùng khít với người, chỉ trừ đúng một chỗ: nó đổi chỗ s2 và s3 (hạng 3 và 2). $\rho$ và $\tau$ bằng bao nhiêu?

Xuất phát điểm là hai bảng trùng khít ($\rho = 1$), rồi chỉ một cặp hạng liền kề bị hoán đổi, cụm s4–s5–s6 vẫn khớp hoàn hảo. Hai câu bị đổi cho $\delta = +1$ và $\delta = -1$, nên $\sum \delta_i^2 = 2$. Vậy $\rho = 1 - 6\cdot 2/210 = 1 - 12/210 = \mathbf{0.9429}$. Với Kendall: một lần hoán đổi hai phần tử liền kề tạo ra đúng 1 cặp nghịch, nên $D = 1$, $C = 14$, và $\tau = (14-1)/15 = 13/15 = \mathbf{0.8667}$. Accuracy trên cặp $= (0.8667+1)/2 = 0.933$, tức 14/15. Bài học: một lỗi xếp hạng ở giữa bảng làm $\rho$ tụt rất ít (từ 1.0 xuống 0.943), trong khi ba lỗi tập trung ở đuôi bảng như ví dụ gốc kéo $\rho$ xuống tận 0.771 — $\rho$ phạt nặng những chỗ lệch xa nhau về hạng, vì nó bình phương $\delta$.

4 · Vì sao không nên dùng COMET-QE để nghiệm thu bản distil của model dịch tiên hiệp?

Ba lý do chồng lên nhau. ① Không có reference nghĩa là không có gì neo điểm vào nội dung đúng: một câu trôi chảy, đúng ngữ pháp, đúng độ dài nhưng bịa nội dung vẫn có thể ăn điểm cao — mà đó chính là kiểu lỗi student hay mắc sau distillation. ② Miền: COMET-QE học từ dữ liệu DA chủ yếu là tin tức; hệ thuật ngữ tu luyện nằm ngoài miền đó, nên điểm của nó có độ tin cậy chưa được kiểm. ③ Lỗi con số và tên riêng bị mọi metric học được coi nhẹ. Với BERTScore thì tính được rành mạch: 0.0208 cho một con số sai, so với 0.0300 cho ba từ đồng nghĩa vô hại (mục 5). Với COMET-QE thì mức phạt nằm trong trọng số đã học, phụ thuộc dữ liệu MQM nó từng thấy — mà tiên hiệp không có trong đó, nên bạn không có cơ sở nào để tin nó bắt được. Dùng đúng chỗ của QE là xếp hàng đợi cho biên tập trên dữ liệu production, nơi bạn không có lựa chọn nào khác; còn nghiệm thu thì phải có reference và phải có người.

Phần II — Bất định & kiểm định · Module 03

03 Một con số không có sai số chỉ là tin đồn

Cần biết trước: Module 01 (BLEU, chrF) Dựng nên: khoảng tin cậy cho mọi con số bạn báo cáo Dẫn tới: Module 04 — so hai hệ thống

Mục tiêu

  • Kể tên ba nguồn phương sai đứng sau mọi điểm eval, và ước lượng phần đóng góp của từng nguồn bằng số.
  • Tự tay chia bộ test thành các khối con để nhìn thấy điểm nhảy — trước khi tin bất kỳ công thức nào.
  • Viết được thuật toán bootstrap trong 6 dòng và giải thích từng dòng làm gì.
  • Đọc một khoảng tin cậy (confidence interval) đúng nghĩa tần suất, và bác bỏ được cách hiểu sai phổ biến nhất về nó.
  • Chọn số vòng $B$ có căn cứ, và báo cáo 54.3 ± 1.1 thay vì 54.2935.

1. Con số bạn báo cáo là một biến ngẫu nhiên

Bạn chạy eval, ra chrF = 54.29. Chạy lại lần nữa với đúng model đó, đúng bộ test đó, ra đúng 54.29. Cảm giác chắc chắn đó là ảo giác: bạn vừa lặp lại một phép đo, không phải lặp lại thí nghiệm.

Có ít nhất ba nguồn phương sai (variance) làm con số ấy lung lay, và chỉ nguồn thứ nhất là thứ hầu hết mọi người từng nghĩ tới:

  1. Chọn mẫu test. 500 câu của bạn là một mẫu bốc ra từ biển web novel. Bốc 500 câu khác, điểm khác.
  2. Seed khởi tạo và thứ tự data. Cùng công thức, đổi seed, model ra khác. Đây là phương sai của quy trình huấn luyện, không phải của bộ test.
  3. Ngẫu nhiên khi giải mã. Sampling, nucleus, thậm chí thứ tự batch khi inference và sai số dấu phẩy động của kernel int8 cũng đủ làm vài câu đổi.

Ba nguồn cộng theo phương sai chứ không theo độ lệch chuẩn. Với bộ test 500 câu mà ta sẽ dùng suốt module này, phần do chọn mẫu tính được là $0.564$ điểm chrF. Giả sử thêm phần seed là $0.30$ và phần giải mã là $0.15$ — hai con số này là giả định để minh hoạ, không phải đo được — thì:

$$\sigma_{\text{tot}} = \sqrt{0.564^2 + 0.30^2 + 0.15^2} = 0.656$$

Tức là chỉ tính nguồn (1) bạn báo ±1.10; tính đủ ba nguồn thì phải báo ±1.29. Bỏ qua seed và decode làm khoảng của bạn hẹp đi 14.1% — nó khiến bạn tự tin hơn thực tế. Đóng góp phương sai: chọn mẫu 73.8%, seed 20.9%, decode 5.2%.

Bẫy thường gặp

  • Cố định seed rồi tuyên bố hết ngẫu nhiên. Cố định seed chỉ làm phép đo lặp lại được, nó không xoá phương sai — nó giấu phương sai đi. Bạn vẫn đang báo cáo một mẫu rút từ phân phối các kết quả có thể có.
  • Cả module này chỉ xử lý nguồn (1), vì nó đo được từ chính dữ liệu bạn đang có. Nguồn (2) muốn đo phải train lại nhiều seed — đắt, và thuộc phần ngân sách eval ở Module 10.

2. Bạn đang đo mẫu, không đo tổng thể

Cần tách bạch hai thứ hay bị gọi chung một tên:

  • $\theta$ — tham số tổng thể (population parameter): điểm chrF trung bình mà model của bạn sẽ đạt trên toàn bộ văn bản web novel zh→vi trên đời. Bạn không bao giờ biết nó.
  • $\hat{\theta}$ — ước lượng (estimate): điểm đo trên 500 câu bạn có. Bạn biết nó chính xác tới từng chữ số, và nó gần như chắc chắn khác $\theta$.

Để nhìn thấy khoảng cách đó, cả module này dùng một tổng thể giả lập nhưng đã biết trước: điểm chrF từng câu tuân theo phân phối Beta$(7.9,\,6.7)$ nhân 100. Ta biết chắc $\theta = 54.11$ và $\sigma = 12.62$. Bốc ra 500 câu bằng np.round(100*default_rng(109).beta(7.9, 6.7, 500), 2) được bộ test dùng suốt bài — bạn tái tạo lại y hệt được:

$$\bar{x} = \hat{\theta} = \frac{1}{n}\sum_{i=1}^{n} x_i = 54.29, \qquad s = 12.60, \qquad n = 500$$

Ở đây $s$ là độ lệch chuẩn mẫu (sample standard deviation) của 500 điểm câu — căn bậc hai của $\frac{1}{n-1}\sum (x_i - \bar{x})^2$. Nó đo độ tản mát của từng câu, chưa nói gì về độ tản mát của điểm corpus. Lệch $0.18$ điểm so với sự thật, và bạn sẽ không bao giờ biết độ lệch đó nếu đây là dữ liệu thật. Điểm câu trải từ $17.75$ tới $85.91$ — độ tản mát ở cấp câu khổng lồ so với chênh lệch giữa hai bản model mà bạn thường tranh cãi.

3. Chạy tay: chia 500 câu thành 5 khối 100 câu

Trước khi động tới công thức nào, làm việc này bằng tay. Cắt 500 câu theo thứ tự thành 5 khối 100 câu, tính điểm trung bình mỗi khối. Không lấy mẫu lại, không thống kê — chỉ chia và cộng.

Khối (100 câu)chrF trung bìnhLệch so với 54.29$s$ trong khối
Câu 1–10056.08+1.7912.02
Câu 101–20052.75−1.5412.29
Câu 201–30054.53+0.2411.50
Câu 301–40055.02+0.7313.60
Câu 401–50053.09−1.2013.42
Cả 500 câu54.29—12.60

Đọc bảng này chậm lại một chút. Cùng một model, cùng một domain, không đổi một dòng code nào: điểm nhảy từ 52.75 tới 56.08 — biên độ 3.33 điểm chrF. Nếu khối 2 là bộ test của đối thủ và khối 1 là bộ test của bạn, bạn "thắng" 3.33 điểm mà không làm gì cả.

So sánh với thứ người ta hay khoe: "bản mới hơn 0.4 điểm". Chênh lệch 0.4 chỉ bằng 1/8 biên độ dao động thuần tuý do đổi 100 câu test ($3.33 / 0.4 = 8.3$). Đó là toàn bộ luận đề của quyển sách này, gói trong một bảng.

Trung bình 5 điểm khối đúng bằng 54.29 (hiển nhiên, vì các khối bằng nhau). Giờ đọc cột thứ hai theo chiều dọc: năm con số ấy có độ lệch chuẩn $1.378$, trong khi độ tản mát của từng câu là $s = 12.60$ — nhỏ hơn $12.60/1.378 = 9.14$ lần. Mỗi khối gồm 100 câu, và $\sqrt{100} = 10$. Quy luật $1/\sqrt{n}$ không phải thứ bạn phải tin vì có ai đó bảo thế: nó vừa rơi thẳng ra khỏi bảng bạn tự cộng. Trung bình của $n$ câu tản mát ít hơn một câu đơn lẻ chừng $\sqrt{n}$ lần, và đại lượng $s/\sqrt{n}$ ấy có tên riêng — sai số chuẩn (standard error) của ước lượng.

Khớp tới đâu thì phải nói thẳng: công thức cho $s/\sqrt{100} = 1.260$, số đo được là $1.378$, tỉ số $1.378/1.260 = 1.09$ — lệch 9%. Với vỏn vẹn 5 quan sát thì 9% là mức chênh hoàn toàn bình thường, nhưng nó cũng nói luôn rằng con số $1.378$ quá nhiễu để đem ra làm sai số. Năm điểm khối đó là một mẫu bé xíu rút từ phân phối mẫu (sampling distribution) của $\hat{\theta}$: phân phối của điểm corpus khi bạn lặp đi lặp lại việc bốc một bộ test mới cùng cỡ rồi chấm lại. Đó đúng là thứ bạn cần biết, và cũng đúng là chỗ bí — dựng nó cho tử tế cần hàng nghìn bộ test, mà bạn chỉ có một.

4. Bootstrap: bốc lại có hoàn lại từ chính mẫu của bạn

Mục này chế ra đúng thứ vừa thiếu: một bản xấp xỉ của phân phối mẫu, dựng từ vỏn vẹn một bộ test. Ý tưởng của bootstrap (bootstrap resampling) gọn tới mức thấy vô lý: nếu 500 câu của bạn là ảnh đại diện tốt của tổng thể, thì bốc lại từ chính 500 câu đó gần giống bốc một bộ test mới từ tổng thể. Bốc $n$ lần có hoàn lại (with replacement) để mỗi mẫu bootstrap vẫn có đúng $n$ câu, nhưng câu này lặp hai lần, câu kia vắng mặt.

Con số làm việc đó cụ thể: xác suất một câu không được bốc lần nào là $(1 - 1/500)^{500} = 0.3675$. Nên mỗi mẫu bootstrap chỉ chứa khoảng 316 câu khác nhau trong 500 — đúng 63.2%, phần còn lại là bản sao. Đo thật trên 5 vòng đầu: 311, 316, 322, 313, 309 câu khác nhau. Riêng vòng 1 có 189 câu vắng mặt và một câu bị bốc trúng 5 lần.

Chạy tay một lần

Tám câu, điểm chrF: 41 · 12 · 55 · 28 · 63 · 7 · 34 · 20. Tổng $= 260$, nên $\hat{\theta} = 260/8 = 32.5$.

Bốc lại 8 chỉ số có hoàn lại, ba lần. Bạn làm được bằng xúc xắc 8 mặt:

  • Vòng 1 — chỉ số 3 7 1 3 8 5 2 6 → giá trị 55 34 41 55 20 63 12 7. Tổng $287$, trung bình 35.875. (Câu 3 vào hai lần; câu 4 biến mất.)
  • Vòng 2 — chỉ số 6 2 4 4 1 7 8 2 → 7 12 28 28 41 34 20 12. Tổng $182$, trung bình 22.75.
  • Vòng 3 — chỉ số 5 5 3 1 2 8 4 7 → 63 63 55 41 12 20 28 34. Tổng $316$, trung bình 39.5.

Ba con số 35.875 · 22.75 · 39.5 dao động trong biên độ 16.75 điểm quanh 32.5. Sự tản mát đó chính là ước lượng độ bất định — bootstrap không thêm thông tin nào, nó chỉ bắt dữ liệu bạn có tự khai ra nó mong manh cỡ nào. Chạy đủ 20000 vòng trên 8 số này cho độ lệch chuẩn $6.55$ và khoảng 95% là $[19.88,\ 45.63]$; công thức cổ điển $s/\sqrt{8}$ cho $7.02$ — cùng cỡ, và với $n=8$ thì cả hai đều chỉ nên coi là cảnh báo "mẫu quá nhỏ".

Viết lại thành thuật toán cho bộ 500 câu — sáu dòng, chạy được nguyên văn:

bootstrap percentile cho điểm trung bình câu · numpy
import numpy as np
rng = np.random.default_rng(0)                        # cố định seed để lặp lại được
x = np.loadtxt("diem_cau.txt")                        # 500 điểm chrF, mỗi câu một dòng
idx = rng.integers(0, len(x), size=(1000, len(x)))    # B=1000 vòng, mỗi vòng bốc lại n chỉ số
means = x[idx].mean(axis=1)                           # 1000 điểm corpus giả lập
print(round(x.mean(), 2), np.round(np.percentile(means, [2.5, 97.5]), 2))
# 54.29 [53.21 55.35]

Dòng 4 là toàn bộ linh hồn: ma trận chỉ số $1000 \times 500$, mỗi hàng là một bộ test giả lập. Dòng 5 biến nó thành 1000 điểm corpus. Dòng 6 cắt hai đầu.

① Mẫu bạn thật sự có ② Bốc lại có hoàn lại ③ Phân phối bootstrap của điểm corpus n = 500 câu test chrF của 11 câu đầu θ̂ = 54.29 mẫu #1 → 52.65 mẫu #2 → 53.99 mẫu #3 → 53.60 … tới mẫu #1000 mỗi mẫu = 500 lần bốc có hoàn lại ≈ 316 câu khác nhau, còn lại là bản sao 53545556 θ̂ = 54.29 2.5% → 53.21 97.5% → 55.35 25 vòng 25 vòng cột cao nhất: 142 trong 1000 vòng
Một mẫu gốc sinh ra 1000 bộ test giả lập; 1000 điểm corpus của chúng dựng thành phân phối bootstrap; cắt ở phân vị 2.5% và 97.5% ra khoảng tin cậy. Đúng 25 vòng nằm dưới vạch trái và 25 vòng nằm trên vạch phải. Độ lệch chuẩn của 1000 điểm ấy là $0.561$, gần trùng với $s/\sqrt{n} = 0.564$.

Kéo thử ở phòng thí nghiệm dưới đây trước khi đọc tiếp — bấm từng vòng một để thấy histogram mọc dần.

Phòng thí nghiệm 03

Máy bootstrap

Bấm "1 vòng" vài lần cho quen tay, rồi mới xả 1000 vòng
Mẫu gốc — điểm chrF của từng câu trong bộ test
Phân phối của trung bình bootstrap, dựng dần theo từng vòng
500
Đổi n là đổi mẫu gốc, nên mọi vòng đã chạy bị xoá sạch. Mẫu nhỏ nằm gọn trong mẫu lớn: 500 câu đầu của n = 2000 đúng là mẫu n = 500, nên so hai cỡ n là so công bằng.
Trung bình mẫu—
Biên dưới 2.5%—
Biên trên 97.5%—
Nửa rộng CI—
Số vòng B0
Mẫu gốc

—

Điều cần thấy: ① Để nguyên n = 500 rồi bấm 1000 vòng: trung bình mẫu là 55.12, khoảng tin cậy 95% ra [54.06, 56.14], tức 55.1 ± 1.0. Viết "chrF 55.1249" trong báo cáo là khoe ba chữ số bạn không hề có. ② Kéo n lên 2000 (gấp 4) rồi lại 1000 vòng: nửa rộng tụt từ ±1.04 xuống ±0.57 — hẹp đi 1.83 lần, gần đúng quy luật $1/\sqrt{n}$ chứ không chẵn 2 lần, vì mẫu 2000 câu vớ thêm mấy câu tệ nên $s$ nhích từ 12.28 lên 12.49. Muốn sai số nhỏ một nửa: trả giá gấp bốn số câu. ③ Đặt lại rồi bấm đúng 1 vòng: 172 trong 500 câu (34.4%) không được bốc lần nào, sát với lý thuyết $(1-1/n)^n = 36.8\% \approx 1/e$; đổi lại có câu bị bốc tới 4 lần. Trung bình mẫu con đó là 54.26, lệch −0.87 điểm so với 55.12. Toàn bộ bootstrap chỉ là chuyện bỏ ra và nhân đôi đó, lặp lại thật nhiều lần. ④ Khung trên không giống hình chuông ở bất cứ chỗ nào: một khối lớn quanh 58–60 điểm, một đuôi lê thê kéo xuống tận 13.8, và 25 câu dưới 30 điểm nằm tách hẳn ra thành cụm riêng — trung vị 57.60 cao hơn trung bình 55.12. Vậy mà khung dưới lại gần như hình chuông, trùng khít đường vàng đứt nét $\mathcal{N}(\bar{x},\, s/\sqrt{n})$ — bootstrap cho ±1.04, công thức cho ±1.08. Bootstrap tới được con số đó mà không giả định gì về hình dạng khung trên. ⑤ Đặt lại, bấm 100 vòng rồi bấm tiếp 1000 vòng (B = 1100): hai biên chỉ dịch 0.05 và 0.10 điểm, trên một khoảng rộng 2.09. Tăng $B$ chỉ mài mịn hai biên; nó không bao giờ làm khoảng ngắn lại.

Bẫy thường gặp

  • corpus-BLEU không phải trung bình của sentence-BLEU (cơ chế cộng dồn đã dựng ở Module 01, mục 5). Dòng x[idx].mean(axis=1) chỉ hợp lệ khi điểm corpus đúng là trung bình điểm câu (chrF macro-average, COMET, accuracy). Với corpus-BLEU hay chrF micro-average, bạn phải bốc lại các đếm n-gram của từng câu rồi cộng dồn và tính lại công thức corpus. Bốc lại điểm câu rồi lấy trung bình là sai — nó tính khoảng tin cậy cho một đại lượng khác.
  • Bootstrap không sửa được mẫu lệch. Nếu 500 câu của bạn toàn thể loại tu tiên, bootstrap sẽ cho một khoảng rất chặt quanh một câu trả lời sai cho câu hỏi "model dịch web novel tốt cỡ nào". Nó đo độ ổn định của phép đo, không đo tính đại diện của mẫu. Thiết kế bộ test là chuyện của Module 08.
  • Câu trong bộ test không độc lập nếu bạn cắt 500 câu từ 20 chương liên tiếp. Khi đó phải bốc lại theo chương chứ không theo câu, nếu không khoảng sẽ hẹp giả tạo.

5. Khoảng tin cậy percentile 95% — và cách đọc nó cho đúng

Có 1000 điểm bootstrap $\hat{\theta}^{*(1)},\dots,\hat{\theta}^{*(B)}$. Sắp xếp tăng dần, cắt bỏ 2.5% mỗi đầu:

$$\mathrm{CI}_{95\%} = \left[\, q_{2.5}(\hat{\theta}^{*}),\ \; q_{97.5}(\hat{\theta}^{*}) \,\right] = [53.21,\ 55.35]$$

Nói bằng lời

Xếp 1000 con số theo hàng từ nhỏ tới lớn. Bỏ 25 con nhỏ nhất, bỏ 25 con lớn nhất. Hai con còn lại ở hai đầu chính là hai biên. Hết. Không có phân phối chuẩn, không có bảng tra $t$, không giả định gì về hình dáng dữ liệu.

Bề rộng tính trên số thô: $55.353 - 53.205 = 2.148$, tức $\approx 2.15$; nửa bề rộng $1.07$. Cách cổ điển $\hat{\theta} \pm 1.96\,s/\sqrt{n}$ cho $[53.19,\ 55.40]$ — gần như trùng khít, vì trung bình của 500 số thì định lý giới hạn trung tâm đã kịp có hiệu lực. Giá trị của bootstrap là nó vẫn chạy khi $\hat{\theta}$ không phải một trung bình: BLEU, TER, tỉ lệ câu đạt ngưỡng, phân vị 10% xấu nhất, và cả hệ số tương quan Spearman/Kendall với điểm người ở Module 02 — với $\rho$ và $\tau$ thì mỗi vòng bốc lại các cặp (điểm người, điểm metric) rồi tính lại hệ số, tức chỉ đổi đúng dòng 5 của đoạn code trên.

Giờ tới phần mà gần như ai cũng đọc sai. Câu SAI là:

"Có 95% xác suất giá trị thật $\theta$ nằm trong đoạn [53.21, 55.35]."

Sai ở đâu? Trong khung tần suất, $\theta$ là một hằng số — ở đây ta còn biết nó bằng đúng $54.11$. Một hằng số hoặc nằm trong một đoạn cố định, hoặc không. Xác suất của mệnh đề đó là 0 hoặc 1, không bao giờ là 0.95. Đoạn $[53.21, 55.35]$ này chứa $54.11$: xác suất bằng 1.

Con số 95% thuộc về quy trình, không thuộc về đoạn. Nói cho đúng: nếu lặp lại toàn bộ thí nghiệm rất nhiều lần — mỗi lần bốc một bộ test 500 câu mới và tính lại khoảng theo đúng cách này — thì khoảng 95% số khoảng thu được sẽ chứa $\theta$. Đoạn cụ thể bạn đang cầm trong tay chỉ là một vé trong lô vé đó.

Đây là loại khẳng định kiểm chứng được, nên hãy kiểm. Mô phỏng 2000 lần thí nghiệm, mỗi lần bốc 500 câu mới từ tổng thể đã biết rồi chạy bootstrap $B = 1000$: 1901/2000 = 95.05% số khoảng chứa $\theta = 54.11$, bề rộng trung bình $2.20$. Đúng như hứa hẹn.

20 lần lặp lại thí nghiệm — mỗi vạch là một khoảng 95% từ một bộ test 500 câu mới θ = 54.11 (giá trị thật) lần 1 → 20 trượt 5253545556
Chấm giữa là $\hat{\theta}$ của lần đó. Vạch đỏ ở lần thứ 18 nằm trọn bên trái đường thật — bộ test đó xui, và không có cách nào biết được từ bên trong lần chạy ấy. Đó là ý nghĩa thật của "95%": một trên hai mươi vé là vé trượt.

6. Bao nhiêu vòng $B$ là đủ

Có hai nguồn sai số chồng lên nhau, và người mới hay lẫn:

  • Sai số thống kê — do bạn chỉ có 500 câu. Giảm nó phải tăng $n$. Đây là nửa bề rộng $1.07$.
  • Sai số Monte Carlo — do bạn chỉ chạy $B$ vòng bốc lại thay vì vô hạn. Giảm nó chỉ tốn CPU.

Sai số Monte Carlo của một biên là sai số của một phân vị mẫu, xấp xỉ bằng

$$\mathrm{SE}_{\mathrm{MC}}\!\left(q_p\right) \approx \frac{\sqrt{p(1-p)/B}}{f(q_p)}$$

Nói bằng lời

Tử số là độ nhiễu của việc đếm xem bao nhiêu vòng rơi xuống dưới vạch — đúng sai số của một tỉ lệ $p$ đo trên $B$ lần thử. Mẫu số $f$ là mật độ của phân phối bootstrap ngay tại chỗ cắt: chỗ nào histogram dày thì vạch cắt đứng yên, chỗ nào thưa thì nó trượt xa.

Lấy số cụ thể ra dùng. Phân phối bootstrap ở đây gần chuẩn với độ lệch chuẩn $0.564$, nên mật độ tại vạch 2.5% là $\varphi(1.96)/0.564 = 0.0584/0.564 = 0.104$. Thay vào với $B = 1000$: $\sqrt{0.025 \times 0.975/1000}/0.104 = 0.00494/0.104 = 0.047$. Đó chính là cột "Công thức xấp xỉ" của bảng dưới — đổi $B$ rồi bấm lại, bạn ra được cả ba dòng.

Ta có thể vừa dùng công thức vừa đo thẳng: lặp lại toàn bộ bootstrap nhiều lần trên cùng 500 câu và xem hai biên nhảy bao nhiêu.

$B$SE Monte Carlo của biên dưới (đo)của biên trên (đo)Công thức xấp xỉBề rộng CI trung bìnhKết luận
1000.1340.1340.1502.11Nhiễu bằng 12.5% nửa bề rộng. Khoảng còn hẹp giả 4.2%.
1 0000.0470.0460.0472.20Đủ để báo cáo một chữ số thập phân.
10 0000.0150.0150.0152.21Biên ổn định tới hàng phần trăm điểm.

Cột "đo" lấy từ 4000 lần lặp cho $B = 100$ và $B = 1000$, và 800 lần cho $B = 10\,000$. Ba điều đáng nhớ:

  1. Quy luật $1/\sqrt{B}$. Tăng $B$ gấp 10 chỉ giảm nhiễu $\sqrt{10} \approx 3.16$ lần — đo được $0.047/0.015 = 3.13$, khớp. Riêng nhịp $100 \to 1000$ chỉ được $0.134/0.047 = 2.85$: ở $B = 100$ công thức tiệm cận đã hết chuẩn (nó dự đoán $0.150$, thực tế $0.134$), vì phân vị lúc đó chỉ tựa lên vài phần tử rời rạc.
  2. $B$ nhỏ làm khoảng hẹp giả. Bề rộng kỳ vọng ở $B=100$ là $2.113$, ở $B=10\,000$ là $2.205$ — thiếu 4.2%. Với $B = 100$, biên 2.5% là nội suy giữa phần tử nhỏ thứ 3 và thứ 4 ($0.025 \times 99 = 2.475$); toàn bộ biên dưới của bạn đang tựa lên đúng hai con số. Với $B = 1000$ thì là giữa phần tử 25 và 26 — vẫn ít, nhưng đuôi đã đủ dày.
  3. Chạy thật $B = 100$ với sáu seed khác nhau trên cùng 500 câu cho sáu khoảng: $[53.22, 55.30]$, $[53.26, 55.37]$, $[53.33, 55.46]$, $[53.28, 55.10]$, $[53.20, 55.57]$, $[53.12, 55.33]$. Biên trên nhảy từ $55.10$ tới $55.57$ — 0.47 điểm chỉ vì đổi seed của thư viện random.

Khuyến nghị vận hành: $B = 1000$ cho vòng lặp phát triển hằng ngày, $B = 10\,000$ cho con số đưa vào báo cáo hay bài viết. Bootstrap 10000 vòng trên 500 câu chỉ là 5 triệu phép cộng — dưới một giây. Không có lý do gì để tiết kiệm ở đây.

7. Báo cáo cho đúng: 54.3 ± 1.1 chứ không 54.2935

Quy tắc làm tròn rút gọn thành hai bước: (1) làm tròn nửa bề rộng khoảng về một hoặc hai chữ số có nghĩa; (2) làm tròn ước lượng về đúng hàng đó. Ở đây nửa bề rộng $1.0741 \to 1.1$, nên $54.2935 \to 54.3$.

Viết thế nàyVấn đềViết thế này thay vào
chrF = 54.2935Bốn chữ số thập phân trong khi sai số là ±1.1. Ba chữ số cuối là tiếng ồn được in ra bằng mực.chrF = 54.3 ± 1.1
54.3 ± 1.1 (không nói gì thêm)±1.1 là gì? Ở bộ dữ liệu này độ lệch chuẩn điểm câu là $12.60$, sai số chuẩn là $0.564$, nửa khoảng 95% là $1.10$ — ba con số cách nhau tới 22 lần.54.3, CI 95% [53.2, 55.4], bootstrap percentile, $B=10^4$, $n=500$
54.3 vs 53.9 → "bản A tốt hơn"Hai khoảng chồng lên nhau gần hết. Chênh 0.4 chỉ bằng 0.37 lần nửa bề rộng 1.07.So bằng phương pháp ghép cặp — Module 04. Đừng so bằng cách nhìn hai khoảng có chồng nhau không.
Báo điểm mà không báo $n$Người đọc không tính lại được gì. $n$ quyết định toàn bộ bề rộng.Luôn kèm $n$, seed, phiên bản bộ test, và signature của công cụ tính metric.

Một mẹo kiểm tra nhanh cho mọi bảng kết quả bạn sắp gửi đi: che cột điểm lại, hỏi "nếu tôi bốc bộ test khác thì con số này còn giữ được mấy chữ số?". Với $n = 500$ và $s \approx 12.6$, câu trả lời là ba chữ số — hàng chục, hàng đơn vị, và một chữ số thập phân đã là ranh giới. Muốn thêm một chữ số nữa thì phải tăng $n$ gấp 100 lần. Muốn chỉ giảm một nửa sai số thôi cũng cần $n = 2000$ (nửa bề rộng còn $0.55$).

Nó ứng dụng vào đâu trong đời thật

  • Bảng so checkpoint trong dự án zh→vi. Thay vì cột "chrF" một con số, thêm cột "CI 95%". Cột đó trả lời đúng một câu hỏi: con số này, đứng lẻ một cột, đáng tin tới mấy chữ số — với 500 câu là ±1.1, nên in 54.2935 ra bảng là tự lừa. Nó không trả lời câu "bản nào hơn bản nào": hai khoảng một mẫu chồng nhau gần hết vẫn có thể đi kèm một chênh lệch chắc nịch, và ngược lại. Câu đó là việc của Module 04.
  • Quyết định có nên train tiếp không. Nhìn nửa bề rộng $1.1$ rồi kết luận "mục tiêu +0.8 chrF quý này không kiểm chứng nổi" là sai — và đây là chỗ dễ vấp nhất của cả module. Nửa bề rộng $1.1$ là độ bất định của một con số đứng lẻ. Thứ quyết định bạn có thấy được $+0.8$ hay không là độ tản mát của hiệu từng câu giữa hai bản: giả sử nó là $\sigma_d = 4$ điểm, sai số của hiệu trên đúng 500 câu ấy chỉ còn $4/\sqrt{500} = 0.18$, và $+0.8$ cách số không tới $4.5$ lần sai số đó — thấy thừa sức. Vậy hãy đo $\sigma_d$ chứ đừng đo $s$ khi đặt mục tiêu; cách biến $\sigma_d$ thành cỡ mẫu nằm ở Module 05.
  • Báo cáo cho người ngoài nhóm. Với PM hoặc người đọc bản dịch, câu "54.3, sai số ±1.1 trên 500 câu" tạo được đúng mức tin cậy nên có. Câu "54.2935" tạo ra một niềm tin giả mà sau này chính bạn phải trả giá khi bản mới ra 53.8 và bị hỏi "sao lại tụt".
  • Bộ hồi quy chạy theo commit. Đừng lấy CI 95% của baseline làm ngưỡng cảnh báo. Khoảng đó rộng $2.15$ điểm và sinh ra từ nguồn phương sai (1) — chọn mẫu test — thứ hoàn toàn không tham gia khi bạn chạy lại đúng bộ test cũ; chuông ấy sẽ ngủ quên qua mọi hồi quy dưới một điểm chrF. Ngưỡng đúng đặt trên hiệu từng câu so với baseline (Module 04), hoặc trên phương sai seed/decode mà bạn tự đo bằng cách chạy lại vài seed — nguồn (2) và (3) ở mục 1.

Tự kiểm tra

1 · Bạn chạy bootstrap $B = 1000$ và được CI 95% là [53.21, 55.35]. Đồng nghiệp nói: "vậy 95% khả năng chrF thật của model nằm giữa 53.21 và 55.35". Bạn sửa lại thế nào?

Giá trị thật $\theta$ là hằng số, không phải biến ngẫu nhiên — nó nằm trong đoạn này hoặc không, xác suất là 1 hoặc 0. Trong ví dụ của module, ta biết $\theta = 54.11$, nằm trong đoạn, nên xác suất đúng bằng 1. Con số 95% mô tả quy trình: lặp lại thí nghiệm với bộ test 500 câu mới rất nhiều lần thì khoảng 95% số khoảng sinh ra sẽ chứa $\theta$. Kiểm bằng mô phỏng 2000 lần: 1901 khoảng chứa $\theta$, tức 95.05%.

2 · Bộ test 500 câu, $s = 12.60$, nửa bề rộng $1.10$. Bạn muốn đưa nó xuống $0.28$. Cần bao nhiêu câu, và vì sao cách "chạy bootstrap 100000 vòng thay vì 1000" không giúp gì?

Sai số thống kê tỉ lệ $1/\sqrt{n}$, nên giảm 4 lần cần $n$ gấp 16: 8000 câu, cho $1.96 \times 12.60/\sqrt{8000} = 0.276$. Tăng $B$ chỉ giảm sai số Monte Carlo — thành phần đã bé sẵn: ở $B=1000$ nó là 0.047, tức 4.4% của nửa bề rộng 1.07. Đẩy $B$ lên $10^5$ đưa nó về khoảng 0.005, nhưng bề rộng khoảng vẫn đứng nguyên ở 2.20, vì bề rộng do 500 câu quyết định chứ không do số vòng bốc lại. Bootstrap không tạo ra dữ liệu mới.

3 · Vì sao mỗi mẫu bootstrap chỉ chứa khoảng 63% số câu khác nhau, và điều đó có làm ước lượng bị lệch không?

Xác suất một câu cụ thể không trúng lần nào trong 500 lần bốc là $(1-1/500)^{500} = 0.3675$, tiến tới $e^{-1} = 0.3679$. Nên số câu khác nhau kỳ vọng là $500 \times 0.6325 = 316$; đo thật trên 5 vòng đầu được 311, 316, 322, 313, 309. Không gây lệch: những câu vắng mặt được bù bằng những câu xuất hiện hai, ba lần, và kỳ vọng của trung bình mẫu bootstrap vẫn đúng bằng trung bình mẫu gốc. Đo được: trung bình 1000 điểm bootstrap là 54.2877 so với 54.2935 — lệch $-0.006$, thuần nhiễu.

4 · Bạn dùng corpus-BLEU của sacreBLEU. Có được áp thẳng sáu dòng code ở mục 4 lên vector điểm BLEU từng câu không?

Không. corpus-BLEU cộng dồn các đếm n-gram khớp và tổng độ dài của cả corpus rồi mới lấy log và nhân brevity penalty, nên nó không phải trung bình cộng của sentence-BLEU (Module 01, mục 5). Cách đúng: với mỗi câu lưu lại bộ thống kê đủ (số n-gram khớp và tổng n-gram ở 4 mức, độ dài bản dịch, độ dài tham chiếu); mỗi vòng bootstrap bốc lại các câu, cộng dồn thống kê của chúng, rồi tính lại công thức corpus-BLEU. Kết quả vẫn là $B$ điểm corpus và vẫn cắt phân vị như cũ. Với chrF macro-average hay COMET thì điểm corpus đúng là trung bình câu, nên sáu dòng kia dùng thẳng được.

5 · Chia 500 câu thành 5 khối 100 câu cho 5 điểm: 56.08, 52.75, 54.53, 55.02, 53.09. Vì sao không dùng thẳng độ lệch chuẩn của 5 số này làm sai số của điểm 500 câu?

Hai lý do. Thứ nhất, độ lệch chuẩn $1.378$ đó là độ tản mát của điểm khối 100 câu, không phải của điểm 500 câu; muốn chuyển sang phải chia thêm $\sqrt{5}$, ra $0.616$ — cao hơn giá trị đúng $0.564$ chừng 9%, đúng bằng độ lệch đã gặp ở mục 3. Thứ hai và quan trọng hơn: 9% ấy không phải lỗi của công thức mà là nhiễu, vì cả ước lượng chỉ dựa trên 5 quan sát — trên cùng dữ liệu này, đổi cách cắt khối là con số đổi theo. Bootstrap giải đúng vấn đề đó bằng cách tạo ra 1000 quan sát thay vì 5, và giữ nguyên cỡ mẫu $n = 500$ ở mỗi lần lặp lại.

Phần II — Bất định & kiểm định · Module 04

04 So hai hệ thống cho đúng

Cần biết trước: Module 03 — phương sai, bootstrap, khoảng tin cậy Dựng nên: câu trả lời có căn cứ cho "bản mới có hơn không" Dẫn tới: Module 05 — cỡ mẫu và power

Mục tiêu

  • Tính được hiệu ghép cặp (paired difference) và giải thích bằng số vì sao nó yên tĩnh hơn điểm thô — trên dữ liệu ở dưới, độ lệch chuẩn tụt từ 12.7 xuống 2.9.
  • Chạy paired bootstrap bằng 19 dòng Python và đọc ra khoảng tin cậy của chênh lệch — trên dữ liệu ở dưới là [+0.10, +4.20].
  • Chạy trọn vẹn một permutation test bằng tay: 6 câu, $2^6 = 64$ hoán vị dấu, ra p-value chính xác dạng phân số.
  • Nói đúng p-value là gì và bác được bốn hiểu lầm phổ biến nhất về nó.
  • Tính được xác suất báo động giả khi so nhiều hệ cùng lúc (12 checkpoint → 45.96%) và hiệu chỉnh bằng Holm.

1. "Hơn 0.4 BLEU" là một câu không có nội dung

Bạn train xong bản mới. Bộ test 500 câu. Bản cũ 33.8 BLEU, bản mới 34.2 BLEU. Bạn viết vào changelog: "cải thiện 0.4 BLEU". Câu đó có nội dung không?

Module 03 đã cho bạn công cụ trả lời. Giả sử hiệu điểm theo từng câu có độ lệch chuẩn $\sigma_d = 8$ điểm (con số này bạn phải tự đo trên bộ test của mình, đừng chép của ai). Sai số chuẩn của trung bình hiệu là:

$$\mathrm{SE}(\bar{d}) = \frac{\sigma_d}{\sqrt{n}} = \frac{8}{\sqrt{500}} = 0.358$$

Chênh lệch quan sát 0.4 chia cho 0.358 được 1.118 — chưa tới hai lần sai số. Khoảng tin cậy 95% của chênh lệch là $0.4 \pm 1.96 \times 0.358$, tức [−0.30, 1.10]. Khoảng này chứa số 0. Nói cách khác: dữ liệu của bạn hoàn toàn tương thích với giả thuyết "hai bản như nhau", và cũng tương thích với "bản mới hơn 1.1 điểm". Bạn không biết gì cả.

Với đúng bộ test 500 câu đó, chênh lệch quan sát được nhỏ nhất còn cho $p < 0.05$ là $1.96 \times 0.358 = 0.70$ điểm. Mọi con số dưới ngưỡng đó là tiếng ồn được đặt tên. Nhưng đây mới là ngưỡng ý nghĩa, chưa phải ngưỡng nhìn thấy được: một chênh lệch thật đúng bằng 0.70 chỉ bị bắt 50% số lần — đúng bằng tung đồng xu. Chênh lệch nhỏ nhất bắt được 80% số lần là 1.00 điểm, nó có tên là MDE và Module 05 lo.

Bẫy thường gặp

  • Làm tròn ngược. 33.81 và 34.24 in ra thành 33.8 và 34.2 rồi bạn nói "hơn 0.4". Sai số thật là ±0.70, nên chữ số thứ nhất sau dấu chấm đã là rác.
  • Đổi cả hai thứ cùng lúc. Bản mới vừa thêm data vừa đổi learning rate. Kể cả khi chênh lệch có ý nghĩa thống kê, bạn vẫn không biết nhờ cái nào.
  • Quên rằng cùng một bộ test đã dùng 30 lần. Đó là mục 7 của module này.

2. Ghép cặp là vũ khí rẻ nhất và mạnh nhất

Đây là ý quan trọng nhất của cả module. Hai hệ thống dịch cùng một bộ câu nguồn. Câu nào khó thì khó cho cả hai; câu nào có tên riêng lạ thì cả hai cùng vấp. Phần lớn dao động của điểm là do câu, không phải do hệ thống.

Nên đừng so hai đống điểm. Hãy so hiệu ghép cặp (paired difference): với câu thứ $i$, đặt $d_i = b_i - a_i$, rồi làm việc trên dãy $d_1, \dots, d_n$. Phương sai của hiệu là:

$$\operatorname{Var}(d) = \operatorname{Var}(a) + \operatorname{Var}(b) - 2 r_{AB}\,\sigma_A \sigma_B$$

Nói bằng lời

Ở đây $r_{AB}$ là tương quan Pearson giữa hai cột điểm câu. Hai hệ càng giống nhau về chỗ mạnh chỗ yếu thì $r_{AB}$ càng gần 1, và số hạng trừ càng nuốt gần hết hai số hạng cộng. Với $\sigma_A = \sigma_B = 12$: nếu $r_{AB} = 0$ thì $\sigma_d = 16.97$; nếu $r_{AB} = 0.9$ thì $\sigma_d = 5.37$, nhỏ hơn 3.16 lần. Mà cỡ mẫu cần thì tỉ lệ với bình phương sai số, nên 500 câu ghép cặp cho độ chính xác bằng 5000 câu không ghép cặp. Ghép cặp là thứ duy nhất trong nghề này cho bạn gấp 10 lần dữ liệu miễn phí.

Dưới đây là dữ liệu ta sẽ dùng suốt phần còn lại của module: sáu câu trích từ một chương truyện tu tiên, chấm bằng chrF điểm câu. Sáu câu là quá ít cho một quyết định thật — nhưng vừa đủ để chạy tay mọi phép tính đến chữ số cuối cùng.

#Đặc điểm câu nguồnhệ Ahệ B$d = b - a$
1thoại ngắn, hai nhân vật31.435.0+3.6
2tả cảnh, câu dài 40 chữ42.041.2−0.8
3dày thuật ngữ tu luyện18.625.1+6.5
4câu ngắn, không tên riêng55.354.0−1.3
5xưng hô sư huynh / tại hạ27.930.4+2.5
6có con số và đơn vị39.541.8+2.3
Trung bình35.78337.917+2.133

Độ lệch chuẩn mẫu: hệ A là 12.73, hệ B là 10.14, còn dãy hiệu chỉ 2.89. Tương quan Pearson giữa hai cột điểm là $r_{AB} = 0.994$ — hai hệ gần như xếp hạng độ khó y hệt nhau. Đó chính là thứ ghép cặp đem ra tiêu.

Cùng một tỉ lệ đo: 13 pixel cho mỗi điểm chrF điểm thô trải 36.7 điểm hệ A hệ B 2030 4050 chrF cả 6 hiệu gói trong 7.8 điểm hiệu d −20−10 0+10 +20 d = B − A Độ lệch chuẩn 12.7 và 10.1 ở trên, chỉ còn 2.9 ở dưới — ghép cặp đã nuốt gần hết phương sai chung.
Cùng một thước đo cho cả hai trục. Điểm thô nhảy từ 18.6 tới 55.3 vì các câu khó dễ khác nhau; nhưng khi trừ theo từng câu, toàn bộ thông tin về "câu này khó" biến mất và chỉ còn lại thứ ta muốn đo.

3. Paired bootstrap resampling

Module 03 đã dựng bootstrap cho một con số. Ở đây chỉ đổi đúng một chi tiết: đơn vị được lấy mẫu lại là câu, không phải điểm. Bốc chỉ số câu có hoàn lại, và mỗi lần bốc thì cặp (điểm A, điểm B) của câu đó đi cùng nhau. Nhờ vậy cấu trúc ghép cặp được giữ nguyên qua từng vòng.

paired bootstrap trên 6 câu — chạy được ngay, không thư viện ngoài
import random
from statistics import mean

diem_a = [31.4, 42.0, 18.6, 55.3, 27.9, 39.5]   # he A, chrF tung cau
diem_b = [35.0, 41.2, 25.1, 54.0, 30.4, 41.8]   # he B tren DUNG nhung cau do
d = [y - x for x, y in zip(diem_a, diem_b)]     # hieu theo tung cau
obs = mean(d)

random.seed(4)
n, B = len(d), 10000
vong = []
for _ in range(B):
    # boc lai co hoan lai THEO CHI SO CAU -> cap (A,B) luon di cung nhau
    mau = [d[random.randrange(n)] for _ in range(n)]
    vong.append(mean(mau))

vong.sort()
lo, hi = vong[int(0.025 * B)], vong[int(0.975 * B)]
thua = sum(1 for m in vong if m <= 0) / B

print(f"hieu quan sat   = {obs:+.3f}")     # +2.133
print(f"CI 95%          = [{lo:+.3f}, {hi:+.3f}]")   # [+0.100, +4.200]
print(f"ti le vong <= 0 = {thua:.4f}")     # 0.0161

Kết quả: chênh lệch +2.133, khoảng tin cậy 95% là [+0.100, +4.200], và chỉ 1.61% số vòng bootstrap cho hệ B thua hoặc hoà. Nhân đôi cho hai phía được $p \approx 0.032$. Nghe như đã thắng.

Giữ con số 0.032 đó trong đầu. Mục sau sẽ tính chính xác cùng một câu hỏi và ra một đáp án khác hẳn.

4. Permutation test: đếm hết 64 khả năng

Bootstrap mô phỏng "nếu ta lấy một bộ test khác thì sao". Permutation test (bản liệt kê đủ gọi là exact randomization test; bản bốc ngẫu nhiên ở cuối mục này mới là approximate randomization) hỏi một câu khác và sắc hơn: nếu hai hệ thật ra như nhau, thì dữ liệu trông sẽ thế nào?

Nếu hai hệ như nhau thì nhãn "A" và "B" gắn lên hai bản dịch của một câu là tuỳ tiện — đổi chỗ chúng, thế giới không đổi. Đổi chỗ hai nhãn ở câu $i$ chính là đổi dấu $d_i$. Với 6 câu có $2^6 = 64$ cách gán dấu, mỗi cách xác suất bằng nhau là $1/64$. Đó là toàn bộ phân phối rỗng (null distribution), liệt kê được hết, không cần giả định gì về hình dạng phân phối.

$$p = \frac{1}{2^n} \sum_{s \in \{-1,+1\}^n} \mathbf{1}\left[\; \left| \frac{1}{n}\sum_{i=1}^{n} s_i d_i \right| \geq \left| \bar{d}_{\mathrm{obs}} \right| \;\right]$$

Nói bằng lời

Duyệt hết mọi cách bật/tắt dấu của từng hiệu. Mỗi cách cho một trung bình giả. Đếm xem bao nhiêu trung bình giả xa số 0 ít nhất bằng trung bình thật. Chia cho tổng số cách. Xong — đó là p-value, và nó là một phân số chính xác chứ không phải xấp xỉ.

Chạy tay một lần

Sáu hiệu: +3.6, −0.8, +6.5, −1.3, +2.5, +2.3. Tổng $= 12.8$, trung bình quan sát $\bar{d}_{\mathrm{obs}} = 12.8/6 = +2.1333$.

Muốn một cách gán dấu cho trung bình lớn hơn +2.1333, ta phải làm tổng lớn hơn 12.8. Chỉ có đúng hai chỗ để moi thêm: hai hiệu âm. Đổi dấu $-0.8$ được thêm $1.6$; đổi dấu $-1.3$ được thêm $2.6$. Đổi dấu bất kỳ hiệu dương nào đều làm tổng giảm. Vậy toàn bộ danh sách các cách đạt hoặc vượt là:

  • (+ + + + + +) → tổng 12.8 → +2.1333 ← chính dữ liệu thật
  • (+ − + + + +) → tổng 14.4 → +2.4000
  • (+ + + − + +) → tổng 15.4 → +2.5667
  • (+ − + − + +) → tổng 17.0 → +2.8333

Bốn cách. Phân phối rỗng đối xứng quanh 0 (lật toàn bộ dấu là ra ảnh gương), nên đuôi trái cũng có đúng 4 cách với trung bình $\leq -2.1333$. Tổng cộng 8.

$$p = \frac{8}{64} = \frac{1}{8} = 0.125$$

Năm trong sáu câu tốt hơn, trung bình hơn 2.13 điểm chrF, mà p vẫn là 0.125. Nếu bạn định ship dựa trên bảng này, bạn đang ship một tin đồn.

cùng phép tính đó, 6 dòng Python
from itertools import product
from statistics import mean

d = [3.6, -0.8, 6.5, -1.3, 2.5, 2.3]
obs = mean(d)
dem = sum(1 for s in product([+1, -1], repeat=len(d))
          if abs(mean([si * di for si, di in zip(s, d)])) >= abs(obs) - 1e-9)
print(dem, "/", 2 ** len(d), "=", dem / 2 ** len(d))   # 8 / 64 = 0.125
Phân phối rỗng — trung bình của cả 64 cách gán dấu Mỗi chấm là một hoán vị dấu. Đỏ: xa 0 ít nhất bằng kết quả thật. 4 chấm đỏ bên trái + 4 chấm đỏ bên phải = 8 trên 64 → p = 8/64 = 0.125 56 hoán vị còn lại nằm giữa hai vạch −2.133 +2.133 = quan sát −3−2 −10 +1+2 +3 trung bình của 6 hiệu sau khi đổi dấu (điểm chrF)
Toàn bộ thế giới của giả thuyết không, vẽ hết không sót một khả năng. Chấm đỏ ngoài cùng bên phải là dữ liệu thật của bạn — nó nằm ở đuôi, nhưng cái đuôi này dày tới 4/64 mỗi bên.

Bẫy thường gặp — vì sao bootstrap ra 0.032 mà permutation ra 0.125

  • Với $n = 6$, hãy tin permutation. Nó đếm chính xác toàn bộ không gian mẫu. Bootstrap percentile chỉ có 6 giá trị gốc để bốc đi bốc lại, tất cả chỉ có $\binom{11}{6} = 462$ mẫu khác nhau, nên hai cái đuôi của nó được dựng từ vài mẫu cực đoan và luôn quá lạc quan ở cỡ mẫu bé.
  • Sàn của permutation. Với $n$ câu, p-value hai phía nhỏ nhất có thể đạt là $2/2^n$. Với $n = 6$ là 0.031; với $n = 5$ là 0.0625 — nghĩa là bộ test 5 câu không bao giờ cho được $p < 0.05$, dù hệ B thắng cách biệt ở cả 5 câu.
  • Ở cỡ mẫu thật thì ba phương pháp hoà nhau. Lấy 300 hiệu có trung bình thật $+0.6$ và $\sigma_d = 8$: chênh lệch chỉ bằng $0.6/(8/\sqrt{300}) = 1.30$ lần sai số chuẩn, tức $p \approx 0.194$. Chạy trên cùng một mẫu, permutation, paired bootstrap và xấp xỉ chuẩn cho ba con số lệch nhau cỡ một phần trăm. Chênh lệch có thật mà cả ba đều không phát hiện được — đó là sai lầm loại II, chuyện của Module 05.
  • Đừng đổi sang một phía sau khi đã nhìn dữ liệu. Ở đây một phía cho $4/64 = 0.0625$. Chia đôi p sau khi thấy chiều thắng là gian lận, dù ở ví dụ này gian lận cũng chưa đủ cứu.

Khi $n$ lớn, liệt kê hết là bất khả: $2^{500} \approx 3.3 \times 10^{150}$. Thay bằng Monte Carlo — đây mới đúng là approximate randomization: bốc ngẫu nhiên $R = 10000$ vector dấu, đếm $b$ lần cực đoan, và báo cáo $\hat{p} = (b+1)/(R+1)$ (cộng 1 để p không bao giờ ra 0). Sai số của ước lượng này quanh mức 0.05 là $\sqrt{0.05 \times 0.95/10000} = 0.0022$ — đủ nhỏ để không ai tranh cãi.

5. Vì sao không dùng t-test hai mẫu độc lập

Cám dỗ lớn nhất là đổ hai cột điểm vào scipy.stats.ttest_ind. Hãy thử ngay trên bảng 6 câu ở trên và xem chuyện gì xảy ra:

  • t ghép cặp trên dãy hiệu: $t = 2.1333/1.1800 = 1.808$, bậc tự do 5, hai phía $p = 0.130$ — sát với permutation (0.125).
  • t hai mẫu độc lập (Welch) trên hai cột điểm: $t = 0.321$, $p = 0.755$. Mù hoàn toàn.

Sai số chuẩn của chênh lệch: ghép cặp 1.180, không ghép cặp 6.643 — gấp 5.63 lần. Muốn phép đo không ghép cặp đạt cùng độ chính xác, bạn cần $5.63^2 \approx 32$ lần số câu. Nghĩa là tội lớn nằm ở chữ "độc lập", không phải ở chữ "t". Nhưng vẫn còn ba lý do nữa để nghiêng về permutation và bootstrap:

  1. Điểm câu không chuẩn. chrF và sentence-BLEU bị chặn trong [0, 100], lệch mạnh, và câu ngắn hay dính đúng 0. Định lý giới hạn trung tâm cứu được ở $n$ vài trăm, nhưng nó không cứu ở $n = 20$.
  2. Câu trong corpus không độc lập với nhau. Cùng một chương thì cùng nhân vật, cùng bộ tên riêng, cùng lối hành văn. Một cái tên dịch sai xuất hiện ở 40 câu là một lỗi, không phải 40 quan sát. Cỡ mẫu hiệu dụng nhỏ hơn $n$, mọi p-value đều bị thổi cho đẹp. Cách chữa: bootstrap theo cụm — bốc lại nguyên chương chứ không bốc lại từng câu.
  3. Metric mức corpus không phải trung bình của metric mức câu. BLEU corpus gộp bộ đếm n-gram của cả bộ test rồi mới chia, nên không tồn tại "điểm câu" hợp lệ để đổ vào t-test (Module 01 đã mổ chỗ này). Bootstrap và permutation không vướng: chúng lấy mẫu lại câu, rồi tính lại metric từ đầu trên mẫu đó.

6. p-value nghĩa là gì — và bốn thứ nó không nghĩa là

Định nghĩa duy nhất đúng, đọc chậm: p là xác suất — trong một thế giới giả định nơi hai hệ thống như nhau — quan sát được chênh lệch xa 0 ít nhất bằng cái bạn đã thấy. Ở mục 4 nó là con số hoàn toàn cụ thể: trong 64 thế giới rỗng có thể có, đúng 8 thế giới cho kết quả bằng hoặc đậm hơn của bạn.

Chú ý thứ tự điều kiện: p tính dữ liệu với điều kiện giả thuyết. Bốn hiểu lầm dưới đây đều sinh ra từ việc đảo ngược thứ tự đó, hoặc từ việc quên rằng p tự nó cũng là một biến ngẫu nhiên.

Người ta hay nóiVì sao saiCon số phản chứng
"$p = 0.03$ nghĩa là 97% khả năng hệ B thật sự tốt hơn." p là $P(|T| \geq |t_{\mathrm{obs}}| \mid H_0)$ — xác suất của một kết quả cực đoan ít nhất bằng cái đã thấy, với điều kiện $H_0$ — chứ không phải $P(H_0 \mid \text{data})$. Muốn đảo chiều phải có xác suất tiên nghiệm — đó là Bayes, không phải kiểm định. Bảng 6 câu cho $p = 0.125$. Nó tuyệt đối không nói "12.5% khả năng hai hệ bằng nhau".
"p càng nhỏ thì chênh lệch càng lớn." p trộn lẫn cỡ hiệu ứng với cỡ mẫu. Một chênh lệch vô nghĩa vẫn cho p siêu nhỏ nếu $n$ đủ lớn. $\Delta = 0.1$ điểm với $n = 100000$, $\sigma_d = 8$ → $p = 7.7 \times 10^{-5}$. Còn $\Delta = 0.4$ với $n = 500$ → $p = 0.264$. Chênh lệch nhỏ hơn 4 lần, p nhỏ hơn khoảng 3400 lần.
"$p = 0.42$ nên hai hệ bằng nhau." Không bác bỏ được không phải là chứng minh bằng nhau. Thiếu bằng chứng khác với bằng chứng của sự thiếu. 300 hiệu có trung bình thật $+0.6$, $\sigma_d = 8$: $p \approx 0.194$, không bác bỏ được gì. Chênh lệch tồn tại; phép đo mù.
"$p < 0.05$ thì chạy lại sẽ ra kết quả tương tự." p rất ồn. Nó là hàm của một mẫu ngẫu nhiên nên bản thân nó nhảy dữ dội giữa các lần lặp. Nếu chênh lệch thật đúng bằng mức khiến p trung vị rơi vào 0.05 (tức power đúng 50%), thì lần chạy lại chỉ có 50.0% cơ hội $p < 0.05$, và 80% khoảng giữa của p trải từ 0.0012 tới 0.484.

Hệ quả thực hành: đừng bao giờ báo cáo p một mình. Luôn đi kèm cỡ hiệu ứng và khoảng tin cậy. "$+2.13$ chrF, CI 95% [+0.10, +4.20], permutation $p = 0.125$, $n = 6$" là một câu có nội dung. "Có ý nghĩa thống kê" là một câu không có nội dung.

7. So nhiều hơn hai: mỗi lần so là một tấm vé số

Ngưỡng $\alpha = 0.05$ nghĩa là: khi hai hệ thật sự như nhau, bạn vẫn tuyên bố thắng 5% số lần. Mua một vé thì 5% là chấp nhận được. Vấn đề là bạn không mua một vé.

Bạn eval 12 checkpoint và so mỗi cái với baseline. Nếu 12 phép so độc lập và giả thuyết không đúng ở cả 12, xác suất không lần nào báo động giả là $0.95^{12} = 0.5404$. Vậy xác suất có ít nhất một kết quả $p < 0.05$ là:

$$1 - (1-\alpha)^m = 1 - 0.95^{12} = 0.4596$$

45.96%. Gần một nửa. Bạn sẽ tìm thấy "người thắng", và cảm giác vui mừng sẽ hoàn toàn chân thật. Cùng công thức đó: 2 phép so cho 9.75%, 5 phép so cho 22.62%, 20 phép so cho 64.15%.

Hai cách chữa, cùng một mục tiêu là giữ xác suất sai lầm toàn họ (family-wise error rate) ở mức 5%:

  • Bonferroni: so mọi p với $\alpha/m$. Với $m = 12$ thì ngưỡng là $0.05/12 = 0.00417$. Đúng đắn nhưng thô — nó khắt khe đều với mọi phép so nên bạn mất nhiều power.
  • Holm: sắp p tăng dần rồi so $p_{(i)}$ với $\alpha/(m-i+1)$. Gặp cái đầu tiên không đạt thì dừng, mọi cái còn lại giữ nguyên giả thuyết không. Holm bảo vệ đúng bằng Bonferroni nhưng luôn bác bỏ được nhiều hơn hoặc bằng.
Thứ tựBiến thể so với baselinep thôngưỡng Holm $\alpha/(m-i+1)$HolmBonferroni $\alpha/m = 0.0125$
1thêm 200k cặp câu0.0100.05/4 = 0.01250bác bỏ ✓bác bỏ ✓
2LoRA r = 160.0140.05/3 = 0.01667bác bỏ ✓giữ
3đổi tokenizer 24k → 32k0.0390.05/2 = 0.02500dừng tại đâygiữ
4tăng beam 4 → 80.2200.05/1 = 0.05000giữgiữ

Đọc bảng: nhìn thô thì có ba biến thể "có ý nghĩa"; Bonferroni chỉ bác bỏ được một; Holm bác bỏ được hai. Chi phí của việc thử nhiều thứ là bạn phải khắt khe hơn với từng thứ — không có bữa trưa miễn phí.

Bẫy thường gặp — con số $m$ lớn hơn bạn tưởng nhiều

  • $m$ phải đếm mọi phép so bạn đã thực hiện, kể cả những phép so bạn im lặng bỏ đi vì xấu. Chọn checkpoint tốt nhất trong 12 cái là 12 phép so, dù báo cáo chỉ có một dòng.
  • Nếu 12 checkpoint có điểm thật bằng nhau và mỗi điểm đo có sai số chuẩn 0.358, thì riêng việc chọn cái cao nhất đã thổi điểm lên khoảng 0.58 điểm — thuần ảo giác, dù mọi checkpoint tốt như nhau. Cơ chế và bảng hằng số ở Module 09, mục 4.
  • Cách chữa rẻ nhất và tốt nhất: giữ một bộ test thứ hai chưa từng đụng tới, chọn xong trên bộ một rồi xác nhận lại trên bộ hai bằng đúng một phép so.

Trước khi đọc tiếp, hãy tự tay kéo cho hai hệ thống thật sự bằng nhau rồi xem bao nhiêu lần bạn vẫn tuyên bố có người thắng.

Phòng thí nghiệm 04

Hai model thật ra bằng nhau

Để Δ thật = 0, rồi kéo m và nhìn cột phải leo lên
Histogram 400 p-value · cột rộng 0.05
Tỉ lệ dự án tuyên bố thắng, theo m
Báo thắng nhầm—
Ít nhất 1 trong m lần—
Lý thuyết nếu Δ = 0—
p trung vị—
Ngưỡng chia đều cho m—
Đã chạy0 / 400
Giả thuyết không

Đang gieo 400 thí nghiệm độc lập…

Hai cách tính p khác hẳn nhau. Đổi qua đổi lại: histogram vẫn phẳng như nhau — cả hai đều được hiệu chỉnh đúng.
200
Bộ test to hơn không hạ được tỉ lệ báo nhầm — nó chỉ làm tăng khả năng bắt được chênh lệch có thật.
0.00
Chênh lệch thật giữa hai model. Để đúng 0 nghĩa là hai model bằng nhau tuyệt đối — mọi thứ bạn thấy sau đó là tiếng ồn.
8.0
Chỉ tỉ số Δ/σ quyết định kết quả: Δ = 0.5 với σ = 4 cho ra đúng từng con số như Δ = 1.0 với σ = 8.
5
3 checkpoint × 2 bộ test × 2 metric là m = 12, và bạn thường không đếm chúng.

Điều cần thấy: ① Mặc định là giả thuyết không đúng nghĩa đen: $\Delta = 0$, $n = 200$, $\sigma_d = 8$, paired bootstrap. Histogram bên trái phẳng — mức phẳng là 20 thí nghiệm mỗi cột, dải ±2 độ lệch chuẩn là [11.3, 28.7], và 20 cột thật rơi từ 15 tới 30. Đúng 18/400 = 4.5% số thí nghiệm cho $p < 0.05$, p trung vị đúng 0.500. Dưới $H_0$, p-value chỉ là một số ngẫu nhiên trải đều trên $[0,1]$ — nó không mang tin tức gì hết. ② Kéo mỗi m từ 1 lên 20, không đụng gì khác: tỉ lệ dự án có ít nhất một lần “thắng” leo từ 4.8% lên 60.6%, bám sát đường lý thuyết $1-0.95^m$ (5.0% → 64.2%). Ở $m = 12$ đã là 41.5%. Hai model y hệt nhau, mà bốn dự án trong mười vẫn công bố được một chiến thắng. ③ Kéo Δ thật lên 1.00: histogram dồn hết về cột đầu, nhưng chỉ 41.8% (167/400) số thí nghiệm bắt được — sáu trong mười lần bạn có cải tiến thật mà vẫn phải kết luận “không đủ bằng chứng”. Kéo tiếp $n$ lên 800 thì con số đó thành 95.5% (382/400). ④ Giữ Δ = 0 và bấm ↻ Gieo lại ba lần liên tiếp: tỉ lệ báo nhầm đi 4.5% → 5.3% → 3.5% → 4.3%. Chính con số bạn dùng để kết luận cũng có sai số của nó: 400 thí nghiệm chỉ cho độ chính xác $\sqrt{0.05 \cdot 0.95 / 400} = 0.011$, tức khoảng ±1.1 điểm phần trăm.

Nó ứng dụng vào đâu trong đời thật

  • Trước khi ghi một dòng changelog cho bản zh→vi mới. Chạy paired bootstrap và permutation trên chính bộ test đã dùng. Nếu khoảng tin cậy chứa 0, dòng changelog phải là "chưa đo được chênh lệch", không phải "cải thiện nhẹ".
  • Bộ test mẫu 200 câu của bạn mù hơn bạn tưởng. Với $\sigma_d = 8$, sai số chuẩn là $8/\sqrt{200} = 0.566$, nên mọi chênh lệch dưới $1.96 \times 0.566 = 1.11$ điểm đều không phân biệt được với 0. Muốn thấy 0.5 điểm thì phải mở rộng bộ test — bao nhiêu là đủ thì Module 05 tính.
  • Bootstrap theo chương, không theo câu. Web novel có tính cụm rất nặng: tên nhân vật, xưng hô, thuật ngữ môn phái lặp lại trong cả chương. Bốc lại theo chương cho khoảng tin cậy rộng hơn và trung thực hơn.
  • So DPO-lite với baseline. Nếu bạn đã thử DPO v1 (thất bại), DPO-lite, và ba mức beta khác nhau, thì $m = 5$ chứ không phải 1. Áp Holm trước khi tuyên bố bản nào thắng.
  • Ghép cặp cả ở khâu giải mã. Dùng chung seed, chung beam size, chung bộ câu nguồn cho hai hệ. Mỗi nguồn ngẫu nhiên bạn để tự do là một phần $r_{AB}$ bị mất, và $r_{AB}$ tụt từ 0.9 xuống 0.5 là lợi thế cỡ mẫu rơi từ 10 lần xuống 2 lần.

Tự kiểm tra

1 · Bộ test có 8 câu, hệ B thắng ở cả 8 câu với cách biệt lớn. p-value hai phía nhỏ nhất bạn có thể đạt bằng permutation test là bao nhiêu?

$2/2^8 = 2/256 = 0.0078$. Chỉ có hai cách gán dấu cho ra trung bình cực đoan bằng hoặc hơn: tất cả dương (chính dữ liệu) và tất cả âm (ảnh gương). Với 8 câu bạn vượt được ngưỡng 0.05, nhưng với 5 câu thì sàn là $2/32 = 0.0625$ — không bao giờ đạt, dù dữ liệu đẹp tới đâu. Đây là lý do một bộ test tí hon không cứu được bằng thống kê giỏi.

2 · Bạn so 5 biến thể với baseline, được p = 0.012, 0.030, 0.041, 0.049, 0.31. Bonferroni và Holm bác bỏ được những cái nào?

Không cái nào cả. Bonferroni: ngưỡng $0.05/5 = 0.010$, mà p nhỏ nhất là 0.012 → không bác bỏ. Holm: bước đầu so $p_{(1)} = 0.012$ với $0.05/5 = 0.010$; 0.012 lớn hơn nên dừng ngay lập tức, và mọi giả thuyết còn lại giữ nguyên. Bốn kết quả "có ý nghĩa" bốc hơi sạch. Bài học: bốn con p nằm sát 0.05 chính là dáng điệu điển hình của việc mua nhiều vé số, chứ không phải của bốn cải tiến thật.

3 · Trên 6 câu, paired bootstrap cho $p \approx 0.032$ còn permutation cho $p = 0.125$. Tin cái nào, và vì sao chúng lệch nhau tới bốn lần?

Tin permutation. Nó liệt kê chính xác cả 64 khả năng của giả thuyết không, không xấp xỉ gì hết. Bootstrap percentile phải dựng hai cái đuôi từ vỏn vẹn 6 giá trị gốc — phân phối bootstrap chỉ có $\binom{11}{6} = 462$ mẫu khác nhau, và chúng không đồng xác suất: khối lượng dồn vào mấy chục mẫu ở giữa, còn cả cái đuôi $\bar{d}^{*} \leq 0$ chỉ nặng $766/46656 = 0.0164$. Ở cỡ mẫu bé bootstrap nổi tiếng là quá hẹp, tức quá lạc quan. Khi $n$ lên vài trăm thì hai phương pháp gặp nhau: ở ví dụ 300 câu trong module, cả hai đều rơi quanh 0.19.

4 · Bộ test 500 câu, bạn chạy permutation bằng Monte Carlo với $R = 10000$ và đếm được $b = 380$ hoán vị cực đoan. Báo cáo p thế nào, sai số bao nhiêu?

$\hat{p} = (b+1)/(R+1) = 381/10001 = 0.0381$. Cộng 1 vào cả tử và mẫu để p không bao giờ ra đúng 0 — bạn chỉ chạy 10000 lần bốc, không có quyền tuyên bố "xác suất bằng không". Sai số Monte Carlo: $\sqrt{0.0381 \times 0.9619/10000} = 0.0019$, nên p thật nằm quanh $0.038 \pm 0.004$. Nếu con số rơi sát 0.05 và bạn cần quyết định, hãy tăng $R$ lên 100000 thay vì tranh cãi.

5 · Vì sao ghép cặp làm giảm phương sai, còn khi hai hệ dịch hai bộ câu khác nhau thì không ghép cặp được?

Vì $\operatorname{Var}(d) = \operatorname{Var}(a) + \operatorname{Var}(b) - 2 r_{AB}\sigma_A\sigma_B$: phần dao động chung do độ khó của câu bị trừ đi. Trên bảng 6 câu, $r_{AB} = 0.994$ nên phương sai hiệu chỉ còn 8.35 trong khi phương sai điểm hệ A là 162.0. Nếu hai hệ chấm trên hai bộ câu khác nhau thì không có $i$ chung để trừ, $r_{AB}$ bằng 0 theo định nghĩa, và bạn phải trả giá bằng khoảng 32 lần số câu để đạt cùng độ chính xác. Đây cũng là lý do phải cố định bộ test trước khi so, chứ không lấy mẫu mới cho mỗi hệ.

Phần II — Bất định & kiểm định · Module 05

05 Cỡ mẫu, power và độ nhạy của bộ test

Cần biết trước: Module 04 (paired bootstrap, p-value) Dựng nên: biết bộ test của bạn phát hiện được chênh lệch nhỏ tới đâu Dẫn tới: Module 06 — đánh giá bằng người

Mục tiêu

  • Tính được power của một bộ test có sẵn từ bốn con số $n$, $\sigma_d$, $\alpha$, $\Delta$ — và MDE, chênh lệch nhỏ nhất nó nhìn thấy được, từ $n$, $\sigma_d$, $\alpha$ và mức power bạn đặt ra.
  • Dùng đúng công thức cỡ mẫu $n = (z_{\alpha/2}+z_\beta)^2\sigma_d^2/\Delta^2$ và giải thích được vì sao mẫu số là $\Delta^2$ chứ không phải $\Delta$.
  • Chứng minh bằng số rằng bộ test 200 câu quen thuộc của bạn có power 14.3% với chênh lệch 0.5 BLEU — tức gần như mù.
  • Xếp hạng bốn cách giảm $\sigma_d$ theo số câu tiết kiệm được, và biết cách nào miễn phí.
  • Chia ngân sách decode cho $k$ hệ thống mà không tự bóp chết power của chính mình.

1. Hai kiểu sai — và kiểu thứ hai mới đang giết dự án của bạn

Module 04 dạy bạn không tuyên bố thắng nhầm. Đó mới là một nửa. Bảng dưới liệt kê cả bốn ô có thể xảy ra khi bạn so hai bản dịch.

Sự thật: $\Delta = 0$ (hai hệ như nhau)Sự thật: $\Delta \ne 0$ (có cải tiến thật)
Bạn kết luận "không khác biệt"Đúng — xác suất $1-\alpha = 95\%$Sai lầm loại II — xác suất $\beta$, không ai đặt trước
Bạn tuyên bố có khác biệt (bác bỏ $H_0$)Sai lầm loại I — xác suất $\alpha = 5\%$, bạn tự đặtĐúng — xác suất $1-\beta$, gọi là power

Để ý sự bất đối xứng. $\alpha$ là con số bạn chọn và kiểm soát chặt: 0.05, ai cũng biết, ai cũng báo cáo. Còn $\beta$ thì không ai chọn cả — nó là hệ quả bị động của cỡ bộ test, và trong đa số dự án dịch máy nó lớn hơn 0.80. Nghĩa là bạn đang chạy một máy dò mà bốn lần trên năm nó im lặng khi có tín hiệu.

Ngay cả quy ước "power 80%" cũng là một lựa chọn giá trị chứ không phải định luật: nó chấp nhận $\beta = 0.20$ trong khi $\alpha = 0.05$, tức bạn khoan dung với sai lầm loại II gấp 4 lần loại I. Với một ablation nội bộ không ai công bố, khoan dung ngược lại có khi hợp lý hơn.

Bẫy thường gặp

  • "$p = 0.31$ nên hai hệ bằng nhau." Sai. Module 04 đã nói p-value không đo điều đó. Power cho bạn cách nói đúng: "bộ test này chỉ có 14% cơ hội bắt được cải tiến 0.5 BLEU, nên việc nó không kêu chẳng nói lên gì." Vắng bằng chứng không phải bằng chứng về sự vắng mặt — và bây giờ bạn có con số để chứng minh câu đó.
  • Tính power sau khi có kết quả, bằng chính hiệu vừa đo được. Cái đó gọi là observed power, và nó chỉ là một hàm đơn điệu của p-value — không thêm thông tin nào. Power phải tính trước, với $\Delta$ là mức cải tiến bạn quan tâm, không phải mức bạn vừa nhìn thấy.

2. Power và MDE: bốn núm xoay, không phải một

Power (statistical power) là xác suất kiểm định của bạn bác bỏ $H_0$ khi cải tiến thật sự tồn tại và có độ lớn $\Delta$. Nó không phải một thuộc tính của bộ test. Nó là một hàm bốn biến:

$$1-\beta \;=\; \Phi\!\left(\frac{\Delta\sqrt{n}}{\sigma_d} - z_{\alpha/2}\right) \;+\; \Phi\!\left(-\frac{\Delta\sqrt{n}}{\sigma_d} - z_{\alpha/2}\right)$$

trong đó $\Phi$ là hàm phân phối tích luỹ chuẩn, $n$ là số câu, $\sigma_d$ là độ lệch chuẩn của hiệu ghép cặp (paired difference) theo từng câu giữa hai hệ, và $z_{\alpha/2} = 1.9600$ với $\alpha = 0.05$ hai phía.

Nói bằng lời

Trung bình hiệu $\bar{d}$ mà bạn đo được dao động quanh giá trị thật $\Delta$ với sai số chuẩn $\sigma_d/\sqrt{n}$. Bạn chỉ tuyên bố thắng khi $|\bar{d}|$ vượt $1.96$ lần sai số chuẩn đó. Vậy power chính là tỉ lệ khối lượng xác suất của $\bar{d}$ nằm ngoài vạch ngưỡng. Số hạng thứ hai là nhánh ngược dấu — tuyên bố thắng nhưng thắng nhầm hướng; nó thường bé xíu và ta sẽ đo nó ở mục 4.

Đại lượng $\Delta\sqrt{n}/\sigma_d$ là toàn bộ câu chuyện. Muốn power lên thì hoặc $\Delta$ lớn (không kiểm soát được), hoặc $n$ lớn (tốn tiền), hoặc $\sigma_d$ nhỏ (rẻ nhất — mục 5).

Đảo ngược câu hỏi cho hữu dụng hơn: với bộ test đang có, chênh lệch nhỏ nhất tôi nhìn thấy được là bao nhiêu? Đó là MDE (minimum detectable effect):

$$\Delta_{\min} \;=\; (z_{\alpha/2} + z_\beta)\,\frac{\sigma_d}{\sqrt{n}}$$
−2−10123hiệu trung bình quan sát được trên 200 câu (điểm BLEU) Δ thật = 0hai hệ như nhauΔ thật = 0.5cải tiến có thật β = 85.7%bỏ sót (loại II)α/2 = 2.5% báo động giả (loại I)power = 14.3%bắt được cải tiến thậtngưỡng: 1.11
Bộ test 200 câu, $\sigma_d = 8$. Đường xám là phân phối của hiệu quan sát nếu hai hệ thật sự bằng nhau; đường xanh là phân phối nếu bản mới hơn đúng 0.5 BLEU. Hai đường gần như chồng lên nhau — đó chính là vấn đề. Vạch đỏ ở 1.11 là ngưỡng tuyên bố thắng ($1.96 \times 8/\sqrt{200}$). Chỉ 14.1% khối lượng của đường xanh nằm bên phải vạch (cộng thêm 0.2% rơi ngược sang đuôi trái thành power 14.3%); 85.7% còn lại là những lần bạn có cải tiến thật trong tay và vẫn báo cáo "không có khác biệt".

3. Công thức cỡ mẫu cho hiệu ghép cặp

Đặt power mục tiêu rồi giải ngược ra $n$:

$$\boxed{\;n \;=\; \frac{\left(z_{\alpha/2} + z_\beta\right)^2 \, \sigma_d^2}{\Delta^2}\;}$$

Năm hạng tử, đọc từng cái:

  • $z_{\alpha/2} = 1.9600$ — ngưỡng báo động tính bằng số sai số chuẩn. Bạn chọn nó khi chọn $\alpha = 0.05$ hai phía.
  • $z_\beta = 0.8416$ — khoảng cách phụ trội để phân phối thật đứng đủ xa ngưỡng, sao cho $1-\beta = 80\%$ khối lượng của nó rơi vào vùng bác bỏ.
  • $(z_{\alpha/2}+z_\beta)^2 = 2.8016^2 = 7.8489$ — hằng số của bài toán. Với 5% / 80% nó luôn là con số này, không phụ thuộc dữ liệu. Nhớ nó đi.
  • $\sigma_d^2$ — phương sai của hiệu ghép cặp $d_i = b_i - a_i$. Đại lượng duy nhất bạn phải đo, và cũng là đại lượng duy nhất bạn can thiệp kỹ thuật được.
  • $\Delta^2$ — bình phương chênh lệch muốn bắt. Bình phương: muốn nhạy gấp đôi phải trả gấp bốn.

Chạy tay một lần

Câu hỏi: cần bao nhiêu câu để bắt được cải tiến 0.5 BLEU với power 80%, $\alpha = 0.05$ hai phía, biết $\sigma_d = 8$? Máy tính bỏ túi là đủ.

  1. Tra hai số $z$. Nhớ hai con số này là xong đời: $z_{0.025} = 1.96$ và $z_{0.20} = 0.84$ (chỉ số dưới luôn là diện tích đuôi, nên con số thứ hai chính là $z_\beta$ với $\beta = 0.20$).
  2. Cộng: $1.96 + 0.84 = 2.80$.
  3. Bình phương: $2.80^2 = 7.84$.
  4. Nhân phương sai: $7.84 \times 64 = 501.76$.
  5. Chia $\Delta^2$: $501.76 / 0.25 = 2007.0$ → làm tròn lên 2008 câu.
  6. Dùng $z$ đầy đủ ($1.959964$ và $0.841621$, tổng bình phương $7.848880$): $7.848880 \times 64 / 0.25 = 2009.3$ → 2010 câu.

Hai kết quả lệch 0.1% — làm tròn $z$ về hai chữ số là an toàn. Con số phải nhớ: khoảng 2000 câu cho một chênh lệch nửa điểm BLEU. Bộ test 200 câu bạn vẫn dùng nhỏ hơn 10 lần mức cần thiết.

Bảng dưới là toàn bộ mục 3 gói lại. Đọc theo hàng để thấy giá của phương sai, đọc theo cột để thấy giá của tham vọng.

$\sigma_d$ \ $\Delta$0.25 BLEU0.5 BLEU1 BLEU2 BLEU
4 (metric mượt, ghép cặp tốt)2 01050312632
8 (sentence-BLEU điển hình)8 0382 010503126
12 (thể loại lẫn lộn, câu dài ngắn thất thường)18 0844 5211 131283

Ba điều đọc ra ngay. Một: đường chéo bằng nhau — gấp đôi $\sigma_d$ tương đương chia đôi $\Delta$, cả hai đều nhân $n$ lên 4. Hai: cột 2 BLEU rẻ đến buồn cười (126 câu) — nếu cải tiến của bạn to như vậy thì bạn chẳng cần thống kê. Ba: cột 0.25 BLEU là vùng gần như không ai đủ tiền vào; những con số "hơn 0.2 BLEU" trong báo cáo nội bộ hầu hết là nhiễu.

Bẫy thường gặp: $\sigma_d$ ở đâu ra?

Nó không rơi từ trên trời. Bạn phải chạy pilot: decode cả hai hệ trên 100–200 câu, tính $d_i$ cho từng câu, lấy độ lệch chuẩn mẫu. Nhưng ước lượng đó cũng có sai số: với pilot 100 câu và $\sigma_d$ thật bằng 8, khoảng 95% của $\hat{\sigma}_d$ là [6.89, 9.11] (đọc thẳng từ phân phối chi bình phương 99 bậc tự do). Vì $n \propto \sigma_d^2$, con số 2010 của bạn thật ra nằm đâu đó trong [1489, 2607]. Cứ lấy cận trên mà đặt kế hoạch.

4. Vì sao bộ test 200 câu gần như mù

Thay số vào công thức power ở mục 2 với $n = 200$, $\Delta = 0.5$, $\sigma_d = 8$:

$$\frac{\Delta\sqrt{n}}{\sigma_d} = \frac{0.5 \times 14.142}{8} = 0.8839 \qquad\Rightarrow\qquad 1-\beta = \Phi(0.8839 - 1.9600) + \Phi(-2.8439)$$

Số hạng đầu là $\Phi(-1.0761) = 0.1410$; số hạng sau là $0.0022$. Cộng lại: power = 0.1432, tức 14.3%.

Dịch sang tiếng người: bạn có một cải tiến thật, đúng 0.5 BLEU. Bạn chạy thí nghiệm trên 200 câu. Cứ 7 lần chạy thì gần 6 lần bạn kết luận "không khác biệt đáng kể" và ném bỏ một cải tiến có thật. $1/0.1432 = 6.98$.

Nhìn từ phía MDE thì rõ hơn nữa: $\Delta_{\min} = 2.8016 \times 8/\sqrt{200} = \mathbf{1.585}$ điểm BLEU. Bộ test 200 câu của bạn không có khả năng nhìn thấy bất cứ thứ gì nhỏ hơn 1.6 điểm. Mọi ablation bạn từng chạy trên nó, mọi lần "thử LoRA rank 8 vs 16 thấy không khác gì", đều là kết quả rỗng chứ không phải kết quả âm.

$n$ (số câu)1002005001 0002 0104 000
power với $\Delta = 0.5$9.6%14.3%28.7%50.7%80.0%97.7%
MDE ở power 80%2.241.581.000.710.500.35
020%50%80%100%01000200030004000n — số câu trong bộ test (Δ = 0.5 BLEU cố định) n = 200 → power chỉ 14.3%σ_d = 4 · đủ 80% ở 503 câuσ_d = 8 · đủ 80% ở 2010 câuσ_d = 12 · 4521 câu, ngoài khung
Power leo theo $\sqrt{n}$, nghĩa là tắt dần rất nhanh. Từ 200 lên 500 câu, power đi từ 14.3% lên 28.7% — gấp đôi. Từ 2000 lên 4000 câu chỉ đi từ 80% lên 97.7%. Chỗ đáng đầu tư nằm ở đoạn dốc, không ở đoạn đuôi. Và để ý đường đỏ: khi $\sigma_d = 12$, bốn nghìn câu vẫn chưa chạm nổi vạch 80%.

Bốn núm xoay này dễ nói hơn là dễ cảm. Kéo thử ở phòng thí nghiệm dưới đây trước khi đọc tiếp — mặt power là một mặt cong, và trực giác của bạn về nó gần như chắc chắn đang sai ở vùng $n$ nhỏ.

Phòng thí nghiệm 05 · 3D

Mặt power

Kéo chuột để xoay mặt · lăn chuột để phóng to
Độ cao = power · trục n theo thang log
8.0
Núm duy nhất bạn can thiệp được bằng kỹ thuật. Kéo xuống là cả mặt nhấc lên — không thêm một câu nào.
200
Vị trí quả bóng theo trục n. Thang log vì power leo theo căn bậc hai của n — nhìn theo thang thường bạn sẽ tưởng nó gần tuyến tính.
0.50
Mức cải tiến bạn muốn bắt được, không phải mức vừa đo thấy. Chọn nó trước khi chạy thí nghiệm.
Hiệu chỉnh đa so sánh nâng ngưỡng z lên, và cả mặt power tụt xuống theo.
Power hiện tại—
MDE tại n này—
n cần cho Δ này—
Tỉ lệ bỏ sót β—
Chẩn đoán bộ test

—

Điều cần thấy: ① Mặc định là đúng bộ test bạn vẫn dùng: $n = 200$, $\Delta = 0.5$, $\sigma_d = 8$. Quả bóng đậu ở độ cao 14.3%, thấp lè tè, cách đường jade một quãng dựng đứng. Bốn ô số nói cùng một chuyện theo bốn kiểu: power 14.3% · MDE 1.585 · cần 2 010 câu · bỏ sót 85.7%. Ô lời bình đếm lại bằng mô phỏng 2 000 thí nghiệm: 284 lần tuyên bố thắng, tức 14.2% — mặt cong này không phải hình trang trí. ② Kéo σ từ 8 xuống 4, không đụng vào $n$ và $\Delta$. Cả mặt nhấc lên như thuỷ triều: power tại đúng chỗ cũ nhảy từ 14.3% lên 42.4%, MDE co từ 1.585 xuống 0.792, số câu cần thiết rơi từ 2 010 xuống 503. Đó là toàn bộ nội dung mục 5 gói trong một cú kéo — greedy decode seed cố định rẻ hơn mọi cách mua thêm câu. ③ Đường jade là đường mức power = 80%, có bóng chiếu xuống đáy hộp. Với $\sigma_d = 8$ nó chỉ chui vào hộp từ $n = 126$ (ở mép $\Delta = 2$) rồi bò xuống tận $\Delta = 0.317$ khi $n = 5000$. Kéo σ lên 16: nó lùi tới $n = 503$ mới xuất hiện. Kéo về 2: nó tràn gần kín mặt, ngay ở $n = 50$ đã chỉ còn $\Delta = 0.792$. Vùng nằm dưới đường jade là vùng mù, và ở mọi σ nó luôn chiếm phần lớn cái hộp. ④ Bấm “⤴ Kéo n tới power 80%”: $n$ nhảy lên 2 010 và quả bóng đậu đúng lên đường jade, power 80.0%. Đọc kèm dòng sai lệch độ lớn trong lời bình — lúc này $|\bar{d}|$ trung bình của các lần thắng chỉ còn gấp 1.12 lần sự thật, so với 2.78 lần khi $n = 200$. Đủ power không chỉ chữa bệnh bỏ sót; nó chữa luôn bệnh thổi phồng. ⑤ Đổi sang 6 phép so (4 hệ thống trong một bảng): mặt tụt xuống thấy rõ. Power tại $n = 200$ từ 14.3% còn 4.0%, số câu cần thiết từ 2 010 lên 3 101. Mỗi hệ thống bạn thêm vào bảng so sánh là một lần bóp cổ chính thí nghiệm của mình. ⑥ Kéo σ lên 16, giữ $n = 200$: power còn 7.3%, và mô phỏng cho 20 trong 154 lần thắng là thắng ngược dấu — 13.0%. Ở vùng power thấp, có ý nghĩa thống kê không bảo đảm được cả chiều của kết luận.

Bẫy phản trực giác: power thấp còn thổi phồng kết quả

Bạn có thể nghĩ "power thấp thì tệ nhất là tôi bỏ sót". Không phải. Với $n = 200$, ngưỡng tuyên bố thắng là $|\bar{d}| > 1.109$. Vậy mọi kết quả có ý nghĩa mà bạn thu được đều bắt buộc lớn hơn 1.11 điểm — trong khi sự thật chỉ là 0.5.

Tính kỳ vọng có điều kiện: $E[\,|\bar{d}| \mid$ có ý nghĩa $] = 1.396$, tức gấp 2.79 lần giá trị thật. Đó là sai lệch độ lớn (type M error). Tệ hơn: trong số các lần có ý nghĩa, 1.56% rơi ngược dấu — cải tiến có thật 0.5 điểm, nhưng bạn lại thu được $p < 0.05$ theo hướng bản mới tệ hơn và ghi vào báo cáo rằng nó làm hỏng model (type S error).

Hệ quả thực tế: thí nghiệm power thấp không chỉ im lặng, nó còn nói dối to mỗi khi nó mở miệng. Đó là lý do những cải tiến "+1.4 BLEU trên bộ test nhỏ" hay bốc hơi khi bạn mở rộng bộ test.

5. Giảm $\sigma_d$ rẻ hơn tăng $n$ rất nhiều

Vì $n \propto \sigma_d^2$, cắt $\sigma_d$ đi một nửa chia bốn số câu cần thiết. Còn muốn giảm $n$ đi 4 lần bằng cách khác thì bạn chỉ có mỗi cách tăng $\Delta$ — mà $\Delta$ là thứ trời cho. Vậy phương sai là mặt trận duy nhất.

5.1 Ghép cặp — vũ khí lớn nhất, và nó đã miễn phí sẵn

Module 04 đã nói ghép cặp làm phương sai nhỏ hơn. Đây là con số. Với hai hệ chấm trên cùng bộ câu:

$$\sigma_d^2 = \sigma_A^2 + \sigma_B^2 - 2r\,\sigma_A\sigma_B \qquad\xrightarrow{\;\sigma_A=\sigma_B=\sigma\;}\qquad \sigma_d = \sigma\sqrt{2(1-r)}$$

Ở đây $r$ là tương quan Pearson giữa hai cột điểm câu (tương quan giá trị, không phải hạng). Nếu không ghép cặp (mỗi hệ chấm trên một mẫu riêng), $r = 0$ và $\sigma_d = \sigma\sqrt{2}$. Hệ số tiết kiệm $n$ đúng bằng $1/(1-r)$ — Module 04, mục 2 đã chạy ca $r = 0.9$ (cắt 10 lần); ở đây ta cần con số đó để xếp ghép cặp cạnh ba can thiệp còn lại ở mục 5.2.

Hai bản checkpoint cách nhau vài nghìn bước có $r$ rất cao — câu nào khó với bản cũ thì cũng khó với bản mới. Đó là lý do ghép cặp thường tiết kiệm cỡ một bậc độ lớn.

Chạy tay một lần

Mười câu, sentence-BLEU (đã smoothing) của hai hệ. Cột $d_i = B_i - A_i$:

câu12345678910trung bình
hệ A5822417315366429471239.7
hệ B6513517027297037422342.7
$d_i$+7−9+10−3+12−7+6+8−5+11+3.00
  1. Độ lệch chuẩn từng hệ: $s_A = 20.84$, $s_B = 20.53$. Điểm câu tán loạn từ 12 tới 73 — đúng như sentence-BLEU vẫn thế.
  2. Tương quan Pearson: $r = 0.924$.
  3. Độ lệch chuẩn của cột hiệu: $s_d = \mathbf{8.08}$. Đây chính là chỗ con số $\sigma_d = 8$ dùng suốt module này ra đời.
  4. Nếu không ghép cặp: $\sqrt{s_A^2 + s_B^2} = \sqrt{434.2 + 421.6} = \mathbf{29.25}$ — rộng hơn 3.62 lần.
  5. Số câu cần cho $\Delta = 0.5$: ghép cặp 2 052 câu, không ghép cặp 26 869 câu. Gấp 13.1 lần.

Và một bài học phụ: mẫu 10 câu này có $\bar{d} = +3.0$ trông rất thuyết phục, nhưng $t = 3.00/(8.08/\sqrt{10}) = 1.17$ — không có ý nghĩa thống kê. Mười câu thì cái gì cũng không có ý nghĩa.

02040608004080điểm câu của hệ Ar = 0.924 — mọi điểm bám đường chéo không ghép cặp — 100 hiệu chéoσ = 27.9 ghép cặp — 10 hiệu trên cùng 10 câuσ = 8.08 mười chấm dưới là mười giá trị hiệu thật −60−300+30+60 hiệu điểm (BLEU điểm câu)
Trái: mười câu, điểm hệ A trên trục ngang, hệ B trên trục dọc. Chúng bám đường chéo vì độ khó của câu chi phối điểm mạnh hơn nhiều so với chất lượng hệ thống. Phải: thanh đỏ là độ tán của 100 hiệu chéo (lấy điểm bất kỳ của B trừ điểm bất kỳ của A) — $\sigma = 27.9$; thanh xanh là 10 hiệu ghép cặp — $\sigma = 8.08$, hẹp hơn 3.45 lần (phần chạy tay ở trên cho 29.25 và 3.62 lần — cùng một đại lượng, chỉ khác ở chỗ dùng ước lượng không chệch). Ghép cặp không làm dữ liệu tốt hơn; nó chỉ trừ đi độ khó của câu, thứ vốn giống hệt nhau ở cả hai vế.

5.2 Ba cách còn lại, xếp theo giá

Tách phương sai của hiệu thành hai phần: phần do hệ thống khác nhau thật và phần do ngẫu nhiên khi giải mã, $\sigma_d^2 = \sigma_{\text{sys}}^2 + \sigma_{\text{dec}}^2$. Giả sử trong $\sigma_d = 8$ có $\sigma_{\text{dec}} = 4$ (sampling với temperature). Khi đó $\sigma_{\text{sys}} = \sqrt{64-16} = 6.93$.

Can thiệp$\sigma_d$ mới$n$ cần cho $\Delta=0.5$Tiết kiệmGiá phải trả
Không làm gì (sampling, một lần decode)8.002 010——
Greedy / beam cố định seed — bỏ hẳn $\sigma_{\text{dec}}$6.931 507−25%Miễn phí. Chỉ cần một dòng config.
Trung bình 4 lần decode rồi mới trừ7.211 633−19%Decode gấp 4 lần. Tệ hơn cách trên về mọi mặt.
Phân tầng theo thể loại/độ dài, nếu 30% phương sai nằm giữa các tầng6.691 407−30%Công thiết kế bộ test (Module 08 lo).
Đổi sang metric có $\sigma_d$ nhỏ hơn 20%6.401 286−36%Phải kiểm định metric mới bám điểm người (Module 02).

Dòng thứ hai đáng nhớ nhất: cố định seed và dùng greedy decode cho mọi bản so sánh cắt được một phần tư số câu cần thiết, không tốn một xu. Dòng thứ ba là bài học ngược: lặp lại decode để lấy trung bình nghe có vẻ khoa học, nhưng nó chỉ đánh vào $\sigma_{\text{dec}}$ và đánh theo kiểu $1/k$ — bốn lần decode chỉ đổi được 19%, trong khi bỏ hẳn sampling đổi được 25%.

6. Ngân sách eval: đừng chia đều một cách ngây thơ

Giả sử ngân sách của bạn là 4 000 lượt decode (mỗi lượt = một câu, một hệ thống). Bạn có thể tiêu nó theo nhiều cách:

Cách tiêucâu / hệpower với $\Delta=0.5$MDE
2 hệ thống (1 phép so)2 00079.8%0.50
3 hệ thống (3 phép so)1 33362.6%0.61
4 hệ thống (6 phép so)1 00050.7%0.71

Đó mới là một nửa của cái giá. Nửa còn lại: nhiều hệ thống thì phải hiệu chỉnh đa so sánh (Module 04 đã giải thích vì sao), mà hiệu chỉnh làm $z_{\alpha/2}$ to ra và $n$ cần thiết to theo.

Với Bonferroni, $z_{\alpha/2}$ leo từ 1.960 ($m=1$) lên 2.241 ($m=2$), 2.394 ($m=3$), 2.638 ($m=6$), 2.807 ($m=10$). Số câu cần cho $\Delta = 0.5$, $\sigma_d = 8$ leo theo: 2 010 → 2 434 → 2 681 → 3 101 → 3 409, tức đắt thêm 21%, 33%, 54%, 70%.

Hai gọng kìm ép cùng lúc: thêm một hệ thống vừa lấy bớt câu của mỗi arm, vừa đòi thêm câu vì phải hiệu chỉnh. Bốn hệ thống trong cùng ngân sách 4 000 lượt cho bạn 1 000 câu mỗi arm trong khi bài toán đòi 3 101 — thiếu ba lần.

Ba nguyên tắc rút ra:

  1. Chọn MDE trước, tính $n$ sau. Không bao giờ chọn $n$ bằng cảm giác rồi hy vọng. Câu hỏi đúng là: "chênh lệch nhỏ nhất mà tôi sẽ hành động theo là bao nhiêu?" Nếu bạn chỉ ship khi hơn 1 điểm, đừng trả tiền cho bộ test 4 000 câu.
  2. Lọc thô rồi mới đo tinh. Sàng 12 checkpoint trên 300 câu là hợp lệ — bạn chỉ đang loại bỏ những cái tệ rõ ràng. Nhưng khi còn 2 ứng viên, phải chạy lại trên bộ test đủ lớn. Đừng bao giờ để một quyết định ship dựa trên vòng sàng.
  3. Bộ test lớn là chi phí một lần. Xây 2 000 câu tham chiếu tốn công một lần rồi dùng lại cho mọi thí nghiệm về sau; chi phí biên mỗi lần chạy chỉ là decode. So với việc chạy mù suốt sáu tháng, nó rẻ.

Nói bằng lời — một cảnh báo về phạm vi

Công thức $n$ ở đây giả định metric của bạn là trung bình theo câu. Điều đó đúng với chrF, COMET, TER và sentence-BLEU trung bình. Corpus-BLEU thì không phải trung bình theo câu (Module 01, mục 5). Với corpus-BLEU, hãy coi con số $n$ tính được là ước lượng bậc độ lớn, còn con số chính xác thì lấy bằng paired bootstrap: chạy Module 04 trên nhiều cỡ mẫu con và xem tỉ lệ bác bỏ.

Nó ứng dụng vào đâu trong đời thật

Trong dự án dịch web novel zh→vi, bốn quyết định đổi hẳn khi bạn có power trong tay:

  • Trước khi chạy ablation, tính MDE. Nếu bộ test 200 câu cho MDE = 1.58 mà bạn kỳ vọng LoRA rank 16 hơn rank 8 khoảng 0.3 điểm, đừng chạy thí nghiệm đó — nó không có khả năng trả lời. Hoặc mở bộ test, hoặc đổi câu hỏi sang thứ đo được (tỉ lệ rò Hán-Việt, số lỗi tên riêng).
  • DPO-lite vs baseline. Nếu kỳ vọng cải tiến nửa điểm BLEU, bạn cần cỡ 2 000 câu ghép cặp. Nếu chỉ có 500 câu, hãy nói thẳng trong báo cáo: "bộ test này chỉ nhìn thấy được chênh lệch từ 1.0 điểm trở lên" — đó là một câu trung thực và hữu ích hơn hẳn một p-value.
  • Cố định decode ngay hôm nay. Greedy hoặc beam với seed cố định cho mọi bản đem so. Đây là thay đổi một dòng config, đổi lại 25% số câu cần thiết. Không có can thiệp nào khác trong module này rẻ bằng.
  • Cảnh giác với chính những chiến thắng đẹp của mình. Nếu một cải tiến ra "+1.4 BLEU" trên 200 câu, hãy nhớ: với power 14%, kỳ vọng của kết quả có ý nghĩa là 1.40 dù sự thật chỉ 0.5. Con số đẹp trên bộ test nhỏ là bằng chứng của bộ test nhỏ, không phải bằng chứng của cải tiến lớn. Chạy lại trên 2 000 câu trước khi đưa vào bản ship.

Tự kiểm tra

1 · Bộ test của bạn có 500 câu và $\sigma_d = 8$. Chênh lệch nhỏ nhất bạn nhìn thấy được ở power 80% là bao nhiêu?

$\Delta_{\min} = (1.9600 + 0.8416) \times 8/\sqrt{500} = 2.8016 \times 8/22.361 = \mathbf{1.00}$ điểm BLEU. Nghĩa là mọi cải tiến dưới 1 điểm nằm ngoài tầm nhìn của bộ test đó. Nếu bạn từng báo cáo "+0.6 BLEU, có vẻ khá hơn" trên 500 câu, con số đó không phân biệt được với 0.

2 · Bạn đổi từ chrF sang sentence-BLEU và $\sigma_d$ nhảy từ 8 lên 12. Phải tăng $n$ bao nhiêu để giữ nguyên power?

Nhân với $(12/8)^2 = 2.25$. Cụ thể với $\Delta = 0.5$: từ 2 010 lên 4 521 câu. Đây là lý do metric mượt hơn không chỉ "đẹp hơn" — nó rẻ hơn theo nghĩa tiền thật. Ngược lại, giảm $\sigma_d$ 20% (từ 8 xuống 6.4) tiết kiệm $1 - 0.8^2 = 36\%$ số câu.

3 · Bạn chạy trên 200 câu, ra $\bar{d} = +1.4$ BLEU với $p < 0.05$. Nên tin bao nhiêu phần?

Rất ít. Với $n=200$, $\sigma_d=8$, ngưỡng có ý nghĩa là $1.96 \times 8/\sqrt{200} = 1.109$ — nên bất kỳ kết quả có ý nghĩa nào cũng phải lớn hơn 1.11. Nếu sự thật là $\Delta = 0.5$, kỳ vọng của $|\bar{d}|$ với điều kiện có ý nghĩa là 1.396, gấp 2.79 lần sự thật. Con số 1.4 của bạn khớp gần như hoàn hảo với kịch bản "cải tiến thật 0.5 điểm, gặp may". Việc phải làm: chạy lại trên bộ test lớn hơn, và đừng quote con số 1.4.

4 · Hai hệ có tương quan Pearson $r = 0.9$ trên điểm câu. Ghép cặp tiết kiệm bao nhiêu so với chấm trên hai mẫu riêng?

$\sigma_d$ nhỏ hơn $1/\sqrt{1-0.9} = \sqrt{10} = \mathbf{3.16}$ lần, nên $n$ nhỏ hơn $1/(1-0.9) = \mathbf{10}$ lần. Trên ví dụ 10 câu ở mục 5, $r = 0.924$ và tỉ lệ thực tế là 13.1 lần (2 052 so với 26 869 câu). Nói cách khác: không ghép cặp là cách đắt nhất để làm eval, và nó không mua lại được gì cả.

5 · Ngân sách 4 000 lượt decode. So 2 hệ hay 4 hệ?

2 hệ, gần như luôn luôn. 4 hệ cho 1 000 câu mỗi arm → power 50.7% với $\Delta=0.5$, tức tung đồng xu; đồng thời 6 phép so đòi Bonferroni, nâng $n$ cần thiết lên 3 101. Bạn thiếu hơn ba lần. Cách đúng: sàng 4 hệ trên một bộ nhỏ để loại 2 cái tệ rõ ràng, rồi dồn toàn bộ ngân sách còn lại cho 2 ứng viên cuối.

Phần III — Con người & judge · Module 06

06 Đánh giá bằng người có kỷ luật

Cần biết trước: Module 03 (khoảng tin cậy), Module 05 (cỡ mẫu) Dựng nên: một phiên chấm người cho ra số dùng được Dẫn tới: Module 07 — LLM-as-judge

Mục tiêu

  • Chọn một trong ba thang đo (DA · so cặp · MQM) bằng lập luận độ nhạy và chi phí, không bằng thói quen.
  • Viết rubric minor/major/critical cho zh→vi mà hai người lạ đọc xong chấm ra kết quả gần nhau.
  • Thiết kế phiên chấm có mù, có ngẫu nhiên hoá vị trí, có câu mồi, và có ngưỡng huỷ phiên bằng số.
  • Tính Cohen $\kappa$ bằng tay, và giải thích được vì sao agreement thô 90% có thể chỉ là $\kappa = 0.20$.
  • Ước lượng ngân sách: bao nhiêu câu, bao nhiêu người, bao nhiêu giờ để nhìn thấy chênh lệch bạn quan tâm.

1. Khi nào bắt buộc phải có người

Không phải lúc nào cũng cần. Metric tự động rẻ hơn hàng nghìn lần và bạn nên xài nó cho 95% số lần đo. Người chỉ bắt buộc khi metric mù đúng cái bạn đang sửa.

Ba tình huống mù điển hình trong dịch web novel zh→vi:

  • Lỗi hiếm nhưng chết người. Giả sử bộ test 500 câu, 1% (tức 5 câu) có lỗi đảo nghĩa. Bạn sửa được cả 5, điểm câu của chúng nhảy từ 5 lên 55. Trung bình điểm câu tăng $5 \times 50 / 500 = 0.5$ điểm. Đây là so sánh ghép cặp nên phải nhìn phân phối của hiệu, không phải phương sai của một hệ: hiệu gồm 5 giá trị 50 và 495 giá trị 0, cho $\sigma_d = 4.98$, $\mathrm{SE}_d = 4.98/\sqrt{500} = 0.22$ và $t = 2.25$ — trung bình có nhúc nhích thật. Nhưng toàn bộ tín hiệu nằm trên đúng 5 câu: paired bootstrap 20 000 vòng cho khoảng 95% là $[0.10, 1.00]$, cận trên gấp mười lần cận dưới, còn sign test chỉ cho $p = 0.0625$. Quan trọng hơn, con số 0.5 không nói được 5 câu đó nặng cỡ nào — metric cân một câu đảo nghĩa đúng bằng một câu văn vụng. 5 câu đó là 5 độc giả bỏ truyện.
  • Thứ vượt quá một câu. Xưng hô nhân vật nhất quán xuyên 300 chương, tên riêng ổn định, giọng kể không đổi. BLEU và chrF đọc từng câu độc lập, chúng không có khái niệm "chương trước gọi là sư tỷ".
  • Thứ mà reference không đại diện. Một câu Trung có mười bản dịch Việt đều hay. Reference chỉ có một bản. Bạn sửa cho bản dịch tự nhiên hơn nhưng xa reference hơn — metric bề mặt trừ điểm bạn.

Còn một lý do nền tảng hơn: COMET và BLEURT là hồi quy trên điểm người. Gốc của mọi thang đo, kể cả thang tự động, cuối cùng vẫn là người. Nếu bạn không bao giờ chấm tay, bạn không có cách nào biết metric của mình còn đúng trên miền dữ liệu của bạn.

2. Ba thang đo và cái giá của từng thang

Ba lựa chọn thực dụng: DA/Likert (direct assessment — cho điểm tuyệt đối), so cặp (pairwise comparison — chọn bản nào hơn), MQM (Multidimensional Quality Metrics — đánh dấu từng span lỗi kèm mức nặng nhẹ). Chúng khác nhau ở thứ chúng trả về, chứ không phải ở "cái nào chính xác hơn".

ThangDữ liệu ra là gìĐộ nhạyChi phí — 300 câu × 2 ngườiKhi nào dùng
DA / Likert Một số cho mỗi bản dịch: 0–100 liên tục (DA) hoặc 1–5 thứ bậc (Likert). So được giữa các phiên khác nhau, các tháng khác nhau. Thấp nhất. Với $\sigma_d = 20$ điểm trên hiệu ghép cặp, cần $n = 785$ câu để thấy $\Delta = 2$ điểm, $n = 126$ để thấy $\Delta = 5$. Một lỗi critical đơn lẻ (90 tụt xuống 20) chỉ kéo trung bình 200 câu xuống 0.35 điểm; hiệu ghép cặp của đúng kịch bản đó — một câu lệch 70, 199 câu lệch 0 — có $\mathrm{SE}_d = 0.35$, tức $t = 1.0$: vô hình. 30 s/câu/người → 5.0 giờ công Cần một con số tuyệt đối theo dõi dài hạn qua nhiều hệ, nhiều tháng.
So cặp Một nhãn A / B / hoà cho mỗi câu. Không có thang tuyệt đối — chỉ biết ai hơn, không biết hơn bao nhiêu. Cao nhất trên chênh lệch nhỏ, vì người chấm chỉ phải so hai thứ đặt cạnh nhau chứ không phải nhớ thang. Để phân biệt tỉ lệ thắng thật 55% với 50%: $n = 783$; với 60%: $n = 194$; với 65%: $n = 85$. 25 s/cặp/người → 4.2 giờ công Câu hỏi đúng dạng "bản mới có hơn bản cũ không". Đây là mặc định tốt.
MQM Danh sách span lỗi, mỗi span mang loại × mức. Vừa ra một điểm phạt, vừa ra bảng phân loại lỗi dùng được để quyết định sửa cái gì. Duy nhất nhìn thấy lỗi hiếm: một critical trên 200 câu hiện ngay trong bảng đếm. Đổi lại, điểm tổng phụ thuộc trọng số bạn tự chọn — xem ngay dưới đây. 180 s/câu/người → 30.0 giờ công
gấp 6.0× DA · 7.2× so cặp
Khi bạn cần biết sửa cái gì, không chỉ biết ai thắng. Chạy một lần mỗi quý, không chạy mỗi tuần.

Trọng số MQM là chính sách, không phải phép đo

Hai hệ, cùng một bộ chú thích MQM đã chốt. Hệ A: 30 minor, 10 major, 1 critical. Hệ B: 60 minor, 6 major, 0 critical. Trọng số minor = 1, major = 5, còn critical thì tuỳ bạn:

  • critical = 5 → A: $30 + 50 + 5 = 85$ · B: $60 + 30 = 90$ → A thắng
  • critical = 10 → A: $30 + 50 + 10 = 90$ · B: 90 → hoà
  • critical = 25 → A: $30 + 50 + 25 = 105$ · B: 90 → B thắng

Không có con số nào thay đổi. Chỉ một tham số trong bảng tính thay đổi, và thứ hạng lật hai lần. Bài học: chốt trọng số trước khi nhìn kết quả, ghi nó vào cùng chỗ với preregister ở Module 09. Nếu bạn chỉnh trọng số sau khi đã thấy bảng đếm, bạn không còn đang đo nữa.

3. Rubric zh→vi: minor · major · critical

Rubric là thứ quyết định $\kappa$ của bạn, chứ không phải chất lượng người chấm. Một rubric mơ hồ khiến hai dịch giả giỏi bất đồng; một rubric có quy tắc phân xử khiến hai sinh viên năm hai đồng thuận. Quy tắc phân xử của ta chỉ có một câu hỏi:

Người đọc chưa từng xem bản gốc có hiểu sai một sự kiện nào không?

Không → minor. Có, nhưng cục bộ, mạch truyện vẫn theo được → major. Có, và đoạn văn hỏng hoặc nghĩa bị đảo → critical.

minor — hiểu đúng hết, chỉ thấy văn vụng

Thông tin nguyên vẹn. Người đọc chỉ cảm thấy câu cứng, thừa chữ, hoặc quá "convert". Không ai phải đọc lại.

  • Ví dụ 1 · dịch sát cấu trúc. Nguồn: 他没有回答,只是把剑收了回去。
    Bản dịch: "Hắn không có trả lời, chỉ là đem kiếm thu trở về."
    Vì sao minor: nghĩa đúng 100% — hắn im lặng và cất kiếm. "Không có + động từ", "chỉ là", "đem … thu trở về" là khung tiếng Trung bê nguyên. Bản gọn: "Hắn không đáp, chỉ tra kiếm vào vỏ."
  • Ví dụ 2 · Hán-Việt sống sượng. Nguồn: 她快步走进丹房。
    Bản dịch: "Nàng khoái bộ tẩu tiến đan phòng."
    Vì sao minor: độc giả web novel lâu năm vẫn giải mã được "khoái bộ tẩu tiến" là "bước nhanh vào". Không mất thông tin nào. Nhưng nếu 30% câu như thế thì bản dịch không đọc được với người mới — đó là lý do minor vẫn phải đếm, chỉ là trọng số 1.

major — sai hoặc mất một chi tiết, mạch vẫn theo được

Người đọc tiếp nhận một thông tin sai, nhưng không sai tới mức hỏng chương. Đây là mức đông nhất, và cũng là mức hai người chấm hay cãi nhau nhất — nên nó cần nhiều ví dụ nhất trong tài liệu hướng dẫn của bạn.

  • Ví dụ 1 · đảo vai vế xưng hô. Nguồn: 师姐,你先走。
    Bản dịch: "Sư muội, muội đi trước đi."
    Vì sao major: 师姐 là sư tỷ — vai trên. Bản dịch lật ngược quan hệ hai nhân vật. Người đọc hiểu sai thứ bậc, và nếu lỗi này lặp lại thì cả tuyến quan hệ lệch. Chưa tới critical vì cảnh vẫn diễn ra đúng: một người bảo người kia đi trước.
  • Ví dụ 2 · sai con số. Nguồn: 他闭关了三百年。
    Bản dịch: "Hắn bế quan ba nghìn năm."
    Vì sao major: sai một chi tiết định lượng cụ thể, và con số này thường được nhắc lại ở chương sau nên sẽ tạo mâu thuẫn nội tại. Vẫn không đảo nghĩa sự kiện: hắn có bế quan.
  • Ví dụ 3 · rơi một mệnh đề. Nguồn: 他握紧了拳头,却终究没有出手。
    Bản dịch: "Hắn siết chặt nắm đấm."
    Vì sao major: mất vế "nhưng rốt cuộc không ra tay" — mất một quyết định của nhân vật. Câu còn lại vẫn đúng, không tự mâu thuẫn, nên chưa phải critical.

critical — đoạn hỏng, nghĩa đảo, hoặc nội dung bịa

Người đọc bị dẫn tới một kết luận sai về cốt truyện, hoặc phải bỏ đoạn. Đây là mức đáng để bạn dừng ship. Trong dữ liệu thật của một hệ dịch web novel, mức này thường hiếm — vài phần trăm — và chính vì hiếm nên nó biến mất khỏi mọi con số trung bình.

  • Ví dụ 1 · nuốt phủ định. Nguồn: 他并没有杀死那个孩子。
    Bản dịch: "Hắn đã giết chết đứa bé đó."
    Vì sao critical: nghĩa bị đảo hoàn toàn. Nhân vật chính từ người tha mạng thành kẻ giết trẻ con. Một câu này làm hỏng cả arc nhân vật, và độc giả không có cách nào tự sửa vì bản dịch đọc rất trôi chảy.
  • Ví dụ 2 · ảo giác, sinh thêm nội dung. Nguồn: 外面下起了雨。
    Bản dịch: "Bên ngoài trời đổ mưa. Nàng biết rằng sư phụ mình đã chết."
    Vì sao critical: câu thứ hai không tồn tại trong nguồn. Model bịa ra một sự kiện cốt lõi. Đây là loại lỗi mà BLEU trừ rất ít (câu đầu khớp reference) nhưng độc giả thì mất niềm tin ngay lập tức.
  • Ví dụ 3 · sập kỹ thuật. Bản dịch trả về "Nàng nói nàng nói nàng nói nàng nói…" hoặc một chuỗi rỗng.
    Vì sao critical: đoạn không dùng được. Đừng gộp mức này vào major chỉ vì nó "rõ ràng quá" — bạn cần đếm được nó tách riêng để đặt guardrail trong bộ hồi quy.

Nói bằng lời

Một câu có thể chứa nhiều lỗi, và bạn đếm span chứ không đếm câu. Một câu có 3 minor và 1 major đóng góp $3 \times 1 + 1 \times 5 = 8$ điểm phạt. Nếu bạn chỉ ghi "câu này major" thì bạn đã ném đi thông tin bạn vừa trả tiền để lấy về.

4. Thiết kế một phiên chấm mù

Một phiên chấm cẩu thả không cho ra số nhiễu — nó cho ra số lệch, và lệch thì không bootstrap nào cứu được. Bốn thứ bắt buộc: mù nhãn hệ thống, ngẫu nhiên hoá vị trí trái/phải theo từng câu, chèn câu mồi, và giới hạn độ dài phiên.

Một phiên = 80 cặp thật + 12 câu mồi = 92 mục, khoảng 38 phút ① 80 cặp nguồn zhphân tầng theo thể loại ② Tung đồng xu mỗi cặpquyết bản nào hiện bên trái ③ Trộn thứ tự, chèn 12 mồichuẩn hoá font và dấu câu ④ Người chấm, mù nhãn3 mồi đầu phiên: khởi động ⑤ Cổng câu mồitrượt ≥ 3/12 thì phiên hỏng ⑥ Tháo mã trái/phải → A/Btheo bảng ánh xạ đã lưu ⑦ Phần chồng lấn → κkiểm chính người chấm ⑧ Tỉ lệ thắng ± CI 95%con số duy nhất được báo cáo Huỷ phiên, chấm lại từ đầumất 38 phút, rẻ hơn nhiều so với số sai Hai ô tô đậm ở hàng trên là hai chỗ duy nhấtbơm tính ngẫu nhiên vào. Bỏ một trong hailà mọi con số phía sau mất giá trị.
Một phiên chấm mù. Đồng xu ở bước ② phải tung lại cho từng cặp, không phải một lần cho cả phiên. Ngưỡng ở bước ⑤ được chọn bằng phân phối nhị thức: nếu người chấm nghiêm túc trượt mỗi câu mồi với xác suất 0.05 thì $P(X \geq 3)$ trên 12 mồi chỉ là 2.0% — đó là tỉ lệ báo động giả bạn chấp nhận. Còn người bấm bừa (trượt 50%) bị bắt với xác suất 98.1%.

Vài chi tiết dễ bỏ sót nhưng làm hỏng phiên:

  • Chuẩn hoá hiển thị trước khi trộn. Nếu hệ B luôn xuống dòng sau dấu chấm còn hệ A thì không, người chấm nhận ra nhãn sau 5 câu và mọi thứ mù đều vô nghĩa. Chuẩn hoá khoảng trắng, dấu câu, viết hoa, ký tự đặc biệt về cùng một dạng.
  • Hai loại câu mồi. Loại một: bản dịch cố tình phá (đảo phủ định hoặc cắt nửa câu) — người tỉnh táo phải chấm thấp. Loại hai: cặp giống hệt nhau — người tỉnh táo phải chọn "hoà". Loại hai bắt được người luôn bấm bên trái, loại một bắt được người bấm bừa.
  • Lưu bảng ánh xạ ra ngoài. Ánh xạ vị-trí → hệ-thống phải nằm ở file khác, không nằm trong giao diện chấm. Nghe hiển nhiên, nhưng lỗi phổ biến là để tên hệ thống trong thuộc tính HTML rồi tự hỏi vì sao κ cao bất thường.
  • Giới hạn phiên. 92 mục × 25 giây ≈ 38 phút. Ba mục đầu phiên đều là câu mồi — chúng không bao giờ đóng góp vào tỉ lệ thắng, nên mất đúng $3/92 = 3.3\%$ thời gian phiên chứ không đụng vào 80 cặp thật, đổi lấy việc khử được hiệu ứng neo ở đầu phiên.

5. Năm thiên vị của người chấm

Người chấm không phải cảm biến. Họ là hệ thống có trạng thái, có mệt mỏi, có kỳ vọng. Năm thiên vị (bias) dưới đây đều đã đủ mạnh để tự mình tạo ra một "kết quả có ý nghĩa thống kê".

Chạy tay một lần — thiên vị vị trí một mình đủ để tạo p < 0.001

Giả sử hai hệ hoàn toàn ngang nhau. Người chấm khi phân vân thì nghiêng về bản bên trái, đủ để tỉ lệ chọn trái là 60%. Bạn đặt hệ A cố định bên trái, chấm $n = 300$ cặp.

Sai số chuẩn của một tỉ lệ dưới giả thuyết không: $\sqrt{0.25/300} = 0.028868$. Thống kê kiểm định:

$$z = \frac{0.60 - 0.50}{0.028868} = 3.4641 \quad\Rightarrow\quad p = 0.000532$$

Bạn sẽ viết vào báo cáo: "hệ A thắng 60% số cặp, $p < 0.001$". Con số đúng. Kết luận sai hoàn toàn — bạn vừa đo cái ghế của người chấm.

Bây giờ tung đồng xu cho từng cặp: A ở bên trái nửa số lần. Tỉ lệ thắng của A trở thành $0.5 \times 0.6 + 0.5 \times 0.4 = 0.50$. Thiên vị triệt tiêu chính xác, không cần biết nó mạnh bao nhiêu. Đây là lý do ngẫu nhiên hoá không phải là "cho chắc" mà là điều kiện cần.

Thiên vịNó làm gìCách chặn
Vị tríNghiêng về bản bên trái (hoặc bản hiện trước). 60% đủ tạo $p = 0.000532$ trên 300 cặp.Tung đồng xu mỗi cặp. Sau phiên, kiểm tra tỉ lệ chọn-trái tổng thể: lệch xa 50% là dấu hiệu.
Độ dàiBản dài hơn trông "đầy đủ" hơn. Giả sử 60% số cặp thì B dài hơn, và khi dài hơn thì B được chọn 58% thay vì 50%. Tỉ lệ thắng của B thành $0.6(0.58) + 0.4(0.50) = 0.548$; với $n = 800$ thì $z = 2.715$, $p = 0.0066$ — "có ý nghĩa" từ hư không.Báo cáo tỉ lệ thắng tách theo nhóm B-dài-hơn / B-ngắn-hơn. Nếu hai nhóm cho hai câu trả lời khác nhau, bạn đang đo độ dài.
Mệt mỏiĐiểm trôi dần theo thứ tự trong phiên. Giả sử trôi 0.15 điểm DA mỗi mục sau mục thứ 40: tới mục 92 — trần phiên của ta — đã lệch $52 \times 0.15 = 7.8$ điểm; để phiên chạy tới 120 mục thì thành 12 điểm. Cả hai đều lớn hơn mọi $\Delta$ bạn đang săn.Phiên ≤ 92 mục. Trộn thứ tự nên trôi rơi đều lên cả hai hệ. Sau phiên, hồi quy điểm theo chỉ số thứ tự để đo độ trôi thật.
Học được hệ thốngSau 30 mục người chấm nhận ra "bản nào nhiều Hán-Việt là hệ cũ", rồi chấm theo nhãn suy đoán chứ không theo chất lượng.Chuẩn hoá hiển thị. Trộn nhiều hơn hai hệ. Chia nhỏ phiên và đổi người.
NeoMục đầu tiên đặt mốc cho cả phiên; trong một cặp, bản đọc trước đặt mốc cho bản đọc sau.3 mục mồi đầu phiên làm khởi động, không tính điểm (3.3% thời gian phiên). Thang có mô tả bằng chữ cho từng mức, không phải chỉ con số trần.

6. Đo đồng thuận: agreement thô nói dối, $\kappa$ nói thật

Bạn có hai người chấm cùng một tập câu. Câu hỏi đầu tiên phải là: họ có đang đo cùng một thứ không? Nếu $\kappa$ thấp thì mọi con số phía sau chỉ là ý kiến của một người, và người kia không tái tạo được.

Cám dỗ là báo cáo agreement thô (raw agreement) — tỉ lệ câu hai người cho cùng nhãn. Nó luôn nghe hay, vì nó tính cả phần trùng nhau do may rủi. Cohen $\kappa$ trừ phần đó đi:

$$\kappa = \frac{p_o - p_e}{1 - p_e}, \qquad p_e = \sum_k p_{A,k}\, p_{B,k}$$

Nói bằng lời

$p_o$ là tỉ lệ trùng nhau thật sự quan sát được. $p_e$ là tỉ lệ trùng nhau mà hai người sẽ đạt được nếu họ dán nhãn ngẫu nhiên nhưng vẫn giữ đúng tần suất từng nhãn của mình — tích của hai tỉ lệ lề, cộng qua mọi nhãn.

Mẫu số $1 - p_e$ là dư địa còn lại sau khi may rủi đã lấy phần của nó. Tử số $p_o - p_e$ là phần bạn thật sự chiếm được. Vậy $\kappa$ là tỉ lệ dư địa mà hai người khai thác được. $\kappa = 0$ nghĩa là họ không hơn gì hai đồng xu có cùng độ lệch. $\kappa = 1$ là trùng khít.

Chạy tay một lần — Cohen $\kappa$ trên bảng chéo 2×2

Hai dịch giả chấm 100 câu, mỗi câu một nhãn: OK (dùng được) hay FIX (phải sửa). Bảng chéo:

B: OKB: FIXTổng hàng
A: OK451055
A: FIX153045
Tổng cột6040100

Bước 1 — agreement quan sát. Đường chéo là chỗ hai người trùng nhau:

$$p_o = \frac{45 + 30}{100} = 0.75$$

Bước 2 — tỉ lệ lề. A gán OK cho $55/100 = 0.55$ số câu, B gán OK cho $60/100 = 0.60$. Tương ứng FIX: $0.45$ và $0.40$.

Bước 3 — agreement do may rủi. Cả hai cùng bấm OK một cách độc lập: $0.55 \times 0.60 = 0.33$. Cả hai cùng bấm FIX: $0.45 \times 0.40 = 0.18$. Cộng lại:

$$p_e = 0.33 + 0.18 = 0.51$$

Bước 4 — ghép.

$$\kappa = \frac{0.75 - 0.51}{1 - 0.51} = \frac{0.24}{0.49} = 0.4898$$

Đọc ra tiếng Việt: hai người trùng nhau 75% số câu, nhưng 51% trong đó tung đồng xu cũng đạt được. Trong 49 điểm phần trăm dư địa còn lại, họ chỉ chiếm được 24 — chưa tới một nửa. Con số 75% nghe rất ổn; con số 0.49 nói rằng rubric của bạn còn mơ hồ.

Cùng một agreement thô, năm giá trị $\kappa$ khác nhau

Đây là chỗ phản trực giác nhất của module này, nên nói thẳng: agreement thô không so sánh được giữa hai bộ dữ liệu có tần suất nhãn khác nhau. Bảng dưới giữ agreement thô cố định ở đúng 90% (270/300 câu trùng, 30 câu bất đồng, chia đều 15/15), chỉ thay đổi độ hiếm của lớp cần bắt.

Cả hai nói "không"A có, B khôngA không, B cóCả hai nói "có"Tần suất lớp "có"$p_o$$p_e$$\kappa$
265151556.7%0.9000.87560.196
25515151510.0%0.9000.82000.444
24015153015.0%0.9000.74500.608
21015156025.0%0.9000.62500.733
135151513550.0%0.9000.50000.800

Hàng đầu là trường hợp thật của bạn: gắn cờ lỗi critical trên 300 câu, lớp hiếm. Người A gắn cờ 20 câu, người B cũng gắn cờ 20 câu, nhưng chỉ trùng nhau 5 câu. Kiểm tra: $p_e = (280/300)^2 + (20/300)^2 = 0.8756$, nên

$$\kappa = \frac{0.900 - 0.8756}{1 - 0.8756} = \frac{0.0244}{0.1244} = 0.196$$

Agreement thô 90% — nghe như xuất sắc. Nhưng hai người dán nhãn ngẫu nhiên với đúng tần suất đó đã đạt 87.6% rồi. Bạn chỉ hơn may rủi 2.4 điểm phần trăm trên dư địa 12.4 điểm. Một cách nhìn khác cùng dữ liệu: positive specific agreement $= 2 \times 5 / (20 + 20) = 0.25$ — khi một người đã gắn cờ critical, người kia chỉ gắn cờ theo trong 25% số lần. Đọc kiểu thứ ba cũng trên đúng dữ liệu đó: có $5 + 15 + 15 = 35$ câu bị ít nhất một người gắn cờ, và chỉ 5 câu trong đó — 14.3% — được cả hai đồng ý. Rubric critical của bạn chưa tồn tại; nó chỉ đang ở trong đầu từng người một cách khác nhau.

Bảng A · nhãn cân bằng · n = 100κ = 0.49 p_e = 0.51p_o = 0.75trùng nhau do may rủi Bảng B · lớp critical hiếm · n = 300κ = 0.20 p_e = 0.876p_o = 0.90may rủi đã nuốt gần hết thang đo 00.250.500.751tỉ lệ câu trùng nhãn
$\kappa$ là phần xanh chia cho khoảng từ $p_e$ tới 1. Bảng A: phần xanh dài 158.4 điểm ảnh trên dư địa 323.4 → $158.4/323.4 = 0.4898$. Bảng B: agreement thô cao hơn hẳn (0.90 > 0.75) nhưng dư địa chỉ còn một mẩu, nên phần xanh 16.13 trên 82.13 cho $16.13/82.13 = 0.1964$. Thanh xám dài không phải thành tích — nó là chỗ may rủi đã ngồi sẵn.

Kéo thử bảng chéo ở phòng thí nghiệm dưới đây trước khi đọc tiếp — đổi vài ô rồi xem hai con số rời nhau ra sao.

Phòng thí nghiệm 06

Đo đồng thuận

Gõ lại bốn ô rồi nhìn hai con số tách nhau ra
Bảng chéo · ô đậm = nhiều câu · số mờ = số câu may rủi cũng đạt
κ trên thang phân loại · vạch dưới = khoảng 95% bootstrap
n = 100
Hai người chấm cùng một tập câu, mỗi câu một nhãn: tốt (dùng được) hay kém (phải sửa). Bốn ô là bốn cách hai phán quyết gặp nhau.
Agreement thô—
Trùng do may rủi—
Dư địa còn lại—
Cohen kappa—
Khoảng 95% bootstrap—
Đồng thuận riêng lớp kém—
Đọc bảng

—

Quét xu hướng chọn ô bên trái, từ 50% tới 95%
50%
Hai người chấm so cặp bản dịch trái/phải trên 20 000 cặp. Năng lực thật của họ không đổi một li khi bạn kéo núm này (cùng nhiễu riêng $\sigma = 1$); chỉ cái ngưỡng bấm nút dịch đi. Ở mức 50% giá trị lý thuyết chính xác là $p_o = 2/3$ và $\kappa = 1/3$; mô phỏng 20 000 cặp cho 67.3% và 0.345.
Thu nhỏ bảng mô phỏng về 400 câu rồi nạp vào bốn ô phía trên, với quy ước trái → tốt, phải → kém. Dùng để xem khoảng tin cậy của $\kappa$ ở cỡ mẫu thật.
Tỉ lệ chọn trái A / B—
Agreement thô—
Cohen kappa—
Agreement thô tăng—
Kappa đổi—
Thiên vị vị trí

—

Điều cần thấy: ① Bảng mặc định đúng là bảng đã chạy tay ở trên. Agreement thô 75.0%, nhưng may rủi đã ngồi sẵn ở 51.0%, nên $\kappa$ chỉ còn 0.490 — trong 49.0 điểm dư địa, hai người chiếm được 24.0. Khoảng 95% bootstrap là [0.32, 0.66] — trải từ “vừa” sang “tốt”: với 100 câu bạn chưa chốt được mức nào. ② Bấm ② 90% mà κ thấp (265/15/15/5). Agreement thô nhảy lên 90.0% — cao hơn hẳn bảng đầu — mà $\kappa$ tụt xuống 0.196 và thanh phân loại đổi từ “khá” sang “kém”. Nhìn số mờ trong ô trên-trái: riêng may rủi đã cho 261.3 trên 265 câu. Đồng thuận riêng cho lớp “kém” chỉ 0.250, và khoảng 95% là [0.01, 0.38] — không kết luận được gì. ③ Bấm ③ Hai người ngẫu nhiên (72/48/48/32): $p_o = 52\%$, $p_e$ cũng đúng 52.0%, nên $\kappa = 0.000$ bằng không tuyệt đối. Hai người trùng nhau hơn nửa số câu mà không đồng thuận một chút nào. ④ Nửa dưới, kéo xu hướng chọn ô bên trái từ 50% lên 95%: agreement thô leo 67.3% → 92.6% (+25.3 điểm), còn $\kappa$ đi ngược lại, 0.345 → 0.207. Năng lực thật của hai người chấm không thay đổi một li — 25 điểm phần trăm “chất lượng” kia sinh ra từ đúng một thói quen bấm nút. ⑤ Ở mức 95%, bấm ⇧ Đưa bảng này lên trên: bảng thành 366/15/14/5 trên 400 câu, $\kappa = 0.218$ với khoảng 95% [0.02, 0.39] — rộng gần bằng cả thang phân loại. Lớp hiếm không chỉ kéo $\kappa$ xuống thấp, nó còn làm $\kappa$ gần như không đo được.

$\kappa$ cũng là một ước lượng, nên nó cũng phải có sai số

Đây là chỗ luận đề của cả quyển này quay lại cắn bạn. $\kappa = 0.4898$ ở trên được tính từ 100 câu. Xấp xỉ sai số chuẩn thường dùng:

$$\mathrm{SE}(\kappa) \approx \sqrt{\frac{p_o(1-p_o)}{n\,(1-p_e)^2}} = \sqrt{\frac{0.75 \times 0.25}{100 \times 0.49^2}} = 0.0884$$

Khoảng tin cậy 95% là $0.49 \pm 1.96 \times 0.0884 = 0.49 \pm 0.173$, tức [0.32, 0.66]. Khoảng này trải từ "tạm được" sang "khá tốt" theo mọi thang quy ước. Nói cách khác, với 100 câu bạn không biết rubric của mình tốt hay xoàng. Muốn nửa khoảng còn một nửa thì cần gấp 4 lần số câu: $n = 400$ cho $\pm 0.087$.

Ba mốc đáng nhớ, tất cả quanh $\kappa = 0.49$: với $n = 78$ câu chồng lấn thì $\mathrm{SE}(\kappa) = 0.1001$, khoảng 95% là [0.29, 0.69] — chỉ đủ phát hiện thảm hoạ ($\kappa$ gần 0); $n = 120$ cho 0.0807 và [0.33, 0.65] — vẫn không phân biệt được 0.4 với 0.6; $n = 400$ cho 0.0442 và [0.40, 0.58] — đủ để nói rubric ở mức trung bình chứ không tốt.

Bẫy thường gặp quanh $\kappa$

  • Đọc thang quy ước như luật. Các mốc kiểu "0.41–0.60 là moderate" là quy ước tiện tay, không phải ngưỡng có cơ sở. Với lớp hiếm, $\kappa = 0.4$ đã là khó; với nhãn nhị phân cân bằng dễ, $\kappa = 0.7$ vẫn là kém.
  • Dùng Cohen $\kappa$ cho nhãn có thứ tự. minor/major/critical là thang thứ bậc: nhầm minor với major nhẹ hơn nhầm minor với critical. Cohen $\kappa$ thường phạt cả hai như nhau. Dùng bản có trọng số (weighted $\kappa$) cho trường hợp này.
  • Ba người chấm trở lên hoặc thiếu ô. Cohen $\kappa$ định nghĩa cho đúng hai người chấm và dữ liệu đầy đủ. Khi có 3+ người, khi mỗi người chấm một tập con khác nhau, hoặc khi thang là liên tục — dùng Krippendorff-$\alpha$, nó nhận mọi tình huống đó bằng một công thức duy nhất. Trên đúng bảng 2×2 ở trên, Krippendorff-$\alpha = 0.4910$ và Scott-$\pi = 0.4885$, tức chênh với Cohen $\kappa = 0.4898$ chưa tới 0.003 — với hai người và dữ liệu đầy đủ, chọn cái nào cũng được, đừng tranh cãi. Chữ $\alpha$ ở đây là tên riêng của hệ số, không phải mức ý nghĩa $\alpha = 0.05$ dùng ở mục 7; $\pi$ cũng vậy, nó không phải hằng số 3.14159.
  • $\kappa$ thấp thì đổ tại người chấm. Chín trên mười lần là tại rubric. Lấy 20 câu hai người bất đồng, ngồi chung, viết thêm quy tắc phân xử, rồi chấm lại từ đầu trên tập khác.
Cohen κ và khoảng tin cậy bootstrap — không thư viện ngoài
import random
from collections import Counter

def cohen_kappa(cap):
    """cap: danh sach cac cap nhan (a, b) cua hai nguoi cham."""
    n = len(cap)
    po = sum(1 for a, b in cap if a == b) / n
    fa = Counter(a for a, _ in cap)
    fb = Counter(b for _, b in cap)
    nhan = set(fa) | set(fb)
    pe = sum((fa[k] / n) * (fb[k] / n) for k in nhan)
    return (po - pe) / (1 - pe)

# dung lai bang cheo 45 / 10 / 15 / 30 o tren
du_lieu = ([("OK", "OK")] * 45 + [("OK", "FIX")] * 10
           + [("FIX", "OK")] * 15 + [("FIX", "FIX")] * 30)

print(round(cohen_kappa(du_lieu), 4))          # 0.4898

# khoang tin cay: bootstrap tren chinh cac cau, dung ky thuat cua Module 03
rng = random.Random(0)
mau = [cohen_kappa(rng.choices(du_lieu, k=len(du_lieu))) for _ in range(2000)]
mau.sort()
print(round(mau[50], 3), round(mau[1949], 3))  # hai duoi 2.5% va 97.5%

Bootstrap ở đây tốt hơn công thức xấp xỉ, vì phân phối của $\kappa$ lệch khi lớp mất cân bằng. Kỹ thuật y hệt Module 03, chỉ đổi thống kê được tính lại trong mỗi vòng.

7. Tiêu ngân sách human eval cho khôn

Human eval đắt theo cách mà metric tự động không bao giờ đắt: chi phí tuyến tính theo số câu, và không tái sử dụng được khi bạn đổi checkpoint. Nên mọi quyết định là quyết định phân bổ.

Trước hết, tính xem bạn cần bao nhiêu

Dùng công thức cỡ mẫu cho một tỉ lệ so với 0.5 (Module 05), với $\alpha = 0.05$ hai phía và power 80%:

$$n = \frac{\left(z_{\alpha/2}\sqrt{0.25} + z_{\beta}\sqrt{p(1-p)}\right)^2}{(p - 0.5)^2}$$
  • Tỉ lệ thắng thật 55% → $n = 783$. Chênh lệch nhỏ nhưng thật. Đây là mức bạn hay gặp sau một lần fine-tune.
  • 60% → $n = 194$. Chênh lệch rõ. Một buổi chiều là xong.
  • 65% → $n = 85$. Nếu bạn chấm hết 85 phán quyết mà vẫn không thắng, chênh lệch không tới 65%.

Chú ý một chỗ dễ hụt ngân sách: công thức trên là công thức cho một tỉ lệ nhị phân, nên $n$ là số phán quyết không hoà, không phải số cặp bạn phải chấm. Nếu tỉ lệ hoà là $t$ thì số cặp phải chấm là $n/(1-t)$ — với $t = 0.40$ thì 783 thành 1305.

Giả sử bạn đã lọc bỏ những câu hai hệ ra bản dịch giống hệt nhau và tỉ lệ hoà còn lại là $t = 0.20$: cần $783/0.8 = 979$ cặp, lấy tròn 980. Thêm 147 cặp chấm lần hai (15%) để đo $\kappa$, thành 1127 phán quyết. Chèn câu mồi theo tỉ lệ 12/80 thì có thêm 169 mục mồi, tổng 1296 mục. Ở 25 giây/mục, đó là 9.0 giờ công, chia thành 15 phiên. Chín giờ để trả lời một câu hỏi mà chrF trả lời trong ba giây — nhưng chrF không trả lời được câu hỏi này.

Bốn cách tiêu tiền thông minh

  • Chỉ chấm câu hai hệ khác nhau. Nếu 40% số câu hai hệ cho ra bản dịch giống hệt nhau, chấm chúng là ném tiền: chúng đóng góp đúng 0 vào hiệu. Lọc trước rồi chấm 300 câu khác biệt thì lượng thông tin tương đương $300/0.6 = 500$ câu lấy ngẫu nhiên. Cảnh báo: bạn đã đổi đại lượng đang ước lượng — nó là "tỉ lệ thắng trong số câu có khác biệt". Muốn quay về mức toàn corpus thì tính hoà bằng nửa điểm: $0.6\,p_{\mathrm{diff}} + 0.4 \times 0.5$. Với $p_{\mathrm{diff}} = 0.60$ thì mức toàn corpus là 0.56 chứ không phải 0.60 — và mốc null vẫn là 0.5, đừng nhân 0.6 rồi so với 0.5, làm thế hệ đang thắng biến thành hệ đang thua.
  • Chấm đơn nhiều câu thắng chấm đôi ít câu. Mô hình: điểm = giá trị thật của câu + nhiễu người, với $\sigma_{\text{item}} = 15$ và $\sigma_{\text{rater}} = 10$. Cùng ngân sách 600 lượt chấm:
    • 300 câu × 2 người: $\mathrm{SE} = \sqrt{(225 + 100/2)/300} = 0.957$
    • 600 câu × 1 người: $\mathrm{SE} = \sqrt{(225 + 100)/600} = 0.736$
    Chấm đơn cho sai số nhỏ hơn 23%. Phản trực giác, nhưng logic đơn giản: nhiễu người trung bình hoá qua nhiều câu cũng tốt như trung bình hoá qua nhiều người, mà nhiễu câu thì chỉ có thêm câu mới giảm được.
  • Vẫn giữ một phần chồng lấn — nhưng vì lý do khác. Chấm đôi không phải để giảm sai số của trung bình, mà để đo được $\kappa$. Với 600 lượt và 15% chồng lấn: 521 câu tổng, trong đó 78 câu chấm đôi, hết 599 lượt. Đừng ảo tưởng: 78 câu chỉ cho $\mathrm{SE}(\kappa) = 0.10$, đủ để phát hiện rubric hỏng, không đủ để khoe rubric tốt.
  • Đừng để dữ liệu chết sau một lần dùng. Mỗi phán quyết người là một dòng dữ liệu vàng: nó là golden set để kiểm định LLM-as-judge (Module 07), là hạt giống cho bộ hồi quy (Module 08), và là căn cứ để chọn metric tự động nào tương quan tốt nhất với người trên miền của bạn (Module 02). Lưu kèm mã câu, mã người chấm, thời điểm, và vị trí trong phiên — thiếu trường cuối là bạn không bao giờ đo được độ trôi mệt mỏi.

Nó ứng dụng vào đâu trong đời thật

Trong dự án dịch web novel zh→vi, kiến thức này đổi ba quyết định cụ thể:

  • Quyết định "distil xong có tốt hơn không". Nếu student và teacher chênh nhau ở mức tỉ lệ thắng 55%, bạn cần 783 phán quyết không hoà — khoảng 980 cặp chấm mù, không phải 50 câu xem qua. Biết trước con số đó giúp bạn hoặc chấp nhận chi 9 giờ, hoặc thừa nhận rằng bạn sẽ không biết câu trả lời và đừng viết vào báo cáo như thể đã biết.
  • Quyết định "DPO-lite có giảm lỗi nặng không". Đúng thứ bạn đang sửa — đảo phủ định, bịa nội dung — là lớp hiếm. DA trung bình sẽ không thấy (một critical chỉ kéo trung bình 200 câu xuống 0.35 điểm, đúng bằng $\mathrm{SE}_d$ của kịch bản đó — $t = 1.0$). Phải dùng MQM và đếm số span critical, hoặc ít nhất là gắn cờ nhị phân rồi báo cáo tỉ lệ, kèm CI.
  • Quyết định "có tin bảng chấm này không". Trước khi tin bất kỳ kết luận nào từ chấm tay, hỏi ba câu: vị trí có được tung đồng xu theo từng cặp không, có câu mồi và ngưỡng huỷ phiên không, và $\kappa$ trên phần chồng lấn là bao nhiêu kèm khoảng tin cậy. Nếu câu trả lời là "90% agreement" mà không có $\kappa$, hãy tự tính — với lớp hiếm, 90% có thể là 0.196.

Tự kiểm tra

1 · Hai người chấm 200 câu, trùng nhau 180 câu. A gắn cờ lỗi 24 câu, B gắn cờ 24 câu. Tính $\kappa$.

Trùng 180/200 nên $p_o = 0.90$. Bất đồng 20 câu; vì hai người cùng gắn cờ 24 câu nên bảng đối xứng: 10 câu A-có-B-không, 10 câu A-không-B-có, còn lại 14 câu cả hai cùng gắn cờ và 166 câu cả hai cùng bỏ qua. Kiểm tra: $166 + 10 + 10 + 14 = 200$ ✓, và $10 + 14 = 24$ ✓.

Tỉ lệ lề: cả hai đều gắn cờ $24/200 = 0.12$, đều bỏ qua $0.88$. Vậy $p_e = 0.88^2 + 0.12^2 = 0.7744 + 0.0144 = 0.7888$, và $\kappa = (0.90 - 0.7888)/(1 - 0.7888) = 0.1112/0.2112 = 0.5265$.

Điểm cần thấy: cùng $p_o = 0.90$ như bảng B trong bài, nhưng ở đây lớp hiếm ít hiếm hơn (12% thay vì 6.7%) và hai người trùng nhau nhiều hơn trên chính lớp đó, nên $\kappa$ nhảy từ 0.196 lên 0.527. Đừng bao giờ so agreement thô giữa hai bảng.

2 · Bạn có 600 lượt chấm. Đồng nghiệp đề xuất 300 câu chấm đôi "cho chắc". Bạn phản đối thế nào bằng số?

Với $\sigma_{\text{item}} = 15$, $\sigma_{\text{rater}} = 10$: 300 câu × 2 người cho $\mathrm{SE} = \sqrt{(225+50)/300} = 0.957$, còn 600 câu × 1 người cho $\sqrt{(225+100)/600} = 0.736$ — nhỏ hơn 23%. Nếu mục tiêu là ước lượng điểm trung bình, chấm đôi toàn bộ là lãng phí.

Nhưng đừng bỏ hẳn chấm đôi: nó là cách duy nhất đo $\kappa$. Phương án tốt: 15% chồng lấn — 521 câu tổng, 78 câu chấm đôi, hết 599 lượt. Kèm cảnh báo trung thực rằng $\mathrm{SE}(\kappa) = 0.10$ ở cỡ đó, tức chỉ đủ để phát hiện rubric hỏng.

3 · Vì sao ngẫu nhiên hoá vị trí trái/phải triệt tiêu thiên vị vị trí chính xác, không cần biết thiên vị mạnh bao nhiêu?

Gọi $q$ là xác suất người chấm chọn bản bên trái khi hai bản thật ra ngang nhau. Nếu A ở bên trái với xác suất 0.5 độc lập với mọi thứ khác, tỉ lệ thắng của A là $0.5q + 0.5(1-q) = 0.5$ với mọi $q$. Tham số $q$ biến mất khỏi biểu thức.

Với $q = 0.6$ và cố định A bên trái, ta có $z = (0.60-0.50)/\sqrt{0.25/300} = 3.464$, $p = 0.000532$ — một "phát hiện" hoàn toàn giả. Với ngẫu nhiên hoá thì tỉ lệ về đúng 0.50. Lưu ý: ngẫu nhiên hoá không làm thiên vị biến mất khỏi từng phán quyết, nó chỉ làm thiên vị không còn tương quan với nhãn hệ thống. Đó là tất cả những gì bạn cần.

4 · Đội bạn chấm MQM và báo "hệ B tốt hơn". Bạn cần hỏi thêm đúng một câu. Câu gì?

"Trọng số critical được chốt trước hay sau khi nhìn bảng đếm?" Với cùng bộ chú thích — A: 30 minor / 10 major / 1 critical, B: 60 minor / 6 major / 0 critical — điểm phạt là 85 vs 90 khi critical = 5 (A thắng), 90 vs 90 khi critical = 10 (hoà), 105 vs 90 khi critical = 25 (B thắng). Thứ hạng lật hai lần mà không một quan sát nào thay đổi.

Câu hỏi phụ đáng hỏi tiếp: bảng đếm span thô là gì? Bảng đếm là dữ liệu, điểm tổng chỉ là một hàm tổng hợp bạn tự chọn — và nó không nên là thứ duy nhất được báo cáo.

5 · Phiên chấm của bạn có 12 câu mồi, ngưỡng huỷ là trượt từ 3 câu trở lên. Một người chấm nghiêm túc trượt mỗi mồi với xác suất 0.05. Bao nhiêu phần trăm phiên tốt bị huỷ oan?

Số câu mồi trượt theo phân phối nhị thức $n = 12$, $p = 0.05$. $P(X \geq 3) = 1 - P(0) - P(1) - P(2) = 1 - 0.5404 - 0.3413 - 0.0988 \approx 0.0196$, tức 2.0%. Chấp nhận được: cứ 50 phiên thì mất oan 1 phiên, tốn thêm 38 phút.

Đổi lại, người bấm bừa (trượt 50% số mồi) bị bắt với xác suất $1 - (1 + 12 + 66)/2^{12} = 1 - 79/4096 = 0.981$, tức 98.1%. Nếu bạn hạ ngưỡng xuống 2 thì báo động giả nhảy lên 11.8% — quá đắt. Ba là điểm cân bằng đúng ở cỡ 12 mồi.

Phần III — Con người & judge · Module 07

07 LLM-as-judge: dùng được, nhưng phải kiểm định

Cần biết trước: Module 06 — rubric, thiết kế phiên chấm mù, Cohen $\kappa$ Dựng nên: một judge có bằng chứng là đáng tin Dẫn tới: Module 08 — golden set và nhiễm dữ liệu

Mục tiêu

  • Tính chi phí thật của judge so với người chấm bằng USD, không bằng cảm giác.
  • Gọi tên bốn thiên vị (bias) của judge, và với mỗi cái có một phép đo cụ thể để phát hiện.
  • Giải thích được vì sao lấy chính teacher đã distil ra student để chấm student là một vòng lặp tự khen, và nó thổi điểm theo hướng nào.
  • Chứng minh bằng số học: nhiễu co lại theo $1/\sqrt{n}$ còn thiên vị thì không — nên bộ eval càng lớn, kết luận sai càng "chắc chắn".
  • Chạy được quy trình kiểm định judge chín bước và ra quyết định nhận/loại bằng ngưỡng $\kappa$ chốt trước.

1. Vì sao judge hấp dẫn — và cái giá thật của nó

Module 06 vừa cho bạn thấy human eval đắt tới mức nào. Judge tự động là câu trả lời hiển nhiên. Hãy tính ra con số thay vì nói "rẻ hơn nhiều".

Giả sử: 500 câu, người chấm mất 45 giây mỗi câu, trả 8 USD/giờ. Judge dùng 1200 token vào và 300 token ra mỗi câu, giá 0.30 USD/1M token vào và 1.50 USD/1M token ra.

  • Người: $500 \times 45 = 22500$ giây $= 6.25$ giờ $\to$ 50.00 USD, và bạn phải chờ hết ngày.
  • Judge: $0.6$M token vào $\times\,0.30 = 0.18$ USD, cộng $0.15$M token ra $\times\,1.50 = 0.225$ USD $\to$ 0.405 USD. Với 8 luồng song song, 4 giây mỗi lần gọi: 250 giây.

Tỉ lệ: rẻ hơn 123 lần ($50 / 0.405 = 123.5$) và nhanh hơn 90 lần ($22500 / 250$). Đó là lý do judge chạy được ở mỗi commit còn người thì không. Nhưng chú ý ngay: con số 123 lần chỉ là chi phí chạy. Chi phí kiểm định judge — mục 6 — vẫn phải trả bằng tiền người, và không được bỏ.

2. Ba dạng judge, và dạng nào mở cửa cho thiên vị nào

  • Pointwise — "cho bản dịch này mấy điểm trên thang 0–100". Một lần gọi mỗi bản, xếp hạng $k$ hệ tốn $O(k)$, so được với kết quả tháng trước. Yếu: thang trôi giữa các phiên, và judge dồn điểm vào một dải hẹp nên chênh lệch nhỏ bị nuốt.
  • Pairwise — "A hay B tốt hơn". Nhiệm vụ dễ hơn cho model nên nhạy hơn với chênh lệch nhỏ, hợp đúng câu hỏi "bản mới có hơn không". Yếu: tốn $O(k^2)$ khi so nhiều hệ, và mở toang cửa cho thiên vị vị trí.
  • Không tham chiếu (QE) — chỉ đưa nguồn và bản dịch. Dùng được trên dữ liệu production không có bản dịch chuẩn. Yếu: judge phải tự đọc hiểu tiếng Trung, và với zh→vi đây đúng là chỗ nó yếu nhất.

Trong dự án dịch web novel, câu hỏi thường trực là "checkpoint mới có hơn checkpoint cũ không" — đó là câu hỏi pairwise. Nên phần còn lại của module tập trung vào pairwise, nơi thiên vị nguy hiểm nhất.

3. Bốn thiên vị đã đo được, không phải bốn nỗi lo

Bốn thứ dưới đây không phải "judge đôi khi thiếu chính xác". Chúng là sai lệch có hướng: lặp lại bao nhiêu lần cũng lệch về cùng một phía. Đó là khác biệt sống còn — xem mục 4.

Thiên vịBiểu hiệnCách phát hiệnCách chữaChi phí chữa
Vị trí Bản đặt ở ô thứ nhất thắng nhiều hơn mức đáng thắng. Đổi chỗ hai bản thì phán quyết đổi theo. Chạy mỗi cặp hai chiều. Ước lượng $\hat{b} = (\hat{p}_1 - \hat{p}_2)/2$. Với thiên vị 8 pp, 76 cặp là đủ để khoảng tin cậy tách khỏi 0 — đây là cận trên tính theo hai tỉ lệ độc lập; thiết kế ghép cặp thật sự chỉ cần khoảng 45 cặp (mục 5), và cả hai con số đều ở power 50%. Hoán vị vị trí rồi lấy trung bình hai chiều — thiên vị triệt tiêu chính xác, không phải gần đúng. Gấp đôi số lần gọi: 0.405 → 0.81 USD/500 câu. Vẫn rẻ hơn người 62 lần.
Độ dài Bản dài hơn, nhiều mệnh đề giải thích hơn, được chấm cao hơn dù không đúng hơn. Lọc ra các cặp mà người chấm là ngang nhau, xem judge chọn bản dài bao nhiêu phần trăm. 62% trên 150 cặp $\to$ thiên vị 12 pp, CI 95% $[54.2\%;\ 69.8\%]$, $p = 0.0025$. Rubric có severity và tính điểm cơ học (mục 5); cấm thưởng cho phần thêm thắt; báo cáo tách theo bin độ dài. Viết lại rubric một lần, cộng một cột số token trong log. Gần như bằng 0 sau lần đầu.
Tự khen (self-preference) Judge chấm cao hơn cho văn bản do chính nó sinh ra, kể cả khi chất lượng ngang nhau. Trộn vào bộ chấm một hệ chính là output của judge, chấm mù. Nếu nó tự xếp mình nhất trong khi golden người xếp nó thứ ba, bạn có bằng chứng. Không bao giờ để judge chấm hệ do chính nó sinh; hoặc lấy hai judge khác họ rồi chỉ tin phần hai bên đồng ý. Tiền của judge thứ hai, cộng một lượt kiểm định lại từ đầu cho judge đó.
Same-teacher Student distil từ teacher $T$; judge cũng là $T$. Điểm student cao bất thường, và cao nhất ở đúng những câu student sao chép lại lỗi của $T$. Đo chrF giữa output từng hệ và output của $T$. Nếu thứ hạng judge trùng khít thứ hạng "độ giống $T$", bạn đang đo độ giống chứ không đo chất lượng. Judge phải khác họ model với teacher. Bắt buộc có golden người cho đúng những câu mà $T$ hay sai. Cao nhất: giữ hai nhà cung cấp model, cộng một golden set người 200–300 câu phải làm mới định kỳ.

Same-teacher: vòng lặp tự khen của chính bạn

Đây là cái bẫy được thiết kế riêng cho tình huống của bạn, nên nói thẳng ra từng bước.

Bạn có một teacher $T$ mạnh. Bạn làm sequence-level KD: cho $T$ dịch cả kho song ngữ, rồi train student $S$ trên chính bản dịch của $T$. Mục tiêu huấn luyện của $S$, viết cho đúng, là tái tạo phân phối đầu ra của $T$ — không phải "dịch hay". Sau đó bạn cần biết $S$ có hơn baseline không, và bạn gọi... $T$ ra chấm.

Vòng đã khép. $S$ được thưởng vì giống $T$; $T$ chấm cao cho thứ giống mình. Cái bạn đo được gọi đúng tên là khoảng cách tới $T$, đội lốt "chất lượng dịch".

Teacher T model lớn, đắt Student S train trên bản dịch của T Bản dịch của S Judge = chính T chấm bản dịch của S Điểm cao giữ checkpoint, đẩy tiếp seq-KD cùng một model — vòng khép lại ở đây Vòng này tối ưu ĐỘ GIỐNG với T, không tối ưu chất lượng dịch. Lỗi hệ thống của T được S học thuộc — rồi chính T chấm những lỗi đó là đúng.
Năm mũi tên đen là quy trình bạn đang chạy — $T \to S \to$ bản dịch $\to$ judge $\to$ điểm cao $\to$ quay lại $S$. Mũi tên đỏ đứt nét là thứ không ai vẽ trong tài liệu thiết kế: hộp đầu và hộp chấm là cùng một model. Mọi vòng lặp cải tiến chạy trên sơ đồ này đều đẩy $S$ về phía $T$, kể cả những chỗ $T$ sai.

Nó thổi điểm theo hướng nào — nói cho chính xác. Ba hướng, không phải một:

  1. Thổi điểm tuyệt đối của $S$ lên. $S$ dùng đúng lối diễn đạt, đúng lối ngắt câu, đúng cách xưng hô mà $T$ ưa. Judge thấy quen, chấm cao.
  2. Thổi mạnh hơn nữa khi so với baseline không distil từ $T$. Giả sử tỉ lệ thắng thật của $S$ trước baseline là 46% — tức $S$ đang thua 4 điểm phần trăm. Thiên vị same-teacher 12 pp đẩy tỉ lệ quan sát lên $46 + 12 = 58\%$. Judge báo cáo "$S$ thắng 58–42", tức thắng 8 pp, trong khi sự thật là thua 4 pp. Đổi dấu. Và chỉ cần 151 cặp là cái tỉ lệ 58% quan sát được đó tách khỏi mốc 50% ở mức "có ý nghĩa thống kê" — đại lượng phải kiểm là độ lệch 8 pp so với 50%, không phải 12 pp, nên con số cỡ mẫu là đúng con số ở mục 4.
  3. Thổi mù đúng chỗ nguy hiểm nhất. $T$ có lỗi hệ thống — rớt Hán-Việt ở một số cấu trúc, dịch tên nhân vật không nhất quán giữa các chương. $S$ học thuộc cả những lỗi đó vì chúng nằm sẵn trong data distil. Khi $T$ chấm, nó không thấy đó là lỗi: đấy là đầu ra bình thường của chính nó. Nghĩa là đúng loại lỗi bạn cần bắt nhất lại là loại judge này mù nhất.

Bẫy thường gặp

  • Nghĩ rằng đổi prompt cho judge là chữa được same-teacher. Không. Thiên vị nằm trong trọng số của $T$, không nằm trong prompt.
  • Nghĩ rằng cho judge xem bản tham chiếu của người là đủ. Bớt được một phần, nhưng khi bản dịch của $S$ và bản tham chiếu diễn đạt khác nhau, $T$ vẫn nghiêng về lối diễn đạt của chính nó.
  • Dùng $T$ chấm để chọn data distil (lọc bản dịch tốt của $T$ để train tiếp). Đây là vòng lặp same-teacher chạy hai lần: bạn lọc data bằng chính cái model sinh ra data. Kết quả là $S$ hội tụ về vùng $T$ tự tin nhất, kể cả vùng $T$ tự tin sai.

4. Thiên vị không co lại theo $n$ — và đó là toàn bộ vấn đề

Nhiễu và thiên vị trông giống nhau trên một lần chạy, nhưng cư xử ngược nhau khi bạn tăng cỡ mẫu. Gọi $h$ là nửa rộng khoảng tin cậy 95% cho một tỉ lệ thắng, lấy trường hợp xấu nhất $p = 0.5$:

$$h = 1.96\sqrt{\frac{0.5 \times 0.5}{n}} = \frac{0.98}{\sqrt{n}}$$

Thiên vị thì không có $n$ trong công thức. Nó là một hằng số. Đặt hai thứ đó cạnh nhau và câu chuyện tự kể.

05101520255010020040080016004000 số cặp n (thang log) nửa rộng CI 95% (điểm phần trăm) dải nhiễu 95% — co theo 1/√n thiên vị vị trí 8 pp — phẳng, không co lại bao giờ chênh lệch thật 3 pp n = 151 n = 1068 917 cỡ mẫu mà judge tuyên bố thắng — bằng rác
Đường đen cong xuống là nhiễu: nó tuân lệnh cỡ mẫu. Hai đường ngang là thiên vị và tín hiệu thật: chúng không quan tâm $n$ là bao nhiêu. Vùng đỏ là dải cỡ mẫu mà thiên vị đã vượt nhiễu còn tín hiệu thật thì chưa — nơi judge nói "có ý nghĩa thống kê" về một thứ không tồn tại.

Điều kiện để một hiệu ứng biên độ $b$ vượt dải nhiễu, tức tuyên bố được "khác 50% một cách có ý nghĩa" ở $\alpha = 0.05$ hai phía:

$$\frac{0.98}{\sqrt{n}} < b \quad\Longleftrightarrow\quad n > \left(\frac{0.98}{b}\right)^{2}$$
  • Thiên vị vị trí $b = 0.08$: $\ (0.98/0.08)^2 = 12.25^2 = 150.06 \to$ $n = 151$ cặp.
  • Chênh lệch thật $b = 0.03$: $\ (0.98/0.03)^2 = 32.67^2 = 1067.1 \to$ $n = 1068$ cặp.

Trả lời câu hỏi của mục này: chỉ cần 151 cặp là thiên vị vị trí 8 pp trở thành "kết quả có ý nghĩa thống kê", trong khi chênh lệch thật 3 pp phải đợi tới 1068 cặp. Tỉ số là $(8/3)^2 = 7.11$ — thiên vị về đích nhanh gấp 7.1 lần sự thật. Giữa 151 và 1068 có 917 cỡ mẫu mà bạn sẽ nhận một câu trả lời tự tin và sai.

Nói cho chặt về chữ "đủ". Ngưỡng $n > (0.98/b)^2$ là chỗ nửa rộng CI vừa bằng hiệu ứng, tức chỗ bạn tuyên bố được đúng một nửa số lần chạy — power 50%, bằng tung đồng xu. Tính lại chính xác: tại $n = 151$ với tỉ lệ thật 58%, xác suất bác bỏ được $H_0$ là $0.502$; tại $n = 1068$ với 53% là $0.500$. Muốn power 80% như quy ước ở Module 05 (dùng $z_{\alpha/2}+z_\beta = 2.8016$ thay cho $1.96$) thì cần 305 cặp cho 8 pp và 2178 cặp cho 3 pp. Mọi con số cỡ mẫu trong module này là ngưỡng dễ dãi nhất — và ngay ở ngưỡng dễ dãi nhất, thiên vị vẫn về đích trước sự thật 7.1 lần.

Cụ thể tại $n = 800$ — cỡ bộ test điển hình: sai số chuẩn là $0.5/\sqrt{800} = 0.0177$, nên thiên vị 8 pp đứng ở $4.53\sigma$ còn chênh lệch thật 3 pp mới ở $1.70\sigma$. Cái rác thì "chắc chắn tuyệt đối", cái thật thì chưa đủ ngưỡng.

Nói bằng lời

Tăng số câu test chỉ chữa được một nửa vấn đề. Nó bóp nhiễu lại, và làm đúng việc đó rất tốt. Nhưng nó khuếch đại tác hại của thiên vị, vì thiên vị được giữ nguyên trong khi thanh chắn "thế nào là đáng tin" thì tụt xuống. Chi 10 lần tiền cho một bộ test lớn hơn mà không kiểm định judge là chi tiền để sai một cách tự tin hơn.

Chạy tay một lần

Bộ 300 cặp. Sự thật (không ai biết): bản mới thắng 53%, tức hơn 3 pp. Judge có thiên vị vị trí 8 pp. Bạn viết script và, như mọi người, luôn đặt bản mới ở ô thứ nhất.

  • Lượt 1 — bản mới đứng trước. Tỉ lệ quan sát $= 0.53 + 0.08 = 0.61 \to$ thắng $183/300$. $\mathrm{SE} = \sqrt{0.61 \times 0.39 / 300} = 0.0282$. $z = (0.61-0.5)/0.0282 = 3.91$, $p = 0.000094$. CI 95% $= [55.5\%;\ 66.5\%]$ — không chạm 50%. Bạn viết vào báo cáo: "bản mới thắng áp đảo, $p < 0.001$".
  • Lượt 2 — đổi chỗ, bản mới đứng sau. $0.53 - 0.08 = 0.45 \to$ thắng $135/300$. $z = -1.74$, $p = 0.082$. Cùng một model, cùng một bộ 300 câu, và bây giờ bản mới đang thua.
  • Trung bình hai chiều: $(0.61 + 0.45)/2 = 0.53$. Đúng bằng sự thật. Thiên vị triệt tiêu sạch.

Còn nếu judge hoàn toàn không thiên vị, 300 cặp cho $z = 1.04$, $p = 0.30$ — chênh lệch thật không nhìn thấy được ở cỡ mẫu này. Nói cách khác: cái $p = 0.000094$ ở lượt 1 toàn bộ là thiên vị.

Bật từng loại thiên vị lên và xem tỉ lệ quyết định sai nhảy thế nào trước khi đọc tiếp.

Phòng thí nghiệm 07

Judge có thiên vị

Bật ba công tắc bias, rồi tích ô hoán vị — chỉ một trong ba khối tan đi
Tỉ lệ bản mới thắng · vạch dọc = CI 95% bootstrap 600 vòng
Ngân sách thiên vị: từ sự thật tới con số judge báo cáo
Sự thật—
Judge đo được—
CI 95% của judge—
Độ lệch judge − sự thật—
p-value hai phía—
Lật khi đổi chỗ—
Quyết định ship—
Phán quyết

—

53%
Không ai trong dự án biết con số này. 53% là chênh lệch thật sự nhỏ — đúng cỡ một lần fine-tune tử tế. Kéo xuống dưới 50% để bản mới thật ra thua.
400
Mỗi cặp là một câu nguồn kèm hai bản dịch. Judge trả lời hú hoạ ở 25% số lượt — nên hỏi lại cùng một cặp có thể ra đáp án khác.
8 pp
Bản mới luôn được script đặt ở ô thứ nhất. Đây là loại bias duy nhất chữa được bằng đại số.
12 pp
Bản mới dài hơn ở 75% số cặp (student distil hay thêm mệnh đề giải thích). Tác động ròng vì thế chỉ bằng một nửa biên độ: 12 × (0.75 − 0.25) = 6 pp.
12 pp
Judge chính là teacher T đã distil ra bản mới. Nó cộng thẳng vào mọi cặp, không cặp nào thoát.
Chấm mỗi cặp hai lần, đổi chỗ A và B, rồi lấy trung bình hai phán quyết. Gấp đôi tiền gọi API. Xem nó xoá được cái gì và không xoá được cái gì.

Điều cần thấy: ① Mặc định ($p$ thật $= 53\%$, $n = 400$, bias 8 / 12 / 12 pp, chấm một chiều): ngân sách cộng lại là $0.53 + 0.08 + 0.06 + 0.12 = 0.79$, và judge đo được đúng 79.5%, CI 95% [75.0, 83.3]. Cột xanh bên cạnh là cùng 400 cặp đó, cùng những lượt hú hoạ đó, chỉ bỏ bias đi: 54.0%, CI [49.0, 58.8], $p = 0.1157$ — không đủ bằng chứng, giữ lại. Một bộ dữ liệu, hai kết luận trái ngược. ② Tắt bias độ dài và tự khen, chỉ để lại bias vị trí 8 pp: judge đo 61.3%, CI [56.3, 66.0] → tuyên bố ship. Bây giờ tích hoán vị vị trí: tụt về 53.3%, CI [49.0, 57.3] → không ship nữa. Máy đếm lật báo 124/400 = 31.0% số cặp đổi phán quyết, trong đó 94 nghiêng về ô thứ nhất và 30 ngược lại; ước lượng McNemar $\hat{b} = (94-30)/(2 \times 400) = \mathbf{0.0800}$ — trúng phóc biên độ bạn vừa đặt. Giữ nguyên hoán vị rồi bật lại hai bias kia: con số vọt lên 72.1% — hoán vị không chạm được vào chúng. ③ Kéo sự thật xuống 46% — bản mới thật ra thua. Judge có đủ ba bias đo 70.8%, CI [66.3, 74.8], p-value tụt xuống dưới $10^{-6}$: ship, và ship một cách rất tự tin. Chuỗi gỡ từng lớp, giữ hoán vị bật: 63.4% (còn độ dài + tự khen, vẫn ship) → tắt tự khen còn 52.0%, CI [47.8, 56.4] (hết ship) → tắt độ dài còn 46.4%, CI [42.1, 50.2]. Chỉ khi sạch cả ba thì con số mới quay về sự thật. ④ Kéo $n$ từ 100 lên 1200 ở cấu hình ③: judge có bias đi 69.0% (CI [60.0, 77.0]) → 70.6% (CI [68.1, 73.2]) — kết luận sai không đổi, chỉ ngày càng chắc chắn. Cùng lúc đó judge sạch đi từ CI [37.0, 57.0] (không kết luận được) tới CI [43.3, 48.6], nằm trọn dưới 50% và phát hiện đúng rằng bản mới thua. Tiền mua thêm câu test chỉ trả công cho bên nào đo đúng. Ở cấu hình mặc định ($n = 400$), judge hô ship với mọi sự thật từ 28.9% trở lên; bật hoán vị thì ngưỡng đó nhích lên 36.1%, còn bỏ sạch bias mới về 54.9% — tức mới thật sự đòi bản mới phải thắng.

5. Năm cách hiệu chỉnh, xếp theo tỉ lệ hiệu quả trên chi phí

  1. Hoán vị vị trí rồi lấy trung bình. Rẻ nhất, hiệu quả nhất, triệt tiêu thiên vị vị trí đúng bằng đại số như phép tính ở trên. Giá: gấp đôi số lần gọi. Phần thưởng kèm theo: tỉ lệ lật — số cặp mà judge đổi phán quyết khi đổi chỗ — là chỉ số sức khoẻ tốt nhất bạn có. Ví dụ trên 300 cặp: 96 cặp lật (32%), trong đó 72 lật theo hướng ủng hộ ô thứ nhất và 24 theo hướng ngược. Ước lượng thiên vị $\hat{b} = (72-24)/(2 \times 300) = 0.08$, $\mathrm{SE} = 0.0157$, CI 95% $= [4.9;\ 11.1]$ pp. Kiểm định McNemar cho $z = 4.90$, $p = 9.6 \times 10^{-7}$: thiên vị có thật, không phải tưởng tượng.
  2. Ép rubric có severity và tính điểm cơ học. Đừng hỏi "bản nào hay hơn". Bắt judge liệt kê lỗi, gán mức critical/major/minor, rồi bạn cộng điểm bằng công thức cố định. Judge còn quyền quyết định về sự kiện, không còn quyền quyết định về thang điểm. Cắt gần hết thiên vị độ dài.
  3. Bắt trích dẫn bằng chứng. Mỗi lỗi phải kèm đoạn nguyên văn trích từ bản dịch. Lỗi không trích được thì không tồn tại. Đây cũng là bộ lọc rác duy nhất bạn kiểm được bằng máy: chuỗi trích dẫn phải là substring của bản dịch, sai thì loại cả phán quyết.
  4. Dùng logprob thay vì chữ. Với pairwise, lấy log-xác suất của token "A" và "B" ở vị trí trả lời rồi so — được một con số liên tục thay vì nhãn nhị phân, nên phương sai nhỏ hơn và phát hiện được cả những cặp judge lưỡng lự. Điều kiện: API phải trả logprob.
  5. Chấm mù nguồn gốc. Không bao giờ để tên hệ thống, tên checkpoint, hay bất cứ dấu vết nào lọt vào prompt. Nghe hiển nhiên, nhưng chuỗi kiểu ckpt_v3_lora_r16 lọt vào field metadata là chuyện xảy ra thật.
Prompt judge pointwise cho zh→vi — rubric severity + bắt buộc trích dẫn
Bạn là giám khảo dịch thuật Trung sang Việt cho tiểu thuyết mạng.
Chấm MỘT bản dịch. Không so sánh với bản nào khác.

[NGUỒN]      {src_zh}
[THAM CHIẾU] {ref_vi}
[BẢN DỊCH]   {hyp_vi}

RUBRIC — xếp mỗi lỗi vào đúng một mức:
  critical : sai nghĩa khiến người đọc hiểu ngược; mất hoặc thêm cả một
             mệnh đề; sai tên nhân vật; sai con số hoặc đơn vị.
  major    : sai nghĩa cục bộ nhưng còn đoán ra; sai xưng hô giữa hai
             nhân vật; rớt Hán-Việt thô ("đích", "liễu", "ngã tại");
             đảo ngược phủ định.
  minor    : đúng nghĩa nhưng vụng: lặp từ, sai dấu câu, trạng ngữ đặt
             sai chỗ, thiếu tự nhiên.

QUY TẮC BẮT BUỘC:
  1. Mỗi lỗi phải chép NGUYÊN VĂN đoạn sai của [BẢN DỊCH] vào trường
     "trich_dan". Không chép được nguyên văn thì lỗi đó không tồn tại.
  2. Không phạt vì diễn đạt khác [THAM CHIẾU]. Chỉ phạt khi sai so với
     [NGUỒN]. [THAM CHIẾU] là một cách dịch đúng, không phải cách duy nhất.
  3. Không thưởng và không phạt vì bản dịch dài hơn hay ngắn hơn.
  4. Không suy đoán model nào sinh ra bản dịch. Không nhắc tới model.
  5. Điểm tính cơ học: bắt đầu 100; trừ 25 mỗi critical, 8 mỗi major,
     2 mỗi minor; sàn 0. Cấm cho điểm khác kết quả của phép trừ này.

TRẢ VỀ đúng một khối JSON, không kèm chữ nào khác:
{
  "loi": [
    {"muc": "major", "trich_dan": "...", "ly_do": "...", "sua_thanh": "..."}
  ],
  "diem": 84,
  "do_tin": "cao"
}
Trường "do_tin" nhận một trong ba giá trị: cao, trung binh, thap.
Ghi "thap" khi [NGUỒN] mơ hồ hoặc thiếu ngữ cảnh chương trước.

Ví dụ trong khối JSON kiểm được bằng tay: một lỗi major và bốn lỗi minor cho $100 - 8 - 4 \times 2 = 84$. Nếu judge trả về điểm không khớp phép trừ, bạn bỏ điểm của nó và tự cộng lại từ danh sách lỗi — đó chính là điểm mạnh của cách tính cơ học.

6. Quy trình kiểm định judge — chín bước, có ngưỡng để loại

Coi judge như một người chấm mới vào nghề. Bạn sẽ không cho người mới ký quyết định ship khi chưa đo đồng thuận của họ; judge cũng vậy. Công thức Cohen $\kappa$ đã có ở Module 06, ở đây chỉ dùng lại.

  1. Chốt quyết định mà judge sẽ phục vụ. "Chọn 1 trong 12 checkpoint" và "gác cổng regression mỗi commit" là hai bài toán khác nhau, ngưỡng chấp nhận khác nhau.
  2. Dựng golden người: 200–300 câu, ít nhất hai người chấm độc lập bằng rubric của Module 06, mù nguồn gốc.
  3. Đo mốc người: tính $\kappa_{hh}$ giữa hai người, trên toàn bộ golden. Giả sử đo được $\kappa_{hh} = 0.68$. Đây là mốc để chia tỉ lệ, không phải một trần tuyệt đối — đọc cảnh báo dưới bảng ngưỡng.
  4. Chốt nhãn tham chiếu từ các câu hai người đồng thuận; câu bất đồng thì người thứ ba xử, hoặc loại và ghi lại đã loại bao nhiêu.
  5. Chạy judge trên chính golden đó, mù, hoán vị vị trí, nhiệt độ 0, ghi lại phiên bản model và toàn văn prompt.
  6. Tính $\kappa_{jh}$ giữa judge và nhãn tham chiếu, kèm khoảng tin cậy bootstrap. Tính thêm một lần nữa trên đúng tập mục của bước 3 — toàn bộ, kể cả câu hai người bất đồng — với nhãn của một người chấm cố định. Chỉ con số thứ hai này mới đặt cạnh $\kappa_{hh}$ được.
  7. Áp ngưỡng (bảng dưới). Đây là bước có quyền nói "không".
  8. Đo từng thiên vị riêng: vị trí bằng thí nghiệm hai chiều (76 cặp cho thiên vị 8 pp theo cận trên không ghép cặp, khoảng 45 nếu tính theo SE ghép cặp ở mục 5 — cả hai ở power 50%, nhân $(2.8016/1.96)^2 = 2.04$ nếu muốn power 80%), độ dài bằng các cặp mà người chấm ngang nhau, tự khen bằng cách trộn output của chính judge vào bộ chấm.
  9. Đóng băng và niêm phong: phiên bản model + prompt + nhiệt độ + seed là một artifact có số hiệu. Đổi một dấu phẩy trong prompt là một judge mới, phải kiểm định lại từ bước 5. Chạy lại toàn bộ quy trình mỗi quý, hoặc ngay khi nhà cung cấp đổi model.
$\kappa_{jh}$ đo đượcKết luậnĐược dùng vào việc gì
$\kappa_{jh} < 0.40$Loại. Không bàn thêm, không tinh chỉnh prompt để cứu.Không việc gì. Xoá khỏi pipeline.
$0.40 \leq \kappa_{jh} < 0.60$Chỉ làm chuông báo.Cảnh báo regression thô ("điểm tụt 20%, vào xem đi"). Cấm dùng để chọn checkpoint hay ký quyết định ship.
$\kappa_{jh} \geq 0.60$ và $\kappa_{jh} \geq 0.80\,\kappa_{hh}$Nhận làm số chính thức.Báo cáo được, nhưng luôn kèm khoảng tin cậy và luôn chạy hoán vị vị trí.
$\kappa_{jh} \geq 0.60$ nhưng $< 0.80\,\kappa_{hh}$Nhận có điều kiện. Judge kém người rõ rệt dù $\kappa$ nghe khá.Theo dõi liên tục thì được; quyết định cuối vẫn phải có người xem 50 câu.

Vì sao cần cả hai vế? Nếu $\kappa_{hh} = 0.85$ mà judge chỉ đạt $\kappa_{jh} = 0.62$, tỉ lệ là $0.62/0.85 = 0.73$ — judge mới với tới 73% trần người, dù $0.62$ nghe rất ổn. Ngược lại nếu chính hai người của bạn chỉ đồng thuận $\kappa_{hh} = 0.55$ thì rubric mới là thứ hỏng, không phải judge, và đòi judge đạt 0.60 là đòi vô lý — quay về Module 06 sửa rubric trước.

Cảnh báo 1 — hai con số tuyệt đối là quy ước. Module 06 đã nói thẳng rằng các mốc kiểu "0.41–0.60 là moderate" là quy ước tiện tay, không phải ngưỡng có cơ sở. Ở đây 0.40 và 0.60 được dùng làm cổng vận hành chỉ vì luôn đi kèm vế thứ hai — tỉ lệ với $\kappa_{hh}$ — và chính vế đó mới là vế có cơ sở. Hai con số tuyệt đối chỉ là sàn tối thiểu.

Cảnh báo 2 — $\kappa_{hh}$ không phải cái trần mà judge không thể vượt. Bước 4 loại hoặc phân xử đúng những câu hai người bất đồng, nên nếu bạn tính $\kappa_{jh}$ trên golden đã lọc đó thì nó đang chấm một tập dễ hơn tập đã sinh ra $\kappa_{hh}$. Mô phỏng 20000 mục, ba nhãn, chạy đúng bước 3 → 4 → 6: người nhiễu 22%, judge nhiễu 35% — judge dở hơn người rõ ràng — vẫn cho $\kappa_{hh} = 0.61$ và $\kappa_{jh} = 0.63$ trên golden đã lọc (còn lại 74% số mục). Judge "vượt trần" trong khi thực chất kém hơn. Tính lại trên toàn bộ mục với nhãn của một người chấm cố định thì $\kappa_{jh}$ tụt về $0.50$, thấp hơn mốc người đúng như phải thế. Tỉ số $\kappa_{jh}/\kappa_{hh}$ chỉ có nghĩa khi hai vế đo trên cùng một tập mục.

Áp bảng ngưỡng lên một judge thật

Golden 200 cặp, ba nhãn: bản A thắng / hoà / bản B thắng.

judge: Ajudge: hoàjudge: Btổng
người: A6214480
người: hoà18301664
người: B5173456
tổng856154200

Bảng này cho $p_o = 0.63$ và $p_e = 13728/40000 = 0.3432$, nên Cohen $\kappa$ không trọng số $= \mathbf{0.437}$ (cách tính từng bước ở Module 06, mục 6). Bootstrap 20000 vòng trên chính 200 mục này cho SD $= 0.051$ và CI 95% $= [0.334;\ 0.535]$.

Nhưng A thắng / hoà / B thắng là thang thứ bậc, đúng cái bẫy Module 06 vừa dựng: nhầm "A thắng" thành "hoà" nhẹ hơn hẳn nhầm "A thắng" thành "B thắng", mà $\kappa$ không trọng số phạt hai lỗi đó như nhau. Ở bảng trên, 65 trong 74 cặp bất đồng chỉ lệch một ô ($14+18+16+17$), chỉ 9 cặp lệch cực đoan ($4+5$). Nên con số đúng để báo cáo là $\kappa$ trọng số tuyến tính — lệch một ô tính nửa lỗi: $p_o^{w} = 0.7925$, $p_e^{w} = 0.5581$, $\kappa_w = \mathbf{0.530}$. Đọc $0.437$ như chặn dưới, $0.530$ như con số chính thức.

Đọc ra quyết định: cả $0.437$ lẫn $0.530$ đều rơi vào ô thứ hai của bảng — chuông báo, không phải trọng tài. So với mốc người $\kappa_{hh} = 0.68$ thì $0.530/0.68 = 78\%$, vẫn dưới vế $0.80$. Hai cách tính cho cùng một quyết định, nhưng nếu bạn lỡ dùng trọng số bình phương thì ra $0.62$ và bảng lật sang ô "nhận" — nên trọng số phải chốt trước khi nhìn số, đúng như Module 06 dặn.

Còn CI rộng $0.20$ thì đừng suy ra "vậy không so được hai judge". Đó là kiểu lập luận "khoảng tin cậy chồng nhau nên không khác nhau" mà Module 04 dành cả module để đập bỏ. Hai judge chấm chung một golden là bài toán ghép cặp: cách đúng là bootstrap lại chính 200 mục đó và lấy CI của hiệu $\kappa_A - \kappa_B$. Với $\mathrm{SE} = 0.051$ mỗi bên và tương quan $\rho = 0.95$ giữa hai judge, $\mathrm{SE}$ của hiệu chỉ còn $0.016$, nên chênh $0.04$ cho $z = 2.48$ — phân biệt được hẳn hoi. Cái CI rộng $0.20$ chỉ nói rằng con số tuyệt đối $0.437$ chưa chắc tới chữ số thứ hai.

7. Ba tình huống KHÔNG được dùng judge

  • Judge chính là thứ bạn đang tối ưu. Nếu bạn chọn checkpoint theo điểm judge, rồi lại báo cáo điểm judge như bằng chứng, đó là Goodhart ở Module 00 đội lốt kỹ thuật. Điểm dùng để chọn và điểm dùng để báo cáo phải đến từ hai nguồn khác nhau.
  • Judge yếu ở chính ngôn ngữ đó. Judge đọc tiếng Trung tàm tạm và viết tiếng Việt tàm tạm thì nó không phát hiện được rớt Hán-Việt hay sai xưng hô — đúng hai lỗi bạn quan tâm nhất. Cách kiểm: chèn vào golden 30 câu có lỗi cấy sẵn thuộc đúng loại đó và đếm judge bắt được bao nhiêu.
  • Lỗi tinh vi, hiếm, và đắt. Một tên nhân vật dịch lệch ở chương 214 so với chương 12 là lỗi giết trải nghiệm đọc, nhưng judge chấm từng câu độc lập không bao giờ thấy nó. Loại lỗi xuyên tài liệu phải bắt bằng luật và bằng người, không bằng judge câu lẻ.

Nó ứng dụng vào đâu trong đời thật

Trong dự án dịch web novel zh→vi, đây là chỗ kiến thức này đổi quyết định:

  • Không dùng teacher đã distil để chấm student. Nếu $T$ là model duy nhất bạn có quyền truy cập, thì kết quả của nó chỉ được dùng làm chuông báo, và mọi quyết định ship phải dựa trên golden người cộng chrF/COMET. Bù lại rẻ: một golden 200 câu chấm hai người tốn 40 USD theo đơn giá ở mục 1 ($200 \times 2 \times 45 = 18000$ giây $= 5$ giờ, nhân 8 USD/giờ) — bằng chi phí chạy judge trên 49 nghìn câu.
  • Đo độ giống teacher như một metric riêng. Ghi chrF giữa output mỗi hệ và output của $T$ vào cùng bảng kết quả. Khi thứ hạng theo điểm judge trùng khít thứ hạng theo độ giống $T$, bạn đã có bằng chứng đang đo nhầm thứ, và có nó trước khi tiêu tiền cho một vòng distil nữa.
  • Luôn chạy hai chiều. Gấp đôi chi phí judge cho 500 câu là 0.405 USD. Không có lý do kỹ thuật nào để tiết kiệm 0.4 USD rồi đổi lấy nguy cơ đọc sai dấu của một chênh lệch.
  • Niêm phong prompt như niêm phong seed. Prompt judge phải nằm trong git cùng commit sinh ra số. So hai kết quả chấm bởi hai phiên bản prompt khác nhau là so hai thước đo khác nhau.

Tự kiểm tra

1 · Judge báo bản mới thắng 61% trên 300 cặp, $p = 0.000094$. Bạn kết luận gì?

Chưa kết luận gì cả, vì chưa biết judge có thiên vị vị trí không. Nếu thiên vị là 8 pp và bản mới luôn đứng trước, thì $61\% = 53\% + 8\%$: chênh lệch thật chỉ 3 pp, và ở $n = 300$ chênh lệch thật ấy cho $z = 1.04$, $p = 0.30$ — tức là không nhìn thấy được. Toàn bộ cái $p$ đẹp đẽ kia là thiên vị. Việc phải làm trước tiên: chạy lại đúng 300 cặp đó với thứ tự đảo, lấy trung bình hai chiều.

2 · Thiên vị 8 pp cần bao nhiêu cặp để "có ý nghĩa"? So với chênh lệch thật 3 pp thì sao?

$n > (0.98/b)^2$. Với $b = 0.08$: $12.25^2 = 150.06 \to 151$ cặp. Với $b = 0.03$: $32.67^2 = 1067.1 \to 1068$ cặp. Tỉ số $(8/3)^2 = 7.11$. Cả hai con số này là ngưỡng power 50% — muốn power 80% thì thành 305 và 2178 cặp, tỉ số vẫn thế. Trong khoảng từ 151 tới 1067 cặp — 917 cỡ mẫu — judge tuyên bố có ý nghĩa thống kê về một hiệu ứng thuần rác, còn hiệu ứng thật thì vẫn im lặng. Tăng $n$ không cứu được: nhiễu co theo $1/\sqrt{n}$, thiên vị thì hằng số.

3 · Vì sao hoán vị vị trí chữa được thiên vị vị trí mà không chữa được same-teacher?

Vì thiên vị vị trí gắn với chỗ ngồi: đổi chỗ thì nó đổi dấu, $(+b) + (-b) = 0$, triệt tiêu bằng đại số — như phép tính $(0.61+0.45)/2 = 0.53$ trong callout chạy tay. Same-teacher gắn với nội dung: bản dịch của student giống teacher ở ô nào cũng vẫn giống teacher. Đổi chỗ chỉ khuân nguyên cái ưu ái đó sang bên kia. Chữa nó cần đổi judge, không phải đổi thứ tự.

4 · Judge đồng ý với người trên 63% số câu. Bạn cho nó ký quyết định ship chứ?

Không. Với phân bố nhãn ở bảng mục 6, đồng thuận do may rủi đã là $p_e = 0.3432$, nên $\kappa = (0.63-0.3432)/(1-0.3432) = 0.437$. Thang A/hoà/B là thang thứ bậc nên con số nên báo cáo là $\kappa$ trọng số tuyến tính, $\kappa_w = 0.530$ — vẫn rơi vào vùng "chỉ làm chuông báo". So với mốc người $\kappa_{hh} = 0.68$ thì tỉ lệ là $0.530/0.68 = 78\%$, chưa qua vế $0.80$. Thêm nữa CI 95% của $\kappa$ là $[0.334;\ 0.535]$, rộng 0.20, nên bản thân con số 0.437 cũng chỉ chính xác tới một chữ số.

5 · Bạn phát hiện 96/300 cặp bị lật khi đổi chỗ, 72 lật về phía ô thứ nhất. Thiên vị bằng bao nhiêu?

$\hat{p}_1 - \hat{p}_2 = (72-24)/300 = 0.16$, nên $\hat{b} = 0.16/2 = 0.08$, tức 8 pp. Sai số chuẩn ghép cặp cho $\hat{b}$ là 0.0157 nên CI 95% $= [4.9;\ 11.1]$ pp, và McNemar cho $z = 4.90$, $p = 9.6\times10^{-7}$. Thiên vị có thật. Lưu ý tỉ lệ lật 32% tự nó chưa phải bằng chứng thiên vị — judge có thể lật đối xứng vì lưỡng lự; cái tố cáo thiên vị là độ lệch 72 so với 24.

Phần IV — Hệ eval trong dự án · Module 08

08 Golden set, nhiễm dữ liệu và bộ hồi quy

Cần biết trước: Module 00 (bốn tầng eval), Module 05 (MDE và cỡ mẫu) Dựng nên: một bộ test đáng tin và sống lâu Dẫn tới: Module 09 — kỷ luật thí nghiệm

Mục tiêu

  • Thiết kế một bộ test bằng ma trận phủ (hiện tượng × độ khó) thay vì lấy ngẫu nhiên, và giải thích được bằng số vì sao lấy ngẫu nhiên làm bạn mù với hiện tượng hiếm.
  • Tính được n-gram overlap và Jaccard bằng tay giữa hai câu, và biết hai đại lượng đó khác nhau chỗ nào.
  • Tính ra con số thổi điểm khi một tỉ lệ $f$ của bộ test bị nhiễm — và so nó với biên độ nhiễu của chính bộ test.
  • Đọc một báo cáo phân tầng và chỉ ra tầng nào tụt trong khi trung bình vẫn đẹp.
  • Viết được một bộ hồi quy chạy mỗi commit: cấu trúc file, ngưỡng, lệnh CI, và tỉ lệ báo động giả của ngưỡng đó.

1. Năm cái kho, và năm mũi tên không được phép tồn tại

Câu "bộ test của tôi là 1000 câu lấy ngẫu nhiên từ corpus" nghe có vẻ khách quan. Thực ra nó nói đúng một điều: bạn chưa quyết định bộ test dùng để trả lời câu hỏi gì.

Một dự án dịch zh→vi nghiêm túc có năm kho dữ liệu tách rời, mỗi kho trả lời một câu hỏi khác nhau. Con số dưới đây là thiết kế mẫu ta sẽ dùng suốt module (giả định, nhưng nhất quán từ đầu tới cuối):

  • train — 10.4M cặp câu. Câu hỏi: model học được gì?
  • dev (validation) — 4 000 câu. Câu hỏi: dừng ở checkpoint nào, chọn siêu tham số nào?
  • test — 600 câu, thiết kế theo ma trận phủ. Câu hỏi: bản này có hơn bản trước không?
  • golden set — 300 câu, có điểm người theo rubric MQM. Câu hỏi: metric tự động có đang nói thật không?
  • bộ hồi quy (regression suite) — 120 câu "không được sai". Câu hỏi: commit vừa rồi có làm hỏng thứ đã từng đúng không?

Năm kho này chỉ có giá trị khi không có dòng dữ liệu nào chảy ngược. Hình dưới vẽ các mũi tên rò rỉ (leakage) đã thật sự xảy ra trong các dự án dịch — bằng nét đứt đỏ.

Corpus song ngữ thô12M cặp, chưa lọc Web crawlpretrain của LLM judge CỔNG KHỬ TRÙNG chuẩn hoá Unicodededup chính xácMinHash + LSH n-gram overlapkiểm tên riêngchạy MỘT chiều train10.4M cặp dev4 000 câu · chọn checkpoint test600 câu · ma trận phủ golden300 câu · điểm người MQM regression120 câu · chạy mỗi commit ①②③④⑤ Năm mũi tên rò rỉ ① train → test: câu train nằm nguyên trong test② test → dev: lấy test ra chọn checkpoint ③ golden → train: nhặt câu golden vào train④ regression → train: dạy model thuộc bài kiểm tra ⑤ crawl → test: test công khai đã nằm trong pretrain Bốn mũi tên đầu do chính bạn tạo ra và chặn được.Mũi tên ⑤ đi vòng qua cổng khử trùng: bạn không kiểm soát corpus pretrain của teacher hay của judge.
Năm kho, một cổng khử trùng chạy một chiều. Mũi tên ④ là loại xảo quyệt nhất: bạn sửa bug bằng cách thêm đúng câu trong bộ hồi quy vào train — lần sau CI xanh, người dùng vẫn gặp lỗi.

Golden set: kho duy nhất không bao giờ được dùng để tối ưu

Bốn kho kia đo model. Golden set đo chính cái thước: 300 câu có điểm người theo rubric MQM (Module 06), dùng để trả lời "chrF, COMET hay judge có đang nói thật không" (Module 02 và Module 07).

Chọn câu nào. Lấy mẫu con phân tầng từ đúng ma trận phủ ở mục 2 — 10 hiện tượng × 30 câu = 300 — để mỗi tầng còn đủ câu mà nói, thay vì bốc 300 câu ngẫu nhiên rồi phát hiện tầng thơ chỉ có một câu.

Vì sao 300 chứ không 100. Việc của golden set là ước lượng tương quan giữa metric và người. Với $r = 0.60$, khoảng tin cậy 95% (Fisher, Module 03) trên 100 câu là $[0.46\,;\,0.71]$ — rộng 0.26, không đủ để nói "COMET bám người tốt hơn chrF". Trên 300 câu nó co còn $[0.52\,;\,0.67]$, rộng 0.15.

Hai luật cứng. Một: golden set không bao giờ tham gia chọn checkpoint, chọn siêu tham số hay chỉnh prompt của judge — nó nằm ngoài mọi vòng lặp tối ưu, vì đó là cái thước dùng để kiểm những thước khác. Hai: chỉ chấm lại khi metric hoặc judge đổi, không chấm lại mỗi commit; điểm người là thứ đắt nhất trong cả hệ eval.

2. Ma trận phủ: thiết kế theo hiện tượng, không theo tần suất

Lấy ngẫu nhiên nghĩa là bạn để tần suất tự nhiên của corpus quyết định bộ test. Với web novel, tần suất tự nhiên rất lệch: thoại ngắn chiếm phần lớn, còn thơ và câu đối thì hiếm.

Giả sử thơ/câu đối chiếm 0.3% corpus. Lấy ngẫu nhiên 600 câu, bạn được $600 \times 0.003 = 1.8$ câu — làm tròn là 2. Mà 2 mới chỉ là kỳ vọng: xác suất bốc trúng không câu thơ nào là $0.997^{600} = 16.5\%$, tức cứ sáu lần dựng bộ test thì một lần tầng đó rỗng sạch.

Với 2 câu và $\sigma_d = 8$ điểm câu, MDE của tầng đó (công thức Module 05) là 15.85 điểm. Con số đó còn là bản lạc quan: công thức dùng $z$ nên giả định $n$ lớn; tính lại bằng $t$ với $df = 1$ thì MDE thật là 79.7 điểm. Nghĩa là: model có thể dịch thơ tệ đi 15 điểm mà bạn vẫn không có quyền nói gì. Bạn không phải "đo kém" — bạn hoàn toàn mù với hiện tượng đó.

Ma trận phủ đảo ngược logic: quyết định trước mỗi hiện tượng cần bao nhiêu câu để nhìn thấy một mức tụt đáng lo, rồi mới đi lấy câu.

Hiện tượng zh→viDễVừaKhóTổngVí dụ ở mức Khó
Xưng hô & ngôi thứ24362080本座 / 在下 / 妾身 trong cùng một đoạn thoại ba người
Tên riêng, danh xưng nhân vật20281260Tên mới xuất hiện lần đầu, không có trong glossary
Cảnh giới & thuật ngữ tu luyện18302270筑基后期圆满 — bốn thành phần ghép liền
Thành ngữ bốn chữ10242660Thành ngữ dùng chệch nghĩa để châm biếm
Chủ ngữ tỉnh lược16261860Ba câu liên tiếp không câu nào có chủ ngữ
Số, đơn vị, thời gian cổ22181050三千六百里 · 半个时辰 · 丈 và 尺 trong một câu
Câu dài nhiều mệnh đề8223060Trên 60 ký tự Hán, bốn mệnh đề, không dấu chấm
Thoại nhiều lượt liên tiếp14201650Năm lượt xen kẽ, người nói chỉ suy ra từ ngữ khí
Thơ, câu đối, chơi chữ4102640Đối bảy chữ, phải giữ vần và số chữ
Chiến đấu & từ tượng thanh20302070Chuỗi bốn hiệu ứng liên tiếp, nhịp nhanh
Tổng15624420060026.0% · 40.7% · 33.3%

Tầng thơ/đối bây giờ có 40 câu, không phải 2. MDE của nó tụt từ 15.85 xuống 3.54 điểm — vẫn thô, nhưng đã đủ nhìn thấy một cú gãy thật sự. Số câu đi từ 2 lên 40, tức 20 lần; MDE cải thiện đúng $\sqrt{20} = 4.47$ lần ($15.85/3.54 = 4.48$, lệch chữ số cuối do làm tròn) — luật căn bậc hai, không hơn.

Bẫy thường gặp

  • Ma trận phủ không phải phân phối thật. Điểm tổng của bộ test này không ước lượng chất lượng bạn giao cho người đọc, vì bạn đã cố tình nhồi câu khó. Muốn ước lượng chất lượng thật thì cần một bộ thứ hai lấy ngẫu nhiên đúng phân phối. Hai bộ, hai mục đích, đừng trộn.
  • Một câu thuộc nhiều hiện tượng. Câu 三千里外,本座已候多时 vừa là số/đơn vị vừa là xưng hô. Gán nhãn nhiều lớp, đừng ép một câu vào một ô — nhưng khi đó tổng các tầng lớn hơn 600 và bạn không được cộng chúng lại.
  • Độ khó do người gán thì trôi. Hai người gán "Khó" khác nhau. Viết tiêu chí số: "Khó = trên 40 ký tự Hán, hoặc chứa ít nhất một mục ngoài glossary, hoặc có ít nhất hai lượt thoại."

3. Nhiễm dữ liệu: đo trùng lặp bằng hai đại lượng

Nhiễm (contamination) là khi câu trong bộ test, hoặc một bản gần giống nó, đã có mặt trong dữ liệu huấn luyện. Model không dịch nữa — nó nhớ lại. Với web novel thì chuyện này gần như chắc chắn xảy ra: cùng một bộ truyện có nhiều bản dịch trôi nổi trên mạng, và bạn cào cả hai.

Muốn dò thì phải định nghĩa "gần giống" bằng số. Hai đại lượng, đừng lẫn.

Jaccard — đo mức giống nhau của hai tập hợp. Với hai tập $A$ và $B$:

$$J(A,B) = \frac{|A \cap B|}{|A \cup B|}$$

n-gram overlap (containment) — đo phần của câu test đã nằm sẵn trong kho train. Gọi $G_n(x)$ là tập n-gram bậc $n$ của chuỗi $x$, và $G_n(S)$ là hợp mọi n-gram của cả kho $S$:

$$\mathrm{ov}_n(t \mid S) = \frac{\bigl|G_n(t) \cap G_n(S)\bigr|}{\bigl|G_n(t)\bigr|}$$

Nói bằng lời

Jaccard hỏi: "hai câu này chia nhau bao nhiêu phần trong tổng số thứ mà cả hai cộng lại có?" — nó đối xứng, đổi chỗ $A$ và $B$ kết quả không đổi.

Overlap hỏi: "bao nhiêu phần trăm câu test đã có sẵn bên train?" — nó không đối xứng, và đó chính là điều bạn cần. Một câu test 4 chữ nằm gọn trong một câu train 13 chữ có overlap bằng 1.0 nhưng Jaccard chỉ $4/13 = 0.31$. Dò nhiễm bằng Jaccard sẽ để lọt đúng loại rò rỉ nguy hiểm nhất: câu ngắn bị nuốt trọn.

Chạy tay một lần

Hai câu, mỗi câu 13 token, tách theo khoảng trắng:

A (train) — Diệp Phàm chậm rãi mở mắt trong đan điền linh khí cuồn cuộn
B (test) — Diệp Phàm thong thả mở mắt trong đan điền linh khí cuồn cuộn

Bước 1 — Jaccard trên token. Cả hai câu đều có 13 token phân biệt. Giao nhau: 11 token (mọi thứ trừ chậm, rãi bên A và thong, thả bên B). Hợp: $13 + 13 - 11 = 15$. Vậy $J = 11/15 = 0.733$.

Bước 2 — đếm 4-gram. Chuỗi 13 token cho $13 - 4 + 1 = 10$ bốn-gram. Của B lần lượt là: Diệp Phàm thong thả · Phàm thong thả mở · thong thả mở mắt · thả mở mắt trong · mở mắt trong đan · mắt trong đan điền · trong đan điền linh · đan điền linh khí · điền linh khí cuồn · linh khí cuồn cuộn.

Bước 3 — so với A. Bốn cái đầu chứa thong hoặc thả nên không thể trùng. Sáu cái còn lại nằm nguyên trong A. Vậy $\mathrm{ov}_4(B \mid A) = 6/10 = 0.600$, và Jaccard trên tập 4-gram là $6/14 = 0.429$.

Bước 4 — đẩy lên bậc 8. Chỉ còn $13 - 8 + 1 = 6$ tám-gram mỗi câu, và chỉ 2 cái trùng: $\mathrm{ov}_8 = 2/6 = 0.333$. Bậc càng cao con số càng tụt, nhưng không phải vì bậc cao phá nhiều n-gram hơn: cả hai bậc đều mất đúng 4 n-gram — những cái chạm vào vị trí 3 hoặc 4. Cái co lại là mẫu số, từ 10 xuống 6.

Kết luận đọc được: $\mathrm{ov}_4 = 0.60$ với một cặp chỉ khác nhau đúng hai token. Nếu bạn đặt ngưỡng cảnh báo ở $\mathrm{ov}_4 \geq 0.50$ thì cặp này bị bắt. Nếu đặt ở $0.80$ thì lọt — và bộ test của bạn có một câu mà model đã thấy 99% nội dung.

Chọn bậc $n$ là chọn đánh đổi. Bậc thấp bắt được cả trùng lặp lỏng nhưng báo động giả tràn lan (mọi câu tiếng Việt đều chia nhau từ trong, của). Bậc cao thì sạch nhưng để lọt bản viết lại. Với zh→vi, một cấu hình chạy được: overlap 8-gram trên token đích làm cờ chính, cộng Jaccard 5-gram ký tự (bỏ khoảng trắng) làm cờ phụ để bắt trường hợp tách từ khác nhau.

Không thể duyệt vét cạn — MinHash và LSH

So 600 câu test với 10.4 triệu câu train theo kiểu tất cả với tất cả là $600 \times 1.04{\cdot}10^7 = 6.24$ tỉ phép so tập hợp. Không chạy nổi mỗi lần bạn đổi dữ liệu.

MinHash nén mỗi tập n-gram thành $k$ số nguyên bằng $k$ hàm băm, rồi ước lượng Jaccard bằng tỉ lệ vị trí khớp nhau:

$$\hat{J} = \frac{1}{k}\,\#\{\,i : h_i(A) = h_i(B)\,\}$$

Đây là trung bình của $k$ phép thử Bernoulli với xác suất đúng bằng $J$, nên sai số chuẩn là $\sqrt{J(1-J)/k}$. Với $J = 0.8$: $k = 128$ cho sai số chuẩn 0.0354 và nửa khoảng 95% là ±0.069; muốn xuống ±0.049 phải trả gấp đôi, $k = 256$. Đừng đặt ngưỡng ở 0.80 rồi tin từng chữ số — bản thân ước lượng đã ±0.07.

LSH banding cắt $k$ chữ ký thành $b$ băng, mỗi băng $r$ dòng ($k = br$), rồi chỉ so những cặp rơi trùng ít nhất một băng. Xác suất một cặp trở thành ứng viên:

$$P_{\mathrm{cand}} = 1 - \left(1 - J^{\,r}\right)^{b}$$
Jaccard thật của cặp câu1.00 00.51.0 ngưỡng thiết kế J = 0.6 b = 32, r = 4 — bắt rộngJ=0.4 → 56.4% · J=0.6 → 98.8%J=0.2 → 5.0% (rác phải lọc lại) b = 16, r = 8 — bắt hẹpJ=0.6 → 23.7% · J=0.8 → 94.7%J=0.2 → 0.004% (gần như sạch rác)
Cùng $k=128$ chữ ký, hai cách cắt băng cho hai đường cong khác hẳn. Dò nhiễm nên chọn đường xanh lục (recall trước, rồi so chính xác lại từng cặp ứng viên) — bỏ sót một câu nhiễm đắt hơn nhiều so với việc so thêm vài nghìn cặp.

Với $b=32$, $r=4$, mỗi câu test chỉ cần 32 lần tra bảng băm. Cả bộ test là $600 \times 32 = 19\,200$ lần tra thay vì 6.24 tỉ phép so — nhanh hơn 325 000 lần về số phép toán cấp cao.

4. Nhiễm 10% thổi điểm lên bao nhiêu?

Đây là chỗ phải tính bằng số, vì trực giác của hầu hết mọi người sai theo hướng đánh giá thấp.

Gọi $f$ là tỉ lệ câu test bị nhiễm, $S_c$ là điểm trung bình trên phần sạch, $S_m$ là điểm trên phần bị nhiễm. Điểm bạn báo cáo là trung bình có trọng số:

$$\tilde{S} = (1-f)\,S_c + f\,S_m \qquad\Longrightarrow\qquad \Delta S = f\,(S_m - S_c)$$

Chạy tay một lần

Vẫn bộ test 600 câu ở mục 2, điểm câu trung bình thật $S_c = 34.0$. Có $f = 10\%$ tức 60 câu nằm nguyên văn trong train, và model nhớ đúng từng chữ nên sentence-BLEU của 60 câu đó là 100.

$\tilde{S} = 0.9 \times 34.0 + 0.1 \times 100 = 30.6 + 10.0 = \mathbf{40.6}$

Kiểm lại bằng cách cộng thô: $(540 \times 34 + 60 \times 100)/600 = (18\,360 + 6\,000)/600 = 24\,360/600 = 40.6$. Khớp.

Độ thổi: $\Delta S = 0.1 \times (100 - 34) = \mathbf{+6.6}$ điểm, tức 19.4% so với điểm thật 34.0.

Đặt cạnh thực tế. Một cải tiến kiến trúc thật sự trong dịch máy hiếm khi cho hơn 0.8 BLEU. Nhiễm 10% cho bạn $6.6/0.8 = \mathbf{8.25}$ lần cái đó — miễn phí, và sai.

Ngưỡng đau. Muốn độ thổi chỉ bằng 1.1 điểm (nửa khoảng tin cậy điển hình của một bộ 500 câu, Module 03), cần $f = 1.1/66 = \mathbf{1.67\%}$. Nói cách khác: chỉ 1.7% nhiễm đã ăn hết toàn bộ sai số của bạn. Đây là chỗ phản trực giác — người ta hay nghĩ "vài phần trăm thì kệ nó".

Nếu nhớ không trọn vẹn. Giả sử model chỉ nhớ lõm bõm, điểm phần nhiễm là 85 chứ không 100: $\Delta S = 0.1 \times (85 - 34) = +5.1$. Vẫn gấp hơn 6 lần một cải tiến thật.

Nếu nhiễm dồn vào một tầng. 60 câu nhiễm rơi hết vào tầng "chiến đấu & tượng thanh" gồm 70 câu: điểm tầng đó thành $(10 \times 34 + 60 \times 100)/70 = 6\,340/70 = \mathbf{90.6}$, trong khi chín tầng kia vẫn 34.0. Một tầng cao hơn phần còn lại 56.6 điểm là dấu hiệu nhiễm rõ hơn bất kỳ máy dò nào.

Kéo thử ở phòng thí nghiệm dưới đây trước khi đọc tiếp: chỉnh ngưỡng overlap, xem bao nhiêu câu bị bắt và điểm bị thổi thay đổi ra sao.

Phòng thí nghiệm 08

Máy dò nhiễm

Kéo bậc n về 4 rồi lên 10 — hai kiểu hỏng ngược chiều nhau
20% · 8/40 câu
Bao nhiêu phần trăm câu test được chép từ kho train rồi chỉnh nhẹ. Các câu nhiễm vào theo thứ tự xen kẽ bốn mức chỉnh sửa, nên mọi tỉ lệ đều có đủ cả bốn: chép nguyên văn, đổi 1 cụm, 2 cụm, 3 cụm.
8
Máy dò tính $\mathrm{ov}_n$ = phần n-gram của câu test đã có sẵn trong kho train. Bậc thấp bắt rộng, bậc cao bắt hẹp.
0.50
Câu nào có $\mathrm{ov}_n$ từ ngưỡng trở lên thì bị nghi là nhiễm và bị loại khỏi bộ test.
Câu bị gắn cờ—
Precision máy dò—
Recall máy dò—
Điểm bạn báo cáo—
Điểm phần sạch—
Điểm phần nhiễm—
Độ thổi—
Sau khi loại câu bị cờ—
Mỗi vạch = một câu test
Quét bậc n, giữ nguyên ngưỡng
Đọc con số

—

Ô này không nằm trong bộ 40 câu ở trên — nó chỉ đem một câu duy nhất đi hỏi kho train. Bấm Chép một câu train để bắt đầu từ overlap 1,000, rồi bấm Đổi một cụm vài lần và nhìn từng bậc n rụng với tốc độ khác hẳn nhau.
Overlap n = 8—
Cùng câu đó, n = 4—
Cùng câu đó, n = 10—
Jaccard token, câu train gần nhất—
Máy dò kết luận—
—
# Câu test Sự thật đã gieo Overlap n = 8 Điểm câu Máy dò phán
——————

Điều cần thấy: ① Mặc định (gieo nhiễm 20% tức 8/40 câu, n = 8, ngưỡng 0,50) máy dò gắn cờ 5 câu: 4 bắt đúng, 1 báo nhầm, precision 0,800 — nhìn rất đẹp. Nhưng recall chỉ 0,500: bốn câu nhiễm vẫn nằm nguyên trong bộ test. Điểm bạn báo cáo 42,43, điểm thật của phần sạch 32,28, độ thổi +10,15; loại hết câu bị cờ rồi vẫn còn 36,72, tức +4,44 — 43,8% độ thổi sống sót qua máy dò. ② Kéo n về 4: gắn cờ 39/40 câu, recall lên tròn 1,000 nhưng precision rơi xuống 0,205 và 31 câu sạch bị xoá oan — bộ test chỉ còn 1 câu sống sót, con số "sau khi loại câu bị cờ" 52,57 tính trên đúng một câu là vô nghĩa. Đó là báo động giả ở dạng thuần khiết nhất. ③ Kéo n lên 10: chỉ còn 4 cờ, precision 0,750, nhưng recall tụt còn 0,375 — 5 câu lọt. Câu #26 chỉ bị đổi đúng một cụm: $\mathrm{ov}_4$ của nó là 0,941 nhưng $\mathrm{ov}_{10}$ chỉ còn 0,182 (2/11), trong khi model vẫn nhớ 90,0% số token và chấm nó 93,4 điểm. ④ Bậc cân bằng nhất là n = 7 — precision 0,750 và recall 0,750, F1 = 0,750; không bậc nào cho cả hai cùng vượt 0,8. Bấm Gieo kho khác vài lần: chiều của hai đường không đổi (kiểm trên 24 kho: precision trung bình đi từ 0,203 ở n = 4 lên 0,882 ở n = 10, recall đi ngược từ 0,995 xuống 0,354). ⑤ Kéo tỉ lệ nhiễm: độ thổi đi +3,02 (5%) → +10,15 (20%) → +21,56 (40%), đúng theo $\Delta S = f\,(S_m - S_c)$ tới chữ số thứ 14. ⑥ Trong ô thử tay: câu train chép nguyên văn có cả bốn bậc tròn 1,000. Bấm Đổi một cụm lần đầu — "chậm rãi" thành "lẳng lặng" — $\mathrm{ov}_4$ còn 0,824 nhưng $\mathrm{ov}_{10}$ đã rụng xuống 0,455. Bấm lần thứ hai — "tối sầm lại" thành "rung lên" — thì $\mathrm{ov}_{10} = 0{,}000$ và $\mathrm{ov}_8 = 0{,}167$, trong khi $\mathrm{ov}_4$ vẫn 0,813. Đổi hai cụm trên câu 19 token là đủ xoá sạch mọi 10-gram mà gần như không đụng tới 4-gram.

5. Phân tầng: vì sao trung bình che mất cú gãy

Đây là kết quả so hai bản trên đúng bộ 600 câu ở mục 2. Giả định $\sigma_d = 8$ điểm câu cho mọi tầng, hiệu ghép cặp $\Delta = B - A$ (số dương nghĩa là B tốt hơn):

TầngnΔ = B − ASE± 95%Kết luận sau Bonferroni (10 tầng, α = 0.005)
Xưng hô & ngôi thứ80+1.800.8941.75Vượt 95% nhưng thua ngưỡng Bonferroni 2.51 — chưa kết luận
Tên riêng, danh xưng60+0.401.0332.02Không kết luận
Cảnh giới & thuật ngữ70+2.600.9561.87Sát ngưỡng Bonferroni 2.68 — nghi ngờ tốt, chưa chốt
Thành ngữ bốn chữ60+0.901.0332.02Không kết luận
Chủ ngữ tỉnh lược60+1.201.0332.02Không kết luận
Số, đơn vị, thời gian50−0.301.1312.22Không kết luận
Câu dài nhiều mệnh đề60+0.701.0332.02Không kết luận
Thoại nhiều lượt50+0.501.1312.22Không kết luận
Thơ, câu đối, chơi chữ40−4.201.2652.48Vượt cả ngưỡng Bonferroni 3.55 — B thật sự tệ hơn
Chiến đấu & tượng thanh70+0.200.9561.87Không kết luận
Tổng thể600+0.620.3270.64Khoảng tin cậy [−0.02; +1.26] chứa số 0 — không kết luận được gì

Đọc bảng này ba lần. Con số tổng thể $+0.62 \pm 0.64$ là con số bạn định đưa vào báo cáo, và nó không nói được gì. Nhưng phía dưới, tầng thơ/đối tụt 4.20 điểm với khoảng tin cậy hoàn toàn nằm dưới 0, sống sót cả sau hiệu chỉnh Bonferroni cho 10 phép so. Trung bình đã hoà tan một cú gãy thật vào chín tầng không có gì.

Vì sao nó hoà tan được: tầng thơ chỉ có 40/600 câu, tức trọng số 6.7%. Cú tụt 4.2 điểm đóng góp $40 \times (-4.2)/600 = -0.28$ vào trung bình — bị nuốt gọn bởi $+0.90$ mà chín tầng kia cộng lại.

Bẫy thường gặp

  • Phân tầng xong là bạn đang so nhiều lần. Mười tầng là mười phép kiểm định cùng trả chung một hoá đơn. Không hiệu chỉnh, mà 10 tầng thật sự bằng nhau hết, thì xác suất có ít nhất một tầng "có ý nghĩa" ở mức 5% là $1 - 0.95^{10} = 40.1\%$. Module 04 đã đưa Bonferroni/Holm — dùng nó, đừng bỏ.
  • Chia tầng sau khi nhìn kết quả là gian lận với chính mình. Ma trận phủ phải được chốt trước khi chạy eval; nếu chia lại tầng cho tới khi thấy chỗ đẹp thì bạn đang đi trong vườn của những lối rẽ (Module 09).
  • Tầng quá nhỏ thì đừng báo cáo p-value, chỉ báo cáo số câu sai kèm ví dụ. Tầng 12 câu không có tư cách kết luận thống kê.

6. Taxonomy lỗi zh→vi và máy dò tự động

Điểm số nói "tệ đi 4.2 điểm". Taxonomy nói "tệ ở đâu". Mỗi loại lỗi cần một máy dò tự động chạy được trên toàn bộ output — không chờ người đọc.

Loại lỗiVí dụ (nguồn → bản sai → bản đúng)Cách phát hiện tự động
Rò Hán-Việt
hanviet-leak
老板娘 → "lão bản nương" → "bà chủ quán"Danh sách đen âm Hán-Việt hiếm; hoặc gắn cờ token có tần suất dưới 1 trên $10^{6}$ trong corpus tiếng Việt hiện đại nhưng khớp một âm Hán-Việt hợp lệ.
Tên riêng sai / trôi
name-drift
叶凡 → "Lá Phàm" (chương 3) rồi "Diệp Phong" (chương 7) → "Diệp Phàm"Glossary ánh xạ 1-1 bắt buộc; quét toàn tập, gắn cờ khi một chuỗi Hán ánh xạ ra hơn một chuỗi đích, hoặc khi đích không nằm trong glossary.
Sai xưng hô / ngôi
pronoun
徒儿,你过来 → "Đồ đệ, bạn qua đây" → "Đồ nhi, con lại đây"Bảng quan hệ nhân vật (sư phụ–đệ tử, huynh–muội) sinh ra tập đại từ hợp lệ; gắn cờ mọi đại từ ngoài tập, và mọi lần xuất hiện của "bạn/tôi" trong thoại cổ phong.
Sai số / đơn vị
number
三千六百里 → "ba trăm sáu mươi dặm" → "ba nghìn sáu trăm dặm"Trích tập số ở cả hai phía bằng regex, chuẩn hoá chữ số Hán 一二三…万 và chữ số tiếng Việt về dạng số; so hai tập, khác nhau là lỗi cứng. Đây là máy dò chính xác nhất trong bảng — gần như không có báo động giả.
Dịch sót
omission
Nguồn 3 mệnh đề, bản dịch chỉ còn 2 — mệnh đề cuối biến mấtTỉ lệ ký tự đích/nguồn. Hiệu chuẩn trên 5 000 cặp tốt cho $\mu = 2.6$, $s = 0.45$; ngưỡng $\mu \pm 3s$ là $[1.25\,;\,3.95]$, cho khoảng 0.27% báo động giả — trên 600 câu là 1.6 câu, đủ rẻ để người xem hết.
Thêm nội dung / ảo giác
addition
Bản dịch mọc thêm một câu mô tả không có trong nguồnCùng máy dò tỉ lệ độ dài (đầu trên). Kết hợp: COMET-QE thấp trong khi chrF vẫn cao là chữ ký điển hình của ảo giác trôi chảy.
Lặp thoái hoá
repeat
"linh khí cuồn cuộn cuồn cuộn cuồn cuộn"Tồn tại một 4-gram xuất hiện từ 3 lần trở lên trong cùng câu; hoặc tỉ lệ token phân biệt trên tổng token dưới 0.5 với câu dài trên 20 token.
Chêm từ hiện đại
register
Cảnh cổ đại: "OK, vậy thì tôi update cho ngươi"Danh sách cấm (OK, email, update, ổn áp…) áp riêng cho tầng cổ phong; gắn cờ theo nhãn thể loại của câu, không áp toàn cục.

Chú ý phân biệt hai loại máy dò. Loại tất định (số/đơn vị, glossary, lặp) cho ra đúng/sai, không cần ngưỡng, dùng làm điều kiện chặn cứng trong CI. Loại thống kê (tỉ lệ độ dài, QE) cho ra một điểm số và bạn phải chọn ngưỡng — mỗi ngưỡng là một đánh đổi báo động giả. Ở $\mu \pm 2s$ tỉ lệ báo động giả là 4.55%, trên 600 câu là 27.3 câu người phải đọc; đẩy lên $\mu \pm 3s$ còn 1.6 câu. Đừng đặt máy dò thống kê làm cổng chặn cứng.

7. Bộ hồi quy chạy mỗi commit

Bộ hồi quy không phải bộ test thu nhỏ. Nó là danh sách những thứ đã từng sai và không được sai lại. Mỗi mục sinh ra từ một bug thật, và mang theo số hiệu bug đó.

tests/regression/zhvi.yaml — 120 mục, mỗi mục một câu "không được sai"
- id: NAME-0007              # id cố định vĩnh viễn, không đổi kể cả khi sửa câu
  src: "叶凡缓缓睁开眼睛。"
  ref: "Diệp Phàm chậm rãi mở mắt."
  tags: [ten-rieng, xung-ho]  # trùng nhãn của ma trận phủ
  bucket: kho                 # de | vua | kho
  origin: "bug #412 — model dịch 叶凡 thành 'Lá Phàm' từ chương 3"
  added: 2025-11-04
  assert:
    - kind: must_contain        # chặn cứng
      value: "Diệp Phàm"
    - kind: must_not_contain
      value: ["Lá Phàm", "Ye Fan"]
    - kind: numbers_match       # tập số nguồn phải bằng tập số đích
    - kind: no_repeat_4gram
    - kind: chrf_at_least       # chặn mềm, có ngưỡng
      value: 55

Phần ngưỡng để riêng, một chỗ duy nhất, có ghi lý do:

tests/regression/gates.yaml
gates:
  hard_assert_pass_rate: 1.00   # 120/120. Một mục hỏng là đỏ CI, không thương lượng.
  chrf_drop_max: 2.5            # so với baseline đang chạy production
  length_ratio_range: [1.25, 3.95]   # mu ± 3s, hiệu chuẩn trên 5 000 cặp
  repeat_4gram_max: 2
decode:                         # cố định để loại phương sai giải mã (Module 03)
  seed: 1234
  beam: 4
  batch: 32
  temperature: 0.0

Vì sao ngưỡng chrF là 2.5 chứ không 1.0. Bộ hồi quy chỉ có 120 câu. Với $\sigma_d = 8$ điểm câu, sai số chuẩn của hiệu là $8/\sqrt{120} = 0.730$. Chú ý cổng này chỉ đỏ khi điểm tụt — trường ở trên tên là chrf_drop_max, không có ngưỡng nào cho chiều tăng — nên mọi xác suất báo động giả dưới đây là một phía.

Đặt ngưỡng 1.0 tức là $1.0/0.730 = 1.37$ lần sai số chuẩn, cho xác suất báo động giả mỗi lần chạy là 8.55%. Với 30 commit một tuần, đó là 2.6 lần đỏ giả mỗi tuần — sau hai tuần cả đội sẽ bỏ qua CI đỏ, và bộ hồi quy chết.

Đặt ở 2.5 tức là 3.42 sai số chuẩn: xác suất báo động giả tụt còn 0.031%, tức khoảng một lần đỏ giả mỗi 108 tuần. Đổi lại, bạn mất khả năng phát hiện những cú tụt nhỏ hơn 2.5 điểm — nhưng đó không phải việc của bộ hồi quy. Việc của nó là phần hard_assert: 120 khẳng định tất định, phương sai bằng 0, không có báo động giả nào cả.

Nói bằng lời

Bộ hồi quy có hai nửa với hai triết lý ngược nhau. Nửa tất định đặt ngưỡng ở mức tuyệt đối (100% phải qua) vì nó không có nhiễu. Nửa thống kê đặt ngưỡng rất lỏng (2.5 điểm) chính vì nó có nhiễu, và một cổng hay kêu oan là một cổng bị tắt. Ngưỡng không phải chọn theo "bao nhiêu thì hợp lý", mà theo "bao nhiêu lần báo động giả mỗi tuần thì đội còn chịu được".

.ci/regression.sh — chạy ở mọi pull request
#!/usr/bin/env bash
set -euo pipefail

# 1. Kiểm tra vệ sinh: bộ hồi quy KHÔNG được rò vào train
python -m eval.leakcheck \
  --suite tests/regression/zhvi.yaml \
  --against data/train.manifest.jsonl \
  --ngram 8 --max-overlap 0.30      # vượt là hỏng build, không phải cảnh báo

# 2. Dịch 120 câu với tham số giải mã đã cố định
python -m eval.regress \
  --suite  tests/regression/zhvi.yaml \
  --gates  tests/regression/gates.yaml \
  --model  ckpt/current --baseline ckpt/production \
  --report out/regress.json

# Mã thoát: 0 = xanh · 1 = hard_assert hỏng · 2 = chrF tụt quá ngưỡng

Chi phí: giả sử CTranslate2 int8 chạy 12 câu mỗi giây trên máy CI, 120 câu mất 10 giây. Ba mươi commit một tuần là 300 giây, tức 5 phút máy mỗi tuần. Đây là thứ rẻ nhất trong toàn bộ hệ eval — không có lý do gì để không có nó.

Bước 1 trong script không phải trang trí. Nó chính là mũi tên ④ trong hình ở mục 1: cách sửa bug tự nhiên nhất là thêm câu bị lỗi vào train, và như thế bạn vừa xoá luôn khả năng của bộ hồi quy trong việc phát hiện bug đó tái phát.

8. Bảo trì: test set cũng già đi

Một bộ test không tự hỏng, nhưng nó bị bào mòn theo ba cách.

Một — bạn nhìn nó quá nhiều lần. Mỗi lần bạn thử một thay đổi và giữ lại nếu điểm test lên, bạn đang chọn cực đại của nhiễu: với bộ 600 câu và $\sigma_d = 8$, thử 10 thay đổi hoàn toàn vô dụng vẫn cho +0.50 điểm ở cái tốt nhất. Con số đó không tồn tại ngoài đời — nó là tiền lãi bạn tự trả cho mình. Module 09 đo đầy đủ cơ chế này.

Hai — miền dữ liệu trôi. Bạn bắt đầu với truyện tu tiên, sáu tháng sau người dùng đọc đô thị và trọng sinh. Bộ test cũ vẫn đo đúng thứ nó đo, chỉ là thứ đó không còn quan trọng.

Ba — làm mới thì mất khả năng so. Nếu bạn thay 20% bộ test mỗi 6 tháng thì sau 4 vòng, tức 2 năm, chỉ còn $0.8^4 = 40.96\%$ câu gốc. Tệ hơn: nếu 20% câu mới khó hơn trung bình 5 điểm, điểm tổng tụt $0.2 \times 5 = 1.0$ điểm — từ 34.0 xuống 33.0 — mà model không hề đổi một tham số nào.

Cách xử lý là tách bộ test làm hai phần với hai luật khác nhau:

  • Phần neo (anchor), 70% — đóng băng, không bao giờ đổi, chỉ dùng để so theo thời gian. Chấp nhận nó sẽ dần bão hoà.
  • Phần luân chuyển, 30% — làm mới mỗi 6 tháng theo phân bố người dùng hiện tại, dùng để đo chất lượng hôm nay.

Khi công bố, luôn báo cáo hai con số riêng và ghi rõ phiên bản bộ test (testset-v3.2, neo v1.0). Một điểm số không kèm phiên bản bộ test cũng vô nghĩa như một điểm số không kèm sai số.

Nó ứng dụng vào đâu trong đời thật

Trong dự án dịch web novel zh→vi, ba quyết định đổi ngay:

  • Trước khi tin bất kỳ con số nào của bản distil. Dữ liệu sequence-level KD sinh ra từ teacher, mà teacher đã dịch cả bộ truyện — trong đó có những câu bạn lấy làm test. Chạy leakcheck overlap 8-gram giữa test và dữ liệu distil trước, rồi mới đọc BLEU. Nếu 10% test dính, con số bạn đang mừng bị thổi tới 6.6 điểm, gấp 8.25 lần một cải tiến kiến trúc thật.
  • Khi DPO-lite cho +0.6 BLEU tổng thể. Đừng ship theo con số tổng. Bẻ ra 10 tầng như bảng mục 5: rất có thể DPO đã kéo lên phần thoại (nơi cặp ưa thích dày) và kéo tụt phần thơ/đối (nơi cặp ưa thích thưa). Tổng thể $+0.62 \pm 0.64$ không cho phép kết luận gì, nhưng $-4.20$ trên 40 câu thơ thì có.
  • Bộ hồi quy 120 câu là thứ đầu tiên nên dựng. Rẻ hơn mọi thứ khác trong module này: 10 giây mỗi lần chạy, 5 phút máy mỗi tuần. Mỗi lần một người đọc báo lỗi "tên nhân vật đổi giữa chương", biến đúng câu đó thành một mục YAML có origin trỏ về báo cáo lỗi. Sau một năm bạn có một tài sản không mua được bằng tiền.

Tự kiểm tra

1 · Bộ test 600 câu, $\sigma_d = 8$. Bạn phát hiện 3% số câu bị nhiễm và model nhớ nguyên văn phần đó (điểm câu 100), điểm sạch 34. Độ thổi là bao nhiêu, và nó lớn hơn nửa khoảng tin cậy 95% của hiệu ghép cặp mấy lần?

Độ thổi $= 0.03 \times (100 - 34) = 0.03 \times 66 = \mathbf{1.98}$ điểm.

Nửa khoảng tin cậy 95% của hiệu ghép cặp trên 600 câu — đúng dải ở dòng cuối bảng mục 5: $1.96 \times 8/\sqrt{600} = 1.96 \times 0.3266 = \mathbf{0.64}$ điểm. Nhớ rằng $\sigma_d$ là độ lệch chuẩn của hiệu giữa hai hệ, nên 0.64 là dải nhiễu của $\Delta$ chứ không phải của một điểm tuyệt đối: dải của điểm tuyệt đối rộng hơn vì $\sigma$ điểm câu lớn hơn $\sigma_d$ (Module 03 dùng $\sigma = 12.6$, cho $1.96 \times 12.6/\sqrt{600} = 1.01$).

Tỉ lệ $1.98/0.64 = \mathbf{3.09}$ lần. Nghĩa là chỉ 3% nhiễm — mức mà hầu hết mọi người coi là không đáng kể — đã tạo ra một tín hiệu giả to gấp ba lần toàn bộ dải nhiễu của phép đo. Bạn sẽ thấy nó "có ý nghĩa thống kê" một cách rất thuyết phục. Độ thổi chỉ triệt tiêu khi cả hai hệ cùng thuộc lòng đúng 3% đó — mà bản distil từ teacher thường là hệ duy nhất chạm phải phần nhiễm.

2 · Câu test t = "linh khí cuồn cuộn" (4 token) nằm gọn trong câu train A 13 token. Tính $\mathrm{ov}_4(t \mid A)$ và $J$ trên tập token. Vì sao dò nhiễm phải dùng cái thứ nhất?

$t$ có $4 - 4 + 1 = 1$ bốn-gram duy nhất, và nó có mặt trong A. Vậy $\mathrm{ov}_4(t \mid A) = 1/1 = \mathbf{1.00}$ — nhiễm hoàn toàn.

Jaccard token: giao $= 4$, hợp $= 13$ (mọi token của $t$ đều thuộc A), nên $J = 4/13 = \mathbf{0.308}$.

Nếu bạn đặt ngưỡng "Jaccard ≥ 0.8 thì báo nhiễm", câu này lọt qua với 0.31 dù model đã thấy nguyên văn nó. Jaccard đối xứng nên bị phạt vì độ dài chênh lệch; containment không đối xứng nên hỏi đúng câu hỏi: phần nào của test đã nằm bên train. Hệ quả thực tế: câu càng ngắn càng dễ lọt máy dò Jaccard, mà câu ngắn lại chính là loại model dễ thuộc lòng nhất.

3 · Bộ hồi quy 120 câu, $\sigma_d = 8$. Đồng nghiệp muốn hạ ngưỡng chrF từ 2.5 xuống 1.5 cho "nhạy hơn". Tính hậu quả với 30 commit mỗi tuần.

Sai số chuẩn: $8/\sqrt{120} = 0.730$. Ngưỡng 1.5 là $1.5/0.730 = 2.054$ lần sai số chuẩn.

Cổng chỉ bắt chiều tụt nên xác suất báo động giả là một phía: $1 - \Phi(2.054) = \mathbf{2.00\%}$. Với 30 commit: $30 \times 0.02 = \mathbf{0.6}$ lần đỏ giả mỗi tuần.

So với ngưỡng 2.5 (0.031% mỗi lần, tức 0.0093 lần mỗi tuần, khoảng một lần mỗi 108 tuần), bạn vừa tăng số lần đỏ giả lên khoảng 65 lần. Câu trả lời đúng không phải "hạ ngưỡng" mà là "tăng $n$": muốn phát hiện 1.5 điểm với cùng tỉ lệ báo động giả, phải tăng số câu, hoặc chuyển cú tụt đó sang loại khẳng định tất định không có nhiễu.

4 · Vì sao bảng ở mục 5 kết luận "chưa chốt" cho tầng Cảnh giới ($\Delta = +2.60$, SE $= 0.956$) dù $2.60 > 1.87$?

Vì bạn đang so 10 tầng cùng lúc, và cái giá của đa so sánh đã tính ở Module 04.

Bonferroni chia $\alpha$ cho 10, còn 0.005, tương ứng $z = 2.807$. Ngưỡng của tầng này thành $2.807 \times 0.956 = \mathbf{2.684}$, mà $2.60 < 2.684$. Chưa qua.

Trong khi đó tầng thơ/đối có ngưỡng Bonferroni $2.807 \times 1.265 = 3.551$ và $|-4.20| > 3.551$ — sống sót. Đây chính là điểm mạnh của phân tầng làm đúng: nó vẫn cho một kết luận chắc chắn ngay cả sau khi trả giá cho đa so sánh.

5 · Bạn thay 25% bộ test mỗi 6 tháng. Sau 2 năm còn bao nhiêu phần trăm câu gốc, và điểm số sẽ dịch bao nhiêu nếu câu mới khó hơn trung bình 4 điểm?

Bốn vòng thay: còn $0.75^4 = 0.3164$, tức 31.6% câu gốc. Sau hai năm, gần 70% bộ test đã khác — mọi so sánh với con số của năm ngoái đều không hợp lệ.

Mỗi vòng thay, điểm dịch $0.25 \times (-4) = -1.0$ điểm chỉ do đổi câu. Đó là lý do phải giữ một phần neo đóng băng: nếu không, mỗi lần làm mới bộ test bạn lại tạo ra một cú "tụt" 1 điểm mà không ai truy được nguyên nhân, và ai đó sẽ dành một tuần đi tìm bug trong model.

Phần IV — Hệ eval trong dự án · Module 09

09 Sổ tay thí nghiệm: đọc số mà không tự lừa mình

Cần biết trước: Module 04 (p-value, đa so sánh) và Module 05 (power, MDE) Dựng nên: quy trình chạy thí nghiệm không tự lừa Dẫn tới: Module 10 — từ số ra quyết định

Mục tiêu

  • Viết được một preregister ba dòng và dán nó vào đầu mỗi run, trước khi bấm train.
  • Đọc đúng bốn dáng điệu của cặp đường loss/eval, và biết dáng nào không chứa thông tin nào cả.
  • Tính bằng số độ thổi của việc chọn checkpoint tốt nhất: eval 20 lần với nhiễu $\sigma = 0.4$ điểm thì con số bạn khoe bị thổi trung bình 0.75 điểm.
  • Trả lời được: nhìn p sau mỗi mốc thay vì một lần thì mức 5% biến thành bao nhiêu (đáp án: 24.9%).
  • Liệt kê được tối thiểu 14 trường phải ghi vào sổ run để ba tháng sau còn tin được kết quả của chính mình.

1. Preregister ba dòng: viết kết luận trước khi có số

Tám module trước dạy bạn đo. Module này nói về kẻ đứng chen giữa phép đo và kết luận: chính bạn.

Không ai gian lận có ý thức. Cái xảy ra tinh vi hơn nhiều. Bạn nhìn số, số hơi xấu, bạn nghĩ ra một lý do chính đáng để nhìn lại theo cách khác — bỏ mấy câu quá dài, đổi sang chrF, lấy checkpoint kế bên. Lần này số đẹp. Bạn dừng. Từng bước một đều hợp lý; sản phẩm cuối là rác.

Thuốc giải rẻ tiền nhất là đăng ký trước (preregistration): viết ra bạn sẽ kết luận gì với kết quả nào, trước khi có kết quả. Ba dòng là đủ.

Mẫu preregister ba dòng — dán vào đầu file run, không sửa sau khi bấm train
GIA THUYET   Them 40k cau song ngu the loai tu tien vao tap train se nang chrF2
             tren tang "tu tien" cua testset-v3, khong lam hong cac tang con lai.

QUYET DINH   Ship neu KTC 95% cua delta chrF2 (paired bootstrap, B=1000) nam tron
             tren +0.30 VA moi tang khac co can duoi KTC khong thap hon -0.40.
             Moi ket qua khac: giu baseline, ghi la that bai, khong chay lai.

KHOA TRUOC   Bo test testset-v3 (600 cau, 10 tang, hash 4f9c...). Metric chinh chrF2,
             ghi kem sacreBLEU signature. Guardrail: ti le sai ten rieng.
             n = 600 co dinh. alpha = 0.05 hai phia. Nhin ket qua DUNG 1 LAN, sau
             khi train xong. Checkpoint = buoc co val-loss thap nhat trong 5 lan eval.
             Ngan sach: 2 nhanh x 6 gio GPU = 12 gio; qua 15 gio thi huy.

Dòng hai là dòng đắt nhất, và cũng là dòng người ta hay bỏ. Nếu bạn không viết ngưỡng trước, bạn sẽ chọn ngưỡng sau khi thấy số. Mục 6 đo giá của thói quen đó: chỉ sáu lối rẽ nhỏ vô hại như vậy đã đẩy tỉ lệ tuyên bố thắng nhầm từ 5.0% lên 17.9%.

Preregister không cấm bạn tò mò

Nó chỉ bắt bạn dán nhãn. Mọi thứ nằm trong ba dòng là khẳng định (confirmatory) — được phép kết luận. Mọi thứ bạn nghĩ ra sau khi nhìn số là thăm dò (exploratory) — chỉ được phép trở thành giả thuyết cho run kế tiếp. Cùng một phép tính, hai giá trị bằng chứng khác hẳn nhau, và cái phân biệt chúng không nằm trong dữ liệu mà nằm ở thứ tự thời gian.

2. Mẫu hai nhánh và chốt chặn ngân sách

Mẫu chuẩn của một thí nghiệm dùng được: hai nhánh (arm) khác nhau đúng một thứ. Cùng seed khởi tạo, cùng thứ tự data, cùng cấu hình decode, cùng quy tắc chọn checkpoint, cùng bộ test, cùng phiên bản thư viện. Đổi một biến, giữ nguyên phần còn lại.

Vì sao khắt khe thế? Giả sử bạn đổi cùng lúc ba thứ (thêm data, tăng LoRA rank, đổi learning rate) và nhánh mới thắng. Có $2^3 = 8$ giả thuyết về nguyên nhân — kể cả giả thuyết "không cái nào cả, chỉ là nhiễu".

Tách chúng ra thì có hai đường. Thiết kế giai thừa (factorial design) $2^3$ = 8 nhánh, 6 giờ mỗi nhánh là 48 giờ GPU: đắt, nhưng nó trả về cả ba hiệu ứng chính lẫn bốn tương tác, và mỗi hiệu ứng chính lấy từ 4 run mỗi mức nên sai số chuẩn nhỏ hơn 2 lần so với so 1-vs-1. Rẻ hơn là OFAT — baseline cộng ba nhánh, 4 run, 24 giờ — nhưng mù hoàn toàn với tương tác. Thứ không được làm là đổi ba biến trong một nhánh rồi kết luận: 6 giờ, và trả lời đúng 0 câu hỏi.

Kèm theo là chốt chặn ngân sách (budget guard) — ba con số viết ra trước khi chạy:

  • Trần giờ GPU. Quá thì hủy, không "chạy nốt xem sao".
  • Trần số lần eval checkpoint, ký hiệu $n$. Đây không phải chuyện tiết kiệm tiền — nó là tham số của một công thức thống kê ở Mục 4: eval 20 checkpoint thổi điểm lên 0.75; eval 5 checkpoint chỉ thổi 0.47.
  • Trần số lần nhìn p. Mặc định là 1. Mục 5 cho biết giá của con số này.

Thêm một mẹo rẻ mà ít người làm: chạy một nhánh đối chứng âm (A/A) — lặp lại chính baseline, chỉ đổi seed, chấm trên đúng bộ câu cũ. Vì hai nhánh dùng chung bộ test nên phần phương sai do chọn mẫu test bị triệt tiêu hoàn toàn trong hiệu — đúng cơ chế ghép cặp ở Module 04. Thứ còn lại là nhiễu run-to-run: seed khởi tạo, thứ tự batch, ngẫu nhiên khi giải mã. Nó độc lập giữa hai nhánh, nên $\operatorname{Var}(d) = 2\sigma^2$.

Đó là đại lượng bạn phải tự đo, và nó là đầu vào của mọi thứ còn lại trong module. Giả sử đo được $\sigma = 0.4$ điểm mỗi nhánh: hiệu hai baseline giống hệt nhau vẫn có kỳ vọng trị tuyệt đối $\mathbb{E}|d| = \sigma\sqrt{2}\cdot\sqrt{2/\pi} = 0.45$ điểm, và xác suất lệch quá 0.6 điểm là 28.9%. 95% các hiệu A/A nằm dưới $1.96\,\sigma\sqrt{2} = 1.11$ điểm — đó mới là sàn đo của bạn, không phải một lần chạy lẻ.

3. Loss và eval không trả lời cùng một câu hỏi

Đường loss trên train trả lời: model có đang khớp thêm dữ liệu train không. Đường eval trên val trả lời: thứ bạn thật sự quan tâm có tăng không. Hai câu hỏi khác nhau thì hai đường được phép mâu thuẫn, và chúng mâu thuẫn theo đúng bốn kiểu.

loss trên train eval trên val (điểm BLEU) ① Khớp tốt eval lên rồi phẳng, loss còn giảm nhẹ → train thêm cũng vô ích. Dừng. ② Overfit kinh điển đỉnh val loss giảm tiếp, eval tụt 1.9 điểm khỏi đỉnh → dừng ở đỉnh — nhưng đỉnh đã bị thổi. ③ Loss giảm, eval đứng loss giảm 3.1, eval nhúc nhích 0.6 → đang học thứ metric không đo. ④ Cả hai đều nhiễu eval nhảy biên độ 5.3 điểm, độ lệch chuẩn 1.3 → chưa đo được gì. Tăng n hoặc lặp seed.
Bốn dáng điệu, cùng một thang dọc cho cả bốn (loss 0.2–4.0 và eval 24–36 điểm) để so được với nhau. Đường sinh bằng mô phỏng có nhiễu; mọi con số trong chú thích lấy thẳng từ dữ liệu vẽ.

① Khớp tốt. Eval leo rồi bão hoà, loss vẫn bò xuống. Đây là dáng bạn muốn thấy, và điều nó nói là: hết rồi. Train thêm không mua được gì; tiền nên chuyển sang data hoặc kiến trúc.

② Overfit kinh điển. Loss train tiếp tục giảm còn eval quay đầu — ở hình trên, tụt 1.9 điểm khỏi đỉnh. Phản xạ đúng là dừng ở đỉnh. Nhưng nhớ kỹ: chính cái đỉnh đó là con số bị thổi nhiều nhất trong cả run, và Mục 4 sẽ đo thổi bao nhiêu.

③ Loss giảm mà eval đứng. Dáng khó chịu nhất vì trông như đang tiến bộ. Loss giảm 3.1 trong khi eval nhúc nhích đúng 0.6 điểm. Model đang khớp thêm thứ gì đó trong train mà metric của bạn không nhìn thấy — hoặc là chi tiết vô nghĩa, hoặc là chất lượng thật mà BLEU mù. Việc phải làm không phải train tiếp, mà là đọc 20 bản dịch bằng mắt.

④ Cả hai đều nhiễu. Eval nhảy biên độ 5.3 điểm với độ lệch chuẩn 1.3. Chú ý: điểm cao nhất rơi vào mốc thứ 2 trên 26 — nếu đỉnh của bạn nằm ở đầu run thì gần như chắc chắn nó là nhiễu. Ở dáng này mọi câu chuyện bạn kể về đường cong đều là bịa; việc phải làm là tăng cỡ bộ val, cố định seed decode, hoặc eval thưa hơn nhưng lặp lại.

Sàn nhiễu của đường eval — tính trước khi diễn giải nó

Sàn nhiễu của một đường eval không phải sai số của một điểm — nó là sai số của chênh lệch giữa hai mốc. Mọi mốc đều chấm trên cùng bộ val, nên thành phần chọn mẫu test là chung: nó dịch cả đường cong lên hoặc xuống cùng một lượng và đóng góp đúng 0 vào gợn sóng. Thứ sinh ra gợn sóng chỉ là nhiễu run-to-run vừa đo ở Mục 2.

Với $\sigma = 0.4$ điểm mỗi mốc, hiệu hai mốc có độ lệch chuẩn $0.4\sqrt{2} = 0.57$. Mọi gợn sóng nhỏ hơn $1.96 \times 0.57 = 1.11$ điểm là không đọc được. Dáng ③ ở trên có biên độ 0.6 — nằm gọn dưới sàn, nên câu đúng là "eval đứng yên", không phải "eval nhích lên". Đừng nhầm con số này với $\sigma_d = 8$ của Module 05: đó là độ lệch chuẩn của hiệu ghép cặp theo câu, dùng cho MDE ở mục sau; còn độ lệch chuẩn điểm câu của một hệ thì cỡ 20.8.

4. Best-checkpoint bias: chọn đỉnh là đã thổi điểm

Đây là chỗ phản trực giác nhất module, nên nói thẳng: chỉ riêng thao tác lấy giá trị lớn nhất trong một dãy số có nhiễu đã tạo ra một khoản thổi dương, ngay cả khi mọi checkpoint tốt như nhau. Bạn không cần overfit gì cả. Bạn chỉ cần chọn max.

Đặt bài toán bằng số. Bạn eval $n$ checkpoint. Chất lượng thật của mọi checkpoint bằng nhau, gọi là $\mu$. Mỗi lần eval cho ra $\hat{\theta}_i = \mu + \varepsilon_i$ với $\varepsilon_i \sim N(0, \sigma^2)$, $\sigma = 0.4$ điểm — đúng con số nhiễu run-to-run đo bằng nhánh A/A ở Mục 2. Bạn báo cáo $\max_i \hat{\theta}_i$. Kỳ vọng của nó là

$$\mathbb{E}\!\left[\max_i \hat{\theta}_i\right] = \mu + \sigma \cdot a_n, \qquad a_n = \mathbb{E}\!\left[\max_i Z_i\right],\; Z_i \sim N(0,1).$$

Hằng số $a_n$ không có dạng đóng gọn với $n$ bất kỳ, nhưng xấp xỉ Blom cho sai số dưới $0.001$ ở $n = 20$:

$$a_n \approx \Phi^{-1}\!\left(\frac{n - 0.375}{n + 0.25}\right), \qquad a_{20} \approx \Phi^{-1}(0.96914) = 1.8682.$$

Nói bằng lời

Lấy cực đại của $n$ con số nhiễu thì trung bình bạn được thưởng thêm $a_n$ lần độ lệch chuẩn. Với $n = 20$ và $\sigma = 0.4$: $1.868 \times 0.4 = \mathbf{0.75}$ điểm miễn phí, không đến từ model. Và vì $a_n$ lớn theo cỡ $\sqrt{2\ln n}$, khoản thưởng này tăng rất chậm — theo căn của log — tăng số lần eval gấp 5 lần (20 → 100) chỉ làm nó tăng 34%, từ 0.75 lên 1.00. Bạn không thoát được nó bằng cách eval ít đi một chút; bạn thoát bằng cách đo lại trên tập khác.

Chạy tay một lần — trường hợp $n = 2$ có đáp án chính xác

Với hai checkpoint, dùng đồng nhất thức $\max(a,b) = \dfrac{a+b}{2} + \dfrac{|a-b|}{2}$.

Vế đầu có kỳ vọng $\mu$. Vế sau: $\varepsilon_1 - \varepsilon_2 \sim N(0, 2\sigma^2)$, mà kỳ vọng trị tuyệt đối của một biến chuẩn trung bình 0 bằng độ lệch chuẩn của nó nhân $\sqrt{2/\pi}$. Ở đây độ lệch chuẩn là $\sigma\sqrt{2}$:

$$\mathbb{E}|\varepsilon_1 - \varepsilon_2| = \sigma\sqrt{2}\cdot\sqrt{\tfrac{2}{\pi}} = \frac{2\sigma}{\sqrt{\pi}} \;\Rightarrow\; \mathbb{E}[\max] = \mu + \frac{\sigma}{\sqrt{\pi}}.$$

Bấm máy: $1/\sqrt{\pi} = 0.5642$, nhân $0.4$ được 0.2257 điểm. Mô phỏng 4 triệu lần cho 0.2255 — khớp tới chữ số thứ ba. Chỉ hai checkpoint đã thổi hơn hai phần mười điểm; đó là cỡ chênh lệch mà nhiều báo cáo nội bộ đang dùng để chọn model.

$n$ checkpoint đã eval$a_n$ (tích phân số)Blom xấp xỉĐộ thổi với $\sigma=0.4$
20.56420.58950.23
30.84630.86940.34
51.16301.17980.47
101.53881.54660.62
201.86751.86820.75
502.24912.24330.90
1002.50762.49861.00

Đọc bảng cho đau: với 500 câu và $\sigma_d = 8$ — độ lệch chuẩn hiệu ghép cặp theo câu, đúng con số Module 05 đo được — MDE ở power 80% là $2.80 \times 8/\sqrt{500} = 1.00$ điểm.

Hai con số dựng từ hai nguồn phương sai khác nhau — một cái là nhiễu giữa các lần eval, một cái là phương sai của hiệu giữa hai hệ — nhưng cùng đơn vị điểm và cùng trả lời một câu: bao nhiêu điểm thì đáng tin. Độ thổi 0.75 bằng 75% của ngưỡng đó; thao tác chọn đỉnh một mình nó đã sinh ra gần trọn cái "cải tiến" mà bộ val của bạn có khả năng nhìn thấy.

Một sắc thái quan trọng, đo bằng mô phỏng 400 nghìn lần với $n = 20$, $\sigma = 0.4$: nếu các checkpoint thật sự khác nhau, độ thổi co lại. Với độ lệch chuẩn của chất lượng thật $\sigma_{\text{true}} = 0.4$ (bằng đúng nhiễu), độ thổi từ 0.75 xuống 0.53; với $\sigma_{\text{true}} = 1.6$ chỉ còn 0.18.

Nhưng đổi lại xuất hiện thiệt hại thứ hai: bạn chọn nhầm checkpoint. Ở $\sigma_{\text{true}} = 0.4$, checkpoint bạn chọn kém checkpoint tốt nhất thật trung bình 0.22 điểm. Nhiễu vừa thổi phồng con số vừa lấy mất của bạn model tốt nhất.

Bốn cách chữa, theo thứ tự nên làm:

  • Chọn trên val, báo cáo trên test. Bộ test chưa từng tham gia chọn lựa nên điểm trên nó không bị thiên vị. Đây là cách khử triệt để mà vẫn giữ được quyền chọn checkpoint theo số, và nó miễn phí.
  • Khoá quy tắc chọn trước. "Checkpoint cuối" hoặc "bước 40k" là quy tắc không nhìn số — độ thổi bằng 0.
  • Giảm $n$. Eval 5 lần thay vì 20 cắt độ thổi 37.7% (0.75 → 0.47).
  • Nếu buộc phải báo điểm val, ghi kèm $n$ và trừ $\sigma a_n$ ra khỏi con số. Đó là lý do sổ run ở Mục 7 bắt buộc lưu $n$.

Kéo thử ở phòng thí nghiệm dưới đây trước khi đọc tiếp: chỉnh tần suất eval, xem đỉnh val và điểm test tương ứng tách nhau ra sao.

Phòng thí nghiệm 09

Bẫy chọn đỉnh

Kéo “eval mỗi bao nhiêu bước” xuống 500: đường đỏ leo, chấm xanh dương đứng yên
Một lần train
Trung bình 200 seed
20 000
Chất lượng thật bão hoà với hằng số thời gian 4000 bước. Train dài hơn chỉ thêm checkpoint nằm trên phần phẳng — thêm vé số, không thêm chất lượng.
1000 · n = 20
Núm quan trọng nhất. Eval dày hơn = rút thăm nhiều lần hơn, mà bạn chỉ giữ lại lần rút cao nhất.
0.40
Bộ val 500 câu với độ lệch chuẩn điểm câu 8 có sàn nhiễu 8/√500 = 0.36. Kéo về 0 để thấy: không nhiễu thì độ thổi bằng đúng 0.00.
Số lần eval n—
Val tại đỉnh—
Test tại chính đỉnh đó—
Độ thổi trung bình—
Test ở checkpoint cuối—
Chất lượng thật bỏ lỡ—
Đang gieo

—

Điều cần thấy: ① Mặc định 20 000 bước, eval mỗi 1000 bước ($n = 20$), $\sigma = 0.4$. Run đang vẽ có đỉnh val ở checkpoint 16/20: val 34.27, nhưng test tại đúng checkpoint đó chỉ 33.69. Trung bình 200 seed: val tại đỉnh 34.48, test tại đỉnh 33.87, độ thổi 0.61 điểm. Chất lượng thật ở bước cuối là 33.96 — đó là con số duy nhất có thật trong cả biểu đồ. ② Kéo eval mỗi bao nhiêu bước từ 5000 xuống 500, tức $n$ đi từ 4 lên 40, không đụng gì khác: val tại đỉnh leo 34.16 → 34.60 (+0.44) trong khi test tại đỉnh đứng nguyên 33.86 → 33.88 (+0.02). Đường quét ở canvas dưới kéo dài chuyện đó: $n = 2$ cho val 34.03 / test 33.84; $n = 40$ cho val 34.60 / test 33.88. Đỏ leo, xanh dương nằm bẹp — toàn bộ phần chênh là ảo. ③ Nhìn hai ô thống kê cuối: test ở checkpoint cuối 33.95 lại cao hơn test tại đỉnh val (33.87). Thao tác chọn đỉnh vừa thổi con số lên 0.61 điểm vừa giao cho bạn model kém hơn — phần “chất lượng thật bỏ lỡ” trung bình 0.09 điểm. Quy tắc “lấy checkpoint cuối” ở đây thắng cả hai mặt. ④ Kéo σ: 0.10 → độ thổi 0.13 · 0.40 → 0.61 · 1.50 → 2.66. Trần lý thuyết là $\sigma \cdot a_{20}$ với $a_{20} = 1.8675$, tức 2.80 ở $\sigma = 1.5$; lab luôn thấp hơn trần vì các checkpoint đầu run có chất lượng thật thấp hẳn nên gần như không bao giờ thắng cuộc rút thăm. Kéo σ về 0: mọi con số về đúng 33.96 và độ thổi thành 0.00. Từ mặc định (0.61), bấm ▶ Chạy 200 lần seed khác nhau năm lần liên tiếp: 0.61 → 0.62 → 0.60 → 0.65 → 0.59 — chính con số trung bình cũng có sai số của nó, $0.468/\sqrt{200} = 0.033$.

5. Peeking: nhìn p nhiều lần thì 5% không còn là 5%

Tình huống quen: bạn đang chấm dần một bộ test, hoặc đang chạy eval trên tập val lớn dần. Cứ thêm 100 câu bạn lại chạy paired test và liếc p. Thấy $p < 0.05$ thì dừng, tuyên bố thắng, đi ăn trưa.

Mô phỏng đúng tình huống đó dưới giả thuyết không: hai hệ thống bằng nhau tuyệt đối, hiệu điểm câu $d_i \sim N(0, 8^2)$, 20 mốc mỗi mốc thêm 100 câu (tối đa 2000 câu), paired t-test hai phía $\alpha = 0.05$ tại mỗi lần nhìn, 400 nghìn lần chạy.

Cách nhìnSố lần nhìn pTỉ lệ tuyên bố thắng nhầm (mô phỏng 400k, ±0.03)Nếu các lần nhìn độc lập: $1-0.95^K$Ngưỡng p phải dùng để giữ 5%
Chỉ nhìn 1 lần, ở n = 200014.97%5.00%0.050
Nhìn mỗi 10 mốc28.26%9.75%0.0297
Nhìn mỗi 5 mốc412.60%18.55%0.0182
Nhìn mỗi 2 mốc1019.37%40.13%0.0106
Nhìn mỗi mốc2024.92%64.15%0.0075

Ba điều đáng nhớ trong bảng này. Thứ nhất, cột "độc lập" là chặn trên chứ không phải đáp án: các lần nhìn dùng dữ liệu chồng lên nhau nên tương quan mạnh, 20 lần nhìn cho 24.9% chứ không phải 64.2%. Thứ hai, một phần tư số run sẽ tuyên bố thắng khi không có gì để thắng. Thứ ba, cột cuối là lối thoát nếu bạn thật sự cần nhìn nhiều lần: hạ ngưỡng xuống 0.0075 thì mức 5% được giữ nguyên — đó là ý tưởng của biên tuần tự kiểu Pocock, và giá phải trả là power thấp hơn.

Dừng sớm còn thổi cả độ lớn hiệu ứng

Trong số các lần tuyên bố thắng nhầm ở chiến lược nhìn mỗi mốc, hiệu chênh lệch trung bình được báo cáo là 0.99 BLEU — toàn bộ là nhiễu. Lý do: bạn chỉ dừng khi số đủ đẹp, mà số đẹp ở $n$ nhỏ đòi hỏi $|\hat{\Delta}|$ lớn. Đúng 20.2% các lần thắng nhầm xảy ra ngay ở lần nhìn đầu tiên ($n = 100$, sai số chuẩn 0.80 điểm), và 43.6% xảy ra khi $n \leq 300$. Những lần nhìn sớm nhất là những lần nguy hiểm nhất — ngược hẳn với cảm giác "cứ liếc thử, sau này còn nhiều dữ liệu mà".

6. Vườn của những lối rẽ

Peeking là bậc tự do theo thời gian. Còn một loại nữa, âm thầm hơn: bậc tự do theo cách phân tích. Bạn không nhìn lại nhiều lần, bạn chỉ nhìn một lần — nhưng theo sáu góc khác nhau, rồi kể lại góc đẹp nhất.

Mô phỏng: một bộ test 500 câu, hiệu điểm câu $d_i \sim N(0, 8^2)$ tức không có chênh lệch thật. Mỗi câu gắn thêm một độ dài ngẫu nhiên 5–60 token và một nhãn domain A/B ngẫu nhiên, cả hai độc lập với điểm. Sáu lối rẽ hoàn toàn "chính đáng": toàn bộ · chỉ câu ngắn (≤ 20 token) · chỉ câu dài · chỉ domain A · chỉ domain B · cắt 2% đuôi hai bên rồi test như thường. 200 nghìn lần chạy.

Tỉ lệ có ít nhất một lối rẽ báo $p < 0.05$ leo lên theo từng lối bạn thử: 4.97% → 8.96% → 11.38% → 14.13% → 16.82% → 17.87%. Sáu lát cắt vô hại biến mức 5% thành gần 18%. Riêng lối rẽ "cắt 2% đuôi" tự nó đã ăn gian: một mình nó cho 7.48%, vì ngưỡng cắt được chọn từ chính dữ liệu.

Con số 6 còn là rất khiêm tốn. Một dự án dịch thật có bao nhiêu lối rẽ? Ba metric (BLEU, chrF, COMET) × bốn quy tắc chọn checkpoint × hai kiểu tokenize khi chấm × năm cách lọc tập test — đó là 120 lối rẽ. Nếu chúng độc lập thì xác suất có ít nhất một lối cho $p < 0.05$ là 99.8%. Chúng không độc lập nên con số thật thấp hơn, nhưng thông điệp không đổi: với đủ lối rẽ, mọi dữ liệu đều biết nói điều bạn muốn nghe.

Cách chữa không phải là tính toán tinh vi hơn. Nó là ba dòng ở Mục 1, cộng thêm một thói quen: báo cáo cả những lối rẽ bạn đã thử và bỏ đi. Một dòng "đã thử thêm lát cắt theo độ dài, không có ý nghĩa" làm người đọc — kể cả chính bạn ba tháng sau — hiệu chỉnh được kỳ vọng.

7. Sổ ghi run: tối thiểu phải lưu gì

Mọi thứ ở trên vô nghĩa nếu ba tháng sau bạn không dựng lại được run. Tiêu chuẩn đúng không phải "tôi nhớ đại khái" mà là: một người khác, chỉ đọc sổ, chạy lại ra cùng con số. Đây là danh sách tối thiểu — 14 trường, tất cả đều là văn bản; dump ra JSON có thụt lề thì một run đầy đủ nặng 1.8 KB, tức 1000 run vẫn chưa tới 2 MB.

NhómTrường bắt buộcBa tháng sau bạn cần nó để làm gì
MãCommit hash của repo train và repo evalDiff được với run kế tiếp; biết bản eval nào đã sinh số này.
Cấu hìnhFile config đã dump ra sau khi merge mọi mặc định, kèm hashLệnh gõ tay không phản ánh giá trị thực tế của các tham số mặc định.
SeedSeed khởi tạo, seed shuffle data, seed decodeKhông có nó thì một cải tiến 0.3 điểm không tách được khỏi sàn A/A 1.11 điểm đo ở Mục 2.
Dữ liệuDanh sách file + số câu sau lọc + hash nội dungBộ data "giống hệt" của tháng sau thường đã khác 3 000 câu trên 10.4M — 0.03%, đủ để hai run hết so được với nhau.
TokenizerFile model, vocab size, hashĐổi tokenizer làm BLEU nhảy tới 8.7 điểm mà không ai để ý — Module 01, mục 8.
Môi trườngPhiên bản framework, CUDA, loại và số GPUKernel khác nhau cho kết quả lệch ở chữ số thập phân thứ nhất.
LệnhDòng lệnh nguyên văn + biến môi trườngTái lập không cần đoán.
PreregisterBa dòng ở Mục 1, đúng bản viết trước khi chạyPhân biệt khẳng định với thăm dò — thứ tự thời gian là bằng chứng.
Bộ testTên, phiên bản, hash, sacreBLEU signatureKhông có signature thì con số không so được với bất cứ gì.
Chọn ckptBước đã chọn, tiêu chí chọn, $n$ = số checkpoint đã evalKhông có $n$ thì không tính lại được độ thổi ở Mục 4. Nó là một số nguyên, tốn 4 byte.
Đường congFile CSV loss/eval thô theo bướcẢnh chụp màn hình không cho bạn tính lại độ lệch chuẩn của eval.
Kết quảĐiểm + khoảng tin cậy + $n$ câu + số lần đã nhìn pCon số trần là tin đồn; ba thứ đi kèm biến nó thành bằng chứng.
Chi phíGiờ GPU, tiền, thời gian tườngĐể tính được cải tiến này đắt bao nhiêu mỗi điểm.
Kết luậnQuyết định đã ra, lý do, người viết, ngàyChống việc sáu tháng sau chạy lại đúng thí nghiệm đã thất bại.

Nó ứng dụng vào đâu trong đời thật

  • Chọn checkpoint cho model dịch zh→vi. Nếu bạn eval 20 checkpoint trên bộ val 500 câu rồi báo cáo đỉnh, con số đó thổi khoảng 0.75 BLEU — trong khi chính bộ val đó chỉ nhìn thấy được chênh lệch từ 1.00 trở lên. Cách sửa tốn 0 đồng: giữ nguyên việc chọn đỉnh trên val, nhưng báo cáo trên bộ test riêng chưa từng dùng để chọn checkpoint.
  • Đánh giá một mẻ distillation hoặc DPO. Đây đúng là chỗ dễ sinh nhiều nhánh nhất: nhiều nhiệt độ, nhiều hệ số beta, nhiều teacher. Mỗi cấu hình bạn thử là một vé số. Preregister dòng hai — "ship nếu KTC 95% nằm trọn trên +0.30" — biến một loạt vé số thành một quyết định.
  • Chấm người theo đợt. Chấm 2000 câu tốn tiền nên ai cũng muốn liếc p ở mốc 300 câu. Bảng Mục 5 nói giá của thói quen đó: nhìn mỗi 100 câu đẩy 5% lên 24.9%, và 43.6% các lần thắng nhầm rơi vào lúc mới chấm được 300 câu. Nếu buộc phải liếc, dùng ngưỡng 0.0075.
  • Đọc đường cong lúc 2 giờ sáng. Trước khi kể chuyện về một đường eval, đo nhiễu run-to-run $\sigma$ bằng một nhánh A/A rồi lấy $1.96\,\sigma\sqrt{2}$ làm sàn. Với $\sigma = 0.4$ thì sàn là 1.11 điểm; mọi gợn sóng dưới ngưỡng đó không được phép có lời giải thích.

Tự kiểm tra

1 · Nhánh A/A cho bạn nhiễu run-to-run $\sigma = 0.4$ điểm. Bạn eval 10 checkpoint trên bộ val rồi báo cáo điểm của checkpoint cao nhất. Con số đó bị thổi khoảng bao nhiêu?

Tra bảng Mục 4: $a_{10} = 1.5388$. Độ thổi $= 1.5388 \times 0.4 = \mathbf{0.62}$ điểm. Nếu bạn khoe "bản mới hơn baseline 0.6 BLEU" mà baseline được đo bằng một checkpoint cố định còn bản mới là đỉnh của 10 lần eval, thì toàn bộ chênh lệch đó là thủ tục chọn max, không phải model.

2 · Loss train giảm đều, còn BLEU trên val ba lần eval liên tiếp là 33.9 · 34.2 · 33.8. Đồng nghiệp kết luận "đang bắt đầu overfit, dừng ở 34.2 đi". Bạn trả lời thế nào?

Biên độ dao động là $34.2 - 33.8 = 0.4$ điểm. Với nhiễu mỗi mốc $\sigma = 0.4$, hiệu hai mốc có độ lệch chuẩn 0.57, nên vùng không đọc được là $\pm 1.11$. Dao động 0.4 nằm gọn dưới sàn — đây là dáng ③ (eval đứng), không phải dáng ② (overfit). Hai việc cần làm: (a) không kết luận gì, chạy thêm vài mốc; (b) nếu vẫn muốn chọn 34.2 thì phải nhớ đó là max của 3 lần, thổi $a_3 \times \sigma = 0.846 \times 0.4 = 0.34$ điểm — tức chất lượng thật của nó gần 33.9 hơn là 34.2.

3 · Bạn chấm người theo đợt 100 câu, tối đa 2000 câu, chạy paired test và nhìn p sau mỗi đợt, dừng khi $p < 0.05$. Hai hệ thống thật ra bằng nhau. Xác suất bạn tuyên bố thắng là bao nhiêu, và sửa thế nào?

24.9% — một phần tư số lần. Không phải 64.2% ($=1-0.95^{20}$) vì các lần nhìn dùng dữ liệu chồng nhau nên tương quan mạnh. Hai cách sửa: (a) cố định $n$ trước, nhìn đúng một lần ở cuối — khi đó mức đúng bằng 5.00% theo định nghĩa (mô phỏng cho 4.97%, sai số Monte Carlo ±0.03); (b) nếu cần nhìn 20 lần thì hạ ngưỡng xuống 0.0075 — mô phỏng cho thấy đúng mức 5% được giữ. Đổi lại, cách (b) làm power giảm, nên (a) vẫn tốt hơn khi bạn kiểm soát được ngân sách chấm.

4 · Vì sao chọn checkpoint theo val rồi báo cáo điểm trên chính val đó là sai, còn chọn theo val rồi báo cáo trên test lại ổn — dù cả hai đều dùng cùng một checkpoint?

Vì thiên vị không nằm ở checkpoint, nó nằm ở con số. Điểm val của checkpoint được chọn đúng bằng $\max_i \hat{\theta}_i$, mà kỳ vọng của cực đại lớn hơn giá trị thật $\sigma a_n$ (với $n=20$, $\sigma=0.4$: 0.75 điểm). Điểm test thì không tham gia vào phép chọn, nên nhiễu của nó độc lập với việc checkpoint nào thắng — kỳ vọng của nó bằng đúng chất lượng thật, thổi bằng 0. Hệ quả thực hành: bộ dùng để chọn và bộ dùng để báo cáo phải là hai bộ khác nhau, và bộ báo cáo chỉ được đụng vào một lần.

5 · Hai run baseline hoàn toàn giống nhau, chỉ khác seed, cho 33.4 và 34.0. Có nên đi tìm nguyên nhân của 0.6 điểm đó không?

Không. Hai run chấm trên cùng bộ câu nên phần chọn mẫu test đã triệt tiêu; cái còn lại là nhiễu seed, độc lập giữa hai run, nên hiệu có phân phối $N(0,\, 2\sigma^2)$ với độ lệch chuẩn $0.4\sqrt{2} = 0.566$. Kỳ vọng trị tuyệt đối của hiệu là $0.566 \times \sqrt{2/\pi} = \mathbf{0.45}$ điểm, và xác suất lệch quá 0.6 điểm là 28.9% — gần một phần ba số lần. Giá trị của nhánh A/A chính là ở đây: nó đo sàn đo của bạn bằng thực nghiệm. Đừng chốt sàn bằng đúng một con số 0.6: lặp nhánh A/A vài lần, lấy $1.96\,\sigma\sqrt{2} = 1.11$ điểm làm sàn, và mọi "cải tiến" dưới ngưỡng đó phải ngừng được coi là cải tiến.

Phần IV — Hệ eval trong dự án · Module 10

10 Từ số ra quyết định: ship hay không ship

Cần biết trước: cả bốn phần trước — metric (m01–m02), khoảng tin cậy (m03), so hai hệ thống (m04), power (m05), người và judge (m06–m07), bộ test (m08), kỷ luật thí nghiệm (m09) Dựng nên: quyết định cuối cùng, viết ra được bằng số Dẫn tới: Sổ tay thuật ngữ

Mục tiêu

  • Viết được một bảng quyết định sáu ô cho dự án của bạn, mỗi ô có điều kiện số kiểm được, không ô nào phụ thuộc vào cảm giác.
  • Chọn đúng một metric chủ và một bộ guardrail, và với mỗi ngưỡng, đối chiếu nó với nền nhiễu của chính phép đo đó.
  • Tính được lưu lượng cần cho A/B so với interleaving, và giải thích được vì sao tỉ số đó bằng $2/(1-\rho)$.
  • Chạy được checklist 14 điểm trước khi đưa bất kỳ con số nào ra khỏi máy bạn.
  • Biết eval ngốn bao nhiêu phần trăm ngân sách — và vì sao con số đó lớn hơn bạn tưởng rất nhiều.

1. Quyết định là hàm của khoảng tin cậy, không phải của điểm số

Chín module trước dạy bạn đo. Module này dạy bạn dừng đo và làm một việc. Điểm mấu chốt: đầu vào của quyết định không phải con số $\hat{\Delta}$, mà là cả khoảng tin cậy của nó đặt cạnh một ngưỡng thực dụng (practical threshold) $\delta^{*}$ mà bạn đã cam kết trước khi nhìn kết quả.

Đặt bối cảnh số một lần, dùng cho cả module. Giả sử: bộ test phân tầng $n = 1200$ câu zh→vi — đã nới từ 600 câu của m08, đúng vì lý do ở ô CHƯA KẾT LUẬN ĐƯỢC bên dưới; metric chủ là COMET có tham chiếu; độ lệch chuẩn của hiệu điểm câu giữa hai hệ thống đo được là $\sigma_d = 0.055$.

Khi đó sai số chuẩn của hiệu là $0.055/\sqrt{1200} = 0.00159$, nửa rộng khoảng tin cậy 95% là $1.96 \times 0.00159 = 0.00311$, và chênh lệch nhỏ nhất bộ test nhìn thấy được ở power 80% là $(1.96+0.84)\times 0.055/\sqrt{1200} = 0.00445$ (m05). Ngưỡng thực dụng phải nằm trên nền đó: chọn $\delta^{*} = +0.005$.

Cùng một trục, năm khoảng tin cậy — năm ô quyết định Mỗi thanh là khoảng tin cậy 95% ghép cặp của hiệu metric chủ; chấm là điểm ước lượng. 0δ* = +0.005 SHIPΔ = +0.0100 · CI [+0.0069, +0.0131] — cả khoảng trên δ* SHIP CÓ ĐIỀU KIỆNΔ = +0.0071 · CI [+0.0040, +0.0102] — trên 0, chạm δ* GIỮ LẠIΔ = +0.0018 · CI [−0.0013, +0.0049] — chứa 0, cả khoảng dưới δ* CHƯA KẾT LUẬN ĐƯỢCn = 300 · nửa rộng 0.0062 > δ* — đo lại, đừng đoán HỒI QUYΔ = −0.0055 · CI [−0.0086, −0.0024] — cả khoảng dưới 0 −0.010−0.0050+0.005+0.010+0.015 hiệu metric chủ (điểm COMET)
Hàng thứ tư mới là hàng đáng sợ. Điểm ước lượng +0.0040 của nó lớn hơn hàng thứ ba (+0.0018), nhưng nó đo trên 300 câu nên khoảng rộng gấp đôi. Nếu bạn quyết định bằng điểm số, hàng 4 thắng hàng 3. Nếu bạn quyết định bằng khoảng, hàng 4 thậm chí chưa được vào bảng.
Ô quyết địnhĐiều kiện số — phải thoả tất cảHành động cụ thể
SHIP① CI 95% ghép cặp của $\Delta$ metric chủ nằm hoàn toàn trên $\delta^{*} = +0.005$; ② 6/6 guardrail đạt (bảng mục 2); ③ bộ hồi quy 120 câu: 120/120 đúng; ④ p95 latency $\leq 1.15\times$ bản đang chạy; ⑤ kết luận này trùng với bản preregister ba dòng viết trước khi train (m09).Canary 5% → cổng 1000 đoạn (2.5 ngày ở lưu lượng 8000 đoạn/ngày) → 25% → cổng 5000 đoạn → 100%.
SHIP CÓ ĐIỀU KIỆN① CI nằm hoàn toàn trên 0 nhưng cận dưới rơi vào khoảng $(0,\ \delta^{*})$ — ví dụ $[+0.0040,\ +0.0102]$; ② nhiều nhất 1 guardrail ở vùng xám (đạt nhưng cách biên dưới $0.5$ sai số chuẩn); ③ 120/120 bộ hồi quy.Dừng canary ở 25%. Chạy một vòng interleaving 99 phiên (khoảng 6 giờ, mục 3). Chỉ lên 100% khi hiệu online cùng dấu với hiệu offline.
GIỮ LẠI① CI chứa 0 và cận trên của CI nằm dưới $\delta^{*}$ — ví dụ $[-0.0013,\ +0.0049]$, tức thí nghiệm đã loại trừ được cả khả năng "hơn một cách thực dụng"; hoặc ② $\Delta$ dương nhưng có $\geq 1$ guardrail trượt.Giữ bản cũ, ghi vào sổ run. Ngoại lệ duy nhất: bản mới nhỏ hơn $\geq 30\%$ hoặc p95 thấp hơn $\geq 20\%$ và cận dưới CI của $\Delta$ vẫn $> -0.005$ — khi đó ship vì rẻ, không vì tốt.
CHƯA KẾT LUẬN ĐƯỢCNửa rộng CI 95% $> \delta^{*}$, bất kể dấu của $\Delta$. Với $\sigma_d = 0.055$ và $n = 300$, nửa rộng là $0.0062 > 0.005$ — ô này đúng, dù bạn có thích con số $+0.0040$ đến mấy.Nới bộ test tới $n \geq \left(2.8016 \times 0.055 / 0.005\right)^2 = 950$ câu — mức 465 câu chỉ đủ để nửa rộng CI bằng $\delta^{*}$, tức power 50% chứ không phải 80%. Hoặc giảm $\sigma_d$ (ghép cặp, chung seed decode, phân tầng — m05). Không được đọc lại kết quả cũ thêm lần nào.
THÊM DATA① $\Delta$ tổng thể dưới $\delta^{*}$; ② báo cáo phân tầng cho thấy có tầng chiếm $\leq 15\%$ số câu nhưng gánh $\geq 35\%$ số lỗi critical; ③ bước nhân đôi dữ liệu gần nhất vẫn còn cho $\geq +0.6$ BLEU với CI loại trừ 0 — tức đường cong dữ liệu chưa phẳng.Thu thêm dữ liệu đúng tầng đó, không thu tràn lan. Đặt trước mục tiêu: bao nhiêu câu, kỳ vọng bao nhiêu điểm, đo lại bằng đúng bộ test cũ.
ĐỔI KIẾN TRÚC① Hai bước nhân đôi dữ liệu liên tiếp đều cho $\Delta < 0.6$ BLEU với CI chứa 0; ② nhân đôi tham số trong cùng họ kiến trúc cũng cho $\Delta$ dưới MDE; ③ phân loại tay $\geq 100$ lỗi còn lại cho thấy $\geq 30\%$ thuộc lớp mà kiến trúc hiện tại không thể biểu diễn (ngữ cảnh vượt cửa sổ, xưng hô cần trí nhớ nhân vật xuyên chương).Đổi bài toán chứ không chỉ đổi mô hình: thêm ngữ cảnh chương, thêm bộ nhớ nhân vật. Coi là dự án mới, viết preregister mới, và đóng băng bộ test cũ làm mốc.

Đọc bảng từ trên xuống, ô đầu tiên khớp thì dừng. Các điều kiện có chỗ chồng lấn: một CI hoàn toàn nằm trên $\delta^{*}$ đo trên 300 câu vẫn có nửa rộng $0.0062 > \delta^{*}$, tức khớp cả ô SHIP lẫn ô CHƯA KẾT LUẬN ĐƯỢC. Thứ tự đọc là một phần của luật, không phải chi tiết trình bày.

Bẫy thường gặp

  • Điều kiện ③ của ô "đổi kiến trúc" là điều kiện đắt nhất trong cả bảng — nó bắt bạn ngồi phân loại tay 100 lỗi. Đó là chủ ý. Ô này tiêu của bạn hàng tháng trời, nên nó phải khó vào hơn mọi ô khác.
  • Ô "chưa kết luận được" thường bị bỏ trống trong các bảng tự chế, vì nó không cho phép làm gì cả. Nhưng đó là ô hay rơi vào nhất khi bộ test dưới 500 câu.
  • Đường cong dữ liệu "phẳng" có thể chỉ là bộ test của bạn mù. Với $\sigma_d = 8$ BLEU điểm câu (con số của m05) và $n = 1200$, MDE là $0.65$ — một bước nhân đôi cho $+0.25$ BLEU sẽ hiện ra như "không có gì". Muốn thấy $+0.25$ bạn cần $n = (2.8016 \times 8 / 0.25)^2 = 8037$ câu. Đừng kết luận "hết cứu bằng data" khi thật ra bạn chỉ thiếu thước đo.

2. Một metric chủ, sáu guardrail, khoá trước khi chạy

Sáu metric nhìn cùng một cặp hệ thống sẽ kể sáu câu chuyện. Nếu bạn được quyền chọn metric sau khi thấy kết quả, bạn luôn thắng: với 6 phép so độc lập ở mức $\alpha = 0.05$, xác suất có ít nhất một cái "thắng" ngay cả khi hai hệ thống bằng hệt nhau là $1 - 0.95^{6} = 26.5\%$. Với 12 phép so (4 metric × 3 checkpoint) con số đó là $46.0\%$ — đúng cơ chế vé số ở Module 04, mục 7.

Một chú thích quan trọng: $46.0\%$ là cận trên, vì công thức $1-0.95^{m}$ giả định $m$ phép so độc lập. Bốn metric chấm trên cùng 1200 câu và ba checkpoint của cùng một run thì tương quan mạnh, nên tỉ lệ thật thấp hơn — m09 đo được 24.9% cho 20 lần nhìn chồng dữ liệu, thay vì 64.2% của công thức độc lập.

Cách chặn duy nhất: metric chủ (primary metric) đúng một cái, chọn trước, có quyền tuyên bố thắng. Mọi thứ còn lại là guardrail — chúng chỉ có quyền phủ quyết, không có quyền tuyên bố thắng. Một guardrail đi lên không phải tin vui đáng báo cáo; nó chỉ là chưa có tin xấu.

Vai tròChỉ sốĐo trênNgưỡng quyết địnhNền nhiễu của chính phép đo
Metric chủCOMET có tham chiếu — ở cổng ship; vòng lặp nhanh mỗi commit vẫn dùng chrF++ (m02)1200 câu phân tầngCI 95% ghép cặp nằm trên 0, cận dưới $\geq +0.005$SE 0.00159 · nửa rộng 0.00311 · MDE80 0.00445
Guardrail 1chrF++ tầng hội thoại240 câuKhông thua kém, biên $M = 1.5$ → trượt khi $\Delta < -0.54$SE 0.581 · 1.96·SE = 1.139
Guardrail 2Tỉ lệ lỗi critical (MQM)300 câu, 2 người chấmMcNemar exact $p \geq 0.05$ và $b - c \leq 3$SE của tỉ lệ 2% là 0.81 pp · cần $b \geq 6$ (khi $c = 0$) mới ra $p = 0.031$
Guardrail 3Nhất quán tên riêng500 câu có tên riêng$\geq 98\%$SE 0.63 pp · ±1.23 pp
Guardrail 4Length ratio trung bình1200 câuNằm trong $[0.95,\ 1.10]$SE 0.0052 → biên rộng 9.6–19.2 SE, chỉ bắt thảm hoạ
Guardrail 5Câu rỗng / lặp vô hạn5000 câu decode0 sự cố0/5000 → giới hạn trên 95% là 0.060%
Guardrail 6p95 latency2000 request$\leq 1.15\times$p95 tựa trên ~100 điểm · hạng 95% nằm trong [1881, 1919]
Ràng buộc cứngVRAM đỉnh, kích thước fileđo một lần$\leq 1.00\times$ bản đang chạykhông có nhiễu — hoặc đạt hoặc không

Cột cuối là cột quan trọng nhất và cũng là cột hầu như không ai viết. Một ngưỡng nhỏ hơn nền nhiễu của nó là đồ trang trí. Ví dụ sống: guardrail "tỉ lệ lỗi critical không tăng quá 0.5 điểm phần trăm" nghe rất nghiêm túc, nhưng sai số chuẩn của một tỉ lệ 2% trên 300 câu đã là $\sqrt{0.02 \times 0.98/300} = 0.81$ điểm phần trăm. Ngưỡng đó nhỏ hơn nhiễu — nó sẽ kêu bừa hoặc im bừa, tuỳ may rủi.

Nói bằng lời

Guardrail viết theo kiểu "không được tụt quá $M$" là một kiểm định không thua kém (non-inferiority test), không phải kiểm định thông thường. Bạn không hỏi "có khác 0 không", bạn hỏi "có chắc là không tụt quá $M$ không". Cách làm: lấy cận dưới một phía 95% của $\Delta$ rồi xem nó có $> -M$ hay không.

Với tầng hội thoại 240 câu, $\sigma_d = 9$ chrF: sai số chuẩn là $9/\sqrt{240} = 0.581$, cận dưới một phía là $\hat{\Delta} - 1.645 \times 0.581 = \hat{\Delta} - 0.956$. Đặt $M = 1.5$ thì guardrail trượt khi $\hat{\Delta} < -(1.5 - 0.956) = -0.544$. Nghĩa là: biên bạn khai là 1.5, nhưng ngưỡng thực tế mà mắt bạn nhìn thấy là 0.54 — phần chênh lệch chính là tiền bạn trả cho việc chỉ có 240 câu.

Chạy tay một lần

Guardrail 2, tình huống hay gặp. Chấm MQM 300 câu bằng hai người rồi gộp: bản cũ có 6 câu lỗi critical, bản mới có 9. Nghe như hồi quy 50%. Nhưng đây là dữ liệu ghép cặp — cùng 300 câu — nên phải đếm theo ô lệch:

  • $b = 4$ câu: bản cũ đúng, bản mới sai (lỗi mới).
  • $c = 1$ câu: bản cũ sai, bản mới đúng (lỗi đã sửa).
  • Kiểm tra: $6 - 1 + 4 = 9$ ✓. Còn 295 câu hai bên giống nhau — chúng không mang thông tin nào về sự khác biệt và bị loại khỏi phép tính.

Chỉ còn $b + c = 5$ câu lệch. Dưới giả thuyết không (hai hệ thống như nhau), mỗi câu lệch nghiêng về bên nào là một lần tung đồng xu công bằng. Xác suất thấy $\geq 4$ trong 5 câu nghiêng về cùng một bên, hai phía:

$$p = 2 \times \frac{\binom{5}{4} + \binom{5}{5}}{2^{5}} = 2 \times \frac{5 + 1}{32} = \frac{12}{32} = 0.375$$

Máy tính bỏ túi cũng bấm được: $p = 0.375$. Bằng chứng gần như bằng không. Đừng chặn ship vì con số 6→9, nhưng cũng đừng nói "an toàn" — bạn chỉ đang mù. Muốn phân biệt được 2% với 4% ở power 80% trên đúng thiết kế ghép cặp này — giả sử tỉ lệ câu lệch $\psi = 3\%$ — công thức McNemar cho $n = (1.96\sqrt{0.03} + 0.8416\sqrt{0.03 - 0.02^2})^2 / 0.02^2 = 587$ cặp câu, tức gấp gần 2 lần ngân sách chấm hiện tại. Con số 1141 câu mỗi bên là của thiết kế hai bộ câu rời nhau: bỏ ghép cặp thì đắt gấp đôi.

3. Online eval: A/B, interleaving, và vì sao offline không bao giờ đủ

Bộ test offline đo bản dịch. Người đọc web novel không tiêu thụ bản dịch, họ tiêu thụ một buổi đọc: tốc độ đọc, số lần phải dừng lại đoán nghĩa, số lần bỏ chương giữa chừng. Không metric offline nào chạm tới mấy thứ đó. Offline là bộ lọc để không ship rác; online là bằng chứng để tin rằng đã ship đúng.

A/B: mỗi người một hệ thống

Chia người đọc làm hai nhánh, nhánh A dùng model cũ, nhánh B dùng model mới, so trung bình. Vấn đề: phương sai bạn phải chiến đấu là phương sai giữa người đọc và giữa chương, thứ chẳng liên quan gì tới model. Giả sử tỉ lệ "bấm báo lỗi dịch" trung bình mỗi phiên là 6.0%, độ lệch chuẩn giữa các phiên $\sigma = 0.050$, và bạn muốn phát hiện mức giảm $\Delta = 0.004$ (6.0% → 5.6%). Số phiên cần cho mỗi nhánh:

$$n_{\text{arm}} = \frac{2\,(z_{0.975} + z_{0.80})^2\,\sigma^2}{\Delta^2} = \frac{2 \times 7.849 \times 0.0025}{0.000016} = 2453$$

Tổng 4906 phiên. Ở lưu lượng 400 phiên/ngày, đó là 12.3 ngày.

Interleaving: mỗi người nhận cả hai

Trộn đầu ra của hai hệ thống trong cùng một chương: chia đoạn theo team-draft cân bằng, một nửa số đoạn do A dịch, một nửa do B, thứ tự ưu tiên hoán đổi ngẫu nhiên. Mỗi phiên đọc giờ cho ra một hiệu ghép cặp: tỉ lệ báo lỗi trên đoạn của A trừ tỉ lệ trên đoạn của B, trong cùng một người, cùng một chương, cùng một tâm trạng.

Nói bằng lời

Toàn bộ lợi thế của interleaving nằm trong một công thức. Nếu hai nhánh trong cùng một phiên có tương quan $\rho$ — tương quan giữa hai giá trị đo ghép cặp, không phải tương quan hạng ở bảng ký hiệu — thì độ lệch chuẩn của hiệu là $\sigma_d = \sigma\sqrt{2(1-\rho)}$.

Dịch ra: phần phương sai do người đọc và do chương gây ra xuất hiện y hệt ở cả hai nhánh, và bị phép trừ xoá sạch. Chỉ phần nhiễu riêng của từng nhánh sống sót. Với $\rho = 0.96$: $\sigma_d = 0.050\sqrt{0.08} = 0.0141$ — nhỏ hơn $\sigma$ gần 3.5 lần, mà phương sai thì nhỏ hơn 12.5 lần. Tỉ số lưu lượng giữa hai thiết kế rút gọn thành một biểu thức không còn $\sigma$, không còn $\Delta$, không còn power:

$$\frac{2\,n_{\text{arm}}}{n_{\text{int}}} = \frac{4\sigma^2}{2(1-\rho)\sigma^2} = \frac{2}{1-\rho}$$

Thay số: $n_{\text{int}} = 7.849 \times 0.014142^2 / 0.004^2 = 98.1 \to$ 99 phiên. So với 4906 phiên của A/B: ít hơn 49.6 lần, và $2/(1-0.96) = 50$ đúng như công thức. Ở 400 phiên/ngày, interleaving xong sau 5.9 giờ thay vì 12.3 ngày.

A/B — mỗi người đọc chỉ thấy một hệ thống Nhánh A · model cũ2453 phiên Nhánh B · model mới2453 phiên hiệu hai trung bìnhσ = 0.050 còn nguyên Phương sai giữa người đọc và giữa chươngkhông bị khử — nó nằm thẳng trong mẫu số. Tổng 4906 phiên≈ 12.3 ngày ở 400 phiên/ngày Interleaving — mỗi chương chứa cả hai Một nhóm duy nhất99 phiên · 20 đoạn/phiên một chương · đoạn đỏ = A, đoạn xanh = B, thứ tự hoán đổi hiệu trong phiênσ_d = 0.0141 Người đọc là đối chứng của chính mình:σ_d = σ·√(2(1−ρ)) với ρ = 0.96. Tổng 99 phiên≈ 5.9 giờ · ít hơn 49.6 lần
Lợi thế không đến từ thống kê khéo, nó đến từ thiết kế. Bạn không đo chính xác hơn — bạn đo cùng một thứ nhưng đã trừ đi phần nhiễu mà cả hai nhánh cùng chịu. Đây chính là lý do ghép cặp thắng ở m04, chỉ khác là lần này ghép trên người thật.
$\rho$ trong phiên$\sigma_d$Phiên cần cho interleavingLợi $2/(1-\rho)$Khi nào rơi vào đây
0.500.050012274×Trộn xuyên chương, xuyên ngày — gần như hỏng thiết kế
0.800.031649110×Trộn ở mức chương, mỗi phiên chỉ vài đoạn mỗi bên
0.900.022424620×Trộn trong chương nhưng độ dài đoạn lệch nhau
0.960.01419950×Team-draft cân bằng, 10 đoạn mỗi bên trong cùng một chương
0.990.007125200×Lý thuyết; thực tế hiếm giữ được vì phong cách hai bản lệch nhau

Bẫy thường gặp

  • Interleaving trả lời câu hỏi khác. Nó nói "A hay B được ưa hơn", không nói "chỉ số kinh doanh thay đổi bao nhiêu". Doanh thu, tỉ lệ quay lại sau 7 ngày, thời lượng đọc trung bình — mấy thứ đó vẫn phải A/B. Đừng thay thế, hãy xếp tầng: interleaving lọc nhanh, A/B xác nhận cái sống sót.
  • Riêng với dịch, interleaving tạo confound của chính nó: trộn hai phong cách dịch trong một chương làm bản dịch đọc lệch. Người đọc có thể báo lỗi vì đứt mạch văn chứ không vì đoạn đó dịch sai. Cách giảm: trộn ở đơn vị đủ lớn (cả cảnh, không phải từng câu), và luôn chạy thêm một nhánh A-vs-A để đo nhiễu nền của chính thiết kế.
  • Mất cân bằng là chết. Nếu A hay được đoạn mở đầu chương hơn B, bạn đang đo vị trí chứ không đo model. Ghi nhật ký bên nào được ưu tiên và kiểm tỉ lệ phải nằm quanh 50/50.
  • Nếu $\rho$ thật của bạn chỉ là 0.80 thay vì 0.96, số phiên cần nhảy từ 99 lên 491. Hãy đo $\rho$, đừng giả định nó.

4. Sau khi ship: canary, trôi, và khiếu nại thành case eval

Ship không phải điểm kết thúc của phép đo, nó là điểm bắt đầu của phép đo rẻ nhất bạn từng có: dữ liệu thật, miễn phí, chảy liên tục.

Cổng offlineCI + 6 guardrail Canary 5%1000 đoạn · 2.5 ngày Mở 25%5000 đoạn · 2.5 ngày 100%interleaving xác nhận Giám sát3σ tuần = 0.0080 QE ≥1 sự cố → quay lui ngay≥1 sự cố → quay lui ngay khiếu nại người đọc → phân loại theo taxonomy lỗi → case eval mới → golden set ở 400 phiên/ngày và tỉ lệ khiếu nại 1/500 phiên: 0.8 case/ngày ≈ 45 case sau 8 tuần
Bốn cổng, một vòng phản hồi. Vòng phản hồi mới là thứ làm bộ test của bạn già đi chậm hơn thế giới thật.

Canary và luật ba. Canary không phải để đo chất lượng — nó quá ít lưu lượng cho việc đó. Nó để bắt sự cố nhị phân: câu rỗng, lặp vô hạn, tràn bộ nhớ. Ở đây có một quy tắc gọn đáng thuộc: nếu bạn quan sát 0 sự cố trong $n$ lần, giới hạn trên 95% của tỉ lệ sự cố xấp xỉ $3/n$. Với $n = 1000$: giới hạn trên $0.30\%$ (giá trị chính xác $1 - 0.05^{1/1000} = 0.2991\%$). Với $n = 5000$: $0.060\%$.

Ngược lại: muốn nhìn thấy ít nhất một sự cố có tỉ lệ thật 2% với xác suất 95%, bạn cần $n = \ln(0.05)/\ln(0.98) = 148.3 \to 149$ đoạn. Đó là lý do cổng canary 1000 đoạn đủ rộng để bắt lỗi thảm hoạ, nhưng hoàn toàn vô dụng để phân biệt 2% với 4%.

Phát hiện trôi. Dữ liệu vào thay đổi: truyện mới đổi thể loại, tác giả mới, độ dài câu khác. Cách rẻ nhất là chạy một metric không cần tham chiếu (COMET-QE) trên chính lưu lượng thật rồi đặt biểu đồ kiểm soát.

Với 2000 câu mỗi tuần và độ lệch chuẩn điểm QE mỗi câu là $0.12$, sai số chuẩn của trung bình tuần là $0.12/\sqrt{2000} = 0.00268$; ngưỡng $3\sigma$ là 0.0080 điểm QE. Báo động giả của ngưỡng $3\sigma$ hai phía là $0.27\%$ mỗi lần kiểm — kiểm hằng tuần thì trung bình 370 tuần (7.1 năm) mới kêu nhầm một lần; kiểm hằng ngày thì 370 ngày, tức một lần mỗi năm. Tần suất kiểm là một tham số bạn phải chọn có ý thức, không phải để mặc định.

Khiếu nại là dữ liệu vàng. Mỗi báo lỗi của người đọc là một câu mà bạn biết chắc đã sai và biết chắc có người quan tâm. Ở 400 phiên/ngày với tỉ lệ khiếu nại 1 trên 500 phiên, bạn nhận 0.8 case/ngày — sau một chu kỳ 8 tuần là khoảng 45 case. Ném chúng vào taxonomy lỗi zh→vi (m08), giữ lại những cái tái lập được, và bộ hồi quy của bạn lớn lên bằng đúng những lỗi mà thế giới thật quan tâm.

5. Eval ngốn bao nhiêu ngân sách

Câu trả lời làm nhiều người khó chịu: nhiều hơn tiền train, rất nhiều. Bảng dưới dùng đơn giá giả định rõ ràng cho một chu kỳ 8 tuần, một người, để bạn thay số của mình vào.

KhoảnGiả địnhĐơn giá (USD)Số lầnTổng (USD)
Train30 giờ GPU thuê @ 0.40 USD/giờ mỗi run12.00896.00
Eval tự động1200 câu × 4 hệ thống = 4800 câu; decode 12 câu/s (400 s) + chấm COMET 40 câu/s (120 s) = 8.7 phút GPU0.0578603.47
LLM-as-judge300 câu × 2 chiều hoán vị = 600 lượt; 540k token vào @ 3 USD/1M + 90k token ra @ 15 USD/1M — judge hạng trên, đắt gấp 10 lần model rẻ dùng ở m072.97617.82
Chấm người300 câu × 3 phút × 2 người = 30 giờ @ 8 USD/giờ (thang MQM của m06)240.002480.00
Xây & bảo trì golden set24 giờ người @ 8 USD/giờ192.001192.00
Tổng———789.29

Đọc ra ba con số:

  • Eval chiếm 87.8% tổng ngân sách ($693.29/789.29$) khi tính cả công người. Không phải vì eval đắt, mà vì GPU rẻ đến vô lý so với người.
  • Nếu chỉ tính tiền máy, eval chiếm 18.2% ($21.29/117.29$). Đây là con số dùng để tranh luận với người quản chi phí hạ tầng, và 15–25% là vùng lành mạnh: dưới 10% nghĩa là bạn đang bay mù, trên 40% nghĩa là bạn đang đo nhiều hơn làm.
  • Judge rẻ hơn người 80.8 lần ($240.00/2.97$). Tỉ số 123 lần ở m07 còn cao hơn vì ở đó judge là model rẻ và người chấm theo thang nhanh 45 giây/câu. Dù là 80 hay 123, đó vẫn là lý do judge tồn tại — và cũng chính là lý do phải kiểm định nó bằng $\kappa$ trước khi tin (m07). Nếu chấm người ở mọi run, tức 8 lần đúng theo cột "Số lần" của hàng Train, thì hoá đơn là 1920 USD, gấp 20 lần toàn bộ chi phí train.

Kết luận thực dụng: chạy eval tự động mỗi commit (6 xu một lần; riêng 120 câu hồi quy chỉ mất 10 giây GPU ở tốc độ 12 câu/giây), judge ở mỗi mốc lớn, người chỉ ở cổng ship. Đừng phân bổ ngược lại.

6. Checklist 14 điểm trước khi công bố một con số

Chạy từ trên xuống. Mỗi dòng là một hành động có kết quả nhìn thấy được, không phải một lời tự nhủ.

  1. Ghi vào một dòng log duy nhất: commit hash của code, hash của checkpoint, signature sacreBLEU, seed. Dán nguyên dòng đó vào báo cáo.
  2. Chạy lại toàn bộ pipeline eval từ đầu trên môi trường sạch. Hai lần chạy phải trùng nhau tới chữ số thập phân thứ ba; nếu không, truy ra nguồn ngẫu nhiên trước khi làm tiếp.
  3. Đo nhiễm: tỉ lệ câu test có 8-gram trùng với dữ liệu train phải dưới 0.5%. In ra danh sách câu vi phạm và tự đọc, đừng chỉ đọc con số.
  4. Đếm số câu thực sự được chấm rồi so với số dòng trong file test. Lệch một dòng nghĩa là có câu decode rỗng bị lặng lẽ bỏ qua.
  5. So hash hai file output của hai hệ thống. Trùng hash nghĩa là bạn đang so một model với chính nó — lỗi này xảy ra thường xuyên hơn bạn nghĩ.
  6. Tính CI 95% bằng paired bootstrap với $B = 10\,000$ — $B = 1000$ chỉ đủ cho vòng lặp phát triển hằng ngày (m03). Báo cáo đầy đủ theo mẫu của m03: 34.2, CI 95% [33.1, 35.3], bootstrap percentile, B=10^4, n=500 — 34.2 ± 1.1 trọn lỏn không đủ, và không bao giờ báo cáo bốn chữ số thập phân.
  7. Viết cạnh kết quả: nửa rộng CI và MDE của bộ test. Nếu hiệu bạn khoe nhỏ hơn MDE, viết thẳng câu "bộ test này mù với chênh lệch đó".
  8. Đếm tổng số phép so đã làm trong cả chu kỳ (mọi checkpoint × mọi metric) và ghi con số đó ngay cạnh p-value. Với 12 phép so độc lập, xác suất có ít nhất một cái "thắng" nhầm lên tới 46.0% — cận trên, xem m04.
  9. Mở bản preregister ba dòng viết trước khi train và đối chiếu. Nếu kết luận lệch khỏi nó, ghi rõ chỗ lệch — tuyệt đối không sửa lại preregister.
  10. Chạy đủ cả 6 guardrail và dán nguyên bảng kết quả, kể cả những ô không đạt.
  11. Báo cáo phân tầng ít nhất 4 tầng (thể loại, độ dài, có/không hội thoại, có/không tên riêng). Nếu một tầng tụt, con số trung bình mất quyền làm kết luận.
  12. Đọc tay 20 câu mà bản mới dịch tệ hơn bản cũ nhiều nhất. Nếu không giải thích được ít nhất 15 trong 20, bạn chưa hiểu chuyện gì đã xảy ra.
  13. Ghi chi phí đã tiêu để có con số này: giờ GPU, số câu chấm người, tiền judge. Một cải thiện phải chia được cho chi phí của nó.
  14. Ghi ngày hết hạn của kết luận: sau bao nhiêu tuần, hoặc sau bao nhiêu lần thay đổi dữ liệu, thì con số này phải được đo lại mới được trích dẫn tiếp.

7. Bốn dự án luyện tay

Dự án 1 — Bảng quyết định của chính bạn

  • Đầu vào: 3 checkpoint gần nhất, bộ test hiện có, log train của cả ba.
  • Việc phải làm: tính $\sigma_d$ thật của metric chủ trên bộ test hiện có (độ lệch chuẩn của vector hiệu điểm câu); từ đó ra SE, nửa rộng CI 95%, MDE 80%; chọn $\delta^{*}$ nằm trên MDE; viết ra bảng 6 ô ở mục 1 với ngưỡng số của riêng bạn; ghi ngày và commit hash.
  • Xong khi: bảng nằm trong repo dưới dạng file văn bản, và bạn đã chạy nó ngược trên 3 quyết định ship gần nhất — nếu không có quyết định cũ nào bị bảng này bác, ngưỡng của bạn đang quá lỏng.

Dự án 2 — Đo nền nhiễu của mọi guardrail

  • Đầu vào: danh sách guardrail bạn đang dùng; nếu chưa có, lấy 6 dòng ở bảng mục 2.
  • Việc phải làm: với từng guardrail, tính sai số chuẩn trên đúng cỡ mẫu bạn đang dùng (tỉ lệ thì $\sqrt{p(1-p)/n}$, điểm liên tục thì $\sigma_d/\sqrt{n}$); đặt ngưỡng bạn đang khai cạnh $1.96 \times \mathrm{SE}$; đánh dấu mọi ngưỡng nhỏ hơn nền nhiễu.
  • Xong khi: mỗi guardrail hoặc có ngưỡng $\geq 2 \times \mathrm{SE}$, hoặc bị hạ cấp thành "cảnh báo" không có quyền phủ quyết. Mốc để tự kiểm: với 300 câu và tỉ lệ nền 2%, mọi ngưỡng dưới 1.58 điểm phần trăm là đồ trang trí.

Dự án 3 — Một vòng interleaving thật

  • Đầu vào: hai hệ thống đã chạy được, một kênh có người đọc thật (30 người cũng đủ để bắt đầu).
  • Việc phải làm: chia đoạn theo team-draft cân bằng và ghi nhật ký bên nào được ưu tiên đoạn đầu; thu ít nhất một tín hiệu (báo lỗi, sửa tay, bỏ chương); tính hiệu ghép cặp theo từng phiên; đo tương quan $\rho$ giữa hai nhánh trong cùng phiên; chạy thêm một nhánh A-vs-A để biết nhiễu nền của thiết kế.
  • Xong khi: bạn có $\rho$ đo được (không phải đoán) và tính ra lợi $2/(1-\rho)$ của chính mình; tỉ lệ ưu tiên đoạn đầu nằm trong khoảng 45–55%; và nếu $\rho < 0.8$ thì bạn đã tìm ra, ghi lại nguyên nhân (trộn xuyên chương, đơn vị trộn quá to, độ dài đoạn lệch).

Dự án 4 — Biến 30 khiếu nại thành bộ hồi quy

  • Đầu vào: 30 phản hồi hoặc khiếu nại gần nhất từ người đọc (ở tỉ lệ 0.8 case/ngày thì đó là khoảng 5 tuần thu thập).
  • Việc phải làm: gán mỗi cái vào taxonomy lỗi zh→vi; giữ lại những cái tái lập được và viết bản dịch đúng do người viết; chấm model hiện tại trên 30 câu đó; đánh dấu câu nào thuộc mức critical và đặt luật "không được sai".
  • Xong khi: 30 câu nằm trong bộ hồi quy chạy mỗi commit (120 câu ở 12 câu/giây chỉ mất 10 giây GPU), CI được cấu hình chặn merge khi bất kỳ câu critical nào hỏng, và bạn đã kiểm bằng cách cố tình đẩy một commit làm hỏng một câu để xem CI có chặn thật không.

8. Khép vòng: ba quyển nói chuyện với nhau

Bảng quyết định ở mục 1 không kết thúc ở đây — ba trong sáu ô của nó là cửa dẫn sang hai quyển kia. Ô thêm data và ô đổi kiến trúc đưa bạn về Lò Luyện Chọn Model: pipeline dữ liệu, tokenizer, distillation, DPO, quantization, scaling — mọi can thiệp bạn có thể thực hiện sau khi con số đã chỉ đúng chỗ đau.

Còn khi guardrail chỉ ra một lỗi mà bạn không giải thích nổi bằng dữ liệu — attention nhìn sai chỗ, tokenizer băm nát tên riêng, embedding không tách nổi hai cách xưng hô — thì cửa còn lại là Lò Luyện Mạng Nơ-ron, nơi mổ cơ chế bên trong.

Ba quyển là một vòng lặp chứ không phải một hàng dọc: cơ chế → quyết định khi train → đo lường → quay lại cơ chế. Quyển này giữ vai trò cái đồng hồ. Nếu đồng hồ sai, hai quyển kia chỉ giúp bạn chạy nhanh hơn về phía sai hướng.


Câu cuối cùng, đúng bằng câu mở đầu quyển: một con số không kèm sai số là một tin đồn. Sau mười module, bạn đã có đủ công cụ để không phải phát tán tin đồn nào nữa — bootstrap cho khoảng, ghép cặp cho phép so, power cho cỡ mẫu, $\kappa$ cho người chấm, guardrail cho quyền phủ quyết, và một bảng sáu ô để cái miệng không chạy trước cái khoảng tin cậy.

Nó ứng dụng vào đâu trong đời thật

Trong dự án dịch web novel zh→vi, module này đổi đúng bốn quyết định:

  • Bản distil 17M có được thay bản 136M không. Trước: so BLEU, thấy tụt là thôi. Sau: đó là câu hỏi không thua kém với biên $M$ do bạn đặt, cộng điều kiện "nhỏ hơn $\geq 30\%$" ở ô GIỮ LẠI. Model nhỏ hơn 8 lần mà cận dưới CI của $\Delta$ vẫn trên $-0.005$ thì ship — vì rẻ, và bạn nói được thành lời tại sao.
  • DPO-lite có ship không. DPO sửa những thứ BLEU gần như không thấy (xưng hô, giọng nhân vật), nên metric chủ sẽ đứng yên. Quyết định phải nằm ở guardrail tầng hội thoại và ở một vòng interleaving 99 phiên, chứ không ở corpus-BLEU.
  • Bao giờ dừng thu thêm song ngữ. Giả sử bước nhân đôi cuối chỉ còn $+0.25$ BLEU, dưới MDE $0.65$: điều đó không chứng minh dữ liệu hết tác dụng — nó chỉ chứng minh bộ test 1200 câu đang mù. Muốn kết luận thật, hoặc nới bộ test lên 8037 câu, hoặc chuyển sang đo trên đúng tầng bị lỗi nhiều nhất.
  • Same-teacher bias thành điều kiện chặn. Judge dùng chính teacher đã distil ra student thì mọi con số của nó bị loại khỏi vai trò "metric chủ" một cách máy móc, không cần tranh luận — vì bảng đã khoá metric chủ trước khi chạy.

Tự kiểm tra

1 · Bộ test 1200 câu, $\sigma_d = 8$ BLEU điểm câu. Bản mới hơn 0.35 BLEU. Ship hay không?

Không, và quan trọng hơn: bạn thậm chí không được rơi vào ô "giữ lại". Sai số chuẩn là $8/\sqrt{1200} = 0.2309$, CI 95% là $0.35 \pm 0.453 = [-0.103,\ +0.803]$ — chứa 0. Nhưng ngưỡng thực dụng hợp lệ phải nằm trên MDE $0.647$, mà cận trên $+0.803$ của CI còn cao hơn cả thế: bạn chưa loại trừ được khả năng bản mới hơn một cách thực dụng, nên chưa được vào ô GIỮ LẠI. Đây là ô CHƯA KẾT LUẬN ĐƯỢC. Muốn phân biệt được 0.35, bạn cần $n = (2.8016 \times 8/0.35)^2 = 4101$ câu. Kết luận đúng phải viết là "chưa đo được", không phải "không hơn".

2 · Vì sao interleaving cần ít lưu lượng hơn A/B tới 50 lần, mà không hề "khéo hơn" về mặt thống kê?

Vì nó đổi thiết kế chứ không đổi phép tính. Trong A/B, phương sai bạn phải vượt là $\sigma^2$ của cả quần thể người đọc và chương — thứ không liên quan gì tới model. Trong interleaving, mỗi người là đối chứng của chính mình, nên phương sai còn lại chỉ là $\sigma_d^2 = 2(1-\rho)\sigma^2$. Tỉ số lưu lượng rút gọn thành $2/(1-\rho)$: với $\rho = 0.96$ ra đúng 50, với $\rho = 0.80$ chỉ còn 10. Cụ thể: 4906 phiên (12.3 ngày) so với 99 phiên (5.9 giờ). Cái giá phải trả: interleaving chỉ trả lời "bên nào được ưa hơn", không trả lời được câu hỏi về chỉ số kinh doanh.

3 · Chấm MQM 300 câu: bản cũ 6 câu critical, bản mới 9. Guardrail "tỉ lệ critical không tăng" có được kích hoạt không?

Không đủ bằng chứng. Phải đếm theo ô lệch: với $b = 4$ (lỗi mới) và $c = 1$ (lỗi đã sửa), chỉ 5 câu mang thông tin. McNemar chính xác cho $p = 2\left(\binom{5}{4}+\binom{5}{5}\right)/2^5 = 12/32 = 0.375$. Cần tới $b \geq 6$ với $c = 0$ mới xuống dưới 0.05 ($p = 0.031$). Nhưng đừng đảo ngược thành "an toàn": sai số chuẩn của tỉ lệ 2% trên 300 câu là 0.81 điểm phần trăm, nên bạn đang mù với mọi thay đổi dưới khoảng 1.58 điểm phần trăm. Muốn phân biệt 2% với 4% ở power 80% trên thiết kế ghép cặp này cần khoảng 587 cặp câu (McNemar, giả sử tỉ lệ câu lệch 3%) — không phải 1141 câu mỗi bên, đó là con số của thiết kế không ghép cặp.

4 · Canary chạy 5000 đoạn, không thấy sự cố nào. Bạn được phép tuyên bố điều gì?

Chỉ được tuyên bố một giới hạn trên, không được tuyên bố "không có lỗi". Luật ba: 0 sự cố trong $n$ lần cho giới hạn trên 95% xấp xỉ $3/n$; với $n = 5000$ là $0.060\%$ (giá trị chính xác $1 - 0.05^{1/5000} = 0.0599\%$). Nếu bản cũ có tỉ lệ sự cố 0.02%, canary này không loại trừ được khả năng bản mới tệ gấp ba lần — biên $0.060\%$ vừa đúng chạm $3 \times 0.02\%$. Muốn chắc hơn thì phải kéo dài canary, hoặc chấp nhận rủi ro và ghi rõ mức rủi ro đó vào sổ run.

5 · Vì sao guardrail không được phép tuyên bố thắng, chỉ được phủ quyết?

Vì mỗi metric bạn cho quyền tuyên bố thắng là một vé số. Với 6 phép so độc lập ở $\alpha = 0.05$, xác suất ít nhất một cái "thắng" khi hai hệ thống thật ra bằng nhau là $1 - 0.95^6 = 26.5\%$; với 12 phép so là $46.0\%$ — bảng đầy đủ ở Module 04, mục 7, và đó là cận trên vì các metric đo trên cùng bộ test tương quan mạnh. Nếu guardrail được tuyên bố thắng, bạn sẽ luôn tìm được một cái đẹp sau khi nhìn kết quả — đó chính là vườn của những lối rẽ ở m09. Phủ quyết thì ngược lại: nó chỉ làm bạn khó ship hơn, nên sai số nó gây ra nghiêng về phía an toàn.

Phụ lục

☰ Sổ tay thuật ngữ Anh – Việt

Tra nhanh khi đọc tài liệu tiếng Anh. Số trong ngoặc là module giải thích kỹ.

A/B test — chia lưu lượng thật cho hai phiên bản rồi so kết quả. (10)
Ablation — bỏ đi một thành phần để đo xem nó đóng góp bao nhiêu. (00)
Agreement — tỉ lệ hai người chấm cho cùng nhãn, chưa trừ may rủi. (06)
Anchoring bias — thiên vị neo; câu chấm trước ảnh hưởng câu chấm sau. (06)
Annotator — người chấm, người gán nhãn. (06)
Approximate randomization — kiểm định bằng cách hoán vị ngẫu nhiên nhãn hệ thống. (04)
Baseline — mốc so sánh; không có nó thì mọi con số vô nghĩa. (00)
BERTScore — chấm bằng độ tương đồng embedding, ghép tham lam giữa hai câu. (02)
BLEU — metric n-gram kinh điển cho dịch máy: precision có clipping cộng phạt độ dài. (01)
Bonferroni correction — chia mức ý nghĩa cho số lần so để chặn báo động giả. (04)
Bootstrap — lấy mẫu lại có hoàn lại từ chính mẫu để ước lượng sai số. (03)
Brevity penalty — hệ số phạt khi bản dịch ngắn hơn tham chiếu. (01)
Canary — thả bản mới cho một phần nhỏ người dùng trước khi mở toàn bộ. (10)
Chance agreement — phần đồng thuận có được chỉ do may rủi; thứ mà $\kappa$ trừ đi. (06)
chrF — metric dựa trên n-gram ký tự, hợp với ngôn ngữ giàu hình thái. (01)
Clipping — chặn số lần khớp của một n-gram theo số lần nó xuất hiện trong tham chiếu. (01)
COMET — metric neural hồi quy trên điểm người; có bản cần và không cần tham chiếu. (02)
Confidence interval — khoảng tin cậy; dải giá trị tương thích với dữ liệu quan sát. (03)
Contamination — nhiễm; câu trong bộ test đã xuất hiện trong tập train. (08)
Corpus-level — tính trên toàn bộ tập, khác với trung bình của điểm từng câu. (01)
Cohen's kappa — hệ số đồng thuận đã trừ phần trùng ngẫu nhiên. (06)
Direct Assessment (DA) — người chấm cho điểm liên tục 0–100 cho một bản dịch. (06)
Drift — trôi; phân phối dữ liệu thật đổi khác so với lúc đánh giá. (10)
Effect size — độ lớn của chênh lệch, tách khỏi câu hỏi nó có ý nghĩa thống kê hay không. (05)
Error taxonomy — bảng phân loại lỗi; nền của mọi báo cáo chất lượng nghiêm túc. (08)
Evaluation set — bộ dữ liệu dùng để chấm, không bao giờ dùng để train. (00, 08)
Garden of forking paths — vườn của những lối rẽ; mỗi lựa chọn nhỏ là một bậc tự do đã tiêu. (09)
Golden set — tập vàng; bộ câu đã được người xác nhận kỹ, dùng làm chuẩn. (08)
Goodhart's law — khi một thước đo trở thành mục tiêu, nó thôi làm thước đo tốt. (00)
Guardrail metric — metric bảo vệ; không cần tăng, nhưng không được phép tụt. (10)
Held-out — phần dữ liệu giữ riêng ra, không cho model nhìn thấy. (08)
Interleaving — trộn kết quả của hai hệ thống trong cùng một phiên để so trực tiếp. (10)
Inter-annotator agreement — mức đồng thuận giữa những người chấm. (06)
Kendall tau — tương quan hạng, đếm cặp thuận trừ cặp nghịch. (02)
Krippendorff alpha — hệ số đồng thuận tổng quát, chịu được nhiều người chấm và dữ liệu thiếu. (06)
Length bias — thiên vị độ dài; xu hướng chấm cao cho bản dài hơn. (07)
Likert scale — thang điểm rời rạc, thường 1–5. (06)
LLM-as-judge — dùng một model ngôn ngữ làm người chấm tự động. (07)
MDE — chênh lệch nhỏ nhất mà bộ test của bạn còn phát hiện được. (05)
Monte Carlo error — sai số do chỉ chạy hữu hạn vòng mô phỏng, khác với sai số của dữ liệu. (03)
MQM — khung chấm lỗi có phân loại và mức nghiêm trọng, cho điểm bằng cách trừ phạt. (06)
Multiple comparisons — đa so sánh; càng so nhiều lần càng dễ trúng báo động giả. (04)
Null hypothesis — giả thuyết không; mặc định "hai hệ thống bằng nhau". (04)
Paired test — kiểm định ghép cặp; so trên cùng những câu nguồn nên nhạy hơn nhiều. (04)
Peeking — nhìn kết quả giữa chừng rồi dừng khi thấy đẹp; làm hỏng p-value. (09)
Percentile interval — khoảng tin cậy lấy trực tiếp từ phân vị của phân phối bootstrap. (03)
Permutation test — kiểm định bằng cách liệt kê hoặc lấy mẫu các hoán vị nhãn. (04)
Position bias — thiên vị vị trí; ưu ái bản được đặt trước. (07)
Power — xác suất phát hiện được chênh lệch khi nó thật sự tồn tại. (05)
Preregistration — viết trước tiêu chí kết luận, trước khi nhìn kết quả. (09)
Proxy metric — thước đo thay thế cho thứ bạn thật sự quan tâm nhưng không đo được. (00)
p-value — xác suất thấy chênh lệch cỡ này hoặc lớn hơn nếu giả thuyết không đúng. (04)
Quality Estimation (QE) — chấm chất lượng mà không cần bản dịch tham chiếu. (02)
Reference — bản dịch tham chiếu do người làm, dùng làm chuẩn so. (01)
Regression suite — bộ hồi quy; tập câu chạy lại mỗi lần đổi model để bắt lỗi tái phát. (08)
Reproducibility — khả năng chạy lại ra đúng kết quả cũ. (09)
Resampling — lấy mẫu lại; họ phương pháp gồm bootstrap và permutation. (03, 04)
sacreBLEU — cài đặt BLEU chuẩn hoá kèm chữ ký cấu hình để so được giữa các nghiên cứu. (01)
Same-teacher bias — dùng chính teacher đã distil ra student để chấm student; vòng lặp tự khen. (07)
Sample size — cỡ mẫu; số câu trong bộ test. (05)
Self-preference bias — model chấm ưu ái văn phong do chính nó sinh ra. (07)
Severity — mức nghiêm trọng của lỗi: nhẹ, nặng, nghiêm trọng. (06)
Significance level — mức ý nghĩa $\alpha$; tỉ lệ báo động giả bạn chấp nhận trước. (04)
Smoothing — làm mượt để điểm câu không bị về 0 khi thiếu n-gram bậc cao. (01)
Spearman correlation — tương quan hạng; đo mức cùng thứ tự chứ không cùng giá trị. (02)
Standard error — sai số chuẩn của một ước lượng, bằng $s/\sqrt{n}$ với trung bình. (03)
Stratification — phân tầng; báo cáo tách theo domain, độ dài, độ khó. (08)
TER — tỉ lệ chỉnh sửa; số thao tác cần để biến bản dịch thành tham chiếu. (01)
Type I error — sai lầm loại I; kết luận có chênh lệch trong khi thật ra không có. (05)
Type II error — sai lầm loại II; bỏ sót chênh lệch có thật. (05)
Variance reduction — giảm phương sai; cách rẻ nhất để tăng độ nhạy mà không tăng cỡ mẫu. (05)