Một model nhận ảnh chữ số viết tay và trả về mười con số, mỗi số cho một chữ số từ 0 đến 9, chẳng hạn [1.2, -0.4, 6.3, 0.8, …]. Mười số này chưa phải xác suất: có số âm, và tổng không bằng 1. Để đổi chúng thành xác suất, model lấy hàm mũ của từng số rồi chia cho tổng. Khi chấm model, ta lấy log của xác suất mà model dành cho đáp án đúng. Bài này học hai hàm đó, hàm mũ và log, cùng hai hàm dựng từ chúng là sigmoid và softmax.
Bài chỉ cần toán THCS: lũy thừa, phân số và đọc đồ thị. Mọi công thức đều có một đoạn NumPy in ra số để đối chiếu với phép tính tay.
Hàm số: đưa vào một số, nhận ra một số
Một hàm số là một quy tắc: mỗi giá trị đầu vào cho đúng một giá trị đầu ra. Đổi nhiệt độ từ độ C sang độ F là một hàm: . Trong Python, một def nhận số và trả về số cũng là một hàm theo đúng nghĩa ấy.
Dạng đơn giản nhất mà học máy dùng là hàm bậc nhất
là hệ số góc (slope): tăng 1 thì tăng đúng . là giá trị của khi . Đồ thị là một đường thẳng. Bài linear regression ở chương 2 học đúng hai số và này từ dữ liệu.
Cách kiểm nhanh một hàm có phải bậc nhất không là chia độ tăng của cho độ tăng của giữa các điểm liên tiếp. Với hàm bậc nhất, tỉ số này luôn bằng . np.diff trả về hiệu của hai phần tử liền nhau trong mảng.
import numpy as np
def to_f(c):
return 1.8 * c + 32
c = np.array([-10, 0, 10, 20, 30])
f = to_f(c)
print(f)
print(np.diff(f) / np.diff(c))
[14. 32. 50. 68. 86.]
[1.8 1.8 1.8 1.8]
Bốn tỉ số đều bằng 1.8, đúng hệ số góc. độ C cho độ F, đúng hệ số tự do. Hàm cong như thì tỉ số này đổi theo từng đoạn; phần sau của bài gặp nhiều hàm như thế.
Hỏi nhanh
Hàm . Khi tăng từ 4 lên 6, tăng bao nhiêu?
tăng 2 đơn vị, mỗi đơn vị làm tăng 3, nên tăng 6: từ 10 lên 16. Số 2 là độ tăng của , số 3 là hệ số góc, còn 16 là giá trị của tại , không phải độ tăng.
Lũy thừa và hàm mũ
là 2 nhân với chính nó 10 lần, bằng 1024. Ba quy tắc đủ dùng cho cả bài:
Học máy hay dùng cơ số . Đây là một hằng số như . Lý do chọn nó sẽ rõ ở bài 0.6: hàm có đạo hàm bằng chính nó, nên các phép tính về sau rất gọn. Hàm có ký hiệu khác là , trong NumPy là np.exp.
Ba điều cần nhớ về . Nó luôn dương, kể cả khi âm. . âm càng nhiều thì càng sát 0; dương thì tăng rất nhanh, mỗi khi tăng 1 thì giá trị nhân thêm khoảng 2.7 lần.
x = np.array([-5, -1, 0, 1, 2, 5, 10])
print([round(float(v), 4) for v in np.exp(x)])
print(np.exp(2 + 3), np.exp(2) * np.exp(3))
[0.0067, 0.3679, 1.0, 2.7183, 7.3891, 148.4132, 22026.4658]
148.4131591025766 148.4131591025766
Dòng đầu cho thấy đã rất nhỏ, còn đã hơn hai vạn. Dòng thứ hai kiểm quy tắc : hai cách tính cho cùng một số.
Hàm mũ tăng nhanh đến mức máy tính không chứa nổi. Số thực float64 lớn nhất khoảng , nên đã vượt quá và thành inf (vô cùng). Phần softmax ở dưới sẽ gặp lại chuyện này.
with np.errstate(over="ignore"):
print(np.exp(np.array([700.0, 710.0])))
[1.01423205e+304 inf]
Hỏi nhanh
bằng bao nhiêu?
Nhân hai lũy thừa cùng cơ số thì cộng số mũ: . Ra là nhầm số mũ với giá trị . Ra hay là nhân số mũ thay vì cộng.
Log: hỏi ngược lại phép mũ
, nên “2 mũ mấy thì bằng 8?” có câu trả lời là 3. Ta viết . Log là phép hỏi ngược của phép mũ: là số mũ cần đặt lên để được .
Cơ số cho log tự nhiên, viết . Trong sách học máy và trong NumPy, log không ghi cơ số nghĩa là cơ số : np.log là , còn np.log2 và np.log10 cho cơ số 2 và 10. Bài này cũng viết cho log tự nhiên.
Đồ thị của là đồ thị của lật qua đường chéo , vì hai hàm làm ngược việc của nhau.
Từ hình đọc được ba điều. chỉ có nghĩa khi . . Với , như một xác suất, là số âm, và càng sát 0 thì càng âm nhiều.
Quy tắc quan trọng nhất của log đến từ quy tắc nhân lũy thừa:
Log biến phép nhân thành phép cộng. Học máy cần điều này vì xác suất hay được nhân với nhau. Giả sử model chấm 1000 câu, mỗi câu nó cho đáp án đúng xác suất 0.5. Xác suất model đoán đúng cả 1000 câu là , một số cỡ . Thêm 100 câu nữa thì máy làm tròn thành đúng 0, gọi là tràn dưới (underflow). Cộng log thì không có vấn đề gì.
print(np.log2(8), np.log10(1000), np.log(np.e))
print(np.log(3 * 7), np.log(3) + np.log(7))
p = np.full(1100, 0.5)
print(np.prod(p[:1000]), np.prod(p))
print(np.sum(np.log(p[:1000])), np.sum(np.log(p)))
3.0 3.0 1.0
3.044522437723423 3.044522437723423
9.332636185032189e-302 0.0
-693.1471805599454 -762.4618986159398
Tích của 1100 xác suất đã thành 0.0, nên mọi so sánh dựa trên nó đều mất. Tổng log của chúng là , một số bình thường, và vẫn so sánh được: tổng log lớn hơn nghĩa là tích lớn hơn, vì log là hàm tăng.
Hỏi nhanh
Biết và . xấp xỉ bao nhiêu?
, nên . là . là tích , nhầm log của tích với tích của log. là .
Sigmoid: ép một số bất kỳ vào khoảng (0, 1)
Một bộ lọc thư rác tính cho mỗi thư một điểm bằng hàm bậc nhất của vài con số mô tả thư, như số đường link hay số chữ in hoa. có thể là bất kỳ số thực nào, còn điều ta muốn là xác suất thư là rác, một số nằm giữa 0 và 1. Hàm sigmoid làm việc ấy:
Đọc công thức bằng ba trường hợp. thì , nên . dương lớn thì sát 0, nên sát 1. âm nhiều thì rất lớn, nên sát 0. Đồ thị là một đường chữ S, không bao giờ chạm 0 hay 1.
Hình chữ S đối xứng qua điểm , tức là . Điểm cho xác suất thư rác 0.88 thì cho 0.12.
Hỏi ngược lại: xác suất ứng với điểm nào? Giải ra
Tỉ số gọi là odds: cho odds bằng 4, nghĩa là “khả năng xảy ra gấp 4 lần không xảy ra”. Vì vậy còn được gọi là log-odds, hay logit. Từ logit sẽ gặp lại ở mọi bài phân loại.
def sigmoid(z):
return 1 / (1 + np.exp(-z))
z = np.array([-6, -2, 0, 2, 6])
print(np.round(sigmoid(z), 4))
print(np.round(sigmoid(z) + sigmoid(-z), 4))
logit = np.log(0.8 / 0.2)
print(round(logit, 4), sigmoid(logit))
[0.0025 0.1192 0.5 0.8808 0.9975]
[1. 1. 1. 1. 1.]
1.3863 0.8
Dòng thứ hai kiểm ở cả năm điểm. Dòng thứ ba đi một vòng: từ sang logit , rồi sigmoid trả lại đúng .
Hỏi nhanh
Model cho một thư điểm và . Với một thư khác có điểm , sigmoid bằng bao nhiêu?
. Sigmoid không bao giờ âm, nên không thể là kết quả. là quên rằng đổi dấu thì đổi phía. lớn hơn 1, cũng không thể là một xác suất.
Softmax: từ nhiều điểm thành một phân phối
Với mười chữ số, model cho mười điểm , gọi là logits. Softmax đổi chúng thành mười xác suất:
Hai bước: lấy mũ để mọi số thành dương, rồi chia cho tổng để các số cộng lại bằng 1. Logit lớn hơn vẫn cho xác suất lớn hơn, vì là hàm tăng.
Cộng cùng một số vào mọi logit thì softmax không đổi: , và thừa số xuất hiện ở cả tử lẫn mẫu nên triệt tiêu. Tính chất này cứu ta khỏi chuyện tràn số. Logits như [1000, 999, 998] làm np.exp thành inf, và inf / inf cho nan. Trừ đi logit lớn nhất trước khi lấy mũ thì số lớn nhất thành , không còn gì tràn, mà kết quả vẫn y như cũ.
def softmax(z):
e = np.exp(z - z.max())
return e / e.sum()
z = np.array([2.0, 1.0, 0.0])
print(np.round(softmax(z), 4), round(softmax(z).sum(), 6))
print(np.round(softmax(z + 100), 4))
big = np.array([1000.0, 999.0, 998.0])
print(np.round(softmax(big), 4))
with np.errstate(over="ignore", invalid="ignore"):
print(np.exp(big) / np.exp(big).sum())
[0.6652 0.2447 0.09 ] 1.0
[0.6652 0.2447 0.09 ]
[0.6652 0.2447 0.09 ]
[nan nan nan]
Ba dòng đầu cho cùng một phân phối, vì ba bộ logits chỉ khác nhau một hằng số cộng. Dòng cuối là cách viết thẳng theo công thức, không trừ max: kết quả hỏng hoàn toàn.
Với hai lớp, softmax của logits cho lớp đầu xác suất . Chia cả tử và mẫu cho được . Sigmoid là softmax hai lớp.
Hỏi nhanh
Logits ba lớp là . Xác suất của lớp giữa là bao nhiêu?
Lấy mũ được , tổng là 5, nên lớp giữa có . là trường hợp ba logit bằng nhau. là quên một lớp khi cộng mẫu số. là chia logit cho tổng mà bỏ qua bước lấy mũ.
Hỏi nhanh
Model cho ba logits [3, 3, 3]. Softmax trả về gì?
Ba logit bằng nhau thì bằng nhau, chia cho tổng được mỗi lớp. Softmax luôn có tổng bằng 1, nên [1, 1, 1] và [0.95, 0.95, 0.95] đều sai; là , nhầm softmax với sigmoid. Dấu của logits không quan trọng: [-3, -3, -3] cũng cho mỗi lớp.
Hỏi nhanh
Cộng 5 vào cả ba logits [2, 1, 0], thành [7, 6, 5]. Xác suất của lớp đầu thay đổi thế nào?
Cộng cùng một số vào mọi logit thì xuất hiện ở cả tử và mẫu rồi triệt tiêu. Softmax chỉ phụ thuộc vào khoảng cách giữa các logit, mà khoảng cách không đổi. Nhãn thật chỉ cần khi tính loss, không cần khi tính xác suất.
Vì sao loss có log
Model đã cho xác suất. Để chấm, ta nhìn xác suất mà model dành cho đáp án đúng và tính
Dấu trừ để loss không âm, vì khi . Đoán đúng chắc chắn, , cho loss 0. càng nhỏ thì loss càng lớn, và lớn không giới hạn khi tiến về 0. Ta gọi đây là log-loss, hay cross-entropy khi có nhiều lớp.
p_true = np.array([0.99, 0.9, 0.5, 0.1, 0.01])
print(np.round(-np.log(p_true), 3))
[0.01 0.105 0.693 2.303 4.605]
Từ 0.99 xuống 0.9, loss chỉ tăng thêm chưa tới 0.1. Từ 0.1 xuống 0.01, loss tăng thêm 2.3. Giảm đi một nửa thì loss luôn tăng thêm đúng , vì . Model nói “chắc 99% là mèo” mà ảnh là chó thì bị phạt rất nặng.
Lấy trung bình log-loss trên cả tập dữ liệu:
Vế phải là log của tích các xác suất, tức xác suất model gán cho toàn bộ đáp án đúng của tập, nếu coi các mẫu độc lập. Làm loss trung bình nhỏ nhất cũng chính là làm tích ấy lớn nhất. Ta tính bằng tổng log thay vì tích vì lý do đã thấy ở trên: tích của nghìn xác suất tràn dưới thành 0.
Với hai lớp, nhãn và model cho , xác suất của đáp án đúng là khi và khi . Viết gộp lại:
Với mỗi mẫu, chỉ một trong hai số hạng khác 0. Đây là binary cross-entropy của các bài phân loại chương 1 và 2.
Log-loss nhìn thấy điều accuracy không thấy. Hai model dưới đây cùng đúng 3 trên 4 câu khi lấy ngưỡng 0.5, nhưng model B sai một câu với độ tự tin rất cao.
A = np.array([0.9, 0.8, 0.6, 0.4]) # xác suất model A dành cho đáp án đúng
B = np.array([0.6, 0.6, 0.6, 0.05])
for name, p in [("A", A), ("B", B)]:
print(name, "accuracy", (p > 0.5).mean(), "log-loss", round(-np.log(p).mean(), 3))
A accuracy 0.75 log-loss 0.439
B accuracy 0.75 log-loss 1.132
Cùng accuracy 0.75, nhưng log-loss của B gấp hơn hai lần A. Câu B cho đáp án đúng chỉ 0.05 đóng góp vào tổng. Nếu đề chấm bằng log-loss, model B thua xa.
Giờ nối softmax với log-loss. Model phân loại ảnh thành mèo, chó, chim, cho logits ; như mục trên, softmax cho , , . Ảnh thật là chó, nên loss là . Nếu ảnh là mèo thì loss là , là chim thì . Ba số cách nhau đúng 1, bằng khoảng cách giữa các logit, vì lấy log của công thức softmax được
Với cùng một bộ logits, số hạng đầu chung cho mọi lớp, nên lớp có logit lớn hơn 1 thì loss nhỏ hơn đúng 1.
Giờ model sửa logit của chó từ 1 lên 2, thành . Chó được xác suất , loss còn . Huấn luyện ở các chương sau làm đúng việc này hàng triệu lần: chỉnh logits để loss trên đáp án đúng giảm dần.
z = np.array([2.0, 1.0, 0.0])
p = softmax(z)
print(np.round(p, 3), np.round(-np.log(p), 3))
z2 = np.array([2.0, 2.0, 0.0])
print(np.round(softmax(z2), 3), round(-np.log(softmax(z2)[1]), 3))
[0.665 0.245 0.09 ] [0.408 1.408 2.408]
[0.468 0.468 0.063] 0.759
Hỏi nhanh
Model dành cho đáp án đúng xác suất 0.8. Sau một lần sửa, xác suất ấy còn 0.4. Log-loss của mẫu này thay đổi thế nào?
Giảm đi một nửa thì loss tăng thêm : từ lên . Loss không tăng gấp đôi, vì . là độ giảm của , không phải của loss. Log-loss phụ thuộc vào xác suất, không chỉ vào lớp được chọn, nên kể cả khi model vẫn chọn đúng thì loss vẫn đổi.
Khi đi thi
- Kiểm đầu ra của model là logits hay xác suất: xác suất thì không âm và mỗi dòng cộng lại bằng 1.
- Tự tính softmax thì trừ max trước khi lấy mũ; tự tính log-loss thì kẹp
np.clip(p, 1e-15, 1 - 1e-15)để không gặplog(0). - Xem đề chấm bằng accuracy hay log-loss: hai metric có thể xếp hạng hai model ngược nhau.
Tóm tắt
- Hàm bậc nhất : là độ tăng của khi tăng 1, là giá trị tại .
- luôn dương, , tăng rất nhanh; .
- là phép hỏi ngược của mũ, chỉ có nghĩa khi , âm khi ; biến tích thành tổng.
- Sigmoid đưa một số bất kỳ vào ; ; ngược lại là logit .
- Softmax lấy mũ rồi chia tổng; cộng cùng hằng số vào mọi logit không đổi kết quả, nên trừ max để tránh tràn số.
- Log-loss của đáp án đúng: 0 khi chắc chắn đúng, rất lớn khi tự tin mà sai; trung bình log-loss nhỏ nhất tương đương xác suất của cả tập lớn nhất.
Kiểm tra cuối bài
Mười câu dưới đây bao quát cả bài. Làm hết rồi xem điểm; câu nào sai thì đọc lại mục tương ứng.
Câu 1
Hàm sigmoid trả về giá trị nằm trong khoảng nào?
luôn dương nên mẫu luôn lớn hơn 1, do đó nằm giữa 0 và 1 và không bao giờ chạm hai đầu. là miền giá trị của tanh, một hàm hình chữ S khác. là của ReLU, còn là miền của logit đầu vào.
Câu 2
Lớp cuối của một mạng neural dùng softmax. Mạng này nhiều khả năng làm bài toán gì?
Softmax cho một vector không âm có tổng bằng 1, đúng dạng phân phối trên các lớp. Dự đoán giá nhà cần một số thực bất kỳ, nên lớp cuối để nguyên. Hai lớp thì softmax vẫn dùng được, vì nó chính là sigmoid, nhưng softmax không giới hạn ở hai lớp. Lớp ẩn thường dùng ReLU, không dùng softmax.
Câu 3
Cross-entropy dùng để chấm model phân loại. Nó đo điều gì?
Với nhãn one-hot, cross-entropy bằng của xác suất model dành cho lớp đúng. Model càng dồn xác suất vào đúng lớp thì loss càng nhỏ. Bình phương và trị tuyệt đối là MSE và MAE, thường dùng cho hồi quy. Góc giữa hai vector là cosine similarity, một đại lượng khác.
Câu 4
Biết và . bằng bao nhiêu?
. là quên số mũ 2 của . là nhân log thay vì cộng. là bình phương log thay vì nhân log với 2.
Câu 5
Một model trả về vector [0.7, 0.5, -0.2] cho ba lớp. Vector này là gì?
Vector xác suất phải không âm và có tổng bằng 1. Vector này có số nên không phải xác suất; tổng của nó bằng 1.0 chỉ là tình cờ. Đổi số âm thành 0 cũng không cho ra xác suất đúng của model, phải đưa qua softmax. Log-loss thì là một số cho mỗi mẫu, tính khi đã có nhãn thật.
Câu 6
Nhãn thật là , model cho . Binary cross-entropy của mẫu này xấp xỉ bao nhiêu?
Với , loss là . là tính như thể nhãn bằng 1. Số hạng bằng 0 khi , nhưng số hạng còn lại thì không, nên loss không bằng 0.
Câu 7
Một học sinh nhân xác suất của 2000 mẫu, mỗi xác suất khoảng 0.6, và máy in ra 0.0. Vì sao, và nên sửa thế nào?
cỡ , nhỏ hơn số dương nhỏ nhất mà float64 biểu diễn được, nên máy làm tròn thành 0. Tổng log là , một số bình thường. Về toán học tích không bằng 0, nhưng máy tính có giới hạn. Đổi sang 0.5 hay chia đôi phép nhân đều vẫn tràn.
Câu 8
np.exp(np.array([800.0, 801.0])) cho [inf, inf]. Muốn tính softmax của hai logits này mà không bị nan thì làm thế nào?
Trừ cùng một số vào mọi logit không đổi softmax, và trừ cho số lớn nhất thì logit lớn nhất thành 0, , không còn tràn. Chia cho 800 làm đổi khoảng cách giữa hai logits nên đổi luôn kết quả. np.exp2 tính , vẫn tràn ở và còn đổi sang một phép tính khác. Làm tròn không giải quyết gì vì logits đã là số nguyên.
Câu 9
Model A và model B cùng có accuracy 0.80 trên tập validation. Log-loss của A là 0.45, của B là 0.95. Vì sao có chênh lệch ấy?
Accuracy chỉ nhìn lớp được chọn, còn log-loss nhìn xác suất dành cho đáp án đúng. Vài câu tự tin sai, chẳng hạn cho đáp án đúng 0.01, đóng góp mỗi câu hơn 4.6 vào tổng. Cùng accuracy thì số câu đúng bằng nhau. Log-loss là loss, nhỏ hơn mới tốt hơn.
Câu 10
Với logits , log-loss khi lớp đúng là bằng . Tăng riêng lên một chút thì log-loss thay đổi thế nào?
Tăng thêm một lượng nhỏ làm số hạng đầu tăng ít hơn lượng ấy, vì chỉ là một phần của tổng, nên hiệu giảm. Tổng có tăng thật, nhưng chậm hơn . Tính bất biến khi cộng hằng số chỉ đúng khi cộng vào mọi logit, không phải một logit. Loss chỉ tiến về 0 khi xác suất của lớp đúng tiến về 1.
Đọc thêm
- Khan Academy — Exponential and logarithmic functions. Ôn lũy thừa và log từ đầu, nhiều bài tập ngắn có lời giải.
- 3Blue1Brown — What's so special about Euler's number e?. Video giải thích vì sao là hàm có đạo hàm bằng chính nó, chuẩn bị cho bài 0.6.
- Google — Machine Learning Crash Course: Logistic regression. Sigmoid, log-odds và log-loss trong một bài phân loại thật.
- Goodfellow, Bengio, Courville — Deep Learning, chương 4.1. Đọc phần tràn số và cách tính softmax ổn định.