深度学习之优化器算法及python实现

opitimizers
1. 优化器算法
1.1 SGD 算法

没有动量的SGD算法:

θ ← θ − l r ∗ g \theta \leftarrow \theta - lr * g θθlrg

其中 θ \theta θ是参数, l r lr lr是学习率, g g g是梯度。

有动量的SGD算法:

v ← α ∗ v + l r ∗ g v \leftarrow \alpha * v + lr * g vαv+lrg

θ ← θ − v \theta \leftarrow \theta - v θθv

其中 v v v是速度。

加入动量以后首先学习速率会增加,并且能够在一定程度上缓解随机梯度下降法收敛不稳定的问题,并且有一定的摆脱陷入局部最优解的能力。

1.2 AdaGrad算法

算法:
r ← r + g ∗ g r \leftarrow r + g * g rr+gg

Δ θ = l r ∗ g e p s + r \Delta \theta = \frac {lr * g} {eps + \sqrt {r}} Δθ=eps+r lrg

θ ← θ − Δ θ \theta \leftarrow \theta - \Delta \theta θθΔθ

其中 e p s eps eps是一个极小正数值,就是为了避免分母为零。

AdaGrad缩放参数,并且于幅度等于所有梯度历史平方值总和的平方根。所以在损失大偏导出学习速率快,损失偏导大的地方学习速率会小一些。

1.3 RMSProp算法

算法:
r ← ρ r + ( 1 − ρ ) g ∗ g r \leftarrow \rho r + (1 - \rho)g * g rρr+(1ρ)gg

Δ θ = l r ∗ g e p s + r \Delta \theta = \frac {lr * g} {eps + \sqrt {r}} Δθ=eps+r lrg

θ ← θ − Δ θ \theta \leftarrow \theta - \Delta \theta θθΔθ

其中 ρ \rho ρ是0-1的一个调节因子。

RMSProp算法比AdaGrad算法好的一点是在积累平方梯度时,可以调整过去积累和当前的比重。

1.4 Adam算法

算法:
t ← t + 1 t \leftarrow t + 1 tt+1

s ← ρ 1 s + ( 1 − ρ 1 ) g s \leftarrow \rho_1 s + (1 - \rho_1) g sρ1s+(1ρ1)g

r ← ρ 2 r + ( 1 − ρ 2 ) g 2 r \leftarrow \rho_2 r + (1 - \rho_2) g^2 rρ2r+(1ρ2)g2

s ^ ← s / ( 1 − ρ 1 t ) \hat s \leftarrow s /(1 - \rho_1^t) s^s/(1ρ1t)

r ^ ← r / ( 1 − ρ 2 t ) \hat r \leftarrow r / (1 - \rho_2^t) r^r/(1ρ2t)

Δ θ = s ^ e p s + r ^ \Delta \theta = \frac {\hat s} {eps + \sqrt {\hat r}} Δθ=eps+r^ s^

θ ← θ − Δ θ \theta \leftarrow \theta - \Delta \theta θθΔθ

其中 s , r , ρ 1 , ρ 2 s,r,\rho_1,\rho_2 s,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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值