Chuyên sâu

Gradient Descent — vì sao đi ngược đạo hàm?

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.

1. Bài toán tối ưu trong Machine Learning
Học = tìm tham số làm hàm mất mát nhỏ nhất

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 đó.

Trực giác: hình dung \(J(\theta)\) như một mặt địa hình. Bạn đang đứng đâu đó trên sườn núi và muốn xuống thung lũng thấp nhất, nhưng sương mù dày — chỉ nhìn được mặt đất ngay dưới chân. Chiến lược hợp lý: cảm nhận độ dốc tại chỗ đang đứng và bước theo hướng dốc xuống nhất. Lặp lại nhiều lần thì tới đáy.
2. Đạo hàm và gradient là gì
Độ dốc của hàm — và nó chỉ về đâu

Đạ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).

Khẳng định then chốt: tại mỗi điểm, gradient \(\nabla J(\theta)\) là vector chỉ hướng mà hàm \(J\) tăng nhanh nhất, và độ lớn \(\lVert \nabla J\rVert\) chính là tốc độ tăng nhanh nhất đó. Đây là tính chất quyết định mọi thứ phía sau: nếu gradient chỉ hướng lên dốc nhất, thì hướng xuống dốc nhất phải là chiều ngược lại.

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 đó.

3. Vì sao đi ngược gradient — chứng minh
Trực giác → Taylor → Cauchy–Schwarz

Đâ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.

Lưu ý về "nhanh nhất": ngược gradient là hướng giảm nhanh nhất cục bộ (theo xấp xỉ bậc nhất tại điểm đang đứng), chứ không phải đường ngắn nhất tới đáy trên toàn cục. Đó là lý do quỹ đạo Gradient Descent đôi khi đi "zigzag" trong các thung lũng hẹp.
4. Công thức cập nhật
Trái tim của Gradient Descent

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ụ.

5. Ví dụ một chiều trực quan
Vì sao dấu trừ luôn kéo về đáy

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\)
05.00010.0004.00025.000
14.0008.0003.20016.000
23.2006.4002.56010.240
32.5605.1202.0486.554
42.0484.0961.6384.194
51.6383.2771.3112.684
100.5371.0740.4290.288
200.0580.1150.0460.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ụ.

6. Learning rate η
Siêu tham số quan trọng nhấ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ảiGiảm \(J\) đều đặn và nhanh → lý tưởng.
Quá lớnMỗ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.

Chọn η thế nào: không có giá trị "đúng" cho mọi bài. Hãy thử theo thang log (\(0.1,\,0.01,\,0.001,\dots\)) và vẽ đường cong loss: nếu loss giảm rồi nảy lên hay bùng nổ \(\Rightarrow\) \(\eta\) quá lớn; nếu loss giảm cực chậm như đường thẳng \(\Rightarrow\) \(\eta\) quá nhỏ.
Learning rate schedule: kỹ thuật phổ biến là cho \(\eta\) lớn lúc đầu (đi nhanh khi còn xa đáy) rồi giảm dần về sau (bước cẩn thận khi gần đáy). Các lịch thường gặp: giảm theo bậc (step decay), giảm mũ (exponential decay), \(1/t\), hoặc cosine annealing. Các optimizer tự thích nghi như Adam cũng phần nào tự điều chỉnh độ dài bước theo từng tham số.
7. Gradient của hồi quy tuyến tính
Một ví dụ tính gradient cụ thể

Để 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)$$

Đọc ý nghĩa: \(X\theta - y\) là sai số dự đoán trên từng mẫu; nhân với \(X^{\top}\) phân bổ sai số đó ngược về từng trọng số tương ứng. Trọng số nào "góp" nhiều vào sai số sẽ bị điều chỉnh mạnh hơn — đúng tinh thần học từ lỗi.
8. Các biến thể của Gradient Descent
Từ Batch/SGD đến Adam

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ênTốc độ / NhiễuDùng khi nào
Batch GDToàn bộ \(m\) mẫu mỗi bướcMỗi bước chậm, nhưng mượt, ổn định; ít nhiễuDữ liệu nhỏ–vừa, cần quỹ đạo ổn định
Stochastic GD (SGD)1 mẫu ngẫu nhiên mỗi bướcMỗi bước rất nhanh, nhưng nhiễu mạnh, đường đi lắcDữ liệu cực lớn, học online; nhiễu còn giúp thoát local minima
Mini-batch GDMột lô nhỏ (vd 32–256 mẫu)Cân bằng tốc độ & ổn định; tận dụng vector hoá/GPUMặ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:

9. Hội tụ, hàm lồi và các cạm bẫy
Khi nào đảm bảo tới đáy, khi nào không

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.

Luôn chuẩn hoá đặc trưng trước khi chạy Gradient Descent (StandardScaler / MinMaxScaler). Đây là một trong những thủ thuật rẻ tiền nhưng tăng tốc hội tụ rõ rệt nhất, đặc biệt với hồi quy và mạng nơ-ron.
10. Code minh hoạ
Gradient Descent cho hồi quy tuyến tính bằng NumPy

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]
Thử nghiệm: đổi 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.
← Quay lại
Tổng quan Machine Learning