🏷️sec_gd
Trong phần này chúng tôi sẽ giới thiệu các khái niệm cơ bản trong thuật toán hạ gradient.
Nội dung cần thiết sẽ được trình bày ngắn gọn.
Độc giả có thể tham khảo :cite:Boyd.Vandenberghe.2004
để có góc nhìn sâu về bài toán tối ưu lồi.
Mặc dù tối ưu lồi hiếm khi được áp dụng trực tiếp trong học sâu, kiến thức về thuật toán hạ gradient là chìa khóa để hiểu rõ hơn về thuật toán hạ gradient ngẫu nhiên.
Ví dụ, bài toán tối ưu có thể phân kỳ do tốc độ học quá lớn.
Hiện tượng này có thể quan sát được trong thuật toán hạ gradient.
Tương tự, tiền điều kiện (preconditioning) là một kỹ thuật phổ biến trong thuật toán hạ gradient và nó cũng được áp dụng trong các thuật toán tân tiến hơn.
Hãy bắt đầu với một trường hợp đặc biệt và đơn giản.
Hạ gradient trong một chiều là ví dụ tuyệt vời để giải thích tại sao thuật toán hạ gradient có thể giảm giá trị hàm mục tiêu.
Hãy xem xét một hàm số thực khả vi liên tục sec_single_variable_calculus
), ta có
gd-taylor
Trong đó xấp xỉ bậc nhất
Nếu đạo hàm
Hơn nữa, chúng ta luôn có thể chọn
Điều này có nghĩa là, nếu chúng ta áp dụng
để cập nhật
Để đơn giản hóa vấn đề, chúng ta chọn hàm mục tiêu
%matplotlib inline
from d2l import mxnet as d2l
from mxnet import np, npx
npx.set_np()
#@tab all
f = lambda x: x**2 # Objective function
gradf = lambda x: 2 * x # Its derivative
Tiếp theo, chúng ta sử dụng
#@tab all
def gd(eta):
x = 10.0
results = [x]
for i in range(10):
x -= eta * gradf(x)
results.append(float(x))
print('epoch 10, x:', x)
return results
res = gd(0.2)
Đồ thị quá trình tối ưu hóa theo
#@tab all
def show_trace(res):
n = max(abs(min(res)), abs(max(res)))
f_line = d2l.arange(-n, n, 0.01)
d2l.set_figsize()
d2l.plot([f_line, res], [[f(x) for x in f_line], [f(x) for x in res]],
'x', 'f(x)', fmts=['-', '-o'])
show_trace(res)
🏷️section_gd-learningrate
Tốc độ học
#@tab all
show_trace(gd(0.05))
Ngược lại, nếu ta sử dụng tốc độ học quá cao, giá trị gd-taylor
sẽ có thể có giá trị lớn.
Trong trường hợp này, ta không thể đảm bảo rằng việc cập nhật
#@tab all
show_trace(gd(1.1))
Để minh họa quá trình học các hàm không lồi, ta xem xét trường hợp
#@tab all
c = d2l.tensor(0.15 * np.pi)
f = lambda x: x * d2l.cos(c * x)
gradf = lambda x: d2l.cos(c * x) - c * x * d2l.sin(c * x)
show_trace(gd(2))
Bây giờ chúng ta đã có trực quan tốt hơn về trường hợp đơn biến, ta hãy xem xét trường hợp trong đó
Mỗi đạo hàm riêng
gd-multi-taylor
Nói cách khác, chiều giảm mạnh nhất được cho bởi gradient âm
Để xem thuật toán hoạt động như thế nào trong thực tế, ta hãy xây dựng một hàm mục tiêu
#@tab all
def train_2d(trainer, steps=20): #@save
"""Optimize a 2-dim objective function with a customized trainer."""
# s1 and s2 are internal state variables and will
# be used later in the chapter
x1, x2, s1, s2 = -5, -2, 0, 0
results = [(x1, x2)]
for i in range(steps):
x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
results.append((x1, x2))
return results
def show_trace_2d(f, results): #@save
"""Show the trace of 2D variables during optimization."""
d2l.set_figsize()
d2l.plt.plot(*zip(*results), '-o', color='#ff7f0e')
x1, x2 = d2l.meshgrid(d2l.arange(-5.5, 1.0, 0.1),
d2l.arange(-3.0, 1.0, 0.1))
d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
d2l.plt.xlabel('x1')
d2l.plt.ylabel('x2')
Tiếp theo, chúng ta sẽ quan sát quỹ đạo của biến tối ưu hóa
#@tab all
f = lambda x1, x2: x1 ** 2 + 2 * x2 ** 2 # Objective
gradf = lambda x1, x2: (2 * x1, 4 * x2) # Gradient
def gd(x1, x2, s1, s2):
(g1, g2) = gradf(x1, x2) # Compute gradient
return (x1 - eta * g1, x2 - eta * g2, 0, 0) # Update variables
eta = 0.1
show_trace_2d(f, train_2d(gd))
Như chúng ta có thể thấy ở :numref:section_gd-learningrate
, chọn tốc độ học
Trong khai triển Taylor của
gd-hot-taylor
Để tránh việc kí hiệu quá nhiều, ta định nghĩa
Suy cho cùng, cực tiểu của gd-hot-taylor
theo
Nghĩa là, ta cần phải nghịch đảo ma trận Hessian
Với
#@tab all
c = d2l.tensor(0.5)
f = lambda x: d2l.cosh(c * x) # Objective
gradf = lambda x: c * d2l.sinh(c * x) # Derivative
hessf = lambda x: c**2 * d2l.cosh(c * x) # Hessian
def newton(eta=1):
x = 10.0
results = [x]
for i in range(10):
x -= eta * gradf(x) / hessf(x)
results.append(float(x))
print('epoch 10, x:', x)
return results
show_trace(newton())
Giờ hãy xem điều gì xảy ra với một hàm không lồi, ví dụ như
#@tab all
c = d2l.tensor(0.15 * np.pi)
f = lambda x: x * d2l.cos(c * x)
gradf = lambda x: d2l.cos(c * x) - c * x * d2l.sin(c * x)
hessf = lambda x: - 2 * c * d2l.sin(c * x) - x * c**2 * d2l.cos(c * x)
show_trace(newton())
Kết quả trả về là cực kỳ sai.
Có một cách khắc phục là "sửa" ma trận Hessian bằng cách lấy giá trị tuyệt đối của nó.
Một chiến lược khác là đưa tốc độ học trở lại.
Điều này có vẻ sẽ phá hỏng mục tiêu ban đầu nhưng không hẳn.
Có được thông tin bậc hai sẽ cho phép chúng ta thận trọng bất cứ khi nào độ cong trở nên lớn và cho phép thực hiện các bước dài hơn mỗi khi hàm mục tiêu phẳng.
Hãy xem nó hoạt động như thế nào với một tốc độ học khá nhỏ,
#@tab all
show_trace(newton(0.5))
Chúng ta sẽ chỉ phân tích tốc độ hội tụ đối với hàm
Đặt
Điều này đúng với một vài
Thay vào phương trình cập nhật sẽ dẫn đến ràng buộc
Bên cạnh đó, các nhà nghiên cứu tối ưu hóa gọi đây là hội tụ tuyến tính, còn điều kiện
Không có gì ngạc nhiên khi việc tính toán và lưu trữ toàn bộ ma trận Hessian là rất tốn kém. Do đó ta cần tìm kiếm một phương pháp thay thế. Một cách để cải thiện vấn đề này là tránh tính toán toàn bộ ma trận Hessian, chỉ tính toán các giá trị thuộc đường chéo. Mặc dù cách trên không tốt bằng phương pháp Newton hoàn chỉnh nhưng vẫn tốt hơn nhiều so với không sử dụng nó. Hơn nữa, ước lượng các giá trị đường chéo chính là thứ thúc đẩy sự đổi mới trong các thuật toán tối ưu hóa hạ gradient ngẫu nhiên. Thuật toán cập nhật sẽ có dạng
Để thấy tại sao điều này có thể là một ý tưởng tốt, ta ví dụ có hai biến số biểu thị chiều cao, một biến với đơn vị mm, biến còn lại với đơn vị km. Với cả hai đơn vị đo, khi quy đổi ra mét, chúng ta đều có sự sai lệch lớn trong việc tham số hóa. Sử dụng tiền điều kiện sẽ loại bỏ vấn đề này. Tiền điều kiện một cách hiệu quả cùng hạ gradient giúp chọn ra các tốc độ học khác nhau cho từng trục tọa độ.
Một trong những vấn đề chính của hạ gradient là chúng ta có thể vượt quá khỏi mục tiêu hoặc không đạt đủ sự tiến bộ.
Có một cách khắc phục đơn giản cho vấn đề này là sử dụng tìm kiếm đường thẳng (line search) kết hợp với hạ gradient.
Chúng ta sử dụng hướng được cho bởi
Thuật toán này sẽ hội tụ nhanh chóng (xem phân tích và chứng minh ở :cite:Boyd.Vandenberghe.2004
).
Tuy nhiên, đối với mục đích của học sâu thì nó không thực sự khả thi, lý do là mỗi bước của tìm kiếm đường thẳng sẽ yêu cầu chúng ta ước lượng hàm mục tiêu trên toàn bộ tập dữ liệu.
Điều này quá tốn kém để có thể thực hiện.
- Tốc độ học rất quan trọng. Quá lớn sẽ khiến việc tối ưu hóa phân kỳ, quá nhỏ sẽ không thu được sự tiến bộ nào.
- Hạ gradient có thể bị kẹt tại cực tiểu cục bộ.
- Trong bài toán nhiều chiều, tinh chỉnh việc học tốc độ học sẽ phức tạp.
- Tiền điều kiện có thể giúp trong việc tinh chỉnh thang đo.
- Phương pháp Newton nhanh hơn rất nhiều một khi hoạt động trên bài toán lồi phù hợp.
- Hãy cẩn trọng trong việc dùng phương pháp Newton cho các bài toán không lồi mà không tinh chỉnh.
- Hãy thử các tốc độ học, hàm mục tiêu khác nhau cho hạ gradient.
- Khởi tạo tìm kiếm đường thẳng để cực tiểu hóa hàm lồi trong khoảng
$[a, b]$ .- Bạn có cần đạo hàm để tìm kiếm nhị phân không, ví dụ, để quyết định xem sẽ chọn
$[a, (a+b)/2]$ hay$[(a+b)/2, b]$ ? - Tốc độ hội tụ của thuật toán nhanh chậm thế nào?
- Hãy khởi tạo thuật toán và áp dụng nó để cực tiểu hóa
$\log (\exp(x) + \exp(-2*x -3))$ .
- Bạn có cần đạo hàm để tìm kiếm nhị phân không, ví dụ, để quyết định xem sẽ chọn
- Thiết kế một hàm mục tiêu thuộc
$\mathbb{R}^2$ mà việc hạ gradient rất chậm. Gợi ý: sử dụng trục tọa độ có thang đo khác nhau. - Khởi tạo một phiên bản nhỏ gọn của phương pháp Newton sử dụng tiền điều kiện:
- Dùng ma trận đường chéo Hessian làm tiền điều kiện.
- Sử dụng các giá trị tuyệt đối của nó thay vì các giá trị có dấu.
- Áp dụng điều này cho bài toán phía trên.
- Áp dụng thuật toán phía trên cho các hàm mục tiêu (lồi lẫn không lồi). Điều gì sẽ xảy ra nếu xoay các trục tọa độ một góc
$45$ độ?
Bản dịch trong trang này được thực hiện bởi:
- Đoàn Võ Duy Thanh
- Nguyễn Văn Quang
- Nguyễn Lê Quang Nhật
- Nguyễn Văn Quang
- Nguyễn Văn Cường
- Phạm Hồng Vinh
- Phạm Minh Đức
- Nguyễn Thanh Hòa
- Võ Tấn Phát