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

Hàm mất mát đến từ đâu: likelihood, không phải quy ước

Ba đường kẻ tay trên cùng 20 phép đo, ba quy tắc chấm điểm chọn ba người thắng khác nhau. Squared error là một lựa chọn.

Trên trang này

Lưỡi dao cắt chi tiết đang mòn dần. Trong một ca mười giờ, nó mất đủ độ sắc để các chi tiết rời băng chuyền rộng hơn ban đầu một phần nhỏ của milimét, và một khi chúng vượt quá 23,5 milimét, bộ phận kiểm tra sẽ loại. Không ai trong nhà máy biết chuyện đó xảy ra lúc nào. Thứ họ có là một thước cặp, một cuốn sổ, và hai mươi số đọc từ thứ Ba tuần trước: số giờ kể từ khi thay lưỡi dao, và bề rộng của chi tiết được đo tại thời điểm đó.

Có người vẽ một đường qua các điểm. Người khác vẽ một đường hơi khác. Người thứ ba vẽ đường thứ ba. Cả ba đều trông hợp lý trên giấy, và chúng bất đồng với nhau vài giờ về thời điểm cần thay lưỡi dao — ở nhà máy này, đó là khác biệt giữa một tuần yên ổn và một lô hàng bị loại.

Đường nào tốt hơn?

Như đã nêu, câu hỏi đó không có câu trả lời. Không phải một câu trả lời khó — mà là không có câu trả lời nào cả. "Tốt hơn" không phải là một thuộc tính của một đường giống như độ dốc của nó; nó là thuộc tính của một đường cùng với một quy tắc để chấm điểm các đường, và cho đến khi có ai đó viết quy tắc đó ra thì chẳng có gì để tính. Chương này xem câu ấy là nghiêm túc, và kết thúc bằng phát hiện rằng quy tắc phổ biến nhất trong machine learning không phải là một quy ước mà là hệ quả của một tuyên bố về thế giới — một tuyên bố bạn có thể kiểm tra, và đôi khi là sai.

Một lời thú nhận trước dòng code đầu tiên. Hai mươi số đọc này không đến từ một nhà máy thật: tôi tạo chúng từ một đường do tôi chọn, width=20.00+0.30h\text{width} = 20.00 + 0.30 \cdot h, cộng với nhiễu ngẫu nhiên có độ phân tán khoảng một phần mười milimét. Điều đó quan trọng, vì mọi thứ bên dưới đều nói về việc liệu một phương pháp có khôi phục được sự thật hay không, và cách duy nhất để kiểm tra điều đó là biết trước sự thật. Vậy nên: 0,30 milimét mỗi giờ là đáp án ở cuối sách. Bạn không được phép dùng nó, chỉ được kiểm tra đối chiếu với nó.

Đây là các số đọc và ba đường, được chấm theo ba cách: squared error, thứ ai cũng nghĩ đến; absolute error, thứ một nhà thống kê có thể chọn; và worst error, thứ người thợ máy sẽ chọn, vì người kiểm tra không quan tâm đến trung bình của bạn — anh ta loại đúng chi tiết đơn lẻ vượt dung sai.

NumPy xuất hiện ở đây, một chương sau perceptron thuần Python, vì một lý do: đến cuối chương này, chúng ta đánh giá bốn trăm nghìn đường ứng viên trên hai mươi số đọc mỗi đường, và vòng lặp Python là công cụ sai cho việc đó. Nó cũng là ký pháp mà mọi nguồn được trích dẫn bên dưới sử dụng.

loss.pyPYTHON
import numpy as np

# Hours since the blade was changed, and the width of the part measured then.
SHIFT = np.array([
    (0.5, 20.17), (1.0, 20.28), (1.5, 20.53), (2.0, 20.61), (2.5, 20.69),
    (3.0, 20.94), (3.5, 21.21), (4.0, 21.31), (4.5, 21.27), (5.0, 21.35),
    (5.5, 21.58), (6.0, 21.80), (6.5, 21.67), (7.0, 22.07), (7.5, 22.10),
    (8.0, 22.31), (8.5, 22.48), (9.0, 22.66), (9.5, 22.90), (10.0, 23.13),
])
h, y = SHIFT[:, 0], SHIFT[:, 1]

LINES = {"A": (20.10, 0.26), "B": (20.20, 0.28), "C": (20.30, 0.26)}

for name, (a, b) in LINES.items():
    r = y - (a + b * h)                                      
    print(f"{name}   mean square {np.mean(r**2):.5f}"
          f"   mean absolute {np.mean(np.abs(r)):.5f}"
          f"   worst {np.max(np.abs(r)):.3f}")               

Đại lượng trong các dòng được tô sáng là phần dư: điều đường dự đoán trừ đi điều thước cặp đo được, một số cho mỗi số đọc. Mọi quy tắc chấm điểm trong chương này, và mọi loss function trong hai mươi tám chương sau nó, đều là một cách nào đó để ép một danh sách phần dư xuống thành một con số duy nhất. Chúng chỉ khác nhau ở cách ép.

TEXT
A   mean square 0.02699   mean absolute 0.12600   worst 0.430
B   mean square 0.02524   mean absolute 0.13700   worst 0.350
C   mean square 0.03179   mean absolute 0.15000   worst 0.320

Hãy đọc các cột, không phải các hàng. Squared error nói B, absolute error nói A, worst error nói C: ba quy tắc, ba người thắng, trên cùng hai mươi điểm.

Tôi chọn ba đường này sao cho chúng bất đồng, và cần nói rõ như vậy. Điểm đáng chú ý là việc đó dễ đến mức nào — chỉ vài phút tìm kiếm trên các hệ số chặn và độ dốc trông hợp lý đã lộ ra hàng trăm bộ ba như thế. Thứ hạng là thuộc tính của quy tắc bạn chọn, không phải một sự thật về các đường, nên quy tắc không phải chi tiết triển khai: nó chính là định nghĩa của bài toán. Điều đó đặt ra câu hỏi mà chương này tồn tại để trả lời: bạn chọn nó dựa trên cơ sở nào?

Trước hết là một chuyện nhỏ hơn, vì không chỉ có ba đường mà có vô hạn đường. Tạm lấy squared error, vì đó là thứ ai cũng lấy, và thu nhỏ bài toán còn một con số bằng mẹo đã cứu perceptron khỏi mười một nghìn epoch trong Chapter 1: trừ trung bình khỏi cả hai cột. Một khi đám mây điểm được đưa về tâm ở gốc tọa độ, đường tốt nhất theo squared error đi chính xác qua gốc — vậy hệ số chặn đã được quyết định và chỉ còn độ dốc cần chọn.

loss.py (continued)PYTHON
u, v = h - h.mean(), y - y.mean()        # 5.25 hours, 21.553 mm

def mse(theta):
    return np.mean((v - theta * u) ** 2)

grid = np.arange(0.0, 0.6001, 0.001)
curve = np.array([mse(t) for t in grid])
print(grid.size, "candidates ->", f"theta={grid[curve.argmin()]:.3f}", f"mse={curve.min():.6f}")
TEXT
601 candidates -> theta=0.293 mse=0.010115

Sáu trăm lẻ một độ dốc ứng viên, một người thắng: 0,293 milimét mỗi giờ so với sự thật 0,300. Hai mươi số đọc nhiễu và một vòng for đã đến trong phạm vi một phần trăm milimét mỗi giờ — hai phẩy ba phần trăm.

Phần thú vị không phải là người thắng mà là hình dạng của phép tìm kiếm. In toàn bộ đường cong, xoay lại để loss chạy từ trái sang phải:

loss.py (continued)PYTHON
ts = np.arange(0.0, 0.6001, 0.04)
ls = np.array([mse(t) for t in ts])
for t, l in zip(ts, ls):
    col = round(l / ls.max() * 50)
    print(f"theta={t:.2f} |{' ' * col}*{' ' * (50 - col)}| mse={l:7.4f}")
TEXT
theta=0.00 |                                              *    | mse= 0.7244
theta=0.04 |                                  *                | mse= 0.5428
theta=0.08 |                        *                          | mse= 0.3878
theta=0.12 |                *                                  | mse= 0.2593
theta=0.16 |          *                                        | mse= 0.1575
theta=0.20 |     *                                             | mse= 0.0822
theta=0.24 |  *                                                | mse= 0.0336
theta=0.28 | *                                                 | mse= 0.0116
theta=0.32 | *                                                 | mse= 0.0161
theta=0.36 |   *                                               | mse= 0.0473
theta=0.40 |       *                                           | mse= 0.1050
theta=0.44 |            *                                      | mse= 0.1894
theta=0.48 |                   *                               | mse= 0.3004
theta=0.52 |                            *                      | mse= 0.4379
theta=0.56 |                                      *            | mse= 0.6021
theta=0.60 |                                                  *| mse= 0.7928

Đó là một thung lũng, nhìn từ bên cạnh. Nó có một đáy, các vách tăng mượt mà ở cả hai phía, và — đây là phần mà chiếc cầu thang của Chapter 1 không thể cho — tại mọi điểm đơn lẻ trên nó đều có một hướng "xuống dốc" được xác định rõ. Hãy nhớ hình dạng đó. Chapter 3 hoàn toàn nói về việc đi xuống nó mà không ghé qua cả sáu trăm lẻ một điểm, và về điều gì thay đổi khi một thung lũng có nhiều hơn một đáy.

Chúng ta có một thung lũng vì đã bình phương. Absolute error sẽ tạo ra một góc gãy ở đáy; worst error sẽ tạo ra những đoạn phẳng nơi dịch chuyển đường không làm thay đổi gì cả. Bình phương rõ ràng là tiện — và sự tiện lợi đại khái là lý do hầu hết khóa học đưa ra, được mặc bốn bộ áo: nó làm lỗi thành dương (giá trị tuyệt đối cũng vậy); nó phạt lỗi lớn nặng hơn (tại sao nên vậy?); nó khả vi (lũy thừa bậc bốn cũng vậy); nó là thứ mọi người dùng (đúng, và đó không phải một lập luận).

Đây là lập trường trung thực. Squared error chọn đường B và absolute error chọn đường A. Một trong hai đúng cho nhà máy này và cái còn lại sai, và không điều gì đã nói đến giờ có thể cho bạn biết cái nào. Để chọn quy tắc, bạn cần biết điều gì đó về cách các số đọc trở nên khác với đường, và đó là một câu hỏi về thế giới, không phải về toán học. Trả lời nó cần một mảnh máy móc nhỏ.

Đây là tuyên bố biến "đường nào tốt hơn" thành một câu hỏi có câu trả lời.

Giả sử bề rộng của một chi tiết là đường cộng với một lỗi ngẫu nhiên, và giả sử lỗi đó được rút từ một Gaussian — đường cong hình chuông — có trung bình bằng không và độ lệch chuẩn σ\sigma:

yi=θxi+εi,εiN(0,σ2)y_i = \theta x_i + \varepsilon_i, \qquad \varepsilon_i \sim \mathcal{N}(0, \sigma^2)

Mật độ của Gaussian là

p(ε)=1σ2πexp ⁣(ε22σ2)p(\varepsilon) = \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{\varepsilon^2}{2\sigma^2}\right)

Bây giờ làm một việc mà perceptron không thể. Với một độ dốc ứng viên cho trước θ\theta, mỗi số đọc có một phần dư, và công thức trên biến phần dư đó thành một con số: nếu độ dốc này là sự thật, thì một lỗi đúng bằng kích thước đó hợp lý đến mức nào? Một số đọc nằm trên đường nhận số lớn, một số đọc lệch nửa milimét nhận số nhỏ.

Các số đọc độc lập — thước cặp không nhớ chi tiết trước đó — nên quy tắc tích nói rằng độ hợp lý của toàn bộ cuốn sổ là tích của các mật độ riêng lẻ. Tích đó là likelihood của θ\theta.1 Hãy chú ý chiều, vì đó là chiều mà quy tắc Bayes nói tới: dữ liệu là cố định và đã biết, còn tham số là thứ thay đổi. Đây không phải "xác suất của độ dốc". Nó là xác suất mà model gán cho dữ liệu bạn thật sự nhận được, đọc như một hàm của độ dốc.

likelihood.pyPYTHON
SIGMA = 0.12

def gaussian(r, sigma):
    return np.exp(-r ** 2 / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))

def likelihood(theta):
    return np.prod(gaussian(v - theta * u, SIGMA))          

for t in (0.25, 0.293, 0.35):
    print(f"theta={t}   likelihood = {likelihood(t):.6g}")
TEXT
theta=0.25   likelihood = 521.952
theta=0.293   likelihood = 2.42028e+07
theta=0.35   likelihood = 0.190312

Độ dốc 0,293 khiến cuốn sổ này hợp lý hơn 0,25 bốn mươi sáu nghìn lần, và hợp lý hơn 0,35 một trăm hai mươi bảy triệu lần. Maximum likelihood là nguyên tắc rằng bạn chọn tham số khiến điều bạn thực sự quan sát được trở nên ít đáng ngạc nhiên nhất có thể. Nó không phải một định lý mà là một đề xuất về việc "tốt nhất" nên nghĩa là gì — một đề xuất có nội dung, vì nó buộc bạn phải nêu giả định của mình về nhiễu trước khi được phép chấm điểm bất cứ thứ gì.

Chạy cùng ba dòng code trên một tháng ca làm thay vì một ca, và phương pháp sụp đổ.

likelihood.py (continued)PYTHON
rng = np.random.default_rng(7)
u_big = rng.uniform(-5.25, 5.25, 2000)                     # 2000 readings, not 20
v_big = 0.30 * u_big + 0.12 * rng.standard_normal(2000)

print("2000 readings, sigma = 0.12 mm :", np.prod(gaussian(v_big - 0.30 * u_big, 0.12)))
noisy = 0.30 * u_big + 2.0 * rng.standard_normal(2000)
print("2000 readings, sigma = 2.00 mm :", np.prod(gaussian(noisy - 0.30 * u_big, 2.0)))
print("largest float64 :", np.finfo(np.float64).max)
TEXT
RuntimeWarning: overflow encountered in reduce
2000 readings, sigma = 0.12 mm : inf
2000 readings, sigma = 2.00 mm : 0.0
largest float64 : 1.7976931348623157e+308

Hai nghìn phép nhân và đáp án là inf. Đổi một hằng số — một thước cặp cẩu thả hơn, nên các mật độ nhỏ hơn 1 thay vì lớn hơn — và cùng đoạn code trả về 0.0. Cả hai đáp án đều sai, theo hai hướng ngược nhau, không cái nào ném exception để bạn bắt, và cái thứ hai thậm chí không in cảnh báo.

Toán học không có gì sai. Likelihood ở các thiết lập đó là một số hữu hạn được xác định hoàn toàn: logarithm tự nhiên của nó là 1400,91, nên bản thân con số khoảng 1060810^{608}. Vấn đề là máy tính của bạn không có con số đó, và rất đáng hiểu chính xác nó có những con số nào, vì đây không phải lần cuối nó quyết định kết quả.

Cách sửa tích nổ tung là cách quen thuộc: lấy logarithm. Logarithm biến tích thành tổng, nó tăng nghiêm ngặt nên không thể dịch chuyển vị trí của cực đại, và một tổng của hai nghìn số vừa phải là thứ float64 xử lý không phàn nàn. Theo quy ước, ta lấy negative log-likelihood, để tốt hơn nghĩa là nhỏ hơn. Bây giờ thay mật độ Gaussian vào và xem chuyện gì xảy ra.

  1. Bắt đầu từ tích. Likelihood là L(θ)=i=1Np(yiθxi)\mathcal{L}(\theta) = \prod_{i=1}^{N} p(y_i - \theta x_i), với pp là mật độ Gaussian ở trên.

  2. Lấy trừ log. Tích trở thành tổng, và hàm mũ trong mật độ triệt tiêu thẳng với logarithm:

logL(θ)=N2log ⁣(2πσ2)+12σ2i=1N(yiθxi)2-\log \mathcal{L}(\theta) = \frac{N}{2}\log\!\left(2\pi\sigma^2\right) + \frac{1}{2\sigma^2}\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2
  1. Vứt bỏ mọi thứ không chứa θ\theta. Hạng đầu là một hằng số. 1/2σ21/2\sigma^2 trước tổng là một hằng số dương, và nhân một hàm với hằng số dương không thể dịch chuyển nơi cực tiểu của nó. Thứ còn lại là
i=1N(yiθxi)2\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2

chính là tổng các phần dư bình phương — thứ chúng ta bắt đầu chương này cùng với nó vì đó là thứ đầu tiên ai cũng nghĩ đến.

Đó là kết quả mà chương này tồn tại để đưa ra, và nó đáng được phát biểu không rào đón: squared error không phải một quy ước. Nó là negative log-likelihood của một Gaussian, sau khi bỏ các hằng số. Tối thiểu hóa squared error chính xác là cùng một hành động với việc khẳng định lỗi của bạn là Gaussian và hỏi tham số nào khiến dữ liệu của bạn ít đáng ngạc nhiên nhất. Bạn đã luôn khẳng định điều đó; chỉ là không ai nói với bạn.

Tương đương này có thể kiểm tra được, vậy hãy kiểm tra: quét cùng sáu trăm lẻ một độ dốc với negative log-likelihood đầy đủ, gồm cả hằng số, và với squared error thuần.

likelihood.py (continued)PYTHON
N = v.size

def nll(theta):
    r = v - theta * u
    return N * np.log(SIGMA * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * SIGMA ** 2)

nlls = np.array([nll(t) for t in grid])
mses = np.array([mse(t) for t in grid])
print(f"argmin of the negative log-likelihood : theta={grid[nlls.argmin()]:.3f}  nll={nlls.min():.6f}")
print(f"argmin of the mean squared error      : theta={grid[mses.argmin()]:.3f}  mse={mses.min():.6f}")
print("same index:", nlls.argmin() == mses.argmin())
TEXT
argmin of the negative log-likelihood : theta=0.293  nll=-17.001977
argmin of the mean squared error      : theta=0.293  mse=0.010115
same index: True

Các con số khác nhau trên trục dọc, và một trong chúng là âm, điều mà một tổng bình phương không bao giờ có: negative log-likelihood có thể xuống dưới không, vì mật độ có thể vượt quá 1. Cùng đáy của cùng thung lũng, đến đúng điểm lưới cuối cùng.

Hiện toàn bộ phần suy diễn

Chính xác thì những phần bỏ đi nào là an toàn? Cùng thao tác này xuất hiện trong mọi chương suy ra một loss, và không phải lúc nào nó cũng vô hại.

Bỏ một hằng số cộng là an toàn bất cứ khi nào nó không phụ thuộc vào tham số bạn đang tối ưu, và bỏ một hằng số nhân dương là an toàn vì argminθcf(θ)=argminθf(θ)\arg\min_\theta c\,f(\theta) = \arg\min_\theta f(\theta) với mọi c>0c > 0. Cả hai đều thất bại ngay khi σ\sigma cũng đang được fit: khi đó N2log(2πσ2)\frac{N}{2}\log(2\pi\sigma^2) hoàn toàn không phải hằng số, nó là hạng ngăn model tuyên bố σ=0\sigma = 0 và likelihood vô hạn. Đó chính xác là phần tiếp theo.

Chúng lại thất bại theo cách khác ở Chapter 3: một hằng số nhân không dịch chuyển cực tiểu, nhưng nó scale gradient, và gradient bị nhân với learning rate. Chia cho NN để có mean squared error thay vì sum là vô hình với đáp án và cực kỳ hữu hình với lần huấn luyện — với tổng, tăng gấp đôi batch size làm gấp đôi mọi bước bạn đi.

Chúng ta cố định σ\sigma ở 0,12 bằng mệnh lệnh, và không ai ở nhà máy biết độ phân tán lỗi của thước cặp. Hãy xem nó như ẩn số thứ hai và để maximum likelihood quyết định cả nó. Ở đây hạng hằng số vừa bị bỏ quay trở lại, vì nó là thứ duy nhất đứng giữa model và tuyên bố về độ chính xác hoàn hảo.

likelihood.py (continued)PYTHON
r = v - 0.293 * u
sigmas = np.arange(0.01, 1.0001, 0.0001)
nll_sigma = N * np.log(sigmas * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * sigmas ** 2)

print("best sigma on the grid       :", round(float(sigmas[nll_sigma.argmin()]), 4))
print("sqrt(mean squared residual)  :", round(float(np.sqrt(np.mean(r ** 2))), 4))
TEXT
best sigma on the grid       : 0.1006
sqrt(mean squared residual)  : 0.1006

Hai kết quả khớp đến bốn chữ số thập phân, và không phải tình cờ: lấy đạo hàm biểu thức đó và đặt bằng không cho σ^2=1Nri2\hat{\sigma}^2 = \frac{1}{N}\sum r_i^2 chính xác. Vậy mean squared error không chỉ giống một phương sai. Theo model này, nó chính là ước lượng maximum-likelihood của phương sai của nhiễu — con số bạn đã tối thiểu hóa bấy lâu là một ước lượng về mức nhiễu của cảm biến.

Một nếp gấp, nói thì rẻ mà về sau tự khám phá lại thì đắt: ước lượng đó bị chệch thấp, vì các phần dư được đo so với một fit vốn được chọn để làm chúng nhỏ. Hãy mô phỏng — hai trăm nghìn cuốn sổ, mỗi cuốn hai mươi số đọc, rút từ một phân phối có phương sai thật đúng bằng 1, với một tham số của fit được ước lượng từ chính các số đọc. Chia tổng bình phương cho NN cho trung bình 0,9501; chia cho N1N-1 cho 1,0001; và (N1)/N(N-1)/N đúng bằng 0,95. Mỗi tham số bạn fit tiêu tốn một bậc tự do, và đây là trường hợp nhỏ nhất có thể thấy của một vấn đề lớn hơn nhiều: một model luôn trông tốt hơn trên dữ liệu mà nó được fit. Chapter 4 biến điều đó thành kỷ luật giữ lại dữ liệu, và Chapter 6 đặt tên cho hiệu ứng ấy.

Nếu squared error khẳng định rằng nhiễu là Gaussian, câu hỏi tiếp theo là điều gì xảy ra khi khẳng định đó sai. Không phải hơi sai — mà sai theo cách các phép đo thật thường sai.

Trên sàn xưởng, hầu hết số đọc thước cặp chính xác đến một phần mười milimét, và một hoặc hai lần mỗi ca, một mảnh phoi lọt dưới mỏ kẹp khiến số đọc lệch vài milimét. Những lỗi như vậy là đuôi nặng: hầu hết thời gian thì nhỏ, thỉnh thoảng khổng lồ, và khổng lồ thường xuyên hơn nhiều so với điều đường cong chuông cho phép. Phân phối Cauchy là model sạch tiêu chuẩn cho hành vi đó, và mật độ của nó đơn giản như của Gaussian:

p(ε)=1πs(1+(ε/s)2)p(\varepsilon) = \frac{1}{\pi s \left(1 + (\varepsilon/s)^2\right)}

Khác biệt nằm ở phần đuôi: Gaussian giảm như eε2e^{-\varepsilon^2}, nhanh tàn nhẫn, còn Cauchy như 1/ε21/\varepsilon^2, gần như không giảm. Hệ quả dễ thấy hơn dễ nói:

PYTHON
rng = np.random.default_rng(3)
g = 0.12 * rng.standard_normal(10 ** 6)          # Gaussian noise
c = 0.12 * rng.standard_cauchy(10 ** 6)          # Cauchy noise, same scale
for k in (10 ** 2, 10 ** 3, 10 ** 4, 10 ** 5, 10 ** 6):
    print(f"{k:>9,} samples   gaussian var {g[:k].var():.4f}   cauchy var {c[:k].var():10.2f}")
TEXT
      100 samples   gaussian var 0.0164   cauchy var       0.26
    1,000 samples   gaussian var 0.0146   cauchy var      59.88
   10,000 samples   gaussian var 0.0145   cauchy var     358.17
  100,000 samples   gaussian var 0.0144   cauchy var    3097.98
1,000,000 samples   gaussian var 0.0144   cauchy var   32886.10

Phương sai mẫu của Gaussian ổn định ở 0,0144, tức 0.1220.12^2, và ở đó. Cauchy thì tăng, và tiếp tục tăng chừng nào bạn còn lấy mẫu, vì không có gì để nó hội tụ tới: phân phối Cauchy không có phương sai, và cũng không có trung bình. Squared error, thứ toàn bộ công việc là tối thiểu hóa trung bình các bình phương, đang bị yêu cầu một đại lượng không tồn tại.

Vậy đây là một ca mà thước cặp bị đánh lừa. Cùng hai mươi giờ, cùng lưỡi dao, cùng độ trôi 0,30 milimét mỗi giờ — chỉ có nhiễu giờ là Cauchy. Fit nó hai lần: một lần bằng cách tối thiểu hóa phần dư bình phương, một lần bằng cách tối thiểu hóa negative log-likelihood của nhiễu thật sự đã sinh dữ liệu. Mẹo đưa về tâm không giúp gì ở đây — nó chỉ ghim hệ số chặn cho squared error — nên cả hai fit đều dùng brute force trên một lưới hệ số chặn độ dốc, vì chúng ta vẫn chưa có cách tìm đáy thung lũng ngoài việc ghé thăm nó.

swarf.pyPYTHON
SWARF = np.array([
    (0.5, 20.08), (1.0, 21.95), (1.5, 20.86), (2.0, 27.51), (2.5, 20.64),
    (3.0, 20.75), (3.5, 21.01), (4.0, 21.03), (4.5, 21.37), (5.0, 20.60),
    (5.5, 22.03), (6.0, 21.95), (6.5, 21.98), (7.0, 22.01), (7.5, 21.73),
    (8.0, 22.97), (8.5, 22.60), (9.0, 22.66), (9.5, 22.44), (10.0, 22.78),
])
hs, ys = SWARF[:, 0], SWARF[:, 1]

A = np.arange(18.0, 22.001, 0.005)      # 801 intercepts
B = np.arange(-0.20, 0.8001, 0.002)     # 501 slopes
R = ys - (A[:, None, None] + B[None, :, None] * hs)      # every line against every point

SCALE = 0.12
square = np.sum(R ** 2, axis=2)                          # least squares          
cauchy = np.sum(np.log(1 + (R / SCALE) ** 2), axis=2)    # Cauchy likelihood      

for name, surface in (("least squares", square), ("Cauchy likelihood", cauchy)):
    i, j = np.unravel_index(surface.argmin(), surface.shape)
    print(f"{name:>18}:  width = {A[i]:.3f} + {B[j]:.4f} * hours"
          f"   -> 23.5 mm at hour {(23.5 - A[i]) / B[j]:.2f}")
print(f"{'the truth':>18}:  width = 20.000 + 0.3000 * hours"
      f"   -> 23.5 mm at hour {(23.5 - 20.0) / 0.30:.2f}")
print(f"{A.size * B.size:,} candidate lines evaluated")

Hai dòng được tô sáng là toàn bộ khác biệt giữa các fit. Lấy log của mật độ Cauchy, bỏ các hằng số đúng như trước, và log(1+(r/s)2)\sum \log\left(1 + (r/s)^2\right) là thứ còn lại. Cùng công thức, tuyên bố khác về nhiễu.

TEXT
     least squares:  width = 21.380 + 0.1080 * hours   -> 23.5 mm at hour 19.63
 Cauchy likelihood:  width = 19.935 + 0.3020 * hours   -> 23.5 mm at hour 11.80
         the truth:  width = 20.000 + 0.3000 * hours   -> 23.5 mm at hour 11.67
401,301 candidate lines evaluated

Least squares báo độ trôi 0,108 milimét một giờ, khoảng một phần ba tốc độ thật, và kết luận rằng lưỡi dao còn dùng tốt đến giờ 19,6. Đáp án thật là giờ 11,7. Nếu hành động theo fit đó, nhà máy chạy máy ép thêm tám giờ để tạo ra các chi tiết vượt dung sai, dựa trên thẩm quyền của loss function tiêu chuẩn nhất trong lĩnh vực. Cauchy fit, dùng cùng hai mươi số đọc, cùng lưới, và chỉ khác một dòng code, hạ cánh ở giờ 11,8.

Hai phản đối đáng được trả lời, vì cả hai là điều đầu tiên một kỹ sư giỏi sẽ nói.

Điểm ngoại lai quá rõ — cứ xóa nó đi. Bạn có thể, nó giúp, và vẫn chưa đủ. Xóa số đọc tệ nhất duy nhất đưa độ dốc least-squares từ 0,108 lên 0,239, vẫn đặt thời điểm thay lưỡi dao ở giờ 13,1, muộn một tiếng rưỡi; xóa điểm tệ nhất, fit lại, rồi xóa bất kỳ điểm nào tệ nhất bây giờ đưa bạn tới 0,286 — và hãy chú ý rằng đây đã là một thủ tục, không phải một quan sát: nếu thay vào đó xóa hai phần dư lớn nhất của fit ban đầu, bạn hạ cánh ở 0,223. Nhưng giờ bạn đã đưa ra các phán đoán không thể viết xuống hoặc bảo vệ, và tự động hóa quy tắc cũng không cứu nó: drop-the-largest-residual-then-refit, chạy trên một nghìn ca mô phỏng, có sai số độ dốc trung vị 0,0177 so với 0,0100 của likelihood fit, và lệch hơn 0,05 trong 14,7% số ca so với 1,3%. Xóa là một miếng vá chồng lên một giả định sai. Likelihood không cần miếng vá, vì nó chưa bao giờ giả định điểm ngoại lai là bất khả.

Bạn đã chọn một bộ dữ liệu may mắn. Phản đối đó hoàn toàn đúng, và đó là lý do thí nghiệm cuối mô phỏng một nghìn ca độc lập rồi refit theo cả hai cách trên mỗi ca.

swarf.py (continued)PYTHON
A = np.arange(18.0, 22.001, 0.02)        # a coarser grid: a thousand fits to do
B = np.arange(-0.20, 0.8001, 0.005)
lines = A[:, None, None] + B[None, :, None] * hs
rng = np.random.default_rng(2026)
err_sq, err_ca = [], []

for _ in range(1000):                                        # 1000 independent shifts
    ys = 20.00 + 0.30 * hs + SCALE * rng.standard_cauchy(hs.size)
    R = ys - lines
    _, j = np.unravel_index(np.sum(R ** 2, axis=2).argmin(), (A.size, B.size))
    _, q = np.unravel_index(np.sum(np.log1p((R / SCALE) ** 2), axis=2).argmin(), (A.size, B.size))
    err_sq.append(abs(B[j] - 0.30))
    err_ca.append(abs(B[q] - 0.30))

err_sq, err_ca = np.array(err_sq), np.array(err_ca)
for name, e in (("least squares", err_sq), ("Cauchy likelihood", err_ca)):
    print(f"{name:>18}: median slope error {np.median(e):.4f} mm/h"
          f"   off by more than 0.05 in {100 * np.mean(e > 0.05):4.1f}% of shifts"
          f"   worst {e.max():.3f}")
print(f"the likelihood fit is the closer of the two in {100 * np.mean(err_ca < err_sq):.1f}% of shifts")
TEXT
     least squares: median slope error 0.0350 mm/h   off by more than 0.05 in 40.4% of shifts   worst 0.500
 Cauchy likelihood: median slope error 0.0100 mm/h   off by more than 0.05 in  1.3% of shifts   worst 0.090
the likelihood fit is the closer of the two in 75.6% of shifts

Trung vị, không phải trung bình, vì cùng lý do như mọi thứ khác trong phần này: các lỗi least-squares bị một Cauchy chi phối, nên trung bình của chúng không phải thứ ổn định để báo cáo. Least squares sai nặng trong hai ca trên năm; likelihood fit sai nặng trong một ca trên bảy mươi bảy, và thất bại tệ nhất của nó trên một nghìn ca nhỏ hơn một phần năm thất bại tệ nhất của least squares.

Không điều nào trong đây làm squared error trở nên xấu. Nó làm squared error trở nên cụ thể, và số học nói chính xác vì sao. Lấy một phần dư 0,1 mm và một phần dư 7 mm. Sau khi bình phương, số đọc xấu đóng góp vào tổng nhiều gấp 4.900 lần số đọc tốt, nên đường bị kéo nguyên khối về phía nó; dưới Cauchy log-likelihood, hai phần dư đó đóng góp 0,527 và 8,133, tỷ lệ 15,4. Số đọc xấu vẫn được tính, nó chỉ không được quyền quyết định. Đây là khởi đầu của thống kê robust, nơi loss năm 1964 của Huber chia đôi khác biệt bằng cách hành xử bậc hai với phần dư nhỏ và tuyến tính với phần dư lớn,7 và nơi Tukey trước đó đã cho thấy chỉ cần rất ít nhiễm bẩn đã đủ làm phương sai mẫu thành công cụ tệ hơn mean absolute deviation.8

Một ghi chú lịch sử, hay đến mức không nên bỏ qua. Least squares được công bố trước, bởi Legendre năm 1805, như một thiết bị đại số tiện lợi không có biện minh nào ngoài việc nó hiệu quả.9 Bốn năm sau, Gauss chạy lập luận ngược lại: ông xem như đã biết rằng trung bình cộng là cách đúng để kết hợp các phép đo lặp lại, hỏi phân phối lỗi nào khiến trung bình là giá trị có xác suất cao nhất, và chỉ ra rằng về cơ bản chỉ có một phân phối làm được — phân phối nay mang tên ông.10 Suy luận trong chương này là của ông, đã hơn hai thế kỷ tuổi, và vẫn là phần mà hầu hết khóa học bỏ qua.

Đã đạt được. Một loss function là một quy tắc chấm điểm, và thứ hạng nó tạo ra là thuộc tính của quy tắc, không phải của các ứng viên. Mọi loss trong khóa học này là negative log-likelihood của một giả định nào đó về nhiễu, sau khi bỏ các hằng số — Gaussian cho squared error ở đây, Bernoulli cho cross-entropy ở Chapter 4, và một phân phối categorical trên một vocabulary cho next-token loss ở Chapter 8. Công thức không bao giờ đổi: nêu nhiễu, viết likelihood, lấy trừ log. Và khi giả định sai, model không chỉ thiếu chính xác, nó sai theo một hướng bạn có thể dự đoán.

Vẫn còn thiếu. Chúng ta tìm đáy thung lũng bằng cách ghé thăm mọi điểm trong đó. Cách đó hiệu quả với một tham số và sáu trăm ứng viên, và sống sót qua hai tham số ở 401.301 ứng viên trong một phần năm giây. Ba tham số ở cùng độ phân giải là 201.051.801 ứng viên và không còn vừa trong một array; một network nhỏ trong Chapter 5 có hàng nghìn tham số, và các model mà Chapter 10 đặt giá có hàng tỷ. Brute force ở đây không phải chậm, nó bất khả về số học, và không điều gì trong chương này gợi ý một lựa chọn thay thế.

Dù vậy, hãy nhìn lại thung lũng. Đứng tại θ=0.20\theta = 0.20 với loss 0,0822, hướng "xuống dốc" không phải bí ẩn — bạn có thể thấy nó trên trang, đường cong dốc xuống về bên phải. Nếu bạn có thể hỏi loss function rằng nó dốc theo hướng nào tại điểm bạn đang đứng, mà không cần đánh giá nó ở nơi nào khác, bạn có thể bước theo hướng đó, hỏi lại, và lặp lại cho đến khi mặt đất phẳng.

Câu hỏi đó có một cái tên. Độ dốc của một hàm tại một điểm là đạo hàm của nó, và với một hàm nhiều tham số, tập hợp các độ dốc theo mọi hướng cùng lúc là gradient. Chapter 1 không thể dùng gradient, vì lỗi của perceptron là một cầu thang không có độ dốc để hỏi. Chương này đã xây được thứ tốt hơn: một loss trơn ở mọi nơi và đến từ một giả định đã nêu chứ không phải một sở thích.

Vậy câu hỏi cho Chapter 3 không còn là liệu một độ dốc có tồn tại hay không. Nó là cách tính nó, vì sao đi ngược nó lại xuống dốc thay vì lên dốc — một dấu mà hầu như mọi khóa học yêu cầu bạn tin — và bước bao xa trước khi hỏi lại, điều hóa ra là con số duy nhất quyết định một lần huấn luyện hội tụ, dao động quanh đáp án mãi mãi, hay chạy vọt ra vô cực.


Cũng đáng đọc song song với chương này: Prince, Understanding Deep Learning §5.1–5.2 và Appendix C, xây dựng mọi loss trong sách từ maximum likelihood theo thứ tự dùng ở đây; Goodfellow, Bengio và Courville, Deep Learning §3.1–3.11 và §5.5, phần maximum-likelihood cũng suy ra KL divergence mà Chapter 4 cần; Murphy, Probabilistic Machine Learning: An Introduction chapter 2 và §4.2, về những gì maximum likelihood có và không bảo đảm; Deisenroth, Faisal và Ong, Mathematics for Machine Learning §6.1–6.4 cho sum rule, product rule và Bayes' rule được làm đúng; ghi chú ngắn của Tom Mitchell ở CMU Estimating Probabilities: MLE and MAP (2016); và §22.7 của Dive into Deep Learning, đạt cùng kết quả bằng code có thể chạy.

  1. Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222, pp. 309–368 (1922). Nơi likelihood được trình bày như một phương pháp tổng quát, cùng với "parameter", "statistic", tính đủ và hiệu quả. Bản thân cách gọi tên, và sự tách biệt khỏi xác suất, đến sớm hơn một năm: Fisher, R. A., On the "probable error" of a coefficient of correlation deduced from a small sample, Metron 1, pp. 3–32 (1921), pp. 24–25.

  2. IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. Định nghĩa binary32 và binary16, cùng các quy tắc làm tròn khiến thí nghiệm cộng cho ra đúng như vậy.

  3. Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). Các tham số của định dạng, và lập luận cho việc đổi bit mantissa lấy bit exponent.

  4. Micikevicius, P. et al. Mixed Precision Training. ICLR 2018, arXiv:1710.03740. Loss scaling, và các độ lớn gradient đo được khiến nó cần thiết trong float16.

  5. Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), pp. 5–48 (1991). Vẫn là lời giải thích đơn lẻ tốt nhất về vì sao hai thứ tự cộng bất đồng.

  6. Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1), p. 40 (1965). Phép cộng bù trong nửa trang.

  7. Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1), pp. 73–101 (1964). Loss có dạng bậc hai gần zero và tuyến tính ở đuôi, được suy ra chứ không phải vá lại.

  8. Tukey, J. W. A survey of sampling from contaminated distributions, in Contributions to Probability and Statistics (Stanford University Press, 1960), pp. 448–485.

  9. Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (Paris, 1805), phụ lục Sur la méthode des moindres quarrés. Công bố đầu tiên về least squares, như một thiết bị tính toán.

  10. Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburg, 1809), Book II, §§175–179. Lập luận từ trung bình cộng đến luật lỗi chuẩn, và từ đó đến least squares.


Tạo bởi

David Vicente Campos

Nhà sáng lập NeuraLIA Labs & Đồng sáng lập MyRealFood

Tôi là kỹ sư máy tính tốt nghiệp Đại học León. Tôi đồng sáng lập MyRealFood, nơi tôi, với vai trò CTO, đã xây dựng ứng dụng mà hàng triệu người đã dùng để ăn uống lành mạnh hơn, và tôi sáng lập NeuraLIA Labs, nơi tôi xây dựng các sản phẩm AI. Ở đây, tôi viết về những gì tôi đã phải hiểu trong quá trình đó, theo cách mà tôi ước đã có ai đó giải thích cho mình.

Tìm hiểu thêm về tác giả

Xuất bản bởi NeuraLIA Labs.

Nhận bài viết mới trong hộp thư

Tin AI, hướng dẫn và cập nhật sản phẩm — một email ngắn khi chúng tôi có nội dung đáng để bạn đọc.

Thích nhắn tin hơn? Vẫn nội dung đó, ở đây:Cộng đồng WhatsApp (mở trong tab mới)Kênh Telegram (mở trong tab mới)

Mục lục khóa học

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineeringĐọc 16 phút

Kỹ thuật ngữ cảnh cho tác nhân AI dài hạn

Tác nhân chạy lâu không thất bại chỉ vì cửa sổ nhỏ. Chúng thất bại khi tệp, đầu ra công cụ và lịch sử cũ lấn át nhiệm vụ mà tác nhân phải hoàn thành.

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.