自适应学习率调整:AdaDelta
Reference:ADADELTA: An Adaptive Learning Rate Method
超参数
超参数(Hyper-Parameter)是困扰神经网络训练的问题之一,因为这些参数不可通过常规方法学习获得。
神经网络经典五大超参数:
学习率(Leraning Rate)、权值初始化(Weight Initialization)、网络层数(Layers)
单层神经元数(Units)、正则惩罚项(Regularizer|Normalization)
这五大超参数使得神经网络更像是一门实践课,而不是理论课。
懂神经网络可能只要一小时,但是调神经网络可能要几天。
因此,后来Vapnik做SVM支持向量机的时候,通过巧妙的变换目标函数,避免传统神经网络的大部分超参数,
尤其是以自适应型的支持向量替代人工设置神经元,这使得SVM可以有效免于过拟合之灾。
传统对抗这些超参数的方法是经验规则(Rules of Thumb)。
这几年,随着深度学习的推进,全球神经网络研究者人数剧增,已经有大量研究组着手超参数优化问题:
★深度学习先锋的RBM就利用Pre-Traning自适应调出合适的权值初始化值。
★上个世纪末的LSTM长短期记忆网络,可视为“神经网络嵌套神经网络”,自适应动态优化层数。
★2010年Duchi et.al 则推出AdaGrad,自适应来调整学习率。
自适应调整学习率的方法,目前研究火热。一个经典之作,是 Matthew D. Zeiler 2012年在Google实习时,
提出的AdaDelta。
Matthew D. Zeiler亦是Hinton的亲传弟子之一,还是商业天才,大二时办了一个公司卖复习旧书。
Phd毕业之后,创办了Clarifai,估值五百万刀。参考[知乎专栏]
Clarifai的杰出成就是赢得了ImageNet 2013冠军,后来公布出CNN结构的时候,Caffe、Torch之类
的框架都仿真不出他在比赛时候跑的结果,应该是用了不少未公布的黑科技的。
再看他2012年提出的AdaDelta,肯定是用在的2013年的比赛当中,所以后来以普通方式才无法仿真的。
梯度更新
2.1 [一阶方法] 随机梯度
SGD(Stochastic Gradient Descent)是相对于BGD(Batch Gradient Descent)而生的。
BGD要求每次正反向传播,计算所有Examples的Error,这在大数据情况下是不现实的。
最初的使用的SGD,每次正反向传播,只计算一个Example,串行太明显,硬件利用率不高。
后续SGD衍生出Mini-Batch Gradient Descent,每次大概推进100个Example,介于BGD和SGD之间。
现在,SGD通常是指Mini-Batch方法,而不是早期单Example的方法。
一次梯度更新,可视为:
xt+1=xt+ΔxtwhereΔxt=−η⋅gt” role=”presentation”>xt+1=xt+ΔxtwhereΔxt=−η⋅gtxt+1=xt+ΔxtwhereΔxt=−η⋅gt
x” role=”presentation”>xx为梯度
2.2 [二阶方法] 牛顿法
二阶牛顿法替换梯度更新量:
Δxt=Ht−1⋅gt” role=”presentation”>Δxt=H−1t⋅gtΔxt=Ht−1⋅gt
H” role=”presentation”>HH为参数的二阶导矩阵,称为Hessian矩阵。
牛顿法,用Hessian矩阵替代人工设置的学习率,在梯度下降的时候,可以完美的找出下降方向,
不会陷入局部最小值当中,是理想的方法。
但是,求逆矩阵的时间复杂度近似O(n3)” role=”presentation”>O(n3)O(n3),计算代价太高,不适合大数据。
常规优化方法
3.1 启发式模拟退火
早期最常见的手段之一就是模拟退火。当然这和模拟退火算法没有半毛钱关系。
引入一个超参数(常数)的退火公式:
ηt=η01+d×t” role=”presentation”>ηt=η01+d×tηt=η01+d×t
η0” role=”presentation”>η0η0
模拟退火基于一个梯度法优化的事实:
在优化过程中,Weight逐渐变大,因而需要逐渐减小学习率,保证更新平稳。
3.2 动量法
中期以及现在最普及的就是引入动量因子:
Δxt=ρΔxt−1−η⋅gt” role=”presentation”>Δxt=ρΔxt−1−η⋅gtΔxt=ρΔxt−1−η⋅gt
ρ” role=”presentation”>ρρ为动量因子,通常设为0.9
在更新中引入0.9这样的不平衡因子,使得:
★在下降初期,使用前一次的大比重下降方向,加速。
★在越过函数谷面时,异常的学习率,会使得两次更新方向基本相反,在原地”震荡“
此时,动量因子使得更新幅度减小,协助越过函数谷面。
★在下降中后期,函数面局部最小值所在的吸引盆数量较多,一旦陷进吸引盆当中,
Gradient→0” role=”presentation”>Gradient→0Gradient→0,但是前后两次更新方向基本相同。
此时,动量因子使得更新幅度增大,协助跃出吸引盆。
3.3 AdaGrad
AdaGrad思路基本是借鉴L2 Regularizer,不过此时调节的不是W” role=”presentation”>WW:
Δxt=−η∑τ=1t(gτ)2⋅gt” role=”presentation”>Δxt=−η∑tτ=1(gτ)2√⋅gtΔxt=−η∑τ=1t(gτ)2⋅gt
AdaGrad过程,是一个递推过程,每次从τ=1” role=”presentation”>τ=1τ=1的平方根,作为Regularizer。
分母作为Regularizer项的工作机制如下:
★训练前期,梯度较小,使得Regularizer项很大,放大梯度。[激励阶段]
★训练后期,梯度较大,使得Regularizer项很小,缩小梯度。[惩罚阶段]
另外,由于Regularizer是专门针对Gradient的,所以有利于解决Gradient Vanish/Expoloding问题。
所以在深度神经网络中使用会非常不错。
当然,AdaGrad本身有不少缺陷:
★初始化W影响初始化梯度,初始化W过大,会导致初始梯度被惩罚得很小。
此时可以人工加大η” role=”presentation”>ηη会使得Regularizer过于敏感,调节幅度很大。
★训练到中后期,递推路径上累加的梯度平方和越打越多,迅速使得Gradinet” role=”presentation”>GradinetGradinet被惩罚逼近0,提前结束训练。
AdaDelta
AdaDelta基本思想是用一阶的方法,近似模拟二阶牛顿法。
4.1 矩阵对角线近似逆矩阵
1988年,[Becker&LeCun]提出一种用矩阵对角线元素来近似逆矩阵的方法:
Δxt=−1|diag(Ht)|+μ⋅gt” role=”presentation”>Δxt=−1∣∣diag(Ht)∣∣+μ⋅gtΔxt=−1|diag(Ht)|+μ⋅gt
diag” role=”presentation”>diagdiag是常数项,防止分母为0。
2012年,[Schaul&S. Zhang&LeCun]借鉴了AdaGrad的做法,提出了更精确的近似:
Δxt=−1|diag(Ht)|E[gt−w:t]2E[gt2−w:t]⋅gt” role=”presentation”>Δxt=−1∣∣diag(Ht)∣∣E[gt−w:t]2E[g2t−w:t]⋅gtΔxt=−1|diag(Ht)|E[gt−w:t]2E[gt2−w:t]⋅gt
E[gt−w:t]” role=”presentation”>E[gt−w:t]E[gt−w:t]指的是从当前t开始的前w个梯度状态的期望值。
E[gt2−w:t]” role=”presentation”>E[g2t−w:t]E[gt2−w:t]指的是从当前t开始的前w个梯度状态的平方的期望值。
同样是基于Gradient的Regularizer,不过只取最近的w个状态,这样不会让梯度被惩罚至0。
4.2 窗口和近似概率期望
计算E[gt−w:t]” role=”presentation”>E[gt−w:t]E[gt−w:t],需要存储前w个状态,比较麻烦。
AdaDelta使用了类似动量因子的平均方法:
E[g2]t=ρE[g2]t−1+(1−ρ)gt2” role=”presentation”>E[g2]t=ρE[g2]t−1+(1−ρ)g2tE[g2]t=ρE[g2]t−1+(1−ρ)gt2
当ρ=0.5” role=”presentation”>ρ=0.5ρ=0.5时,这个式子就变成了求梯度平方和的平均数。
如果再求根的话,就变成了RMS(均方根):
RMS[g]t=E[g2]t+ϵ” role=”presentation”>RMS[g]t=E[g2]t+ϵ‾‾‾‾‾‾‾‾‾‾√RMS[g]t=E[g2]t+ϵ
再把这个RMS作为Gradient的Regularizer:
Δxt=−ηRMS[g]t⋅gt” role=”presentation”>Δxt=−ηRMS[g]t⋅gtΔxt=−ηRMS[g]t⋅gt
其中,ϵ” role=”presentation”>ϵϵ是防止分母爆0的常数。
这样,就有了一个改进版的AdaGrad。
该方法即Tieleman&Hinton的RMSProp,由于RMSProp和AdaDelta是同年出现的,
Matthew D. Zeiler并不知道这种改进的AdaGrad被祖师爷命名了。
RMSProp利用了二阶信息做了Gradient优化,在BatchNorm之后,对其需求不是很大。
但是没有根本实现自适应的学习率,依然需要线性搜索初始学习率,然后对其逐数量级下降。
另外,RMSProp的学习率数值与MomentumSGD差别甚大,需要重新线性搜索初始值。
注:ϵ” role=”presentation”>ϵϵ的建议取值为1,出处是Inception V3,不要参考V3的初始学习率。
4.3 Hessian方法与正确的更新单元
Zeiler用了两个反复近似的式子来说明,一阶方法到底在哪里输给了二阶方法。
首先,考虑SGD和动量法:
Δx∝g∝∂f∂x∝1x” role=”presentation”>Δx∝g∝∂f∂x∝1xΔx∝g∝∂f∂x∝1x
Δx” role=”presentation”>ΔxΔx。
再考虑二阶导Hessian矩阵法:
这里为了对比观察,使用了[Becker&LeCun 1988]的近似方法,让求逆矩阵近似于求对角阵的倒数:
Δx∝H−1g∝∂f∂x∂2f∂x2∝1x1x∗1x∝x” role=”presentation”>Δx∝H−1g∝∂f∂x∂2f∂x2∝1x1x∗1x∝xΔx∝H−1g∝∂f∂x∂2f∂x2∝1x1x∗1x∝x
Δx” role=”presentation”>ΔxΔx。
可以看到,一阶方法最终正比于1x” role=”presentation”>1x1x,即与参数逆相关:参数逐渐变大的时候,梯度反而成倍缩小。
而二阶方法最终正比于x” role=”presentation”>xx,即与参数正相关:参数逐渐变大的时候,梯度不受影响。
因此,Zeiler称Hessian方法得到了Correct Units(正确的更新单元)。
4.4 由Hessian方法推导出一阶近似Hessian方法
基于[Becker&LeCun 1988]的近似方法,有:
Δx≈∂f∂x∂2f∂x2” role=”presentation”>Δx≈∂f∂x∂2f∂x2Δx≈∂f∂x∂2f∂x2
进而又有:
∂f∂x∂2f∂x2=1∂2f∂x2⋅∂f∂x=1∂2f∂x2⋅gt” role=”presentation”>∂f∂x∂2f∂x2=1∂2f∂x2⋅∂f∂x=1∂2f∂x2⋅gt∂f∂x∂2f∂x2=1∂2f∂x2⋅∂f∂x=1∂2f∂x2⋅gt
简单收束变形一下, 然后用RMS来近似:
1∂2f∂x2=Δx∂f∂x≈−RMS[Δx]t−1RMS[g]t” role=”presentation”>1∂2f∂x2=Δx∂f∂x≈−RMS[Δx]t−1RMS[g]t1∂2f∂x2=Δx∂f∂x≈−RMS[Δx]t−1RMS[g]t
最后,一阶完整近似式:
Δx=−RMS[Δx]t−1RMS[g]t⋅gt” role=”presentation”>Δx=−RMS[Δx]t−1RMS[g]t⋅gtΔx=−RMS[Δx]t−1RMS[g]t⋅gt
值得注意的是,使用了RMS[Δx]t−1” role=”presentation”>RMS[Δx]t−1RMS[Δx]t−1还没算出来。
4.5 算法流程
ALGORITHM:ADADELTARequire:DecayRateρ,ConstantϵRequire:InitialParamx11:InitializeaccumulationvariablesE[g2]0=E[Δx2]0=02:Fort=1:TdoLoopallupdates3:ComputeGradients:gt4:AccumulateGradient:E[g2]t=ρE[g2]t−1+(1−ρ)gt25:ComputeUpdate:Δx=−RMS[Δx]t−1RMS[g]t⋅gt6:AccumulateUpdates:E[Δx2]t=ρE[Δx2]t−1+(1−ρ)Δx27:ApplyUpdate:xt+1=xt+Δxt8:EndFor” role=”presentation”>ALGORITHM:ADADELTARequire:DecayRateρ,ConstantϵRequire:InitialParamx11:InitializeaccumulationvariablesE[g2]0=E[Δx2]0=02:Fort=1:TdoLoopallupdates3:ComputeGradients:gt4:AccumulateGradient:E[g2]t=ρE[g2]t−1+(1−ρ)g2t5:ComputeUpdate:Δx=−RMS[Δx]t−1RMS[g]t⋅gt6:AccumulateUpdates:E[Δx2]t=ρE[Δx2]t−1+(1−ρ)Δx27:ApplyUpdate:xt+1=xt+Δxt8:EndForALGORITHM:ADADELTARequire:DecayRateρ,ConstantϵRequire:InitialParamx11:InitializeaccumulationvariablesE[g2]0=E[Δx2]0=02:Fort=1:TdoLoopallupdates3:ComputeGradients:gt4:AccumulateGradient:E[g2]t=ρE[g2]t−1+(1−ρ)gt25:ComputeUpdate:Δx=−RMS[Δx]t−1RMS[g]t⋅gt6:AccumulateUpdates:E[Δx2]t=ρE[Δx2]t−1+(1−ρ)Δx27:ApplyUpdate:xt+1=xt+Δxt8:EndFor
4.6 Theano实现
论文中,给出的两个超参数的合适实验值。
ρ=0.95ϵ=1e−6” role=”presentation”>ρ=0.95ϵ=1e−6ρ=0.95ϵ=1e−6
Theano的实现在LSTM的教学部分,个人精简了一下:
def AdaDelta(tparams,grads): p=0.95;e=1e-6 # init delta_x2=[theano.shared(p.get_value() * floatX(0.)) for k, p in tparams.iteritems()] g2 = [theano.shared(p.get_value() * floatX(0.)) for k, p in tparams.iteritems()] # first to update g2 update_g2=[(g2, p * g2 + (1-p) * (g ** 2)) for g2, g in zip(g2, grads)] fn_update_1=theano.function(inputs=[],updates=update_g2) #calc delta_x by RMS delta_x=[-T.sqrt(delta_x2_last + e) / T.sqrt(g2_now + e) * g for g, delta_x2_last, g2_now in zip(grads,delta_x2,g2)] # then to update delta_x2 and param update_delta_x2=[(delta_x2, p * delta_x2 + (1-p) * (delta_x ** 2)) for delta_x2, delta_x in zip(delta_x2, delta_x)] update_param=[(param, param + delta) for param, delta in zip(tparams.values(), delta_x)] fn_update_2=theano.function(inputs=[],updates=update_delta_x2+update_param) #return the update function of theano return fn_update_1, fn_update_2
4.7 Dragon(Caffe)实现
默认代码以我的Dragon框架为准,对Caffe代码进行了重写。
// hpp文件 templateclass AdaDeltaSolver :public SGDSolver < Dtype > { public: AdaDeltaSolver(const SolverParameter& param) :SGDSolver (param) { } AdaDeltaSolver(const string& param_file) :SGDSolver (param_file) { } protected: virtual void computeUpdateValue(int param_id, Dtype rate); virtual void applyUpdate(); }; // cpp文件