ADADELTA: AN ADAPTIVE LEARNING RATE METHOD

这篇论文比较短,先看了这篇,本来应该先把ADAGRAD看了的。普通的基于梯度下降的方法,普遍依赖于步长,起始点的选择,所以,受ADAGRAD的启发,作者提出了一种ADADELTA的方法。

Δxt=RMS[Δx]t1RMS[g]tgt

其中gt=f(xt)xt,所以下一步迭代就是:

xt+1=xt+Δxt

主要内容

ADAGRAD方法:

Δxt=ητ=1tgτ2gt

也就是,步长与之前所有的梯度有关,显然这个步长是会逐渐减少的。但是这个缺点也很明显,如果起始点的梯度很大,那么就会导致后续步长很小,而一开始的梯度很小,就会导致后续步长很大,产生振荡,有些怪怪的。
而ADADELTA希望只关心一部分的梯度,比如

τ=tktgτ2

但是这么做,每次迭代都必须记录k个梯度,这显得不怎么效率,于是,作者相处了一个法子:

E[g2]t=ρE[g2]t1+(1ρ)gt2

可以看到,对于g1t+1步之后其影响为:ρt(1ρ)g1,对整个迭代造成的影响是一个等比序列:

(1ρ),ρ(1ρ),,ρt(1ρ)

最后趋向于:

1ρt+11

这么做就俩劝其美啦。
记:

RMS[g]t=E[g2]t+ϵ

其中ϵ是为了让除法有意义而添加的小量。
所以

Δxt=ηRMS[g]tgt

这还不是最终版本,另一个启发决定了η的选择。

我们知道,很多问题是有实际含义的,x可能是有单位的,比如是米,天等,所以,一个很自然的期望是,Δx的单位和x是保持一致的。但是:

unitsofΔxunitsofgfx1unitsofx

也就是说Δx的步长单位和梯度单位是一致的,就像是l=vtΔt的步长单位是m/s,是时间单位的倒数。
而利用二阶导数迭代步长就符合单位一致(如Newton方法):

ΔxH1gfx2fx2unitsofx

其中H为Hessian矩阵。
又注意到:

Δx=fx2fx212fx2=Δxfx

于是,完全体的ADADELTA方法变为如下:

Δxt=RMS[Δx]t1RMS[g]tgt

分子式t1的原因式Δxt压根不知道,所木有办法,就将就一下。

算法

完整的算法如下:
在这里插入图片描述

需要注意一点的是,在实际实验中,我们设置E[Δx2]0=1而不是如算法中所说的0。因为,如果设置为0,那么意味着第一步只进行相当微小的迭代,所以之后也都是微小的迭代。或许作者是将ϵ设置为1?而不是一个小量?

ADADELTA 代码

import numpy as np
import matplotlib.pyplot as plt

这次用比较怪一点的方式来写,首先,创建一个类,用来存放函数f和梯度g

class ADADELTA:
    def __init__(self, function, gradient, rho=0.7):
        assert hasattr(function, "__call__"), "Invalid function"
        assert hasattr(gradient, "__call__"), "Invalid gradient"
        assert 0 < rho < 1, "Invalid rho"
        self.__function = function
        self.__gradient = gradient
        self.rho = rho
        self.acc_gradient = 0 #初始化accumulate gradient
        self.acc_updates = 1 #初始化accumulate updates
        self.progress = []
        
    @property
    def function(self):
        return self.__function
    
    @property
    def gradient(self):
        return self.__gradient
    
    def reset(self):
        self.acc_gradient = 0 #初始化accumulate gradient
        self.acc_updates = 1 #初始化accumulate updates
        self.progress = []

计算累计梯度

E[g2]t=ρE[g2]t1+(1ρ)gt2

def accumulate_gradient(self, gt):
    self.acc_gradient = self.rho * self.acc_gradient \
                            + (1 - self.rho) * gt ** 2
    return self.acc_gradient
ADADELTA.accumulate_gradient = accumulate_gradient

更新E[Δx]t

E[Δx2]t=ρE[Δx2]t1+(1ρ)Δxt2

def accumulate_updates(self, deltax):
    self.acc_updates = self.rho * self.acc_updates \
                        + (1 - self.rho) * deltax ** 2
    return self.acc_updates
ADADELTA.accumulate_updates = accumulate_updates

计算更新步长:

Δxt=RMS[Δx]t1RMS[g]tgt

def step(self, x, smoothingterm=1e-8):
    gt = self.gradient(x)
    self.accumulate_gradient(gt)
    RMS_gt = np.sqrt(self.acc_gradient + smoothingterm)
    RMS_up = np.sqrt(self.acc_updates + smoothingterm)
    deltax = -RMS_up / RMS_gt * gt
    self.accumulate_updates(deltax)
    return x + deltax
ADADELTA.step = step

进行t步

def process(self, startx, t, smoothingterm=1e-8):
    x = startx
    for i in range(t):
        self.progress.append(x)
        x = self.step(x, smoothingterm)
    return self.progress
ADADELTA.process = process

可视化

def plot(self):
    x = np.arange(1, len(self.progress) + 1)
    y = np.array([
        self.function(item) for item in self.progress
    ])
    fig, ax = plt.subplots(constrained_layout=True)
    ax.plot(x, y)
    ax.set_xlabel("steps")
    ax.set_ylabel("value of function")
    ax.set_title("value with steps")
    plt.show()
ADADELTA.plot = plot
def function(x):
    return x[0] ** 2 + 50 * x[1] ** 2

def gradient(x):
    return 2 * x[0] + 100 * x[1]
test = ADADELTA(function, gradient, 0.9)
test.reset()
startx = np.array([10, 10])
test.process(startx, 50)
test.plot()

在这里插入图片描述

posted @   馒头and花卷  阅读(337)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示