梯度下降

一、一维梯度下降

什么是梯度

梯度是指一个函数在某一点上的变化率或斜率 用符号\nabla表示

对于一维来说,梯度就是导数

对于多维来说,梯度就是各个 偏导数 组成的向量

说的很直观了,就不举栗子了

什么是梯度下降

在函数曲线的斜方向上选择一个下降最快的方向(梯度的反方向,至于为什么,因为梯度的正方向是函数值增加最快的方向,后面会说到),以此进行迭代更新,直到找到一个局部最小值

原理浅析

个人觉得动手学深度学习一书中,写的不是很清楚,这里参考《机器学习的数学》给出要选择梯度的负方向去下降的原因

首先,我们明确目的,要找到局部最小值

对于一元函数f(x)f(x),我们对其在xx点作一阶泰勒展开

f(x+Δx)=f(x)+(f(x))Δx+o(Δx)f(x+\Delta x)=f(x)+(\nabla f(x))^\top \Delta x+o(\|\Delta x\|)

如果Δx\Delta x足够小,可以忽略高阶无穷小项,即保证下式不大于零即可使得函数值减小

f(x+Δx)f(x)(f(x))Δx0f(x+\Delta x)-f(x) \approx (\nabla f(x))^\top \Delta x \le 0

很明显,只要我们选择合适的 Δx\Delta x就能保证函数值下降,接下来我们要证明:

增量Δx\Delta x的模一定时,在负梯度方向,函数值下降最快

(f(x))Δx=f(x)Δxcosθ(\nabla f(x))^\top \Delta x = \|\nabla f(x) \| \cdot \|\Delta x\| \cdot cos \theta

显而易见想让上式最小,且小于零,当θ=π\theta = \pi

(f(x))Δx=f(x)Δx(\nabla f(x))^\top \Delta x =- \|\nabla f(x) \| \cdot \|\Delta x\|

此时增量往负梯度方向,函数值下降最快

得证!

继而我们可以得到,梯度下降法每次的迭代增量为:Δx=αf(x)\Delta x = - \alpha \nabla f(x)

其中,α\alpha为学习率,后面会讲到。初看迭代这个词可能还不是很理解,但是给出下列公式,应该就恍然大悟啦:

xk+1=xkαf(x)x_{k+1} = x_k - \alpha \nabla f(x)

反复使用该迭代公式,即可找到函数的局部最小值

进一步理解:这样反复迭代,只要梯度不为零,函数值就能不断的减小,最终收敛到梯度为零的点,即函数的某一个极小值点

实战环节

我们选择对f(x)=x2f(x)=x^2进行梯度下降从而来求其最小值

1
2
3
4
5
6
7
8
9
import numpy as np
import torch
from d2l import torch as d2l

def f(x): # 目标函数
return x ** 2

def f_grad(x): # 目标函数的梯度(导数)
return 2 * x

我们让x=5x=5作为初始值,设学习率α=0.2\alpha = 0.2,迭代十次

1
2
3
4
5
6
7
8
9
10
def gd(eta, f_grad):
x = 5.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)

结果:epoch 10, x: 0.030233

可视化:

1
2
3
4
5
6
7
8
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)

output.svg

学习率

学习率(learning rate)决定目标函数能否收敛到局部最小值,以及何时收敛到最小值

学习率过高可能会导致模型在训练过程中来回震荡,无法收敛。这是因为过大的学习率会使参数更新步长过大,导致算法无法找到最优解。此时,算法会一直在最优解附近震荡,收敛速度非常慢。

举个栗子:如果我们的学习率设置为1.1

1
show_trace(gd(1.1, f_grad), f)

结果:epoch 10, x: 30.958682

1

同样学习率过低,模型可能需要更长时间才能收敛。这是因为过小的学习率意味着每次参数更新的步长较小,需要更多的迭代次数才能达到最优解。

举个栗子:如果我们的学习率设置为0.05

1
show_trace(gd(0.05, f_grad), f)

结果:epoch 10, x: 1.743392

2

因此,超参数学习率的合适的选择(调参过程),对于模型快速收敛到最优解是很重要的


二、多元梯度下降

有了对一维梯度下降的理解,再来稍稍拓展一下,多元就比较easy了

考虑多元函数f(x)f(\mathbf x) ,其中x=[x1,x2,,xd]\mathbf{x} = [x_1, x_2, \ldots, x_d]^\top,则其梯度为

f(x)=[f(x)x1,f(x)x2,,f(x)xd]\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

同样采取和一元梯度相同的方法:xxαf(x)\mathbf{x} \leftarrow \mathbf{x} - \alpha \nabla f(\mathbf{x})

实战环节

对于二元函数f(x)=x12+2x22f(\mathbf{x})=x_1^2+2x_2^2,其梯度为f(x)=[2x1,4x2]\nabla f(\mathbf{x}) = [2x_1, 4x_2]^\top

设置初始位置[5,2][-5, -2],通过梯度下降来观察x\mathbf x的轨迹

--------------------------------下面的部分暂时还没学到,不太懂,留个坑-----------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def train_2d(trainer, steps=20, f_grad=None):  #@save
"""用定制的训练机优化2D目标函数"""
# s1和s2是稍后将使用的内部状态变量
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
"""显示优化过程中2D变量的轨迹"""
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), indexing='ij')
d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
d2l.plt.xlabel('x1')
d2l.plt.ylabel('x2')

接下来,观察学习率α=0.1\alpha = 0.1时优化变量x\mathbf x的轨迹,可以看出,经过20步之后,x\mathbf x的值接近其位于[0,0][0, 0]的最小值,但是很缓慢

1
2
3
4
5
6
7
8
9
10
11
12
def f_2d(x1, x2):  # 目标函数
return x1 ** 2 + 2 * x2 ** 2

def f_2d_grad(x1, x2): # 目标函数的梯度
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))

结果:epoch 20, x1: -0.057646, x2: -0.000073

3

------------未完待续-----------------

ptimization/gd.html%}