深度学习训练 tricks(持续更新)

妈妈,我的炼丹炉子炸啦(不是)
妈妈,我的深度学习模型训练好了!

本文持续更新,如果有什么你知道的深度学习模型训练技巧,可以在评论区提出,我会加进来的。

weight decay

weight decay 是什么

weight_decay 作为一个超参数,传入 optimizer 的构造器中,所以这个参数显然是与梯度更新有关的。

梯度更新公式:
θ t ← θ t − 1 − λ ⋅ g t \theta_t \leftarrow \theta_{t-1} - \lambda\cdot g_t θtθt1λgt
其中 θ \theta θ 是参数, λ \lambda λ 是学习率, g t g_t gt 是 t 时刻的梯度

在梯度更新公式中加入一个衰减参数 β \beta β
θ t ← ( 1 − β ) ⋅ θ t − 1 − λ ⋅ g t \theta_t \leftarrow (1-\beta)\cdot\theta_{t-1} - \lambda\cdot g_t θt(1β)θt1λgt
这个 β \beta β 就是 weight decay 的参数,一般取值比较小,比如0.0005

在深度学习模型中,一般将衰减系数设置为 0.00010.001 之 间的值,这是一个比较常用的范围;
当你不确定模型复杂度和数据集大小的时候,最保守就是从1e-4周围开始尝试;
来源:https://zhuanlan.zhihu.com/p/607909453

weight decay 与 L2 正则化

来源:权重衰减(weight decay)与学习率衰减(learning rate decay)

L2正则化就是在代价函数后面再加上一个正则化项

代价函数,损失函数,目标函数区别

  • 损失函数(Loss Function )是定义在单个样本上的,算的是一个样本的误差。
  • 代价函数(Cost Function )是定义在整个训练集上的,是所有样本误差的平均,也就是损失函数的平均。
  • 目标函数(Object Function)定义为:最终需要优化的函数。等于经验风险+结构风险(也就是代价函数 + 正则化项)。(经验风险最小指,在训练集上的预测最准确;结构风险减低是指,模型简单,过于复杂的模型容易过拟合)

C = C 0 + α 2 n ∑ w w 2 C=C_0+\frac{\alpha}{2n}\sum_ww^2 C=C0+2nαww2
C 0 C_0 C0 是原始代价函数,后面那一项是 L2 正则化项:所有参数 w w w 的平方和,除训练集的样本大小 n n n α \alpha α 是正则项系数,分母中的 2 用于求导系数配平。

为什么说 weight decay 在某些情况下就是 L2 正则化?

对 L2 正则化后的代价函数求导:
∂ C ∂ w = ∂ C 0 ∂ w + α n w \frac{\partial C}{\partial w}=\frac{\partial C_0}{\partial w} + \frac{\alpha}{n}w wC=wC0+nαw
w ← w − λ ∂ C 0 ∂ w − λ α n w w \leftarrow w - \lambda \frac{\partial C_0}{\partial w} - \lambda \frac{\alpha}{n}w wwλwC0λnαw
w ← ( 1 − λ α n ) w − λ ∂ C 0 ∂ w w \leftarrow \left(1-\lambda \frac{\alpha}{n}\right)w - \lambda \frac{\partial C_0}{\partial w} w(1λnα)wλwC0

weight decay:
β = λ α n \beta = \lambda \frac{\alpha}{n} β=λnα

作用

直观上,weight decay 在权重参数上加了一个缩小因子,使得权重整体保持在一个较小在的值,顺便可以避免梯度爆炸。

weight decay 和 L2 正则化项有让 w w w 变小的效果,但是为什么 w w w 变小可以防止过拟合呢?

来源:权重衰减(weight decay)与学习率衰减(learning rate decay)

(1)从模型的复杂度上解释:更小的权值w,从某种意义上说,表示网络的复杂度更低,对数据的拟合更好(这个法则也叫做奥卡姆剃刀),而在实际应用中,也验证了这一点,L2正则化的效果往往好于未经正则化的效果。(2)从数学方面的解释:过拟合的时候,拟合函数的系数往往非常大,为什么?过拟合,就是拟合函数需要顾忌每一个点,最终形成的拟合函数波动很大。在某些很小的区间里,函数值的变化很剧烈。这就意味着函数在某些小区间里的导数值(绝对值)非常大,由于自变量值可大可小,所以只有系数足够大,才能保证导数值很大。而正则化是通过约束参数的范数使其不要太大,所以可以在一定程度上减少过拟合情况。

Warmup

来源:神经网络中 warmup 策略为什么有效;有什么理论解释么?

使用 SGD 训练神经网络时,在初始使用较大学习率而后期切换为较小学习率是一种广为使用的做法,在实践中效果好且最近也有若干文章尝试对其进行了理论解释。

而 warmup 策略则与上述 scheme 有些矛盾。warmup 需要在训练最初使用较小的学习率来启动,并很快切换到大学习率而后进行常见的 decay。那么最开始的这一步 warmup 为什么有效呢?

  • 有助于减缓模型在初始阶段对mini-batch的提前过拟合现象,保持分布的平稳
  • 有助于保持模型深层的稳定性

刚开始模型对数据的“分布”理解为零,或者是说“均匀分布”(当然这取决于你的初始化);在第一轮训练的时候,每个数据点对模型来说都是新的,模型会很快地进行数据分布修正,如果这时候学习率就很大,极有可能导致开始的时候就对该数据“过拟合”,后面要通过多轮训练才能拉回来,浪费时间。当训练了一段时间(比如两轮、三轮)后,模型已经对每个数据点看过几遍了,或者说对当前的batch而言有了一些正确的先验,较大的学习率就不那么容易会使模型学偏,所以可以适当调大学习率。这个过程就可以看做是warmup。那么为什么之后还要decay呢?当模型训到一定阶段后(比如十个epoch),模型的分布就已经比较固定了,或者说能学到的新东西就比较少了。如果还沿用较大的学习率,就会破坏这种稳定性,用我们通常的话说,就是已经接近loss的local optimal了,为了靠近这个point,我们就要慢慢来。
如果一开始就用0.1,虽然最终会收敛,但之后acc还是不会提高(使用了pateaus schedule);如果用了warmup,在收敛后还能有所提高。也就是说,用warmup和不用warmup达到的收敛点,对之后模型能够达到的suboptimal有影响。这说明什么?这说明不用warmup收敛到的点比用warmup收敛到的点更差。这可以从侧面说明,一开始学偏了的权重后面拉都拉不回来……

Drop path

DropPath/drop_path 是一种正则化手段,其效果是将深度学习模型中的多分支结构随机”删除“

drop path 计算方法

pytorch 源码,我对其进行注释:

def drop_path(x, drop_prob: float = 0., training: bool = False, scale_by_keep: bool = True):
    """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).

    This is the same as the DropConnect impl I created for EfficientNet, etc networks, however,
    the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper...
    See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted for
    changing the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use
    'survival rate' as the argument.

    """
    # drop_prob 是随机失活的比例,如果=0,则所有都不失活;如果=1,则所有都失活
    if drop_prob == 0. or not training:
        return x
    # keep_prob 是保留的比例,假设 drop_prob=0.1 则 keep_prob=0.9
    # 含义是,每个分支都有 0.9 的概率被保留,有 0.1 的概率被失活
    keep_prob = 1 - drop_prob
    shape = (x.shape[0],) + (1,) * (x.ndim - 1)  # work with diff dim tensors, not just 2D ConvNets
    # 失活的方法是,生成一个随机 tensor,这个 tensor 的每一个值都是 0 或者 1,有 keep_prob 的概率为 1;
    # 等下这个随机 tensor 和 x 相乘,于是对应的位置 x 变成 0,实现失活的效果
    random_tensor = x.new_empty(shape).bernoulli_(keep_prob)
    if keep_prob > 0.0 and scale_by_keep:
        random_tensor.div_(keep_prob)
    return x * random_tensor

如何将 drop path 加入你的模型?

以 ViT 的 block 代码为例,ViT 的模型结构图:
在这里插入图片描述ViT block 的 pytorch 代码:

class Block(nn.Layer):
    def __init__(self,
                 dim,
                 num_heads,
                 mlp_ratio=4.,
                 qkv_bias=False,
                 qk_scale=None,
                 drop=0.,
                 attn_drop=0.,
                 drop_path=0.,
                 act_layer=nn.GELU,
                 norm_layer='nn.LayerNorm',
                 epsilon=1e-5):
        super().__init__()
        self.norm1 = eval(norm_layer)(dim, epsilon=epsilon)
        # Multi-head Self-attention
        self.attn = Attention(
            dim,
            num_heads=num_heads,
            qkv_bias=qkv_bias,
            qk_scale=qk_scale,
            attn_drop=attn_drop,
            proj_drop=drop)
        # DropPath
        self.drop_path = DropPath(drop_path) if drop_path > 0. else Identity()
        self.norm2 = eval(norm_layer)(dim, epsilon=epsilon)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim,
                       hidden_features=mlp_hidden_dim,
                       act_layer=act_layer,
                       drop=drop)

    def forward(self, x):
        # Multi-head Self-attention, Add, LayerNorm
        x = x + self.drop_path(self.attn(self.norm1(x)))
        # Feed Forward, Add, LayerNorm
        x = x + self.drop_path(self.mlp(self.norm2(x)))
        return x

好好观察,ViT 中使用 drop path 的时候,返回的是:

return x + self.drop_path(self.mlp(self.norm2(x)))
# 这种写法是错误的
# return self.drop_path(self.mlp(self.norm2(x)))

drop path 的作用

DropPath 类似于Dropout,不同的是 Drop将深度学习模型中的多分支结构随机 “失效”,而Dropout 是对神经元随机 “失效”。

注:很多文章里说,dropout / drop path 是随机“删除”一些神经元 / 分支结构。这种说法很容易误导,让人疑惑这个“删除”是怎么个删除法?实际上模型结构并不会改变,只是让这个神经元的输出结果置零

在 dropout 中,比如原始网络的计算结构是:
y = a 1 ⋅ x 1 + a 2 ⋅ x 2 + a 3 ⋅ x 3 + a 4 ⋅ x 4 y=a_1\cdot x_1 + a_2\cdot x_2 + a_3\cdot x_3 + a_4\cdot x_4 y=a1x1+a2x2+a3x3+a4x4
令 dropout = 0.25,即每个神经元有 0.25 的概率,它的输出结果被置零。假设有个被失活的幸运儿是 a 1 a_1 a1,那么:
训练
y = 0 + a 2 ⋅ x 2 + a 3 ⋅ x 3 + a 4 ⋅ x 4 y=0 + a_2\cdot x_2 + a_3\cdot x_3 + a_4\cdot x_4 y=0+a2x2+a3x3+a4x4
测试
y = 0.75 ∗ ( a 1 ⋅ x 1 + a 2 ⋅ x 2 + a 3 ⋅ x 3 + a 4 ⋅ x 4 ) y=0.75 * (a_1\cdot x_1 + a_2\cdot x_2 + a_3\cdot x_3 + a_4\cdot x_4) y=0.75(a1x1+a2x2+a3x3+a4x4)

待修改

(Drop Path 让部分 multi-head 失活??(待做实验验证)

随机丢失不会趋于一致性(平凡化?或者说趋同进化?)

指数移动平均(EMA)

指数移动平均(Exponential Moving Average)也叫权重移动平均(Weighted Moving Average),是一种给予近期数据更高权重的平均方法。

计算方法白话版本解说:

一个模型在训练的时候,维护两套参数。一套参数叫 θ \theta θ ,用梯度下降正常更新:
θ t = θ t − 1 − g t − 1 \theta_t = \theta_{t-1} - g_{t-1} θt=θt1gt1
这里 g g g 是梯度

另一套参数叫影子参数,在这里记为 ν \nu ν,影子参数不用梯度更新,而是用 θ \theta θ 更新:
ν t = α ⋅ ν t − 1 + ( 1 − α ) ⋅ θ t \nu_t=\alpha\cdot \nu_{t-1} + (1-\alpha)\cdot\theta_t νt=ανt1+(1α)θt

然后导出训练结果的时候,不用 θ \theta θ 而是 ν \nu ν

原理的囫囵吞枣讲解,省略公式推导过程,直接看公式推导结果:
θ t = θ 1 − ∑ i = 1 t − 1 g i \theta_t=\theta_1-\sum^{t-1}_{i=1}g_i θt=θ1i=1t1gi
ν t = θ 1 − ∑ i = 1 t − 1 ( 1 − α t − 1 ) g i \nu_t=\theta_1-\sum^{t-1}_{i=1}\left(1-\alpha^{t-1}\right)g_i νt=θ1i=1t1(1αt1)gi
对比上下两个公式,很容易发现,差别就在于:每次通过梯度更新 ν \nu ν 这套参数的时候,加了个系数 ( 1 − α t − 1 ) \left(1-\alpha^{t-1}\right) (1αt1),使得:

  • t t t 越大
  • α t − 1 \alpha^{t-1} αt1 越小( 0 < α < 1 0<\alpha<1 0<α<1
  • ( 1 − α t − 1 ) \left(1-\alpha^{t-1}\right) (1αt1) 这个系数越大

也就是说,越是刚刚算出来的梯度( t t t 越小),对最终导出的结果影响越大。

基本的假设是,模型权重在最后的n步内,会在实际的最优点处抖动,所以我们取最后n步的平均,能使得模型更加的鲁棒。

想知道更详细的公式推导,请看:【炼丹技巧】指数移动平均(EMA)的原理及PyTorch实现

SGD 的 momentum

SGD是随机梯度下降(stochastic gradient descent)的首字母。设模型参数为 θ \theta θ,梯度为 g g g
θ t = θ t − 1 − g t ⋅ l r \theta_t = \theta_{t-1} - g_{t}\cdot lr θt=θt1gtlr
momentum 动量值,帮助参数更新离开盆地:
g t ′ = β ⋅ g t − 1 + ( 1 − β ) ⋅ g t g_t'= \beta\cdot g_{t-1} + (1-\beta)\cdot g_t gt=βgt1+(1β)gt
θ t = θ t − 1 − g t ′ ⋅ l r \theta_t =\theta_{t-1} - g_t'\cdot lr θt=θt1gtlr

AdamW 的 eps 和 betas

来源:优化器:Adam与AdamW
Adam: Adam使用了动量来加速梯度下降,它引入了两个动量参数 β 1 \beta_1 β1(用于一阶矩估计)和 b e t a 2 beta_2 beta2 (用于二阶矩估计)。这些动量参数决定了过去梯度的影响程度。

小标题中的 eps (float, 可选) – 为了增加数值计算的稳定性而加到分母里的项(默认:1e-8)即代码中的 epsilon

# Adam 更新规则
m = beta1*m + (1-beta1)*grad
v = beta2*v + (1-beta2)*(grad**2)
theta = theta - learning_rate * m / (sqrt(v) + epsilon)

AdamW: Adam with Weight Decay Fix

# AdamW 更新规则
m = beta1*m + (1-beta1)*(grad + lamb*theta)
v = beta2*v + (1-beta2)*(grad**2) 
theta = theta - learning_rate * m / (sqrt(v) + epsilon)

二者的区别在于,AdamW 中加入了 lamb,即 λ \lambda λ,weight decay 的参数

Gradient Accumulation 梯度累加

这个技巧是和 batch_size 挂钩的。如果你的显存比较小,但是又想 batch_size 不要太小,就可以试试梯度累加,顾名思义,就是将多次计算得到的梯度值进行累加,然后一次性进行参数更新。

来源:聊聊梯度累加(Gradient Accumulation)
如下图所示,假设我们有batch size = 256的global-batch,在单卡训练显存不足时,将其分为多个小的mini-batch(如图分为大小为64的4个mini-batch),每个step送入1个mini-batch获得梯度,将多次获得的梯度进行累加后再更新参数,以次达到模拟单次使用global-batch训练的目的。

简单来说:时间换空间。加长训练时间,来换取大batch在小设备上可训练。

在这里插入图片描述

梯度消失梯度爆炸-Gradient Clip

如题,这个技巧就是用来缓解梯度爆炸和梯度消失的情况的。

梯度消失容易出现在 sigmoid 作为激活函数的时候。因为 sigmoid 函数会将[+∞,-∞]的输入压缩到[0,1],导致当输入更新时,输出的更新会很小。在这种情况下,就会随着隐藏层数的增加,反向传递时,数值更新将会越来越小。

梯度爆炸容易出现在 relu 作为激活函数的时候。

Gradient Clip 设置一个梯度减切的阈值,如果在更新梯度的时候,梯度超过这个阈值,则会将其限制在这个范围之内,防止梯度爆炸。设置下界阈值,也可以用于梯度消失。

当然这个阈值是一个超参数,根据经验来定义的。

当然这种方法只能在一定程度上缓解。

补充:检查梯度的方法

当然得先看到,才知道有没有发生梯度爆炸或者梯度消失

代码来源:如何打印Pytorch在网络中的梯度值
摘取最关键的部分方便查阅:

# 先做前向传播
outputs = net(inputs)
# 计算损失
loss = criterion(outputs, labels.long())
# 反向传播计算梯度
loss.backward()
# 优化器更新梯度
optimizer.step()

# 然后就可以打印查看每层的梯度值
print("conv21.bias = ",net.conv21.bias)
print("conv21.bias.grad = ",net.conv21.bias.grad)
print("conv21.weight = ",net.conv21.weight)
print("conv21.weight.grad = ",net.conv21.weight.grad)
  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深度学习tricks是指在深度学习模型训练过程中使用的一些技巧和策略,旨在提高模型的性能和训练效果。以下是一些常用的深度学习tricks: 1. 数据增强(Data Augmentation):通过对原始数据进行随机变换和扩充,生成更多的训练样本,以增加模型的泛化能力。 2. 批归一化(Batch Normalization):在每个小批量数据上进行归一化操作,有助于加速模型的收敛速度,提高模型的稳定性和泛化能力。 3. 学习率调整(Learning Rate Schedule):根据训练的进程动态地调整学习率,例如使用学习率衰减或者学习率预热等策略,以提高模型的收敛性能。 4. 正则化(Regularization):通过添加正则化项,如L1正则化或L2正则化,限制模型的复杂度,防止过拟合。 5. 提前停止(Early Stopping):在训练过程中监控验证集上的性能指标,当性能不再提升时停止训练,以避免过拟合。 6. 参数初始化(Parameter Initialization):合适的参数初始化可以帮助模型更快地收敛和更好地泛化,常用的初始化方法包括Xavier初始化和He初始化等。 7. 梯度裁剪(Gradient Clipping):限制梯度的范围,防止梯度爆炸或梯度消失问题,提高模型的稳定性。 8. 集成学习(Ensemble Learning):通过结合多个模型的预测结果,可以提高模型的泛化能力和鲁棒性。 9. 迁移学习(Transfer Learning):利用已经训练好的模型在新任务上进行微调,可以加快模型的训练速度和提高模型的性能。 10. 深度网络结构设计:合理设计网络结构,包括层数、宽度、卷积核大小等,可以提高模型的表达能力和学习能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值