模型优化-AdaGrad

在先前介绍的梯度下降算法以及动量方法都有一个共同点,即对于每一个参数都用相同的学习率(步长)进行更新。但是在实际应用中,各参数的重要性肯定是不同的,所以对于不同的参数要进行动态调整,采取不同的学习率,让目标函数能够更快地收敛。

本篇博客主要借鉴 深度学习优化方法-AdaGrad 以及《百面机器学习》,若构成侵权则立即删除。

【做法】:将每一个参数的每一次迭代的梯度取平方,然后累加并开方得到 r,最后用全局学习率除以 r,作为学习率的动态更新。

【计算过程】:令 α 表示全局学习率,r 为梯度累积变量,初始值为 0。

  • 单独计算每一个参数在当前位置的梯度。
    g = ∂ L ( w ) ∂ w i g = \frac{\partial L(w)}{\partial w_i} g=wiL(w)
  • 累积平方梯度,一般来说 g 是一个向量,而向量的平方通常写为 g T g g^Tg gTg
    r = r + g 2 或 r = r + g T g r = r + g^2 \quad \text{或} \quad r = r + g^Tg r=r+g2r=r+gTg
  • 更新参数。
    w = w − α r g w = w - \frac{\alpha}{\sqrt{r}} g w=wr αg

上述式子存在一个问题,r 在计算过程中有可能变为 0,在代码中分母为零通常都会报错,因此我们需要想办法让分母不为零,同时不会影响到参数的更新。

怎么做呢?我们可以在分母上加一个极小的常数 σ \sigma σ,因为取值极小,即使开方后仍然不会影响参数的更新。通常, σ \sigma σ 大约设置为 10 的 -7 次方。

w = w − α σ + r g w = w - \frac{\alpha}{\sigma + \sqrt{r}} g w=wσ+r αg

从 AdaGrad 算法的计算过程中可以看出,随着不断地迭代,r 的值会越来越大(梯度的平方为正值),那么在每一轮迭代中学习率会越来越小,也就是说当前位置的梯度对参数的影响也越来越小。简单地讲,AdaGrad 算法在初始时鼓励收敛,随着迭代的深入慢慢变成惩罚收敛,速度也越来越慢。

【代码实现】:以线性回归为例。

def AdaGrad(x, y, step=0.01, iter_count=500, batch_size=4):
    length, features = x.shape
    data = np.column_stack((x, np.ones((length, 1))))
    w = np.zeros((features + 1, 1))
    r, eta = 0, 10e-7
    start, end = 0, batch_size
    for i in range(iter_count):
        # 计算梯度
        dw = np.sum((np.dot(data[start:end], w) - y[start:end]) * data[start:end], axis=0) / length        
        # 计算梯度累积变量
        r = r + np.dot(dw, dw)
        # 更新参数
        w = w - (step / (eta + np.sqrt(r))) * dw.reshape((features + 1, 1))
        
        start = (start + batch_size) % length
        if start > length:
            start -= length
        end = (end + batch_size) % length
        if end > length:
            end -= length
    return w
    

print(AdaGrad(x, y, step=1, iter_count=1000))
# 输出:
array([[5.19133285],
       [1.35955132]])

【问题】:从训练开始时积累梯度平方会导致有效学习率过早和过量的减小。这也是为什么在上述代码示例的最后部分使用的全局学习率为 1。如果把 step 设置为 0.1 会发生什么?

print(AdaGrad(x, y, step=0.1, iter_count=1000))
# 输出:
array([[3.37157325],
       [0.6519457 ]])

可以看到迭代 1000 次还没有收敛到最优点附近,且距离最优点还有一段距离。

print(AdaGrad(x, y, step=0.1, iter_count=3000))
# 输出:
array([[4.72572017],
       [0.91424582]])

迭代 3000 次后距离最优点更近了一些。

为了避免这种情况的发生,我们可以在迭代一定次数后再开始累加 r。

def AdaGrad(x, y, step=0.01, iter_count=500, step_count=100, batch_size=4):
    length, features = x.shape
    data = np.column_stack((x, np.ones((length, 1))))
    w = np.zeros((features + 1, 1))
    r, eta = 0, 10e-7
    start, end = 0, batch_size
    for i in range(iter_count):
        # 计算梯度
        dw = np.sum((np.dot(data[start:end], w) - y[start:end]) * data[start:end], axis=0) / length
        # 大于 step_count 时,更新梯度累积平方
        if i > step_count:
            r = r + np.dot(dw, dw)
            w = w - (step / (eta + np.sqrt(r))) * dw.reshape((features + 1, 1))
        else:
            w -= step * dw.reshape((features + 1, 1))        
        start = (start + batch_size) % length
        if start > length:
            start -= length
        end = (end + batch_size) % length
        if end > length:
            end -= length
    return w

此时,我们再调用 AdaGrad,就不会出现上述的情况了。

print(AdaGrad(x, y, iter_count=500))
# 输出:
array([[5.24748173],
       [1.06459711]])

当然,我们也可以通过当前位置的梯度取值来进行判断。

def AdaGrad(x, y, step=0.01, iter_count=500, step_threshold=30, batch_size=4):
    length, features = x.shape
    data = np.column_stack((x, np.ones((length, 1))))
    w = np.zeros((features + 1, 1))
    r, eta = 0, 10e-7
    start, end = 0, batch_size
    for i in range(iter_count):
        dw = np.sum((np.dot(data[start:end], w) - y[start:end]) * data[start:end], axis=0) / length
        dw2 = np.dot(dw, dw)
        if dw2 < step_threshold:
            r = r + dw2
            w = w - (step / (eta + np.sqrt(r))) * dw.reshape((features + 1, 1))
        else:
            w -= step * dw.reshape((features + 1, 1))        
        start = (start + batch_size) % length
        if start > length:
            start -= length
        end = (end + batch_size) % length
        if end > length:
            end -= length
    return w

    
print(AdaGrad(x, y, iter_count=500))
# 输出:
array([[5.12585752],
       [0.95310592]])

所有代码都可从 传送门 内获得。

参考

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值