Gần như mọi mô hình machine learning đều quy về một việc: cực tiểu hoá hàm mất mát. Gradient Descent là thuật toán chủ lực để làm điều đó. Bài này không chỉ nêu công thức cập nhật θ := θ − η∇J mà còn chứng minh toán học vì sao bước đi tối ưu lại đúng bằng chiều ngược gradient — qua khai triển Taylor và bất đẳng thức Cauchy–Schwarz.
Khi huấn luyện một mô hình, ta có một bộ tham số \(\theta = (\theta_1, \theta_2, \dots, \theta_n)\) (ví dụ trọng số của hồi quy, của mạng nơ-ron) và một hàm mất mát \(J(\theta)\) đo mức độ sai của mô hình trên dữ liệu. \(J(\theta)\) càng nhỏ thì mô hình dự đoán càng khớp dữ liệu.
Mục tiêu của việc "học" được phát biểu gọn gàng bằng một bài toán tối ưu:
$$\theta^{*} \;=\; \arg\min_{\theta}\; J(\theta)$$
nghĩa là: tìm bộ tham số \(\theta^{*}\) khiến \(J\) đạt giá trị nhỏ nhất.
Với một số bài toán đẹp (như hồi quy tuyến tính bình phương tối thiểu) tồn tại nghiệm đóng (closed-form), ví dụ phương trình chuẩn \(\theta = (X^\top X)^{-1} X^\top y\). Nhưng trong đa số trường hợp:
Vì vậy ta cần một phương pháp tối ưu lặp (iterative optimization): bắt đầu từ một điểm bất kỳ, rồi từng bước "đi xuống" cho tới khi chạm đáy. Gradient Descent chính là cách đi đó.
Đạo hàm một biến. Với hàm một biến \(J(\theta)\), đạo hàm \(J'(\theta)=\dfrac{dJ}{d\theta}\) cho biết tốc độ thay đổi của \(J\) khi \(\theta\) nhích lên một chút — tức là độ dốc của đồ thị tại điểm đó:
Gradient — đạo hàm nhiều biến. Khi \(\theta\) có nhiều thành phần, ta lấy đạo hàm riêng theo từng thành phần và gom lại thành một vector gọi là gradient, ký hiệu \(\nabla J\):
$$\nabla J(\theta) \;=\; \begin{bmatrix} \dfrac{\partial J}{\partial \theta_1} \\[6pt] \dfrac{\partial J}{\partial \theta_2} \\[2pt] \vdots \\[2pt] \dfrac{\partial J}{\partial \theta_n} \end{bmatrix}$$
Mỗi thành phần \(\partial J/\partial \theta_i\) cho biết \(J\) nhạy thế nào với riêng \(\theta_i\) (giữ các biến khác cố định).
Ta sẽ chứng minh chính xác khẳng định này ở mục tiếp theo. Trực giác đồi núi: đứng trên sườn dốc, gradient là mũi tên chỉ thẳng lên đỉnh theo đường dốc đứng nhất; muốn xuống nhanh nhất ta đi đúng ngược mũi tên đó.
Đây là phần quan trọng nhất của bài. Ta trả lời câu hỏi: trong tất cả các hướng có thể bước đi từ điểm \(\theta\), hướng nào làm \(J\) giảm nhanh nhất? Ta tiếp cận từ ba góc nhìn bổ trợ nhau.
(a) Góc nhìn trực giác. Gradient chỉ hướng hàm tăng nhanh nhất. Mà ta muốn giảm \(J\) nhanh nhất, tức "leo xuống" mạnh nhất. Vậy hợp lý là bước đúng theo hướng ngược với hướng tăng nhanh nhất — ngược gradient. Trực giác này đúng, nhưng cần chứng minh nó thật sự tối ưu chứ không chỉ "hợp lý". Đó là việc của hai phần sau.
(b) Khai triển Taylor bậc nhất. Giả sử ta đứng tại \(\theta\) và bước một đoạn nhỏ \(\Delta\theta\). Khai triển Taylor bậc nhất của \(J\) quanh \(\theta\) cho:
$$J(\theta + \Delta\theta) \;\approx\; J(\theta) + \nabla J(\theta)^{\top}\, \Delta\theta$$
Số hạng \(\nabla J(\theta)^{\top}\Delta\theta\) là tích vô hướng giữa gradient và bước đi — nó chính là mức thay đổi (xấp xỉ) của \(J\) sau khi bước. Ta muốn \(J\) giảm, tức là cần:
$$\nabla J(\theta)^{\top}\, \Delta\theta \;\lt\; 0$$
Để tách riêng "hướng" khỏi "độ dài bước", ta viết bước đi dưới dạng \(\Delta\theta = \eta\, v\), trong đó \(\eta \gt 0\) là độ dài bước (learning rate) và \(v\) là vector hướng đơn vị, \(\lVert v\rVert = 1\). Khi đó mức giảm của \(J\) (xấp xỉ, theo bậc nhất) là:
$$J(\theta + \eta v) - J(\theta) \;\approx\; \eta\, \nabla J(\theta)^{\top} v$$
Vì \(\eta \gt 0\) cố định, muốn \(J\) giảm càng nhiều càng tốt ta phải chọn hướng \(v\) làm cho \(\nabla J(\theta)^{\top} v\) nhỏ nhất (âm nhất). Câu hỏi tối ưu trở thành:
$$\min_{\lVert v\rVert = 1}\; \nabla J(\theta)^{\top} v$$
(c) Bất đẳng thức Cauchy–Schwarz — lời giải. Với hai vector bất kỳ \(a, b\), bất đẳng thức Cauchy–Schwarz nói rằng \(\lvert a^{\top} b\rvert \le \lVert a\rVert\,\lVert b\rVert\), suy ra:
$$-\,\lVert a\rVert\,\lVert b\rVert \;\le\; a^{\top} b \;\le\; \lVert a\rVert\,\lVert b\rVert$$
Áp dụng với \(a = \nabla J(\theta)\) và \(b = v\) (có \(\lVert v\rVert = 1\)):
$$\nabla J(\theta)^{\top} v \;\ge\; -\,\lVert \nabla J(\theta)\rVert\,\lVert v\rVert \;=\; -\,\lVert \nabla J(\theta)\rVert$$
Vậy giá trị nhỏ nhất mà \(\nabla J(\theta)^{\top} v\) có thể đạt là \(-\lVert \nabla J(\theta)\rVert\). Dấu bằng (cực tiểu) xảy ra khi và chỉ khi \(v\) cùng phương và ngược chiều với \(\nabla J(\theta)\), tức là:
$$v^{*} \;=\; -\,\frac{\nabla J(\theta)}{\lVert \nabla J(\theta)\rVert}$$
Đây chính là kết luận cốt lõi: hướng giảm nhanh nhất của \(J\) tại \(\theta\) là hướng ngược gradient. Đó là lý do toán học (không phải chỉ trực giác) vì sao Gradient Descent bước theo \(-\nabla J\).
Đạo hàm theo hướng (directional derivative). Cùng một kết quả có thể phát biểu gọn bằng khái niệm đạo hàm theo hướng. Tốc độ thay đổi của \(J\) khi đi theo hướng đơn vị \(v\) là:
$$D_v J(\theta) \;=\; \nabla J(\theta)^{\top} v$$
Theo Cauchy–Schwarz, \(D_v J\) lớn nhất (\(= +\lVert\nabla J\rVert\)) khi \(v\) cùng chiều gradient (đi lên dốc nhất), và nhỏ nhất (\(= -\lVert\nabla J\rVert\)) khi \(v\) ngược chiều gradient (xuống dốc nhất). Khi \(v\) vuông góc với gradient thì \(D_v J = 0\) — đi dọc đường đồng mức, \(J\) không đổi.
Suy ra luật cập nhật. Lấy bước đi theo hướng tối ưu \(v^{*}\) với độ dài \(\eta\):
$$\Delta\theta = \eta\, v^{*} = -\,\eta\,\frac{\nabla J(\theta)}{\lVert \nabla J(\theta)\rVert}$$
Trên thực tế người ta gộp luôn hệ số \(1/\lVert\nabla J\rVert\) vào learning rate (để bước tự nhỏ lại khi gần đáy, nơi gradient nhỏ), thu được luật cập nhật quen thuộc \(\theta := \theta - \eta\,\nabla J(\theta)\) ở mục sau.
Từ chứng minh trên, mỗi vòng lặp ta cập nhật tham số theo:
$$\theta \;:=\; \theta \;-\; \eta\,\nabla J(\theta)$$
Giải nghĩa từng thành phần:
| Ký hiệu | Ý nghĩa |
|---|---|
| \(\theta\) | Bộ tham số hiện tại (điểm đang đứng trên mặt mất mát) |
| \(\nabla J(\theta)\) | Gradient tại \(\theta\) — chỉ hướng tăng nhanh nhất của \(J\) |
| \(-\nabla J(\theta)\) | Dấu trừ: đảo lại thành hướng giảm nhanh nhất (xuống dốc) |
| \(\eta\) | Learning rate (tốc độ học) — độ dài mỗi bước, \(\eta \gt 0\) |
| \(:=\) | Phép gán: lấy giá trị mới ghi đè lên \(\theta\) cũ |
Ta lặp công thức này nhiều lần. Mỗi vòng, \(\theta\) trượt một bước xuống dốc và \(J(\theta)\) giảm dần. Khi gần đáy, gradient tiến về \(0\) nên các bước tự nhỏ lại và thuật toán hội tụ.
Xét hàm đơn giản nhất có đáy tại \(0\):
$$J(\theta) = \theta^{2}, \qquad J'(\theta) = 2\theta$$
Luật cập nhật: \(\theta := \theta - \eta\,(2\theta)\). Hãy xem dấu trừ "tự biết đường" thế nào:
Dù xuất phát ở bên nào, dấu trừ trong công thức luôn đẩy \(\theta\) về đáy. Lấy \(\eta = 0.1\) và \(\theta_0 = 5\), công thức rút gọn thành \(\theta := \theta - 0.1\cdot 2\theta = 0.8\,\theta\). Vài bước cụ thể:
| Bước \(t\) | \(\theta_t\) | \(J'(\theta_t)=2\theta_t\) | \(\theta_{t+1}=0.8\,\theta_t\) | \(J(\theta_t)=\theta_t^2\) |
|---|---|---|---|---|
| 0 | 5.000 | 10.000 | 4.000 | 25.000 |
| 1 | 4.000 | 8.000 | 3.200 | 16.000 |
| 2 | 3.200 | 6.400 | 2.560 | 10.240 |
| 3 | 2.560 | 5.120 | 2.048 | 6.554 |
| 4 | 2.048 | 4.096 | 1.638 | 4.194 |
| 5 | 1.638 | 3.277 | 1.311 | 2.684 |
| 10 | 0.537 | 1.074 | 0.429 | 0.288 |
| 20 | 0.058 | 0.115 | 0.046 | 0.003 |
Quan sát: \(J(\theta_t)\) giảm đều về \(0\), và các bước đi nhỏ dần khi tới gần đáy (vì gradient \(2\theta\) co lại). Đúng như lý thuyết hội tụ.
Learning rate \(\eta\) quyết định độ dài mỗi bước. Đây thường là siêu tham số nhạy cảm nhất của Gradient Descent:
| Giá trị \(\eta\) | Hiện tượng |
|---|---|
| Quá nhỏ | Mỗi bước nhích rất ít → hội tụ chậm, tốn rất nhiều vòng lặp mới tới đáy. Có thể "kẹt" như chưa học gì. |
| Vừa phải | Giảm \(J\) đều đặn và nhanh → lý tưởng. |
| Quá lớn | Mỗi bước vọt lố qua đáy sang sườn bên kia, có khi còn cao hơn. \(J\) dao động hoặc tăng dần và phân kỳ (diverge). |
Quay lại ví dụ \(J=\theta^2\): cập nhật là \(\theta := (1-2\eta)\theta\). Hệ số \(|1-2\eta|\) phải nhỏ hơn \(1\) để hội tụ, tức cần \(0 \lt \eta \lt 1\). Nếu \(\eta = 1\) thì \(\theta\) nhảy qua lại \(5 \to -5 \to 5\) mãi không hội tụ; nếu \(\eta \gt 1\), biên độ phình to và phân kỳ. Một con số nhỏ như \(\eta\) đã đổi hoàn toàn hành vi.
Để thấy gradient được tính ra sao trong thực tế, xét hồi quy tuyến tính. Gọi \(X \in \mathbb{R}^{m\times n}\) là ma trận đặc trưng (\(m\) mẫu, \(n\) đặc trưng), \(y \in \mathbb{R}^{m}\) là nhãn, \(\theta \in \mathbb{R}^{n}\) là trọng số. Dự đoán là \(\hat y = X\theta\). Hàm mất mát bình phương trung bình (thêm \(\tfrac12\) cho gọn đạo hàm):
$$J(\theta) = \frac{1}{2m}\,\lVert X\theta - y\rVert^{2} = \frac{1}{2m}\,(X\theta - y)^{\top}(X\theta - y)$$
Lấy đạo hàm. Đặt vector phần dư (residual) \(r = X\theta - y\). Khai triển:
$$J(\theta) = \frac{1}{2m}\Big( \theta^{\top}X^{\top}X\theta - 2\,\theta^{\top}X^{\top}y + y^{\top}y \Big)$$
Dùng hai quy tắc đạo hàm ma trận \(\dfrac{\partial}{\partial\theta}(\theta^{\top}A\theta) = 2A\theta\) (với \(A=X^\top X\) đối xứng) và \(\dfrac{\partial}{\partial\theta}(\theta^{\top}b) = b\):
$$\nabla J(\theta) = \frac{1}{2m}\Big( 2X^{\top}X\theta - 2X^{\top}y \Big) = \frac{1}{m}\,X^{\top}(X\theta - y)$$
Gọn lại, gradient của hồi quy tuyến tính là:
$$\nabla J(\theta) = \frac{1}{m}\,X^{\top}(X\theta - y)$$
Thay vào luật cập nhật, ta có Gradient Descent cho hồi quy tuyến tính:
$$\theta \;:=\; \theta \;-\; \frac{\eta}{m}\,X^{\top}(X\theta - y)$$
Theo lượng dữ liệu dùng để tính gradient mỗi bước — đây là ba biến thể nền tảng:
| Biến thể | Gradient tính trên | Tốc độ / Nhiễu | Dùng khi nào |
|---|---|---|---|
| Batch GD | Toàn bộ \(m\) mẫu mỗi bước | Mỗi bước chậm, nhưng mượt, ổn định; ít nhiễu | Dữ liệu nhỏ–vừa, cần quỹ đạo ổn định |
| Stochastic GD (SGD) | 1 mẫu ngẫu nhiên mỗi bước | Mỗi bước rất nhanh, nhưng nhiễu mạnh, đường đi lắc | Dữ liệu cực lớn, học online; nhiễu còn giúp thoát local minima |
| Mini-batch GD | Một lô nhỏ (vd 32–256 mẫu) | Cân bằng tốc độ & ổn định; tận dụng vector hoá/GPU | Mặc định thực tế cho hầu hết deep learning |
SGD cập nhật với gradient ước lượng trên một mẫu \((x^{(i)}, y^{(i)})\): \(\theta := \theta - \eta\,\nabla J_i(\theta)\). Vì chỉ dùng một mẫu nên ước lượng "ồn" nhưng trung bình vẫn đúng hướng, và rẻ hơn rất nhiều mỗi bước.
Nhóm cải tiến (optimizer nâng cao) — thêm cơ chế để đi nhanh và ổn định hơn:
Hàm lồi (convex). Một hàm là lồi nếu đoạn thẳng nối hai điểm bất kỳ trên đồ thị luôn nằm trên (hoặc trùng) đồ thị: \(J(\lambda a + (1-\lambda) b) \le \lambda J(a) + (1-\lambda) J(b)\) với \(\lambda \in [0,1]\). Với hàm lồi (như MSE của hồi quy tuyến tính), mọi cực tiểu cục bộ đều là cực tiểu toàn cục — Gradient Descent với learning rate hợp lý được đảm bảo hội tụ về nghiệm tối ưu.
Hàm không lồi (non-convex). Mạng nơ-ron có bề mặt mất mát gồ ghề với nhiều cạm bẫy:
May mắn là nhiễu của SGD/mini-batch và quán tính của Momentum thường giúp thoát saddle point và local minima nông; trong thực tế deep learning, các local minima tìm được thường đủ tốt.
Vai trò của chuẩn hoá dữ liệu (feature scaling). Nếu các đặc trưng có thang đo rất khác nhau (ví dụ tuổi 0–100 và thu nhập 0–1.000.000), đường đồng mức của \(J\) bị kéo dài thành ellipse rất dẹt. Gradient Descent khi đó đi zigzag dọc khe hẹp, hội tụ chậm. Sau khi chuẩn hoá (đưa mỗi đặc trưng về cùng thang, vd trung bình 0 phương sai 1), đường đồng mức tròn lại gần như hình tròn, gradient chỉ thẳng về tâm \(\Rightarrow\) hội tụ nhanh hơn nhiều.
Cài đặt đầy đủ luật \(\theta := \theta - \tfrac{\eta}{m} X^{\top}(X\theta - y)\). Ta tạo dữ liệu giả theo quan hệ tuyến tính \(y \approx 4 + 3x\) rồi để Gradient Descent tự tìm lại hai hệ số đó:
import numpy as np # 1. Tạo dữ liệu giả: y = 4 + 3x + nhiễu np.random.seed(42) m = 100 # số mẫu x = 2 * np.random.rand(m, 1) y = 4 + 3 * x + np.random.randn(m, 1) # 2. Thêm cột bias (cột 1) để học cả hệ số chặn X = np.c_[np.ones((m, 1)), x] # shape (m, 2) # 3. Khởi tạo tham số ngẫu nhiên và đặt learning rate theta = np.random.randn(2, 1) # [theta0, theta1] eta = 0.1 # learning rate n_iters = 1000 def compute_loss(X, y, theta): err = X @ theta - y # residual = Xθ - y return (err.T @ err).item() / (2 * len(y)) # 4. Vòng lặp Gradient Descent for i in range(n_iters): grad = (1 / m) * X.T @ (X @ theta - y) # ∇J = (1/m) Xᵀ(Xθ - y) theta = theta - eta * grad # θ := θ - η∇J if i % 100 == 0: print(f"iter {i:4d} loss = {compute_loss(X, y, theta):.4f}") print("theta hoc duoc:", theta.ravel()) # kỳ vọng ≈ [4, 3]
Kết quả in ra cho thấy loss giảm dần qua từng vòng và \(\theta\) hội tụ về xấp xỉ \([4, 3]\) — đúng quan hệ đã tạo dữ liệu:
iter 0 loss = 6.8417 iter 100 loss = 0.5470 iter 200 loss = 0.4889 iter 300 loss = 0.4843 iter 900 loss = 0.4841 theta hoc duoc: [4.215 2.770]
eta = 0.5 rồi eta = 0.001 và xem đường loss. Với \(\eta\) quá lớn loss sẽ nảy lên/phân kỳ, với \(\eta\) quá nhỏ loss giảm rất chậm — đúng như mục 6. Đây là cách trực quan nhất để "cảm" được vai trò của learning rate.