在深度学习中我们定义了损失函数以后,会采取各种各样的方法来降低损失函数的数值,从而使模型参数不断的逼近于真实数据的表达。不过损失函数的数值只是我们用来优化模型中参数的一个参考量,我们是通过优化损失函数来间接地优化模型参数,并提高模型的度量指标。而优化算法的作用就是加快模型的收敛,取得最优值。
优化算法可以分为两大类,一类是基于 Momentum 的优化算法, 另一类是自适应的优化算法。
SGD (stochastic gradient descent) 随机梯度下降。随机梯度下降系列可以分为
值得注意的是,通常我们在说随机梯度下降算法的时候, 实际上都是说的 mini-batch SGD + Momentum, 因为它运用的最多。
SGD 是每次随机选择一个数据,喂给模型,然后更新参数。显然这种方式的缺点是由于每次喂入的数据量太少,模型学不到什么东西。
显然 full-batch SGD 是另一个极端, 上面的 SGD 是一条数据一条数据的喂,而 full-batch SGD 则是每一次迭代都将所有数据都喂进去,显然这将是非常大的计算量。所以现实中常常采用的是 mini-batch SGD, 其从训练集中少量采样一些数据拿来计算。这里有一个问题是为什么可以用 mini-batch 来替代 full-batch?
mini-batch SGD 的过程大致如下:
mini-batch SGD |
---|
输入:学习率 ϵ k \epsilon_k ϵk ,初始参数 θ \theta θ |
while 未满足停止条件 do |
从训练集中采集包含 m m m 个样本 { x ( 1 ) , x ( 2 ) , . . . , x ( m ) } \{x^{(1)},x^{(2)},...,x^{(m)}\} {x(1),x(2),...,x(m)} 的小批量,其中数据 x i {x^{i}} xi 和对应目标 y ( i ) y^{(i)} y(i) |
计算梯度估计: ∇ θ k − 1 J ( θ k − 1 ) ← 1 m ∇ θ k − 1 L ( f ( x ( i ) , θ k − 1 ) , y ( i ) ) \nabla_{\theta_{k-1}} J(\theta_{k-1}) \leftarrow\frac{1}{m}\nabla_{\theta_{k-1}} L(f(x^{(i)}, \theta_{k-1}),y^{(i)}) ∇θk−1J(θk−1)←m1∇θk−1L(f(x(i),θk−1),y(i)) |
更新参数: θ k ← θ k − 1 − ϵ k ∇ θ k − 1 J ( θ k − 1 ) \theta_k \leftarrow\theta_{k-1}-\epsilon_k\nabla_{\theta_{k-1}} J(\theta_{k-1}) θk←θk−1−ϵk∇θk−1J(θk−1) |
end while |
上面的学习率 ϵ \epsilon ϵ 是需要学习的超参数。因为仅仅用一个 case 或者一个小的 batch 的 case 来计算梯度,所以仅仅是对训练数据集梯度的一个估计。
总的来说上面的三种 SGD 算法所面临的问题是
针对上面问题,SGD + Momentum 被提了出来,SGD + Momentum 主要有了以下提升:
首先对比下单纯的 SGD 和 SGD + Momentum 的更新方式的区别。
名字 | 更新方式 |
---|---|
单纯 SGD: | θ ← θ − ϵ ∇ θ J ( θ ) \theta \leftarrow\theta-\epsilon \nabla_\theta J(\theta) θ←θ−ϵ∇θJ(θ) |
SGD + Momentum: | v t ← α v t − 1 − ϵ ∇ θ J ( θ ) v_t \leftarrow\alpha v_{t-1}-\epsilon \nabla_\theta J(\theta) vt←αvt−1−ϵ∇θJ(θ); θ ← θ + v t \theta \leftarrow\theta+v_t θ←θ+vt |
上面的 v v v 我们称为速度, α \alpha α 是动量参数, ∇ θ J ( θ ) \nabla_\theta J(\theta) ∇θJ(θ) 是当前轮次计算得到的梯度。
从公式可以看出, v v v 会记录上一次的梯度信息,通过动量参数 α \alpha α 来控制上一次的更新量对本次更新量的影响,注意它是一个非负项,所以它起到了“惯性”的作用。
SGD + Momentum |
---|
输入:学习率 ϵ k \epsilon_k ϵk ,初始参数 θ \theta θ,动量参数 α \alpha α ,初始速度 v v v |
while 未满足停止条件 do |
从训练集中采集包含 m m m 个样本 { x ( 1 ) , x ( 2 ) , . . . , x ( m ) } \{x^{(1)},x^{(2)},...,x^{(m)}\} {x(1),x(2),...,x(m)} 的小批量,其中数据 x i {x^{i}} xi 和对应目标 y ( i ) y^{(i)} y(i) |
计算梯度估计: ∇ θ k − 1 J ( θ k − 1 ) ← 1 m ∇ θ k − 1 L ( f ( x ( i ) , θ k − 1 ) , y ( i ) ) \nabla_{\theta_{k-1}} J(\theta_{k-1}) \leftarrow\frac{1}{m}\nabla_{\theta_{k-1}} L(f(x^{(i)}, \theta_{k-1}),y^{(i)}) ∇θk−1J(θk−1)←m1∇θk−1L(f(x(i),θk−1),y(i)) |
更新速度: v k ← α v k − 1 − ϵ k ∇ θ k − 1 J ( θ k − 1 ) v_k \leftarrow \alpha v_{k-1}-\epsilon_k\nabla_{\theta_{k-1}} J(\theta_{k-1}) vk←αvk−1−ϵk∇θk−1J(θk−1) |
更新参数: θ k ← θ k − 1 + v k \theta_k \leftarrow\theta_{k-1} + v_k θk←θk−1+vk |
end while |
其中:
v k = α v k − 1 − ϵ k ( 1 − α ) ∇ θ k − 1 J ( θ k − 1 ) = α v k − 1 − ϵ k ∇ θ k − 1 J ( θ k − 1 ) v_k = \alpha v_{k-1}-\epsilon_k(1-\alpha)\nabla_{\theta_{k-1}} J(\theta_{k-1}) = \alpha v_{k-1}-\epsilon_k\nabla_{\theta_{k-1}} J(\theta_{k-1}) vk=αvk−1−ϵk(1−α)∇θk−1J(θk−1)=αvk−1−ϵk∇θk−1J(θk−1)
上面的学习率 ϵ \epsilon ϵ 和 α \alpha α 是需要学习的超参数。
Nesterov 首先(试探性地)在之前积累的梯度方向(棕色向量)前进一大步,再根据当前地情况修正,以得到最终的前进方向(绿色向量)。这种基于预测的更新方法,使我们避免过快地前进,并提高了算法地响应能力。
具体来说,Nesterov 是先基于当前的参数和 momentum,预测得到一个新的参数 θ ~ \tilde{\theta} θ~ ,然后基于这个新的参数来计算梯度。其数学表达式如下:
θ ~ = θ k − 1 + α v k − 1 \tilde{\theta} = \theta_{k-1} + \alpha v_{k-1} θ~=θk−1+αvk−1
v k = α v k − 1 − ϵ k ∇ θ k − 1 J ( θ ~ ) v_k = \alpha v_{k-1}-\epsilon_k\nabla_{\theta_{k-1}} J(\tilde{\theta}) vk=αvk−1−ϵk∇θk−1J(θ~)
θ k = θ k − 1 + v k \theta_k = \theta_{k-1} + v_k θk=θk−1+vk
本来 Nesterov 通过预测的方式可以起到加速收敛的作用,但是上面的公式在实际使用中却比较烦琐,因此对上面的公式进行了修改,使得在实际使用中比较简洁。修改后的公式如下:
v k = α v k − 1 − ϵ k ∇ θ k − 1 J ( θ k − 1 ) v_k = \alpha v_{k-1}-\epsilon_k\nabla_{\theta_{k-1}} J(\theta_{k-1}) vk=αvk−1−ϵk∇θk−1J(θk−1)
θ k = θ k − 1 + v k − α J ( θ k − 1 ) \theta_k = \theta_{k-1} + v_k - \alpha J(\theta_{k-1}) θk=θk−1+vk−αJ(θk−1)
上面已经说了为什么要做这个修改,还有一个问题是为什么能这样修改?其实通过简单的迭代计算可以发现两种表达方式是等价的。
Nesterov |
---|
输入:学习率 ϵ k \epsilon_k ϵk ,初始参数 θ \theta θ,动量参数 α \alpha α ,初始速度 v v v |
while 未满足停止条件 do |
从训练集中采集包含 m m m 个样本 { x ( 1 ) , x ( 2 ) , . . . , x ( m ) } \{x^{(1)},x^{(2)},...,x^{(m)}\} {x(1),x(2),...,x(m)} 的小批量,其中数据 x i {x^{i}} xi 和对应目标 y ( i ) y^{(i)} y(i) |
计算预测参数: θ ~ = θ k − 1 + α v k − 1 \tilde{\theta} = \theta_{k-1} + \alpha v_{k-1} θ~=θk−1+αvk−1 |
计算梯度估计: ∇ θ k − 1 J ( θ k − 1 ) ← 1 m ∇ θ k − 1 L ( f ( x ( i ) , θ ~ ) , y ( i ) ) \nabla_{\theta_{k-1}} J(\theta_{k-1}) \leftarrow\frac{1}{m}\nabla_{\theta_{k-1}} L(f(x^{(i)}, \tilde{\theta}),y^{(i)}) ∇θk−1J(θk−1)←m1∇θk−1L(f(x(i),θ~),y(i)) |
更新速度: v k ← α v k − 1 − ϵ k ∇ θ k − 1 J ( θ k − 1 ) v_k \leftarrow \alpha v_{k-1}-\epsilon_k\nabla_{\theta_{k-1}} J(\theta_{k-1}) vk←αvk−1−ϵk∇θk−1J(θk−1) |
更新参数: θ k ← θ k − 1 + α v k − ϵ k ∇ θ k − 1 J ( θ k − 1 ) \theta_k \leftarrow\theta_{k-1} + \alpha v_k - \epsilon_{k}\nabla_{\theta_{k-1}} J(\theta_{k-1}) θk←θk−1+αvk−ϵk∇θk−1J(θk−1) |
end while |
学习率作为一个超参数,就会有调参的困扰,这是工业界公认的事情。动量算法只能在一定程度上缓解调参的压力,但代价是又多引入了一个超参数。而下面讲的方法都是学习率自适应的优化方式,比如对不同的 feature 有不同的 learning rate。
Adagrad算法是一个比较新的优化算法,由 John Duchi 等人在 2011 年于 Adaptive Subgradient Methods for
Online Learning and Stochastic Optimization 提出。 这种优化算法的优点是对不同的参数调整学习率,具体而言,对梯度比较小参数进行大的更新,对梯度比较大的参数进行小的更新。
先来看看它的数学表达式。
r k = r k − 1 + ∇ θ k − 1 J ( θ k − 1 ) ⨀ ∇ θ k − 1 J ( θ k − 1 ) r_{k} = r_{k-1} + \nabla_{\theta_{k-1}} J(\theta_{k-1}) \bigodot \nabla_{\theta_{k-1}} J(\theta_{k-1}) rk=rk−1+∇θk−1J(θk−1)⨀∇θk−1J(θk−1)
θ k = θ t − 1 − ϵ r k + σ ⨀ ∇ θ k − 1 J ( θ k − 1 ) \theta_k = \theta_{t-1} - \frac{\epsilon}{\sqrt{r_k + \sigma}} \bigodot \nabla_{\theta_{k-1}} J(\theta_{k-1}) θk=θt−1−rk+σϵ⨀∇θk−1J(θk−1)
其中 ⨀ \bigodot ⨀ 表示矩阵按位相乘,上面的式子也可以理解为平方。具体的计算过程如下表。
Adagrad |
---|
输入:全局学习率 ϵ \epsilon ϵ ,初始参数 θ \theta θ,小常数 σ \sigma σ(一般取值为 1 0 − 7 10^{-7} 10−7) ,初始化梯度累计量 r = 0 r = 0 r=0 |
while 未满足停止条件 do |
从训练集中采集包含 m m m 个样本 { x ( 1 ) , x ( 2 ) , . . . , x ( m ) } \{x^{(1)},x^{(2)},...,x^{(m)}\} {x(1),x(2),...,x(m)} 的小批量,其中数据 x i {x^{i}} xi 和对应目标 y ( i ) y^{(i)} y(i) |
计算梯度估计: ∇ θ k − 1 J ( θ k − 1 ) ← 1 m ∇ θ k − 1 L ( f ( x ( i ) , θ k − 1 ) , y ( i ) ) \nabla_{\theta_{k-1}} J(\theta_{k-1}) \leftarrow\frac{1}{m}\nabla_{\theta_{k-1}} L(f(x^{(i)}, \theta_{k-1}),y^{(i)}) ∇θk−1J(θk−1)←m1∇θk−1L(f(x(i),θk−1),y(i)) |
累积平方梯度: r k ← r k − 1 + ∇ θ k − 1 J ( θ k − 1 ) ⨀ ∇ θ k − 1 J ( θ k − 1 ) r_k \leftarrow r_{k-1}+\nabla_{\theta_{k-1}} J(\theta_{k-1}) \bigodot \nabla_{\theta_{k-1}} J(\theta_{k-1}) rk←rk−1+∇θk−1J(θk−1)⨀∇θk−1J(θk−1) |
计算参数更新量: Δ θ ← − ϵ r k + σ ⨀ Δ θ k − 1 J ( θ k − 1 ) \Delta \theta \leftarrow- \frac{\epsilon}{\sqrt{r_k + \sigma}} \bigodot \Delta_{\theta_{k-1}} J(\theta_{k-1}) Δθ←−rk+σϵ⨀Δθk−1J(θk−1) |
更新参数: θ k ← θ k − 1 + ∇ θ \theta_k \leftarrow\theta_{k-1} + \nabla \theta θk←θk−1+∇θ |
end while |
从公式我们可以看出, r r r 会一直积累,越来越大,就会导致上面参数更新量的公式的分母部分越来越大,那么更新量会越来越小,最后趋近于零。所以在实际使用中 Adagrad 应用的比较少,但是 Adagrad 的思想却是开创了新的时代。
RMSProp 是 Hinton 大神于 2012 年在一门叫 Neural Networks for Machine Learning 的在线课程中提出(并未正式发表,要不怎么叫大神呢)。
RMSProp 实际上是 Adagrad + Momentum。它的数学表达式如下。
r k = β r k − 1 + ( 1 − β ) ∇ θ k − 1 J ( θ k − 1 ) ⨀ ∇ θ k − 1 J ( θ k − 1 ) r_{k} = \beta r_{k-1} + (1-\beta)\nabla_{\theta_{k-1}} J(\theta_{k-1}) \bigodot \nabla_{\theta_{k-1}} J(\theta_{k-1}) rk=βrk−1+(1−β)∇θk−1J(θk−1)⨀∇θk−1J(θk−1)
θ k = θ t − 1 − ϵ r k + σ ⨀ ∇ θ k − 1 J ( θ k − 1 ) \theta_k = \theta_{t-1} - \frac{\epsilon}{\sqrt{r_k + \sigma}} \bigodot \nabla_{\theta_{k-1}} J(\theta_{k-1}) θk=θt−1−rk+σϵ⨀∇θk−1J(θk−1)
由于RMSProp的计算过程和 Adagrad 的相似,这里就不再赘述了。
RMSProp 运用的并是很多,主要在 WGAN,MobileNet 中比较常见。
前面提到 Adagrad 的学习率是单调递减的,而 Adadelta 的提出就是为了解决这个问题。另外 Adadelta 不再依赖全局学习率 ADADELTA: AN ADAPTIVE LEARNING RATE METHOD。
Adagrad会累加之前所有的梯度平方,而Adadelta只累加固定大小的项,并且也不直接存储这些项,仅仅是近似计算对应的平均值。其计算过程如下。
Adadelta |
---|
输入:初始参数 θ \theta θ,小常数 σ \sigma σ ,初始化梯度累计量 E [ g 2 ] 0 = 0 , E [ ∇ θ 2 ] 0 = 0 E[g^2]_0=0, E[\nabla\theta^2]_0=0 E[g2]0=0,E[∇θ2]0=0 |
while 未满足停止条件 do |
从训练集中采集包含 m m m 个样本 { x ( 1 ) , x ( 2 ) , . . . , x ( m ) } \{x^{(1)},x^{(2)},...,x^{(m)}\} {x(1),x(2),...,x(m)} 的小批量,其中数据 x i {x^{i}} xi 和对应目标 y ( i ) y^{(i)} y(i) |
计算梯度估计: g k = ∇ θ k − 1 J ( θ k − 1 ) ← 1 m ∇ θ k − 1 L ( f ( x ( i ) , θ k − 1 ) , y ( i ) ) g_{k} = \nabla_{\theta_{k-1}} J(\theta_{k-1}) \leftarrow\frac{1}{m}\nabla_{\theta_{k-1}} L(f(x^{(i)}, \theta_{k-1}),y^{(i)}) gk=∇θk−1J(θk−1)←m1∇θk−1L(f(x(i),θk−1),y(i)) |
累积平方梯度期望: E [ g 2 ] k ← ρ E [ g 2 ] k + ( 1 − ρ ) g k 2 E[g^2]_k \leftarrow \rho E[g^2]_k+(1-\rho)g_{k}^{2} E[g2]k←ρE[g2]k+(1−ρ)gk2 |
计算参数更新量: Δ θ ← − E [ Δ θ 2 ] k − 1 + σ E [ g 2 ] k + σ ∇ θ k − 1 J ( θ k − 1 ) \Delta \theta \leftarrow- \frac{\sqrt{E[\Delta \theta^2]_{k-1}+\sigma}}{\sqrt{E[g^2]_k + \sigma}} \nabla_{\theta_{k-1}} J(\theta_{k-1}) Δθ←−E[g2]k+σE[Δθ2]k−1+σ∇θk−1J(θk−1) |
缓存累积更新: E [ Δ θ 2 ] k ← ρ E [ Δ θ 2 ] k − 1 + ( 1 − ρ ) Δ θ 2 E[\Delta \theta^2]_{k} \leftarrow \rho E[\Delta \theta^2]_{k-1} + (1 - \rho)\Delta \theta^2 E[Δθ2]k←ρE[Δθ2]k−1+(1−ρ)Δθ2 |
更新参数: θ k ← θ k − 1 + Δ θ \theta_k \leftarrow\theta_{k-1} + \Delta \theta θk←θk−1+Δθ |
end while |
Adam 是 Diederik P. Kingma 等人在 2014 年于 Adam: A Method for Stochastic Optimization 中提出的优化算法。Adam 是目前运用的比较广泛的一种优化算法。另外 Adam 也有多种改进版本,比如 AdaMax, Nadam等。Adam 的优点主要有:
Adam 利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率,使学习率在每一次更新的时候都有一个固定范围的步长,让参数更新时保持稳定。其计算公式如下:
m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ θ k − 1 J ( θ k − 1 ) m_t = \beta_1 m_{t-1} + (1-\beta_1) \nabla_{\theta_{k-1}} J(\theta_{k-1}) mt=β1mt−1+(1−β1)∇θk−1J(θk−1)
v θ t = β 2 v θ t − 1 + ( 1 − β 2 ) ∇ θ k − 1 J ( θ k − 1 ) ⊙ ∇ θ k − 1 J ( θ k − 1 ) v_{\theta_t} = \beta_2 v_{\theta_{t-1}} + (1-\beta_2) \nabla_{\theta_{k-1}} J(\theta_{k-1}) \odot \nabla_{\theta_{k-1}} J(\theta_{k-1}) vθt=β2vθt−1+(1−β2)∇θk−1J(θk−1)⊙∇θk−1J(θk−1)
m t ^ = m t 1 − β 1 t \hat{m_t} = \frac {m_t} {1- \beta_{1}^{t}} mt^=1−β1tmt
v θ t ^ = v θ t 1 − β 2 t \hat{v_{\theta_t}} = \frac {v_{\theta_t}} {1- \beta_{2}^{t}} vθt^=1−β2tvθt
θ t = θ t − 1 − m t ^ v θ t ^ + σ ϵ \theta_t = \theta_{t-1} - \frac {\hat{m_t}}{\sqrt{\hat{v_{\theta_t}}+\sigma}} \epsilon θt=θt−1−vθt^+σmt^ϵ
其中一般的 β 1 = 0.9 \beta_1 = 0.9 β1=0.9, β 2 = 0.999 \beta_2 = 0.999 β2=0.999, ϵ = 0.001 \epsilon = 0.001 ϵ=0.001, σ = 1 0 − 7 \sigma = 10^{-7} σ=10−7。从公式可以看出,因为 1 − β t 1-\beta^t 1−βt 是一个小于 1 的值,刚开始时 t 很小, 1 − β t 1-\beta^t 1−βt 的取值也很小,这样一阶矩估计和二阶矩估计更新的幅度会比较大, 但是当 t 较大时, 1 − β t 1-\beta^t 1−βt 将会趋近于 1 ,一阶矩估计和二阶矩估计开始趋于稳定。
其具体的计算过程如下。
Adam |
---|
输入:全局学习率 ϵ \epsilon ϵ,初始参数 θ \theta θ,小常数 σ \sigma σ ,矩估计的指数衰减率 β 1 , β 2 \beta_1, \beta_2 β1,β2,初始化一阶和二阶矩变量 m = 0 , v = 0 m=0, v=0 m=0,v=0,初始化时间步长 t = 0 t=0 t=0。 |
while 未满足停止条件 do |
从训练集中采集包含 m m m 个样本 { x ( 1 ) , x ( 2 ) , . . . , x ( m ) } \{x^{(1)},x^{(2)},...,x^{(m)}\} {x(1),x(2),...,x(m)} 的小批量,其中数据 x i {x^{i}} xi 和对应目标 y ( i ) y^{(i)} y(i) |
计算梯度估计: ∇ θ k − 1 J ( θ k − 1 ) ← 1 m ∇ θ k − 1 L ( f ( x ( i ) , θ k − 1 ) , y ( i ) ) \nabla_{\theta_{k-1}} J(\theta_{k-1}) \leftarrow\frac{1}{m}\nabla_{\theta_{k-1}} L(f(x^{(i)}, \theta_{k-1}),y^{(i)}) ∇θk−1J(θk−1)←m1∇θk−1L(f(x(i),θk−1),y(i)) |
t ← t + 1 t \leftarrow t+1 t←t+1 |
更新有偏一阶矩估计: m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ θ k − 1 J ( θ k − 1 ) m_t = \beta_1 m_{t-1} + (1-\beta_1) \nabla_{\theta_{k-1}} J(\theta_{k-1}) mt=β1mt−1+(1−β1)∇θk−1J(θk−1) |
更新有偏二阶矩估计: v θ t = β 2 v θ t − 1 + ( 1 − β 2 ) ∇ θ k − 1 J ( θ k − 1 ) ⊙ ∇ θ k − 1 J ( θ k − 1 ) v_{\theta_t} = \beta_2 v_{\theta_{t-1}} + (1-\beta_2) \nabla_{\theta_{k-1}} J(\theta_{k-1}) \odot \nabla_{\theta_{k-1}} J(\theta_{k-1}) vθt=β2vθt−1+(1−β2)∇θk−1J(θk−1)⊙∇θk−1J(θk−1) |
修正一阶矩的偏差: m t ^ = m t 1 − β 1 t \hat{m_t} = \frac {m_t} {1- \beta_{1}^{t}} mt^=1−β1tmt |
修正二阶矩的偏差: v θ t ^ = v θ t 1 − β 2 t \hat{v_{\theta_t}} = \frac {v_{\theta_t}} {1- \beta_{2}^{t}} vθt^=1−β2tvθt |
计算参数更新量: Δ θ ← − m t ^ v θ t ^ + σ ϵ \Delta \theta \leftarrow- \frac {\hat{m_t}}{\sqrt{\hat{v_{\theta_t}}+\sigma}}\epsilon Δθ←−vθt^+σmt^ϵ |
更新参数: θ t ← θ t − 1 + Δ θ \theta_t \leftarrow\theta_{t-1} + \Delta \theta θt←θt−1+Δθ |
end while |
值得一提的是,虽然 Adam 会自动调整学习率,但是实践证明,在训练的时候采用学习率衰减的策略依然有效。更多可以参考 Adam和学习率衰减(learning rate decay) 。
前面我们提到了 Adam 收敛快,但是 momentum based 的方法精度更好,所以现在有一个发展趋势是一开始用 Adam ,提升速度,后期采用 momentum based 的方法,取得好的精度,但是仅仅是一个发展方向,还并没有提出较好的方法来融合这两中优化方法。