有一定深度学习背景知识的伙伴们都知道,为了得到一个好的模型,都预先定义一个损失函数(优化的目标函数),然后使用合适的优化算法去试图得到一个最小值。优化的目标就是为了降低训练误差,对于深度学习的模型来说,是为了降低泛化误差,因为单纯的降低训练误差,有可能会造成过拟合。在尽可能降低损失函数的值的过程中,会遇到两个问题,局部最小值和鞍点。
在实际问题中,目标函数都比较复杂,为了问题的简化,我们一般将目标函数在某点邻域展开成泰勒多项式来逼近原函数(一阶泰勒展开式就是拉格朗日中值定理),直接上图来看下泰勒展开式在某点的展开,通过推导我们得出了梯度下降的原理
我们从图中的推导,了解到了梯度下降的原理,其中1阶的泰勒展开式也就是大家常说的拉格朗日中值定理 f(x)=f(a)+df(a)*(x-a) —> df(a)=[f(x)-f(a)]/x-a 这些公式对于导数与函数之间起到了桥梁作用,用处很大。
求解过程分为解析解和数值解,简单来说解析解就是数学公式直接推导求得,然而我们知道目标函数一般都比较复杂,所以基本上都是使用数值解。目标函数一般会有若干局部最小值,在这个附近的梯度接近或为0,那么最终迭代求得的值,很大程度上不是全局最小化,而是局部最小化。
我们来看一个函数 f(x)=x*cos(π*x) 的走势图,代码如下
- import d2lzh as d2l
- from mpl_toolkits import mplot3d
- import numpy as np
-
- def f(x):
- return x*np.cos(np.pi*x)
-
- d2l.plt.rcParams['font.family']=['STFangsong']#显示中文
- d2l.set_figsize((5.5,3))
- x=np.arange(-1,2,0.1)
- fig,=d2l.plt.plot(x,f(x))
- #指向的最优值是大概位置
- fig.axes.annotate('局部最小值',xytext=(-0.77,-1.0),xy=(-0.25,-0.25),arrowprops=dict(arrowstyle="->"))
- fig.axes.annotate('全局最小值',xytext=(0.6,0.8),xy=(1.1,-0.95),arrowprops=dict(arrowstyle="->"))
- d2l.plt.xlabel('x')
- d2l.plt.ylabel('f(x)')
图中可以看到这个函数存在局部最小值和全局最小值,一般情况得到的结果都是局部最小值。
除了上面所说的情况之外,另一种就是在鞍点附近,也会使得梯度接近或为0
我们来看下函数 f(x)=x³ 的走势情况,代码如下:
- def f(x):
- return x**3
-
- d2l.plt.rcParams['font.family']=['STFangsong']#显示中文
- x=np.arange(-2,2,0.1)
- fig,=d2l.plt.plot(x,f(x))
- fig.axes.annotate('鞍点',xytext=(-0.5,-5),xy=(0,-0.2),arrowprops=dict(arrowstyle="->"))
- d2l.plt.xlabel('x')
- d2l.plt.ylabel('f(x)')
再看一个三维图形函数 f(x,y)=x²-y² 代码如下:
- d2l.plt.rcParams['font.family']=['STFangsong']#显示中文
- x,y=np.mgrid[-1:1:31j,-1:1:31j]
- print(x.shape)#(31, 31)
- z=x**2-y**2
- ax=d2l.plt.figure().add_subplot(111,projection='3d')
- ax.plot_wireframe(x,y,z,**{'rstride':2,'cstride':2})
- ax.plot([0],[0],[0],'rX')
- ticks=[-1,0,1]
- d2l.plt.xticks(ticks)
- d2l.plt.yticks(ticks)
- ax.set_zticks(ticks)
- d2l.plt.xlabel('x')
- d2l.plt.ylabel('y')
从图中的鞍点位置(红色标注点)可以看出,目标函数在x轴方向上是局部最小值,但是在y轴方向上是局部最大值。那大概率求解出来的是属于哪种情况?我们通过海森矩阵(黑塞矩阵)来了解其原理
海森矩阵是一个多元函数的二阶偏导数构成的方阵,描述了函数的局部曲率,具体详情可以参阅百度百科的黑塞矩阵
如图:
假设函数输入是k维向量,输出是标量,那么黑塞矩阵(Hessian matrix)有k个特征值,该函数在梯度为0的位置上可能是局部最小值、局部最大值或者鞍点三种情况,出现概率如下:
1、当函数的黑塞矩阵在梯度为0的位置上的特征值全为正时,该函数得到局部最小值
2、当函数的黑塞矩阵在梯度为0的位置上的特征值全为负时,该函数得到局部最大值
3、当函数的黑塞矩阵在梯度为0的位置上的特征值有正有负时,该函数得到鞍点
对于一个大的高斯随机矩阵来说,正负概率各占一半,那从图中可以得出,1和2的情况的概率就是0.5的k次方,k一般很大,所以概率是很小的,也就是说目标函数更容易得到的是鞍点而不是局部最优值。
虽然找到目标函数的全局最优解很难,但并非必要,下面的优化算法在实际问题中都能训练出十分有效的深度学习模型
对于梯度大家应该很熟悉了,一维就是导数,二维就是偏导数的集合,我们通过代码可视化梯度下降,看下具体是如何工作的。
函数 f(x)=x² 我们知道其最小值是0,观察它的迭代,代码如下:
- def gd(eta):
- x=10
- results=[x]
- for i in range(10):
- x-=eta*2*x #f(x)=x²的导数是2x
- results.append(x)
- print('epoch 10,x:',x)
- return results
- res=gd(1.1)
-
- #可视化梯度下降轨迹
- def show_trace(res):
- #f_line=np.arange(-10,10,0.1)
- n=max(abs(min(res)),abs(max(res)),10)
- f_line=np.arange(-n,n,0.1)
- d2l.set_figsize()
- d2l.plt.plot(f_line,[x**2 for x in f_line])#抛物线
- d2l.plt.plot(res,[x**2 for x in res],'-o')#梯度迭代节点
- d2l.plt.xlabel('x')
- d2l.plt.ylabel('f(x)')
- show_trace(res)
其中eta学习率是超参数,设定过小、合适、过大,分别看下什么效果:
我们发现学习率过小,x更新缓慢,需要更多迭代才可以,过大就导致 |ηf'(x)| 过大,一阶泰勒展开式就不再成立,也就不能保证迭代x会降低f(x)的值了。
我们可以通过一维梯度推广到多维梯度,设目标函数的输入是一个d维向量,那么有关x的梯度就是一个有d个偏导数组成的向量,现在对函数 进行可视化观察梯度下降情况
- #d2lzh包中已有
- def train_2d(trainer):
- x1,x2,s1,s2=-5,-2,0,0
- results=[(x1,x2)]
- for i in range(20):
- x1,x2,s1,s2=trainer(x1,x2,s1,s2)#s1和s2是自变量状态,后期一些优化算法将被使用
- results.append((x1,x2))
- print('epoch %d,x1 %f,x2 %f' % (i+1,x1,x2))
- return results
-
- def show_trace_2d(f,results):
- 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')
-
- eta=0.1
- def f_2d(x1,x2):
- return x1**2 + 2*x2**2
-
- def gd_2d(x1,x2,s1,s2):
- return (x1-eta*2*x1,x2-eta*4*x2,0,0)
-
- show_trace_2d(f_2d,train_2d(gd_2d))
- #epoch 20,x1 -0.057646,x2 -0.000073
想了解更多关于梯度的伙伴们可以点击: