【DLPytorch】Optimizer(一)

前言

损失函数通常被称作优化问题的目标函数(objective function),是一个基于训练数据集的损失函数,优化的目标在于降低训练误差。依据惯例,优化算法通常只考虑最小化目标函数。其实,任何最大化问题都可以很容易地转化为最小化问题,只需令目标函数的相反数为新的目标函数即可。

为了求得最小化目标函数的数值解,我们将通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。但优化在深度学习中有很多挑战。其中的两个挑战,即局部最小值和鞍点。

局部最小值:深度学习模型的目标函数可能有若干局部最优值。当一个优化问题的数值解在局部最优解附近时,由于目标函数有关解的梯度接近或变成零,最终迭代求得的数值解可能只令目标函数局部最小化而非全局最小化。

def f(x):
    return x * np.cos(np.pi * x)


x = np.arange(-1.0, 2.0, 0.1)
y = f(x)
fig = plt.plot(x, y)

plt.annotate('local minimum', xy=(-0.3, -0.25), xytext=(-0.77, -1.0), arrowprops=dict(arrowstyle='->'))
plt.annotate('global minimun', xy=(1.1, -0.95), xytext=(0.6, 0.8), arrowprops=dict(arrowstyle='->'))

plt.xlabel('x')
plt.ylabel('f(x)')

plt.show()

鞍点:在图的鞍点位置,目标函数在(x)轴方向上是局部最小值,但在(y)轴方向上是局部最大值。

x, y = np.mgrid[-1: 1: 31j, -1: 1: 31j]
# np.mgrid[ 第1维,第2维 ,第3维 , …] 

z = x**2 - y**2

fig = plt.figure()
ax = fig.add_subplot(111, projection = '3d')
ax.plot_wireframe(x, y, z, **{'rstride': 2, 'cstride': 2})
ax.plot([0], [0], [0], 'rx')
# 设置xyz的刻度
ticks = [-1,  0, 1]
plt.xticks(ticks)
plt.yticks(ticks)

ax.set_zticks(ticks)
plt.xlabel('x')
plt.ylabel('y')
plt.show()

假设一个函数的输入为(k)维向量,输出为标量,那么它的海森矩阵(Hessian matrix)有(k)个特征值。该函数在梯度为0的位置上可能是局部最小值、局部最大值或者鞍点。

  • 当函数的海森矩阵在梯度为零的位置上的特征值全为正时,该函数得到局部最小值。
  • 当函数的海森矩阵在梯度为零的位置上的特征值全为负时,该函数得到局部最大值。
  • 当函数的海森矩阵在梯度为零的位置上的特征值有正有负时,该函数得到鞍点。

随机矩阵理论告诉我们,对于一个大的高斯随机矩阵来说,任一特征值是正或者是负的概率都是0.5 [1]。那么,以上第一种情况的概率为 (0.5^k)。由于深度学习模型参数通常都是高维的((k)很大),目标函数的鞍点通常比局部最小值更常见。

在深度学习中,虽然找到目标函数的全局最优解很难,但这并非必要。通过学习各种的优化算法,帮助我们解决上述的两类常见的问题。

梯度下降和随机梯度下降

梯度下降

通过(x leftarrow x - eta f'(x))来迭代(x),函数(f(x))的值可能会降低。因此在梯度下降中,我们先选取一个初始值(x)和常数(eta > 0),然后不断通过上式来迭代(x),直到达到停止条件,例如(f'(x)^2)的值已足够小或迭代次数已达到某个值。其实就是每次的梯度都会指引x越来越靠近最值。

以目标函数(f(x)=x^2)为例来看一看梯度下降是如何工作的,使用(x=10)作为初始值,并设(eta=0.2)。使用梯度下降对(x)迭代10次,可见最终(x)的值较接近最优解。

def gd(eta):
    x = 10
    results = [x]
    for i in range(10):
        x -= eta * 2 * x
        results.append(x)
    print("after epoch 10, x: ", x) # epoch 10, x: 0.06046617599999997
    return results


def show_trace(res):
    f_line = np.arange(-10, 10, 0.1)
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(f_line, [x * x for x in f_line])
    ax.plot(res, [x * x for x in res], '-o')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()


res = gd(0.2)
show_trace(res)

学习率

上述梯度下降算法中的正数(eta)通常叫作学习率。这是一个超参数,需要人工设定。如果使用过小的学习率,会导致(x)更新缓慢从而需要更多的迭代才能得到较好的解。

当学习率过小时,最终(x)的值依然与最优解存在较大偏差。

如果使用过大的学习率,(left|eta f'(x) ight|)可能会过大从而使前面提到的一阶泰勒展开公式不再成立:这时我们无法保证迭代(x)会降低(f(x))的值。

因此,使用适当的学习率,沿着梯度反方向更新自变量可能降低目标函数值。梯度下降重复这一更新过程直到得到满足要求的解。当学习率过大或过小都有问题。一个合适的学习率通常是需要通过多次实验找到的。

多维度梯度下降

对每一个维度都要进行梯度下降,比如目标函数(f(oldsymbol{x})=x_1^2+2x_2^2)。那么,梯度( abla f(oldsymbol{x}) = [2x_1, 4x_2]^ op)

def gd_2d(x1, x2):
    return (x1 - eta * 2 * x1, x2 - eta * 4 * x2)

还需要画等高线来观察:

随机梯度下降

如果使用梯度下降,每次自变量迭代的计算开销为(mathcal{O}(n)),它随着(n)线性增长。因此,当训练数据样本数很大时,梯度下降每次迭代的计算开销很高。

随机梯度下降(stochastic gradient descent,SGD)减少了每次迭代的计算开销。在随机梯度下降的每次迭代中,我们随机均匀采样的一个样本索引(iin{1,ldots,n}),并计算梯度( abla f_i(oldsymbol{x}))来迭代(oldsymbol{x})

[oldsymbol{x} leftarrow oldsymbol{x} - eta abla f_i(oldsymbol{x}). ]

这里(eta)同样是学习率。可以看到每次迭代的计算开销从梯度下降的(mathcal{O}(n))降到了常数(mathcal{O}(1))

我们通过在梯度中添加均值为0的随机噪声来模拟随机梯度下降,以此来比较它与梯度下降的区别。

def sgd_2d(x1, x2, s1, s2):
    return (x1 - eta * (2 * x1 + np.random.normal(0.1)),
            x2 - eta * (4 * x2 + np.random.normal(0.1)), 0, 0)

show_trace_2d(f_2d, train_2d(sgd_2d))

所以,当训练数据集的样本较多时,梯度下降每次迭代的计算开销较大,随机梯度下降通常更受青睐。

问题补充

1. Matplotlib中的annotate(注解)的用法

链接:https://blog.csdn.net/leaf_zizi/article/details/82886755

Axes.annotate(s, xy, *args, **kwargs)
params:
  s:注释文本的内容
	xy:被注释的坐标点,二维元组形如(x,y)
	xytext:注释文本的坐标点,也是二维元组,默认与xy相同
	arrowprops:箭头的样式,dict(字典)型数据,如果该属性非空,则会在注释文本和被注释点之间画一个箭头。如果不设置'arrowstyle' 关键字,则允许包含以下关键字:
  
示例:
plt.annotate('local minimum', xy=(-0.3, -0.25), xytext=(-0.77, -1.0), arrowprops=dict(arrowstyle='->'))

2. np.mgrid()用法

链接:https://www.cnblogs.com/wanghui-garcia/p/10763103.html

# 功能:返回多维结构,常见的如2D图形,3D图形
np.mgrid[ 第1维,第2维 ,第3维 , …] 

# 第n维的书写形式为:
a:b:c # c表示步长,为实数表示间隔;该为长度为[a,b),左开右闭

-- 
a:b:cj # cj表示步长,为复数表示点数;该长度为[a,b],左闭右闭
    
    
# 举例说明:
# 生成1D数组:
a=np.mgrid[-4:4:3j] # 在[-4,4]区间内取3个值
# 返回array([-4.,  0.,  4.])
 
# 生成个2D矩阵:
a = np.mgrid[[1:3:3j, 4:5:2j]]
# 生成的是3*2的矩阵

x, y = np.mgrid[1:3:3j, 4:5:2j]

"""
x
array([[1., 1.],
       [2., 2.],
       [3., 3.]])
"""

3. Python zip() 函数

zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。

zip([iterable, ...])
params:
	iterabl -- 一个或多个迭代器;
return:
	返回元组列表

>>>a = [1,2,3]
>>> b = [4,5,6]
>>> c = [4,5,6,7,8]
>>> zipped = zip(a,b)     # 打包为元组的列表
[(1, 4), (2, 5), (3, 6)]
>>> zip(a,c)              # 元素个数与最短的列表一致
[(1, 4), (2, 5), (3, 6)]
>>> zip(*zipped)          # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
[(1, 2, 3), (4, 5, 6)]

zip(a,b):将a和b中的元素对应组合成元组。结果:(1,4),(2,5),(3,6)。而要想看到这个结果,需用*zip()函数

*zip(a,b):先将a和b中的元素对应组合成元组形成结果,再将这个结果解压,即 * 表示解压。但不输出,需借助print打印出来:

4. plt.contour的用法

链接:https://blog.csdn.net/qq_42505705/article/details/88771942

contour([X, Y,] Z, [levels], *kwargs) # 绘制等高线

params:
  - X,Y 坐标
  - Z :绘制轮廓的高度值。
  
# 示例
plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
原文地址:https://www.cnblogs.com/recoverableTi/p/13524375.html