通过loss对 ω \omega ω的一阶导数来找下降方向,并且以迭代的方式来更新参数。
W t + 1 = W t − η ∇ L ( W t ) W_{t+1} = W_{t}-\eta\nabla L(W_{t}) Wt+1=Wt−η∇L(Wt), 其中 η \eta η为学习率。
均匀的随机选择其中一个样本( X ( i ) , Y ( i ) X^{(i)},Y^{(i)} X(i),Y(i)),用它代表整个样本,即把它的值乘以N,就相当于获得了梯度的无偏估计值。
公式: W t + 1 = W t − η N ∇ J ( W t , X ( i ) , Y ( i ) ) \mathbf{W}_{t+1}=\mathbf{W}_{t}-\eta N \nabla J\left(\mathbf{W}_{t}, X^{(i)}, Y^{(i)}\right) Wt+1=Wt−ηN∇J(Wt,X(i),Y(i))
每次迭代使用m个样本来对参数进行更新,公式:
W t + 1 = W t − η 1 m ∑ k = i i + m − 1 ∇ J ( W t , X ( k ) , Y ( k ) ) \mathbf{W}_{t+1}=\mathbf{W}_{t}-\eta \frac{1}{m} \sum_{k=i}^{i+m-1} \nabla J\left(\mathbf{W}_{t}, X^{(k)}, Y^{(k)}\right) Wt+1=Wt−ηm1∑k=ii+m−1∇J(Wt,X(k),Y(k))
梯度下降法优点:简单
缺点:训练速度慢,会进入局部极小值点,随机选择梯度的同时会引入噪声,使得权重更新的方向不一定正确
增加一个动量,使当前训练数据的梯度受到之前训练数据的影响。
{ v t = α v t − 1 + η t ∇ J ( W t , X ( i s ) , Y ( i s ) ) W t + 1 = W t − v t \left\{\begin{array}{l}v_{t}=\alpha v_{t-1}+\eta_{t} \nabla J\left(W_{t}, X^{\left(i_{s}\right)}, Y^{\left(i_{s}\right)}\right) \\ W_{t+1}=W_{t}-v_{t}\end{array}\right. {vt=αvt−1+ηt∇J(Wt,X(is),Y(is))Wt+1=Wt−vt
加速收敛,有一定摆脱局部最优的能力
但仍具有SGD一部分的缺点
实际中可以尝试
牛顿加速梯度动量优化方法(NAG, Nesterov accelerated gradient):用上一步的速度先走一小步,再看当前的梯度然后再走一步,
{ v t = α v t − 1 + η t Δ J ( W t − α v t − 1 ) W t + 1 = W t − v t \left\{\begin{array}{l}v_{t}=\alpha v_{t-1}+\eta_{t} \Delta J\left(W_{t}-\alpha v_{t-1}\right) \\ W_{t+1}=W_{t}-v_{t}\end{array}\right. {vt=αvt−1+ηtΔJ(Wt−αvt−1)Wt+1=Wt−vt
https://zhuanlan.zhihu.com/p/62585696
可以理解为在标准动量中添加一个校正因子。
理解:在momentum中小球会盲目的跟从下坡的梯度,容易发生错误,所以需要一个更聪明的小球,能提前知道它要去哪,还有知道走到坡地的时候速度慢下来,而不是又崇尚另一坡。
优点:梯度下降的方向更加准确
缺点:对收敛率作用不是很大
模型每次反向传播都会给可学习参数p计算出一个偏导数 g t g_t gt,用于更新对应的参数p。通常 g t g_t gt不会直接作用到对应的可学习参数p上,而是通过优化器做一下处理,得到新的值 g ^ t \hat g_t g^t,即 F ( g t ) F(g_t) F(gt),然后和学习率lr一起用于更新参数
torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)
参数:
params(iterable): iterable of parameters to optimize or dicts defining parameter groups
lr(float): 学习率
momentum (float, optional) : 动量因子 默认 0。通过上一次的v和当前的偏导数,得到本次的v。即: v t = v t − 1 ∗ m o m e n t u m + g t v_t = v_{t-1} * momentum + g_t vt=vt−1∗momentum+gt, 这就是F。
怎么理解:动量使得v具有惯性,这样可以缓和v的抖动,有时可以跳出局部极小。如:上次计算得到v=10,参数更新后本次的偏导是0,那么使用momentum=0.9后,最终用于更新可学习参数的v=9, 而不是0,这样参数仍会得到较大的更新,增加挑出局部极小值的可能性。
dampening: 是乘到偏导g上的一个数,即: v t = v t − 1 ∗ m o m e n t u m + g t ∗ ( 1 − d a m p e n i n g ) v_t = v_{t-1}*momentum + g_t * (1-dampening) vt=vt−1∗momentum+gt∗(1−dampening),dampening在优化器第一次更新时不起作用。
weight_decay (float, optional): 权重衰减 (L2 penalty)默认为0,即:L2 正则化,选择合适的权重衰减很重要,需要根据具体的情况取尝试,初步尝试可以选择 1e-4 或者 1e-3。
作用于当前可学习参数p的值,即: g t = g t + ( p ∗ w e i g h t d e c a y ) g_t = g_t + (p*weight_decay) gt=gt+(p∗weightdecay)这里待更新的参数p的偏导就是 g t g_t gt。
nesterov (bool, optional): 当nesterov为True时,在上述 v t v_t vt的基础上,最终得到 v t = g t + v t ∗ m o m e n t u m v_t = g_t + v_t * momentum vt=gt+vt∗momentum
pytorch中SGD更新公式和其他框架略有不同。
pytorch中是:v=m*v+g p=p-lr*v=p-lr*p*v-lr*g
其他框架:v=m*v+lr*g p=p-v=p-m*v-lr*g
动量的小知识:
其物理意义和摩擦系数一致,有效抑制了速度,降低了系统的动能。通过交叉验证,这个参数通常设置为[0.5,0.9,0.95,0.99]中的一个。
和学习率随着时间退火类似,momentum随时间变化的设置有时能略微改善效果,其中动量在学习过程的后阶段会上升。
一个典型的设置是,刚开始将动量设为0.5,而在后面的多个周期中慢慢升到0.99.
AdaGrad算法通过记录历史梯度,能够随着训练过程自动减小学习率
θ t = θ t − 1 − η ∑ i = 0 t − 1 ( g i ) 2 g t − 1 \theta_{t}=\theta_{t-1}-\frac{\eta}{\sqrt{\sum_{i=0}^{t-1}\left(g_{i}\right)^{2}}} g_{t-1} θt=θt−1−∑i=0t−1(gi)2ηgt−1
之前的方法是对所有的参数都是一个学习率,现在对不同的参数有不同的学习率。(接收到较大梯度值的权重更新的学习率将减小,而接收到较小梯度值的权重学习率将会变大),适用于数据稀疏或者分布不平衡的数据集。
torch.optim.Adagrad(params,lr=0.01,lr_decay=0,weight_decay=0,initial_accumulator_value=0)
参数:
params(iterable): 学习参数
lr(float, optional) 学习率,默认0.01
lr_decay(float, optional): 学习率衰减,默认0
weight_decay(float, optional): L2惩罚系数 默认0
initial_accumulator_value: 初始加速值,默认为0
优点:自动调整学习率
缺点:依赖一个人工设置的全局学习率,随着迭代次数增多,学习率会越来越小,最后趋于0(因为Adagrad累加之前所有梯度平方作为分母)
实际中不推荐
对Adagrad的改进,Adadelta分母中采用距离当前时间点比较近的累加项,这样可以避免训练后期,学习率过小。
torch.optim.Adadelta(params, lr=1.0,rho=0.9,eps=1e-6,weight_decay=0)
参数:
优点:避免后期,学习率过小,初期和中期,加速效果不错,训练速度快
缺点:还需要手动指定初始学习率,初始梯度很大的话,会导致整个训练过程学习率一直很小,在模型训练后期,模型会反复的在局部最小值附近抖动,从而导致学习时间变长。
Root Mean Square Prop,均方根传递
RMSProp简单修改了AdaGrad方法,它做了一个梯度平方的滑动平均。
θ t = θ t − 1 − η v t g t − 1 \theta_{t}=\theta_{t-1}-\frac{\eta}{\sqrt{v_{t}}} g_{t-1} θt=θt−1−vtηgt−1
v 1 = g 0 2 v_{1}=g_{0}^{2} v1=g02
v t = α v t − 1 + ( 1 − α ) ( g t − 1 ) 2 v_{t}=\alpha v_{t-1}+(1-\alpha)\left(g_{t-1}\right)^{2} vt=αvt−1+(1−α)(gt−1)2
通过累计各个变量的梯度的平法v,然后用每个变量的梯度除以v,即可有效缓解变量间的梯度差异。以下伪代码:
- 初始化 x,y (假设只有两个学习参数)
- 初始化学习率 lr
- 初始化平滑常数(或者叫衰减速率) α \alpha α
- 初始化 e 防止分母为0
- 初始化,梯度的平方 v x = 0 v_x=0 vx=0, v y = 0 v_y=0 vy=0 (有几个参数就有几个v)
- while 没有停止训练 do
- 计算梯度 g x , g y g_x, g_y gx,gy
- 累计梯度的平方: v x = α v x + ( 1 − α ) ( g x ) 2 v_x = \alpha v_x + (1-\alpha)(g_x)^2 vx=αvx+(1−α)(gx)2 ( v y v_y vy 同理)
- 更新可学习参数: x = x − g x v x + e l r x = x - \frac{g_x}{\sqrt{v_x}+e}lr x=x−vx+egxlr (y的更新同理)
- end while
思想:梯度震动较大的项,在下降时,减小其下降速度,对于震动幅度较小的项,在下降时,加速其下降速度。采用均方根作为分母,可缓解Adagrad学习率下降较快的问题。
torch.optim.RMSProp(params,lr=0.01,alpha=0.99,eps=1e-8,weight_decay=0,momentum=0,centered=False)
参数:
params (iterable): 待优化参数
lr(float, optional) 学习率 默认0.01
momentum (float, optional) 动量因子,默认0。
在伪代码第8行,如果momentum=0,则继续后边的计算;
否则计算过程变为: v ^ x = v ^ x ∗ m o m e n t u m + g x v x + e , x = x − v ^ x ∗ l r \hat v_x = \hat v_x * momentum + \frac{g_x}{\sqrt{v_x} + e}, x = x - \hat v_x *lr v^x=v^x∗momentum+vx+egx,x=x−v^x∗lr, 其中 v ^ x \hat v_x v^x初始化为0, g x g_x gx是x的梯度, v x v_x vx是上述累计的x的梯度的平方。
alpha (float, optional) 平滑常数,默认0.99
eps (float, optional) 防止分母为0, 默认1e-8
centered(bool, optional) 如果是false,按照伪代码计算
如果为True,即: g ^ x = α g ^ x + ( 1 − α ) g x , v x = v x − ( g ^ x ) 2 \hat g_x = \alpha \hat g_x + (1-\alpha)g_x, v_x=v_x-(\hat g_x)^2 g^x=αg^x+(1−α)gx,vx=vx−(g^x)2, 这里 g ^ x \hat g_x g^x初始化为0
centered 和 RMSProp并无直接联系,是为了让结果更加平稳
weight_decay (float, optional) L2惩罚 默认为0
优点:可缓解Adagrad学习率下降较快的问题,并且引入均方根,可减少摆动,对于RNN效果很好。
缺点:依然依赖全局学习率
推荐尝试,已经被证明是一种有效且实用的深度神经网络优化算法。
Adam看起来像是RMSProp的动量版本,做了两个改进:梯度滑动平均和偏差纠正。
梯度滑动平均
在RMSProp中,梯度的平方是通过平滑常数得到的,但并没有对梯度本身做平滑处理。在Adam中,对梯度也做了平滑处理,平滑后的滑动均值用m表示:即 m t = β ∗ m t − 1 + ( 1 − β ) ∗ g t m_t = \beta * m_{t-1} + (1-\beta)*g_t mt=β∗mt−1+(1−β)∗gt
偏差纠正
上述m的滑动均值计算,当t=1, m 1 = β ∗ m 0 + ( 1 − β ) ∗ g 1 m_1 = \beta * m_0 + (1-\beta)*g_1 m1=β∗m0+(1−β)∗g1,由于 m 0 m_0 m0初始为0,且 β \beta β接近1,因此t较小时,m的值偏向于0,v也一样。这里通过除以 1 − β t 1-\beta ^ t 1−βt来进行偏差纠正,即 m ^ t = m t 1 − β t \hat m_t = \frac{m_t}{1-\beta ^ t} m^t=1−βtmt
伪代码:
初始lr, β 1 , β 2 \beta_1 , \beta_2 β1,β2分别用于平滑m和v,可学习参数 θ 0 \theta_0 θ0, m 0 = 0 , v 0 = 0 , t = 0 m_0=0,v_0=0,t=0 m0=0,v0=0,t=0
while 没有停止训练 do
训练次数更新 t=t+1
计算梯度: g t g_t gt (所有学习参数都有自己的梯度,因此 g t g_t gt表示的是全部梯度的集合)
累计梯度: m t = β 1 ∗ m t − 1 + ( 1 − β 1 ) ∗ g t m_t=\beta_1 * m_{t-1} + (1-\beta_1)*g_t mt=β1∗mt−1+(1−β1)∗gt (每个梯度对应一个m,m是一个集合)
累计梯度的平方: v t = β 2 ∗ v t − 1 + ( 1 − β 2 ) ∗ ( g t ) 2 v_t = \beta_2 * v_{t-1} + (1-\beta_2)*(g_t)^2 vt=β2∗vt−1+(1−β2)∗(gt)2 (v是一个集合)
偏差纠正m: m ^ t = m t 1 − ( β 1 ) t \hat m_t = \frac{m_t}{1-(\beta_1) ^ t} m^t=1−(β1)tmt
偏差纠正v: v ^ t = v t 1 − ( β 2 ) t \hat v_t = \frac{v_t}{1-(\beta_2) ^ t} v^t=1−(β2)tvt
更新参数: θ t = θ t − 1 − m ^ t v ^ t + e l r \theta_t = \theta_{t-1} - \frac{\hat m_t}{\sqrt{\hat v_t}+e}lr θt=θt−1−v^t+em^tlr
将momentum算法和RMSProp算法结合起来使用的一种算法,既用动量来累积梯度,又使得收敛速度更快,同时使得波动的幅度更小,并进行了偏差修正。
torch.optim.Adam(params, lr=0.001, betas=(0.9,0.999),eps=1e-8,weight_decay=0,amsgrad=False)
参数:
params
lr (float, optional) 学习率 默认1e-3
betas (Tuple[float, float], optional) 用于计算梯度以及梯度平法的滑动平均值的系数 (默认[0.9,0.99])
eps (float, optional) 防止分母为0 默认1e-8
weight_decay (float, optional) L2惩罚
amsgrad(bool, optional) 是否使用amsgrad。如果为true,在上述伪代码基础上,保留历史最大的 v t v_t vt, 记为 v m a x v_{max} vmax,每次计算都用最大的 v m a x v_{max} vmax,否则用当前 v t v_t vt
优点:
- 对目标函数没有平稳要求,即loss function可以随时间变化
- 参数的更新不受梯度的伸缩变换影响
- 更新步长和梯度大小无关,
性能比较
论文中的给出的结论,在训练数据上Adam比较好,在验证数据上SGDM表现比较好,一般选择Adam或者SGDM。或者在训练后期,使用SGDM。
常用SGDM,adam, RMSProp
学习率调整时模型训练中重要的参数之一,针对学习率的优化方法有很多种,warmup是重要的一种
什么是warmup
warmup是一种学习率优化方法,最早出现在resnet论文中,在模型训练初期选用较小的学习率,训练一段时间之后(10epoch 或者 10000steps)使用预设的学习率进行训练
为什么使用
实现:
wenet
class WarmupLR(_LRScheduler):
"""The WarmupLR scheduler
This scheduler is almost same as NoamLR Scheduler except for following
difference:
NoamLR:
lr = optimizer.lr * model_size ** -0.5
* min(step ** -0.5, step * warmup_step ** -1.5)
WarmupLR:
lr = optimizer.lr * warmup_step ** 0.5
* min(step ** -0.5, step * warmup_step ** -1.5)
Note that the maximum lr equals to optimizer.lr in this scheduler.
"""
def __init__(
self,
optimizer: torch.optim.Optimizer,
warmup_steps: Union[int, float] = 25000,
last_epoch: int = -1,
):
assert check_argument_types()
self.warmup_steps = warmup_steps
# __init__() must be invoked before setting field
# because step() is also invoked in __init__()
super().__init__(optimizer, last_epoch)
def __repr__(self):
return f"{self.__class__.__name__}(warmup_steps={self.warmup_steps})"
def get_lr(self):
step_num = self.last_epoch + 1
return [
lr
* self.warmup_steps ** 0.5
* min(step_num ** -0.5, step_num * self.warmup_steps ** -1.5)
for lr in self.base_lrs
]
def set_step(self, step: int):
self.last_epoch = step
欢迎关注个人公众号,分享知识和日常