一、L-BFGS:牛顿法(迭代求驻点,一般驻点就是我们损失函数的最优点,Xk+1=Xk-F’(Xk)/F’’(Xk),但是二阶导数通常比较难求),BFGS(迭代求牛顿法里的参数,二阶导数的倒数,公式比较复杂,可以理解为从梯度下降逐步转换为牛顿法求解的一个算法,但是有个缺点就是其中的迭代矩阵较大,存储不易),L-BFGS(受限制的BFGS,存储少量数据,牺牲时间和部分精度获得较少的存储空间。如:存储一个大矩阵 10*10,可以存储几个计算出这个矩阵的元素 10*1,1*5,5*10,要用大矩阵时再计算出来。要是还是过大,如存了100组因子了,可以存下一组的同时,删除第一组)本质上是个二阶方法,如果参数过多,会造成计算困难。
二、梯度下降法:
J(θ)是损失函数,优化对象
grad是梯度,各个维度的偏导组成的向量
α是学习率
θ是优化的参数
(1)BGD(批量梯度下降):θn+1=θn-α*(1/n)*sum(grad(J(θ))每次计算所有样本的梯度取平均,迭代次数少,收敛快,每次迭代计算耗时多,内存占用大,可能陷入局部最优。
(2)SGD(随机梯度下降):θn+1=θn-α*grad(J(θ)),每次只用一个样本计算梯度,速度快,可能跳出局部最优,受噪声影响大,不是每次都朝最优方向走。
(3)MBGD(小批量梯度下降):θn+1=θn-α*(1/n)*sum(grad J(θ))每次计算取一小批数据来计算,属于BGD和SGD的折中。
三、Adam:梯度下降算法的一个拓展,让学习率可以自适应调整的同时引入动量。一般的梯度下降通常为θn+1=θn-α*grad(J(θ)),直接是学习率乘梯度,由梯度决定位置。
(1)动量的引入可以看作梯度仅仅决定速度,而速度才决定位置(待优化参数)
β看作一个衰减系数,逐渐遗忘之前的数据通常0.999,由速度来改变θ。
(2)自适应学习率的引入就是让学习率根据历史梯度进行适当调整。RMSprop就是通过历史梯度和当前梯度的平方和对学习率进行适度调整
,β确保学习率不会一直加速,并逐渐减少历史数据的影响。
(3)adam就是将上述两种方法结合起来。
既引入动量来调整参数,又引入自适应学习率来调整步长。一般h里还有一个1e-10来确保除数不为零。
题主说的是这篇论文吧~
Adam: A Method for Stochastic Optimization-学术范 (xueshufan.com)
一、Adam算法
Adam(Adaptive momentum)是一种自适应动量的随机优化方法(A method for stochastic optimization),经常作为深度学习中的优化器算法。
二、算法详细步骤
三、Adam优化算法的基本机制
Adam 算法和传统的随机梯度下降不同。随机梯度下降保持单一的学习率(即 alpha)更新所有的权重,学习率在训练过程中并不会改变。而 Adam 通过计算梯度的***一阶矩估计***和***二阶矩估计***而为不同的参数设计独立的自适应性学习率。Adam 算法的提出者描述其为两种随机梯度下降扩展式的优点集合,即:
适应性梯度算法(AdaGrad)为每一个参数保留一个学习率以提升在稀疏梯度(即自然语言和计算机视觉问题)上的性能。
均方根传播(RMSProp)基于权重梯度最近量级的均值为每一个参数适应性地保留学习率。这意味着算法在非稳态和在线问题上有很有优秀的性能。
Adam 算法同时获得了 AdaGrad 和 RMSProp 算法的优点。Adam 不仅如 RMSProp 算法那样基于一阶矩均值计算适应性参数学习率,它同时还充分利用了梯度的二阶矩均值(即有偏方差/uncentered variance)。具体来说,算法计算了梯度的指数移动均值(exponential moving average),超参数 beta1 和 beta2 控制了这些移动均值的衰减率。
Adam优化算法需要做偏差修正:
更新权重:
一些超参数:
α :最重要的超参,一般都需要去调节
β:一般默认值分别是0.9和0.999,一般去默认值不会去调节
ε:一个不怎么重要的超参,一般不用调节
四、附
- 更多专业文献,尽在学术范~
在本章中,我们已经学习了许多有效优化的技术。在本节讨论之前,我们先详细回顾以下这些技术:
- 随机梯度下降:在解决优化问题时比梯度下降更有效
- 小批量随机梯度下降:在一个小批量中使用更大的观测值集,可以通过向量化提供额外效率。这是高效的多机、多GPU和整体并行处理的关键
- 动量法:添加了一种机制,用于汇总过去梯度的历史以加速收敛
- AdaGrad算法:对每个坐标缩放来实现高效计算的预处理器
- RMSProp算法:通过学习率的调整来分离每个坐标的缩放
Adam算法将所有这些技术汇总到一个高效的学习算法中。不出预料,作为深度学习中使用的更强大和有效的优化算法之一,它非常受欢迎。但是它并非没有问题,尤其是[Reddi et al., 2019]表明,有时Adam算法可能由于?差控制不良?发散。在完善?作中,[Zaheer et al., 2018]给Adam算法提供了?个称为Yogi的热补丁来解决这些问题。下?我们了解?下Adam算法
从头开始实现Adam算法并不难,为了方便起见,我们将时间步t存储在hyperparams字典中。除此之外,一切都很简单
%matplotlib inline
import torch
from d2l import torch as d2l
?
def init_adam_states(feature_dim):
v_w,v_b = torch.zeros((feature_dim,1)),torch.zeros(1)
s_w,s_b = torch.zeros((feature_dim,1)),torch.zeros(1)
return ((v_w,s_w),(v_b,s_b))
?
def adam(params,states,hyperparams):
beta1,beta2,eps = 0.9,0.999,1e-6
for p,(v,s) in zip(params,states):
with torch.no_grad():
v[:] = beta1 * v + (1 - beta1) * p.grad
s[:] = beta2 * s + (1 - beta2) * torch.square(p.grad)
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:] -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr + eps))
p.grad.data.zero_()
hyperparams['t'] += 1
现在,我们用以上Adam算法来训练模型,这里我们使用η=0.01的学习率
data_iter,feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adam,init_adam_states(feature_dim),{'lr':0.01,'t':1},data_iter,feature_dim);
loss: 0.246, 0.014 sec/epoch
此外,我们可以用深度学习框架自带算法应用Adam算法,这里我们只需要传递配置参数
trainer = torch.optim.Adam
d2l.train_concise_ch11(trainer,{'lr':0.01},data_iter)
loss: 0.247, 0.015 sec/epoch
def yogi(params,states,hyperparams):
beta1,beta2,eps = 0.9,0.999,1e-3
for p,(v,s) in zip(params,states):
with torch.no_grad():
v[:] = beta1 * v + (1 - beta1) * p.grad
s[:] = s + (1 - beta2) * torch.sign(torch.square(p.grad) -s ) * torch.square(p.grad)
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:] -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr) + eps)
p.grad.data.zero_()
hyperparams['t'] += 1
data_iter,feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(yogi,init_adam_states(feature_dim),{'lr':0.01,'t':1},data_iter,feature_dim)
loss: 0.244, 0.007 sec/epoch
([0.006999015808105469,
0.01399993896484375,
0.02099919319152832,
0.026999235153198242,
0.03399944305419922,
0.0410001277923584,
0.04800128936767578,
0.05700254440307617,
0.06400370597839355,
0.07200503349304199,
0.07900643348693848,
0.08518671989440918,
0.09218716621398926,
0.10018706321716309,
0.10867691040039062],
[0.3831201309363047,
0.30505007115999855,
0.27388086752096813,
0.25824862279494604,
0.248792000691096,
0.24663881778717042,
0.24533938866853713,
0.24811744292577106,
0.2440877826611201,
0.24333851114908855,
0.24304762629667917,
0.24334035567442577,
0.24402384889125825,
0.24259794521331787,
0.2435852948029836])
?
- Adam算法将许多优化算法的功能结合到了相当强大的更新规则中
- Adam算法在RMSProp算法基础上创建的,还在小批量的随机梯度上使用EWMA
- 在估计动量和二次矩时,Adam算法使用偏差校正来调整缓慢的启动速度
- 对于具有显著差异的梯度,我们可能会遇到收敛性问题,我们可以通过使用更大的小批量或者切换到改进的估计值$s_t$来修正它们。Yogi提供了这样的替代方案
在众多数学建模时所使用的优化算法中,Adam是我使用体验感最好的优化算法。初次接触到 Adam 优化算法时,只知道Adam有着自适应的学习率已经更快的收敛速度,但在接触了数字信号处理之后,才幡然醒悟:
如果说Momentum-SGD是将物理中的动量概念加入到了梯度下降里,我则更愿意相信Adam是将数字信号处理中IIR滤波器的概念考虑了进来:
这两行便是Adam的核心,假如令 =0.9,将其一展开便可得到:
若把每一次迭代后计算得到的梯度看作是一个冲激信号,那么随着迭代次数的增加,该冲激信号乘以其所对应的权重后,在时间上将呈现为如下一个指数衰减信号:
即,该式等效于:将冲激序列(历史梯度)与指数衰减序列卷积;
换一种说法,假设这是一个线性时不变系统,则 、 对单位脉冲信号的响应(系统函数)为指数衰减函数。
同时,单个为指数衰减函数的系统函数,其表达形式为:
而IIR数字滤波器的系统函数表示形式为:
即Adam中对历史梯度的处理,可以看成一个 ; 的IIR数字滤波处理,是对梯度的滤波;是对梯度平方的滤波(反应幅值)。而后面对误差的修正,在我看来,主要是为了修正零状态响应所带来的影响。
所以,从这个角度看,如果想增加Adam对梯度的敏感性,有两种途径:
1.增加迭代时的项数,即使其变为高阶滤波器
2.增大beta值;
反之亦然
在不加偏移修正项的情况下,由上一部分可得出 Adam 的等效公式为:
其中 S(t) 为最终的步长序列;
g(t) 为梯度序列;
为验证该式的正确性以及研究单位冲激信号为整个系统带来的单位冲激响应(系统函数),我使用了以下代码:
import numpy as np
import matplotlib.pyplot as plt
class Adam_Core:
def __init__(self):
self.lr=5e-2
self.eps=1e-9
self.beta_1=0.9
self.beta_2=0.999
self.grad_smooth=0
self.velocity_smooth=0
# 迭代函数
def forward(self, x):
y=[]
for n, grad in enumerate(x):
self.grad_smooth=self.beta_1 * self.grad_smooth + (1 - self.beta_1) * grad
# 偏移修正项
self.velocity_smooth=self.beta_2 * self.velocity_smooth + (1 - self.beta_2) * np.power(grad, 2)
self.grad_smooth=self.grad_smooth / (1 - np.power(self.beta_1, n + 1))
self.velocity_smooth=self.velocity_smooth / (1 - np.power(self.beta_1, n + 1))
step=(self.lr * self.grad_smooth) / (np.power(self.velocity_smooth, 1 / 2) + self.eps)
y.append(step)
return y
# 等效函数
def adam_core_conv(x, lr, beta1=0.9, beta2=0.999, eps=1e-9):
beta1_list=np.logspace(0, len(x)-1, num=len(x), endpoint=True, base=beta1)
beta2_list=np.logspace(0, len(x) - 1, num=len(x), endpoint=True, base=beta2)
g=np.convolve(x, beta1_list, 'full')
v=np.convolve(np.power(x, 2), beta2_list, 'full')
y=lr * ((1 - beta1) * g) / (np.power((1 - beta2) * v, 0.5) + eps)
return y
if __name__=='__main__':
# 构造单位冲激函数
test_x=np.append(np.array([0, 0, 1]), np.zeros(80))
adam_core=Adam_Core()
res=adam_core.forward(test_x)
# res=adam_core_conv(test_x, lr=5e-2)
plt.bar(range(len(test_x)), test_x, color='r')
plt.bar(range(len(res)), res, color='b')
plt.savefig('1.png')
plt.show()
?使用迭代计算出的结果:
使用等效计算式计算出的结果(因为是完全卷积,所以结果序列长度为输入长度的两倍):
添加入偏移修正项的结果 :
# 其中:红色为单位冲激序列,蓝色为单位冲击响应序列
以上数据所采用的参数为:
lr=5e-2
eps=1e-9
beta_1=0.9
beta_2=0.999
?
于是可以得到以下结论:
等效计算式具有正确性;
偏移修正项对整体系统的单位冲激响应影响显著,具体表现为:
从幅值上看,添加偏移修正项相比未添加对梯度的敏感性有显著提高,添加后最大为2倍,而未添加则仅为0.18倍左右;
从响应时间上看,添加偏移修正项相比未添加在响应时间上有着约15次迭代的延迟,而这就正意味着:冲激响应在冲激到来后并不会立刻达到峰值,换一种说法就是仍具有一定动量,提高了在局部最小值点的逃逸能力。
# 由于添入偏移修正项后,等效计算式过于复杂(包含了累乘以及二次卷积)所以这里并没有给出
综上,可以得出以下结论:
1.增加迭代时的项数,即使其变为高阶滤波器 或 增大 值, 可增加Adam对梯度的敏感性;
2.偏移修正项在增加了Adam对梯度敏感性的同时,也为其增加了在局部最小值点的逃逸能力;
3.lr ( 学习率 ) 对优化效果的影响至关重要,其限制了实际每次迭代步长的上限。所以设置过小将导致算法在甚至数万次的迭代后仍无法收敛,设置过大则会导致其对梯度过于敏感同样影响收敛。使用时需根据具体场景做出合适选择。
具体代码实现详见文章:
Z-MiCTrue:从信号与系统的角度再看 Adam 优化算法