梯度下降沿着整个训练集的梯度方向下降。可以使用随机梯度下降很大程度地加速,沿着随机挑选的小批量数据的梯度下降。
批量算法和小批量算法
使用小批量的原因
- n个样本均值的标准差是 <nobr> σn√ </nobr>,其中 <nobr> σ </nobr>是样本值真实的标准差。分母 <nobr> n√ </nobr>表明使用更多的样本来估计梯度的方法的回报是低于线性的。
- 另一个促使从小数目样本中获得梯度的统计估计的动机是训练集的冗余。大量样本可能对梯度做出了非常相似的贡献。
- 可能是由于小批量在学习过程中加入了噪声,它们会有一些正则化效果。
其他
鲁棒性
不同的算法使用不同的方法从小批量中获取不同的信息。有些算法对采样误差比其他算法更敏感,这通常有两个原因。一个是它们使用了很难在少量样本上精确估计的信息,另一个是它们以放大采样误差的方式使用了信息。
基于梯度 <nobr> g </nobr>的更新方法通常相对鲁棒,并能使用较小的批量获得成功,如100。使用Hessian矩阵 <nobr> H </nobr>,计算如 <nobr> H−1g </nobr>更新的二阶方法通常需要更大的批量,如10000。随机顺序
小批量是随机抽取的这点也很重要。从一组样本中计算出梯度期望的无偏估计要求这些样本是独立的。在数据集中的顺序有很大影响的情况下,有必要在抽取小批量样本前打乱样本顺序。
不以某种方式打乱样本顺序会极大地降低算法的性能。- 异步并行
在计算小批量样本 <nobr> X </nobr>上最小化 <nobr> J(X) </nobr>的更新时,同时可以计算其他小于样本上的更新。 - 无重复样本,遵循真实泛化误差的梯度
很多小批量随机梯度下降方法的实现都会打乱数据顺序一次,然后多次遍历数据来更新参数。第一次遍历,每个小批量样本都用来计算真实泛化误差的无偏估计。第二次遍历,估计将会是有偏的,因为重新抽取了已经用过的样本,而不是从和原先样本相同的数据生成分布中获取新的无偏的样本。 - 在线学习中的SGD
在线学习中,样本永远不会重复,每次更新的样本是从分布中采样获得的无偏样本。 - 实际使用
在实际使用中,除非训练集特别大,通常还是多次遍历训练集,额外的遍历会由于减小训练误差而得到足够的好处,以抵消其带来的训练误差和测试误差差距的增加。
基本算法
随机梯度下降SGD
SGD及其变种是深度学习中应用最多的优化算法。按照数据生成分布抽取m个小批量(独立同分布)样本,通过计算它们的梯度均值,我们可以得到梯度无偏估计。
SGD算法中的一个关键参数是学习率。在实践中,有必要随着时间的推移逐渐降低学习率。
将第k步迭代的学习率记作 <nobr> ϵk </nobr>。一般会线性衰减学习率直到第 <nobr> τ </nobr>次迭代:
<nobr> ϵk=(1−α)ϵ0+αϵτ </nobr>
其中 <nobr> α=kτ </nobr>。在 <nobr> τ </nobr>次迭代之后,一般使 <nobr> ϵ </nobr>保持常数。
通常 <nobr> ϵτ </nobr>应设为大约 <nobr> ϵ0 </nobr>的1%。
对于足够大的数据集,SGD可能会在处理整个训练集之前就收敛到最终测试集误差的某个固定容差范围内。
批量梯度下降在理论上比随机梯度下降有更好的收敛率。可以在学习过程中逐渐增大批量大大小,以此权衡批量梯度下降和随机梯度下降两者的优点。
带动量的SGD
指数加权平均数
<nobr> vt=βvt−1+(1−β)θt </nobr>
该公式利用过去 <nobr> 11−β </nobr>项的 <nobr> θ </nobr>的加权平均来计算当前的 <nobr> vt </nobr>
偏差修正
使用 <nobr> vt1−βt </nobr>来对进行修正,当t比较大的时候,分母接近于1。
在机器学习中,一般不进行偏差修正,人们一般不使用初期的参数。
另一种形式
在实践中,动量参数一般取值为0.5,0.9和0.99。和学习率一样, <nobr> α </nobr>也会随着时间不断调整。一般初始值是一个较小的值,随后会慢慢变大。随着时间调整 <nobr> α </nobr>没有收缩 <nobr> ϵ </nobr>重要。
Nesterov动量
Nesterov动量中,梯度计算在施加当前速度之后。因此,Nesterov动量可以解释为往标准动量方法中添加了一个校正因子。
标准的动量方法(由Nesterov在1983年提出)是在当前位置计算梯度,然后在累积的更新梯度方向上做一个大的跳跃。下面给出了一种更好地动量方法(由IIya Sutskever在2012年提出),其先在先前累积的梯度方向上做一个大的跳跃,再计算新的梯度并修正错误。
下面对两种方法做了比较,图中蓝色箭头是做两次标准动量方法得到的;而图中棕色箭头是改进动量方法先做的一次大跳跃得到的,红色箭头是修正,绿色箭头是进行一次改进动量方法得到的。可以看到,改进的比标准的要快很多。
自适应学习率算法
习率算法
AdaGrad
AdaGrad独立地适应所有模型参数的学习率,缩放每个参数反比于其所有梯度历史平方值总和的平方根。
在凸优化中,AdaGrad算法具有一些令人满意的理论性质。然而,经验上对于训练深度神经网络模型而言,从训练开始时积累梯度平方会导致有效学习率过早和过量减小。
AdaDelta
AdaGrad主要存在三个问题
- 其学习率是单调递减的,训练后期学习率非常小
- 其需要手工设置一个全局的初始学习率
- 更新 <nobr> θ </nobr>时,左右两边的单位不同一
我们令每一个时刻的 <nobr> r </nobr>随之时间按照 <nobr> ρ </nobr>指数衰减,这样就相当于仅使用离当前时刻比较近的 <nobr> g </nobr>信息,不会使得 <nobr> r </nobr>增加过快,分母过大而导致参数更新减缓。在AdaDelta中,累积平方梯度定义为
<nobr> r←ρr+(1−ρ)g⊙g </nobr>
在计算更新时,用梯度历史该变量平方值期望的平方根代替学习率 <nobr> η </nobr>,则得到Adadelta更新规则:
由此看出,甚至不需要设定缺省学习率,因为更新规则已经不受它影响了
RMSProp
RMSProp算法修改AdaGrad以在非凸设定下效果更好,改变梯度累积为指数加权的移动平均。
AdaGrad旨在应用于凸问题时快速收敛。AdaGrad根据平方梯度的整个历史收缩学习率,可能使得学习率在达到这样的凸结构前就变得太小了。
RMSprop使用指数衰减平均来丢弃遥远过去的历史,使其在找到凸结构后快速收敛,就像一个初始化于该碗装结构的AdaGrad算法实例。
相比于AdaGrad,使用移动平均引入了一个新的超参数 <nobr> ρ </nobr>,用来控制移动平均的长度范围。
Adam
派生自短语”adaptive moments”
Adam被看作结合RMSProp和具有一些重要区别的动量的变种。
首先,Adam中动量直接并入梯度一阶矩(指数加权)的估计。
其次,Adam包括偏置修正,修泽和那个从原点初始化的一阶矩(动量项)和(非中心的)二阶矩的估计。RMSProp也采用了(非中心的)二阶矩估计,然而缺失了修正因子。因此,RMSProp二阶矩估计可能在训练初期有很高的偏置。
Adam通常被认为对超参数的选择相当鲁棒,尽管学习率有时需要遵从建议的默认参数0.001。
二阶近似方法
牛顿法
牛顿法是基于二阶泰勒级数展开在某点 <nobr> θ0 </nobr>附近来近似 <nobr> J(θ) </nobr>的优化方法,其忽略了高阶导数
<nobr> J(θ)≈J(θ0)+(θ−θ0)T∇θJ(θ0)+12(θ−θ0)TH(θ−θ0) </nobr>,
其中 <nobr> H </nobr>是 <nobr> J </nobr>相对于 <nobr> θ </nobr>的Hessian矩阵在 <nobr> θ0 </nobr>处的矩估计。通过求解这个函数的临界点,得到牛顿参数更新规则:
<nobr> θ∗=θ0−H−1∇θJ(θ0) </nobr>
因此,对于局部的二次函数(具有正定的H),用 <nobr> H−1 </nobr>重新调整梯度,牛顿法会直接跳到极小值。如果目标函数是凸的但非二次的(有高阶项),该更新是迭代的。
共轭梯度
共轭梯度是一种通过迭代下降的共轭方向以有效避免Hessian矩阵求逆计算的方法。
在共轭梯段法中,我们寻求一个和先前线性搜索方向共轭的搜索方向,即它不会撤销该方向上的进展。在训练迭代t时,下一步的搜索方向 <nobr> dt </nobr>的形式如下:
<nobr> dt=∇θJ(θ)+βtdt−1 </nobr>
其中,系数 <nobr> βt </nobr>的大小控制我们应沿方向 <nobr> dt−1 </nobr>加回多少到当前搜索方向。
如果 <nobr> dTtHdt−1=0 </nobr>,其中 <nobr> H </nobr>是Hessian矩阵,则两个方向 <nobr> dt </nobr>和 <nobr> dt−1 </nobr>被称为共轭的。
BFGS
Broyden-Fletcher-Goldfarb-Shanno(BFGS)算法具有牛顿法的一些优点,但没有牛顿法的计算负担。
http://blog.csdn.net/u012151283/article/details/78148890#t3
拟牛顿法采用矩阵 <nobr> Mt </nobr>近似逆,迭代地低秩更新精度以更好地近似 <nobr> H−1 </nobr>。
当Hessian逆近似 <nobr> Mt </nobr>更新时,下降方向 <nobr> ρt </nobr>为 <nobr> ρt=Mtgt </nobr>。该方向上的线性搜索用于决定该方向上的步长 <nobr> ϵ∗ </nobr>。参数的最后更新为:
<nobr> θt+1=θt+ϵ∗ρt </nobr>
相比于共轭梯度,BFGS的优点是其花费较少的时间改进每个线性搜索。在另一方面,BFGS算法必须存储Hessian逆矩阵M,需要 <nobr> O(n2) </nobr>的存储空间,使BFGS不适用于大多数具有百万级参数的现代深度学习模型。
L-BFGS
L-BFGS算法就是对拟牛顿算法的一个改进。L-BFGS算法的基本思想是:算法只保存并利用最近m次迭代的曲率信息来构造海森矩阵的近似矩阵。
LBFGS,起始假设是 <nobr> G(t−1) </nobr>是单位矩阵,而不是每一步都要存储近似矩阵。每步存储一些用于更新 <nobr> Gk </nobr>的向量,且每步存储代价为 <nobr> O(n) </nobr>。
BFGS算法关于 <nobr> Gk </nobr>的迭代形式如下
<nobr> Gk+1=(I−skyTkyTksk)Gk(I−yksTkyTksk)+sksTkyTksk </nobr>
令 <nobr> ρk=1yTksk </nobr>, <nobr> Vk=I−ρkyksTk </nobr>,则有
<nobr> Gk+1=VTkGkVk+ρksksTk </nobr>
对于给定的 <nobr> G0=I </nobr>,可以迭代地计算 <nobr> Gk </nobr>, <nobr> Gk </nobr>的计算只依赖于 <nobr> [si,yi]ki=0 </nobr>。
L-BFGS算法通过存储最近连续的m组 <nobr> si,yi </nobr>向量,来近似地计算 <nobr> Gk </nobr>。
批标准化
批标准化是一种自适应重参数化的方法,试图解决训练非常深的模型的困难。批标准化主要解决的是训练极深网络时梯度消失的问题。
BN起作用的原因
- 通过使得批量数据归一化具有0均值1方差的统计分布,避免数据处于激活函数的饱和区,具有较大的梯度,从而加速网络的训练过程。
- 减少了网络输入变化过大的问题,使得网络的输入稳定,减弱了与前层参数关系之间的作用,使得当前层独立于整个网络
BN具有轻微正则化的效果,可以和dropout一起使用
主要是归一化激活值前的隐藏单元来加速训练,正则化是副作用
BN具有正则化效果的原因
- 每个批量的数据仅根据当前批量计算均值和标准差,然后缩放
- 这就为该批量的激活函数带来了一些噪音,类似于dropout向每一层的激活函数带来噪音
- 若使用了较大的batch_size如512,则减小了噪音,减少了正则化带来的效果
训练过程中的算法
由于BN在最后重参数化过程中会学习得到一个 <nobr> γ </nobr>,所以神经网络中的bias可以省略掉。
推断时算法
由于在推断的时,batch的大小不能确定,很有可能1次只有一个样本,不通过对当前批量的数据的归一化来加速训练。作者提出使用训练数据集上的全部数据来计算均值和方差。通常出与计算效率考虑,使用滑动平均的方法来计算
tensorflow实现
使用tf.layers.batch_normalization
可以快速实现BN算法。注意使用该API时在优化器调用时要加入以下代码:
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_op = optimizer.minimize(loss)
这里还有一个我看到知乎匿名用户的实现,感觉比较清晰
from tensorflow.python.training.moving_averages import assign_moving_average
def batch_norm(x, train, eps=1e-05, decay=0.9, affine=True, name=None):
with tf.variable_scope(name, default_name='BatchNorm2d'):
params_shape = tf.shape(x)[-1:]
moving_mean = tf.get_variable('mean', params_shape,
initializer=tf.zeros_initializer,
trainable=False)
moving_variance = tf.get_variable('variance', params_shape,
initializer=tf.ones_initializer,
trainable=False)
def mean_var_with_update():
mean, variance = tf.nn.moments(x, tf.shape(x)[:-1], name='moments')
with tf.control_dependencies([assign_moving_average(moving_mean, mean, decay),
assign_moving_average(moving_variance, variance, decay)]):
return tf.identity(mean), tf.identity(variance)
mean, variance = tf.cond(train, mean_var_with_update, lambda: (moving_mean, moving_variance))
if affine:
beta = tf.get_variable('beta', params_shape,
initializer=tf.zeros_initializer)
gamma = tf.get_variable('gamma', params_shape,
initializer=tf.ones_initializer)
x = tf.nn.batch_normalization(x, mean, variance, beta, gamma, eps)
else:
x = tf.nn.batch_normalization(x, mean, variance, None, None, eps)
return x
优化策略和元算法
- 坐标下降
参考资料
《深度学习》第8章
LBFGS算法笔记
知乎匿名用户回答BN的实现