本节主要讲了神经网络学习率退火和超参数调优等问题
训练深度网络时,让学习率随着时间退火通常很有帮助。如果学习率很高,系统的动能就过大,参数向量就会无规律地跳动,不能够稳定到损失函数更深更窄的部分去。
何时开始衰减学习率是有技巧的:缓慢减小它,可能很长时间内只能是浪费计算资源地看着它混沌地跳动,实际进展很少。而如果快速地减小它,系统可能过快地失去能量,不能到达原本可以到达的最好位置。
通常,实现学习率退火以下三种:
实践中,随步数衰减的随机失活(dropout)更受欢迎,因为它使用的超参数(衰减系数和以周期为时间单位的步数)比kk更有解释性。
该优化方法基于牛顿法,其迭代方式如下:
x←x−[Hf(x)]−1∇f(x)
x \leftarrow x - [H f(x)]^{-1} \nabla f(x) 这里Hf(x)Hf(x)是Hessian矩阵,它是函数的二阶偏导数的平方矩阵。∇f(x) \nabla f(x)是梯度向量,这和梯度下降中一样。直观理解上,Hessian矩阵描述了损失函数的局部曲率,从而使得可以进行更高效的参数更新。具体来说,就是乘以Hessian转置矩阵可以让最优化过程在曲率小的时候大步前进,在曲率大的时候小步前进。这个公式中没有学习率这个超参数,因此比一阶方法好得多。
在实际深度学习应用中,上述更新方法很难运用,这是因为计算(以及求逆)Hessian矩阵操作非常耗费时间和空间。例如,假设一个有一百万个参数的神经网络,其Hessian矩阵大小就是[1,000,000 x 1,000,000],将占用将近3725GB的内存。好消息是,各种各样的拟-牛顿法就被发明出来用于近似转置Hessian矩阵。最流行的是L-BFGS,该方法使用随时间的梯度中的信息来隐式地近似(整个矩阵没有被计算)。
然而,即使解决了存储空间的问题,L-BFGS应用的一个巨大劣势是需要对整个训练集进行计算,而整个训练集一般包含几百万的样本。和小批量随机梯度下降(mini-batch SGD)不同,让L-BFGS在小批量上运行起来是很需要技巧,同时也是研究热点。 在实践中,使用L-BFGS之类的二阶方法并不常见。基于(Nesterov的)动量更新的各种随机梯度下降方法更加常用,因为它们更加简单且容易扩展。 感兴趣的同学可以参考以下文献:
前面讨论的基于梯度的优化方法(SGD、Momentum、NAG)的学习率是全局的,且对所有参数是相同的。 参数的有些维度变化快,有些维度变化慢;有些维度是负的斜坡,有些维度是正的斜坡(如鞍点);采用相同的学习率并不科学,比如有的参数可能已经到了仅需要微调的阶段,但又有些参数由于对应样本少等原因,还需要较大幅度的调动。理想的方案是根据参数每个维度的变化率,采用对应的学习率。 学习率调参是很耗费计算资源的过程,所以很多工作投入到发明能够适应性地对学习率调参的方法,甚至是逐个参数适应学习率调参。很多这些方法仍需其他超参数设置,但普遍认为这些方法对于更广范围的超参数比原始的学习率方法有更良好的表现。下面将介绍一些在实践中可能遇到的常用适应算法:
Adagrad是一个由Duchi等提出的适应性学习率算法
# 假设有梯度和参数向量x
cache += dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
变量cache的尺寸和梯度矩阵的尺寸相同,还跟踪了每个参数的梯度的平方和。这个一会儿将用来归一化参数更新步长,归一化是逐元素进行的。注意,接收到高梯度值的权重更新的效果被减弱,而接收到低梯度值的权重的更新效果将会增强。平方根的操作非常重要,如果去掉,算法的表现将会糟糕很多。用于平滑的式子eps
(一般设为1e−41e^{-4}到1e−81e^{-8}之间)防止出现除0的情况。
优点:不需要手动调整Learning Rate,默认值为0.01即可。 缺点:在分母中累积了梯度的平方,且此累积值单调递增,从而导致学习率单调递减,直至无限小(在深度学习中单调的学习率被证明通常过于激进且过早停止学习),从而不能再学到相关知识(AdaDelta、RMSprop、Adam专为解决此问题而生)。 AdaGrad方法给参数的每个维度给出适应的学习率。给不经常更新的参数以较大的学习率, 给经常更新的参数以较小的学习率。Google使用此优化方法“识别Youtube视频中的猫” 。 在AdaGrad中,每个参数θi\theta_i在每一次更新时都使用不同的学习率。其公式如下:
gt,i=∇θJ(θi)θt+i=θt,i−η⋅gt,iθt+i=θt,i−ηGt,ii+ϵ−−−−−−−√⋅gt,iGt∈Rd×d是一个对角矩阵,每个对角元素i,i是θi过去所有梯度平方的和ϵ是一个平滑项。避免除0,通常取1e−8θt+1=θt−ηGt+ϵ⊙gt
\begin {align} & g_{t,i}=\nabla_\theta J(\theta_i) \\ &\theta_{t+i}= \theta_{t,i}-\eta\cdot g_{t,i} \\ &\theta_{t+i}= \theta_{t,i}-\frac{\eta}{\sqrt{G_{t,ii}+\epsilon}}\cdot g_{t,i}\\ &G_{t}\in \mathbb{R}^{d\times d} \text{是一个对角矩阵,每个对角元素}i,i\text{是}\theta_i\text{过去所有梯度平方的和}\\ & \epsilon\text{是一个平滑项。避免除0,通常取}1e^{-8}\\ & \theta_{t+1}=\theta_t - \frac{\eta}{G_t + \epsilon}\odot g_t \end {align} learning_rate 是初始学习率,由于之后会自动调整学习率,所以初始值就不像之前的算法那样重要了。其含义是,对于每个参数,随着其更新的总距离增多,其学习速率也随之变慢。
优点: 1) 解决了AdaGrad Learning Rate单调递减的问题。 (是AdaGrad的扩展) 2) 不需要设置默认的Learning Rate Adagrad算法存在三个问题: 1)其学习率是单调递减的,训练后期学习率非常小 2)其需要手工设置一个全局的初始学习率 3)更新W时,左右两边的单位不同
Adadelta针对上述三个问题提出了比较漂亮的解决方案。
RMS(Root Mean Squared均方根) 是一个非常高效,但没有公开发表的适应性学习率方法。引自Geoff Hinton的Coursera课程的第六课的第29页PPT。这个方法用一种很简单的方式修改了Adagrad方法,让它不那么激进,单调地降低了学习率。具体说来,就是它使用了一个梯度平方的滑动平均:
E[g2]t=0.9E[g2]t−1+0.1g2tθt+1=θt−ηE[g2]t+ϵ−−−−−−−−√⋅gtη is 0.001
\begin{align} & E[g^2]_t = 0.9E[g^2]_{t-1}+ 0.1g_t^2\\ &\theta_{t+1}=\theta _t - \frac{\eta}{\sqrt{E[g_2]_t+\epsilon}}\cdot g_t\\ &\eta\text{ is 0.001 } \end{align}
cache = decay_rate * cache + (1 - decay_rate) * dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
代码中,decay_rate
是一个超参数,常用的值是[0.9,0.99,0.999][0.9,0.99,0.999]。其中x+=
和Adagrad中是一样的,但是cache
变量是不同的。因此,RMSProp仍然是基于梯度的大小来对每个权重的学习率进行修改,这同样效果不错。但是和Adagrad不同,其更新不会让学习率单调变小。
Adam是最近才提出的一种更新方法,它看起来像是RMSProp的动量版。简化的代码是下面这样:
mt=β1mt−1+(1−β1)gtvt=β2vt−1+(1−β2)g2tmt:梯度的均值(the mean)估计vt:梯度的非中心方差(the uncentered variance)估计m^t=mt1−βt1v^t=vt1−βt2θt+1=θt−ηvt^−−√+ϵ⋅m^t0.9 for β1,0.999 for β2 and 10−8 for ϵ
\begin{align} &m_t=\beta_1m_{t-1}+(1-\beta_1)g_t\\ &v_t=\beta_2v_{t-1}+(1-\beta_2)g_t^2\\ &m_t:梯度的均值(the mean)估计\\ &v_t:梯度的非中心方差(the uncentered variance)估计\\ &\hat m_t=\frac{m_t}{1-\beta_1^t}\\ &\hat v_t=\frac{v_t}{1-\beta_2^t}\\ &\theta_{t+1}=\theta_t - \frac{\eta}{\sqrt{\hat{v_t}}+\epsilon }\cdot\hat m_t\\ &0.9\text{ for } \beta_1,0.999 \text{ for } \beta_2\text{ and } 10^{-8}\text{ for } \epsilon \end{align}
m = beta1*m + (1-beta1)*dx
v = beta2*v + (1-beta2)*(dx**2)
x += - learning_rate * m / (np.sqrt(v) + eps)
这个更新方法看起来很像RMSProp,除了使用的是平滑版的梯度m
,而不是用的原始梯度向量dx
。论文中推荐的参数值eps=1e-8, beta1=0.9, beta2=0.999
。在实际操作中,我们推荐Adam作为默认的算法,一般而言跑起来比RMSProp要好一点。但是也可以试试SGD+Nesterov动量。完整的Adam更新算法也包含了一个偏置(bias)矫正机制,因为m,v
两个矩阵初始为0,在没有完全热身之前存在偏差,需要采取一些补偿措施。
有兴趣地可以读以下文献:
Matthew Zeiler提出的Adadelta
Adam: A Method for Stochastic Optimization
Unit Tests for Stochastic Optimization
1)总结
神经网络最常用的超参数设置有:初始学习率、学习率衰减方式(例如一个衰减常量)、正则化强度(L2惩罚,随机失活强度)。下面将介绍一些额外的调参要点和技巧:
随机设置参数并记录。对于大的深层次神经网络而言,我们需要很多的时间去训练。因此在此之前我们花一些时间去做超参数搜索,以确定最佳设定是非常有必要的。最直接的方式就是在框架实现的过程中,设计一个会持续变换超参数实施优化,并记录每个超参数下每一轮完整训练迭代下的验证集状态和效果。
使用验证集。实际工程中,神经网络里确定这些超参数,我们一般很少使用n折交叉验证,一般使用一份固定的交叉验证集就可以了。
超参数范围。一般对超参数的尝试和搜索都是在对数域进行的。例如,一个典型的学习率搜索序列就是learning_rate = 10 ** uniform(-6, 1)
。我们先生成均匀分布的序列,再以10为底做指数运算,其实我们在正则化系数中也做了一样的策略。比如常见的搜索序列为[0.5,0.9,0.95,0.99][0.5, 0.9, 0.95, 0.99]。
随机搜索优于网格搜索。 Bergstra和Bengio在文章Random Search for Hyper-Parameter Optimization中说“随机选择比网格化的选择更加有效”,而且在实践中也更容易实现。
对于边界上的最优值要小心。这种情况一般发生在你在一个不好的范围内搜索超参数(比如学习率)的时候。比如,假设我们使用learning_rate = 10 ** uniform(-6,1)
来进行搜索。一旦我们得到一个比较好的值,一定要确认你的值不是出于这个范围的边界上,不然你可能错过更好的其他搜索范围。
从粗到细地分阶段搜索。在实践中,先进行初略范围(比如10 ** [-6, 1])搜索,然后根据好的结果出现的地方,缩小范围进行搜索。进行粗搜索的时候,让模型训练一个周期就可以了,因为很多超参数的设定会让模型没法学习,或者突然就爆出很大的损失值。第二个阶段就是对一个更小的范围进行搜索,这时可以让模型运行5个周期,而最后一个阶段就在最终的范围内进行仔细搜索,运行很多次周期。
贝叶斯超参数最优化是一整个研究领域,主要是研究在超参数空间中更高效的导航算法。其核心的思路是在不同超参数设置下查看算法性能时,要在探索和使用中进行合理的权衡。基于这些模型,发展出很多的库,比较有名的有: Spearmint, SMAC, 和Hyperopt。然而,在卷积神经网络的实际使用中,比起上面介绍的先认真挑选的一个范围,然后在该范围内随机搜索的方法,这个方法还是差一些。这里有更详细的讨论。
在实践的时候,一个能提升神经网络几个百分点准确率的方法是在训练的时候训练几个独立的模型,然后在测试的时候平均它们预测结果。集成的模型数量增加,算法的结果也单调提升(但提升效果越来越少)。还有模型之间的差异度越大,提升效果可能越好。进行集成有以下几种方法:
同一个模型,不同的初始化。使用交叉验证来得到最好的超参数,然后用最好的参数来训练不同初始化条件的模型。这种方法的风险在于多样性只来自于不同的初始化条件。
在交叉验证中发现最好的模型。使用交叉验证来得到最好的超参数,然后取其中最好的几个(比如10个)模型来进行集成。这样就提高了集成的多样性,但风险在于可能会包含不够理想的模型。在实际操作中,这样操作起来比较简单,在交叉验证后就不需要额外的训练了。
一个模型设置多个记录点。如果训练非常耗时,那就在不同的训练时间对网络留下记录点(比如每个周期结束),然后用它们来进行模型集成。很显然,这样做多样性不足,但是在实践中效果还是不错的,这种方法的优势是代价比较小。
在训练的时候跑参数的平均值。和上面一点相关的,还有一个也能得到1-2个百分点的提升的小代价方法,这个方法就是在训练过程中,如果损失值相较于前一次权重出现指数下降时,就在内存中对网络的权重进行一个备份。这样你就对前几次循环中的网络状态进行了平均。你会发现这个“平滑”过的版本的权重总是能得到更少的误差。直观的理解就是目标函数是一个碗状的,你的网络在这个周围跳跃,所以对它们平均一下,就更可能跳到中心去。
模型集成的一个劣势就是在测试数据的时候会花费更多时间。最近Geoff Hinton在“Dark Knowledge”上的工作很有启发:其思路是通过将集成似然估计纳入到修改的目标函数中,从一个好的集成中抽出一个单独模型。
训练一个神经网络需要:
拓展阅读
Leon Bottou的《SGD要点和技巧》。 Yann LeCun的《Efficient BackProp》。 Yoshua Bengio的《Practical Recommendations for Gradient-Based Training of Deep Architectures》。
链接:http://cs231n.github.io/neural-networks-3/ 链接:https://zhuanlan.zhihu.com/p/21741716?refer=intelligentunit https://zhuanlan.zhihu.com/p/21798784?refer=intelligentunit 链接:http://blog.csdn.net/han_xiaoyang/article/details/50521064 链接:http://blog.csdn.net/myarrow/article/details/51848285