梯度下降
一、一维梯度下降
什么是梯度
梯度是指一个函数在某一点上的变化率或斜率 用符号∇ \nabla ∇ 表示
对于一维来说,梯度就是导数
对于多维来说,梯度就是各个 偏导数 组成的向量
说的很直观了,就不举栗子了
什么是梯度下降
在函数曲线的斜方向上选择一个下降最快的方向(梯度的反方向,至于为什么,因为梯度的正方向是函数值增加最快的方向,后面会说到),以此进行迭代更新,直到找到一个局部最小值
原理浅析
个人觉得动手学深度学习一书中,写的不是很清楚,这里参考《机器学习的数学》 给出要选择梯度的负方向去下降的原因
首先,我们明确目的,要找到局部最小值
对于一元函数f ( x ) f(x) f ( x ) ,我们对其在x x x 点作一阶泰勒展开
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\|) f ( x + Δ x ) = f ( x ) + ( ∇ f ( x ) ) ⊤ Δ x + o ( ∥ Δ x ∥ )
如果Δ x \Delta x Δ x 足够小,可以忽略高阶无穷小项,即保证下式不大于零即可使得函数值减小
f ( x + Δ x ) − f ( x ) ≈ ( ∇ f ( x ) ) ⊤ Δ x ≤ 0 f(x+\Delta x)-f(x) \approx (\nabla f(x))^\top \Delta x \le 0 f ( x + Δ x ) − f ( x ) ≈ ( ∇ f ( x ) ) ⊤ Δ x ≤ 0
很明显,只要我们选择合适的 Δ x \Delta x Δ x 就能保证函数值下降,接下来我们要证明:
增量Δ x \Delta x Δ x 的模一定时,在负梯度方向,函数值下降最快
( ∇ f ( x ) ) ⊤ Δ x = ∥ ∇ f ( x ) ∥ ⋅ ∥ Δ x ∥ ⋅ c o s θ (\nabla f(x))^\top \Delta x = \|\nabla f(x) \| \cdot \|\Delta x\| \cdot cos \theta ( ∇ f ( x ) ) ⊤ Δ x = ∥ ∇ f ( x ) ∥ ⋅ ∥ Δ x ∥ ⋅ c o s θ
显而易见想让上式最小,且小于零,当θ = π \theta = \pi θ = π 时
( ∇ f ( x ) ) ⊤ Δ x = − ∥ ∇ f ( x ) ∥ ⋅ ∥ Δ x ∥ (\nabla f(x))^\top \Delta x =- \|\nabla f(x) \| \cdot \|\Delta x\| ( ∇ f ( x ) ) ⊤ Δ x = − ∥ ∇ f ( x ) ∥ ⋅ ∥ Δ x ∥
此时增量往负梯度方向,函数值下降最快
得证!
继而我们可以得到,梯度下降法每次的迭代增量为:Δ x = − α ∇ f ( x ) \Delta x = - \alpha \nabla f(x) Δ x = − α ∇ f ( x )
其中,α \alpha α 为学习率,后面会讲到。初看迭代这个词可能还不是很理解,但是给出下列公式,应该就恍然大悟啦:
x k + 1 = x k − α ∇ f ( x ) x_{k+1} = x_k - \alpha \nabla f(x) x k + 1 = x k − α ∇ f ( x )
反复使用该迭代公式,即可找到函数的局部最小值
进一步理解:这样反复迭代,只要梯度不为零,函数值就能不断的减小,最终收敛到梯度为零的点,即函数的某一个极小值点
实战环节
我们选择对f ( x ) = x 2 f(x)=x^2 f ( x ) = x 2 进行梯度下降从而来求其最小值
1 2 3 4 5 6 7 8 9 import numpy as npimport torchfrom d2l import torch as d2ldef f (x ): return x ** 2 def f_grad (x ): return 2 * x
我们让x = 5 x=5 x = 5 作为初始值,设学习率α = 0.2 \alpha = 0.2 α = 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)
学习率
学习率 (learning rate)决定目标函数能否收敛到局部最小值,以及何时收敛到最小值
学习率过高可能会导致模型在训练过程中来回震荡,无法收敛。这是因为过大的学习率会使参数更新步长过大,导致算法无法找到最优解。此时,算法会一直在最优解附近震荡,收敛速度非常慢。
举个栗子:如果我们的学习率设置为1.1
1 show_trace(gd(1.1 , f_grad), f)
结果:epoch 10, x: 30.958682
同样学习率过低,模型可能需要更长时间才能收敛。这是因为过小的学习率意味着每次参数更新的步长较小,需要更多的迭代次数才能达到最优解。
举个栗子:如果我们的学习率设置为0.05
1 show_trace(gd(0.05 , f_grad), f)
结果:epoch 10, x: 1.743392
因此,超参数学习率的合适的选择(调参过程),对于模型快速收敛到最优解是很重要的
二、多元梯度下降
有了对一维梯度下降的理解,再来稍稍拓展一下,多元就比较easy了
考虑多元函数f ( x ) f(\mathbf x) f ( x ) ,其中x = [ x 1 , x 2 , … , x d ] ⊤ \mathbf{x} = [x_1, x_2, \ldots, x_d]^\top x = [ x 1 , x 2 , … , x d ] ⊤ ,则其梯度为
∇ f ( x ) = [ ∂ f ( x ) ∂ x 1 , ∂ f ( x ) ∂ x 2 , … , ∂ f ( x ) ∂ x d ] ⊤ \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 ∇ f ( x ) = [ ∂ x 1 ∂ f ( x ) , ∂ x 2 ∂ f ( x ) , … , ∂ x d ∂ f ( x ) ] ⊤
同样采取和一元梯度相同的方法:x ← x − α ∇ f ( x ) \mathbf{x} \leftarrow \mathbf{x} - \alpha \nabla f(\mathbf{x}) x ← x − α ∇ f ( x )
实战环节
对于二元函数f ( x ) = x 1 2 + 2 x 2 2 f(\mathbf{x})=x_1^2+2x_2^2 f ( x ) = x 1 2 + 2 x 2 2 ,其梯度为∇ f ( x ) = [ 2 x 1 , 4 x 2 ] ⊤ \nabla f(\mathbf{x}) = [2x_1, 4x_2]^\top ∇ f ( x ) = [ 2 x 1 , 4 x 2 ] ⊤
设置初始位置[ − 5 , − 2 ] [-5, -2] [ − 5 , − 2 ] ,通过梯度下降来观察x \mathbf x 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 ): """用定制的训练机优化2D目标函数""" 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 ): """显示优化过程中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 α = 0 . 1 时优化变量x \mathbf x x 的轨迹,可以看出,经过20步之后,x \mathbf x x 的值接近其位于[ 0 , 0 ] [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
------------未完待续-----------------
引用站外地址
内容参考自动手学深度学习
来自沐神 希望能有更多的自己的理解呀~