精华内容
下载资源
问答
  • 动量法 目标函数有关自变量的梯度代表了目标函数在自变量当前位置下降最快的方向,因此,梯度下降也叫作最陡下降(steepest descent)。在每次迭代中,梯度下降根据自变量当前位置,沿着当前位置的梯度更新自变量。...

    动量法

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

    梯度下降的问题

    考虑一个目标函数:

    • 输入为二维向量 x = [ x 1 , x 2 ] ⊤ \boldsymbol{x} = [x_1, x_2]^\top x=[x1,x2]
    • 输出为标量
    • f ( x ) = 0.1 x 1 2 + 2 x 2 2 f(\boldsymbol{x})=0.1x_1^2+2x_2^2 f(x)=0.1x12+2x22

    这里将 x 1 2 x_1^2 x12系数从 1 1 1减小到了 0.1 0.1 0.1。下面实现基于这个目标函数的梯度下降,并演示使用学习率为 0.4 0.4 0.4时自变量的迭代轨迹。

    %matplotlib inline
    import sys
    sys.path.append("..") 
    from matplotlib import pyplot as plt
    import torch
    
    eta = 0.4 # 学习率
    
    def f_2d(x1, x2):
        return 0.1 * x1 ** 2 + 2 * x2 ** 2
    
    def gd_2d(x1, x2, s1, s2):
        return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0)
        
    def show_trace_2d(f, results):  
        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))
        plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
        plt.xlabel('x1')
        plt.ylabel('x2')
    
    def train_2d(trainer):  
        x1, x2, s1, s2 = -5, -2, 0, 0  # s1和s2是自变量状态
        results = [(x1, x2)]
        for i in range(20):
            x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
            results.append((x1, x2))
        print('epoch %d, x1 %f, x2 %f' % (i + 1, x1, x2))
        return results
        
    show_trace_2d(f_2d, train_2d(gd_2d))
    

    输出:

    epoch 20, x1 -0.943467, x2 -0.000073
    

    这幅图可以理解为 f ( x ) f(\boldsymbol{x}) f(x) x 1 x_1 x1 x 2 x_2 x2平面上的投影,可以看到

    • 同一位置上,目标函数在竖直方向( x 2 x_2 x2轴方向)比在水平方向( x 1 x_1 x1轴方向)的斜率的绝对值更大。
    • 因此,给定学习率,梯度下降迭代自变量时会使自变量在竖直方向比在水平方向移动幅度更大。
    • 我们需要一个较小的学习率从而避免自变量在竖直方向上越过目标函数最优解。然而,这会造成自变量在水平方向上朝最优解移动变慢

    下面我们试着将学习率调得稍大一点,此时自变量在竖直方向不断越过最优解并逐渐发散

    eta = 0.6
    show_trace_2d(f_2d, train_2d(gd_2d))
    

    动量法

    动量法的提出是为了解决梯度下降的上述问题。

    • 设时间步 t t t的自变量为 x t \boldsymbol{x}_t xt
    • 学习率为 η t \eta_t ηt
    • 在时间步 0 0 0,动量法创建速度变量 v 0 \boldsymbol{v}_0 v0,并将其元素初始化成0。在时间步 t > 0 t>0 t>0,动量法对每次迭代的步骤做如下修改:

    v t ← γ v t − 1 + η t g t , x t ← x t − 1 − v t , \begin{aligned} \boldsymbol{v}_t &\leftarrow \gamma \boldsymbol{v}_{t-1} + \eta_t \boldsymbol{g}_t, \\ \boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{v}_t, \end{aligned} vtxtγvt1+ηtgt,xt1vt,

    其中,动量超参数 γ \gamma γ满足 0 ≤ γ < 1 0 \leq \gamma < 1 0γ<1。当 γ = 0 \gamma=0 γ=0时,动量法等价于小批量随机梯度下降。

    在解释动量法的数学原理前,让我们先从实验中观察梯度下降在使用动量法后的迭代轨迹。

    def momentum_2d(x1, x2, v1, v2):
        v1 = gamma * v1 + eta * 0.2 * x1
        v2 = gamma * v2 + eta * 4 * x2
        return x1 - v1, x2 - v2, v1, v2
    
    eta, gamma = 0.4, 0.5
    show_trace_2d(f_2d, train_2d(momentum_2d))
    

    可以看到,使用较小的学习率 η = 0.4 \eta=0.4 η=0.4和动量超参数 γ = 0.5 \gamma=0.5 γ=0.5

    • 动量法在竖直方向上的移动更加平滑
    • 且在水平方向上更快逼近最优解

    下面使用较大的学习率 η = 0.6 \eta=0.6 η=0.6,此时自变量也不再发散。

    eta = 0.6
    show_trace_2d(f_2d, train_2d(momentum_2d))
    

    指数加权移动平均

    为了从数学上理解动量法,需要先解释一下指数加权移动平均(exponentially weighted moving average)。

    • 给定超参数 0 ≤ γ < 1 0 \leq \gamma < 1 0γ<1
    • 当前时间步 t t t的变量 y t y_t yt
    • 上一时间步 t − 1 t-1 t1的变量 y t − 1 y_{t-1} yt1
    • 当前时间步另一变量 x t x_t xt

    当前时间步 t t t的变量 y t y_t yt是上一时间步 t − 1 t-1 t1的变量 y t − 1 y_{t-1} yt1与当前时间步另一变量 x t x_t xt的线性组合:

    y t = γ y t − 1 + ( 1 − γ ) x t . y_t = \gamma y_{t-1} + (1-\gamma) x_t. yt=γyt1+(1γ)xt.

    我们可以对 y t y_t yt展开:

    y t = ( 1 − γ ) x t + γ y t − 1 = ( 1 − γ ) x t + ( 1 − γ ) ⋅ γ x t − 1 + γ 2 y t − 2 = ( 1 − γ ) x t + ( 1 − γ ) ⋅ γ x t − 1 + ( 1 − γ ) ⋅ γ 2 x t − 2 + γ 3 y t − 3   … \begin{aligned} y_t &= (1-\gamma) x_t + \gamma y_{t-1}\\ &= (1-\gamma)x_t + (1-\gamma) \cdot \gamma x_{t-1} + \gamma^2y_{t-2}\\ &= (1-\gamma)x_t + (1-\gamma) \cdot \gamma x_{t-1} + (1-\gamma) \cdot \gamma^2x_{t-2} + \gamma^3y_{t-3}\ &\ldots \end{aligned} yt=(1γ)xt+γyt1=(1γ)xt+(1γ)γxt1+γ2yt2=(1γ)xt+(1γ)γxt1+(1γ)γ2xt2+γ3yt3 

    n = 1 / ( 1 − γ ) n = 1/(1-\gamma) n=1/(1γ),那么 ( 1 − 1 / n ) n = γ 1 / ( 1 − γ ) \left(1-1/n\right)^n = \gamma^{1/(1-\gamma)} (11/n)n=γ1/(1γ)。因为

    lim ⁡ n → ∞ ( 1 − 1 n ) n = exp ⁡ ( − 1 ) ≈ 0.3679 , \lim_{n \rightarrow \infty} \left(1-\frac{1}{n}\right)^n = \exp(-1) \approx 0.3679, nlim(1n1)n=exp(1)0.3679,

    所以当 γ → 1 \gamma \rightarrow 1 γ1时, γ 1 / ( 1 − γ ) = exp ⁡ ( − 1 ) \gamma^{1/(1-\gamma)}=\exp(-1) γ1/(1γ)=exp(1),如 0.9 5 20 ≈ exp ⁡ ( − 1 ) 0.95^{20} \approx \exp(-1) 0.9520exp(1)。如果把 exp ⁡ ( − 1 ) \exp(-1) exp(1)当作一个比较小的数,我们可以在近似中忽略所有含 γ 1 / ( 1 − γ ) \gamma^{1/(1-\gamma)} γ1/(1γ)和比 γ 1 / ( 1 − γ ) \gamma^{1/(1-\gamma)} γ1/(1γ)更高阶的系数的项。例如,当 γ = 0.95 \gamma=0.95 γ=0.95时,

    y t ≈ 0.05 ∑ i = 0 19 0.9 5 i x t − i . y_t \approx 0.05 \sum_{i=0}^{19} 0.95^i x_{t-i}. yt0.05i=0190.95ixti.

    因此,在实际中,我们常常将 y t y_t yt看作是对最近 1 / ( 1 − γ ) 1/(1-\gamma) 1/(1γ)个时间步的 x t x_t xt值的加权平均。例如,

    • γ = 0.95 \gamma = 0.95 γ=0.95时, y t y_t yt可以被看作对最近20个时间步的 x t x_t xt值的加权平均
    • γ = 0.9 \gamma = 0.9 γ=0.9时, y t y_t yt可以看作是对最近10个时间步的 x t x_t xt值的加权平均
    • 而且,离当前时间步 t t t越近的 x t x_t xt值获得的权重越大(越接近1)

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

    现在,我们对动量法的速度变量做变形:

    v t ← γ v t − 1 + ( 1 − γ ) ( η t 1 − γ g t ) . \boldsymbol{v}_t \leftarrow \gamma \boldsymbol{v}{t-1} + (1 - \gamma) \left(\frac{\eta_t}{1 - \gamma} \boldsymbol{g}_t\right). vtγvt1+(1γ)(1γηtgt).

    由指数加权移动平均的形式可得,速度变量 v t \boldsymbol{v}_t vt实际上对序列 { η t − i g t − i / ( 1 − γ ) : i = 0 , … , 1 / ( 1 − γ ) − 1 } \{{\eta_{t-i}\boldsymbol{g}_{t-i} /(1-\gamma):i=0,\ldots,1/(1-\gamma)-1}\} {ηtigti/(1γ):i=0,,1/(1γ)1}做了指数加权移动平均。

    • 换句话说,相比于小批量随机梯度下降,动量法在每个时间步的自变量更新量近似于将最近 1 / ( 1 − γ ) 1/(1-\gamma) 1/(1γ)个时间步的普通更新量(即学习率乘以梯度)做了指数加权移动平均后再除以 1 − γ 1-\gamma 1γ

    所以,在动量法中,自变量在各个方向上的移动幅度不仅取决当前梯度,还取决于过去的各个梯度在各个方向上是否一致

    之前的优化问题中,所有梯度在水平方向上为正(向右),而在竖直方向上时正(向上)时负(向下)。这样,我们就可以使用较大的学习率,从而使自变量向最优解更快移动。

    实现动量法

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

    def get_data_ch7():  
        data = np.genfromtxt('../../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个特征)
    
    def train_ch7(optimizer_fn, states, hyperparams, features, labels,
                  batch_size=10, num_epochs=2):
        # 初始化模型
        net, loss = linreg, squared_loss
        
        w = torch.nn.Parameter(torch.tensor(np.random.normal(0, 0.01, size=(features.shape[1], 1)), dtype=torch.float32),
                               requires_grad=True)
        b = torch.nn.Parameter(torch.zeros(1, dtype=torch.float32), requires_grad=True)
    
        def eval_loss():
            return loss(net(features, w, b), labels).mean().item()
    
        ls = [eval_loss()]
        data_iter = torch.utils.data.DataLoader(
            torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)
    
    features, labels = get_data_ch7()
    
    def init_momentum_states():
        v_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
        v_b = torch.zeros(1, dtype=torch.float32)
        return (v_w, v_b)
    
    def sgd_momentum(params, states, hyperparams):
        for p, v in zip(params, states):
            v.data = hyperparams['momentum'] * v.data + hyperparams['lr'] * p.grad.data
            p.data -= v.data
    

    先将动量超参数momentum设0.5,这时可以看成是特殊的小批量随机梯度下降:

    • 其小批量随机梯度为最近2个时间步的2倍小批量梯度的加权和。
    train_ch7(sgd_momentum, init_momentum_states(),
                  {'lr': 0.02, 'momentum': 0.5}, features, labels)
    

    将动量超参数momentum增大到0.9,这时依然可以看成是特殊的小批量随机梯度下降:

    • 其小批量随机梯度为最近10个时间步的10倍小批量梯度的加权和。我们先保持学习率0.02不变

    目标函数值在后期迭代过程中的变化不够平滑。直觉上,10倍小批量梯度比2倍小批量梯度大了5倍,试着将学习率减小到原来的1/5。此时目标函数值在下降了一段时间后变化更加平滑。

    train_ch7(sgd_momentum, init_momentum_states(),
                  {'lr': 0.004, 'momentum': 0.9}, features, labels)
    
    • 动量法使用了指数加权移动平均的思想。它将过去时间步的梯度做了加权平均,且权重按时间步指数衰减。
    • 动量法使得相邻时间步的自变量更新在方向上更加一致。
    展开全文
  • 动量法 使用梯度下降法,每次都会朝着目标函数下降最快的方向,这也称为最速下降法。这种更新方法看似非常快,实际上存在一些问题。 1. 梯度下降法的问题 考虑一个二维输入,[x1,x2][x_1, x_2][x1​,x2​],输出的...

    动量法

    使用梯度下降法,每次都会朝着目标函数下降最快的方向,这也称为最速下降法。这种更新方法看似非常快,实际上存在一些问题。

    1. 梯度下降法的问题

    考虑一个二维输入, [ x 1 , x 2 ] [x_1, x_2] [x1,x2],输出的损失函数 L : R 2 → R L: R^2 \rightarrow R L:R2R,下面是这个函数的等高线:

    在这里插入图片描述

    可以想象成一个很扁的漏斗,这样在竖直方向上,梯度就非常大,在水平方向上,梯度就相对较小,所以我们在设置学习率的时候就不能设置太大,为了防止竖直方向上参数更新太过了,这样一个较小的学习率又导致了水平方向上参数在更新的时候太过于缓慢,所以就导致最终收敛起来非常慢。

    2. 动量法

    动量法的提出就是为了应对这个问题,我们梯度下降法做一个修改如下:

    v i = γ v i − 1 + η ∇ L ( θ ) v_i = \gamma v_{i-1} + \eta \nabla L(\theta) vi=γvi1+ηL(θ)

    θ i = θ i − 1 − v i \theta _i = \theta_{i-1} - v_i θi=θi1vi

    其中 v i v_i vi 是当前速度, γ \gamma γ 是动量参数,是一个小于 1的正数, η \eta η 是学习率

    相当于每次在进行参数更新的时候,都会将之前的速度考虑进来,每个参数在各方向上的移动幅度不仅取决于当前的梯度,还取决于过去各个梯度在各个方向上是否一致,如果一个梯度一直沿着当前方向进行更新,那么每次更新的幅度就越来越大,如果一个梯度在一个方向上不断变化,那么其更新幅度就会被衰减,这样我们就可以使用一个较大的学习率,使得收敛更快,同时梯度比较大的方向就会因为动量的关系每次更新的幅度减少,如下图

    在这里插入图片描述

    比如我们的梯度每次都等于 g,而且方向都相同,那么动量法在该方向上使参数加速移动,有下面的公式:

    v 0 = 0 v_0 = 0 v0=0

    v 1 = γ v 0 + η g = η g v_1 = \gamma v_0 + \eta g = \eta g v1=γv0+ηg=ηg

    v 2 = γ v 1 + η g = ( 1 + γ ) η g v_2 = \gamma v_1 + \eta g = (1 + \gamma) \eta g v2=γv1+ηg=(1+γ)ηg

    v 3 = γ v 2 + η g = ( 1 + γ + γ 2 ) η g v_3 = \gamma v_2 + \eta g = (1 + \gamma + \gamma^2) \eta g v3=γv2+ηg=(1+γ+γ2)ηg

    ⋯ \cdots

    v + ∞ = ( 1 + γ + γ 2 + γ 3 + ⋯   ) η g = 1 1 − γ η g v_{+ \infty} = (1 + \gamma + \gamma^2 + \gamma^3 + \cdots) \eta g = \frac{1}{1 - \gamma} \eta g v+=(1+γ+γ2+γ3+)ηg=1γ1ηg

    如果我们把 γ \gamma γ 定为 0.9,那么更新幅度的峰值就是原本梯度乘学习率的 10 倍。

    本质上说,动量法就仿佛我们从高坡上推一个球,小球在向下滚动的过程中积累了动量,在途中也会变得越来越快,最后会达到一个峰值,对应于我们的算法中就是,动量项会沿着梯度指向方向相同的方向不断增大,对于梯度方向改变的方向逐渐减小,得到了更快的收敛速度以及更小的震荡。

    下面我们手动实现一个动量法,公式已经在上面了:

    def sgd_momentum(parameters, vs, lr, gamma):
        for param, v in zip(parameters, vs):
            v[:] = gamma * v + lr * param.grad.data
            param.data = param.data - v
    
    import numpy as np
    import torch
    from torch.utils.data import DataLoader
    from torchvision.datasets import MNIST
    import torch.nn as nn
    import torch.nn.functional as F
    
    import time
    import matplotlib.pyplot as plt
    %matplotlib inline
    
    def data_tf(x):
        x = np.array(x, dtype='float32') / 255
        x = (x - 0.5) / 0.5 # 标准化,这个技巧之后会讲到
        x = x.reshape((-1,)) # 拉平
        x = torch.from_numpy(x)
        return x
    
    # 使用MNIST数据集,没有的话需要下载
    train_set = MNIST('./data', train=True, transform=data_tf) # 载入数据集,申明定义的数据变换
    test_set = MNIST('./data', train=False, transform=data_tf)
    
    # 定义 loss 函数
    criterion = nn.CrossEntropyLoss()
    
    # DataLoader 对数据进行加载打包
    train_data = DataLoader(train_set, batch_size=64, shuffle=True)
    # 使用 Sequential 定义 3 层神经网络
    net = nn.Sequential(
        nn.Linear(784, 200),
        nn.ReLU(),
        nn.Linear(200, 10),
    )
    
    # 将速度初始化为和参数形状相同的零张量
    vs = []
    for param in net.parameters():
        vs.append(torch.zeros_like(param.data))
    
    # 开始训练
    losses = []
    
    start = time.time() # 记时开始
    for e in range(5):
        train_loss = 0
        for im, label in train_data:
    
            # 前向传播
            out = net(im)
            loss = criterion(out, label)
            # 反向传播
            net.zero_grad()
            loss.backward()
            sgd_momentum(net.parameters(), vs, 1e-2, 0.9) # 使用的动量参数为 0.9,学习率 0.01
            # 记录误差
            train_loss += loss.item()
    
            losses.append(loss.item())
        print('epoch: {}, Train Loss: {:.6f}'
              .format(e, train_loss / len(train_data)))
    end = time.time() # 计时结束
    print('使用时间: {:.5f} s'.format(end - start))
    
    epoch: 0, Train Loss: 0.363505
    epoch: 1, Train Loss: 0.173747
    epoch: 2, Train Loss: 0.124349
    epoch: 3, Train Loss: 0.101248
    epoch: 4, Train Loss: 0.084900
    使用时间: 21.00233 s
    

    可以看到,加完动量之后 loss 能下降非常快,但是一定要小心学习率和动量参数,这两个值会直接影响到参数每次更新的幅度,所以可以多试几个值

    当然,pytorch 内置了动量法的实现,非常简单,直接在torch.optim.SGD(momentum=0.9) 即可,下面实现一下

    train_data = DataLoader(train_set,batch_size = 64,shuffle=True)
    
    # 使用nn.Module定义3层网络
    
    class my_net(nn.Module):
        def __init__(self):
            super(my_net,self).__init__()
            self.L1 = nn.Linear(784,200)
            self.L2 = nn.Linear(200,10)
            
        def forward(self,x):
            x = F.relu(self.L1(x))
            x = F.relu(self.L2(x))
            return x
    
    net=my_net()
    print(net)
    
    my_net(
      (L1): Linear(in_features=784, out_features=200, bias=True)
      (L2): Linear(in_features=200, out_features=10, bias=True)
    )
    
    import torch.optim as optim
    optimizer=optim.SGD(net.parameters(),lr=1e-2,momentum=0.9)
    
    criterion = nn.CrossEntropyLoss()
    
    ## 开始训练
    losses = []
    idx=0
    start = time.time()# 计时开始
    for e in range(5):
        train_loss=0
        for im,label in train_data:
            # 前向传播
            out = net(im)
            loss = criterion(out,label)
            
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
            # 记录误差
            train_loss += loss.item()
            if idx%30==0: # 30步记录一次
                losses.append(loss.item())
            idx += 1
            
        print(f'epoch:{e},Train Loss:{train_loss/len(train_data):.6f}')
    end = time.time()
    print(f'使用时间:{end-start:.6f}s')
    
    epoch:0,Train Loss:0.253888
    epoch:1,Train Loss:0.236733
    epoch:2,Train Loss:0.221444
    epoch:3,Train Loss:0.206973
    epoch:4,Train Loss:0.193939
    使用时间:19.242222s
    
    x_axis = np.linspace(0, 5, len(losses), endpoint=True)
    plt.semilogy(x_axis, losses, label='momentum: 0.9')
    plt.legend(loc='best')
    
    <matplotlib.legend.Legend at 0x19ab8e64808>
    

    在这里插入图片描述

    我们可以对比一下不加动量的随机梯度下降法

    # 使用 Sequential 定义 3 层神经网络
    net = nn.Sequential(
        nn.Linear(784, 200),
        nn.ReLU(),
        nn.Linear(200, 10),
    )
    
    optimizer = torch.optim.SGD(net.parameters(), lr=1e-2) # 不加动量
    # 开始训练
    losses1 = []
    idx = 0
    start = time.time() # 记时开始
    for e in range(5):
        train_loss = 0
        for im, label in train_data:
    
            # 前向传播
            out = net(im)
            loss = criterion(out, label)
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # 记录误差
            train_loss += loss.item()
            if idx % 30 == 0: # 30 步记录一次
                losses1.append(loss.item())
            idx += 1
        print('epoch: {}, Train Loss: {:.6f}'
              .format(e, train_loss / len(train_data)))
    end = time.time() # 计时结束
    print('使用时间: {:.5f} s'.format(end - start))
    
    epoch: 0, Train Loss: 0.748911
    epoch: 1, Train Loss: 0.366224
    epoch: 2, Train Loss: 0.320932
    epoch: 3, Train Loss: 0.294529
    epoch: 4, Train Loss: 0.272778
    使用时间: 18.94132 s
    

    补充:格式化输出

    a= 1.23333233313

    • print(‘a:{:.6f}’.format(a))
    • print(f’a:{a:.6f}’)
      两种输出形式是一样的
    x_axis = np.linspace(0, 5, len(losses), endpoint=True)
    plt.semilogy(x_axis, losses, label='momentum: 0.9')
    plt.semilogy(x_axis, losses1, label='no momentum')
    plt.legend(loc='best')
    
    <matplotlib.legend.Legend at 0x19aba401048>
    

    在这里插入图片描述

    可以看到加完动量之后的 loss 下降的程度更低了,可以将动量理解为一种惯性作用,所以每次更新的幅度都会比不加动量的情况更多。

    参考:PyTorch中文手册

    展开全文
  • Pytorch Note16 优化算法2 动量法(Momentum) 文章目录Pytorch Note16 优化算法2 动量法(Momentum)动量法(Momentum)梯度下降法的问题MomentumNesterov Accelerated Gradient代码从0实现pytorch 内置优化器对比 动量 +...

    Pytorch Note16 优化算法2 动量法(Momentum)


    全部笔记的汇总贴: Pytorch Note 快乐星球

    动量法(Momentum)

    SGD 在 ravines 的情况下容易被困住, ravines 就是曲面的一个方向比另一个方向更陡,这时 SGD 会发生震荡而迟迟不能接近极小值:
    在这里插入图片描述

    Momentum

    考虑一个二维输入, [ x 1 , x 2 ] [x_1, x_2] [x1,x2],输出的损失函数 L : R 2 → R L: R^2 \rightarrow R L:R2R,下面是这个函数的等高线

    可以想象成一个很扁的漏斗,这样在竖直方向上,梯度就非常大,在水平方向上,梯度就相对较小,所以我们在设置学习率的时候就不能设置太大,为了防止竖直方向上参数更新太过了,这样一个较小的学习率又导致了水平方向上参数在更新的时候太过于缓慢,所以就导致最终收敛起来非常慢。

    动量法的提出就是为了应对这个问题,我们梯度下降法做一个修改如下,可以加速 SGD, 并且抑制震荡
    v i = γ v i − 1 + η ∇ L ( θ ) v_i = \gamma v_{i-1} + \eta \nabla L(\theta) vi=γvi1+ηL(θ)

    θ i = θ i − 1 − v i \theta_i = \theta_{i-1} - v_i θi=θi1vi

    其中 v i v_i vi 是当前速度, γ \gamma γ 是动量参数,是一个小于 1的正数, η \eta η 是学习率

    相当于每次在进行参数更新的时候,都会将之前的速度考虑进来,每个参数在各方向上的移动幅度不仅取决于当前的梯度,还取决于过去各个梯度在各个方向上是否一致,如果一个梯度一直沿着当前方向进行更新,那么每次更新的幅度就越来越大,如果一个梯度在一个方向上不断变化,那么其更新幅度就会被衰减,这样我们就可以使用一个较大的学习率,使得收敛更快,同时梯度比较大的方向就会因为动量的关系每次更新的幅度减少

    比如我们的梯度每次都等于 g,而且方向都相同,那么动量法在该方向上使参数加速移动,有下面的公式:

    v 0 = 0 v_0 = 0 v0=0

    v 1 = γ v 0 + η g = η g v_1 = \gamma v_0 + \eta g = \eta g v1=γv0+ηg=ηg

    v 2 = γ v 1 + η g = ( 1 + γ ) η g v_2 = \gamma v_1 + \eta g = (1 + \gamma) \eta g v2=γv1+ηg=(1+γ)ηg

    v 3 = γ v 2 + η g = ( 1 + γ + γ 2 ) η g v_3 = \gamma v_2 + \eta g = (1 + \gamma + \gamma^2) \eta g v3=γv2+ηg=(1+γ+γ2)ηg

    ⋯ \cdots

    v + ∞ = ( 1 + γ + γ 2 + γ 3 + ⋯   ) η g = 1 1 − γ η g v_{+ \infty} = (1 + \gamma + \gamma^2 + \gamma^3 + \cdots) \eta g = \frac{1}{1 - \gamma} \eta g v+=(1+γ+γ2+γ3+)ηg=1γ1ηg

    如果我们把 γ \gamma γ 定为 0.9,那么更新幅度的峰值就是原本梯度乘学习率的 10 倍。

    一般来说, 一般 γ 取值 0.9 左右。

    本质上说,动量法就仿佛我们从高坡上推一个球,小球在向下滚动的过程中积累了动量,在途中也会变得越来越快,最后会达到一个峰值,对应于我们的算法中就是,动量项会沿着梯度指向方向相同的方向不断增大,对于梯度方向改变的方向逐渐减小,得到了更快的收敛速度以及更小的震荡。

    在这里插入图片描述

    但是这种情况相当于小球从山上滚下来时是在盲目地沿着坡滚,如果它能具备一些先知,例如快要上坡时,就知道需要减速了的话,适应性会更好。所以我们就提出了Nesterov Accelerated Gradient。

    Nesterov Accelerated Gradient

    Nesterov Accelerated Gradient(NAG)是一种为我们的动量提供这种先见之明的方法。我们知道我们会用 γ v t − 1 γv{t-1} γvt1去更更新我们的参数 θ。因此,在我们计算 θ − γ v t − 1 θ - γv{t-1} θγvt1为我们提供了参数下一个位置的近似值,就可以通过计算梯度来有效的向前看。

    简单来说在计算梯度时,不是在当前位置,而是未来的位置上

    img

    一般来说, 一般 γ 还是取值 0.9 左右。

    img

    蓝色是 Momentum 的过程,会先计算当前的梯度,然后在更新后的累积梯度后会有一个大的跳跃。
    而 NAG 会先在前一步的累积梯度上(brown vector)有一个大的跳跃,然后衡量一下梯度做一下修正(red vector),这种预期的更新可以避免我们走的太快。

    NAG 可以使 RNN 在很多任务上有更好的表现。

    目前为止,我们可以做到,在更新梯度时顺应 loss function 的梯度来调整速度,并且对 SGD 进行加速

    代码从0实现

    我们还是利用minst的数据

    import numpy as np
    import torch
    from torchvision.datasets import MNIST # 导入 pytorch 内置的 mnist 数据
    from torch.utils.data import DataLoader
    from torch import nn
    from torch.autograd import Variable
    import time
    import matplotlib.pyplot as plt
    %matplotlib inline
    
    def data_tf(x):
        x = np.array(x, dtype='float32') / 255
        x = (x - 0.5) / 0.5 # 标准化,这个技巧之后会讲到
        x = x.reshape((-1,)) # 拉平
        x = torch.from_numpy(x)
        return x
    
    train_set = MNIST('./data', train=True, transform=data_tf, download=True) # 载入数据集,申明定义的数据变换
    test_set = MNIST('./data', train=False, transform=data_tf, download=True)
    
    # 定义 loss 函数
    criterion = nn.CrossEntropyLoss()
    

    公式上面已经给了,现在手动实现一下动量法

    def sgd_momentum(parameters, vs, lr, gamma):
        for param, v in zip(parameters, vs):
            v[:] = gamma * v + lr * param.grad.data
            param.data = param.data - v
    
    train_data = DataLoader(train_set, batch_size=64, shuffle=True)
    # 使用 Sequential 定义 3 层神经网络
    net = nn.Sequential(
        nn.Linear(784, 200),
        nn.ReLU(),
        nn.Linear(200, 10),
    )
    
    # 将速度初始化为和参数形状相同的零张量
    vs = []
    for param in net.parameters():
        vs.append(torch.zeros_like(param.data))
        
    # 开始训练
    losses = []
    idx = 0
    start = time.time() # 记时开始
    for e in range(5):
        train_loss = 0
        for im, label in train_data:
            im = Variable(im)
            label = Variable(label)
            # 前向传播
            out = net(im)
            loss = criterion(out, label)
            # 反向传播
            net.zero_grad()
            loss.backward()
            sgd_momentum(net.parameters(), vs, 1e-2, 0.9) # 使用的动量参数为 0.9,学习率 0.01
            
            train_loss += loss.data
            # 记录误差
            if idx % 30 == 0: # 30 步记录一次
                losses.append(loss.data)
            idx += 1
        print('epoch: {}, Train Loss: {:.6f}'
              .format(e, train_loss / len(train_data)))
    end = time.time() # 计时结束
    print('使用时间: {:.5f} s'.format(end - start))
    
    epoch: 0, Train Loss: 0.369268
    epoch: 1, Train Loss: 0.179249
    epoch: 2, Train Loss: 0.127735
    epoch: 3, Train Loss: 0.102495
    epoch: 4, Train Loss: 0.086471
    使用时间: 30.78685 s
    
    x_axis = np.linspace(0, 5, len(losses), endpoint=True)
    plt.semilogy(x_axis, losses, label='momentum: 0.9')
    plt.legend(loc='best')
    

    在这里插入图片描述

    可以看到,加完动量之后 loss 能下降非常快,但是一定要小心学习率和动量参数,这两个值会直接影响到参数每次更新的幅度,所以可以多试几个值

    pytorch 内置优化器

    当然,pytorch 内置了动量法的实现,非常简单,直接在 torch.optim.SGD(momentum=0.9) 即可,下面实现一下

    train_data = DataLoader(train_set, batch_size=64, shuffle=True)
    # 使用 Sequential 定义 3 层神经网络
    net = nn.Sequential(
        nn.Linear(784, 200),
        nn.ReLU(),
        nn.Linear(200, 10),
    )
    
    optimizer = torch.optim.SGD(net.parameters(), lr=1e-2, momentum=0.9) # 加动量
    # 开始训练
    losses = []
    idx = 0
    start = time.time() # 记时开始
    for e in range(5):
        train_loss = 0
        for im, label in train_data:
            im = Variable(im)
            label = Variable(label)
            # 前向传播
            out = net(im)
            loss = criterion(out, label)
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # 记录误差
            train_loss += loss.data
            if idx % 30 == 0: # 30 步记录一次
                losses.append(loss.data)
            idx += 1
        print('epoch: {}, Train Loss: {:.6f}'
              .format(e, train_loss / len(train_data)))
    end = time.time() # 计时结束
    print('使用时间: {:.5f} s'.format(end - start))
    
    epoch: 0, Train Loss: 0.368348
    epoch: 1, Train Loss: 0.172883
    epoch: 2, Train Loss: 0.126296
    epoch: 3, Train Loss: 0.100889
    epoch: 4, Train Loss: 0.085021
    使用时间: 27.03662 s
    
    x_axis = np.linspace(0, 5, len(losses), endpoint=True)
    plt.semilogy(x_axis, losses, label='momentum: 0.9')
    plt.legend(loc='best')
    

    在这里插入图片描述

    对比 动量 + 不加动量 的 SGD

    我们可以对比一下不加动量的随机梯度下降法

    # 使用 Sequential 定义 3 层神经网络
    net = nn.Sequential(
        nn.Linear(784, 200),
        nn.ReLU(),
        nn.Linear(200, 10),
    )
    
    optimizer = torch.optim.SGD(net.parameters(), lr=1e-2) # 不加动量
    # 开始训练
    losses1 = []
    idx = 0
    start = time.time() # 记时开始
    for e in range(5):
        train_loss = 0
        for im, label in train_data:
            im = Variable(im)
            label = Variable(label)
            # 前向传播
            out = net(im)
            loss = criterion(out, label)
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # 记录误差
            train_loss += loss.data
            if idx % 30 == 0: # 30 步记录一次
                losses1.append(loss.data)
            idx += 1
        print('epoch: {}, Train Loss: {:.6f}'
              .format(e, train_loss / len(train_data)))
    end = time.time() # 计时结束
    print('使用时间: {:.5f} s'.format(end - start))
    
    epoch: 0, Train Loss: 0.745222
    epoch: 1, Train Loss: 0.364928
    epoch: 2, Train Loss: 0.318353
    epoch: 3, Train Loss: 0.291162
    epoch: 4, Train Loss: 0.268610
    使用时间: 25.78207 s
    
    x_axis = np.linspace(0, 5, len(losses), endpoint=True)
    plt.semilogy(x_axis, losses, label='momentum: 0.9')
    plt.semilogy(x_axis, losses1, label='no momentum')
    plt.legend(loc='best')
    

    在这里插入图片描述

    可以看到加完动量之后的 loss 下降的程度更低了,可以将动量理解为一种惯性作用,所以每次更新的幅度都会比不加动量的情况更多

    可视化

    正常梯度下降迭代100次求解

    在这里插入图片描述

    加了动量的梯度下降迭代50次

    在这里插入图片描述

    下一章传送门:Note17 优化算法3 Adagrad算法

    展开全文
  • 动量法

    2018-12-18 17:08:00
    之前有讨论过梯度下降法: 参数迭代 于是会产生问题,学习参数过小...那么动量法呢? 简单地将梯度下降公式增加一个动量V,迭代公式如下: %matplotlib inline from mxnet import nd import numpy ...

    之前有讨论过梯度下降法:

     

    参数迭代

     

    于是会产生问题,学习参数过小,模型很难到达最优点,而参数过大,某个参数会发散。

     

    小批量随机梯度下降也讨论过了(线性回归的公式如下):

     

    那么动量法呢?

     

    简单地将梯度下降公式增加一个动量V,迭代公式如下:

     

    %matplotlib inline
    from mxnet import nd
    import numpy as np
    import gluonbook as gb
    
    def f_2d(x1,x2):
        return 0.1*x1**2 + 2*x2**2
    
    eta  = 0.4
    
    def gd_2d(x1,x2,s1,s2):
        return (x1 - eta *0.2*x1,x2-eta*4*x2,0,0)
    
    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)
            results.append((x1,x2))
        print('epoch %d, x1 %f, x2 %f' % (i + 1, x1, x2))
        return results
    
    def show_trace_2d(f,results):
        gb.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))
        gb.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
        gb.plt.xlabel('x1')
        gb.plt.ylabel('x2')
    
    
    def momentum_2d(x1,x2,v1,v2):
        v1 = gamma * v1 + eta * 0.2 * x1
        v2 = gamma * v2 + eta * 4 * x2
        return x1 - v1,x2-v2,v1,v2
    
    
    eta = 0.4
    gamma = 0.5
    
    show_trace_2d(f_2d,train_2d(momentum_2d))

    eta = 0.6
    show_trace_2d(f_2d,train_2d(momentum_2d))

    原理:

    当前阶段 t (时间步t)的变量 yt 是上一个阶段 t-1 的变量 yt-1 与当前阶段的另一个变量xt的线性组合:

     

    对yt展开:

     容易知道(高等数学求极限):

     

    当gama 趋于 1 时,如0.95,也就是说:

    即:

    因此,常常将yt看做对最近 1/(1-gama) 个时间步的 xt 值得加权平均。例如,当 γ=0.95 时,yt 可以被看作是对最近 20 个时间步的 xt 值的加权平均;

    当 γ=0.9 时,yt 可以看作是对最近 10 个时间步的 xt 值的加权平均。而且,离当前时间步 t 越近的 xt 值获得的权重越大(越接近 1)。

     

    对动量法做同样的变形:

    可以同样展开,即对序列

    做了指数加权移动平均。

    相比于小批量随机梯度下降,动量法每个阶段的自变量更新量近似于前者对应的最近1/1-gama个阶段做指数加权平均移动后除以1-gama。

     

     动量法:

    就是每次状态转移时,不仅取决于当前梯度,并且要取决于过去的各个梯度在各个方向上是否一致

     

    转载于:https://www.cnblogs.com/TreeDream/p/10138427.html

    展开全文
  • 深度学习---动量法

    2019-12-26 18:30:56
    损失函数有关自变量的梯度代表了损失函数在自变量当前位置下降最快的方向。...所以在动量法中,自变量在各个方向上的移动幅度不仅取决于当前梯度,还取决于过去的各个梯度在各个方向上是否一致。
  • Nesterov牛顿动量法

    2020-11-24 01:31:10
    先上结论: 1.Nesterov是Momentum的变种。 2.与Momentum唯一区别就是,计算梯度的不同,Nesterov先用当前的速度v更新一遍参数,在用更新的临时参数计算梯度。 3.相当于添加了矫正因子的Momentum。...
  • 动量法 梯度下降存在问题,因为是对整个梯度用学习率做的衰减和增强,所以所有的梯度分量都享受同一个权重学习率,容易造成有些分量衰减的过于缓慢,有些分量震荡的剧烈到最后发散的可能 动量法在一定程度上能解决...
  • 移动平均   移动平均,它的思想是根据时间序列资料,逐项递推,依次计算包含一定项数的平均值,用以反应长期趋势,即用一组最近的实际数据值来预测未来的值的一种方法。   简单移动平均各个元素的权重相等,...
  • 动量法 1.动量法 动量是物理学中的概念,一般指物体在它运动方向上保持运动的趋势,是该物体质量和速度的乘积。 在深度学习中,动量法是用之前积累动量来代替真正的梯度,这样,每个参数实际更新差值取决于最近一段...
  • 优化法——动量法

    2021-03-13 18:25:04
    动量梯度下降(Gradient Descent With Momentum),简称为动量方法(Momentum),运行速度几乎总是快于标准的梯度下降算法,并且能够解决随机梯度下降所遇到的山谷震荡以及鞍部停滞问题,这部分内容请阅读上一篇博客...
  • q: 为什么要用动量法 a: 梯度下降存在以下问题: 一个输入和输出分别为二维向量x=[x1,x2]T和 标量的目标函数f(x)=0.1x12 + 2x22 基于这个目标函数的梯度下降,并演示使用学习率为0.40.4时自变量的迭代轨迹。 import ...
  • 优化算法-momentum(动量法)

    千次阅读 2020-03-13 21:53:38
    动量法 基于指数加权移动平均的思想,提出了动量法予以解决上述问题。设时间步 ttt的自变量为 xt\boldsymbol{x}_txt​,学习率为 ηt\eta_tηt​。 在时间步 000,动量法创建速度变量 v0\boldsymbol{v}_0v0​,并将...
  • “随机梯度下降、牛顿法、动量法、Nesterov、AdaGrad、RMSprop、Adam”随机梯度下降法牛顿法动量法Nesterov学习率应该慢慢减小的。AdaGradRMSpropAdamNadam 随机梯度下降法 怎么减小每次计算的参数量? 梯度下降法性...
  • Nesterov Momentum牛顿动量法

    千次阅读 2018-07-30 00:02:53
    需要:学习速率 ϵ, 初始参数 θ, 初始速率v, 动量衰减参数α 每步迭代过程:  1. 从训练集中的随机抽取一批容量为m的样本{x1,…,xm},以及相关的输出yi 2. 计算梯度和误差,并更新速度v和参数θ:    注意在估...
  • Momentum动量法

    2018-07-30 00:02:31
    Momentum算法借用了物理中的动量概念,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,可以在一定程度上增加稳定性,从而学习地...
  • 《python深度学习》对于动量法的解释 动量解决了 SGD 的两个问题:收敛速度和局部极小点。 图 2-13 给出了损失作为网络参数的函数的曲线。 如你所见,在某个参数值附近,有一个局部极小点(local minimum):在...
  • 在 小批量梯度下降中,如果每次选取样本数量比较小,损失会呈现震荡的方式下降。 动量是模拟物理中的概念。一般而言,一个物体的动量指的是这个物体在它运动...动量法移动公式 为时间步 t 的小批量数据上⽬标函数.
  • pytorch学习:动量法momentum

    千次阅读 2018-09-02 21:06:02
    关于动量法的原理这里不写了,参考别的文章:https://blog.csdn.net/tsyccnh/article/details/76270707 以下是代码实现: # -*- coding: utf-8 -*- """ Created on Sun Sep 2 15:54:06 2018 @...
  • 动量法中,自变量在各个方向上的移动幅度不仅取决当前梯度,还取决于过去的各个梯度在各个方向上是否一致。这样,我们就可以使用较大的学习率,从而使自变量向最优解更快移动。 动量法的代码实现: def ...
  • 深度学习优化函数详解(4)-- momentum 动量法

    万次阅读 多人点赞 2017-07-28 17:36:34
    深度学习优化函数详解系列目录 深度学习优化函数详解(0)– 线性回归问题 深度学习优化函数详解(1)– Gradient Descent... 深度学习优化函数详解(4)– momentum 动量法 深度学习优化函数详解(5)– Neste...
  • 随机梯度下降的动量法 Momentum

    千次阅读 2019-05-21 18:45:10
    转自:https://blog.csdn.net/BVL10101111/article/details/72615621 (下面选中的这句话很直观)
  • 动量法(Momentum) 其中是一阶动量,是动量因子。 优点:改善“之字形”震荡,动量项在梯度指向方向相同的方向逐渐增大,对梯度指向改变的方向逐渐减小(不断中和),一定程度可以靠惯性冲出局部低点。 3. ...
  • 实现BP神经网络自动控制PID三个参数,并用附加动量项和自学习速率来改进BP神经网络容易掉入局部极小值的缺点
  • 对比一下梯度下降动量梯度下降 梯度下降 初始化参数 θ0\theta^0θ0 计算梯度 g0g^0g0 更新参数 θ1=θ0−ηg0\theta^1 = \theta^0-\eta g^0θ1=θ0−ηg0 计算梯度 g1g^1g1 更新参数 θ2=θ1−ηg1\theta^...
  • 在训练模型时,会使⽤优化算法不断迭代模型参数以降低模型损失函数的值,本节详细学习常用的优化算法:梯度下降、动量法、AdaGrad算法、RMSProp算法、AdaDelta算法、Adam算法
  • 动量法。。。。

    2021-06-16 08:32:01
    参考:深度学习优化函数详解
  • 标准BP神经网络算法和附加动量法在沉降监测中的应用研究.pdf
  • 综合动量法和可变学习速度的BP神经网络地震初至拾取.pdf

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,430
精华内容 3,772
关键字:

动量法