文章目录
一. 优化算法
1.1 基本算法
1.1.1 随机梯度下降(SGD)
我们从数据集 M M M中随机抽取 m m m个小批量样本,通过计算它们梯度均值,可以得到梯度的无偏估计。
换句话讲,就是在模型训练的时候,不是将整个数据集都作为输入来训练模型,而是从数据集中抽取一定的样本来作为输入。此时,参数的更新方式为:
θ
=
θ
−
α
∇
L
(
f
(
θ
;
x
)
,
y
)
\theta = \theta - \alpha∇L(f(\theta;x),y)
θ=θ−α∇L(f(θ;x),y)
其中
θ
\theta
θ为模型的参数,
α
\alpha
α为学习率,
∇
L
∇L
∇L为模型的梯度。
线性衰减学习率
在模型训练过程中,我们希望学习率能够随着模型的训练而发现相应的改变,使得模型能够更便于找到最优解。因此就引入了线性衰减学习率。
ϵ
k
=
(
1
−
α
)
ϵ
0
+
α
ϵ
τ
\epsilon_k=(1-\alpha)\epsilon_0+\alpha\epsilon_\tau
ϵk=(1−α)ϵ0+αϵτ
其中,
α
=
k
ϵ
\alpha={k\over\epsilon}
α=ϵk,
ϵ
k
\epsilon_k
ϵk表示第
k
k
k次迭代的学习率,
τ
\tau
τ被设置为需要反复遍历训练集几百次的迭代次数。通常
ϵ
τ
\epsilon_\tau
ϵτ大约设置为
ϵ
0
\epsilon_0
ϵ0的1%。
引入随机梯度下降的优点
- 每一步更新不依赖于数据集的大小。
1.1.2 动量
随机梯度下降存在学习过程过慢这一问题。而为了解决这一问题,我们引入了动量,记作
v
v
v,动量算法累计了之前梯度指数衰减的移动平均,并继续沿着该方向移动。引入动量后,参数更新的方式变为了:
v
=
α
v
−
ϵ
∇
L
(
f
(
x
;
θ
)
,
y
)
θ
=
θ
+
v
v=\alpha v-\epsilon∇L(f(x;\theta),y) \\ \theta=\theta+v
v=αv−ϵ∇L(f(x;θ),y)θ=θ+v
其中
α
\alpha
α表示之前梯度对衰减的贡献,
α
∈
[
0
,
1
)
\alpha∈[0,1)
α∈[0,1),
ϵ
\epsilon
ϵ表示学习率。
使用动量法相当于每次在进行参数更新的时候,会将之前的速度考虑进来,每个参数在各方向上的移动幅度不仅取决于当前的梯度,还取决于过去各个梯度在各个方向上是否一致,如果一个梯度一直沿着当前方向进行更新(水平方向),那么每次更新的幅度就越来越大,如果一个梯度在一个方向上不断变化(竖直方向),那么其更新幅度就会被衰减,这样我们就可以使用一个较大的学习率,使得收敛更快,同时梯度比较大的方向就会因为动量的关系每次更新的幅度减少。
Nesterov动量
动量算法存在一个问题,那就是动量
v
v
v会随着迭代的增加导致
v
v
v累计过大,导致在遇到最优解时直接“冲出去”,而不是收敛于该点。
Nesterov动量是在动量算法基础上的改进,引进Nesterov后,优化算法可以在“山坡”再次上升之前“减速”。其参数更新的方法为:
v
=
α
v
−
ϵ
∇
L
(
f
(
x
;
θ
−
α
v
)
,
y
)
θ
=
θ
+
v
v=\alpha v-\epsilon∇L(f(x;\theta-\alpha v),y) \\ \theta=\theta+v
v=αv−ϵ∇L(f(x;θ−αv),y)θ=θ+v
Nesterov动量和动量的区别在于梯度计算上,Nesterov动量的梯度计算是在施加了动量
v
v
v之后。
1.2 自适应学习率算法
1.2.1 AdaGrad
AdaGrad算法独立地适应所有模型参数的学习率,缩放每个参数反比于其梯度历史平方值总和的平方根。具有最大偏导的参数相应在参数空间有一个快速下降的学习率,而具有小偏导的参数在学习率上有较小的下降。
AdaGrad参数更新的方式为:
r
=
r
+
g
⊙
g
△
θ
=
−
ϵ
δ
+
r
⊙
g
θ
=
θ
+
△
θ
r=r+g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta
r=r+g⊙g△θ=−δ+rϵ⊙gθ=θ+△θ
其中
- ⊙ ⊙ ⊙表示逐元素乘法;
- ϵ \epsilon ϵ为学习率;
- δ \delta δ为一个小常量;通常为 1 0 − 7 10^{-7} 10−7;
- r r r为梯度积累变量,初始化为0;
- g g g为梯度。
优点:消除了手动调整学习率的需要。
缺点:会导致累计量
r
r
r在迭代过程中不断增加,最终导致学习率变得无限小,导致参数不再更新。
1.2.2 RMSProp
RMSProp是在AdaGrad的基础上继续的修改,使其在非凸问题中效果更好,它改变梯度累计为指数加权平均的移动平均,该算法使用指数衰减平均以丢弃遥远过去的历史。
RMSProp参数更新的方式为:
r
=
ρ
r
+
(
1
−
ρ
)
g
⊙
g
△
θ
=
−
ϵ
δ
+
r
⊙
g
θ
=
θ
+
△
θ
r=\rho r+(1-\rho)g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta
r=ρr+(1−ρ)g⊙g△θ=−δ+rϵ⊙gθ=θ+△θ
带Nesterov动量的RMSProp参数更新的方式为:
θ
^
=
θ
+
α
v
g
=
∇
L
(
f
(
x
;
θ
^
)
,
y
)
r
=
ρ
r
+
(
1
−
ρ
)
g
⊙
g
△
θ
=
−
ϵ
δ
+
r
⊙
g
θ
=
θ
+
△
θ
\hat\theta=\theta+\alpha v \\ g=∇L(f(x;\hat\theta),y) \\ r=\rho r+(1-\rho)g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta
θ^=θ+αvg=∇L(f(x;θ^),y)r=ρr+(1−ρ)g⊙g△θ=−δ+rϵ⊙gθ=θ+△θ
1.2.3 Adam
Adam更新参数:
- 更新一阶矩估计(相当于动量): s = ρ 1 s + ( 1 − ρ 1 ) g s=\rho_1s+(1-\rho_1)g s=ρ1s+(1−ρ1)g
- 更新二阶矩估计(相当于梯度累积): r = ρ 2 r + ( 1 − ρ 2 ) g ⊙ g r=\rho_2r+(1-\rho_2)g⊙g r=ρ2r+(1−ρ2)g⊙g
- 修正一阶矩估计偏差: s ^ = s 1 − ρ 1 t \hat s={s\over {1-\rho^t_1}} s^=1−ρ1ts
- 修正二阶矩估计偏差: r ^ = r 1 − ρ 1 t \hat r={r\over {1-\rho^t_1}} r^=1−ρ1tr
- 计算更新: △ θ = − ϵ s ^ r ^ + δ △\theta=-\epsilon{\hat s \over\sqrt{\hat r}+\delta} △θ=−ϵr^+δs^
- 更新: θ = θ + △ θ \theta=\theta+△\theta θ=θ+△θ
其中:
- ρ 1 , ρ 2 ∈ [ 0 , 1 ) \rho_1,\rho_2∈[0,1) ρ1,ρ2∈[0,1),一般 ρ 1 = 0.9 , ρ 2 = 0.999 \rho_1=0.9,\rho_2=0.999 ρ1=0.9,ρ2=0.999
- δ \delta δ为一个小常量;通常为 1 0 − 8 10^{-8} 10−8;
- 一阶、二阶矩估计变量 s , r s,r s,r初始值为0.
1.2.4其他优化算法:
AdaMax
在Adam中,
s
s
s的更新是基于梯度
g
g
g的
L
2
L_2
L2范数即
∣
g
∣
2
|g|^2
∣g∣2,我们可以将其推广到
L
p
L_{p}
Lp范数,即
s
=
ρ
1
s
+
(
1
−
ρ
1
)
∣
g
∣
p
s=\rho_1s+(1-\rho_1)|g|^p
s=ρ1s+(1−ρ1)∣g∣p
当
p
p
p很大时,会导致数值变得不稳定,所以通常采用
L
1
L_1
L1和
L
2
L_2
L2范数,而当
p
→
∞
p→∞
p→∞时,通常也会表现为稳定。
所以就有了:
s
=
ρ
1
∞
s
+
(
1
−
ρ
1
∞
)
∣
g
∣
∞
=
m
a
x
(
ρ
1
s
,
∣
g
∣
)
s=\rho_1^∞s+(1-\rho_1^∞)|g|^∞=max(\rho_1s,|g|)
s=ρ1∞s+(1−ρ1∞)∣g∣∞=max(ρ1s,∣g∣)
Nadam
Adam与Nesterov的结合。
AMSGrad
- 更新一阶矩估计(相当于动量): s = ρ 1 s + ( 1 − ρ 1 ) g s=\rho_1s+(1-\rho_1)g s=ρ1s+(1−ρ1)g
- 二阶矩估计(相当于梯度累积): r ^ = ρ 2 r + ( 1 − ρ 2 ) g ⊙ g \hat{r}=\rho_2r+(1-\rho_2)g⊙g r^=ρ2r+(1−ρ2)g⊙g
- 更新二阶矩估计: r = m a x ( r , r ^ ) r=max(r,\hat{r}) r=max(r,r^)
- 计算更新: △ θ = − ϵ s ^ r ^ + δ △\theta=-\epsilon{\hat s \over\sqrt{\hat r}+\delta} △θ=−ϵr^+δs^
- 更新: θ = θ + △ θ \theta=\theta+△\theta θ=θ+△θ
1.3 牛顿法
Hessian矩阵:
如果一个函数
F
F
F的二阶导数都存在,则
F
F
F的Hessian矩阵为
[
∂
2
F
∂
x
1
2
∂
2
F
∂
x
1
∂
x
2
⋅
⋅
⋅
∂
2
F
∂
x
1
∂
x
n
∂
2
F
∂
x
2
∂
x
1
∂
2
F
∂
x
2
2
⋅
⋅
⋅
∂
2
F
∂
x
2
∂
x
n
.
.
.
.
.
.
.
.
.
.
.
.
∂
2
F
∂
x
n
∂
x
1
∂
2
F
∂
x
n
∂
x
2
⋅
⋅
⋅
∂
2
F
∂
x
n
2
]
\begin{bmatrix} {∂^2F\over∂x_1^2}&{∂^2F\over∂x_1∂x_2}&···&{∂^2F\over∂x_1∂x_n} \\ {∂^2F\over∂x_2∂x_1}&{∂^2F\over∂x_2^2}&···&{∂^2F\over∂x_2∂x_n} \\.&.&.&. \\.&.&.&. \\.&.&.&. \\{∂^2F\over∂x_n∂x_1}&{∂^2F\over∂x_n∂x_2}&···&{∂^2F\over∂x_n^2} \end{bmatrix}
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡∂x12∂2F∂x2∂x1∂2F...∂xn∂x1∂2F∂x1∂x2∂2F∂x22∂2F...∂xn∂x2∂2F⋅⋅⋅⋅⋅⋅...⋅⋅⋅∂x1∂xn∂2F∂x2∂xn∂2F...∂xn2∂2F⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
牛顿法解决优化问题:
对于函数
f
(
x
)
f(x)
f(x),我们要想求得
f
(
x
)
f(x)
f(x)的最小值,实际上就是找到
f
(
x
)
f(x)
f(x)一阶导数为0,二阶导数大于0的点,将这些点记为
x
i
x_i
xi,那么
f
m
i
n
(
x
)
=
m
i
n
(
f
(
x
i
)
)
f_{min}(x)=min(f(x_i))
fmin(x)=min(f(xi))。
那么又如何通过类似于梯度下降算法的迭代算法帮助我们找到 f m i n ( x ) f_{min}(x) fmin(x)呢?
现在我们假设
f
(
x
)
f(x)
f(x)具有二阶偏导数,在迭代第
k
k
k次时的自变量为
x
(
k
)
x^{(k)}
x(k),那么可将
f
(
x
)
f(x)
f(x)在
x
(
k
)
x^{(k)}
x(k)附近进行二阶泰勒展开:
f
(
x
)
=
f
(
x
(
k
)
)
+
g
k
T
(
x
−
x
(
k
)
)
+
1
2
(
x
−
x
(
k
)
)
T
H
(
x
(
k
)
)
(
x
−
x
(
k
)
)
{f(x) = f(x^{(k)})+g^T_k(x-x^{(k)})+{1\over2}(x-x^{(k)})^TH(x^{(k)})(x-x^{(k)})}
f(x)=f(x(k))+gkT(x−x(k))+21(x−x(k))TH(x(k))(x−x(k))
其中,
g
k
T
=
∇
f
(
x
(
k
)
)
g^T_k=∇f(x^{(k)})
gkT=∇f(x(k))是
f
(
x
)
f(x)
f(x)在
x
(
k
)
x^{(k)}
x(k)点处的梯度。
H
(
x
(
k
)
)
H(x^{(k)})
H(x(k))是
f
(
x
)
f(x)
f(x)在
x
(
k
)
x^{(k)}
x(k)点处的Hessian矩阵。
为了找到一阶导数为0,二阶导数大于0的点。我们需要对二阶展开式再次求导,即:
∇
f
(
x
)
=
g
k
+
H
k
(
x
−
x
(
k
)
)
∇f(x) = g_k+H_k(x-x^{(k)})
∇f(x)=gk+Hk(x−x(k))
令
∇
f
(
x
)
=
0
∇f(x)=0
∇f(x)=0,得到
g
k
+
H
k
(
x
−
x
(
k
)
)
=
0
g_k+H_k(x-x^{(k)})=0
gk+Hk(x−x(k))=0,那么
x
=
x
(
k
)
−
H
k
−
1
g
k
x=x^{(k)}-H_k^{-1}g_k
x=x(k)−Hk−1gk
于是我们就得到了迭代求解极小值点
x
x
x的方法,即
x
(
k
+
1
)
=
x
(
k
)
−
H
k
−
1
g
k
x^{(k+1)}=x^{(k)}-H_k^{-1}g_k
x(k+1)=x(k)−Hk−1gk
牛顿法存在的缺点:
- 计算量太大。Hessian矩阵中元素的个数是参数个数的平方,对与参数数目为 k k k,牛顿法需要计算 k × k k×k k×k矩阵的逆,计算复杂度为 O ( k 3 ) O(k^3) O(k3)。
- Hessian矩阵需满足正定。若果Heissian矩阵不满足正定,会导致更新朝着错误的方向移动。
拟牛顿法:
思想:不用二阶偏导数而构造出可以近似Hessian矩阵(或Heissian矩阵的逆)的正定对称矩阵。
二. 一些优化算法的代码实现
2.1 批量随机梯度下降:
def update_parameters(parameters,grads,learning_rate):
"""
使用梯度下降更新参数
:param parameters:包含参数的字典
:param grads:包含梯度的字典
:param learning_rate:学习率,为超参数
:return:
parameters:更新后的参数
"""
L = len(parameters) // 2
for l in range(L):
parameters["w" + str(l + 1)] = parameters["w" + str(l + 1)] - learning_rate * grads["dw" + str(l + 1)]
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads["db" + str(l + 1)]
return parameters
2.2带动量的梯度下降
def update_parameters_with_momentun(parameters,grads,v,beta,learning_rate):
"""
使用动量更新参数
:param parameters:个字典类型的变量,包含了以下字段:
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
:param grads:一个包含梯度值的字典变量,具有以下字段:
grads["dW" + str(l)] = dWl
grads["db" + str(l)] = dbl
:param v:包含当前速度的字典变量,具有以下字段:
v["dW" + str(l)] = ...
v["db" + str(l)] = ...
:param beta:超参数,动量,实数
:param learning_rate:学习率,实数
:return:
:parameters - 更新后的参数字典
:v - 包含了更新后的速度变量
"""
L = len(parameters) // 2
for l in range(L):
v["dw" + str(l + 1)] = beta * v["dw" + str(l + 1)] + (1 - beta) * grads["dw" + str(l + 1)]
v["db" + str(l + 1)] = beta * v["db" + str(l + 1)] + (1 - beta) * grads["db" + str(l + 1)]
parameters["w" + str(l + 1)] = parameters["w" + str(l + 1)] - learning_rate * v["dw" + str(l + 1)]
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * v["db" + str(l + 1)]
return parameters,v
2.3 Adam
def update_parameters_with_adam(parameters, grads, v, s, t, learning_rate=0.01, beta1=0.9, beta2=0.999, epsilon=1e-8):
"""
使用Adam更新参数
参数:
parameters - 包含了以下字段的字典:
parameters['W' + str(l)] = Wl
parameters['b' + str(l)] = bl
grads - 包含了梯度值的字典,有以下key值:
grads['dW' + str(l)] = dWl
grads['db' + str(l)] = dbl
v - Adam的变量,第一个梯度的移动平均值,是一个字典类型的变量
s - Adam的变量,平方梯度的移动平均值,是一个字典类型的变量
t - 当前迭代的次数
learning_rate - 学习率
beta1 - 动量,超参数,用于第一阶段,使得曲线的Y值不从0开始(参见天气数据的那个图)
beta2 - RMSprop的一个参数,超参数
epsilon - 防止除零操作(分母为0)
返回:
parameters - 更新后的参数
v - 第一个梯度的移动平均值,是一个字典类型的变量
s - 平方梯度的移动平均值,是一个字典类型的变量
"""
L = len(parameters) // 2
v_corrected = {} # 偏差修正后的值
s_corrected = {} # 偏差修正后的值
for l in range(L):
# 梯度的移动平均值,输入:"v , grads , beta1",输出:" v "
v["dw" + str(l + 1)] = beta1 * v["dw" + str(l + 1)] + (1 - beta1) * grads["dw" + str(l + 1)]
v["db" + str(l + 1)] = beta1 * v["db" + str(l + 1)] + (1 - beta1) * grads["db" + str(l + 1)]
# 计算第一阶段的偏差修正后的估计值,输入"v , beta1 , t" , 输出:"v_corrected"
v_corrected["dw" + str(l + 1)] = v["dw" + str(l + 1)] / (1 - np.power(beta1, t) + epsilon)
v_corrected["db" + str(l + 1)] = v["db" + str(l + 1)] / (1 - np.power(beta1, t) + epsilon)
# 计算平方梯度的移动平均值,输入:"s, grads , beta2",输出:"s"
s["dw" + str(l + 1)] = beta2 * s["dw" + str(l + 1)] + (1 - beta2) * np.square(grads["dw" + str(l + 1)])
s["db" + str(l + 1)] = beta2 * s["db" + str(l + 1)] + (1 - beta2) * np.square(grads["db" + str(l + 1)])
# 计算第二阶段的偏差修正后的估计值,输入:"s , beta2 , t",输出:"s_corrected"
s_corrected["dw" + str(l + 1)] = s["dw" + str(l + 1)] / (1 - np.power(beta2, t) + epsilon)
s_corrected["db" + str(l + 1)] = s["db" + str(l + 1)] / (1 - np.power(beta2, t) + epsilon)
# 更新参数,输入: "parameters, learning_rate, v_corrected, s_corrected, epsilon". 输出: "parameters".
parameters["w" + str(l + 1)] = parameters["w" + str(l + 1)] - learning_rate * (
v_corrected["dw" + str(l + 1)] / np.sqrt(s_corrected["dw" + str(l + 1)] + epsilon))
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * (
v_corrected["db" + str(l + 1)] / np.sqrt(s_corrected["db" + str(l + 1)] + epsilon))
return (parameters, v, s)