深度学习之优化器算法及python实现
opitimizers
1. 优化器算法
1.1 SGD 算法
没有动量的SGD算法:
θ←θ−lr∗g\theta \leftarrow \theta - lr * gθ←θ−lr∗g
其中θ\thetaθ是参数,lrlrlr是学习率,ggg是梯度。
有动量的SGD算法:
v←α∗v+lr∗gv \leftarrow \alpha * v + lr * gv←α∗v+lr∗g
θ←θ−v\theta \leftarrow \theta - vθ←θ−v
其中vvv是速度。
加入动量以后首先学习速率会增加,并且能够在一定程度上缓解随机梯度下降法收敛不稳定的问题,并且有一定的摆脱陷入局部最优解的能力。
1.2 AdaGrad算法
算法:
r←r+g∗gr \leftarrow r + g * gr←r+g∗g
Δθ=lr∗geps+r\Delta \theta = \frac {lr * g} {eps + \sqrt {r}}Δθ=eps+rlr∗g
θ←θ−Δθ\theta \leftarrow \theta - \Delta \thetaθ←θ−Δθ
其中epsepseps是一个极小正数值,就是为了避免分母为零。
AdaGrad缩放参数,并且于幅度等于所有梯度历史平方值总和的平方根。所以在损失大偏导出学习速率快,损失偏导大的地方学习速率会小一些。
1.3 RMSProp算法
算法:
r←ρr+(1−ρ)g∗gr \leftarrow \rho r + (1 - \rho)g * gr←ρr+(1−ρ)g∗g
Δθ=lr∗geps+r\Delta \theta = \frac {lr * g} {eps + \sqrt {r}}Δθ=eps+rlr∗g
θ←θ−Δθ\theta \leftarrow \theta - \Delta \thetaθ←θ−Δθ
其中ρ\rhoρ是0-1的一个调节因子。
RMSProp算法比AdaGrad算法好的一点是在积累平方梯度时,可以调整过去积累和当前的比重。
1.4 Adam算法
算法:
t←t+1t \leftarrow t + 1t←t+1
s←ρ1s+(1−ρ1)gs \leftarrow \rho_1 s + (1 - \rho_1) gs←ρ1s+(1−ρ1)g
r←ρ2r+(1−ρ2)g2r \leftarrow \rho_2 r + (1 - \rho_2) g^2r←ρ2r+(1−ρ2)g2
s^←s/(1−ρ1t)\hat s \leftarrow s /(1 - \rho_1^t)s^←s/(1−ρ1t)
r^←r/(1−ρ2t)\hat r \leftarrow r / (1 - \rho_2^t)r^←r/(1−ρ2t)
Δθ=s^eps+r^\Delta \theta = \frac {\hat s} {eps + \sqrt {\hat r}}Δθ=eps+r^s^
θ←θ−Δθ\theta \leftarrow \theta - \Delta \thetaθ←θ−Δθ
其中s,r,ρ1,ρ2s,r,\rho_1,\rho_2s,r,ρ1,ρ2分别是一阶矩,二阶矩,以及一阶矩和二阶矩的调节因子。
Adam是adaptive moments。它可以被看作结合RMAProp和具有动量的变种,此外Adam进行了偏置修正。
2. python实现优化器
具体的逻辑可以看一下代码
import numpy as np
class SGC(object):
def __init__(self, lr=0.01, momentum=0.0):
"""
A stochastic gradient descent optimizer.
:param lr: 学习率,默认0.01
:param momentum: 动量因子,初始化0.0
:param cache: 由于下一次计算会用到上一次的动量,这个参数为了保存上一次的结果,也为了区分是具体什么地方使用
"""
self.lr = lr
self.momentum = momentum
self.cache = {}
def __str__(self):
return 'SGD(lr={},momentum={})'.format(str(self.lr), str(self.momentum))
def __call__(self, param, param_grad, param_name):
self.update(param, param_grad, param_name)
def update(self, param, param_grad, param_name):
"""
更新参数
计算速度更新:v_new = momentum * v_old + lr * param_grad
计算参数更新:param_new = param_old - v_new
:param param: 参数的原值,shape(m,n)
:param param_grad: 一般是损失函数的梯度,shape(m,n)
:param param_name: 一般是标示在哪个地方用到了,str
:return: 更新以后的结果,shape(m,n)
"""
# 如果是第一次执行,没有保存参数对应的动量,则直接计算
if param_name not in self.cache:
update = self.lr * param_grad
else:
# 在有动量的情况下计算更新值
update = self.momentum * self.cache[param_name] + self.lr * param_grad
# 保存更新结果,以便下一次继续计算
self.cache[param_name] = update
return param - update
class AdaGrad(object):
def __init__(self, lr=0.01, eps=1e-7):
"""
AdaGrad, 随着时间的积累,学习率会不断下降
:param lr: 学习率,默认值0.01
:param eps: 一个趋近于0的极小值,为了防止分母为零而报错
:param cache: 由于下一次计算会用到上一次的动量,这个参数为了保存上一次的结果,也为了区分是具体什么地方使用
"""
self.lr = lr
self.eps = eps
self.cache = {}
def __str__(self):
return 'AdaGrad(lr={})'.format(str(self.lr))
def __call__(self, param, param_grad, param_name):
return self.update(param, param_grad, param_name)
def update(self, param, param_grad, param_name):
"""
更新参数
积累平方梯度:r_old = r_new + param_grad ** 2
计算更新: update = lr * param_grad / sqrt(r_old)
更新参数: param_old = param_old - update
:param param: 参数的原值,shape(m,n)
:param param_grad: 一般是损失函数的梯度,shape(m,n)
:param param_name: 一般是标示在哪个地方用到了,str
:return: 更新以后的结果,shape(m,n)
"""
if param_name not in self.cache:
self.cache[param_name] = np.zeros_like(param_grad)
# 积累平方梯度:r_old = r_new + param_grad ** 2
self.cache[param_name] += param_grad ** 2
# 计算更新: update = lr * param_grad / sqrt(r_old)
update = self.lr * param_grad / (np.sqrt(self.cache[param_name]) + self.eps)
return param - update
class RMSProp(object):
def __init__(self, lr=0.001, decay=0.9, eps=1e-7):
"""
RMSProp
:param lr: 学习率,默认值0.01
:param eps: 一个趋近于0的极小值,为了防止分母为零而报错
:param decay: 积累平方梯度是,一个对于当前梯度平方和过去梯度平方积累的调节因子
"""
self.lr = lr
self.decay = decay
self.eps = eps
self.cache = {}
def __str__(self):
return 'RMSProp(lr={},decay={})'.format(str(self.lr), str(self.decay))
def __call__(self, param, param_grad, param_name):
self.update(param, param_grad, param_name)
def update(self, param, param_grad, param_name):
"""
更新参数
积累平方梯度:r_old = delay *r_new + (1-delay)param_grad ** 2
计算更新: update = lr * param_grad / sqrt(r_old)
更新参数: param_old = param_old - update
:param param: 参数的原值,shape(m,n)
:param param_grad: 一般是损失函数的梯度,shape(m,n)
:param param_name: 一般是标示在哪个地方用到了,str
:return: 更新以后的结果,shape(m,n)
"""
if param_name not in self.cache:
self.cache[param_name] = np.zeros_like(param_grad)
# 积累平方梯度:r_old = delay *r_new + (1-delay)param_grad ** 2
self.cache[param_name] = self.decay * self.cache[param_name] + (1 - self.decay) * param_grad ** 2
# 计算更新: update = lr * param_grad / sqrt(r_old)
update = self.lr * param_grad / (np.sqrt(self.cache[param_name]) + self.eps)
return param - update
class Adam(object):
def __init__(self, lr=0.001, decay1=0.9, decay2=0.999, eps=1e-7):
"""
Adam
:param lr: 学习率,默认值0.001
:param decay1: 一阶矩的过去积累量和当前一阶矩的调整因子
:param decay2: 二阶矩的过去积累量和当前二阶矩的调整因子
:param eps: 一个趋近于0的极小值,为了防止分母为零而报错
"""
self.lr = lr
self.decay1 = decay1
self.decay2 = decay2
self.eps = eps
self.cache = {}
def __str__(self):
return 'Adam(lr={},decay1={},decay2={})'.format(str(self.lr), str(self.decay1), str(self.decay2))
def __call__(self, param, param_grad, param_name):
self.update(param, param_grad, param_name)
def update(self, param, param_grad, param_name):
"""
更新参数
时间因子: t_new = t_old + 1
有偏一阶矩估计:mean_new = delay1 * mean_old + (1 - delay1) * param_grad
有偏二阶矩估计:var_new = delay2 * var_old + (1 - delay2) * param_grad ** 2
修正一阶矩偏差:mean_new = mean_new / (1 - delay1 ** t)
修正二阶矩偏差:var_new = var_new / (1 - delay2 ** t)
计算更新值:update = lr * mean_new / sqrt(var_new)
:param param: 参数的原值,shape(m,n)
:param param_grad: 一般是损失函数的梯度,shape(m,n)
:param param_name: 一般是标示在哪个地方用到了,str
:return: 更新以后的结果,shape(m,n)
"""
if param_name not in self.cache:
self.cache[param_name] = {
"t": 0,
"mean": np.zeros_like(param_grad),
"var": np.zeros_like(param_grad),
}
# 时间因子: t_new = t_old + 1
t = self.cache[param_name]["t"] + 1
var = self.cache[param_name]["var"]
mean = self.cache[param_name]["mean"]
self.cache[param_name]["t"] = t
# 有偏二阶矩估计:var_new = delay2 * var_old + (1 - delay2) * param_grad ** 2
self.cache[param_name]["var"] = self.decay2 * var + (1 - self.decay2) * param_grad ** 2
# 有偏一阶矩估计:mean_new = delay1 * mean_old + (1 - delay1) * param_grad
self.cache[param_name]["mean"] = self.decay1 * mean + (1 - self.decay1) * param_grad
# 修正二阶矩偏差:var_new = var_new / (1 - delay2 ** t)
v_hat = self.cache[param_name]["var"] / (1 - self.decay2 ** t)
# 修正二阶矩偏差:var_new = var_new / (1 - delay2 ** t)
m_hat = self.cache[param_name]["mean"] / (1 - self.decay1 ** t)
# 计算更新值:update = lr * mean_new / sqrt(var_new)
update = self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
return param - update
参考资料:
《深度学习》 伊恩·古德菲洛等著
https://github.com/ddbourgin/numpy-ml/tree/master/numpy_ml/neural_nets/optimizers
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)