深度学习中的优化算法和策略

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Shingle_/article/details/82355260

绝大多数深度学习中的目标函数都很复杂。因此,很多优化问题并不存在解析解,而需要使用基于数值方法的优化算法找到近似解。这类优化算法一般通过不断迭代更新解的数值来找到近似解。我们讨论的优化算法都是这类基于数值方法的算法。

非凸优化的阻碍:局部最小值鞍点

梯度下降

批量梯度下降 (batch gradient descent)

在每一轮自变量迭代里,梯度下降使用整个训练数据集来计算梯度

x x η f ( x )

f ( x ) = 1 n i = 1 n f i ( x )

f ( x ) = 1 n i = 1 n f i ( x )

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

随机梯度下降(stochastic gradient descent)

每次只随机采样一个样本来计算梯度

x x η f i ( x )

E i f i ( x ) = 1 n i = 1 n f i ( x ) = f ( x )

每次迭代的开销从梯度下降的 O ( n ) 降到了常数 O ( 1 )

随机梯度下降的更新轨迹相对于梯度下降更加曲折,实际中,它来自样本的噪音使得梯度的准确度下降,所以在使用同样的超参数的情况下,随机梯度下降收敛到的值相对梯度下降来说更最优值远。但因为随机梯度下降每一次迭代的计算比梯度下降更加简单,在同样运行时间下,随机梯度下降可以进行更多次的自变量迭代,它最终得到的解的质量可能会比梯度下降更优。

小批量随机梯度下降(mini-batch stochastic gradient descent)

深度学习训练中实际使用,每一轮迭代里我们随机采样多个样本来组成一个小批量(mini-batch),然后对它计算梯度

x t x t 1 η t g t

g t f B t ( x t 1 ) = 1 | B t | i B t f i ( x t 1 )

小批量随机梯度下降中每次迭代的计算开销为 O(|Bt|)

  • 当批量大小为 1 时,该算法即随机梯度下降;
  • 当批量大小等于训练数据样本数时,该算法即梯度下降。
  • 当批量较小时,每次迭代中使用的样本少,这会导致并行处理和内存使用效率变低。这使得在计算同样数目样本的情况下比使用更大批量时所花时间更多。
  • 当批量较大时,每个小批量梯度里可能含有更多的冗余信息,且在处理了同样多样本的情况下,它比批量较小的情况下对自变量的迭代次数更少,这两个因素共同导致了它靠近解的速度更慢。
  • 为了得到较好的解,批量较大时比批量较小时可能需要计算更多数目的样本。因此批量大小是一个重要的用来权衡计算效率和靠近解速度的超参数。

可以通过调整的批量大小来权衡计算效率和训练误差下降速度。


深度学习常使用的优化算法均基于小批量随机梯度下降,但区别在于如何使用梯度来更新自变量。这些改进算法主要基于两个技术:使用指数加权移动平均来平滑时间步之间的变化,和对每个自变量元素使用自适应的学习率。

动量法(momentum)

在时间步 0,动量法创建速度变量 v 0 R d ,并将其元素初始化成 0。在时间步 t > 0 ,动量法对每次迭代的步骤做如下修改:

v t γ v t 1 + η t g t

x t x t 1 v t

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

def sgd_momentum(params, states, hyperparams):
    hp = hyperparams
    for p, v in zip(params, states):
        v[:] = hp['mom'] * v + hp['lr'] * p.grad
        p[:] -= v

指数加权移动平均(exponentially weighted moving average, EMA)

y t = γ y t 1 + ( 1 γ ) x t

注意:存在冷启动问题,初始状态初始化为0,不断地用0来做平均,拖后腿

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

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

v t γ v t 1 + ( 1 γ ) ( η t 1 γ g t )

由指数加权移动平均的形式可得,速度变量 v t 实际上对序列做了指数加权移动平均,相比于小批量随机梯度下降,动量法在每个时间步的自变量更新量近似于将前者对应的最近 1 / ( 1 γ ) 个时间步的更新量做了指数加权移动平均后再除以 1 γ

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

重点

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

自适应学习率算法

Adagrad

据自变量在每个维度的梯度值的大小来调整各个维度上的学习率,从而避免统一的学习率难以适应所有维度的问题。

在时间步 0,adagrad 将 s 0 中每个元素初始化为 0。在每次迭代中,首先将梯度 g t 按元素平方累加到变量 s t

s t s t + g t g t

x t x t 1 η t s t + ϵ g t

ϵ 是为了维持数值稳定性而添加的常数,例如 10 6 。这里开方、除法和乘法的运算都是按元素进行的。这些按元素运算使得目标函数自变量中每个元素都分别拥有自己的学习率。

def adagrad(params, states, hyperparams):
    hp, eps = hyperparams, 1e-6
    for p, s in zip(params, states):
        s[:] += p.grad.square()
        p[:] -= hp['lr'] * p.grad / (s + eps).sqrt()

重点

小批量随机梯度按元素平方的累加变量 s 出现在学习率的分母项中。

  • 如果目标函数有关自变量中某个元素的偏导数一直都较大,那么就让该元素的学习率下降快一点;
  • 如果目标函数有关自变量中某个元素的偏导数一直都较小,那么就让该元素的学习率下降慢一点。
  • 由于 s 一直在累加按元素平方的梯度,自变量中每个元素的学习率在迭代过程中一直在降低(或不变)。所以,当学习率在迭代早期降得较快且当前解依然不佳时,Adagrad 在迭代后期由于学习率过小,可能较难找到一个有用的解。

RMSProp

RMSProp 算法对 Adagrad 做了一点小小的修改,RMSProp 将过去时间步里梯度按元素平方做指数加权移动平均

s t γ s t 1 + ( 1 γ ) g t g t

x t x t 1 η t s t + ϵ g t

因为 RMSProp 的状态变量是对平方项 g t g t 的指数加权移动平均,因此可以看作是最近 1 / ( 1 γ ) 个时刻的梯度平方项的加权平均,这样自变量每个元素的学习率在迭代过程中避免了“直降不升”的问题。

def rmsprop(params, states, hyperparams):
    hp, eps = hyperparams, 1e-6
    for p, s in zip(params, states):
        s[:] = hp['gamma'] * s + (1 - hp['gamma']) * p.grad.square()
        p[:] -= hp['lr'] * p.grad / (s + eps).sqrt()

Adadelta

s t ρ s t 1 + ( 1 ρ ) g t g t

g t Δ x t 1 + ϵ s t + ϵ g t

x t x t 1 g t

Δ x t ρ Δ x t 1 + ( 1 ρ ) g t g t

Adadelta 跟 RMSProp 不同的地方在于使用 Δ x t 来替代了超参数 η t ,因此它的主要优势在于不需要手动选取学习率

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.square()
        g = ((delta + eps).sqrt() / (s + eps).sqrt()) * p.grad
        p[:] -= g
        delta[:] = rho * delta + (1 - rho) * g * g

Adam

Adam 是一个对 RMSProp 的改进算法。

  • 动量变量,对梯度做了指数加权移动平均(Momentum)
  • 梯度按元素平方做指数加权移动平均,(RMSProp)
  • 偏差修正:使得过去各时刻权值之和为 1,解决EMA的冷启动问题。

v t β 1 v t 1 + ( 1 β 1 ) g t

v t v t 1 β 1 t

s t β 2 s t 1 + ( 1 β 2 ) g t g t

s t s t 1 β 2 t

x t x t 1 η s t + ϵ v t

作者建议β1=0.9,β2=0.999

def adam(params, states, hyperparams):
    hp, beta1, beta2, eps = hyperparams, 0.9, 0.999, 1e-6
    for p, (v, s) in zip(params, states):
        v[:] = beta1 * v + (1 - beta1) * p.grad
        s[:] = beta2 * s + (1 - beta2) * p.grad.square()
        v_bias_corr = v / (1 - beta1 ** hp['t'])
        s_bias_corr = s / (1 - beta2 ** hp['t'])
        p[:] -= hp['lr'] * v_bias_corr / (s_bias_corr.sqrt() + eps)
    hp['t'] += 1

重点

  • Adam 在 RMSProp 基础上对梯度也做了指数加权移动平均。
  • Adam 使用了偏差修正。

参数初始化

初始参数需要在不同单元间“破坏对称性”。

通常情况下,使用高斯或均匀分布中随机抽取的值初始化权重(随机初始化权重)。

有些启发式方法可以用于选择权重的初始大小。一种初始化m个输入和n个输出的全连接层的权重的启发式方法是从分布 U ( 1 / s q r t ( m ) , 1 / s q r t ( m ) ) 中采样权重。

Glorot and Bengio(2010)建议使用标准初始化:

W i , j U ( 6 m + n , 6 m + n )

设置偏置的方法必须和设置权重的方法协调,通常设置偏置为0是可行的。


批标准化(Batch Normalization)

设全连接层的输入为 u,权重参数和偏差参数分别为 W 和 b,激活函数为 ϕ。设批量归一化的操作符为 BN。那么,使用批量归一化的全连接层的输出为

ϕ ( B N ( x ) )

其中批量归一化输入 x 由仿射变换

x = W u + b

得到。考虑一个由 m 个样本组成的小批量,仿射变换的输出为一个新的小批量 B = x ( 1 ) , , x ( m ) 。它们正是批量归一化层的输入。对于小批量 B 中任意样本 x ( i ) , 1 i m ,批量归一化层的输出

y ( i ) = B N ( x ( i ) )

由以下几步求得。首先,对小批量 B 求均值和方差:

μ B = 1 m i = 1 m x ( i ) ,

σ B 2 = 1 m i = 1 m ( x ( i ) μ B ) 2 ,

其中的平方计算是按元素求平方。接下来,我们使用按元素开方和按元素除法对 x(i) 标准化:

x ^ ( i ) = x ( i ) μ B σ B 2 + ϵ

这里 ϵ>0 是一个很小的常数,保证分母大于 0。在上面标准化的基础上,批量归一化层引入了两个可以学习的模型参数,拉升(scale)参数 γ 和偏移(shift)参数 β。这两个参数和 x(i) 形状相同,并与 x(i) 分别做按元素乘法(符号 ⊙)和加法计算:

y ^ ( i ) = γ x ^ ( i ) + β .

至此,我们得到了 x(i) 的批量归一化的输出 y(i)。 值得注意的是,可学习的拉升和偏移参数保留了不对 x^(i) 做批量归一化的可能:我们可以对此这样理解,如果批量归一化无益,理论上学出的模型可以不使用批量归一化。

def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 通过 autograd 来获取是不是在训练模式下。
    if not autograd.is_training():
        # 如果是在预测模式下,直接使用传入的移动平滑均值和方差。
        X_hat = (X - moving_mean) / nd.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        # 使用全连接层的情况,计算特征维上的均值和方差。
        if len(X.shape) == 2:
            mean = X.mean(axis=0)
            var = ((X - mean) ** 2).mean(axis=0)
        # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。这里我们需要保持 X
        # 的形状以便后面可以正常的做广播运算。
        else:
            mean = X.mean(axis=(0, 2, 3), keepdims=True)
            var = ((X - mean) ** 2).mean(axis=(0, 2, 3), keepdims=True)
        # 训练模式下用当前的均值和方差做标准化。
        X_hat = (X - mean) / nd.sqrt(var + eps)
        # 更新移动平滑均值和方差。
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
    # 拉升和偏移。
    Y = gamma * X_hat + beta
    return Y, moving_mean, moving_var

对于全连接的2D输入,对每个特征在样本间的均值转变成0,方差变为1。

对于卷积,对每个chennal,均值变成0,方差变为1。

class BatchNorm(nn.Block):
    def __init__(self, num_features, num_dims, **kwargs):
        super(BatchNorm, self).__init__(**kwargs)
        shape = (1, num_features) if num_dims == 2 else (1, num_features, 1,
                                                         1)
        # 参与求导和更新的模型参数,分别初始化成 0 和 1。
        self.gamma = self.params.get('gamma', shape=shape, init=init.One())
        self.beta = self.params.get('beta', shape=shape, init=init.Zero())
        # 不参与求导的模型参数。全在 CPU 上初始化成 0。
        self.moving_mean = nd.zeros(shape)
        self.moving_var = nd.zeros(shape)

    def forward(self, X):
        # 如果 X 不在 CPU 上,将 moving_mean 和 moving_varience 复制到对应设备上。
        if self.moving_mean.context != X.context:
            self.moving_mean = self.moving_mean.copyto(X.context)
            self.moving_var = self.moving_var.copyto(X.context)
        # 保存更新过的 moving_mean 和 moving_var。
        Y, self.moving_mean, self.moving_var = batch_norm(
            X, self.gamma.data(), self.beta.data(), self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y

好处:收敛更快,学习率可以设置的大一点。附带好处:缓解过拟合。

小结

  • 在模型训练时,批量归一化利用小批量上的均值和标准差,不断调整神经网络中间输出,从而使得整个神经网络在各层的中间输出的数值更稳定。
  • 对全连接层和卷积层做批量归一化的方法稍有不同。
  • 批量归一化层和丢弃层一样,在训练模式和预测模式的计算结果是不一样的。

http://zh.gluon.ai/chapter_optimization/index.html

Duchi, J., Hazan, E., & Singer, Y. (2011). Adaptive subgradient methods for online learning and stochastic optimization. Journal of Machine Learning Research, 12(Jul), 2121-2159.

Tieleman, T., & Hinton, G. (2012). Lecture 6.5-rmsprop: Divide the gradient by a running average of its recent magnitude. COURSERA: Neural networks for machine learning, 4(2), 26-31.

Zeiler, M. D. (2012). ADADELTA: an adaptive learning rate method. arXiv preprint arXiv:1212.5701.

Kingma, D. P., & Ba, J. (2014). Adam: A method for stochastic optimization. arXiv preprint arXiv:1412.6980.

Ioffe, S., & Szegedy, C. (2015). Batch normalization: Accelerating deep network training by reducing internal covariate shift. arXiv preprint arXiv:1502.03167.

《深度学习》 8

http://zh.gluon.ai/chapter_convolutional-neural-networks/batch-norm.html

猜你喜欢

转载自blog.csdn.net/Shingle_/article/details/82355260
今日推荐