动手学深度学习(九、深度学习优化算法)

参考链接:优化算法

存在问题:

  • matplotlib库不会使用
  • 指数加权移动平均变量
  • AdaDelta算法的torch实现存在问题

对于深度学习优化算法

小批量随机梯度下降方法torch.optim.SGD  {"lr": 0.05}lr学习率
动量法torch.optim.SGD{'lr': 0.004, 'momentum': 0.9} lr学习率, momentum动量超参数
Adagrad算法 torch.optim.Adagrad{'lr': 0.1}lr学习率
RMSprop算法 torch.optim.RMSprop{'lr': 0.01, 'alpha': 0.9} lr学习率, alpha超参数
Adadelta算法 torch.optim.Adadelta{'rho': 0.9} rho超参数
Adam算法torch.optim.Adam{'lr': 0.01}lr学习率超参数

一、优化与深度学习


  • 由于优化算法的目标函数通常是一个基于训练数据集的损失函数,优化的目标在于降低训练误差。
  • 由于深度学习模型参数通常都是高维的,目标函数的鞍点通常比局部最小值更常见。

优化与深度学习的关系,以及优化在深度学习中的挑战。在一个深度学习问题中,我们通常会预先定义一个损失函数。有了损失函数以后,我们就可以使用优化算法试图将其最小化。在优化中,这样的损失函数通常被称作优化问题的目标函数(objective function)。依据惯例,优化算法通常只考虑最小化目标函数。其实,任何最大化问题都可以很容易地转化为最小化问题,只需令目标函数的相反数为新的目标函数即可。

优化与深度学习的关系

虽然优化为深度学习提供了最小化损失函数的方法,但本质上,优化与深度学习的目标是有区别的。 由于优化算法的目标函数通常是一个基于训练数据集的损失函数,优化的目标在于降低训练误差。 而深度学习的目标在于降低泛化误差。为了降低泛化误差,除了使用优化算法降低训练误差以外,还需要注意应对过拟合。

优化在深度学习中的挑战

深度学习中绝大多数目标函数都很复杂。因此,很多优化问题并不存在解析解,而需要使用基于数值方法的优化算法找到近似解,即数值解。为了求得最小化目标函数的数值解,我们将通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。优化在深度学习中有很多挑战。下面描述了其中的两个挑战,即局部最小值和鞍点。

局部最小解

对于一个目标函数, 存在局部最小值、全局最小值, 例如:

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

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l
from mpl_toolkits import mplot3d # 三维画图
import numpy as np

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

d2l.set_figsize((4.5, 2.5))
x = np.arange(-1.0, 2.0, 0.1)
#plot方法以列表的形式返回包含绘图中每一行信息的对象。在python中,可以用逗号扩展列表的元素。
fig,   = d2l.plt.plot(x, f(x))

fig.axes.annotate('local minimum', xy=(-0.3, -0.25), xytext=(-0.77, -1.0),
                  arrowprops=dict(arrowstyle='->'))
fig.axes.annotate('global minimum', xy=(1.1, -0.95), xytext=(0.6, 0.8),
                  arrowprops=dict(arrowstyle='->'))
d2l.plt.xlabel('x')
d2l.plt.ylabel('f(x)');

鞍点

梯度接近或变成零可能是由于当前解在局部最优解附近造成的。事实上,另一种可能性是当前解在鞍点(saddle point)附近。例如函数f(x) = x^{3}

x = np.arange(-2.0, 2.0, 0.1)
fig, = d2l.plt.plot(x, x**3)
fig.axes.annotate('saddle point', xy=(0, -0.2), xytext=(-0.52, -5.0),
                  arrowprops=dict(arrowstyle='->'))
d2l.plt.xlabel('x')
d2l.plt.ylabel('f(x)');

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

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

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

随机矩阵理论告诉我们,对于一个大的高斯随机矩阵来说,任一特征值是正或者是负的概率都是0.5。那么,以上第一种情况的概率为 0.5^{k}。由于深度学习模型参数通常都是高维的(k很大),目标函数的鞍点通常比局部最小值更常见。在深度学习中,虽然找到目标函数的全局最优解很难,但这并非必要。我们将在接下来的几节中逐一介绍深度学习中常用的优化算法,它们在很多实际问题中都能够训练出十分有效的深度学习模型。

二、梯度下降和随机梯度下降

参考:梯度下降

  • 使用适当的学习率,沿着梯度反方向更新自变量可能降低目标函数值。梯度下降重复这一更新过程直到得到满足要求的解。
  • 学习率过大或过小都有问题。一个合适的学习率通常是需要通过多次实验找到的。
  • 当训练数据集的样本较多时,梯度下降每次迭代的计算开销较大,因而随机梯度下降通常更受青睐。

本节介绍梯度下降(gradient descent)的工作原理。虽然梯度下降在深度学习中很少被直接使用,但理解梯度的意义以及沿着梯度反方向更新自变量可能降低目标函数值的原因是学习后续优化算法的基础。随后,我们将引出随机梯度下降(stochastic gradient descent)。

一维梯度下降

通过 x \leftarrow x - \eta f^{'}(x) 来迭代x, 函数f(x)的值可能会降低。 因此在梯度下降中, 选取一个初始值x和常数\eta > 0, 然后不断通过上式来迭代x, 直到达到停止条件, 例如f^{'}(x)^{2}的值足够小或者迭代次数已达到某个值。下面以目标函数f(x) = x^{2}为例来看梯度下降如何工作

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l
from mpl_toolkits import mplot3d # 三维画图
import numpy as np
import torch
import math

sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l

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

#输出10步之后的值
res = gd(0.2)

def show_trace(res):
    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 * x for x in f_line])
    d2l.plt.plot(res, [x * x for x in res], '-o')
    d2l.plt.xlabel('x')
    d2l.plt.ylabel('f(x)')

show_trace(res)

 输出:

学习率

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

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

多维梯度下降

随机梯度下降

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

随机梯度下降(stochastic gradient descent,SGD)减少了每次迭代的计算开销。在随机梯度下降的每次迭代中,我们随机均匀采样的一个样本索引i∈{1,…,n},并计算梯度∇fi(x)来迭代x。这意味着,平均来说,随机梯度是对梯度的一个良好的估计。

随机梯度是对梯度的一个良好的估计。随机梯度下降中自变量的迭代轨迹相对于梯度下降中的来说更为曲折。这是由于实验所添加的噪声使模拟的随机梯度的准确度下降。在实际中,这些噪声通常指训练数据集中的无意义的干扰。

三、小批量随机梯度下降

  • 小批量随机梯度每次随机均匀采样一个小批量的训练样本来计算梯度。
  • 在实际中,(小批量)随机梯度下降的学习率可以在迭代过程中自我衰减。
  • 通常,小批量随机梯度在每个迭代周期的耗时介于梯度下降和随机梯度下降的耗时之间。

在每一次迭代中,梯度下降使用整个训练数据集来计算梯度,因此它有时也被称为批量梯度下降(batch gradient descent)。而随机梯度下降在每次迭代中只随机采样一个样本来计算梯度。我们还可以在每轮迭代中随机均匀采样多个样本来组成一个小批量,然后使用这个小批量来计算梯度。下面就来描述小批量随机梯度下降。

基于随机采样得到的梯度的方差在迭代过程中无法减小,因此在实际中,(小批量)随机梯度下降的学习率可以在迭代过程中自我衰减,例如\eta_{t} = \eta t^{\alpha }(\alpha = -1 or -0.5)\eta_{t} = \eta \alpha ^{t}(\alpha = 0.95)    或者每迭代若干次后将学习率衰减一次。如此一来,学习率和(小批量)随机梯度乘积的方差会减小。而梯度下降在迭代过程中一直使用目标函数的真实梯度,无须自我衰减学习率。

小批量随机梯度下降中每次迭代的计算开销为O(∣B∣)。当批量大小为1时,该算法即为随机梯度下降;当批量大小等于训练数据样本数时,该算法即为梯度下降。当批量较小时,每次迭代中使用的样本少,这会导致并行处理和内存使用效率变低。这使得在计算同样数目样本的情况下比使用更大批量时所花时间更多。当批量较大时,每个小批量梯度里可能含有更多的冗余信息。为了得到较好的解,批量较大时比批量较小时需要计算的样本数目可能更多,例如增大迭代周期数。3.2节已经实现过小批量梯度下降算法

简洁实现

在PyTorch里可以通过创建optimizer实例来调用优化算法。这能让实现更简洁。下面实现一个通用的训练函数,它通过优化算法的函数optimizer_fn和超参数optimizer_hyperparams来创建optimizer实例。

#2021-4-10-15:25 7.3.2
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import numpy as np

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#获取数据样本中的前1500个数据以及5个特征
def get_data_ch7():  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    data = np.genfromtxt('./Dive-into-DL-PyTorch-master/data/airfoil_self_noise.dat', delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)
    return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
    torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)

features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])

# 优化算法的函数optimizer_fn
# 超参数optimizer_hyperparams
def train_pytorch_ch7(optimizer_fn, optimizer_hyperparams, features, labels, batch_size = 10, num_epochs = 2):
    #初始化模型
    net = nn.Sequential(
        nn.Linear(features.shape[-1], 1)
    )
    loss = nn.MSELoss()
    optimizer = optimizer_fn(net.parameters(), **optimizer_hyperparams)
    
    def eval_loss():
        return loss(net(features).view(-1), labels).item() / 2
    
    ls = [eval_loss()]
    data_iter = torch.utils.data.DataLoader(
        torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)

    for _ in range(num_epochs):
        start = time.time()
        for batch_i, (X, y) in enumerate(data_iter):
            # 除以2是为了和train_ch7保持一致, 因为squared_loss中除了2
            l = loss(net(X).view(-1), y) / 2 

            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            if (batch_i + 1) * batch_size % 100 == 0:
                ls.append(eval_loss())
    # 打印结果和作图
    print('loss: %f, %f sec per epoch' % (ls[-1], time.time() - start))
    d2l.set_figsize()
    d2l.plt.plot(np.linspace(0, num_epochs, len(ls)), ls)
    d2l.plt.xlabel('epoch')
    d2l.plt.ylabel('loss')
    
train_pytorch_ch7(optim.SGD, {"lr": 0.05}, features, labels, 10)

四、动量法

  • 动量法使用了指数加权移动平均的思想。它将过去时间步的梯度做了加权平均,且权重按时间步指数衰减。
  • 动量法使得相邻时间步的自变量更新在方向上更加一致。

梯度下降的问题

目标函数有关自变量的梯度代表了目标函数在自变量当前位置下降最快的方向。因此,梯度下降也叫作最陡下降(steepest descent)。在每次迭代中,梯度下降根据自变量当前位置,沿着当前位置的梯度更新自变量。然而,如果自变量的迭代方向仅仅取决于自变量当前位置,这可能会带来一些问题。

可以看到,同一位置上,目标函数在竖直方向(x2轴方向)比在水平方向(x1轴方向)的斜率的绝对值更大。因此,给定学习率,梯度下降迭代自变量时会使自变量在竖直方向比在水平方向移动幅度更大。那么,我们需要一个较小的学习率从而避免自变量在竖直方向上越过目标函数最优解。然而,这会造成自变量在水平方向上朝最优解移动变慢。我们试着将学习率调得稍大一点,此时自变量在竖直方向不断越过最优解并逐渐发散。

动量法

动量法的提出是为了解决梯度下降的上述问题。时间步t的小批量随机梯度g_{t}, ​设时间步t的自变量为x_{t}​,学习率为\eta _{t}。 在时间步0,动量法创建速度变量v_{0}​,并将其元素初始化成0。在时间步t>0,动量法对每次迭代的步骤做如下修改:

v_{t} \leftarrow \gamma v_{t - 1} + \eta _{t}g_{t}

x_{t} \leftarrow x_{t - 1} - v_{t}

当γ=0时,动量法等价于小批量随机梯度下降。

使用动量法之后移动更加平滑, 自变量也不再发散。

指数加权移动平均

由指数加权移动平均理解动量法

在动量法中,自变量在各个方向上的移动幅度不仅取决当前梯度,还取决于过去的各个梯度在各个方向上是否一致。在本节之前示例的优化问题中,所有梯度在水平方向上为正(向右),而在竖直方向上时正(向上)时负(向下)。这样,我们就可以使用较大的学习率,从而使自变量向最优解更快移动。

从零开始实现

相对于小批量随机梯度下降,动量法需要对每一个自变量维护一个同它一样形状的速度变量,且超参数里多了动量超参数。实现中,我们将速度变量用更广义的状态变量states表示。

需要设计好动量超参数、学习率超参数, 使得目标数值的下降变化更加平滑。

简洁实现

在PyTorch中,只需要通过参数momentum来指定动量超参数即可使用动量法.

#2021-4-10-15:25 7.3.2
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import numpy as np

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#获取数据样本中的前1500个数据以及5个特征
def get_data_ch7():  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    data = np.genfromtxt('./Dive-into-DL-PyTorch-master/data/airfoil_self_noise.dat', delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)
    return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
    torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)

features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])
    
#只需要写一行即可实现动量法
d2l.train_pytorch_ch7(torch.optim.SGD, {'lr':0.004, 'momentum':0.9}, features, labels)

五、AdaGrad算法

  • AdaGrad算法在迭代过程中不断调整学习率,并让目标函数自变量中每个元素都分别拥有自己的学习率。
  • 使用AdaGrad算法时,自变量中每个元素的学习率在迭代过程中一直在降低(或不变)。

在之前介绍过的优化算法中,目标函数自变量的每一个元素在相同时间步都使用同一个学习率来自我迭代。(动量法)里我们看到当x1​和x2​的梯度值有较大差别时,需要选择足够小的学习率使得自变量在梯度值较大的维度上不发散。但这样会导致自变量在梯度值较小的维度上迭代过慢。动量法依赖指数加权移动平均使得自变量的更新方向更加一致,从而降低发散的可能。AdaGrad算法,它根据自变量在每个维度的梯度值的大小来调整各个维度上的学习率,从而避免统一的学习率难以适应所有维度的问题

算法

这些按元素运算使得目标函数自变量中每个元素都分别拥有自己的学习率。

特点

在时间步0,AdaGrad将s_0中每个元素初始化为0。在时间步t,首先将小批量随机梯度g_t按元素平方后累加到变量s_t, 其中⊙是按元素相乘。

小批量随机梯度按元素平方的累加变量s_t出现在学习率的分母项中。因此,如果目标函数有关自变量中某个元素的偏导数一直都较大,那么该元素的学习率将下降较快;反之,如果目标函数有关自变量中某个元素的偏导数一直都较小,那么该元素的学习率将下降较慢。然而,由于s_t一直在累加按元素平方的梯度,自变量中每个元素的学习率在迭代过程中一直在降低(或不变)。所以,当学习率在迭代早期降得较快且当前解依然不佳时,AdaGrad算法在迭代后期由于学习率过小,可能较难找到一个有用的解

对于之前所述的例子, 使用AdaGrad算法并且学习率0.4, 自变量的迭代轨迹较平滑。但由于s_t的累加效果使学习率不断衰减,自变量在迭代后期的移动幅度较小。当将学习率增大到2。可以看到自变量更为迅速地逼近了最优解。

#2021-4-10 21:37 7.3.2
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import numpy as np

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#获取数据样本中的前1500个数据以及5个特征
def get_data_ch7():  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    data = np.genfromtxt('./Dive-into-DL-PyTorch-master/data/airfoil_self_noise.dat', delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)
    return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
    torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)

features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])

def init_adagrad_states():
    s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
    s_b = torch.zeros(1, dtype=torch.float32)
    return (s_w, s_b)

(a, b) = init_adagrad_states()

def adagrad(params, states, hyperparams):
    eps = 1e-6
    for p, s in zip(params, states):
        s.data += (p.grad.data ** 2)
        p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
    
d2l.train_ch7(adagrad, init_adagrad_states(), {'lr': 0.1}, features, labels)

#简洁实现
d2l.train_pytorch_ch7(torch.optim.Adagrad, {'lr': 0.1}, features, labels)

输出:

六、RMSProp算法

  • RMSProp算法和AdaGrad算法的不同在于,RMSProp算法使用了小批量随机梯度按元素平方的指数加权移动平均来调整学习率。

AdaGrad算法因为调整学习率时分母上的变量s_t​一直在累加按元素平方的小批量随机梯度,所以目标函数自变量每个元素的学习率在迭代过程中一直在降低(或不变)。因此,当学习率在迭代早期降得较快且当前解依然不佳时,AdaGrad算法在迭代后期由于学习率过小,可能较难找到一个有用的解。为了解决这一问题,RMSProp算法对AdaGrad算法做了一点小小的修改。该算法源自Coursera上的一门课程,即“机器学习的神经网络”

算法

不同于AdaGrad算法里状态变量s_t​是截至时间步t所有小批量随机梯度g_t按元素平方和,RMSProp算法将这些梯度按元素平方做指数加权移动平均。和AdaGrad算法一样,RMSProp算法将目标函数自变量中每个元素的学习率通过按元素运算重新调整,然后更新自变量。自变量每个元素的学习率在迭代过程中就不再一直降低(或不变)。

(AdaGrad算法)使用的学习率为0.4的AdaGrad算法,自变量在迭代后期的移动幅度较小。但在同样的学习率下,RMSProp算法可以更快逼近最优解。

我们将初始学习率设为0.01,并将超参数γ设为0.9。此时,变量s_t​可看作是最近1/(1−0.9)=10个时间步的平方项g_{t}g_{t}的加权平均。

#2021-4-11 13:55 7.6
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import numpy as np

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#获取数据样本中的前1500个数据以及5个特征
def get_data_ch7():  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    data = np.genfromtxt('./Dive-into-DL-PyTorch-master/data/airfoil_self_noise.dat', delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)
    return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
    torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)

features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])

def init_rmsprop_states():
    s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
    s_b = torch.zeros(1, dtype=torch.float32)
    return (s_w, s_b)

def rmsprop(params, states, hyperparams):
    gamma, eps = hyperparams['gamma'], 1e-6
    for p, s in zip(params, states):
        s.data = gamma * s.data + (1 - gamma) * (p.grad.data) ** 2
        p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
        
#我们将初始学习率设为0.01,并将超参数设为0.9
d2l.train_ch7(rmsprop, init_rmsprop_states(), {'lr':0.01, 'gamma':0.9}, features, labels)

#简洁实现, 通过名称RMSprop的优化器方法, 我们便可使用PyTorch提供的RMSProp算法来训练模型。注意,超参数γ通过alpha指定。
d2l.train_pytorch_ch7(torch.optim.RMSprop, {'lr':0.01, 'alpha':0.9}, features, labels)

七、AdaDelta算法

  • AdaDelta算法没有学习率超参数,它通过使用有关自变量更新量平方的指数加权移动平均的项来替代RMSProp算法中的学习率。

除了RMSProp算法以外,另一个常用优化算法AdaDelta算法也针对AdaGrad算法在迭代后期可能较难找到有用解的问题做了改进 。AdaDelta算法没有学习率这一超参数

AdaDelta算法也像RMSProp算法一样,使用了小批量随机梯度g_{t}​按元素平方的指数加权移动平均变量s_{t}。在时间步0,它的所有元素被初始化为0。给定超参数0≤ρ<1(对应RMSProp算法中的γ),在时间步t>0,同RMSProp算法一样计算。与RMSProp算法不同的是,AdaDelta算法还维护一个额外的状态变量\Delta x_t,其元素同样在时间步0时被初始化为0。可以看到,如不考虑ϵ的影响,AdaDelta算法跟RMSProp算法的不同之处在于使用Δxt−1​​来替代学习率η

 

 

#2021-4-11 14:22 7.7
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import numpy as np

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#获取数据样本中的前1500个数据以及5个特征
def get_data_ch7():  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    data = np.genfromtxt('./Dive-into-DL-PyTorch-master/data/airfoil_self_noise.dat', delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)
    return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
    torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)

features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])

def init_adadelta_states():
    s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
    delta_w, delta_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
    return ((s_w, delta_w), (s_b, delta_b))

def adadelta(params, states, hyperparams):
    rho, eps = hyperparams['rho'], 1e-5
    for p, (s, delta) in zip(params, states):
        s[:] = rho * s + (1 - rho) * (p.grad.data**2)
        g =  p.grad.data * torch.sqrt((delta + eps) / (s + eps))
        p.data -= g
        delta[:] = rho * delta + (1 - rho) * g * g
        
d2l.train_ch7(adadelta, init_adadelta_states(), {'rho': 0.9}, features, labels)

#通过名称为Adadelta的优化器方法,我们便可使用PyTorch提供的AdaDelta算法。它的超参数可以通过rho来指定。
d2l.train_pytorch_ch7(torch.optim.Adadelta, {'rho': 0.9}, features, labels)

pytorch中的torch.optim.AdaDelta算法的实现,与手动模拟实现存在较大差异

八、Adam算法

  • Adam算法在RMSProp算法的基础上对小批量随机梯度也做了指数加权移动平均。
  • Adam算法使用了偏差修正。

Adam算法在RMSProp算法基础上对小批量随机梯度也做了指数加权移动平均。所以Adam算法可以看做是RMSProp算法与动量法的结合。

Adam算法使用了动量变量v_{t}和RMSProp算法中小批量随机梯度按元素平方的指数加权移动平均变量s_{t},并在时间步0将它们中每个元素初始化为0。给定超参数0\leq \beta_{1} < 1(算法作者建议设为0.9),时间步t的动量变量v_{t}即小批量随机梯度g_{t}​的指数加权移动平均, 和RMSProp算法中一样,给定超参数0\leq \beta _{2}< 1(算法作者建议设为0.999)

#2021-4-11 14:22 7.7
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import numpy as np

import sys
sys.path.append("F:\数据\MRC\动手学深度学习\Dive-into-DL-PyTorch-master\code")
import d2lzh_pytorch as d2l

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#获取数据样本中的前1500个数据以及5个特征
def get_data_ch7():  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    data = np.genfromtxt('./Dive-into-DL-PyTorch-master/data/airfoil_self_noise.dat', delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)
    return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
    torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)

features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])

def init_adam_states():
    v_w, v_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
    s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
    return ((v_w, s_w), (v_b, s_b))

def adam(params, states, hyperparams):
    beta1, beta2, eps = 0.9, 0.999, 1e-6
    for p, (v, s) in zip(params, states):
        v[:] = beta1 * v + (1 - beta1) * p.grad.data
        s[:] = beta2 * s + (1 - beta2) * p.grad.data**2
        v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
        s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
        p.data -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr) + eps)
    hyperparams['t'] += 1
        
#我们按照Adam算法中的公式实现该算法。其中时间步t通过hyperparams参数传入adam函数。
d2l.train_ch7(adam, init_adam_states(), {'lr':0.01, 't':1}, features, labels)

#通过名称为Adadelta的优化器方法,我们便可使用PyTorch提供的AdaDelta算法。它的超参数可以通过rho来指定。
d2l.train_pytorch_ch7(torch.optim.Adam, {'lr': 0.01}, features, labels)

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐