Một model đoán điểm bằng với một trọng số . Đặt thì đoán sai nhiều, loss lớn. Nên tăng hay giảm , và đổi bao nhiêu? Với một trọng số thì có thể thử từng giá trị, nhưng mạng neural có hàng triệu trọng số. Đạo hàm cho biết đổi một chút thì loss đổi bao nhiêu và theo hướng nào. Bài này đi từ đạo hàm tới gradient và gradient descent, thuật toán dùng để huấn luyện gần như mọi model trong khóa học.
Độ dốc tại một điểm
Ở bài 0.4, hàm bậc nhất có độ dốc không đổi: tăng 1 thì tăng . Hàm thì khác: gần đồ thị gần như nằm ngang, càng xa thì càng dốc. Ta cần nói “độ dốc tại ”.
Cách làm: lấy một điểm gần đó, , và chia độ tăng của cho độ tăng của :
Đây là độ dốc của đường thẳng nối hai điểm trên đồ thị. Cho nhỏ dần, đường ấy xoay dần về tiếp tuyến, đường thẳng chỉ chạm đồ thị tại đúng điểm . Độ dốc của tiếp tuyến là đạo hàm của tại 3, ký hiệu .
import numpy as np
def f(x):
return x ** 2
for h in [1, 0.1, 0.01, 0.001]:
print(h, round((f(3 + h) - f(3)) / h, 6))
1 7.0
0.1 6.1
0.01 6.01
0.001 6.001
Với nhỏ dần, tỉ số tiến về 6. Viết tay: , và cho đúng 6.
Đọc đạo hàm như một lời hứa nhỏ: tăng thêm một lượng rất nhỏ thì tăng thêm khoảng . Đạo hàm dương thì hàm đang đi lên, âm thì đang đi xuống, bằng 0 thì đang nằm ngang, như ở đáy một cái bát.
Hỏi nhanh
. Tăng từ 2 lên 2.01 thì thay đổi khoảng bao nhiêu?
Thay đổi xấp xỉ : đạo hàm âm nên hàm giảm. Giảm 3 là quên nhân với độ tăng của . Một độ tăng nhỏ vẫn làm đổi, chỉ đổi ít.
Các quy tắc tính đạo hàm
Không ai tính giới hạn mỗi lần. Vài quy tắc đủ cho phần lớn học máy:
| Hàm | Đạo hàm |
|---|---|
| hằng số | |
Hàng cho , khớp con số 6 tại . Hàng là lý do bài 0.4 chọn cơ số : đạo hàm của nó là chính nó. Hai hàng cuối nói đạo hàm đi qua phép nhân với hằng số và phép cộng: .
Kiểm bằng số: so công thức với sai phân tại .
def numeric(g, x, h=1e-6):
return (g(x + h) - g(x - h)) / (2 * h)
x = 2.0
tests = [("x^3", lambda t: t ** 3, 3 * x ** 2),
("e^x", np.exp, np.exp(x)),
("log x", np.log, 1 / x),
("3x^2 - 4x + 1", lambda t: 3 * t ** 2 - 4 * t + 1, 6 * x - 4)]
for name, g, exact in tests:
print(f"{name:14s} công thức {exact:.6f} sai phân {numeric(g, x):.6f}")
x^3 công thức 12.000000 sai phân 12.000000
e^x công thức 7.389056 sai phân 7.389056
log x công thức 0.500000 sai phân 0.500000
3x^2 - 4x + 1 công thức 8.000000 sai phân 8.000000
Hàm numeric lấy hai điểm hai bên, và , gọi là sai phân trung tâm; nó chính xác hơn cách lấy một bên ở mục trước. Mục cuối bài dùng lại hàm này để kiểm gradient.
Hỏi nhanh
. bằng bao nhiêu?
, nên . là , giá trị hàm chứ không phải đạo hàm. là quên đạo hàm của . là cộng thay vì trừ.
Quy tắc chuỗi
Loss của model hiếm khi là một hàm đơn giản của trọng số. Nó là một chuỗi các bước: trọng số cho ra dự đoán, dự đoán cho ra sai số, sai số cho ra loss. Với hàm hợp , quy tắc chuỗi nói
Đọc bằng lời hứa ở trên: nhích thì nhích khoảng , và nhích chừng ấy thì nhích khoảng nhân lượng đó. Tốc độ đổi qua nhiều bước là tích các tốc độ đổi của từng bước.
Ví dụ . Bên trong là với ; bên ngoài là với . Nên , tại bằng 12.
Áp vào model với loss . Coi là biến, và là số đã biết. Chuỗi gồm hai bước: có đạo hàm , và có đạo hàm . Nhân lại:
Mạng neural chỉ là một chuỗi dài hơn. Thuật toán backpropagation ở chương 3 là quy tắc chuỗi áp dụng có hệ thống, từ loss đi ngược về từng trọng số.
Một ví dụ quan trọng là sigmoid . Áp quy tắc chuỗi hai lần rồi rút gọn, ta được một công thức rất gọn:
Tại , nên , lớn nhất có thể. Ở hai đầu chữ S, gần 0. Một mạng nhiều lớp sigmoid nhân nhiều thừa số không quá 0.25 với nhau, nên gradient ở các lớp đầu nhỏ dần. Chương 3 gọi đây là vanishing gradient.
def sigmoid(z):
return 1 / (1 + np.exp(-z))
for z in [0.0, 2.0, 5.0]:
s = sigmoid(z)
print(z, round(s * (1 - s), 4), round(numeric(sigmoid, z), 4))
w, x, y = 1.0, 2.0, 6.0
loss = lambda w: (w * x - y) ** 2
print(2 * (w * x - y) * x, round(numeric(loss, w), 4))
0.0 0.25 0.25
2.0 0.105 0.105
5.0 0.0066 0.0066
-16.0 -16.0
Công thức và sai phân khớp nhau ở cả ba điểm của sigmoid. Dòng cuối: với , model đoán trong khi đáp án là ; đạo hàm âm, nghĩa là tăng thì loss giảm. Đúng như mong đợi, vì model đang đoán thấp.
Hỏi nhanh
với , và . bằng bao nhiêu, và nên đổi theo hướng nào?
Dự đoán , sai số , đạo hàm . Đạo hàm dương nên tăng làm loss tăng; phải giảm . là loss, chưa phải đạo hàm. là quên nhân . là đổi dấu sai số.
Đạo hàm riêng và gradient
Loss thường phụ thuộc nhiều tham số cùng lúc. Với hàm hai biến , đạo hàm riêng theo , viết , là đạo hàm khi coi như hằng số. Ví dụ :
Xếp mọi đạo hàm riêng thành một vector, ta được gradient:
Gradient chỉ hướng làm tăng nhanh nhất, và độ dài của nó cho biết dốc cỡ nào. Hướng ngược lại, , làm giảm nhanh nhất. Hình dưới là bản đồ đường đồng mức của cái bát ; mỗi vòng là một mức cao. Gradient tại mỗi điểm vuông góc với đường đồng mức và chỉ ra ngoài.
def bowl(v):
return v[0] ** 2 + v[1] ** 2
def grad_numeric(g, v, h=1e-6):
out = np.zeros_like(v)
for i in range(len(v)):
e = np.zeros_like(v)
e[i] = h
out[i] = (g(v + e) - g(v - e)) / (2 * h)
return out
v = np.array([1.0, 2.0])
print(2 * v, np.round(grad_numeric(bowl, v), 6))
[2. 4.] [2. 4.]
grad_numeric làm sai phân trung tâm theo từng tọa độ, giữ các tọa độ khác cố định: đúng định nghĩa đạo hàm riêng. Tại , gradient là , chỉ ra xa gốc.
Hỏi nhanh
. Gradient tại là gì?
không phụ thuộc điểm, . là quên nhân 2 khi đạo hàm . là thay giá trị vào hàm thay vì đạo hàm. Gradient của hàm hai biến là một vector hai số, không phải một số.
Gradient descent
Đã biết hướng làm loss giảm nhanh nhất là . Gradient descent đi một bước nhỏ theo hướng ấy, rồi tính lại gradient, rồi đi tiếp:
là learning rate, quyết định mỗi bước dài bao nhiêu. Hình ảnh quen thuộc: đi xuống núi trong sương mù, chỉ thấy độ dốc dưới chân, nên mỗi lần bước một bước theo hướng dốc xuống nhất.
Thử với model một trọng số ở trên, , , đáp án tốt nhất là . Bắt đầu từ , so hai learning rate.
def descend(eta, steps, w=0.0):
path = []
for _ in range(steps):
grad = 2 * (w * x - y) * x
w = w - eta * grad
path.append(round(w, 4))
return path
print(descend(0.05, 6))
print(descend(0.05, 30)[-1])
print(descend(0.3, 6))
[1.2, 1.92, 2.352, 2.6112, 2.7667, 2.86]
3.0
[7.2, -2.88, 11.232, -8.5248, 19.1347, -19.5886]
Với , tiến dần về 3, bước sau ngắn hơn bước trước vì càng gần đáy thì gradient càng nhỏ. Sau 30 bước đã bằng 3 tới bốn chữ số thập phân. Với , bước đầu nhảy lên 7.2, vượt qua đáy rất xa; mỗi bước sau lại vượt sang phía bên kia, xa hơn bước trước, và lớn dần không giới hạn. Ta nói gradient descent phân kỳ. Khi loss tăng vọt hay thành nan trong lúc huấn luyện, nghi phạm đầu tiên là learning rate quá lớn.
Với nhiều mẫu, loss là trung bình loss của từng mẫu, nên gradient cũng là trung bình gradient của từng mẫu. Lấy hai mẫu và , bắt đầu từ với . Hai dự đoán đều là 0, sai số và , loss . Gradient của từng mẫu là : và , trung bình . Bước cập nhật cho . Sai số mới là và , loss còn .
xs = np.array([1.0, 2.0])
ys = np.array([2.0, 5.0])
w = 0.0
for step in range(3):
err = w * xs - ys
grad = np.mean(2 * err * xs)
print(step, round(w, 4), round(np.mean(err ** 2), 4), round(grad, 4))
w -= 0.1 * grad
0 0.0 14.5 -12.0
1 1.2 3.7 -6.0
2 1.8 1.0 -3.0
Dòng 0 và dòng 1 khớp phép tính tay. Đáp án tốt nhất là , nơi gradient trung bình bằng 0; vài bước nữa sẽ tới đó.
Hỏi nhanh
Huấn luyện một model bằng gradient descent, loss sau mỗi lượt chạy hết dữ liệu (gọi là một epoch) lần lượt là 2.1, 5.8, 31.4, 260.0, rồi nan. Nên thử gì trước?
Loss tăng theo cấp số rồi tràn số là dấu hiệu bước quá dài, nhảy qua đáy và văng ra xa dần. Tăng learning rate làm nặng thêm. Chạy thêm epoch chỉ làm loss tăng tiếp. Bớt dữ liệu không sửa được độ dài bước.
Kiểm gradient bằng sai phân
Tự viết công thức gradient thì dễ sai một hệ số 2 hay một dấu trừ, mà code vẫn chạy. Cách kiểm: so gradient từ công thức với gradient tính bằng sai phân trên một ví dụ nhỏ. Nếu sai số tương đối cỡ trở xuống thì công thức gần như chắc đúng.
Model tuyến tính ba trọng số, loss MSE trên năm mẫu: . Quy tắc chuỗi cho gradient
Đọc bằng shape của bài 0.5: là , phần dư là , tích là , đúng một số cho mỗi trọng số.
rng = np.random.default_rng(0)
X = rng.normal(size=(5, 3))
y_vec = rng.normal(size=5)
w_vec = rng.normal(size=3)
def mse(w):
return np.mean((X @ w - y_vec) ** 2)
exact = 2 / len(y_vec) * X.T @ (X @ w_vec - y_vec)
approx = grad_numeric(mse, w_vec)
wrong = 1 / len(y_vec) * X.T @ (X @ w_vec - y_vec) # quên số 2
rel = lambda a, b: np.linalg.norm(a - b) / np.linalg.norm(a + b)
print(np.round(exact, 6))
print(np.round(approx, 6))
print(f"đúng: {rel(exact, approx):.1e} quên số 2: {rel(wrong, approx):.1e}")
[ 1.753666 1.112768 -0.48581 ]
[ 1.753666 1.112768 -0.48581 ]
đúng: 3.1e-11 quên số 2: 3.3e-01
Công thức đúng cho sai số tương đối rất nhỏ; bản quên số 2 cho sai số cỡ 0.3, lộ ngay. Đây cũng là cách PyTorch kiểm các phép tính tự viết (torch.autograd.gradcheck).
Hỏi nhanh
Kiểm gradient tự viết bằng sai phân, sai số tương đối ra . Nhiều khả năng do đâu?
Thiếu một hệ số hay sai một dấu là lỗi hay gặp nhất. Sai phân trung tâm với cỡ cho sai số cỡ hay nhỏ hơn; là lệch một nửa, quá lớn để là sai số làm tròn. còn làm tệ hơn vì phép trừ hai số gần bằng nhau mất hết chữ số. Learning rate không tham gia vào việc tính gradient.
Khi đi thi
- Tự viết gradient thì kiểm bằng sai phân trung tâm trên ví dụ nhỏ, .
- Gradient phải có đúng shape của tham số.
- Loss tăng vọt hay thành
nanthì giảm learning rate trước.
Tóm tắt
- Đạo hàm là giới hạn của , độ dốc tiếp tuyến; nhích thì nhích khoảng .
- , , ; đạo hàm đi qua phép cộng và nhân hằng số.
- Quy tắc chuỗi: ; .
- Gradient là vector các đạo hàm riêng, chỉ hướng tăng nhanh nhất; chỉ hướng giảm nhanh nhất.
- Gradient descent: ; quá lớn thì phân kỳ.
- MSE tuyến tính có ; kiểm mọi gradient tự viết bằng sai phân trung tâm.
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
Đạo hàm của là gì?
Hàm bậc nhất có độ dốc không đổi, bằng hệ số góc 5; hằng số 3 có đạo hàm 0. là hệ số tự do, là cộng hai hệ số. là giữ nguyên biến, như thể đạo hàm của là chính nó.
Câu 2
Quy tắc chuỗi nói bằng gì?
Đạo hàm của hàm ngoài phải tính tại giá trị của hàm trong, , rồi nhân với đạo hàm hàm trong. là quên thế vào. Phép cộng là quy tắc cho tổng hai hàm, không phải hàm hợp.
Câu 3
Gradient của là gì?
Đạo hàm riêng theo coi là hằng số, được ; tương tự theo được . là quên hệ số 2. là chép lại hai số hạng. là cộng hai đạo hàm riêng thành một số, trong khi gradient là một vector.
Câu 4
Đạo hàm của là gì?
là hàm có đạo hàm bằng chính nó. là áp nhầm quy tắc lũy thừa , vốn chỉ dùng khi cơ số là biến. và là coi như hằng số hoặc như hàm bậc nhất.
Câu 5
Trong gradient descent, vector âm của gradient, , chỉ về đâu?
Gradient chỉ hướng tăng nhanh nhất, nên ngược lại là hướng giảm nhanh nhất tại chỗ đang đứng. Hướng ấy không nhất thiết chỉ thẳng tới cực tiểu: trong một thung lũng dài và hẹp, nó chỉ chéo xuống vách. Điểm yên ngựa là một loại điểm có gradient bằng 0, không phải một hướng.
Câu 6
Đạo hàm của sigmoid theo là gì?
Quy tắc chuỗi cho , rút gọn được . là nhầm với tính chất của . luôn âm, trong khi sigmoid là hàm tăng nên đạo hàm phải dương. là chỉ lấy tử số.
Câu 7
Với và , bằng gì?
Chuỗi cho . là đạo hàm theo , dừng giữa chuỗi. Bỏ số 2 là quên đạo hàm của bình phương. là đạo hàm của , không phải của loss này.
Câu 8
Gradient của , bình phương chuẩn L2, là gì?
, đạo hàm riêng theo là , nên gradient là . Đây là bản nhiều chiều của câu 3. là quên hệ số 2. là một số, không phải vector. không có nghĩa rõ ràng với vector.
Câu 9
Loss , đang ở , learning rate . Sau một bước gradient descent, bằng bao nhiêu?
tại , nên . là cộng gradient thay vì trừ, đi ngược hướng. là quên số 2 trong đạo hàm. là đáp án tốt nhất, chỉ đạt được ngay nếu learning rate là .
Câu 10
Một học sinh kiểm gradient bằng sai phân một bên và thấy sai số nhỏ dần khi giảm từ xuống , nhưng tăng lên khi . Vì sao?
Giảm làm sai số do “lấy hai điểm cách nhau” giảm, nhưng và gần bằng nhau đến mức máy tính chỉ giữ được vài chữ số của hiệu, rồi chia cho rất nhỏ lại khuếch đại sai số làm tròn ấy. Vì vậy chọn cỡ là vừa. Công thức sai thì sai số không nhỏ dần như vậy. Hàm không khả vi thì sai số không giảm đều.
Đọc thêm
- 3Blue1Brown - Essence of calculus, tập 1-4. Đạo hàm như độ dốc và quy tắc chuỗi, bằng hình động.
- 3Blue1Brown - Gradient descent, how neural networks learn. Gradient descent trên một mạng neural thật, chuẩn bị cho chương 3.
- Khan Academy - Differential calculus. Bài giảng và bài tập luyện quy tắc đạo hàm.
- Deisenroth, Faisal, Ong - Mathematics for Machine Learning, chương 5. Gradient của hàm vector và ma trận, khi cần đi xa hơn bài này.
- Stanford CS231n - Optimization: stochastic gradient descent. Kiểm gradient bằng sai phân và chọn learning rate trong thực tế.