Chuyển đến nội dung
4/30Chương 4 trên 30

Phân loại, cross-entropy và cách đừng tự đánh lừa mình

Xây dựng logistic classifier, rồi thấy vì sao accuracy 98% đôi khi là một model chẳng tìm thấy gì.

Trên trang này

Một model trả lời phần này ổn với mọi chi tiết rời khỏi băng chuyền thì đúng 98,15 % thời gian. Nó cũng vô dụng: trong 74 chi tiết lỗi ở tập test, nó không bắt được cái nào.

Cả hai câu đều mô tả cùng một model. Khoảng cách giữa chúng chính là chương này.

Nửa đầu xây dựng classifier. Nó hầu như không cần gì mới: Chương 2 đã đưa công thức biến một giả định về cách dữ liệu được tạo ra thành một loss function, và Chương 3 đã đưa bộ máy để đi xuống dốc trên bất kỳ loss nào công thức đó trao cho bạn. Áp dụng cả hai vào một câu hỏi có/không và logistic regression hiện ra, cộng thêm một ý mới — một logit — sẽ được dùng lại trong Chương 17.

Nửa sau mới là phần khó hơn. Từ điểm này trở đi trong khóa học, mọi thứ đều được đánh giá bằng một con số do ai đó đo, và nếu bạn không phân biệt được cải thiện thật với artefact đo lường, mọi chương sau chỉ là trang trí. Vì vậy: confusion matrix, precision và recall, ba split, leakage, và câu hỏi hầu như không ai trả lời thành thật — thực ra tôi cần bao nhiêu ví dụ test?

Phần tính toán ở đây chạy trên 20.000 hàng, nên toàn bộ được vector hóa — NumPy đã làm việc từ Chương 2, và từ đây trở đi chuyện đó không còn đáng nhắc nữa.

Cùng nhà máy như Chương 1, câu hỏi khó hơn. Thay vì chấp nhận hay loại bỏ, câu hỏi là chi tiết này có lỗi không — và lỗi thì hiếm, khiến nửa đo lường của chương này khó còn nửa mô hình hóa thì dễ một cách đánh lừa.

belt.pyPYTHON
import numpy as np

rng = np.random.default_rng(4)
N = 20_000
width  = rng.normal(22.0, 0.9, N)      # millimetres
weight = rng.normal(57.0, 3.0, N)      # grams

z_true = -5.90 + 1.90 * (width - 22.0) + 0.42 * (weight - 57.0)
y = (rng.random(N) < 1 / (1 + np.exp(-z_true))).astype(float)

perm = rng.permutation(N)
train, val, test = perm[:12_000], perm[12_000:16_000], perm[16_000:]
TEXT
N = 20000  defects = 337  base rate = 0.0169
defects per split = 203 60 74

Ba split, không phải hai. Lý do xứng đáng có một mục riêng và sẽ có ở dưới; hiện tại, train trên tập đầu, tune trên tập thứ hai, và đừng nhìn tập thứ ba.

Các feature được chuẩn hóa — trừ mean, chia cho standard deviation — chỉ dùng thống kê của training, vì lý do Chương 1 đã minh họa bằng cận hội tụ của perceptron: dữ liệu chưa được centered làm hình học trở nên thù địch. Bạn được phép tính mean đó từ những hàng nào sẽ trở thành câu hỏi thật sự ở phần sau chương này.

Perceptron trả về một dấu. Một dấu không thể phân biệt loại bỏ với loại bỏ, nhưng chỉ vừa sát nút, và khác biệt đó chính là thứ một nhà máy cần để quyết định chi tiết nào con người nên kiểm tra lại trước.

Vì vậy hãy làm đúng theo công thức của Chương 2. Viết ra điều bạn khẳng định về cách một nhãn được tạo ra, lấy likelihood, lấy log, đổi dấu, và bạn có một loss. Với kết quả có/không, khẳng định đó là phân phối Bernoulli: có một xác suất pp rằng chi tiết bị lỗi, và

P(yp)=py(1p)1yP(y \mid p) = p^{\,y}\,(1-p)^{\,1-y}

đây chỉ là cách viết gọn của “pp nếu y=1y = 1, và 1p1-p nếu y=0y = 0”. Lấy log của biểu thức đó rồi đổi dấu, loss cho một ví dụ là

L=[ylogp+(1y)log(1p)]L = -\big[\,y \log p + (1 - y)\log(1 - p)\,\big]

Đây là binary cross-entropy. Nó không được chọn vì tiện; nó là negative log-likelihood của phân phối duy nhất mà một cú tung đồng xu có thể có. Không có lựa chọn nào khác.

Thứ còn thiếu là pp đến từ đâu. Model tính một tổng có trọng số s=wx+bs = \mathbf{w}\cdot\mathbf{x} + b, là một số thực trải trên toàn trục số, còn xác suất phải nằm trong (0,1)(0,1). Hàm chuyển giữa hai thứ đó là logistic sigmoid:

σ(s)=11+es\sigma(s) = \frac{1}{1 + e^{-s}}
TEXT
logit -4.0  ->  p = 0.0180        loss when y=1 and p=0.9  : 0.1054
logit -1.0  ->  p = 0.2689        loss when y=1 and p=0.5  : 0.6931
logit  0.0  ->  p = 0.5000        loss when y=1 and p=0.01 : 4.6052
logit  4.0  ->  p = 0.9820

Hãy đọc cột bên phải như một bảng giá. Đúng với độ tự tin 90 % tốn 0,105. Không chịu cam kết tốn 0,693 — tức log2\log 2, cái giá của một cái nhún vai. Tự tin mà sai tốn 4,6, đắt hơn bốn mươi bốn lần, và cái giá tăng không giới hạn khi model càng chắc chắn về một sai lầm. Cross-entropy không chỉ đếm lỗi: nó tính phí sự kiêu ngạo.

Chương 3 nói: để train bất cứ thứ gì, hãy lấy derivative của loss theo từng parameter. Làm với một ví dụ. Với s=wx+bs = \mathbf{w}\cdot\mathbf{x} + bp=σ(s)p = \sigma(s):

Ls=py,Lw=(py)x,Lb=py\frac{\partial L}{\partial s} = p - y, \qquad \frac{\partial L}{\partial \mathbf{w}} = (p - y)\,\mathbf{x}, \qquad \frac{\partial L}{\partial b} = p - y
Hiện chi tiết

Hai dòng khiến mớ hỗn độn triệt tiêu. Sigmoid có một derivative dễ chịu bất thường, σ(s)=σ(s)(1σ(s))=p(1p)\sigma'(s) = \sigma(s)\,(1 - \sigma(s)) = p(1-p). Và loss vi phân thành

Lp=yp+1y1p=pyp(1p)\frac{\partial L}{\partial p} = -\frac{y}{p} + \frac{1-y}{1-p} = \frac{p - y}{p\,(1-p)}

Nhân hai thứ bằng chain rule và p(1p)p(1-p) xuất hiện một lần ở trên, một lần ở dưới. Nó triệt tiêu chính xác, và pyp - y là thứ còn lại. Sự triệt tiêu đó không phải ngẫu nhiên — đó là điều xảy ra mỗi khi loss là negative log-likelihood của một phân phối và output function là hàm phân phối đó dùng một cách tự nhiên. Cặp đôi đó có tên — generalised linear model — và gradient gọn gàng là dấu vân tay của nó.1

Vì vậy update là dự đoán trừ sự thật, nhân với input. Không gì khác. Đây là toàn bộ trainer, tức descent của Chương 3 với một dòng thay đổi:

logistic.pyPYTHON
def sigmoid(z):
    return np.where(z >= 0, 1.0 / (1.0 + np.exp(-z)),
                    np.exp(np.minimum(z, 0)) / (1.0 + np.exp(np.minimum(z, 0))))


def fit_logistic(X, y, lr=0.5, epochs=4000):
    w, b = np.zeros(X.shape[1]), 0.0
    for _ in range(epochs):
        p = sigmoid(X @ w + b)
        g = p - y                        
        w -= lr * (X.T @ g) / len(y)     
        b -= lr * g.sum() / len(y)       
    return w, b

np.where trong sigmoid không phải trang trí. Tính trực tiếp 1/(1+es)1/(1+e^{-s}) sẽ overflow với ss âm lớn; nhánh chọn dạng tương đương đại số nào giữ exponent âm. Đây là chiếc hộp floating-point của Chương 2 đang thu món nợ đầu tiên, và nó sẽ thu món lớn hơn sau hai mục nữa.

Vì sao không dùng squared error, và vì sao câu trả lời nằm ở gradient

Liên kết đến mục: Vì sao không dùng squared error, và vì sao câu trả lời nằm ở gradient

Cách giải thích chuẩn cho việc thích cross-entropy hơn squared error là lập luận likelihood ở trên: squared error là thứ bạn có khi giả định Gaussian noise, nhãn không phải Gaussian, vậy đừng dùng. Điều đó đúng và chẳng thuyết phục được ai, vì bạn có thể viết L=(py)2L = (p - y)^2 trên một sigmoid và nó vẫn train.

Lập luận thấm hơn là về gradient. Đặt squared error lên trên sigmoid và chain rule cho ra

Ls=2(py)p(1p)\frac{\partial L}{\partial s} = 2\,(p - y)\,p\,(1-p)

Thừa số p(1p)p(1-p) thêm vào chính là thứ đã triệt tiêu lúc trước. Bây giờ nó không triệt tiêu, và nó tiến về 0 bất cứ khi nào model tự tin — kể cả khi model tự tin sai. Tính cả hai tại vài score, với một ví dụ có nhãn thật là 1:

score ssppcross-entropy L/s\partial L/\partial ssquared error L/s\partial L/\partial sratio
8-80.0003350.999665-0.9996650.000670-0.0006701,491
4-40.0179860.982014-0.9820140.034690-0.03469028.3
2-20.1192030.880797-0.8807970.184956-0.1849564.8
000.5000000.500000-0.5000000.250000-0.2500002.0
+2+20.8807970.119203-0.1192030.025031-0.0250314.8

s=8s = -8 model sai hết mức có thể, và squared error đáp lại bằng gradient nhỏ hơn cross-entropy 1.491 lần. Sai càng nặng, model càng học ít từ sai lầm đó. Trong khi đó gradient của cross-entropy bão hòa ở 1-1: sai tối đa tạo tín hiệu lớn tối đa, và không lớn hơn.

Cho chúng chạy đua. Hai nghìn điểm cân bằng, cùng trọng số khởi tạo được chọn để tự tin sai (w=[6,6]\mathbf{w} = [-6, -6]), cùng learning rate, chỉ khác loss. Cả hai lần chạy đều được chấm bằng cross-entropy để các cột so sánh được.

| epoch | cross-entropy loss | accuracy | squared-error loss | accuracy | |---|---|---|---| | 1 | 5.4865 | 0.2300 | 5.9499 | 0.2290 | | 10 | 1.5525 | 0.2460 | 5.9042 | 0.2290 | | 50 | 0.4642 | 0.7780 | 5.6913 | 0.2320 | | 100 | 0.4639 | 0.7770 | 5.3955 | 0.2410 | | 200 | 0.4639 | 0.7770 | 4.6311 | 0.2745 | | 500 | 0.4639 | 0.7770 | 0.5291 | 0.7660 | | 1,000 | 0.4639 | 0.7770 | 0.4640 | 0.7765 |

Cross-entropy xong từ epoch 50. Squared error vẫn ở accuracy 24 % tại epoch 100 — và không nhúc nhích khỏi 23 % ở epoch 10 — tệ hơn đoán mò, vì nó bắt đầu bằng trạng thái tự tin sai và gradient lẽ ra cứu nó đã bị nhân với 0,0007. Nó thoát ra khoảng epoch 500 và đáp xuống cùng một chỗ. Vì vậy tóm tắt thành thật là squared error trên sigmoid không sai; nó chậm đúng ở nơi tốc độ quan trọng nhất. Trên model hai parameter, bạn mất 450 epoch. Trên một network trăm layer, nơi luôn có một unit nào đó tự tin sai, bạn mất cả training run.

Ba đại lượng, cần dùng đúng ở Chương 8 cho perplexity và ở Chương 11 cho penalty giữ một policy đã fine-tuned gần với reference của nó. Chúng dễ hơn danh tiếng của mình.2

Entropy là số bit trung bình bạn phải dùng để truyền đạt một mẫu rút từ phân phối, nếu bạn dùng code tốt nhất có thể cho nó:

H(p)=ipilog2piH(p) = -\sum_i p_i \log_2 p_i

Cross-entropy là thứ bạn phải trả khi dùng một code xây cho qq trên dữ liệu thật ra đến từ pp:

H(p,q)=ipilog2qiH(p, q) = -\sum_i p_i \log_2 q_i

KL divergence là phần dư — phần lãng phí, tính bằng bit, do tin qq khi sự thật là pp:

DKL(pq)=H(p,q)H(p)D_{\mathrm{KL}}(p \parallel q) = H(p,q) - H(p)

Kiểm tra cả ba trên băng chuyền:

TEXT
test defect rate                                = 0.0185
entropy of that coin                            = 0.1329 bits
cross-entropy of the constant predictor on test = 0.1330 bits
KL(test coin || fair coin)                      = 0.8671 bits
H + KL                                          = 1.0000 bits
cross-entropy of the p=0.5 predictor on test    = 1.0000 bits

Có hai điều thấy được ở đó. Thứ nhất, một model chỉ báo base rate của training, 1,69 %, đạt cross-entropy 0,1330 bit, gần như đúng bằng entropy của nhãn test — như nó phải vậy, vì nó có đúng phân phối và không có thông tin nào khác. Entropy là sàn mà sự thiếu hiểu biết về từng cá thể mua cho bạn. Thứ hai, một model nhún vai và nói 0,5 trả đúng 1 bit, và khoảng cách giữa hai thứ, 0,8671 bit, chính xác là KL divergence. H+DKL=H(p,q)H + D_{\mathrm{KL}} = H(p,q) không phải một identity để học thuộc; nó là một hóa đơn bạn có thể nhìn thấy đang cộng dồn.

Và liên hệ trở lại training: khi nhãn là một class đã biết duy nhất, phân phối “thật” là one-hot, entropy của nó bằng 0, và cross-entropy bằng KL divergence. Minimize cross-entropy và kéo phân phối của model về phía sự thật là cùng một hành động.

Nhiều hơn hai câu trả lời: softmax, và phép dịch không tốn gì

Liên kết đến mục: Nhiều hơn hai câu trả lời: softmax, và phép dịch không tốn gì

Lỗi không chỉ có một loại. Trong ép khuôn, một chi tiết có thể ra thành short shot (thiếu vật liệu), flash (thừa, bị ép tràn khỏi khuôn), hoặc burn. Bốn kết quả, nên bốn logits, và chúng phải trở thành bốn xác suất có tổng bằng một. Đó là softmax:

softmax(z)i=ezijezj\operatorname{softmax}(\mathbf{z})_i = \frac{e^{z_i}}{\sum_j e^{z_j}}

Nó có một tính chất trông như tai nạn nhưng thật ra là toàn bộ implementation:

softmax(z+c)=softmax(z)\operatorname{softmax}(\mathbf{z} + c) = \operatorname{softmax}(\mathbf{z})

với bất kỳ hằng số cc nào, vì ezi+c=ecezie^{z_i + c} = e^{c} e^{z_i}ece^c triệt tiêu ở tử và mẫu. Chỉ chênh lệch giữa các logits mới có ý nghĩa. Mức tuyệt đối không phải thông tin.

May là vậy, vì mức tuyệt đối là thứ làm máy tính hỏng:

TEXT
logits            = [800. 801. 799.]
naive softmax     = [nan nan nan]
shifted by -max   = [0.2447 0.6652 0.09  ]
same softmax after adding 1000 to every logit: True

e800e^{800} overflow một float 64-bit, tổng trở thành vô cực, và vô cực chia vô cực là nan — không lỗi, không crash, chỉ là một lỗ hổng im lặng nơi ba xác suất từng tồn tại. Trừ đi logit lớn nhất không thay đổi gì về mặt toán học và thay đổi mọi thứ về mặt số học, vì exponent lớn nhất trở thành đúng e0=1e^0 = 1. Đây là mẹo logsumexp của Chương 2 mặc đồ đi làm, và mọi implementation nghiêm túc đều làm vậy:

softmax.pyPYTHON
def softmax(Z):
    Z = Z - Z.max(axis=1, keepdims=True)   
    E = np.exp(Z)
    return E / E.sum(axis=1, keepdims=True)


def fit_softmax(X, Y, lr=1.0, epochs=6000):
    W, b = np.zeros((X.shape[1], Y.shape[1])), np.zeros(Y.shape[1])
    for _ in range(epochs):
        G = (softmax(X @ W + b) - Y) / len(X)   
        W -= lr * (X.T @ G)
        b -= lr * G.sum(0)
    return W, b

Gradient lại là dự đoán trừ sự thật, giờ với YY one-hot. Trường hợp binary hóa ra luôn chỉ là một trường hợp đặc biệt.

Train trên 3.000 chi tiết và test trên 1.000, với ba phép đo mỗi chi tiết (width, weight, melt temperature), nó đạt 94,00 % accuracy. Đây là thứ con số đó đang che giấu:

truth ↓ / predicted →okshort shotflashburnrecall
ok8505900.984
short shot2221000.488
flash2003010.588
burn300390.929
precision0.9500.8080.7690.975

Model tìm thấy chưa đến một nửa short shot. Accuracy không thấy điều này, vì 86 % chi tiết là ổn và đoán đúng chúng đã đủ kéo trung bình lên. Macro F1 — mean của F1 theo từng class, đặt rare class ngang trọng số với common class — là 0,7983, so với micro F1 0,9400, theo định nghĩa giống hệt accuracy. Bất cứ khi nào ai đó báo một con số F1, hãy hỏi là loại nào.

Đó là phần cuối của mô hình hóa. Phần còn lại của chương nói về các con số.

Lấy binary model đã train và tạo hai biến thể bằng cách nhân mọi logit với một hằng số: 0,35 cho phiên bản do dự, 4 cho phiên bản quá tự tin. Nhân với số dương không thể đổi dấu nào, nên cả ba model dự đoán chính xác cùng một nhãn cho toàn bộ 4.000 chi tiết test. Accuracy không phân biệt được chúng. Cross-entropy thì không gặp khó khăn:

modelaccuracycross-entropymean loss when rightmean loss when wrongworst single loss
hesitant (logits × 0.35)0.98300.15490.13691.19902.80
as trained0.98300.05640.01472.46897.82
overconfident (logits × 4)0.98300.15630.00099.142727.63

Model do dự trả một khoản thuế nhỏ trên mọi chi tiết, kể cả hàng nghìn chi tiết nó làm đúng. Model quá tự tin gần như miễn phí khi đúng và thảm họa khi sai — một chi tiết trong test set đó tự nó đã tốn 27,63 nats. Hai model đến gần cùng tổng chi phí bằng hai con đường ngược nhau, còn model đã train, có xác suất được calibrated theo dữ liệu, nằm thấp hơn cả hai khoảng ba lần.

Đây là cách sắc nhất để nói khác biệt giữa lossmetric. Loss là thứ bạn optimize: nó phải differentiable, và nó thấy mọi điều model đã nói, kể cả model chắc đến mức nào. Metric là thứ bạn bị đánh giá: nó có thể là step function, business rule, hay số lỗi bị bỏ sót. Chúng không phải cùng một vật và không luôn đồng ý — đó là lý do bạn định nghĩa cả hai trước khi bắt đầu, và không bao giờ để loss đóng vai metric chỉ vì nó tình cờ hiện trên màn hình.

Trước mọi model, yêu cầu là: câu trả lời lười nhất có thể đạt điểm bao nhiêu? Trên băng chuyền này, luôn nói ổn:

TEXT
always-say-fine baseline: accuracy = 0.9815
confusion (tn, fp, fn, tp) = (3926, 0, 74, 0)

98,15 %. Giờ đến logistic model đã train, ở threshold mặc định 0,5:

TEXT
logistic @0.5: accuracy=0.9830 precision=0.8000 recall=0.1081 F1=0.1905
confusion (tn, fp, fn, tp) = (3924, 2, 66, 8)

98,30 %. Nó thắng baseline 0,15 điểm phần trăm, và bất kỳ báo cáo nào dừng ở accuracy sẽ gọi đó là thắng lợi. Confusion matrix nói điều thật sự đã xảy ra:

predicted finepredicted defective
actually fine3,9242
actually defective668

Nó tìm thấy 8 chi tiết lỗi trong 74 và để lọt 66. Ba con số gọi tên ba cách đọc bảng đó:

  • Precision =TP/(TP+FP)=8/10=0.800= \mathrm{TP}/(\mathrm{TP}+\mathrm{FP}) = 8/10 = 0.800. Trong các chi tiết nó gắn cờ, bao nhiêu cái thật sự lỗi. Đây là chi phí của kiểm tra lãng phí.
  • Recall =TP/(TP+FN)=8/74=0.108= \mathrm{TP}/(\mathrm{TP}+\mathrm{FN}) = 8/74 = 0.108. Trong các chi tiết lỗi, nó bắt được bao nhiêu cái. Đây là chi phí của việc gửi một chi tiết hỏng tới khách hàng.
  • F1 =2PR/(P+R)=0.190= 2PR/(P+R) = 0.190, harmonic mean của hai số trên, nằm gần số nhỏ hơn và vì thế không chịu bị tâng bốc chỉ bởi một trong hai.

Điều nào quan trọng phụ thuộc vào nhà máy, không phụ thuộc vào toán học: một lần kiểm tra tốn vài giây còn lỗi đã gửi đi có thể tốn một thông báo thu hồi, nên ở đây recall chiếm ưu thế và 0,108 là thất bại.

Nhưng model không phải vấn đề. Threshold mới là vấn đề, và threshold không phải một phần của model — nó là quyết định kinh doanh áp dụng sau đó lên một xác suất. Quét nó:

thresholdTPFPFNaccuracyprecisionrecallF1
0.50082660.98300.8000.1080.190
0.2002728470.98120.4910.3650.419
0.10042118320.96250.2630.5680.359
0.05054236200.93600.1860.7300.297
0.0206757070.85580.1050.9050.188
0.005711,36030.65930.0500.9590.094

Đọc cột accuracy từ trên xuống. Nó giảm suốt đường — từ 98,30 % xuống 65,93 % — trong khi model đi từ bắt được 8 lỗi đến bắt được 71 trong 74. Mọi điều hữu ích mà model này có thể làm đều khiến accuracy của nó tệ hơn. Một đội optimize con số headline sẽ ship phiên bản không tìm thấy gì.

Hiện chi tiết

Class weighting không tạo ra tín hiệu, nó dịch chuyển operating point. Phản xạ đầu tiên thường gặp với class mất cân bằng là đặt trọng số cao hơn cho rare class trong loss. Làm vậy, với trọng số 1, 10 và 60 cho positive:

weight on positivesaccuracyprecisionrecallF1AUC
10.98300.8000.1080.1900.9363
100.96050.2530.5810.3520.9361
600.82900.0910.9190.1660.9361

Precision và recall di chuyển rất xa. AUC — xác suất model xếp một chi tiết lỗi ngẫu nhiên cao hơn một chi tiết tốt ngẫu nhiên, hoàn toàn bỏ qua threshold — chỉ dịch 0,0002, tức chẳng là gì. Reweighting chỉ trượt cùng model dọc theo cùng một đường trade-off. Đó thường là điều bạn muốn, và nó không bao giờ là thông tin mới: nếu ranking tệ, không có weighting scheme nào cứu được.

Vì sao ba split mà không phải hai? Vì khoảnh khắc bạn dùng một tập ví dụ để chọn bất cứ thứ gì — threshold, learning rate, model nào trong sáu model để ship — tập đó đã được dùng cho fitting, và điểm của nó không còn unbiased.3 Đo trên băng chuyền này: quét threshold trên validation set chọn 0,196, rồi model đạt F1 = 0,4122 trên test set chưa đụng tới. Nếu quét trực tiếp trên test set, điểm tốt nhất ở đó là 0,4186 — một con số không ai có quyền báo cáo.

Khoảng cách ở đây nhỏ, 0,006, vì đó là một hyperparameter được quét một lần trên 4.000 ví dụ validation. Nó lớn lên với mỗi quyết định thêm và mỗi lần validation set nhỏ đi. Cũng lưu ý rằng hướng không được đảm bảo trong một lần chạy: threshold được chọn đạt 0,3902 trên validation và 0,4122 trên test, nên lần này validation đánh giá thấp nó. Bias có tính hệ thống qua nhiều quyết định, không nhìn thấy trong một lần.4

Giờ đến bài tập. Log của băng chuyền có thêm cột thứ ba, station_seconds: mỗi chi tiết ở trạm kiểm tra bao lâu. Thêm nó chỉ là một thay đổi một dòng trong preprocessing. Đây là kết quả:

modelaccuracyprecisionrecallF1cross-entropyAUC
width + weight0.98300.8000.1080.1900.05640.9363
+ station_seconds0.99200.7920.7700.7810.02360.9970

Recall đi từ 10,8 % lên 77,0 %. F1 tăng hơn bốn lần. Và hãy chú ý accuracy đã làm gì: 98,30 % → 99,20 %, tăng chín phần mười điểm, kiểu con số rất dễ bị làm tròn thành “khoảng 99 % dù sao cũng vậy” trên slide tóm tắt. Accuracy trước đó không thấy thất bại, và giờ không thấy gian lận.

Trước khi đọc tiếp: model đang gian lận. Hãy tìm xem bằng cách nào.

Cách săn leak, theo thứ tự tìm ra nhanh nhất.

  1. So sánh train và test. Overfitting hiện ra như một khoảng cách lớn. Ở đây: model trung thực 0,9838 train / 0,9830 test; model leak 0,9936 train / 0,9920 test. Cả hai gap đều dưới 0,2 điểm. Leak không trông giống overfitting — leaky feature cũng sẵn có ở test time, nên model generalizes rất đẹp vào một thế giới không tồn tại.

  2. Train một model cho từng feature, đơn lẻ. Bất kỳ thứ gì mang câu trả lời sẽ tự khai báo:

    feature aloneaccuracyrecallF1AUC
    width0.98150.0140.0260.8691
    weight0.98150.0000.0000.7914
    station_seconds0.98500.4050.5000.9960

    Một cột, tự nó, xếp hạng lỗi với AUC 0,9960. Hai phép đo bằng thước cặp và cân chỉ đạt 0,87 và 0,79. Sự bất đối xứng đó là chuông báo động.

  3. Hỏi mỗi con số được ghi xuống khi nào. Mean dwell time: 2,23 giây với chi tiết đạt, 15,56 giây với chi tiết trượt. Tất nhiên rồi. Một chi tiết ở lại trạm vì một inspector đã kéo nó khỏi băng chuyền — điều xảy ra sau, và chỉ vì, ai đó quyết định nó bị lỗi. Cột đó không phải phép đo của chi tiết. Nó là phép đo của phán quyết.

the planted leakPYTHON
station = 1.8 + rng.exponential(0.35, N)                     # a part just passing through
audited = rng.random(N) < 0.006                              # random spot checks
station[audited] += rng.uniform(6.0, 26.0, audited.sum())
station[y == 1] = 9.0 + rng.exponential(7.0, (y == 1).sum())  

Dòng được tô sáng là leak: dwell time của một chi tiết lỗi được rút từ một phân phối khác, vì con người đã lấy nó khỏi băng chuyền. Đây là bug nghiêm trọng phổ biến nhất trong applied machine learning, và nó có tên: target leakage — thông tin trong training features sẽ không có sẵn tại khoảnh khắc cần đưa ra prediction.5 Nó không ném exception. Nó tạo ra một con số tốt hơn. Mọi incentive trong một dự án đều chỉ về phía giữ nó lại.

Phòng thủ là một câu hỏi, hỏi với mọi cột: tại đúng thời điểm tôi cần prediction này, giá trị này đã tồn tại chưa? Trên băng chuyền live, station_seconds chưa biết cho đến sau khi chi tiết được kiểm tra — chính là việc model lẽ ra phải thay thế.

Giả sử bạn chấm một model trên 20 ví dụ và nó đúng 17. Bạn báo 85 %.

TEXT
17 correct out of 20 -> accuracy 0.8500
  Wilson    95% CI : [0.6396, 0.9476]
  bootstrap 95% CI : [0.7000, 1.0000]
  P(a 65% model scores 17 or more out of 20) = 0.0444
  P(an 85% model scores 17 or more out of 20) = 0.6477

Cách đọc trung thực của 17/20 là đâu đó giữa 64 % và 95 %. Một model thật sự 65 % tạo ra kết quả này 4,4 % thời gian — một lần trong hai mươi ba — và nếu bạn thử vài prompt rồi báo cái tốt nhất, bạn đã tự sản xuất lần chạy đó. Mười bảy trên hai mươi không phân biệt được model 85 % với model 65 %.

Hai cách đặt interval lên một rate, và cả hai đều nên có trong bộ công cụ của bạn:

uncertainty.pyPYTHON
def wilson(k, n, z=1.959963985):
    """95% interval for k successes in n trials. Correct at small n; no simulation."""
    ph, d = k / n, 1 + z * z / n
    centre = (ph + z * z / (2 * n)) / d
    half = z * (ph * (1 - ph) / n + z * z / (4 * n * n)) ** 0.5 / d
    return centre - half, centre + half


def bootstrap_ci(correct, n_resamples=10_000, alpha=0.05, seed=0):
    """95% interval for the mean of any per-example score array. Works on F1 too."""
    rng = np.random.default_rng(seed)
    correct = np.asarray(correct, dtype=float)
    draws = correct[rng.integers(0, len(correct), size=(n_resamples, len(correct)))]
    lo, hi = np.quantile(draws.mean(axis=1), [alpha / 2, 1 - alpha / 2])
    return float(correct.mean()), float(lo), float(hi)

Dùng Wilson6 cho một success rate đơn giản; nó vẫn cư xử tốt ở mọi nn và không cần randomness. Lưu ý ở trên rằng tại n=20n = 20, đầu trên của bootstrap là 1,0000 — resampling 20 điểm rất dễ rút ra 20 điểm đúng, nên nó không thể biểu diễn một interval hẹp hơn granularity của chính nó. Dùng bootstrap7 khi không có công thức, tức hầu hết các trường hợp thú vị: F1, macro-averages, BLEU, pass@1, điểm của rubric-based judge. Trên băng chuyền này, F1 0,4122 của model đã tune mang bootstrap interval [0.3009, 0.5156] — đó mới là con số nên xuất hiện trong báo cáo, vì point estimate một mình mời gọi một so sánh mà nó không đủ sức hỗ trợ.

Một phép đo nữa, vì nó thay đổi cách bạn nên so sánh hai model. Hai model được chấm trên cùng 500 ví dụ:

TEXT
model A: 0.8580  95% CI [0.8260, 0.8880]
model B: 0.8120  95% CI [0.7780, 0.8460]
the two intervals overlap: True
paired difference A-B: 0.0460  95% CI [0.0260, 0.0680]
they disagree on 31 of 500 examples (A right 27, B right 4)

Intervals của chúng overlap, và quy tắc dân gian — error bars overlap nghĩa là không có khác biệt đáng kể — sẽ gọi so sánh này là chưa kết luận. Không phải vậy. Hai model chạy trên cùng ví dụ, nên đại lượng đúng là chênh lệch theo từng ví dụ, có interval là [0.0260, 0.0680], nằm thoải mái trên 0. Chúng bất đồng chỉ trên 31 trong 500 item, và A thắng 27 bất đồng đó; các ví dụ chung, cả dễ lẫn khó, triệt tiêu thay vì thêm nhiễu. Hãy so sánh model theo cặp, và bạn đạt cùng kết luận với một phần nhỏ dữ liệu.

Giờ bạn có một model xuất ra xác suất đã calibrated, một loss được suy ra từ một claim về dữ liệu thay vì chọn cho tiện, một gradient đúng nghĩa là dự đoán trừ sự thật, và — quan trọng hơn — bộ máy để biết liệu bất kỳ điều nào trong đó có hoạt động hay không. Wilson interval mười dòng ở trên được dùng lại nguyên văn: nó mang các biến thể prompt trong Chương 15, các bảng retrieval trong Chương 19, và golden set trong Chương 29. Bootstrap là thứ bạn dùng khi không có công thức.

Nhưng model vẫn chỉ có một layer. Nó vẽ một đường, và Chương 1 đã chứng minh bằng bốn hàng XOR rằng một đường là không đủ. Cách sửa là xếp chồng: một layer đầu bẻ cong không gian, một layer thứ hai vẽ đường trong không gian đã bị bẻ cong.

Đó là nơi gradient gọn gàng của chương này hết tác dụng. Mọi thứ ở trên hoạt động vì L/s=py\partial L/\partial s = p - y có thể được viết tay, một lần, cho một model có một layer giữa input và loss. Đặt layer thứ hai vào giữa và câu hỏi đổi hình dạng: derivative của loss theo một weight không chạm trực tiếp output là gì — một weight có ảnh hưởng chỉ đi qua layer khác, có thể theo nhiều đường cùng lúc?

Derivative đó tồn tại. Tính nó bằng tay là vô vọng với bất cứ thứ gì lớn hơn đồ chơi, và tính từng parameter một thì vô vọng ở một quy mô khác. Thứ cần thiết là một quy trình lấy mọi derivative trong network từ một backward pass duy nhất trên cùng graph mà forward pass vừa đi qua.

Đó là Chương 5, và nó là động cơ vận hành phần còn lại của khóa học.


Cũng đáng đọc song song với chương này: Bishop, Pattern Recognition and Machine Learning §1.2, §1.5, §1.6 và §4.3, bao phủ probability, decision theory, information theory và linear classification theo đúng thứ tự chương này đi theo; Murphy, Probabilistic Machine Learning: An Introduction, chương 6 và 10; Prince, Understanding Deep Learning §5.4–5.7; và Saito và Rehmsmeier, The Precision-Recall Plot Is More Informative than the ROC Plot When Evaluating Binary Classifiers on Imbalanced Datasets (PLOS ONE, 2015) — vì sao AUC được trích ở trên không nên là con số threshold-free duy nhất bạn nhìn vào khi 1,7 % chi tiết bị lỗi.

  1. Ma, T. và Ng, A. CS229 Lecture Notes, Stanford University, chương 2 và 3. Nơi sự triệt tiêu tạo ra pyp - y không còn giống may mắn nữa: chọn phân phối exponential-family khớp với output của bạn, dùng canonical link của nó, và gradient luôn là dự đoán trừ sự thật.

  2. Olah, C. Visual Information Theory (2015), colah.github.io/posts/2015-09-Visual-Information. Lời giải thích rõ nhất hiện có về entropy, cross-entropy và KL divergence như chi phí tính bằng bit thay vì công thức.

  3. Abu-Mostafa, Y. S., Magdon-Ismail, M. và Lin, H.-T. Learning From Data (AMLBook, 2012), bài giảng 13 và 17 của khóa Caltech. Bài 13 là validation; bài 17, về ba nguyên lý học, là nơi data snooping được gọi tên. Giữa hai bài, chúng là nguồn của kỷ luật trong chương này: mỗi lần nhìn vào một data set là một quyết định fitting, dù bạn có chạy optimizer hay không.

  4. James, G., Witten, D., Hastie, T. và Tibshirani, R. An Introduction to Statistical Learning, ấn bản 2 (Springer, 2021), chương 2 và 5, cho bias–variance decomposition và resampling. Tập đồng hành là nơi cái bẫy lựa chọn được nói thẳng: Hastie, Tibshirani và Friedman, The Elements of Statistical Learning, ấn bản 2, §7.10.2, The Wrong and Right Way to Do Cross-validation.

  5. Kaufman, S., Rosset, S., Perlich, C. và Stitelman, O. Leakage in Data Mining: Formulation, Detection, and Avoidance. ACM Transactions on Knowledge Discovery from Data 6(4), 2012. Một xử lý hình thức của thất bại đã minh họa ở trên, với case study từ các cuộc thi mà model thắng vì đã học artefact của cách dữ liệu được lắp ráp.

  6. Wilson, E. B. Probable Inference, the Law of Succession, and Statistical Inference. Journal of the American Statistical Association 22(158), tr. 209–212 (1927). Score interval dùng trong wilson() ở trên, vẫn là mặc định đúng cho một proportion. Textbook interval p^±zp^(1p^)/n\hat{p} \pm z\sqrt{\hat{p}(1-\hat{p})/n} là thứ nên tránh: nó cho kết quả vô nghĩa gần 0 và 1, và undercovers nặng khi nn nhỏ.

  7. Efron, B. Bootstrap Methods: Another Look at the Jackknife. The Annals of Statistics 7(1), tr. 1–26 (1979). Ý tưởng cho phép bạn đặt interval lên bất kỳ statistic nào bạn tính được, kể cả những statistic không có sampling theory.

Sẵn sàng để LIA chọn giúp bạn chưa?

Xây dựng cùng mọi mô hình AI ở một nơi — bắt đầu miễn phí ngay hôm nay.