优化的目标就是为了降低训练误差,对于深度学习的模型来说,是为了降低泛化误差,因为单纯的降低训练误差,有可能会造成过拟合。在尽可能降低损失函数的值的过程中,会遇到两个问题,局部最小值和鞍点。
        在实际问题中,目标函数都比较复杂,为了问题的简化,我们一般将目标函数在某点邻域展开成泰勒多项式来逼近原函数(一阶泰勒展开式就是拉格朗日中值定理),直接上图来看下泰勒展开式在某点的展开,通过推导我们得出了梯度下降的原理

taylor series 推导Gradient Descent 泰勒展开推导过程_黑塞矩阵

我们从图中的推导,了解到了梯度下降的原理,其中1阶的泰勒展开式也就是大家常说的拉格朗日中值定理 f(x)=f(a)+df(a)*(x-a)  —>  df(a)=[f(x)-f(a)]/x-a 这些公式对于导数与函数之间起到了桥梁作用,用处很大。

局部最小值(Local Minimum)

求解过程分为解析解和数值解,简单来说解析解就是数学公式直接推导求得,然而我们知道目标函数一般都比较复杂,所以基本上都是使用数值解。目标函数一般会有若干局部最小值,在这个附近的梯度接近或为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)')

taylor series 推导Gradient Descent 泰勒展开推导过程_2d_02

图中可以看到这个函数存在局部最小值和全局最小值,一般情况得到的结果都是局部最小值。

鞍点(Saddle Point)

除了上面所说的情况之外,另一种就是在鞍点附近,也会使得梯度接近或为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)')

taylor series 推导Gradient Descent 泰勒展开推导过程_最小值_03

再看一个三维图形函数 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')

taylor series 推导Gradient Descent 泰勒展开推导过程_最小值_04

从图中的鞍点位置(红色标注点)可以看出,目标函数在x轴方向上是局部最小值,但是在y轴方向上是局部最大值。那大概率求解出来的是属于哪种情况?我们通过海森矩阵(黑塞矩阵)来了解其原理

海森矩阵(Hessian matrix)

海森矩阵是一个多元函数的二阶偏导数构成的方阵,描述了函数的局部曲率,具体详情可以参阅百度百科的黑塞矩阵

如图:

taylor series 推导Gradient Descent 泰勒展开推导过程_python显示中文_05

假设函数输入是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学习率是超参数,设定过小、合适、过大,分别看下什么效果:

taylor series 推导Gradient Descent 泰勒展开推导过程_黑塞矩阵_06

taylor series 推导Gradient Descent 泰勒展开推导过程_深度学习_07

taylor series 推导Gradient Descent 泰勒展开推导过程_深度学习_08

我们发现学习率过小,x更新缓慢,需要更多迭代才可以,过大就导致 |ηf'(x)| 过大,一阶泰勒展开式就不再成立,也就不能保证迭代x会降低f(x)的值了。

多维梯度下降

我们可以通过一维梯度推广到多维梯度,设目标函数的输入是一个d维向量,那么有关x的梯度就是一个有d个偏导数组成的向量,现在对函数 

taylor series 推导Gradient Descent 泰勒展开推导过程_黑塞矩阵_09

 进行可视化观察梯度下降情况

#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

taylor series 推导Gradient Descent 泰勒展开推导过程_python显示中文_10