.. _sec_gd:
Gradient Descent
================
Trong phần này, chúng ta sẽ giới thiệu các khái niệm cơ bản bên dưới \*
gradient descent\*. Mặc dù nó hiếm khi được sử dụng trực tiếp trong học
sâu, một sự hiểu biết về gradient descent là chìa khóa để hiểu các thuật
toán gốc gradient ngẫu nhiên. Ví dụ, vấn đề tối ưu hóa có thể phân kỳ do
tốc độ học tập quá lớn. Hiện tượng này đã có thể được nhìn thấy trong
gradient gốc. Tương tự như vậy, preconditioning là một kỹ thuật phổ biến
trong chuyển đổi gradient và mang đến các thuật toán tiên tiến hơn. Hãy
để chúng tôi bắt đầu với một trường hợp đặc biệt đơn giản.
Một chiều Gradient Descent
--------------------------
Gradient gốc trong một chiều là một ví dụ tuyệt vời để giải thích lý do
tại sao thuật toán gốc gradient có thể làm giảm giá trị của hàm khách
quan. Xem xét một số chức năng có giá trị thực liên tục khác biệt
:math:`f: \mathbb{R} \rightarrow \mathbb{R}`. Sử dụng bản mở rộng
Taylor, chúng tôi có được
.. math:: f(x + \epsilon) = f(x) + \epsilon f'(x) + \mathcal{O}(\epsilon^2).
:label: gd-taylor
Đó là, trong xấp xỉ thứ tự thứ nhất :math:`f(x+\epsilon)` được đưa ra
bởi giá trị hàm :math:`f(x)` và đạo hàm đầu tiên :math:`f'(x)` tại
:math:`x`. Nó không phải là không hợp lý để giả định rằng đối với nhỏ
:math:`\epsilon` di chuyển theo hướng của gradient âm sẽ giảm :math:`f`.
Để giữ cho mọi thứ đơn giản, chúng tôi chọn một kích thước bước cố định
:math:`\eta > 0` và chọn :math:`\epsilon = -\eta f'(x)`. Cắm điều này
vào bản mở rộng Taylor ở trên chúng tôi nhận được
.. math:: f(x - \eta f'(x)) = f(x) - \eta f'^2(x) + \mathcal{O}(\eta^2 f'^2(x)).
:label: gd-taylor-2
Nếu đạo hàm :math:`f'(x) \neq 0` không biến mất chúng ta tiến bộ kể từ
:math:`\eta f'^2(x)>0`. Hơn nữa, chúng tôi luôn có thể chọn :math:`\eta`
đủ nhỏ để các điều khoản bậc cao hơn trở nên không liên quan. Do đó
chúng tôi đến
.. math:: f(x - \eta f'(x)) \lessapprox f(x).
Điều này có nghĩa rằng, nếu chúng ta sử dụng
.. math:: x \leftarrow x - \eta f'(x)
để lặp lại :math:`x`, giá trị của hàm :math:`f(x)` có thể giảm. Do đó,
trong gradient descent đầu tiên chúng ta chọn một giá trị ban đầu
:math:`x` và một hằng số :math:`\eta > 0` và sau đó sử dụng chúng để
liên tục lặp lại :math:`x` cho đến khi đạt đến điều kiện dừng, ví dụ,
khi độ lớn của gradient :math:`|f'(x)|` là đủ nhỏ hoặc số lần lặp đã đạt
đến một số nhất định giá trị.
Để đơn giản, chúng tôi chọn hàm mục tiêu :math:`f(x)=x^2` để minh họa
cách thực hiện gradient descent. Mặc dù chúng ta biết rằng :math:`x=0`
là giải pháp để giảm thiểu :math:`f(x)`, chúng tôi vẫn sử dụng chức năng
đơn giản này để quan sát cách :math:`x` thay đổi.
.. raw:: html
.. raw:: html
.. code:: python
%matplotlib inline
from mxnet import np, npx
from d2l import mxnet as d2l
npx.set_np()
def f(x): # Objective function
return x ** 2
def f_grad(x): # Gradient (derivative) of the objective function
return 2 * x
.. raw:: html
.. raw:: html
.. code:: python
%matplotlib inline
import numpy as np
import torch
from d2l import torch as d2l
def f(x): # Objective function
return x ** 2
def f_grad(x): # Gradient (derivative) of the objective function
return 2 * x
.. raw:: html
.. raw:: html
.. code:: python
%matplotlib inline
import numpy as np
import tensorflow as tf
from d2l import tensorflow as d2l
def f(x): # Objective function
return x ** 2
def f_grad(x): # Gradient (derivative) of the objective function
return 2 * x
.. raw:: html
.. raw:: html
Tiếp theo, chúng tôi sử dụng :math:`x=10` làm giá trị ban đầu và giả sử
:math:`\eta=0.2`. Sử dụng gradient descent để lặp lại :math:`x` trong 10
lần chúng ta có thể thấy rằng, cuối cùng, giá trị của :math:`x` tiếp cận
giải pháp tối ưu.
.. raw:: html
.. raw:: html
.. code:: python
def gd(eta, f_grad):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x)
results.append(float(x))
print(f'epoch 10, x: {x:f}')
return results
results = gd(0.2, f_grad)
.. parsed-literal::
:class: output
epoch 10, x: 0.060466
.. raw:: html
.. raw:: html
.. code:: python
def gd(eta, f_grad):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x)
results.append(float(x))
print(f'epoch 10, x: {x:f}')
return results
results = gd(0.2, f_grad)
.. parsed-literal::
:class: output
epoch 10, x: 0.060466
.. raw:: html
.. raw:: html
.. code:: python
def gd(eta, f_grad):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x)
results.append(float(x))
print(f'epoch 10, x: {x:f}')
return results
results = gd(0.2, f_grad)
.. parsed-literal::
:class: output
epoch 10, x: 0.060466
.. raw:: html
.. raw:: html
Tiến trình tối ưu hóa hơn :math:`x` có thể được vẽ như sau.
.. raw:: html
.. raw:: html
.. code:: python
def show_trace(results, f):
n = max(abs(min(results)), abs(max(results)))
f_line = np.arange(-n, n, 0.01)
d2l.set_figsize()
d2l.plot([f_line, results], [[f(x) for x in f_line], [
f(x) for x in results]], 'x', 'f(x)', fmts=['-', '-o'])
show_trace(results, f)
.. figure:: output_gd_79c039_27_0.svg
.. raw:: html
.. raw:: html
.. code:: python
def show_trace(results, f):
n = max(abs(min(results)), abs(max(results)))
f_line = torch.arange(-n, n, 0.01)
d2l.set_figsize()
d2l.plot([f_line, results], [[f(x) for x in f_line], [
f(x) for x in results]], 'x', 'f(x)', fmts=['-', '-o'])
show_trace(results, f)
.. figure:: output_gd_79c039_30_0.svg
.. raw:: html
.. raw:: html
.. code:: python
def show_trace(results, f):
n = max(abs(min(results)), abs(max(results)))
f_line = tf.range(-n, n, 0.01)
d2l.set_figsize()
d2l.plot([f_line, results], [[f(x) for x in f_line], [
f(x) for x in results]], 'x', 'f(x)', fmts=['-', '-o'])
show_trace(results, f)
.. figure:: output_gd_79c039_33_0.svg
.. raw:: html
.. raw:: html
.. _subsec_gd-learningrate:
Tỷ lệ học tập
~~~~~~~~~~~~~
Tỷ lệ học tập :math:`\eta` có thể được thiết lập bởi nhà thiết kế thuật
toán. Nếu chúng ta sử dụng tốc độ học tập quá nhỏ, nó sẽ khiến :math:`x`
cập nhật rất chậm, đòi hỏi nhiều lần lặp lại hơn để có được giải pháp
tốt hơn. Để hiển thị những gì xảy ra trong trường hợp như vậy, hãy xem
xét tiến trình trong cùng một vấn đề tối ưu hóa cho :math:`\eta = 0.05`.
Như chúng ta có thể thấy, ngay cả sau 10 bước, chúng ta vẫn còn rất xa
giải pháp tối ưu.
.. raw:: html
.. raw:: html
.. code:: python
show_trace(gd(0.05, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: 3.486784
.. figure:: output_gd_79c039_39_1.svg
.. raw:: html
.. raw:: html
.. code:: python
show_trace(gd(0.05, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: 3.486784
.. figure:: output_gd_79c039_42_1.svg
.. raw:: html
.. raw:: html
.. code:: python
show_trace(gd(0.05, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: 3.486784
.. figure:: output_gd_79c039_45_1.svg
.. raw:: html
.. raw:: html
Ngược lại, nếu chúng ta sử dụng tỷ lệ học tập quá cao,
:math:`\left|\eta f'(x)\right|` có thể quá lớn đối với công thức mở rộng
Taylor bậc nhất. Đó là, thuật ngữ :math:`\mathcal{O}(\eta^2 f'^2(x))`
trong :eq:`gd-taylor-2` có thể trở nên quan trọng. Trong trường hợp
này, chúng tôi không thể đảm bảo rằng việc lặp lại :math:`x` sẽ có thể
hạ giá trị :math:`f(x)`. Ví dụ, khi chúng ta đặt tỷ lệ học tập thành
:math:`\eta=1.1`, :math:`x` vượt qua giải pháp tối ưu :math:`x=0` và dần
dần phân kỳ.
.. raw:: html
.. raw:: html
.. code:: python
show_trace(gd(1.1, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: 61.917364
.. figure:: output_gd_79c039_51_1.svg
.. raw:: html
.. raw:: html
.. code:: python
show_trace(gd(1.1, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: 61.917364
.. figure:: output_gd_79c039_54_1.svg
.. raw:: html
.. raw:: html
.. code:: python
show_trace(gd(1.1, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: 61.917364
.. figure:: output_gd_79c039_57_1.svg
.. raw:: html
.. raw:: html
Minima địa phương
~~~~~~~~~~~~~~~~~
Để minh họa những gì xảy ra cho các chức năng không lồi xem xét trường
hợp của :math:`f(x) = x \cdot \cos(cx)` cho một số hằng số :math:`c`.
Chức năng này có vô hạn nhiều minima cục bộ. Tùy thuộc vào sự lựa chọn
của chúng tôi về tỷ lệ học tập và tùy thuộc vào mức độ điều kiện của vấn
đề, chúng tôi có thể kết thúc với một trong nhiều giải pháp. Ví dụ dưới
đây minh họa mức độ học tập cao (không thực tế) sẽ dẫn đến mức tối thiểu
địa phương kém.
.. raw:: html
.. raw:: html
.. code:: python
c = np.array(0.15 * np.pi)
def f(x): # Objective function
return x * np.cos(c * x)
def f_grad(x): # Gradient of the objective function
return np.cos(c * x) - c * x * np.sin(c * x)
show_trace(gd(2, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: -1.528165
.. figure:: output_gd_79c039_63_1.svg
.. raw:: html
.. raw:: html
.. code:: python
c = torch.tensor(0.15 * np.pi)
def f(x): # Objective function
return x * torch.cos(c * x)
def f_grad(x): # Gradient of the objective function
return torch.cos(c * x) - c * x * torch.sin(c * x)
show_trace(gd(2, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: -1.528166
.. figure:: output_gd_79c039_66_1.svg
.. raw:: html
.. raw:: html
.. code:: python
c = tf.constant(0.15 * np.pi)
def f(x): # Objective function
return x * tf.cos(c * x)
def f_grad(x): # Gradient of the objective function
return tf.cos(c * x) - c * x * tf.sin(c * x)
show_trace(gd(2, f_grad), f)
.. parsed-literal::
:class: output
epoch 10, x: -1.528165
.. figure:: output_gd_79c039_69_1.svg
.. raw:: html
.. raw:: html
Đa biến Gradient Descent
------------------------
Bây giờ chúng ta có một trực giác tốt hơn về trường hợp thống nhất,
chúng ta hãy xem xét tình huống mà
:math:`\mathbf{x} = [x_1, x_2, \ldots, x_d]^\top`. Đó là, hàm khách quan
:math:`f: \mathbb{R}^d \to \mathbb{R}` ánh xạ vectơ thành vô hướng.
Tương ứng gradient của nó là đa biến, quá. Nó là một vectơ gồm :math:`d`
dẫn xuất từng phần:
.. math:: \nabla f(\mathbf{x}) = \bigg[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_d}\bigg]^\top.
Mỗi phần tử phái sinh một phần
:math:`\partial f(\mathbf{x})/\partial x_i` trong gradient cho biết tốc
độ thay đổi của :math:`f` tại :math:`\mathbf{x}` đối với đầu vào
:math:`x_i`. Như trước đây trong trường hợp thống nhất, chúng ta có thể
sử dụng xấp xỉ Taylor tương ứng cho các chức năng đa biến để có được một
số ý tưởng về những gì chúng ta nên làm. Đặc biệt, chúng tôi có điều đó
.. math:: f(\mathbf{x} + \boldsymbol{\epsilon}) = f(\mathbf{x}) + \mathbf{\boldsymbol{\epsilon}}^\top \nabla f(\mathbf{x}) + \mathcal{O}(\|\boldsymbol{\epsilon}\|^2).
:label: gd-multi-taylor
Nói cách khác, lên đến các thuật ngữ bậc hai trong
:math:`\boldsymbol{\epsilon}` hướng xuống dốc nhất được đưa ra bởi
gradient âm :math:`-\nabla f(\mathbf{x})`. Chọn một tỷ lệ học tập phù
hợp :math:`\eta > 0` mang lại thuật toán gốc gradient nguyên mẫu:
.. math:: \mathbf{x} \leftarrow \mathbf{x} - \eta \nabla f(\mathbf{x}).
Để xem cách thuật toán hoạt động trong thực tế chúng ta hãy xây dựng một
hàm khách quan :math:`f(\mathbf{x})=x_1^2+2x_2^2` với một vector hai
chiều :math:`\mathbf{x} = [x_1, x_2]^\top` như đầu vào và vô hướng như
đầu ra. Gradient được đưa ra bởi
:math:`\nabla f(\mathbf{x}) = [2x_1, 4x_2]^\top`. Chúng ta sẽ quan sát
quỹ đạo của :math:`\mathbf{x}` bằng cách giảm gradient từ vị trí ban đầu
:math:`[-5, -2]`.
Để bắt đầu, chúng ta cần thêm hai chức năng trợ giúp. Đầu tiên sử dụng
chức năng cập nhật và áp dụng nó 20 lần cho giá trị ban đầu. Người trợ
giúp thứ hai hình dung quỹ đạo của :math:`\mathbf{x}`.
.. raw:: html
.. raw:: html
.. code:: python
def train_2d(trainer, steps=20, f_grad=None): #@save
"""Optimize a 2D objective function with a customized trainer."""
# `s1` and `s2` are internal state variables that will be used later
x1, x2, s1, s2 = -5, -2, 0, 0
results = [(x1, x2)]
for i in range(steps):
if f_grad:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2, f_grad)
else:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
results.append((x1, x2))
print(f'epoch {i + 1}, x1: {float(x1):f}, x2: {float(x2):f}')
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 = np.meshgrid(np.arange(-5.5, 1.0, 0.1),
np.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')
.. raw:: html
.. raw:: html
.. code:: python
def train_2d(trainer, steps=20, f_grad=None): #@save
"""Optimize a 2D objective function with a customized trainer."""
# `s1` and `s2` are internal state variables that will be used later
x1, x2, s1, s2 = -5, -2, 0, 0
results = [(x1, x2)]
for i in range(steps):
if f_grad:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2, f_grad)
else:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
results.append((x1, x2))
print(f'epoch {i + 1}, x1: {float(x1):f}, x2: {float(x2):f}')
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 = torch.meshgrid(torch.arange(-5.5, 1.0, 0.1),
torch.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')
.. raw:: html
.. raw:: html
.. code:: python
def train_2d(trainer, steps=20, f_grad=None): #@save
"""Optimize a 2D objective function with a customized trainer."""
# `s1` and `s2` are internal state variables that will be used later
x1, x2, s1, s2 = -5, -2, 0, 0
results = [(x1, x2)]
for i in range(steps):
if f_grad:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2, f_grad)
else:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
results.append((x1, x2))
print(f'epoch {i + 1}, x1: {float(x1):f}, x2: {float(x2):f}')
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 = tf.meshgrid(tf.range(-5.5, 1.0, 0.1),
tf.range(-3.0, 1.0, 0.1))
d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
d2l.plt.xlabel('x1')
d2l.plt.ylabel('x2')
.. raw:: html
.. raw:: html
Tiếp theo, chúng ta quan sát quỹ đạo của biến tối ưu hóa
:math:`\mathbf{x}` cho tỷ lệ học tập :math:`\eta = 0.1`. Chúng ta có thể
thấy rằng sau 20 bước, giá trị của :math:`\mathbf{x}` đạt đến mức tối
thiểu của nó ở mức :math:`[0, 0]`. Tiến bộ khá cư xử tốt mặc dù khá
chậm.
.. raw:: html
.. raw:: html
.. code:: python
def f_2d(x1, x2): # Objective function
return x1 ** 2 + 2 * x2 ** 2
def f_2d_grad(x1, x2): # Gradient of the objective function
return (2 * x1, 4 * x2)
def gd_2d(x1, x2, s1, s2, f_grad):
g1, g2 = f_grad(x1, x2)
return (x1 - eta * g1, x2 - eta * g2, 0, 0)
eta = 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad=f_2d_grad))
.. parsed-literal::
:class: output
epoch 20, x1: -0.057646, x2: -0.000073
.. figure:: output_gd_79c039_87_1.svg
.. raw:: html
.. raw:: html
.. code:: python
def f_2d(x1, x2): # Objective function
return x1 ** 2 + 2 * x2 ** 2
def f_2d_grad(x1, x2): # Gradient of the objective function
return (2 * x1, 4 * x2)
def gd_2d(x1, x2, s1, s2, f_grad):
g1, g2 = f_grad(x1, x2)
return (x1 - eta * g1, x2 - eta * g2, 0, 0)
eta = 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad=f_2d_grad))
.. parsed-literal::
:class: output
epoch 20, x1: -0.057646, x2: -0.000073
/home/d2l-worker/miniconda3/envs/d2l-vi-release-1/lib/python3.8/site-packages/torch/functional.py:568: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at ../aten/src/ATen/native/TensorShape.cpp:2228.)
return _VF.meshgrid(tensors, **kwargs) # type: ignore[attr-defined]
.. figure:: output_gd_79c039_90_1.svg
.. raw:: html
.. raw:: html
.. code:: python
def f_2d(x1, x2): # Objective function
return x1 ** 2 + 2 * x2 ** 2
def f_2d_grad(x1, x2): # Gradient of the objective function
return (2 * x1, 4 * x2)
def gd_2d(x1, x2, s1, s2, f_grad):
g1, g2 = f_grad(x1, x2)
return (x1 - eta * g1, x2 - eta * g2, 0, 0)
eta = 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad=f_2d_grad))
.. parsed-literal::
:class: output
epoch 20, x1: -0.057646, x2: -0.000073
.. figure:: output_gd_79c039_93_1.svg
.. raw:: html
.. raw:: html
Phương pháp thích ứng
---------------------
Như chúng ta có thể thấy trong :numref:`subsec_gd-learningrate`, nhận
được tỷ lệ học tập :math:`\eta` “vừa phải” là khó khăn. Nếu chúng ta
chọn nó quá nhỏ, chúng ta sẽ tiến bộ rất ít. Nếu chúng ta chọn nó quá
lớn, giải pháp dao động và trong trường hợp xấu nhất nó thậm chí có thể
phân kỳ. Điều gì sẽ xảy ra nếu chúng ta có thể xác định :math:`\eta` tự
động hoặc thoát khỏi việc phải chọn một tỷ lệ học tập ở tất cả? Các
phương thức thứ hai không chỉ nhìn vào giá trị và gradient của hàm mục
tiêu mà còn ở độ cong \* của nócó thể giúp ích trong trường hợp này. Mặc
dù các phương pháp này không thể áp dụng trực tiếp vào deep learning do
chi phí tính toán, chúng cung cấp trực giác hữu ích về cách thiết kế các
thuật toán tối ưu hóa nâng cao bắt chước nhiều thuộc tính mong muốn của
các thuật toán được nêu dưới đây.
Phương pháp Newton
~~~~~~~~~~~~~~~~~~
Xem xét việc mở rộng Taylor của một số chức năng
:math:`f: \mathbb{R}^d \rightarrow \mathbb{R}` không cần phải dừng lại
sau nhiệm kỳ đầu tiên. Trong thực tế, chúng ta có thể viết nó như
.. math:: f(\mathbf{x} + \boldsymbol{\epsilon}) = f(\mathbf{x}) + \boldsymbol{\epsilon}^\top \nabla f(\mathbf{x}) + \frac{1}{2} \boldsymbol{\epsilon}^\top \nabla^2 f(\mathbf{x}) \boldsymbol{\epsilon} + \mathcal{O}(\|\boldsymbol{\epsilon}\|^3).
:label: gd-hot-taylor
Để tránh ký hiệu rườm rà, chúng tôi định nghĩa
:math:`\mathbf{H} \stackrel{\mathrm{def}}{=} \nabla^2 f(\mathbf{x})` là
Hessian của :math:`f`, là ma trận :math:`d \times d`. Đối với :math:`d`
nhỏ và các vấn đề đơn giản :math:`\mathbf{H}` rất dễ tính toán. Đối với
các mạng thần kinh sâu, mặt khác, :math:`\mathbf{H}` có thể rất lớn, do
chi phí lưu trữ :math:`\mathcal{O}(d^2)` mục. Hơn nữa nó có thể quá tốn
kém để tính toán thông qua backpropagation. Bây giờ chúng ta hãy bỏ qua
những cân nhắc như vậy và nhìn vào thuật toán nào chúng ta sẽ nhận được.
Rốt cuộc, tối thiểu :math:`f` thỏa mãn :math:`\nabla f = 0`. Tuân theo
các quy tắc giải tích trong :numref:`subsec_calculus-grad`, bằng cách
dùng các dẫn xuất của :eq:`gd-hot-taylor` liên quan đến
:math:`\boldsymbol{\epsilon}` và bỏ qua các điều khoản bậc cao hơn,
chúng tôi đến
.. math::
\nabla f(\mathbf{x}) + \mathbf{H} \boldsymbol{\epsilon} = 0 \text{ and hence }
\boldsymbol{\epsilon} = -\mathbf{H}^{-1} \nabla f(\mathbf{x}).
Đó là, chúng ta cần đảo ngược Hessian :math:`\mathbf{H}` như một phần
của vấn đề tối ưu hóa.
Như một ví dụ đơn giản, đối với :math:`f(x) = \frac{1}{2} x^2`, chúng
tôi có :math:`\nabla f(x) = x` và :math:`\mathbf{H} = 1`. Do đó đối với
bất kỳ :math:`x` chúng tôi có được :math:`\epsilon = -x`. Nói cách khác,
bước \* single\* là đủ để hội tụ hoàn hảo mà không cần bất kỳ điều chỉnh
nào! Than ôi, chúng tôi đã có một chút may mắn ở đây: bản mở rộng Taylor
là chính xác kể từ
:math:`f(x+\epsilon)= \frac{1}{2} x^2 + \epsilon x + \frac{1}{2} \epsilon^2`.
Hãy để chúng tôi xem những gì xảy ra trong các vấn đề khác. Với hàm
cosin hyperbol lồi :math:`f(x) = \cosh(cx)` cho một số hằng số
:math:`c`, chúng ta có thể thấy rằng mức tối thiểu toàn cầu tại
:math:`x=0` đạt được sau một vài lần lặp lại.
.. raw:: html
.. raw:: html
.. code:: python
c = np.array(0.5)
def f(x): # Objective function
return np.cosh(c * x)
def f_grad(x): # Gradient of the objective function
return c * np.sinh(c * x)
def f_hess(x): # Hessian of the objective function
return c**2 * np.cosh(c * x)
def newton(eta=1):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x) / f_hess(x)
results.append(float(x))
print('epoch 10, x:', x)
return results
show_trace(newton(), f)
.. parsed-literal::
:class: output
epoch 10, x: 0.0
.. figure:: output_gd_79c039_99_1.svg
.. raw:: html
.. raw:: html
.. code:: python
c = torch.tensor(0.5)
def f(x): # Objective function
return torch.cosh(c * x)
def f_grad(x): # Gradient of the objective function
return c * torch.sinh(c * x)
def f_hess(x): # Hessian of the objective function
return c**2 * torch.cosh(c * x)
def newton(eta=1):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x) / f_hess(x)
results.append(float(x))
print('epoch 10, x:', x)
return results
show_trace(newton(), f)
.. parsed-literal::
:class: output
epoch 10, x: tensor(0.)
.. figure:: output_gd_79c039_102_1.svg
.. raw:: html
.. raw:: html
.. code:: python
c = tf.constant(0.5)
def f(x): # Objective function
return tf.cosh(c * x)
def f_grad(x): # Gradient of the objective function
return c * tf.sinh(c * x)
def f_hess(x): # Hessian of the objective function
return c**2 * tf.cosh(c * x)
def newton(eta=1):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x) / f_hess(x)
results.append(float(x))
print('epoch 10, x:', x)
return results
show_trace(newton(), f)
.. parsed-literal::
:class: output
epoch 10, x: tf.Tensor(0.0, shape=(), dtype=float32)
.. figure:: output_gd_79c039_105_1.svg
.. raw:: html
.. raw:: html
Bây giờ chúng ta hãy xem xét một hàm *nonconvex*, chẳng hạn như
:math:`f(x) = x \cos(c x)` cho một số hằng số :math:`c`. Rốt cuộc, lưu ý
rằng trong phương pháp của Newton, chúng ta sẽ chia cho Hessian. Điều
này có nghĩa là nếu đạo hàm thứ hai là *tiêu cực* chúng ta có thể đi
theo hướng \* tăng\* giá trị của :math:`f`. Đó là một lỗ hổng gây tử
vong của thuật toán. Hãy để chúng tôi xem những gì xảy ra trong thực tế.
.. raw:: html
.. raw:: html
.. code:: python
c = np.array(0.15 * np.pi)
def f(x): # Objective function
return x * np.cos(c * x)
def f_grad(x): # Gradient of the objective function
return np.cos(c * x) - c * x * np.sin(c * x)
def f_hess(x): # Hessian of the objective function
return - 2 * c * np.sin(c * x) - x * c**2 * np.cos(c * x)
show_trace(newton(), f)
.. parsed-literal::
:class: output
epoch 10, x: 26.834133
.. figure:: output_gd_79c039_111_1.svg
.. raw:: html
.. raw:: html
.. code:: python
c = torch.tensor(0.15 * np.pi)
def f(x): # Objective function
return x * torch.cos(c * x)
def f_grad(x): # Gradient of the objective function
return torch.cos(c * x) - c * x * torch.sin(c * x)
def f_hess(x): # Hessian of the objective function
return - 2 * c * torch.sin(c * x) - x * c**2 * torch.cos(c * x)
show_trace(newton(), f)
.. parsed-literal::
:class: output
epoch 10, x: tensor(26.8341)
.. figure:: output_gd_79c039_114_1.svg
.. raw:: html
.. raw:: html
.. code:: python
c = tf.constant(0.15 * np.pi)
def f(x): # Objective function
return x * tf.cos(c * x)
def f_grad(x): # Gradient of the objective function
return tf.cos(c * x) - c * x * tf.sin(c * x)
def f_hess(x): # Hessian of the objective function
return - 2 * c * tf.sin(c * x) - x * c**2 * tf.cos(c * x)
show_trace(newton(), f)
.. parsed-literal::
:class: output
epoch 10, x: tf.Tensor(26.834133, shape=(), dtype=float32)
.. figure:: output_gd_79c039_117_1.svg
.. raw:: html
.. raw:: html
Điều này đã đi sai một cách ngoạn mục. Làm thế nào chúng ta có thể sửa
nó? Một cách sẽ là “sửa chữa” Hessian bằng cách lấy giá trị tuyệt đối
của nó thay thế. Một chiến lược khác là mang lại tốc độ học tập. Điều
này dường như đánh bại mục đích, nhưng không hoàn toàn. Có thông tin thứ
hai cho phép chúng ta thận trọng bất cứ khi nào độ cong lớn và thực hiện
các bước lâu hơn bất cứ khi nào chức năng khách quan phẳng hơn. Hãy để
chúng tôi xem cách điều này hoạt động với tốc độ học tập nhỏ hơn một
chút, nói :math:`\eta = 0.5`. Như chúng ta có thể thấy, chúng ta có một
thuật toán khá hiệu quả.
.. raw:: html
.. raw:: html
.. code:: python
show_trace(newton(0.5), f)
.. parsed-literal::
:class: output
epoch 10, x: 7.26986
.. figure:: output_gd_79c039_123_1.svg
.. raw:: html
.. raw:: html
.. code:: python
show_trace(newton(0.5), f)
.. parsed-literal::
:class: output
epoch 10, x: tensor(7.2699)
.. figure:: output_gd_79c039_126_1.svg
.. raw:: html
.. raw:: html
.. code:: python
show_trace(newton(0.5), f)
.. parsed-literal::
:class: output
epoch 10, x: tf.Tensor(7.26986, shape=(), dtype=float32)
.. figure:: output_gd_79c039_129_1.svg
.. raw:: html
.. raw:: html
Phân tích hội tụ
~~~~~~~~~~~~~~~~
Chúng tôi chỉ phân tích tốc độ hội tụ của phương pháp Newton đối với một
số hàm khách quan lồi và ba lần khác biệt :math:`f`, trong đó đạo hàm
thứ hai là nonzero, tức là :math:`f'' > 0`. Bằng chứng đa biến là một
phần mở rộng đơn giản của đối số một chiều bên dưới và bỏ qua vì nó
không giúp chúng ta nhiều về trực giác.
Biểu thị bằng :math:`x^{(k)}` giá trị của :math:`x` tại lần lặp
:math:`k^\mathrm{th}` và để
:math:`e^{(k)} \stackrel{\mathrm{def}}{=} x^{(k)} - x^*` là khoảng cách
từ tối ưu tại lần lặp :math:`k^\mathrm{th}`. Bằng cách mở rộng Taylor,
chúng tôi có rằng điều kiện :math:`f'(x^*) = 0` có thể được viết là
.. math:: 0 = f'(x^{(k)} - e^{(k)}) = f'(x^{(k)}) - e^{(k)} f''(x^{(k)}) + \frac{1}{2} (e^{(k)})^2 f'''(\xi^{(k)}),
which mà holds giữ for some
:math:`\xi^{(k)} \in [x^{(k)} - e^{(k)}, x^{(k)}]`. Chia việc mở rộng
trên cho sản lượng :math:`f''(x^{(k)})`
.. math:: e^{(k)} - \frac{f'(x^{(k)})}{f''(x^{(k)})} = \frac{1}{2} (e^{(k)})^2 \frac{f'''(\xi^{(k)})}{f''(x^{(k)})}.
Nhớ lại rằng chúng tôi có bản cập nhật
:math:`x^{(k+1)} = x^{(k)} - f'(x^{(k)}) / f''(x^{(k)})`. Cắm vào phương
trình cập nhật này và lấy giá trị tuyệt đối của cả hai bên, chúng ta có
.. math:: \left|e^{(k+1)}\right| = \frac{1}{2}(e^{(k)})^2 \frac{\left|f'''(\xi^{(k)})\right|}{f''(x^{(k)})}.
Do đó, bất cứ khi nào chúng tôi đang ở trong một khu vực có giới hạn
:math:`\left|f'''(\xi^{(k)})\right| / (2f''(x^{(k)})) \leq c`, chúng tôi
có một lỗi giảm bốn lần
.. math:: \left|e^{(k+1)}\right| \leq c (e^{(k)})^2.
Ngoài ra, các nhà nghiên cứu tối ưu hóa gọi sự hội tụ \* tuyến tính\*
này, trong khi một điều kiện như
:math:`\left|e^{(k+1)}\right| \leq \alpha \left|e^{(k)}\right|` sẽ được
gọi là tốc độ hội tụ \* không đổi\*. Lưu ý rằng phân tích này đi kèm với
một số cảnh báo. Đầu tiên, chúng tôi không thực sự có nhiều sự đảm bảo
khi chúng tôi sẽ đạt được khu vực hội tụ nhanh chóng. Thay vào đó, chúng
ta chỉ biết rằng một khi chúng ta đạt được nó, sự hội tụ sẽ rất nhanh
chóng. Thứ hai, phân tích này đòi hỏi :math:`f` được cư xử tốt đến các
dẫn xuất bậc cao hơn. Nó đi xuống để đảm bảo rằng :math:`f` không có bất
kỳ thuộc tính “đáng ngạc nhiên” nào về cách nó có thể thay đổi giá trị
của nó.
Điều hòa trước
~~~~~~~~~~~~~~
Khá không ngạc nhiên khi tính toán và lưu trữ Hessian đầy đủ là rất tốn
kém. Do đó, nó là mong muốn để tìm lựa chọn thay thế. Một cách để cải
thiện vấn đề là \* điều kiện tiên chuẩn\ *. Nó tránh tính toán toàn bộ
Hessian nhưng chỉ tính toán các mục nhập *\ diagonal\*. Điều này dẫn đến
cập nhật các thuật toán của biểu mẫu
.. math:: \mathbf{x} \leftarrow \mathbf{x} - \eta \mathrm{diag}(\mathbf{H})^{-1} \nabla f(\mathbf{x}).
Mặc dù điều này không hoàn toàn tốt bằng phương pháp Newton đầy đủ,
nhưng nó vẫn tốt hơn nhiều so với việc không sử dụng nó. Để xem lý do
tại sao đây có thể là một ý tưởng tốt, hãy xem xét một tình huống mà một
biến biểu thị chiều cao tính bằng milimét và cái kia biểu thị chiều cao
tính bằng km. Giả sử rằng đối với cả hai quy mô tự nhiên đều tính bằng
mét, chúng ta có một sự không phù hợp khủng khiếp trong các tham số hóa.
May mắn thay, sử dụng preconditioning loại bỏ điều này. Điều hòa trước
hiệu quả với số lượng gốc gradient để lựa chọn một tốc độ học tập khác
nhau cho mỗi biến (tọa độ của vector :math:`\mathbf{x}`). Như chúng ta
sẽ thấy sau này, điều hòa trước thúc đẩy một số sự đổi mới trong các
thuật toán tối ưu hóa dòng dốc ngẫu nhiên.
Gradient Descent với tìm kiếm dòng
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Một trong những vấn đề chính trong gradient gốc là chúng ta có thể vượt
qua mục tiêu hoặc không đủ tiến bộ. Một sửa chữa đơn giản cho vấn đề là
sử dụng tìm kiếm dòng kết hợp với gradient gốc. Đó là, chúng tôi sử dụng
hướng được đưa ra bởi :math:`\nabla f(\mathbf{x})` và sau đó thực hiện
tìm kiếm nhị phân như tỷ lệ học tập :math:`\eta` giảm thiểu
:math:`f(\mathbf{x} - \eta \nabla f(\mathbf{x}))`.
Thuật toán này hội tụ nhanh chóng (để phân tích và chứng minh xem ví dụ,
:cite:`Boyd.Vandenberghe.2004`). Tuy nhiên, với mục đích học sâu, điều
này không hoàn toàn khả thi, vì mỗi bước của tìm kiếm dòng sẽ yêu cầu
chúng ta đánh giá chức năng khách quan trên toàn bộ tập dữ liệu. Đây là
cách quá tốn kém để thực hiện.
Tóm tắt
-------
- Tỷ lệ học vấn đề. Quá lớn và chúng tôi phân kỳ, quá nhỏ và chúng tôi
không đạt được tiến bộ.
- Gradient gốc có thể bị mắc kẹt trong minima địa phương.
- Ở kích thước cao, việc điều chỉnh tốc độ học tập rất phức tạp.
- Điều hòa trước có thể giúp điều chỉnh quy mô.
- Phương pháp của Newton nhanh hơn rất nhiều khi nó đã bắt đầu hoạt
động bình thường trong các bài toán lồi.
- Cẩn thận với việc sử dụng phương pháp Newton mà không có bất kỳ điều
chỉnh nào đối với các bài toán không lồi.
Bài tập
-------
1. Thử nghiệm với các tốc độ học tập khác nhau và chức năng khách quan
để chuyển đổi độ dốc.
2. Thực hiện tìm kiếm dòng để giảm thiểu một hàm lồi trong khoảng
:math:`[a, b]`.
1. Bạn có cần các dẫn xuất cho tìm kiếm nhị phân, tức là, để quyết
định chọn :math:`[a, (a+b)/2]` hoặc :math:`[(a+b)/2, b]`.
2. Tốc độ hội tụ cho thuật toán nhanh như thế nào?
3. Thực hiện thuật toán và áp dụng nó để giảm thiểu
:math:`\log (\exp(x) + \exp(-2x -3))`.
3. Thiết kế một chức năng khách quan được xác định trên
:math:`\mathbb{R}^2` trong đó chuyển đổi độ dốc cực kỳ chậm. Gợi ý:
quy mô tọa độ khác nhau khác nhau.
4. Thực hiện phiên bản nhẹ của phương pháp Newton bằng cách sử dụng điều
hòa trước:
1. Sử dụng đường chéo Hessian như preconditioner.
2. Sử dụng các giá trị tuyệt đối của giá trị đó chứ không phải là giá
trị thực tế (có thể ký).
3. Áp dụng điều này cho vấn đề trên.
5. Áp dụng thuật toán trên cho một số hàm khách quan (lồi hay không).
Điều gì xảy ra nếu bạn xoay tọa độ :math:`45` độ?
`Discussions `__