Gradient descent, hay thuật toán hạ dốc, là cách tìm giá trị nhỏ nhất của một hàm bằng cách lặp đi lặp lại một việc: tính đạo hàm tại chỗ đang đứng, rồi bước một bước nhỏ theo hướng ngược với đạo hàm. Trong học máy, hàm cần làm nhỏ là hàm mất mát, đo mô hình đang sai bao nhiêu; các biến là tham số của mô hình. Gần như mọi mạng nơ-ron hiện nay, kể cả các mô hình ngôn ngữ lớn, được huấn luyện bằng một biến thể của thuật toán này.
Công thức cốt lõi chỉ có một dòng: tham số mới bằng tham số cũ trừ đi tốc độ học nhân với đạo hàm. Bài này tính tay vài bước với một hàm đơn giản, cho thấy điều gì xảy ra khi tốc độ học quá lớn hoặc quá nhỏ, rồi mở rộng lên trường hợp hàng triệu tham số và các biến thể mà thư viện học máy đang dùng.
Hình ảnh trực giác: xuống núi trong sương mù
Hình dung bạn đứng trên sườn núi lúc sương dày, không nhìn thấy thung lũng ở đâu, chỉ cảm nhận được độ dốc ngay dưới chân. Cách hợp lý là dò xem hướng nào đổ xuống nhiều nhất, bước một bước theo hướng đó, rồi dò lại. Lặp đủ nhiều lần, bạn sẽ xuống tới một chỗ trũng, nơi mặt đất phẳng theo mọi hướng.
Thuật toán hạ dốc làm đúng như vậy. Độ cao là giá trị hàm mất mát, vị trí là bộ tham số của mô hình, độ dốc dưới chân là đạo hàm. Thuật toán không cần biết toàn bộ hình dạng ngọn núi, nó chỉ cần đạo hàm tại điểm hiện tại. Đây là lý do nó dùng được cho những hàm có hàng tỷ biến mà không ai vẽ ra nổi.
Hình ảnh này cũng gợi ý ngay các rủi ro. Bước quá dài thì có thể nhảy qua thung lũng sang sườn bên kia. Bước quá ngắn thì đi mãi chưa tới. Và chỗ trũng đầu tiên bạn tìm thấy chưa chắc là chỗ thấp nhất của cả dãy núi. Ba rủi ro đó ứng với ba chủ đề chính của bài: tốc độ học, tốc độ hội tụ và cực tiểu địa phương.
Công thức một dòng và ý nghĩa từng thành phần
Với hàm một biến f(w), quy tắc cập nhật là: w mới = w cũ − η · f′(w cũ). Ký hiệu η (đọc là “eta”) là tốc độ học, một số dương nhỏ do người huấn luyện chọn. Đạo hàm f′(w) cho biết độ dốc: dương nghĩa là hàm đang đi lên khi w tăng, nên muốn đi xuống phải giảm w; âm nghĩa là hàm đang đi xuống khi w tăng, nên phải tăng w. Dấu trừ trong công thức lo đúng việc đổi hướng đó.
Độ lớn của bước đi bằng η nhân với độ lớn đạo hàm. Khi còn xa đáy, dốc lớn nên bước dài; khi gần đáy, dốc nhỏ dần nên bước tự ngắn lại. Đây là tính chất rất tiện: bạn không cần ra lệnh dừng chậm lại, chính đạo hàm làm việc đó. Tại đúng đáy, đạo hàm bằng 0 và tham số không đổi nữa.
Khi có nhiều biến, đạo hàm được thay bằng gradient, tức vector gom các đạo hàm riêng theo từng biến. Gradient chỉ hướng hàm tăng nhanh nhất, nên đi ngược gradient là hướng giảm nhanh nhất tại chỗ đó. Công thức giữ nguyên hình dạng, chỉ có w giờ là một vector và phép trừ là trừ từng thành phần.
Tính tay ba bước với một hàm đơn giản
Lấy hàm f(w) = (w − 3)², có giá trị nhỏ nhất bằng 0 tại w = 3. Đạo hàm là f′(w) = 2(w − 3). Bắt đầu tại w = 0, chọn tốc độ học η = 0,1. Mỗi bước tính đạo hàm rồi cập nhật:
| Bước | w hiện tại | Đạo hàm 2(w − 3) | w mới = w − 0,1 · đạo hàm | f(w mới) |
|---|---|---|---|---|
| 1 | 0 | −6 | 0 + 0,6 = 0,6 | 5,76 |
| 2 | 0,6 | −4,8 | 0,6 + 0,48 = 1,08 | 3,6864 |
| 3 | 1,08 | −3,84 | 1,08 + 0,384 = 1,464 | 2,359296 |
| 4 | 1,464 | −3,072 | 1,464 + 0,3072 = 1,7712 | 1,50994944 |
Để ý khoảng cách từ w tới đích 3 lần lượt là 3; 2,4; 1,92; 1,536; 1,2288. Mỗi bước khoảng cách bị nhân với 0,8, vì w mới − 3 = (w − 3) − 0,2(w − 3) = 0,8(w − 3). Nghĩa là thuật toán tiến về đích theo cấp số nhân: nhanh lúc đầu, chậm dần về sau, không bao giờ chạm đúng 3 trong hữu hạn bước nhưng tới gần tuỳ ý.
Bạn có thể tự kiểm bảng này bằng máy tính bỏ túi trong vài phút. Đây cũng là bài tập tốt để đưa cho chatbot rồi so kết quả: nếu nó báo sai số ở bước 3 không phải 2,359296, bạn biết phải dè chừng các phép tính khác của nó.
Tốc độ học quá nhỏ, vừa, quá lớn: cùng hàm, kết cục khác hẳn
Với hàm trên, công thức tổng quát là w mới − 3 = (1 − 2η)(w − 3). Hệ số (1 − 2η) quyết định mọi thứ. Bảng dưới so sánh năm giá trị η, cùng xuất phát tại w = 0:
| Tốc độ học η | Hệ số 1 − 2η | Điều xảy ra | Năm giá trị w đầu |
|---|---|---|---|
| 0,01 | 0,98 | Hội tụ rất chậm | 0; 0,06; 0,1188; 0,1764; 0,2329 |
| 0,1 | 0,8 | Hội tụ đều | 0; 0,6; 1,08; 1,464; 1,7712 |
| 0,5 | 0 | Tới đích ngay một bước | 0; 3; 3; 3; 3 |
| 1 | −1 | Dao động mãi giữa hai điểm | 0; 6; 0; 6; 0 |
| 1,1 | −1,2 | Phân kỳ, càng lúc càng xa | 0; 6,6; −1,32; 8,184; −3,2208 |
Trường hợp η = 0,5 tới đích trong một bước chỉ là may mắn của hàm bậc hai đơn giản; với hàm thật không có tốc độ học “hoàn hảo” như vậy. Bài học chung là: tốc độ học quá lớn khiến sai số tăng vọt hoặc nhảy lung tung, quá nhỏ khiến huấn luyện tốn thời gian vô ích. Khi thấy đường biểu diễn sai số theo vòng lặp đi lên hoặc răng cưa dữ dội, việc đầu tiên nên thử là giảm tốc độ học.
Từ một biến lên hàng triệu tham số: các biến thể thực tế
Hạ dốc theo toàn bộ dữ liệu. Phiên bản gốc tính gradient của hàm mất mát trên toàn bộ tập huấn luyện rồi mới bước một bước. Chính xác nhưng chậm khi dữ liệu có hàng triệu mẫu, vì mỗi bước phải đọc hết dữ liệu.
Hạ dốc ngẫu nhiên theo lô nhỏ. Thay vì toàn bộ dữ liệu, mỗi bước chỉ lấy một lô vài chục đến vài trăm mẫu ngẫu nhiên, tính gradient trên lô đó rồi cập nhật. Gradient bị nhiễu, nhưng mỗi bước rẻ hơn rất nhiều, và trên thực tế nhiễu đó còn giúp thoát khỏi một số vùng xấu. Tên tiếng Anh là stochastic gradient descent, viết tắt SGD; ngày nay khi nói SGD người ta thường ngầm hiểu là theo lô nhỏ.
Thêm quán tính. Phương pháp momentum cộng dồn một phần các bước trước vào bước hiện tại, giống hòn bi lăn xuống dốc có đà. Nó giúp đi nhanh qua các thung lũng dài, hẹp, nơi gradient thuần tuý cứ dội qua dội lại giữa hai vách.
Tốc độ học riêng cho từng tham số. Các phương pháp như RMSProp và Adam điều chỉnh bước đi của từng tham số dựa trên lịch sử gradient của chính nó: tham số có gradient lớn thất thường thì bước ngắn lại, tham số ít thay đổi thì bước dài hơn. Adam được giới thiệu năm 2014 bởi Kingma và Ba, và hiện là lựa chọn mặc định trong rất nhiều dự án.
Lịch giảm tốc độ học. Thay vì giữ η cố định, người ta thường bắt đầu với η lớn rồi giảm dần theo số vòng lặp, đôi khi kèm một giai đoạn khởi động tăng dần ở đầu. Lý do khớp với bảng ở trên: lúc xa đích cần bước dài, lúc gần đích cần bước ngắn để không dao động.
Những chỗ thuật toán có thể kẹt lại
Cực tiểu địa phương. Một chỗ trũng nhỏ trên sườn núi, thấp hơn xung quanh nhưng chưa phải thấp nhất. Với hàm một hai biến đây là rủi ro thật. Với mạng nơ-ron rất nhiều tham số, kinh nghiệm thực tế và nhiều phân tích cho thấy các cực tiểu tìm được thường có giá trị gần nhau, nên đây ít khi là vấn đề lớn nhất.
Điểm yên ngựa. Điểm mà theo hướng này là đáy nhưng theo hướng kia là đỉnh, giống mặt yên ngựa. Gradient tại đó bằng 0 nên thuật toán thuần tuý có thể đứng lại. Trong không gian nhiều chiều, điểm yên ngựa phổ biến hơn cực tiểu địa phương nhiều; nhiễu của SGD và quán tính giúp trượt qua chúng.
Vùng bằng phẳng. Vùng mà gradient rất nhỏ trên một khoảng rộng. Bước đi gần như bằng 0, sai số đứng yên nhiều vòng lặp liền. Người mới hay tưởng mô hình đã học xong, trong khi thật ra nó đang bò rất chậm qua một cao nguyên.
Gradient quá nhỏ hoặc quá lớn ở mạng sâu. Khi đạo hàm phải nhân qua hàng chục lớp, tích có thể teo về gần 0 hoặc phình rất to. Các kỹ thuật như khởi tạo trọng số cẩn thận, chuẩn hoá lớp, kết nối tắt và cắt ngưỡng gradient ra đời chủ yếu để xử lý hiện tượng này.
Dữ liệu chưa chuẩn hoá. Nếu một đặc trưng có giá trị hàng triệu còn đặc trưng khác chỉ từ 0 đến 1, mặt hàm mất mát bị kéo dài như một rãnh hẹp, và một tốc độ học chung không hợp cho cả hai hướng. Đưa các đặc trưng về cùng thang đo trước khi huấn luyện là bước rẻ mà hiệu quả.
Tự luyện với AI và một đoạn mã nhỏ
Cách học gradient descent nhanh nhất là tự cài đặt nó cho một hàm hai biến rồi quan sát. Vài gợi ý để làm cùng chatbot mà vẫn tự hiểu:
- Nhờ chatbot ra một hàm hai biến dạng f(x, y) = x² + 10y², yêu cầu chỉ đưa gradient, phần lặp bạn tự viết.
- Chạy với nhiều tốc độ học, ghi lại số bước cần để sai số dưới một ngưỡng nhỏ; sẽ thấy hàm kéo dài theo một chiều khó hơn hàm tròn đều.
- Thêm quán tính vào mã của bạn rồi so số bước với bản gốc.
- Hỏi chatbot: “Vì sao hàm x² + 10y² làm thuật toán hạ dốc đi zíc zắc? Giải thích bằng hình học, không dùng công thức phức tạp.” Đối chiếu với điều bạn quan sát được.
- Tự tính tay hai bước đầu, so với kết quả mã in ra trước khi tin mã của mình đúng.
Khi tự làm, đừng dán mã nguồn hay dữ liệu thật của công ty vào chatbot công cộng; với bài tập học tập kiểu trên thì không có gì cần che.
Câu hỏi thường gặp
Gradient descent có luôn tìm được giá trị nhỏ nhất không?
Với hàm lồi, ví dụ hàm bậc hai một biến hay hàm mất mát của hồi quy tuyến tính, và tốc độ học đủ nhỏ, thuật toán hội tụ về cực tiểu toàn cục. Với hàm không lồi như mạng nơ-ron, không có bảo đảm đó; thuật toán chỉ tìm được một điểm tốt đủ dùng, và kết quả có thể khác nhau giữa các lần chạy.
Chọn tốc độ học thế nào cho đúng?
Không có công thức chung. Cách thường làm là thử vài giá trị cách nhau mười lần, ví dụ 0,1; 0,01; 0,001, xem giá trị nào làm sai số giảm đều mà không dao động, rồi tinh chỉnh quanh đó. Nhiều thư viện có công cụ quét tốc độ học tự động, nhưng vẫn cần bạn đọc kết quả.
Gradient descent và phương pháp Newton khác nhau ra sao?
Gradient descent chỉ dùng đạo hàm bậc một, tức độ dốc. Phương pháp Newton dùng thêm đạo hàm bậc hai, tức độ cong, để chọn bước đi thông minh hơn và thường hội tụ trong ít bước hơn. Nhưng với hàng triệu tham số, ma trận đạo hàm bậc hai quá lớn để tính và lưu, nên học sâu gần như chỉ dùng các phương pháp bậc một.
Vì sao gọi là ngẫu nhiên trong stochastic gradient descent?
Vì mỗi bước chỉ dùng một lô mẫu được chọn ngẫu nhiên từ dữ liệu, nên gradient tính được là một ước lượng có nhiễu của gradient thật trên toàn bộ dữ liệu. Trung bình qua nhiều bước, các ước lượng này đi đúng hướng, trong khi chi phí mỗi bước thấp hơn rất nhiều.
Một epoch là gì?
Một epoch là một lượt mô hình đi qua toàn bộ tập huấn luyện. Nếu có mười nghìn mẫu và mỗi lô gồm một trăm mẫu, một epoch gồm một trăm bước cập nhật. Huấn luyện thường kéo dài nhiều epoch, và người ta theo dõi sai số trên tập kiểm định sau mỗi epoch để biết khi nào nên dừng.
Có cần tự tính gradient bằng tay khi dùng thư viện không?
Không, các thư viện học sâu tự tính đạo hàm bằng kỹ thuật vi phân tự động. Tuy vậy, tự tính tay vài ví dụ nhỏ giúp bạn hiểu thư viện đang làm gì, và giúp phát hiện lỗi khi bạn tự viết một hàm mất mát hay một lớp mới mà kết quả huấn luyện bất thường.