Skip to content
Phạm Khánh Minh Mẫn / syllabus

MÔN 03 / Bài 01

Đạo hàm

Phóng to đường cong để thấy tiếp tuyến; suy ra tốc độ thay đổi và hiểu giới hạn của sai phân số.

PDF ghi tay

Bản cô đọng: hình trước, công thức sau. Nền trắng, chữ đen.

Trong bài này ↓

~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ỗi x, nó trả về độ dốc tại x. 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ải h. Giảm h mườ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ới h; chỉ đúng một đường sai tỉ lệ với h².

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 theo h — nên khi chia cho h nó vẫn còn một h và 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 theo h, 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 u bấ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 u cù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 khi cos θ = 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ì −∇f là 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 h và 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 d tham số, đó là 2d lầ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

  1. "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é".
  2. 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?
  3. 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ạng 3x²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?
  4. (fg)' ≠ f'g'. Vẽ hình chữ nhật và chỉ ra ô nào bị bỏ, ô đó bậc mấy theo h, và vì sao bỏ được.
  5. Sai số của đạo hàm số có đáy ở h ≈ 1e-8 rồ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 theo h, và vì sao điểm cân bằng bắt buộc phải tồn tại.
  6. 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?
  7. Ở h = 1e-16, code tính (f(x+h) − f(x))/h trả 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?
  8. 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 h và 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

  1. 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ứa h, 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ại x = 2, phải ra 32.
  2. Viết phương trình tiếp tuyến của f(x) = x³ tại x = 2. Dùng nó xấp xỉ f(2.01), rồi so với giá trị thật 2.01³ = 8.120601. Sai lệch bằng bao nhiêu, và nó gần với h² hay h³ hơn? (Cẩn thận — kết quả khác §4, hãy giải thích vì sao.)
  3. Không chạy code: f(x) = 1000x, tính đạo hàm số tại x = 1 với h = 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?)
  4. 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.

Vị trí đọc được lưu trên trình duyệt này khi cho phép lưu trữ; không đồng bộ giữa các thiết bị.