深度学习之神经网络的优化器篇

news2024/11/10 11:34:11

神经网络的优化器

文章目录

  • 神经网络的优化器
  • GD 梯度下降算法
  • 重球法
  • SGD随机梯度下降
  • Momentum动量梯度
  • NAG(Nesterov accelerated gradient)
  • AdaGrad(Adaptive gradient)
  • RMSProp(Root mean square prop)
  • Adam(Adaptive Moment Estimation)
  • AdamW
  • Adan(Adaptive Nesterov Momentum)

本片博客记录一下不同的神经网络的优化器

参考大神链接:神经网络的优化器_电器爆破专家的博客-CSDN博客

目录:

设置一些符号的含义如下:损失值为 ℓ ℓ ,需要被更新的可训练参数为 w w w,使用 b b b 来泛指除 w w w 外的其它可训练参数,记 ℓ \ell w w w 的偏导函数为 J ( w ,   b ) = ∂ ℓ ∂ w J(w, \, b)=\frac{\partial \ell}{\partial w} J(w,b)=w,该函数的自变量为全体可训练参数,记 g t = J ( w t ,   b t ) g_t=J(w_t, \, b_t) gt=J(wt,bt),设学习率为 λ \lambda λ(常取值为 0.01 0.01 0.01),优化器迭代的次数为 t t t

GD 梯度下降算法

计算所有样本的预测值和真实值之间的差值作为损失值 ℓ \ell ,然后去更新当前网络的参数 w w w,每次更新完成,再次计算所有样本的损失值 ℓ \ell ,再次计算梯度,更新权值 w,这样反复进行,知道真实值很预测值之间的差值小于跟定阈值,则停止迭代。

w t + 1 = w t − λ   g t a l l w_{t+1} = w_t - \lambda \ g^{all}_t wt+1=wtλ gtall

使用全部的样本使得更新的速度太慢,下面的随机梯度解决了这个问题,当然了还有很多的问题,慢慢来看

重球法

相比于传统的梯度法, 重球法在迭代中引入冲量 m t = w t − w t − 1 m_t = w_t−w_{t−1} mt=wtwt1, 即

m t = w t − w t − 1 w t + 1 = w t + β m t − λ g t m_t = w_t - w_{t-1}\\ w_{t+1} = w_t + \beta m_t - \lambda g_t mt=wtwt1wt+1=wt+βmtλgt

因为引入了两个时刻之间权值的差值作为后一个时刻的一个权重,使得权重的更新更加稳定,会综合考虑的更多。然而, 重球法少被使用, 因为它可以被下面的性能更好的加速梯度下降法替代。与重球法齐名的冲量技巧——Nesterov冲量算法:

SGD随机梯度下降

随机梯度下降法(stochastic gradient descent, SGD)是原始 BP 算法提供的优化器,也是最早在深度学习中应用的优化器。其主要来源于梯度下降算法,但将其改编成不采用全部样本的损失值作为 loss ,而是采用部分样本的损失值作为loss,因为全部样本更新起来太慢了,其公式如下:

w t + 1 = w t − λ   g t w_{t+1} = w_t - \lambda \ g_t wt+1=wtλ gt

SGD 算法面临着诸多挑战:

  • 当使用 SGD 下降到沟壑或盆地时,SGD 可能产生剧烈的抖动。一方面,抖动可能会使其跳出当前极小值,有机会找到更优的极小值;另一方面,抖动可能使得收敛速度减慢或无法收敛到极小值,此时只能通过手动降低学习率来降低抖动。研究者们最先提出了学习率计划表,为损失值设定阈值及其对应的学习率,当损失值下降到某一阈值时,启用该阈值对应的学习率。但学习率计划表,有针对性没有广泛性,对每一个数据集都需要编制其独有的学习率计划表。
  • SGD 对于所有的可训练参数使用相同的学习率是不恰当的。我们不希望以同样的程度来更新所有参数,对于那些频繁更新的参数我们希望它每次更新能有一个较小的幅度,那些更新频率较低的参数我们希望它每次更新能有一个较大的幅度。

Momentum动量梯度

在沟壑中 SGD 会在沟壑两侧剧烈抖动,而在沟壑的下降方向移动十分缓慢。动量法(momentum)通过累积的方式,可以抑制在沟壑两侧方向上的抖动,在下降方向上使速度叠加。其公式如下:

m t = α m t − 1 + λ   g t w t + 1 = w t − m t m_t =αm_{t−1} +λ \ g_t \\ w _{t+1} = w_t − m_t mt=αmt1+λ gtwt+1=wtmt

其中 α \alpha α 是新引入的常量参数(常取值为 α = 0.9 \alpha=0.9 α=0.9),m_t 是为了实现算法而引入的变量。当 g t − 1 g_{t−1} gt1 的符号与 g t g_t gt 的正负不同时, m t m_t mt 的累加就会使二者得到一定的抵消,即抑制抖动的作用;当 g t − 1 g_{t-1} gt1 的符号与 g t g_t gt 的正负相投时 m t m_t mt 的累加就会使二者叠加,即叠加速度的作用。
在这里插入图片描述

从上图可以看出 Momentum 在短时间内就将抖动抑制,而 SGD 抖动从未停止。并且 Momentum 对在沟壑下降方向上对速度的叠加效果也很明显,仅用 1426 轮迭代就走出了模型,而 SGD 使用了 14778 轮。

NAG(Nesterov accelerated gradient)

在传统凸优化领域,有一个与重球法齐名的冲量技巧——Nesterov冲量算法:

我们蒙着眼睛向前走时,总是伸出自己的两只手,探测自己的前方有无障碍物,以便及时更改前进方向。内斯特洛夫加速梯度(nesterov accelerated gradient,NAG)就使用了这种方法,而是使用前方的梯度来修正当前的前进方向。

其公式如下:

w t + 1 ′ = w t − α m t − 1 m t = α m t − 1 + λ   g ( w t + 1 ′ , b t + 1 ′ ) w t + 1 = w t − m t w'_{t+1} = w_t −\alpha m_{t−1} \\ m_t =αm_{t−1} +\lambda \ g(w'_{t+1} ,b'_{t+1}) \\ w_{t + 1} = w_t − m_t wt+1=wtαmt1mt=αmt1+λ g(wt+1,bt+1)wt+1=wtmt

其中 α \alpha α 是新引入的常量参数(常取值为 α = 0.9 \alpha=0.9 α=0.9 ), w t + 1 ′ w'_{t+1} wt+1 是假设的下一时刻已经更新好的权值, m t m_t mt 是为了实现算法而引入的变量。接下来我们将通过一幅示意图为读者介绍 NAG 的原理,以及其与 Momentum 的对比。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5c6YpzWU-1677582212079)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0ee6e298-c662-4b4e-9e2a-39a25c654899/Untitled.png)]

在上图中,Momentum 求当前点的梯度得到图中蓝色短线所示向量,然后再加上动量(图中蓝色长线所示向量)得到最终的更新向量,即图中紫色线所示向量;NAG 不再求当前点的梯度,而是求当前点加上动量所到达的点的梯度,即图中绿色短线所示向量,与动量复合即得到红色线所示的向量。最终 Momentum 将按照紫色向量更新,NAG 将按照红色向量更新。
Momentum的当前梯度为这一个时刻的梯度,加上之前的动量,nesterov 为下一个时刻的梯度加上之前的动量。

Nesterov冲量算法在光滑且一般凸的问题上,拥有比重球法更快的理论收敛速度,并且理论上也能承受更大的batch size。同重球法不同的是,Nesterov算法不在当前点计算梯度,而是利用冲量找到一个外推点,在该点算完梯度以后再进行冲量累积。

外推点能帮助Nesterov算法提前感知当前点周围的几何信息。这种特性使得Nesterov冲量更加适合复杂的训练范式和模型结构(如ViT),因为它并不是单纯地依靠过去的冲量去绕开尖锐的局部极小点,而是通过提前观察周围的梯度,调整更新的方向。

尽管Nesterov冲量算法拥有一定的优势,但是在深度优化器中,却鲜有被应用与探索。其中一个主要的原因就是Nesterov算法需要在外推点计算梯度,在当前点更新,期间需要多次模型参数重载以及需要人为地在外推点进行back-propagation (BP)。这些不便利性极大地限制了Nesterov冲量算法在深度模型优化器中的应用。

AdaGrad(Adaptive gradient)

前面我们提到为可训练参数设置相同的学习率是不合理的。自适应梯度(adaptive gradient, AdaGrad)提供了一种为参数动态调整学习率的方法。它为频繁更新的参数设置较低的学习率,为不经常更新的参数设置较高的学习率,从而使每个参数都有自己的更新幅度。其公式如下:

v t = v t − 1 + g t 2 w t + 1 = w t − λ v t + ϵ ⋅ g t v_t =v_{t−1} + g_t^2 \\ w_{t+1}=w_t-\frac{\lambda}{\sqrt{v_t+\epsilon}} \cdot g_t vt=vt1+gt2wt+1=wtvt+ϵ λgt

其中为了避免分母为零而引入的常量参数 ϵ \epsilon ϵ (常取值为 ϵ = 1 × 1 0 − 8 \epsilon=1 \times 10^{-8} ϵ=1×108 v t v_t vt 是为了实现算法而引入的变量。 v t v_t vt 一直在对 g t 2 g_t^2 gt2 做累加,如果一个参数频繁更新必然会导致 v t v_t vt 增大的幅度超乎寻常,那么 λ v t + ϵ \frac{\lambda}{\sqrt{v_t+\epsilon}} vt+ϵ λ
就会超乎寻常的相应变小。这种方式也可以抑制抖动,即让那些梯度有剧烈变化的参数有一个较小的学习率。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGITemPF-1677582212079)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fab7a6db-8fa5-474f-994a-bac73b88e23d/Untitled.png)]

如图所示 AdaGrad 为 y 配置了较大的学习率,为 x 配置了较小的学习率,从而使其能够快速脱离马鞍。AdaGrad 仅迭代了 2519 轮,而 SGD 迭代了 125005 轮。

我们看到 v t v_t vt 一直在做正数累加,总体上会使全体参数的学习率趋向无穷小,在训练的后期会使模型的收敛速度变得极慢。不可否认的是,在训练的后期是需要降低学习率,从而稳定下降到极小值,避免在极小值处抖动,即使用退火学习率。笔者推测,AdaGrad 也是出于这种考量,使用正数累加的方式从总体上来降低学习率,让模型在训练后期稳定下降。但 AdaGrad 的现实表现却不尽如人意。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p6rUnWfx-1677582212079)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d40a8e9c-ebc7-4fb1-9660-a04cc0eb5cad/Untitled.png)]

我们可以看到,AdaGrad 在峡谷中十分稳定没有分毫抖动,但不断下降的学习率让它步履维艰,迭代了 100000 轮还没有走出峡谷。

若想深入了解该方法可查阅原始文献《Adaptive Subgradient Methods for Online Learning and Stochastic Optimization》

RMSProp(Root mean square prop)

均方根支撑(root mean square prop, RMSProp)是 Geoff Hinton 在他的课堂讲义中提出的一个尚未发表的方法。RMSProp 相对于 AdaGrad 单调减少的学习率有了很大改善,它的 v t v_t vt 不再是做正数累加,而是使用了衰减平均值,使其能够稳定在一定的范围之中。其公式如下:

v t = β v t − 1 + ( 1 − β ) g t 2 w t + 1 = w t − λ v t + ϵ ⋅ g t v t =βv_{t−1} +(1−β)g_t^2 \\ w_{t+1}=w_t - \frac{\lambda}{\sqrt{v_t + \epsilon}} \cdot g_t vt=βvt1+(1β)gt2wt+1=wtvt+ϵ λgt

其中常量参数 ϵ \epsilon ϵ 的作用及常用取值与 AdaGrad 一致,新引入常量参数 β \beta β 作为 v t v_t vt 的衰减系数(常取值为 β = 0.9 \beta=0.9 β=0.9 ), v t v_t vt 是为了实现算法而引入的变量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1gPbQDg-1677582212080)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4b759127-3d7d-4f67-ab08-ffedf69cec05/Untitled.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rrr0agT-1677582212080)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ec287574-0d68-4474-96f3-6a51c33ddc81/Untitled.png)]

可以看到,无论是在马鞍上还是在峡谷中 RMSProp 在速度和抑制抖动方面都有着非常出色的表现。但细心观察会发现 RMSProp 在峡谷底部还是有细微的抖动,看来仅凭学习率来抑制抖动,还是无法做到根除。

若想深入了解该方法可查阅原始文献《rmsprop: Divide the gradient by a running average of its recent magnitude》

Adam(Adaptive Moment Estimation)

自适应矩估计(Adaptive Moment Estimation, Adam)是个缝合怪,它把 Momentum 和 RMSProp 缝合到了一起,使得它既有自适应调节学习率的能力,也有动量抑制抖动、叠加速度的能力。其表达式如下:

{ m t = α ⋅ m t − 1 + ( 1 − α ) g t v t = β ⋅ v t − 1 + ( 1 − β ) g t 2 w t + 1 = w t − λ v t 1 − β t + ϵ ⋅ m t 1 − α t \left\{ \begin{array}{rcl} m_t =\alpha⋅m_{t−1} +(1− \alpha)g_t \\ v_t =\beta ⋅v_{t−1} +(1−\beta)g_t^2 \end{array}\right. \\ w_{t+1}=w_t - \frac{\lambda}{\sqrt{\frac{v_t}{1-\beta^t}}+\epsilon} \cdot \frac{m_t}{1-\alpha^t} {mt=αmt1+(1α)gtvt=βvt1+(1β)gt2wt+1=wt1βtvt +ϵλ1αtmt

其中 α \alpha α β \beta β 是用作衰减系数的常量参数(常取值为 α = 0.9 , β = 0.999 \alpha=0.9,\beta=0.999 α=0.9,β=0.999),常量参数 ϵ \epsilon ϵ 的作用及常用取值与 AdaGrad 一致, m t m_t mt v t v_t vt 是为了实现算法而引入的变量。值得注意的是 Adam 的作者对 m t m_t mt v t v_t vt 做了如下处理:

m t 1 − α t v t 1 − β t \frac{m_t}{1-\alpha^t} \\ \frac{v_t}{1-\beta^t} 1αtmt1βtvt

因为作者发现 m t m_t mt v t v_t vt 在初始化时为零,所以在刚开始迭代时其值很小(特别是在衰减值设置的很大的时候)。所以作者加入,在刚开始迭代时使其得到适当放大。可以看到随着迭代次数的增加 1 − α t 1-\alpha^t 1αt 1 − β t 1-\beta^t 1βt 的值逐渐趋于 1 1 1,所以迭代次数达到一定值时,二者的影响就可以忽略不计了。

具体解释:

m t = α ⋅ m t − 1 + ( 1 − α ) g t m_t =\alpha⋅m_{t−1} +(1− \alpha)g_t mt=αmt1+(1α)gt :代表当前梯度和当前动量的结合。

上面代表一阶动量:代表惯性,当前梯度更新的方向不仅要考虑当前梯度,还要 考虑历史梯度的影响;

v t = β ⋅ v t − 1 + ( 1 − β ) g t 2 v_t =\beta ⋅v_{t−1} +(1−\beta)g_t^2 vt=βvt1+(1β)gt2 :代表当前自适应梯度的权值。

上面代表二阶动量:用于控制自适应学习率,二阶动量在后面被放置在分母的位置,其越大代表学习率越小,

二阶动量的物理意义:

  • 对于经常更新的参数,不希望被单个样本影响太大,希望学习率慢一些。
  • 对于偶尔更新的参数,希望能够从偶然出现的样本中多学习一些,也就是希望学习率大一点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LSD0FQhm-1677582212080)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/01175672-eaab-44a1-977b-1fbb6d8593f4/Untitled.png)]

通过上图可以看到可以看到 Adam 相对于 RMSProp 在马鞍上的表现更为优秀,下降曲线也比较平滑。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7MXV6YGw-1677582212081)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b400185e-55d6-4d8c-8331-ee2fdb07706f/Untitled.png)]

通过上图可以看到,虽然 Adam 的下降速度比 RMSProp 慢一些,但是在峡谷中没有像 RMSProp 一样发生抖动。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWkuQN8d-1677582212081)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/46c6983f-73c1-4803-90cf-80b0812577f4/Untitled.png)]

通过上图可以更直观的看出 Adam 的优势,Adam 经过 1379 轮迭代后下降到了最小值点,而 RMSProp 一直在最小值附近抖动,经过 100000 轮迭代还没有稳定下来。
若想深入了解该方法可查阅原始文献《ADAM: A METHOD FOR STOCHASTIC OPTIMIZATION》

但是这么好的算法,在提出之后,并没有像想象中的大放异彩,而是在各大论文中不断的被论证 Adam 的精度还会低于 SGD。让我们先来分析一下为什么会出现这种情况。

Adam 缺点分析:

其实Adam本身没有问题,问题在于目前大多数DL框架都是在优化器之前加上L2正则项来替代weight decay。

但是在 Adam 优化器的情况下,使用 L2 正则化来替代 weight decay 并不是等价的。

1、先看在 SGD 的情况下,L2 和 weight decay 是否等价的情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwzc8ANr-1677582212081)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9bb041bc-6bc9-4a96-bb1c-c67dfa7f654f/Untitled.png)]

当 下面的学习率 λ ′ = λ α \lambda' = \frac{\lambda}{\alpha} λ=αλ 你可以发现 ,上面使用 L2 正则化来替代 weight decay 是完全等价的。

2、在看看 Adam 的情况下,L2 和 weight decay 是否等价的情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1VsMbfAp-1677582212082)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e52527a3-ebff-4822-9a96-4cd725739554/Untitled.png)]

从上面可以看出来,只有当 M t = k I M_t = kI Mt=kI 的时候,L2 和 weight decay 是等价,但是这样就代表着,一阶动量要始终为 单位矩阵的时候,这样一阶动量就没有预先设想的那样,带来了很好的效果。

原因:

1、使用Adam优化带L2正则的损失并不有效。如果引入L2正则项,在计算梯度的时候会加上对正则项求梯度的结果 f t r e g ′ = f t ′ ( w ) + λ w f_t^{reg'} = f_t'(w) + \lambda w ftreg=ft(w)+λw

2、那么如果本身比较大的一些权重对应的梯度也会比较大,由于Adam计算步骤中减去项会除以梯度平方的累积开根号,使得减去项偏小。按常理说,越大的权重应该惩罚越大,但是在Adam并不是这样。分子分母相互抵消掉了。公式如下:

w t + 1 = w t − λ v t 1 − β t + ϵ ⋅ m t 1 − α t w_{t+1}=w_t - \frac{\lambda}{\sqrt{\frac{v_t}{1-\beta^t}}+\epsilon} \cdot \frac{m_t}{1-\alpha^t} wt+1=wt1βtvt +ϵλ1αtmt

假设 w t w_t wt 是比较大的,那么 我们会发现

g t = ℓ ′ ( w t , b ) + γ w t λ v t 1 − β t + ϵ ⋅ m t 1 − α t = λ v t 1 − β t + ϵ ⋅ β m t − 1 + ( 1 − β ) ( ℓ ′ ( w t − 1 , b ) + γ w t − 1 ) 1 − α t λ v t 1 − β t + ϵ ⋅ ( β m t − 1 1 − α t + ( 1 − β ) ( ℓ ′ ( w t − 1 , b ) + γ w t − 1 ) 1 − α t ) g_t = \ell'(w_t, b) + \gamma w_t \\\frac{\lambda}{\sqrt{\frac{v_t}{1-\beta^t}}+\epsilon} \cdot \frac{m_t}{1-\alpha^t} \\ = \frac{\lambda}{\sqrt{\frac{v_t}{1-\beta^t}}+\epsilon} \cdot \frac{\beta m_{t-1} + (1-\beta)(\ell'(w_{t-1}, b) + \gamma w_{t-1})}{1-\alpha^t} \\ \frac{\lambda}{\sqrt{\frac{v_t}{1-\beta^t}}+\epsilon} \cdot \left( \frac{\beta m_{t-1} }{1-\alpha^t} + \frac{(1-\beta)(\ell'(w_{t-1}, b) + \gamma w_{t-1})}{1-\alpha^t} \right) gt=(wt,b)+γwt1βtvt +ϵλ1αtmt=1βtvt +ϵλ1αtβmt1+(1β)((wt1,b)+γwt1)1βtvt +ϵλ(1αtβmt1+1αt(1β)((wt1,b)+γwt1))

对于权重的大参数, v t 1 − β t \sqrt{\frac{v_t}{1-\beta^t}} 1βtvt 有很大的值,造成 γ   w t − 1 v t 1 − β t \frac{\gamma \ w_{t-1}}{\sqrt{\frac{v_t}{1-\beta^t}}} 1βtvt γ wt1 很小,反而使得,在大权重上这个方向上,权重 W W W 被正则化的更少。 反而更新率几乎很小,不变了。

3、而权重衰减对所有的权重都采用相同的系数进行更新,越大的权重显然惩罚越大。

4、在常见的深度学习库中只提供了L2正则,并没有提供权重衰减的实现。

那么如何缓和上述adam的局限性呢?且看下面的AdamW

AdamW

因为 Adam 在大的权重更新上面,反而会出现惩罚变小的情况,导致训练效果不佳。AdamW 只是在 Adam 的基础之上,在更新参数的时候,再加上对应权重的正则化的值。

g t = ℓ ′ ( w t , b ) + γ w t { m t = α ⋅ m t − 1 + ( 1 − α ) g t v t = β ⋅ v t − 1 + ( 1 − β ) g t 2 m t ^ = m t a − α t v t ^ = v t 1 − β t w t + 1 = w t − η t ( λ m t ^ v t ^ + ϵ + γ w t ) g_t = \ell'(w_t, b) + \gamma w_t\\ \left\{ \begin{array}{rcl} m_t =\alpha⋅m_{t−1} +(1− \alpha)g_t \\ v_t =\beta ⋅v_{t−1} +(1−\beta)g_t^2 \end{array}\right. \\ \hat{m_t} = \frac{m_t}{a-\alpha_t} \\ \hat{v_t} = \frac{v_t}{1-\beta_t} \\ w_{t+1}=w_t - \eta_t(\frac{\lambda \hat{m_t}}{\sqrt{\hat{v_t}}+\epsilon} + \gamma w_t) gt=(wt,b)+γwt{mt=αmt1+(1α)gtvt=βvt1+(1β)gt2mt^=aαtmtvt^=1βtvtwt+1=wtηt(vt^ +ϵλmt^+γwt)

就是在原有 Adam的基础之上,将原有的 正则项 的倒数加入到参数的更新当中了。

总之一句话,如果使用了weightdecay就不必再使用L2正则化了。

还有:随着Adam训练原始ViT失败,它的改进版本AdamW渐渐地变成了训练ViT甚至ConvNext的首选。但是AdamW并没有改变Adam中的冲量范式,因此在当batch size超过4,096的时候,AdamW训练出的ViT的性能会急剧下降。

代码参考

文章中的算法流程图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypn9F2YU-1677582212082)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c292484c-ec9c-470a-bc0c-f9d339c551a3/Untitled.png)]

对应的解释流程图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWYsXf9Z-1677582212082)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a7e60f73-7d9a-4d4f-acf7-bb8c01e8dd90/Untitled.png)]

Adan(Adaptive Nesterov Momentum)

1、论文链接 2、代码链接 3、参考链接

通过结合改写的Nesterov冲量与自适应优化算法,并引入解耦的权重衰减,可以得到最终的Adan优化器。利用外推点,Adan可以提前感知周围的梯度信息,从而高效地逃离尖锐的局部极小区域,以增加模型的泛化性。

先从下面两个改进,再将两个改进加在一起就变成了 adan 。

1) 自适应的 Nesterov 冲量

先从 Nesterov 梯度优化器推导:

w t + 1 ′ = w t − α m t − 1 m t = α m t − 1 + λ   g ( w t + 1 ′ , b t + 1 ′ ) w t + 1 = w t − m t w'_{t+1} = w_t −\alpha m_{t−1} \\ m_t =αm_{t−1} +\lambda \ g(w'_{t+1} ,b'_{t+1}) \\ w_{t + 1} = w_t − m_t wt+1=wtαmt1mt=αmt1+λ g(wt+1,bt+1)wt+1=wtmt

但是,计算外推点 w t + 1 ′ w'_{t+1} wt+1 处的梯度,会因为同时保留 w t + 1 ′ w'_{t+1} wt+1 w t w_t wt 带来额外的计算和内存开销。

先优化 Nesterov 梯度外导点的方式:

使用 g t + ( 1 − β 2 ) ( g t − g t − 1 ) g_t + (1-\beta_2)(g_t - g_{t-1}) gt+(1β2)(gtgt1) 来替代 g ( w t + 1 ′ ) g(w'_{t+1}) g(wt+1)

替代完成的公式如下:

w t + 1 ′ = w t − α m t − 1 m t = β 1 m t − 1 + [ g t + ( 1 − β 1 ) ( g t − g t − 1 ) ] w t + 1 = w t − m t w'_{t+1} = w_t −\alpha m_{t−1} \\ m_t =\beta_1 m_{t−1} + [g_t + (1-\beta_1)(g_t - g_{t-1})] \\ w_{t + 1} = w_t − m_t wt+1=wtαmt1mt=β1mt1+[gt+(1β1)(gtgt1)]wt+1=wtmt

可以证明,改写的Nesterov冲量算法与原算法等价,两者的迭代点可以相互转化,且最终的收敛点相同。可以看到,通过引入梯度的差分项,已经可以避免手动的参数重载和人为地在外推点进行BP。

将改写的Nesterov冲量算法同自适应类优化器相结合, 将 m t m_t mt的更新由累积形式替换为移动平均形式,并使用二阶moment对学习率进行放缩:

m t = β 1 m t − 1 + [ g t + ( 1 − β 1 ) ( g t − g t − 1 ) ] n t = ( 1 − β 3 ) n t − 1 + β 3 [ g t + ( 1 − β 2 ) ( g t − g t − 1 ) ] 2 η t = η n t + ϵ w t + 1 = w t − η t ∘ m t m_t =\beta_1 m_{t−1} + [g_t + (1-\beta_1)(g_t - g_{t-1})]\\ n_t = (1 - \beta_3)n_{t-1} + \beta_3[g_t + (1-\beta_2)(g_t - g_{t-1})]^2 \\ \eta_t = \frac{\eta}{\sqrt{n_t + \epsilon}} \\ w_{t+1} = w_{t} - \eta_t \circ m_t mt=β1mt1+[gt+(1β1)(gtgt1)]nt=(1β3)nt1+β3[gt+(1β2)(gtgt1)]2ηt=nt+ϵ ηwt+1=wtηtmt

至此已经得到了Adan的算法的基础版本。

理解移动平代替累积形式:

1、累积形式:原始的 m t = m t − 1 + λ   g ( w t + 1 ′ , b t + 1 ′ ) m_t =m_{t−1} +\lambda \ g(w'_{t+1} ,b'_{t+1}) mt=mt1+λ g(wt+1,bt+1) 为累积形式,就是简单将输出的梯度不断地累加。

2、移动平均: m t = β m t − 1 + ( 1 − β )   g ( w t + 1 ′ , b t + 1 ′ ) m_t =\beta m_{t−1} +(1-\beta) \ g(w'_{t+1} ,b'_{t+1}) mt=βmt1+(1β) g(wt+1,bt+1) 为移动平均,对输入的两个输入给予总和为 1 的权重,使得输出的在两个输入之间移动。

2) 梯度差分的冲量

可以发现, m t = β 1 m t − 1 + [ g t + ( 1 − β 1 ) ( g t − g t − 1 ) ] m_t =\beta_1 m_{t−1} + [g_t + (1-\beta_1)(g_t - g_{t-1})] mt=β1mt1+[gt+(1β1)(gtgt1)] 的更新将梯度与梯度的差分耦合在一起 ,但是在实际场景中,往往需要对物理意义不同的两项进行单独处理,因此研究人员引入梯度差分的冲量 v t = ( 1 − β 2 ) v t − 1 + β 2 ( g t − g t − 1 ) v_t = (1 - \beta_2)v_{t-1} + \beta_2(g_t - g_{t-1}) vt=(1β2)vt1+β2(gtgt1)

替换完成的公式如下:

m t = ( 1 − β 1 ) m t − 1 + β 1 g t v t = ( 1 − β 2 ) v t − 1 + β 2 ( g t − g t − 1 ) n t = ( 1 − β 3 ) n t − 1 + β 3 [ g t + ( 1 − β 2 ) ( g t − g t − 1 ) ] 2 η t = η n t + ϵ w t + 1 = w t − η t ∘ m t m_t = (1 - \beta_1)m_{t-1} + \beta_1 g_t \\ v_t = (1 - \beta_2)v_{t-1} + \beta_2(g_t - g_{t-1}) \\ n_t = (1 - \beta_3)n_{t-1} + \beta_3[g_t + (1-\beta_2)(g_t - g_{t-1})]^2 \\ \eta_t = \frac{\eta}{\sqrt{n_t + \epsilon}} \\ w_{t+1} = w_{t} - \eta_t \circ m_t mt=(1β1)mt1+β1gtvt=(1β2)vt1+β2(gtgt1)nt=(1β3)nt1+β3[gt+(1β2)(gtgt1)]2ηt=nt+ϵ ηwt+1=wtηtmt

3) 解耦的权重衰减

对于带L2权重正则的目标函数,目前较流行的AdamW优化器通过对L2正则与训练loss解耦,在ViT和ConvNext上获得了较好的性能。但是AdamW所用的解耦方法偏向于启发式,目前并不能得到其收敛的理论保证。

基于对L2正则解耦的思想,也给Adan引入解耦的权重衰减策略。目前Adan的每次迭代可以看成是在最小化优化目标F的某种一阶近似:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-noA4wgs5-1677582212083)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/09dc8474-d010-4e71-a367-94fba7bbf80f/Untitled.png)]

由于F中的L2权重正则过于简单且光滑性很好,以至于不需要对其进行一阶近似。因此,可以只对训练loss进行一阶近似而忽略L2权重正则,那么Adan的最后一步迭代将会变成:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7aIwYm1-1677582212083)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/cfd38bf9-7974-4d71-9768-40504d4e5ae2/Untitled.png)]

有趣的是,可以发现AdamW的更新准则是Adan更新准则在学习率eta接近0时的一阶近似。因此,可从proximal 算子的角度给Adan甚至AdamW给出合理的解释而不是原来的启发式改进。

所以提出了新的优化器 Nesterov momentum estimation (NME).

具体公式如下:

m t = ( 1 − β 1 ) m t − 1 + β 1 g t v t = ( 1 − β 2 ) v t − 1 + β 2 ( g t − g t − 1 ) n t = ( 1 − β 3 ) n t − 1 + β 3 [ g t + ( 1 − β 2 ) ( g t − g t − 1 ) ] 2 η t = η n t + ϵ w t + 1 = ( 1 + λ η ) − 1 [ w t − η t ∘ ( m t + ( 1 − β 2 ) v k ) ] m_t = (1 - \beta_1)m_{t-1} + \beta_1 g_t \\ v_t = (1 - \beta_2)v_{t-1} + \beta_2(g_t - g_{t-1}) \\ n_t = (1 - \beta_3)n_{t-1} + \beta_3[g_t + (1-\beta_2)(g_t - g_{t-1})]^2 \\ \eta_t = \frac{\eta}{\sqrt{n_t + \epsilon}} \\ w_{t+1} = (1+\lambda \eta)^{-1}[w_{t} - \eta_t \circ ( m_t + (1-\beta_2)v_k)] mt=(1β1)mt1+β1gtvt=(1β2)vt1+β2(gtgt1)nt=(1β3)nt1+β3[gt+(1β2)(gtgt1)]2ηt=nt+ϵ ηwt+1=(1+λη)1[wtηt(mt+(1β2)vk)]

具体公式解析:

第一行:计算了动量

第二行:计算了自适应学习率的更新参数

第三行:其中 g t + ( 1 − β 2 ) ( g t − g t − 1 ) g_t + (1-\beta_2)(g_t - g_{t-1}) gt+(1β2)(gtgt1) 是被用来替代上面 Nesterov 中的下一适合的假象梯度 g ( w t + 1 ′ ) g(w'_{t+1}) g(wt+1),这样就可以节约计算和内存带来的开销。

第四行:自适应动量的参数

第五行:引入 动态L2正则 的权重衰减项

Adan结合了自适应优化器、Nesterov冲量以及解耦的权重衰减策略的优点,能承受更大的学习率和batch size,以及可以实现对模型参数的动态L2正则。

优化器的表现可视化所使用的代码:

from matplotlib import pyplot as plt
from matplotlib import colors
import numpy as np

class Ravine:
    @staticmethod
    def get_name():
        return 'Ravine'

    # 模型的方程
    @staticmethod
    def function(x, y):
        return -np.cos(2 * x) * 50 + np.power(np.e, y)

    # 模型的梯度
    @staticmethod
    def gradient(x, y):
        return np.sin(2 * x) * 100, np.power(np.e, y)

    # 输出模型的范围,依次为:x 轴最小值、x 轴最大值、y 轴最小值、y 轴最大值
    @staticmethod
    def get_scope():
        return -1, 1, -5, 1

    # 输出优化器在本模型上梯度下降的起点
    @staticmethod
    def get_start():
        return -0.8, 0.5

class Saddle:
    @staticmethod
    def get_name():
        return 'Saddle'

    @staticmethod
    def function(x, y):
        return x * x - y * y * y * y

    @staticmethod
    def gradient(x, y):
        return x * 2, -y * y * y * 4

    @staticmethod
    def get_scope():
        return -2, 2, -2, 2

    @staticmethod
    def get_start():
        return -1, -0.01

class Beale:
    @staticmethod
    def get_name():
        return 'Beale'

    @staticmethod
    def function(x, y):
        return (1.5 - x * y)**2 + (2.25 - x - x * y * y)**2 + (2.625 - x + x * y * y * y)**2

    @staticmethod
    def gradient(x, y):
        gradient_x = 2 * ((1.5 - x + x * y) * (-1 + y) + (2.25 - x + x * y * y) * (-1 + y * y) + (
                    2.625 - x + x * y * y * y) * (-1 + y * y * y))
        gradient_y = 2 * ((1.5 - x + x * y) * x + (2.25 - x + x * y * y) * (2 * x * y) + (2.625 - x + x * y * y * y) * (
                    3 * x * y * y))
        return gradient_x, gradient_y

    @staticmethod
    def get_scope():
        return -5, 5, -5, 5

    @staticmethod
    def get_start():
        return 1.5, 1.2

class SGD:
    _learning_rate = 0.01

    def optimize(self, gradient_w, w, t):
        w = w - self._learning_rate * gradient_w
        return w

class Momentum:
    _learning_rate = 0.01
    _alpha = 0.9

    def __init__(self):
        self.m = 0

    def optimize(self, gradiant_w, w, t):
        self.m = self._alpha * self.m + self._learning_rate * gradiant_w
        w = w - self.m
        return w

class NAG:
    _learning_rate = 0.01
    _alpha = 0.9

    def __init__(self):
        self.m = 0

    def get_momentum(self):
        return self._alpha * self.m

    def optimize(self, detection_gradiant_w, w, t):
        self.m = self._alpha * self.m + self._learning_rate * detection_gradiant_w
        w = w - self.m
        return w

class AdaGrad:
    _learning_rate = 0.01
    _epsilon = 0.0000000001

    def __init__(self):
        self.v = 0

    def optimize(self, gradient_w, w, t):
        self.v = self.v + gradient_w**2
        w = w - self._learning_rate / np.sqrt(self.v + self._epsilon) * gradient_w
        return w

class RMSProp:
    _learning_rate = 0.01
    _beta = 0.9
    _epsilon = 0.0000000001

    def __init__(self):
        self.v = 0

    def optimize(self, gradient_w, w, t):
        self.v = self._beta * self.v + (1 - self._beta) * gradient_w**2
        w = w - self._learning_rate / np.sqrt(self.v + self._epsilon) * gradient_w
        return w

class AdaDelta:
    _beta = 0.9
    _epsilon = 0.0000000001

    def __init__(self):
        self.v = 0
        self.d = 0

    def optimize(self, gradient_w, w, t):
        self.v = self._beta * self.v + (1 - self._beta) * gradient_w**2
        t = np.sqrt(self.d + self._epsilon) / np.sqrt(self.v + self._epsilon) * gradient_w
        w = w - t
        self.d = self._beta * self.d + (1 - self._beta) * t**2
        return w

class Adam:
    _learning_rate = 0.01
    _alpha = 0.9
    _beta = 0.99
    _epsilon = 0.0000000001

    def __init__(self):
        self.m = 0
        self.v = 0

    def optimize(self, gradient_w, w, t):
        self.m = self._alpha * self.m + (1 - self._alpha) * gradient_w
        self.v = self._beta * self.v + (1 - self._beta) * gradient_w**2
        w = w - self._learning_rate \
            / (np.sqrt(self.v / (1 - np.power(self._beta, t))) + self._epsilon) \
            * self.m / (1 - np.power(self._alpha, t))
        return w

optimizers = {
    'SGD': SGD,
    'Momentum': Momentum,
    'NAG': NAG,
    'AdaGrad': AdaGrad,
    'RMSProp': RMSProp,
    'AdaDelta': AdaDelta,
    'Adam': Adam,
}

def experiment(axes, model, optimizer):
    scope = model.get_scope()
    x, y = np.meshgrid(np.linspace(scope[0], scope[1], 100), np.linspace(scope[2], scope[3], 100))
    z = model.function(x, y)
#    axes.plot_surface(x, y, z, zorder=1)                                        # 在图上绘制模型
    axes.plot_surface(x, y, z, zorder=1, norm=colors.LogNorm(), cmap='jet')     # 在图上绘制模型
    axes.set_xlabel('x')
    axes.set_ylabel('y')
    axes.set_zlabel('z')
    axes.set_title(f'%s in %s' % (optimizer, model.get_name()))

    optimizer_x = optimizers[optimizer]()   # 为 x 生成优化器
    optimizer_y = optimizers[optimizer]()   # 为 y 生成优化器
    x, y = model.get_start()
    xa, ya = [x], [y]       # 用于记录下降过程中经过的点
    t = 1       # 记录迭代轮次
    while (t < 10
           or (t < 100000
               and not (ya[-1] == ya[-2] and xa[-1] == xa[-2])
               and (scope[0] < x < scope[1] and scope[2] < y < scope[3]))):
        if optimizer == 'NAG':      # 计算梯度
            gradient_x, gradient_y = model.gradient(x - optimizer_x.get_momentum(), y - optimizer_y.get_momentum())
        else:
            gradient_x, gradient_y = model.gradient(x, y)
        x = optimizer_x.optimize(gradient_x, x, t)      # 用优化器优化
        y = optimizer_y.optimize(gradient_y, y, t)      # 用优化器优化
        xa.append(x)
        ya.append(y)
        t = t + 1

    za = [model.function(i, j) for i, j in zip(xa, ya)]         # 生成下降时经过的点的 z 轴坐标
    axes.plot(xa, ya, za, zorder=3, label=optimizer)            # 在图上绘制下降路线
    axes.text(x, y, model.function(x, y), f'epoch=%d' % t)
    axes.legend()

if __name__ == '__main__':
    experiment(plt.subplot(121, projection='3d'), Beale, 'RMSProp')
    experiment(plt.subplot(122, projection='3d'), Beale, 'Adam')
    plt.show()

以下为生成 NAG 示意图的代码:

from matplotlib import pyplot as plt
import numpy as np

ax = plt.subplot(111, aspect='equal')
ax.axis('off')
ax.arrow(0.00, 0.00, 0.02, 0.04, length_includes_head=True, color='b')
ax.arrow(0.02, 0.04, 0.08, 0.04, length_includes_head=True, color='b')
ax.arrow(0.00, 0.00, 0.10, 0.08, length_includes_head=True, color='m')
ax.arrow(0.00, 0.00, 0.08, 0.04, length_includes_head=True, color='g')
ax.arrow(0.08, 0.04, 0.02, -0.04, length_includes_head=True, color='g')
ax.arrow(0.00, 0.00, 0.10, 0.00, length_includes_head=True, color='r')
ax.text(-0.015, 0.02, r'$-\lambda \cdot J(w_t, \omega_t)$', color='b', size=12)
ax.text(0.048, 0.061, r'$-\alpha m_t$', color='b', size=12)
ax.text(0.06, 0.045, r'$-\alpha m_t-\lambda \cdot J(w_t, \omega_t)$', color='m', size=12)
ax.text(0.083, 0.082, 'Momentum', color='m', size=16)
ax.text(0.04, 0.027, r'$-\alpha m_t$', color='g', size=12)
ax.text(0.045, 0.010, r'$-\lambda \cdot J(w_t - \alpha m_t, \omega_t - \alpha\mu_t)$', color='g', size=12)
ax.text(0.02, -0.004, r'$-\alpha m_t - \lambda \cdot J(w_t - \alpha m_t, \omega_t - \alpha\mu_t)$', color='r', size=12)
ax.text(0.102, -0.002, 'NAG', color='r', size=16)
plt.show()

AdamW 的官方代码

def apply_gradients(self, grads_and_vars, global_step=None, name=None):
    """See base class."""
    assignments = []
    for (grad, param) in grads_and_vars:
      if grad is None or param is None:
        continue

      param_name = self._get_variable_name(param.name)

      m = tf.get_variable(
          name=param_name + "/adam_m",
          shape=param.shape.as_list(),
          dtype=tf.float32,
          trainable=False,
          initializer=tf.zeros_initializer())
      v = tf.get_variable(
          name=param_name + "/adam_v",
          shape=param.shape.as_list(),
          dtype=tf.float32,
          trainable=False,
          initializer=tf.zeros_initializer())

      # Standard Adam update.
      next_m = (
          tf.multiply(self.beta_1, m) + tf.multiply(1.0 - self.beta_1, grad))
      next_v = (
          tf.multiply(self.beta_2, v) + tf.multiply(1.0 - self.beta_2,
                                                    tf.square(grad)))

      update = next_m / (tf.sqrt(next_v) + self.epsilon)

      # Just adding the square of the weights to the loss function is *not*
      # the correct way of using L2 regularization/weight decay with Adam,
      # since that will interact with the m and v parameters in strange ways.
      #
      # Instead we want ot decay the weights in a manner that doesn't interact
      # with the m/v parameters. This is equivalent to adding the square
      # of the weights to the loss with plain (non-momentum) SGD.
      if self._do_use_weight_decay(param_name):
        update += self.weight_decay_rate * param

      update_with_lr = self.learning_rate * update

      next_param = param - update_with_lr

      assignments.extend(
          [param.assign(next_param),
           m.assign(next_m),
           v.assign(next_v)])
    return tf.group(*assignments, name=name)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/378200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cesium学习记录03-QGis数据生产=>Postgis存储=>Geoserver发布=>Cesium调用

说明&#xff1a; 参照文章 1&#xff0c;安装 QGIS 下载安装 &#xff08;前四步就可以了&#xff09; 2&#xff0c;下载安装postgresql 3&#xff0c;下载安装PostGis 4&#xff0c;QGIS连接PostGis 5&#xff0c;QGIS 上传到Postgis 1&#xff0c;QGIS图的图 &…

坚鹏:学习贯彻二十大精神 解码共同富裕之道(面向银行)

学习贯彻二十大精神 解码共同富裕之道课程背景&#xff1a; 很多银行从业人员存在以下问题&#xff1a; 不知道如何准确解读二十大精神&#xff1f; 不清楚共同富裕相关政策要求&#xff1f; 不知道如何有效推动共同富裕&#xff1f; 课程特色&#xff1a; 有实战案例 有…

【C++】STL 模拟实现之 list

文章目录一、list 的常用接口及其使用1、list 一般接口2、list 特殊接口3、list 排序的性能分析二、list 迭代器的实现1、迭代器的分类2、list 迭代器失效问题3、list 迭代器源码分析4、list 迭代器模拟实现4.1 普通迭代器4.2 const 迭代器4.3 完整版迭代器三、list 的模拟实现…

05 封装

在对 context 的封装中&#xff0c;我们只是将 request、response 结构直接放入 context 结构体中&#xff0c;对应的方法并没有很好的封装。 函数封装并不是一件很简单、很随意的事情。相反&#xff0c;如何封装出易用、可读性高的函数是非常需要精心考量的&#xff0c;框架中…

Pwn 二进制漏洞审计

PWN的另一个名字是二进制漏洞审计 Pwn和逆向工程一样&#xff0c;是操作底层二进制的&#xff0c;web则是在php层面进行渗透测试 我是从re开始接触CTF的&#xff0c;有一点二进制基础&#xff0c;本文可能会忽略一些基础知识的补充 ”Pwn”是一个黑客语法的俚语词 &#xff0c;…

JS#1 引入方式和基础语法

JavaScript(JS)是一门跨平台, 面向对象的脚本语言, 来控制网页行为的, 它能够是网页可交互一. 引入方式内部脚本与外部脚本内部脚本: 将JS代码定义在HTML页面中外部脚本: 将JS代码定义在外部JS文件中, 然后引入到HTML页面中注意: 在HTML中,JS代码必须位于<script></sc…

纯手动搭建大数据集群架构_记录008_搭建Hbase集群_配置集群高可用---大数据之Hadoop3.x工作笔记0169

首先准备安装包 然后将安装包分发到集群的其他机器上去 然后因为运行hbase需要zookeeper支持,所以这里首先要去,启动zk 走到/opt/module/hadoop-3.1.3/bin/zk.sh 然后 zk.sh start 启动一下,可以看到启动了已经 然后zk.sh status 可以看zookeeper的状态 然后我们再去启动一下…

购买运动耳机应该考虑什么问题、运动达人必备的爆款运动耳机

喜欢运动的小伙伴都知道&#xff0c;运动和音乐是最配的&#xff0c;在运动中伴随着节奏感的音乐能够让自己更兴奋&#xff0c;锻炼的更加起劲儿。在运动耳机方面我也一直都有所研究&#xff0c;购买运动耳机最重要的就是要满足我们运动时候听音乐的需求&#xff0c;从佩戴舒适…

SAP Insurance Analyzer

SAP Insurance Analyzer 是一款用于保险公司财务和风险管理的软件。SAP Insurance analyzer 支持基于 IFRS 17 或 Solvency II 的保险合同估值和计算要求。SAP Insurance Analyzer 于 2013 年 5 月推出&#xff0c;为源数据和结果数据集成了一个预配置的保险数据模型。 源数据…

网上商城系统用户子功能模块

技术&#xff1a;Java、JSP等摘要&#xff1a;网上购物系统又称为网上商城、网络商城、网上商城、网上开店平台、网店管理系统、网店程序、网上购物系统、网上商城系统等。无论是开设个人网上购物商店还是企业网上商城商城&#xff0c;一套好用的网上购物系统都是必须的。网上购…

颠覆你的认知,这3款软件,每一款都非常实用

闲话少说&#xff0c;直上干货。 1、WizTree WizTree是一款最快的磁盘空间分析器&#xff0c;傻瓜式操作&#xff0c;功能还特别强大。它可快速的从硬盘中查找和释放被大量占用的空它将扫描的硬盘驱动器&#xff0c;直观显示哪些文件和文件夹使用的磁盘空间最多&#xff0c;及时…

day22_IO

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、缓冲流 三、字符流 四、缓冲字符流 五、匿名内部类 零、 复习昨日 File: 通过路径代表一个文件或目录 方法: 创建型,查找类,判断类,其他 IO …

EPICS synApps介绍

一、synApps是什么&#xff1f; 1&#xff09; 一个用于同步束线用户的EPICS模块集合。 2&#xff09; EPICS模块 alive, autosave, busy, calc, camac, caputRecorder, dac128V, delaygen, dxp, ip, ip330, ipUnidig, love, mca, measComp, modbus, motor, optics, quadEM,…

如何提升权限运行远程桌面客户端?

​ 我们远程支持他人的时候&#xff0c;有些情况下需要管理员权限才能执行操作&#xff0c;比如更新软件。那么如何提升权限运行远程桌面客户端&#xff1f; 如果您使用 Splashtop SOS 软件远程支持客户&#xff0c;可以使用连线提权功能提升至系统管理员权限&#xff0c;方便…

java 1(概要、变量与运算符)

java ——概要、变量与运算符 ✍作者&#xff1a;电子科大不知名程序员 &#x1f332;专栏&#xff1a;java学习指导 各位读者如果觉得博主写的不错&#xff0c;请诸位多多支持&#xff1b;如果有错误的地方&#xff0c;欢迎在评论区指出 目录java ——概要、变量与运算符命令行…

Linux | 压缩和解压文件详细

linux系统中针对不同的文件&#xff0c;有不同的压缩命令。本文对常见压缩和解压命令进行总结zip文件1.1.unzip解压单个文件unzip 命令可以查看和解压缩 zip 文件。该命令的基本格式如下&#xff1a;unzip filename.zip (文件后可添加压缩相关参数)-d 目录名 将压缩文件解压到指…

牛客网--加法模拟器---题号:NC22007

链接&#xff1a;https://ac.nowcoder.com/acm/problem/22007 来源&#xff1a;牛客网 题目描述 牛牛渐入佳境&#xff0c;他准备做一个加法模拟器来玩玩&#xff0c;输入两个数&#xff0c;分别打印这两个数的横式和竖式运算式子。 输入描述: 输入两个整数a, b 以空格隔开…

大家一起做测试的,凭什么你现在拿20k,我却还只有10k?...

最近我发现一个神奇的事情&#xff0c;我一个97年的朋友居然已经当上了测试项目组长&#xff0c;据我所知他去年还是在深圳的一家创业公司做苦逼的测试狗&#xff0c;短短8个月&#xff0c;到底发生了什么&#xff1f; 于是我立刻私聊他八卦一番。 原来他所在的公司最近正在裁…

softmax与simod如何选择?

前言&#xff1a;博主最近在复现代码的时候遇到一个问题&#xff0c;有的代码使用softmax有的使用sigmod&#xff0c;两者使用到底有什么区别呢&#xff1f; 一、softmax函数 1.1公式 &#xff08;一般只用于最后一层进行分类&#xff09;深度学习中使用Softmax进行分类。 1…

数据结构与算法——5.空间复杂度分析

这篇文章让我们来讨论一下空间复杂度 目录 1.概述 2.java中常见内存占用 2.1基本数据类型内存占用情况 2.2计算机访问内存的方式 2.3引用大小 2.4对象大小 2.5一般内存占用 2.6数组占用地址大小 3.算法的空间复杂度 4.小结 1.概述 计算机的软硬件都经历了一个比较漫…