~3 giờ · cần xong không cần điều kiện gì trước (nhưng nên xong môn 2)
Đích của bài này: hiểu rằng bạn đã dùng đạo hàm mỗi ngày dưới cái tên
rate(), và hiểu vì sao "tốc độ tức thời" là một khái niệm cần định nghĩa lại chứ không phải một phép chia bình thường.
⚡ 30 phút đầu — làm trước, đọc sau
Đừng đọc hết file rồi mới gõ. Mở terminal ngay:
f = lambda x: x * x
# Đích 1 — cho ra ĐÚNG bốn số: 7, 6.1, 6.01, 6.001
for h in [1, 0.1, 0.01, 0.001]:
print(h, (f(3 + h) - f(3)) / h)
# Đích 2 — bây giờ cho h nhỏ nữa, và ĐỪNG tin trực giác của bạn
for k in range(1, 16):
h = 10.0 ** (-k)
print(f"{h:.0e} sai số = {abs((f(3+h) - f(3))/h - 6):.3e}")
Đích 1 phải ra 7, 6.1, 6.01, 6.001 — bốn số này hội tụ về 6, và 6 = 2 × 3.
Đó là toàn bộ nội dung của định nghĩa đạo hàm, gói trong bốn dòng print.
Đích 2 là chỗ bài này thật sự bắt đầu. Sai số không giảm đơn điệu. Nó xuống tới đáy ở
h = 1e-8 (sai số 3.646e-08) rồi đi ngược lên, và ở h = 1e-15 sai số là 6.709e-01 —
tệ hơn đáy 18,4 triệu lần.
Nếu bạn nghĩ "h nhỏ hơn thì chính xác hơn" thì bạn vừa sai. Vì sao sai — mục 🪤 BẪY trả lời.
1. Bạn đã dùng đạo hàm mỗi ngày — nó tên là rate()
Trước khi định nghĩa gì cả, hãy nhìn thứ bạn đã quen.
Prometheus có hai loại metric bạn dùng suốt:
| Là gì | Ví dụ | Tên toán | |
|---|---|---|---|
| counter | Số cộng dồn, chỉ tăng | http_requests_total |
tích phân |
rate() |
Số đó đang tăng nhanh bao nhiêu | rate(http_requests_total[1m]) |
đạo hàm |
Counter thô gần như vô dụng để nhìn: nó chỉ đi lên. Cái bạn thật sự dán lên dashboard là rate().
Và rate() tính thế nào? Lấy giá trị counter ở hai đầu cửa sổ, trừ nhau, chia cho khoảng thời gian:
counter tại t = 0, 60, 120, 180, 240 (giây): 0, 120, 300, 540, 840
rate trên từng cửa sổ 60 giây:
(120 − 0) / 60 = 2 req/s
(300 − 120) / 60 = 3 req/s
(540 − 300) / 60 = 4 req/s
(840 − 540) / 60 = 5 req/s
Công thức bạn vừa dùng là
(f(t + h) − f(t)) / h. Đó chính xác là định nghĩa đạo hàm, chỉ thiếu một chữ: "cho h co lại về 0".
Bạn không học một khái niệm mới. Bạn học tên toán và cách phát biểu chặt của một thứ đã nằm trong dashboard của bạn từ lâu.
Hai điều rút ra ngay, và cả hai đều đáng giữ:
- Đạo hàm không phải "một điểm trên đồ thị". Nó là một QUAN HỆ giữa hai điểm — rồi ta ép hai điểm đó lại gần nhau.
- Counter và rate là hai chiều ngược nhau của cùng một thứ. Cộng dồn rate lại thì ra counter; lấy hiệu counter thì ra rate. Trong toán, cặp quan hệ đó gọi là định lý cơ bản của giải tích. (Ta không học tích phân trong lộ trình này — xem §Bài tập để biết vì sao. Nhưng cặp counter/rate đủ để bạn hiểu ý.)
2. Nghịch lý: "tốc độ tức thời" nghĩa là gì khi thời gian không trôi?
Đây là chỗ mọi người học lướt qua, và là chỗ 3B1B dành hẳn một tập (chương 2) để đào.
Xe của bạn chạy, đồng hồ chỉ 60 km/h tại đúng giây thứ 3. Câu hỏi: tại đúng một khoảnh khắc, xe di chuyển được bao nhiêu?
Bằng 0. Một khoảnh khắc không có độ dài. Không có thời gian trôi thì không có quãng đường đi.
Vậy tốc độ tại khoảnh khắc đó là:
quãng đường / thời gian = 0 / 0
0/0 không phải một con số. Nghịch lý là có thật, không phải chơi chữ. "Tốc độ tức thời" theo
nghĩa đen là một khái niệm mâu thuẫn — bạn không thể đo tốc độ mà không có hai thời điểm.
Cách toán học thoát ra khỏi nghịch lý này là đổi câu hỏi:
Không hỏi "tốc độ tại một khoảnh khắc là bao nhiêu". Hỏi: "tỉ số quãng-đường/thời-gian tiến về đâu khi khoảng thời gian co lại về 0?"
Cái "tiến về đâu" đó là giới hạn. Nó không đòi ta bao giờ đặt h = 0 — chỉ đòi ta biết dãy số
đang hướng tới đâu. Đó là toàn bộ mẹo.
Ẩn dụ backend chính xác đến bất ngờ: rate(...[1m]) không đo tốc độ "ngay bây giờ". Nó đo trên
một cửa sổ 1 phút. Bạn thu cửa sổ xuống 30 giây, 10 giây, 1 giây — số càng ngày càng gần
"tốc độ ngay lúc này", nhưng bạn không bao giờ đặt cửa sổ bằng 0 giây, vì lúc đó công thức là
0/0. Prometheus sống với đúng nghịch lý đó, và giải quyết nó đúng cách toán học giải quyết:
chọn cửa sổ đủ nhỏ, đừng chọn cửa sổ bằng 0.
Khắc sâu: đạo hàm KHÔNG phải "sự thay đổi tại một điểm". Nó là giá trị mà tỉ số thay đổi tiến tới khi cửa sổ co lại. Ai giữ được câu này sẽ không bao giờ bối rối trước
dx,dy,∂.
3. Định nghĩa, và ví dụ tính tay
Định nghĩa:
f'(x) = lim ( f(x + h) − f(x) ) / h
h → 0
Cái phân số bên trong gọi là tỉ số sai phân (difference quotient). Nó đo "đi thêm h thì f thay đổi bao nhiêu, tính trên mỗi đơn vị h".
Ví dụ tính tay — f(x) = x² tại x = 3
Làm trên giấy trước. Chỉ cần khai triển hằng đẳng thức:
f(3 + h) − f(3) (3 + h)² − 9 9 + 6h + h² − 9 6h + h²
─────────────── = ─────────── = ───────────────── = ─────── = 6 + h
h h h h
Tỉ số sai phân bằng đúng 6 + h. Không xấp xỉ, không làm tròn — bằng chính xác. Nên khi h
co về 0, nó tiến về 6, và không cần phải chia cho 0 lần nào.
Bảng kiểm — đã chạy bằng Python, không phải đoán:
h |
tỉ số sai phân (f(3+h) − f(3))/h |
sai lệch so với 6 |
|---|---|---|
| 1 | 7.0 | 1.0 |
| 0.1 | 6.1 | 0.1 |
| 0.01 | 6.01 | 0.01 |
| 0.001 | 6.001 | 0.001 |
Sai lệch đúng bằng h, y hệt đại số đã nói. Đây là kiểu kiểm chéo bạn nên tập phản xạ: đại số
nói "sai lệch = h", số máy nói "sai lệch = h", hai đường tính độc lập cho cùng một kết quả.
Và 6 = 2 × 3. Nếu làm lại tại x = 5 bạn được 10; tại x = 7 được 14. Quy luật:
f(x) = x² ⟹ f'(x) = 2x
Chú ý chỗ dễ trượt:
f'không phải một con số. Nó là một hàm mới — với mỗix, nó trả về độ dốc tạix. Bạn vừa biến một hàm thành một hàm khác. Đạo hàm là một toán tử, không phải một phép tính.
4. Ý nghĩa hình học: độ dốc tiếp tuyến — và xấp xỉ tuyến tính TỐT NHẤT
Vẽ đồ thị y = x². Lấy hai điểm: (3, 9) và (3+h, (3+h)²). Đường thẳng qua hai điểm đó là
cát tuyến, và độ dốc của nó chính là tỉ số sai phân 6 + h.
Cho h co lại: điểm thứ hai trượt về điểm thứ nhất, cát tuyến xoay dần và hội tụ về tiếp tuyến.
Độ dốc tiếp tuyến = 6.
tiếp tuyến tại x = 3: y = 9 + 6(x − 3) = 6x − 9
Nhưng "độ dốc tiếp tuyến" vẫn còn là hình ảnh trang trí cho tới khi bạn hỏi: tiếp tuyến tốt tới mức nào? Đây mới là ý đáng giữ.
Kiểm bằng số (đã chạy) — so f(3+h) = (3+h)² với tiếp tuyến 6(3+h) − 9:
h |
f(3+h) thật |
tiếp tuyến cho | sai lệch | h² |
|---|---|---|---|---|
| 1 | 16.000000 | 15.000000 | 1.0e+00 | 1.0e+00 |
| 0.1 | 9.610000 | 9.600000 | 1.0e−02 | 1.0e−02 |
| 0.01 | 9.060100 | 9.060000 | 1.0e−04 | 1.0e−04 |
| 0.001 | 9.006001 | 9.006000 | 1.0e−06 | 1.0e−06 |
Sai lệch của tiếp tuyến là
h², không phảih. Giảmhmười lần thì sai lệch giảm một trăm lần. Đó là điều làm tiếp tuyến đặc biệt: mọi đường thẳng khác đi qua(3,9)sẽ sai một lượng tỉ lệ vớih; chỉ đúng một đường sai tỉ lệ vớih².
Phát biểu lại, và đây là định nghĩa sẽ theo bạn tới hết môn 7:
Đạo hàm = hệ số của xấp xỉ tuyến tính TỐT NHẤT của hàm quanh một điểm. Nói cách khác: ghé sát vào đồ thị đủ gần thì mọi hàm trơn đều trông như một đường thẳng, và đạo hàm là độ dốc của đường thẳng đó.
Vì sao định nghĩa này quan trọng hơn "độ dốc tiếp tuyến": gradient descent (bài 5) dùng xấp xỉ
tuyến tính để chọn hướng giảm tại chỗ. Learning rate nhân với gradient để tạo bước đi; độ dài
bước còn phụ thuộc độ lớn gradient, không phải chỉ learning rate. Bước quá xa thì h² không
còn nhỏ nữa, và xấp xỉ tuyến tính không còn đáng tin.
5. Quy tắc cơ bản — suy ra bằng hình, không học thuộc
5.1. x² — hai hình chữ nhật và một góc vuông tí hon
Nghĩ x² là diện tích hình vuông cạnh x. Tăng cạnh thêm h, diện tích thêm bao nhiêu?
┌──────────────┬────┐
│ │ │
│ x · x │x·h │ ← dải bên phải: x·h
│ │ │
├──────────────┼────┤
│ x·h │ h² │ ← góc vuông tí hon: h²
└──────────────┴────┘
← dải bên dưới: x·h
(x + h)² − x² = 2xh + h²
Chia cho h: 2x + h. Cho h → 0, cái h cuối biến mất → 2x.
Cái ô
h²chính là lý do đạo hàm tồn tại được. Nó là diện tích của diện tích — bậc hai theoh— nên khi chia chohnó vẫn còn mộthvà tự tan biến. Mọi quy tắc đạo hàm đều là phiên bản của cùng một quan sát: giữ số hạng bậc 1 theoh, vứt mọi thứ bậc ≥ 2.
Kiểm bằng số tại x = 3:
h = 1.0 : (3+h)² − 3² = 7.000000 = 2xh + h² = 6.000000 + 1.000000
h = 0.1 : (3+h)² − 3² = 0.610000 = 2xh + h² = 0.600000 + 0.010000
h = 0.01 : (3+h)² − 3² = 0.060100 = 2xh + h² = 0.060000 + 0.000100
Nhìn cột cuối: h² teo nhanh hơn 2xh rất nhiều. Ở h = 0.01 nó đã nhỏ hơn 600 lần.
5.2. x³ — ba tấm ván và mấy que tăm
Cùng một hình, lên 3 chiều. x³ là thể tích khối lập phương cạnh x. Tăng cạnh thêm h:
(x + h)³ − x³ = 3x²h + 3xh² + h³
↑ ↑ ↑
3 tấm ván 3 que tăm 1 hạt cát
(bậc 1) (bậc 2) (bậc 3)
Chia cho h, cho h → 0: chỉ 3x² sống sót.
Kiểm bằng số tại x = 2 (đã chạy):
h = 1.0 : (2+h)³ − 2³ = 19.000000 = 12.000000 + 6.000000 + 1.000000
h = 0.1 : (2+h)³ − 2³ = 1.261000 = 1.200000 + 0.060000 + 0.001000
h = 0.01 : (2+h)³ − 2³ = 0.120601 = 0.120000 + 0.000600 + 0.000001
Ba số hạng, ba bậc, teo với ba tốc độ khác nhau. Đó là bức tranh.
Tổng quát: d/dx (xⁿ) = n·x^(n−1). Đã kiểm bằng số cho n = 0..5 tại x = 1.3, 2.0, 3.0 —
xem mục Code, assert (b).
n |
xⁿ |
n·x^(n−1) |
kiểm tại |
|---|---|---|---|
| 0 | 1 |
0 |
hằng số không đổi → dốc 0 ✓ |
| 1 | x |
1 |
đường thẳng dốc 1 ✓ |
| 2 | x² |
2x |
tại x=3 → 6 ✓ |
| 3 | x³ |
3x² |
tại x=2 → 12 ✓, tại x=3 → 27 ✓ |
| 4 | x⁴ |
4x³ |
tại x=2 → 32 ✓ |
5.3. Hằng số và tổng — hai quy tắc hiển nhiên tới mức dễ bị bỏ qua
Hằng số: f(x) = 7 thì f(x+h) − f(x) = 0 với mọi h. Chia cho h vẫn là 0. → f' = 0.
Đường nằm ngang thì không dốc. (Đây là lý do + 5 biến mất khỏi đạo hàm — không phải quy ước.)
Tổng: (f + g)' = f' + g'. Vì [f(x+h)+g(x+h)] − [f(x)+g(x)] = [f(x+h)−f(x)] + [g(x+h)−g(x)]
— chỉ là tách một phép trừ. Đạo hàm là phép tuyến tính, đúng nghĩa "tuyến tính" của môn 2:
tôn trọng phép cộng và phép nhân vô hướng.
d/dx (3x² + 5) = 6x tại x = 3 → 18 ✓ kiểm bằng số
5.4. 🪤 Tích — chỗ trực giác đánh lừa lần đầu
Trực giác nói (fg)' = f'g'. Sai. Nhìn bằng hình mới thấy vì sao.
f·g là diện tích hình chữ nhật cạnh f và cạnh g. Tăng x thêm một chút: cạnh f dài ra
df, cạnh g dài ra dg. Diện tích thêm bao nhiêu?
┌──────────────┬────────┐
│ │ │
│ f · g │ f · dg │ ← dải bên phải
│ │ │
├──────────────┼────────┤
│ g · df │ df·dg │ ← góc tí hon, BẬC HAI, bỏ được
└──────────────┴────────┘
Δ(fg) = f·dg + g·df + df·dg
Cái df·dg là ô góc — bậc 2 theo h — nên nó chết khi chia cho h. Còn lại:
(f·g)' = f'·g + f·g'
Kiểm bằng số, f = x², g = x³, tại x = 2, h = 0.01 (đã chạy):
f = 4.0 g = 8.0
df = 0.0401 dg = 0.120601
Δ(fg) thật = 0.80804010009998 ← (2.01)⁵ − 32
f·dg + g·df = 0.8032039999999796 ← thiếu ô góc, lệch 0.00484
f·dg + g·df + df·dg = 0.8080401000999794 ← khớp tới sai số máy (~1e-16) ✓
Ô góc df·dg = 0.004836100099999752 — nhỏ, nhưng không phải rác: nó chính xác là phần
chênh giữa hai dòng trên. Và khi h → 0 nó teo nhanh hơn hai ô kia nên bỏ được.
Đối chiếu công thức: (x²·x³)' = (x⁵)' = 5x⁴. Tại x = 2: 5 × 16 = 80.
Còn f'g + fg' = (2·2)(8) + (4)(3·4) = 32 + 48 = 80 ✓. Hai đường, một kết quả.
Bỏ qua được ở môn này: quy tắc thương
(f/g)', đạo hàm ẩn (3B1B ch.6), đạo hàm cấp cao (ch.10), chuỗi Taylor (ch.11). Bạn chỉ cần biết chúng tồn tại. Thứ thật sự trả cổ tức là chain rule — bài 3, và nó là trái tim của cả môn.
6. Đạo hàm SỐ — khi bạn không có công thức
Ở trên ta có công thức đại số nên tính được đạo hàm chính xác. Trong thực tế thường không có
công thức: hàm của bạn là 200 dòng code, có if, có vòng lặp, gọi ba thư viện.
Lúc đó bạn quay lại đúng định nghĩa và chọn một h cụ thể:
def forward(f, x, h):
return (f(x + h) - f(x)) / h # sai phân TIẾN
Đây gọi là đạo hàm số (numerical differentiation). Nó luôn dùng được, không cần biết f làm gì
bên trong — đúng tinh thần một baseline (quy tắc 2 của bốn quy tắc). Sau này khi bạn tự cài
backprop, đây chính là thứ dùng để kiểm chứng nó: gọi là gradcheck, và mọi framework đều có.
Nhưng nó có một cái giá, và cái giá đó là bẫy lớn nhất của bài này.
7. Nối về môn 2 — đạo hàm nhiều biến sẽ là một VECTOR
Bài này chỉ làm hàm một biến: một số vào, một số ra, đạo hàm là một số.
Loss của một mạng nơ-ron thì không như vậy: nó nhận vào hàng triệu trọng số và trả về một số. Ở bài 4 bạn sẽ thấy đạo hàm của hàm nhiều biến là một vector — gọi là gradient — và mọi thứ bạn học ở môn 2 sẽ chạy vào đây.
Xem trước bằng số, hàm f(x, y) = x² + y² tại điểm (3, 4):
∂f/∂x = 2x = 6 ← giữ y đứng yên, chỉ nhúc nhích x
∂f/∂y = 2y = 8 ← giữ x đứng yên, chỉ nhúc nhích y
∇f(3,4) = (6, 8) ← GRADIENT: một VECTOR, không phải một số
|∇f| = √(36+64) = 10
Và đây là chỗ móc thẳng vào môn 2 bài 1:
Độ dốc theo một hướng
ubất kỳ chính là TÍCH VÔ HƯỚNG∇f · u.
Kiểm bằng số (đã chạy — sai phân trung tâm dọc theo u, h = 1e-6), với u là các
vector đơn vị:
hướng u |
∇f · u tính tay |
đạo hàm số theo hướng đó | đọc là |
|---|---|---|---|
(1, 0) |
6·1 + 8·0 = 6 |
6.000000 | đi dọc trục x |
(0.6, 0.8) = (3,4)/5 |
6(0.6) + 8(0.8) = 10 |
10.000000 | cùng hướng gradient → dốc nhất |
(−0.8, 0.6) |
6(−0.8) + 8(0.6) = 0 |
≈ −1.8e−09 |
vuông góc gradient → PHẲNG |
Ba dòng này nói nguyên một chương:
- Độ dốc lớn nhất đạt được khi
ucùng hướng∇f, và giá trị của nó bằng đúng|∇f| = 10. Vì∇f · u = |∇f||u|cos θvà|u| = 1, nên nó lớn nhất khicos θ = 1. Đây là công thức môn 2 bài 1, không thêm gì. - Vuông góc với gradient thì độ dốc bằng 0 — đi theo hướng đó không lên không xuống. Đó chính là đường đồng mức (contour), và là hình ảnh đúng của "vuông góc = không chia sẻ thông tin" ở môn 2.
Vì sao gradient descent đi theo
−∇f? Vì−∇flà hướng làm∇f · uâm nhất — dốc xuống nhanh nhất. Câu trả lời là phép chiếu của môn 2, chứ không phải một quy tắc mới. Giữ câu này, bài 6 sẽ chỉ là gõ lại nó bằng code.
🪤 BẪY
BẪY 1 — "h nhỏ hơn thì chính xác hơn" là TRỰC GIÁC SAI
Đây là bẫy chính của bài. Nó thuộc quy tắc 1 ở dạng thuần khiết nhất: code chạy, không crash, không cảnh báo, và cho số sai lặng lẽ.
Toán nói h → 0. Nên phản xạ tự nhiên là: chọn h bé nhất có thể, 1e-15 chẳng hạn.
Chạy thử. Hàm g(x) = x³ tại x = 2, đáp án đúng g'(2) = 12. Bảng dưới đây là output
thật, không phải minh hoạ:
h |
sai phân tiến — sai số | sai phân trung tâm — sai số |
|---|---|---|
1e-01 |
6.100e−01 | 1.000e−02 |
1e-02 |
6.010e−02 | 1.000e−04 |
1e-03 |
6.001e−03 | 1.000e−06 |
1e-04 |
6.000e−04 | 1.001e−08 |
1e-05 |
6.000e−05 | 2.118e−10 ← đáy của central |
1e-06 |
6.002e−06 | 7.892e−10 |
1e-07 |
5.843e−07 | 6.316e−09 |
1e-08 |
7.293e−08 ← đáy của forward | 1.173e−07 |
1e-09 |
9.929e−07 | 9.929e−07 |
1e-10 |
9.929e−07 | 9.929e−07 |
1e-11 |
9.929e−07 | 9.929e−07 |
1e-12 |
1.067e−03 | 1.067e−03 |
1e-13 |
9.591e−03 | 9.591e−03 |
1e-14 |
2.569e−01 | 1.236e−01 |
1e-15 |
1.342e+00 | 9.591e−03 |
Đọc bảng theo chiều dọc: đường cong hình chữ V. Đi xuống, chạm đáy, rồi đi lên. Ở h = 1e-15,
sai phân tiến sai 1.342 trên một đáp án bằng 12 — sai hơn 11%, tức là gần như vô nghĩa, và
tệ hơn đáy 18,4 triệu lần.
(Ba dòng 1e-09 … 1e-11 giống hệt nhau và bốn dòng cuối nhảy loạn xạ — đó không phải lỗi in ấn.
Vùng đó là nhiễu thuần tuý; xem BẪY 2 để biết vì sao ba dòng kia trùng nhau.)
Vì sao có chữ V — hai nguồn sai số đánh nhau
| Nguồn sai số | Từ đâu ra | Đổi thế nào khi h giảm |
|---|---|---|
| Sai số cắt cụt (truncation) | Bạn vứt các số hạng bậc ≥ 2. Với x³: bạn vứt 3xh² + h³, còn lại sai ≈ 6h |
GIẢM tỉ lệ với h |
| Sai số làm tròn (rounding) | f(x+h) và f(x) là hai số float gần bằng nhau. Trừ nhau thì các chữ số giống nhau triệt tiêu, chỉ còn vài chữ số cuối — rồi bạn chia cho h rất nhỏ, tức nhân sai số lên 1/h |
TĂNG tỉ lệ với 1/h |
Một cái giảm theo h, một cái tăng theo 1/h. Tổng của chúng bắt buộc phải có một điểm cân bằng.
sai số tổng ≈ C₁·h + C₂·ε/h (ε = eps máy = 2.22e-16)
↑ ↑
cắt cụt làm tròn
Cực tiểu ở h* ≈ √(ε) — và √(2.22e-16) = 1.490e-08. Bảng cho đáy ở 1e-08. Lý thuyết và
số máy khớp nhau.
Nhìn tận mắt chỗ mất chữ số
Đây là dữ liệu thật, g(x) = x³ tại x = 2:
h = 1e-01 : g(x+h) = 9.261000000000001 g(x) = 8.0 hiệu = 1.261000000000001
h = 1e-08 : g(x+h) = 8.00000012 g(x) = 8.0 hiệu = 1.1999999927070348e-07
h = 1e-12 : g(x+h) = 8.000000000012001 g(x) = 8.0 hiệu = 1.2001066806988092e-11
h = 1e-15 : g(x+h) = 8.00000000000001 g(x) = 8.0 hiệu = 1.0658141036401503e-14
h = 1e-16 : g(x+h) = 8.0 g(x) = 8.0 hiệu = 0.0 ← CHẾT HẲN
Nhìn dòng h = 1e-15: 8.00000000000001 có 15 chữ số, trong đó 14 chữ số đầu giống hệt 8.0.
Trừ đi thì 14 chữ số đó biến mất, chỉ còn 1–2 chữ số có nghĩa. Bạn chia thứ rác đó cho 1e-15
và phóng đại nó lên 10¹⁵ lần.
Ở h = 1e-16 thì 2.0 + 1e-16 làm tròn về đúng 2.0 — float64 không đủ chỗ. Hiệu bằng 0,
đạo hàm bằng 0. Code chạy êm, trả về một con số đẹp đẽ, và sai 100%.
Đây là quy tắc 1 ở dạng nhỏ nhất, sờ được: bạn hiểu đúng 100% về đạo hàm, viết đúng công thức trong sách, và vẫn ra số sai — vì một hằng số thiết lập.
Không có exception nào ném ra. Không có warning nào. Cách chặn duy nhất là quét
hvà nhìn đường cong chữ V, hoặc đối chiếu với một implementation tham chiếu.Nếu bạn vừa dính bẫy này: đó là dòng đầu tiên đáng ghi vào nhật ký lỗi của môn 3.
Số cần nhớ, chỉ hai:
| Kiểu | h nên chọn |
Sai số đạt được |
|---|---|---|
| sai phân tiến | ≈ √ε ≈ 1e-8 (nhân với thang của x) |
~1e-8 |
| sai phân trung tâm | ≈ ε^(1/3) ≈ 6e-6 |
~1e-10 |
(ε^(1/3) = 6.055e-06, và bảng cho đáy của central ở 1e-05. Khớp bậc độ lớn.)
Đây chính là lý do gradcheck trong mọi framework dùng h cỡ 1e-5…1e-6, chứ không phải
1e-12. Bây giờ bạn biết vì sao — không phải mê tín, là toán.
BẪY 2 — h bạn viết KHÔNG phải bước máy thật sự đi
Bẫy phụ, tinh vi hơn, và nó giải thích ba dòng trùng nhau ở bảng trên.
1e-9 không biểu diễn được chính xác trong nhị phân, giống 0.1 không biểu diễn được. Nên khi
bạn viết x + h, cái máy thật sự cộng vào không phải h:
bạn viết h = 1e-09
máy đi bước thật (2.0 + h) − 2.0 = 1.000000082740371e-09
lệch tương đối 8.2740e-08
Bạn chia cho h = 1e-9 nhưng thực tế đã đi một bước dài hơn 8.274e-08 lần. Nhân với g'(2) = 12:
12 × 8.2740e-08 = 9.929e-07
Chính xác bằng con số ở dòng h = 1e-09 của bảng. Toàn bộ sai số ở ba dòng đó không phải sai
số cắt cụt, cũng không phải mất chữ số — nó chỉ là bạn chia cho một số khác với số bạn đã cộng.
Cách chữa (thủ thuật kinh điển, một dòng):
def central_fix(f, x, h):
xp, xm = x + h, x - h
return (f(xp) - f(xm)) / (xp - xm) # chia cho bước THẬT, không phải h
Assert (f) trong mục Code chứng minh nó giảm sai số. Nhưng nó không cứu BẪY 1 — mất chữ số
vẫn mất. Đừng nhầm hai chuyện.
BẪY 3 — không phải hàm nào cũng có đạo hàm
f(x) = |x| tại x = 0. Tính từ hai phía (đã chạy, mọi h):
từ bên phải: (|0+h| − 0)/h = +1
từ bên trái: (|0−h| − 0)/(−h) = −1
Hai phía cho hai đáp án khác nhau, với mọi h, không hội tụ. → |x| không có đạo hàm tại 0.
Đồ thị có một góc nhọn; ở đó không tồn tại "một" tiếp tuyến.
Vì sao bạn phải quan tâm: ReLU chính là max(0, x), và nó cũng có một góc nhọn tại 0. Framework
xử lý bằng cách chọn bừa một giá trị (PyTorch trả 0, một số nơi trả 1) — hợp lệ về mặt kỹ thuật
vì xác suất một số float rơi trúng đúng 0.0 là gần bằng không.
Nhưng để ý cách nói: "gần bằng không", không phải "bằng không". Đó là một chỗ nứt được vá bằng quy ước chứ không phải bằng định lý — chính xác kiểu chi tiết mà quy tắc 4 bắt bạn ghi rõ phạm vi.
Cách chữa BẪY 1: sai phân TRUNG TÂM
Thay vì nhìn về phía trước, nhìn cả hai bên:
f(x+h) − f(x) f(x+h) − f(x−h)
tiến: ─────────────────── trung tâm: ─────────────────
h 2h
Vì sao nó tốt hơn — chứng minh bằng đại số, với g(x) = x³ tại x = 2:
TIẾN: ((2+h)³ − 8)/h = (12h + 6h² + h³)/h = 12 + 6h + h²
↑ sai số ≈ 6h (bậc 1)
TRUNG TÂM: ((2+h)³ − (2−h)³)/(2h) = (24h + 2h³)/(2h) = 12 + h²
↑ sai số = h² (bậc 2)
Số hạng 6h triệt tiêu vì nó xuất hiện với dấu ngược nhau ở hai phía. Đó là toàn bộ mẹo:
lấy đối xứng thì mọi số hạng bậc lẻ tự huỷ.
Chứng minh bằng số (đã chạy, cột 2 và 3 của bảng BẪY 1):
h |
sai số tiến = 6h + h² |
sai số trung tâm = h² |
trung tâm tốt hơn |
|---|---|---|---|
1e-01 |
6.100e−01 | 1.000e−02 | 61× |
1e-02 |
6.010e−02 | 1.000e−04 | 601× |
1e-03 |
6.001e−03 | 1.000e−06 | 6 001× |
1e-04 |
6.000e−04 | 1.001e−08 | 59 950× |
1e-05 |
6.000e−05 | 2.118e−10 | 283 235× |
Cột "tiến" khớp đúng 6h + h² (ở h=0.1: 0.6 + 0.01 = 0.61 ✓). Cột "trung tâm" khớp đúng h²
(ở h=0.001: 1e-6 ✓). Hai cột, hai bậc, đại số và số máy nói cùng một câu.
Giá phải trả: trung tâm gọi hàm 2 lần thay vì 1. Với gradcheck trên
dtham số, đó là2dlần gọi thay vìd+1. Đắt gấp đôi, đổi lấy chính xác gấp hàng nghìn lần — gần như luôn đáng.Chỗ nó KHÔNG cứu được bạn: nếu
hđã nhỏ tới vùng mất chữ số (< 1e-9), hai cột cuối bảng BẪY 1 bằng nhau. Sai phân trung tâm chữa sai số cắt cụt, không chữa sai số làm tròn.
Một lưu ý trung thực để bạn không kết luận quá rộng (quy tắc 4): với f(x) = x², sai phân trung
tâm cho ra 2x chính xác tuyệt đối với mọi h — vì hàm bậc hai không có số hạng bậc 3 để mà
sai. Đó là một trường hợp đặc biệt, không phải quy luật. Đừng dùng x² để đo chất lượng của một
lược đồ sai phân; hãy dùng x³ trở lên.
Code — tự cài, KHÔNG dùng hàm có sẵn
Mỗi assert dưới đây tương ứng một khẳng định trong bài. Chạy được toàn bộ nghĩa là bạn không tin
lời tôi, bạn đã tự kiểm.
import numpy as np
from scipy.optimize import approx_fprime
from scipy.differentiate import derivative as scipy_derivative
def forward(f, x, h):
"""Sai phân TIẾN — định nghĩa sách giáo khoa. Sai số bậc 1: ~ C·h."""
return (f(x + h) - f(x)) / h
def central(f, x, h):
"""Sai phân TRUNG TÂM — nhìn cả hai bên. Sai số bậc 2: ~ C·h²."""
return (f(x + h) - f(x - h)) / (2 * h)
def central_fix(f, x, h):
"""Như central, nhưng chia cho BƯỚC THẬT máy đi, không phải h bạn viết."""
xp, xm = x + h, x - h
return (f(xp) - f(xm)) / (xp - xm)
f = lambda t: t ** 2 # f'(x) = 2x
g = lambda t: t ** 3 # g'(x) = 3x²
# (a) Bảng hội tụ §3 — phải ra ĐÚNG 7, 6.1, 6.01, 6.001
for h, want in [(1.0, 7.0), (0.1, 6.1), (0.01, 6.01), (0.001, 6.001)]:
assert abs(forward(f, 3.0, h) - want) < 1e-12, h
print("(a) bảng hội tụ x² tại x=3 → 7, 6.1, 6.01, 6.001 ✓")
# (b) Quy tắc n·x^(n-1), kiểm n = 0..5 tại nhiều điểm
for n in range(6):
p = lambda t, n=n: t ** n
for x in [1.3, 2.0, 3.0]:
assert abs(central(p, x, 1e-5) - n * x ** (n - 1)) < 1e-6, (n, x)
print("(b) quy tắc n·x^(n-1) đúng cho n = 0..5 ✓")
# (c) Hằng số → 0 ; tổng → cộng ; tích → f'g + fg'
assert abs(central(lambda t: 7.0, 3.0, 1e-5)) < 1e-9
assert abs(central(lambda t: 3 * t ** 2 + 5, 3.0, 1e-5) - 18.0) < 1e-8
assert abs(central(lambda t: (t ** 2) * (t ** 3), 2.0, 1e-5) - 80.0) < 1e-6
assert 2 * 2 * 8 + 4 * 3 * 4 == 80 # f'g + fg' tại x=2, tính tay
print("(c) hằng số / tổng / tích ✓ (tích: 2·2·8 + 4·3·4 = 80)")
# (d) 🪤 BẪY — h NHỎ HƠN KHÔNG PHẢI LÚC NÀO CŨNG CHÍNH XÁC HƠN
print("\n(d) quét h cho g(x)=x³ tại x=2, đáp án đúng = 12")
print(f"{'h':>8} | {'|sai số| forward':>17} | {'|sai số| central':>17}")
errs_f, errs_c = {}, {}
for k in range(1, 16):
h = 10.0 ** (-k)
ef = abs(forward(g, 2.0, h) - 12.0)
ec = abs(central(g, 2.0, h) - 12.0)
errs_f[h], errs_c[h] = ef, ec
print(f"{h:>8.0e} | {ef:>17.3e} | {ec:>17.3e}")
h_best_f = min(errs_f, key=errs_f.get)
h_best_c = min(errs_c, key=errs_c.get)
print(f"h tối ưu forward = {h_best_f:.0e} h tối ưu central = {h_best_c:.0e}")
# điểm tối ưu KHÔNG phải h nhỏ nhất — đây là toàn bộ cái bẫy
assert h_best_f == 1e-8 and errs_f[1e-15] > errs_f[1e-8]
assert h_best_c == 1e-5 and errs_c[1e-15] > errs_c[1e-5]
assert errs_f[1e-15] / errs_f[1e-8] > 1e6
print(" h = 1e-15 tệ hơn h = 1e-8 khoảng "
f"{errs_f[1e-15] / errs_f[1e-8]:.3e} lần ✓ (trực giác 'nhỏ hơn = tốt hơn' SAI)")
# lý thuyết: h* ≈ √eps cho forward, ≈ eps^(1/3) cho central
eps = np.finfo(float).eps
print(f" √eps = {np.sqrt(eps):.3e} eps^(1/3) = {eps ** (1/3):.3e} (khớp bậc độ lớn)")
# (e) Central thắng forward ở CÙNG một h — vì h² < h
for k in [1, 2, 3, 4, 5, 6, 7]:
assert errs_c[10.0 ** (-k)] < errs_f[10.0 ** (-k)] # central luôn thắng
for k in [1, 2, 3]: # vùng chưa bị nhiễu làm tròn
h = 10.0 ** (-k)
assert abs(errs_f[h] - (6 * h + h * h)) < 1e-5 * errs_f[h] # forward sai = 6h + h²
assert abs(errs_c[h] - h * h) < 1e-5 * errs_c[h] # central sai = h²
print(f"\n(e) tại h=1e-3: forward sai {errs_f[1e-3]:.3e}, central sai {errs_c[1e-3]:.3e}"
f" → central tốt hơn {errs_f[1e-3] / errs_c[1e-3]:.0f} lần ✓")
# (f) 🪤 BẪY PHỤ — h bạn viết KHÔNG phải bước máy thật sự đi
h = 1e-9
buoc_that = (2.0 + h) - 2.0
print(f"\n(f) bạn viết h = {h:.0e}, máy đi bước thật = {buoc_that!r}")
print(f" lệch tương đối = {(buoc_that - h) / h:.4e} ; nhân f'=12 → {12 * (buoc_that - h) / h:.3e}")
assert abs(12 * (buoc_that - h) / h - (central(g, 2.0, h) - 12.0)) < 1e-12
assert abs(central_fix(g, 2.0, h) - 12.0) < abs(central(g, 2.0, h) - 12.0)
print(" → toàn bộ sai số ở dòng h=1e-9 CHỈ là lỗi biểu diễn h ✓ central_fix chữa được")
# (g) |x| tại 0: hai bên cho hai đáp án khác nhau → KHÔNG có đạo hàm
for h in [1.0, 0.1, 1e-6]:
assert forward(abs, 0.0, h) == 1.0 and forward(abs, 0.0, -h) == -1.0
print("\n(g) |x| tại 0: bên phải → +1, bên trái → −1, không hội tụ → KHÔNG khả vi ✓")
# (h) Đối chiếu scipy — BẮT BUỘC
assert abs(approx_fprime(np.array([2.0]), lambda v: v[0] ** 3, 1.5e-8)[0] - 12.0) < 1e-6
assert abs(scipy_derivative(g, 2.0).df - 12.0) < 1e-10
assert np.allclose(np.gradient(np.linspace(0, 4, 5) ** 2, np.linspace(0, 4, 5)),
[1., 2., 4., 6., 7.])
print("(h) khớp scipy.optimize.approx_fprime, scipy.differentiate.derivative, np.gradient ✓")
# (i) Đạo hàm = XẤP XỈ TUYẾN TÍNH TỐT NHẤT: sai số tiếp tuyến là h², không phải h
tiep_tuyen = lambda t: 6 * t - 9 # tại x=3 của f(x)=x²
for h in [1.0, 0.1, 0.01, 0.001]:
assert abs(f(3 + h) - tiep_tuyen(3 + h) - h * h) < 1e-12
print("(i) sai số tiếp tuyến y=6x−9 đúng bằng h² ✓ → đạo hàm là xấp xỉ tuyến tính tốt nhất")
# (j) Prometheus: counter là tổng dồn, rate là đạo hàm
t = np.array([0., 60., 120., 180., 240.])
counter = np.array([0., 120., 300., 540., 840.])
rate = np.diff(counter) / np.diff(t)
assert np.allclose(rate, [2., 3., 4., 5.])
print("(j) counter", counter.astype(int), "→ rate", rate, "req/s ✓ rate() CHÍNH LÀ tỉ số sai phân")
# (k) Teaser bài 2: lim (a^h − 1)/h = ln a, và bằng ĐÚNG 1 khi a = e
for a in [2.0, 3.0, np.e, 10.0]:
D = central(lambda t, a=a: a ** t, 0.0, 1e-5)
assert abs(D - np.log(a)) < 1e-9, a
assert abs(central(np.exp, 0.0, 1e-5) - 1.0) < 1e-9
print("(k) (a^h−1)/h → ln a : a=2 → 0.6931, a=3 → 1.0986, a=e → 1.0000 ✓")
print("\nTẤT CẢ ASSERT ĐỀU QUA ✓")
Output phải ra:
(a) bảng hội tụ x² tại x=3 → 7, 6.1, 6.01, 6.001 ✓
(b) quy tắc n·x^(n-1) đúng cho n = 0..5 ✓
(c) hằng số / tổng / tích ✓ (tích: 2·2·8 + 4·3·4 = 80)
(d) quét h cho g(x)=x³ tại x=2, đáp án đúng = 12
h | |sai số| forward | |sai số| central
1e-01 | 6.100e-01 | 1.000e-02
1e-02 | 6.010e-02 | 1.000e-04
1e-03 | 6.001e-03 | 1.000e-06
1e-04 | 6.000e-04 | 1.001e-08
1e-05 | 6.000e-05 | 2.118e-10
1e-06 | 6.002e-06 | 7.892e-10
1e-07 | 5.843e-07 | 6.316e-09
1e-08 | 7.293e-08 | 1.173e-07
1e-09 | 9.929e-07 | 9.929e-07
1e-10 | 9.929e-07 | 9.929e-07
1e-11 | 9.929e-07 | 9.929e-07
1e-12 | 1.067e-03 | 1.067e-03
1e-13 | 9.591e-03 | 9.591e-03
1e-14 | 2.569e-01 | 1.236e-01
1e-15 | 1.342e+00 | 9.591e-03
h tối ưu forward = 1e-08 h tối ưu central = 1e-05
h = 1e-15 tệ hơn h = 1e-8 khoảng 1.840e+07 lần ✓ (trực giác 'nhỏ hơn = tốt hơn' SAI)
√eps = 1.490e-08 eps^(1/3) = 6.055e-06 (khớp bậc độ lớn)
(e) tại h=1e-3: forward sai 6.001e-03, central sai 1.000e-06 → central tốt hơn 6001 lần ✓
(f) bạn viết h = 1e-09, máy đi bước thật = 1.000000082740371e-09
lệch tương đối = 8.2740e-08 ; nhân f'=12 → 9.929e-07
→ toàn bộ sai số ở dòng h=1e-9 CHỈ là lỗi biểu diễn h ✓ central_fix chữa được
(g) |x| tại 0: bên phải → +1, bên trái → −1, không hội tụ → KHÔNG khả vi ✓
(h) khớp scipy.optimize.approx_fprime, scipy.differentiate.derivative, np.gradient ✓
(i) sai số tiếp tuyến y=6x−9 đúng bằng h² ✓ → đạo hàm là xấp xỉ tuyến tính tốt nhất
(j) counter [ 0 120 300 540 840] → rate [2. 3. 4. 5.] req/s ✓ rate() CHÍNH LÀ tỉ số sai phân
(k) (a^h−1)/h → ln a : a=2 → 0.6931, a=3 → 1.0986, a=e → 1.0000 ✓
TẤT CẢ ASSERT ĐỀU QUA ✓
Quy tắc đang được luyện ở đây: khi đạo hàm số của bạn lệch đáp án giải tích, nguyên nhân
gần như luôn là h — quá lớn (cắt cụt) hoặc quá nhỏ (làm tròn) — chứ không phải hiểu sai toán.
Ghi vào nhật ký lỗi, kèm giá trị h đã dùng.
Nối vào AI
| Khái niệm bài này | Chỗ dùng trong AI |
|---|---|
| Đạo hàm = tốc độ thay đổi | Toàn bộ tín hiệu học của mạng nơ-ron: "đổi trọng số w một chút thì loss thay đổi bao nhiêu?" — không có nó thì không có huấn luyện |
| Xấp xỉ tuyến tính tốt nhất | Một bước gradient descent chọn hướng giảm theo xấp xỉ tuyến tính tại chỗ. Độ dài bước bằng learning rate nhân chuẩn gradient. lr quá lớn có thể gây phân kỳ (capstone môn 3) |
| Tỉ số sai phân | gradcheck — một cách kiểm chứng backprop tự viết bằng phép tính độc lập. Đây là "implementation tham chiếu" của quy tắc 1 và "baseline ngu nhất chạy được" của quy tắc 2, gộp làm một |
h tối ưu ≈ √ε / ε^(1/3) |
Vì sao gradcheck dùng h ≈ 1e-5…1e-6 chứ không 1e-12. Ai không biết bảng chữ V sẽ chọn h càng nhỏ càng tốt và kết luận sai rằng backprop của mình hỏng |
| Sai phân trung tâm | Chuẩn mực của gradcheck: đắt gấp đôi, chính xác gấp hàng nghìn lần. Đánh đổi này lặp lại khắp nơi trong ML |
| Điểm không khả vi | ReLU tại 0, max, abs, clip, indexing theo argmax — chỗ nào có góc nhọn, chỗ đó gradient là một quy ước, không phải một định lý |
| Đạo hàm là một HÀM, không phải một số | Lấy đạo hàm tiếp cho f'' và Hessian (bài 4), mô tả độ cong. Adam (bài 5) dùng trung bình gradient và gradient bình phương, không trực tiếp tính Hessian |
counter ↔ rate() |
Cặp tích phân ↔ đạo hàm. Trong ML nó xuất hiện lại thành cặp hàm mật độ ↔ hàm phân phối tích luỹ (môn 4) |
| Gradient = vector, độ dốc theo hướng = tích vô hướng | Bài 4 + bài 5. Và nó là công thức môn 2 bài 1, không phải kiến thức mới |
Ba cách lấy đạo hàm — và vì sao AI dùng cách thứ ba:
| Cách | Chính xác? | Chi phí cho d tham số |
Dùng khi |
|---|---|---|---|
| Số (bài này) | ~1e-10, phụ thuộc h |
2d lần gọi hàm |
kiểm chứng, hàm hộp đen |
| Ký hiệu (đạo hàm bằng tay/SymPy) | chính xác | biểu thức phình to theo cấp số nhân | hàm nhỏ, phân tích lý thuyết |
| Autodiff (backprop) | chính xác tới sai số máy | ~1–3 lần chi phí hàm gốc, không phụ thuộc d |
mọi framework deep learning |
Autodiff không phải "đạo hàm số làm nhanh". Nó là chain rule áp dụng một cách có hệ thống — và chain rule là bài 3.
Tự kiểm — trả lời được mới sang bài 2
- "Tốc độ tức thời" theo nghĩa đen là
0/0. Giới hạn né được nghịch lý đó bằng cách nào? Trả lời mà không dùng chữ "vô cùng bé". - Vì sao
f'là một hàm, không phải một con số? Và vì sao điều đó cho phép lấy đạo hàm tiếp để cóf''và Hessian? - Suy
d/dx(x³) = 3x²bằng hình (khối lập phương), không bằng công thức thuộc lòng. Ba số hạng3x²h,3xh²,h³là ba vật thể hình học nào, và vì sao chỉ số hạng đầu sống sót? (fg)' ≠ f'g'. Vẽ hình chữ nhật và chỉ ra ô nào bị bỏ, ô đó bậc mấy theoh, và vì sao bỏ được.- Sai số của đạo hàm số có đáy ở
h ≈ 1e-8rồi tăng trở lại. Nêu hai nguồn sai số, chiều biến thiên của mỗi cái theoh, và vì sao điểm cân bằng bắt buộc phải tồn tại. - Sai phân trung tâm chính xác hơn sai phân tiến hàng nghìn lần ở cùng
h. Chứng minh bằng đại số (khai triển(x+h)³và(x−h)³) — số hạng nào triệt tiêu, và vì sao đối xứng lại làm nó triệt tiêu? - Ở
h = 1e-16, code tính(f(x+h) − f(x))/htrả về 0.0 và không hề báo lỗi. Chuyện gì đã xảy ra ở tầng float? Và một hệ thống thật sẽ phát hiện lỗi này bằng cách nào, nếu nó không bao giờ crash? f(x,y) = x² + y²có∇f(3,4) = (6,8). Không tính lại: độ dốc theo hướng(0.6, 0.8)bằng bao nhiêu và vì sao nó bằng đúng|∇f|? Còn theo hướng(−0.8, 0.6)? (Trả lời bằng tích vô hướng của môn 2 bài 1.)
Câu 5 và câu 7 là hai câu đắt nhất — và câu 7 là câu khó nhất của bài. Câu 5 là chỗ bạn chuyển từ biết công thức sang biết công thức hỏng ở đâu. Câu 7 đi xa hơn: nó hỏi bạn thiết kế một cơ chế phát hiện cho một lỗi không bao giờ tự báo. Nếu bạn trả lời được câu 7 bằng "quét
hvà nhìn đường cong chữ V", bạn vừa tự phát minh ra cách gradcheck thật sự được kiểm chất lượng.Câu 8 là câu trả cổ tức xa nhất: nó nối trực tiếp tới bài 4 và bài 5, và nó chứng minh môn 2 chưa hề kết thúc.
Bài tập về nhà trước bài 2
3Blue1Brown — Essence of Calculus
| Tập | Ưu tiên | Vì sao |
|---|---|---|
| 1 — The essence of calculus | 🟠 | Bản đồ toàn series. Xem một lần, không ghi chép. Ý cần lấy: giải tích không phải một đống công thức, nó là một cách nhìn về sự thay đổi |
| 2 — The paradox of the derivative | 🔴🔴 | ⭐ Tập của bài này. Chính là §2. Xem hai lần. Grant đào đúng chỗ mọi sách giáo khoa lướt qua: "thay đổi tại một khoảnh khắc" là mâu thuẫn, và giới hạn là cách thoát |
| 3 — Derivative formulas through geometry | 🔴 | Chính là §5. Hình chữ nhật f·dg + g·df + df·dg sẽ động đậy trên màn hình. Xem xong quay lại đọc §5.4 một lần nữa, nó sẽ đọc khác hẳn |
Xem không ghi chép, không dừng lại giải bài. Mục tiêu duy nhất: có hình ảnh trong đầu. Giữ sẵn một câu hỏi khi xem tập 2: nếu tại một khoảnh khắc xe không di chuyển, đồng hồ tốc độ đang chỉ cái gì?
Vì sao BỎ QUA một số tập — và ghi rõ phạm vi (quy tắc 4)
| Tập bỏ qua | Lý do |
|---|---|
| 8 — Integration and the fundamental theorem of calculus | Tích phân không xuất hiện trong lộ trình này: backprop, gradient descent, optimizer đều chỉ cần đạo hàm. Bạn đã có đủ trực giác từ cặp counter ↔ rate() ở §1 |
| 9 — What does area have to do with slope? | Cùng lý do — nó là phần hai của tập 8 |
| 11 — Taylor series | Chỉ cần biết là nó tồn tại: mọi hàm trơn xấp xỉ được bằng đa thức, và đạo hàm là số hạng bậc 1 của xấp xỉ đó — bạn đã dùng ý này ở §4. Chuỗi đầy đủ chỉ cần khi đọc chứng minh hội tụ của optimizer, không cần để dùng optimizer |
| 12 — The other way to visualize derivatives | Cách nhìn "transformational" — đẹp, nhưng không dùng lại ở đâu trong 10 môn còn lại |
Phạm vi của kết luận này: "chưa cần học sâu tích phân để làm các bài đạo hàm và tối ưu ở môn này". Không phải "tích phân vô dụng". Nếu bạn rẽ sang mô hình sinh (diffusion, normalizing flows, VAE) hoặc xác suất liên tục ở mức sâu, tích phân quay lại ngay lập tức. Ghi kèm điều kiện lật lại, đừng ghi kết luận trần.
Bài tập giấy — làm TRƯỚC khi gõ code
- Tính
d/dx(x⁴)bằng cách khai triển(x+h)⁴bằng tay. Liệt kê đủ 4 số hạng chứah, khoanh tròn số hạng bậc 1, và nói rõ vì sao ba số hạng còn lại chết. Kiểm bằng số tạix = 2, phải ra 32. - Viết phương trình tiếp tuyến của
f(x) = x³tạix = 2. Dùng nó xấp xỉf(2.01), rồi so với giá trị thật2.01³ = 8.120601. Sai lệch bằng bao nhiêu, và nó gần vớih²hayh³hơn? (Cẩn thận — kết quả khác §4, hãy giải thích vì sao.) - Không chạy code:
f(x) = 1000x, tính đạo hàm số tạix = 1vớih = 1e-13. Dự đoán trước kết quả có tin được không, rồi chạy để kiểm. (Gợi ý: hàm này tuyến tính, sai số cắt cụt bằng 0 — vậy còn lại nguồn sai số nào?) - Hàm
f(x) = x·|x|. Nó có đạo hàm tại 0 không? Tính từ hai phía. (So sánh với|x|ở BẪY 3 — một chữxđổi cả kết luận.)
Bài 2 sẽ là: e^x và log — e^x là nghiệm duy nhất của f' = f khi thêm f(0) = 1, và vì sao tính
chất đó khiến e xuất hiện ở mọi phương trình dạng "tốc độ thay đổi tỉ lệ với hiện trạng".
Câu hỏi mang theo sang bài 2 — bạn đã có sẵn dữ liệu ở assert (k):
đạo hàm của a^x tại x = 0 (tức hằng số nhân vào khi lấy đạo hàm a^x):
a = 2 → 0.6931
a = 3 → 1.0986
a = 10 → 2.3026
a = e → 1.0000 ← ĐÚNG BẰNG 1
2 cho ra một hằng số nhỏ hơn 1, 3 cho ra lớn hơn 1. Ở đâu đó giữa 2 và 3 phải có một số cho ra
đúng 1 — và đó chính là định nghĩa của e = 2.718281828....
e không phải một hằng số kỳ quặc ai đó chọn bừa. Nó là con số duy nhất làm cho phép lấy đạo hàm
không đổi gì cả. Bài 2 giải thích vì sao điều đó khiến e^x có mặt ở cả softmax lẫn công thức
suy giảm trí nhớ e^(−t/S) — cùng một lý do.