文章目录
- 本文说明
- Debug 技巧
- Overfit一个简单的Batch
- 无法复现之前的结果
- 数据处理
- 平衡数据
- 数据增强(Data Augmentation)
- 图片增强
- 使用Embedding数据压缩数据
- 标准化(Normalization)
- 标签平滑(LabelSmoothing)
- Curriculum Learning
- 超参数
- 超参调节
- Batch Size
- Dropout
- Learning Rate
- Optimizer
- 模型构建
- 神经元数量
- 卷积层
- 循环层(Recurrent Layer)
- 使用预训练模型(Pretrained Model)
- 使用迁移学习(Transfer Learning)
- 线性层
- 模型训练
- 提高模型泛化性
- 内存不足(OOM问题)
- 参考资料
本文说明
本文主要是记录一下深度学习过程中的遇到的一些技巧的总结,这样在训练模型时如果效果不好就过来看看有哪些可以用上。不定期更新(等学到了新知识就会更新一下)
Debug 技巧
Overfit一个简单的Batch
在实际训练之前,先找找一个简单的Batch,尝试训练它,使model可以overfit这个batch。如果模型不能成功的把loss降到接近0,说明模型有问题。
常见的错误有如下:
- loss逐渐变大:看看是哪个符号写反了
- loss暴增:可能是学习率太大了,导致发散
- loss上下波动(oscillate):可以尝试减小学习率,并且检查数据中的是不是有错误的Label,或者数据增强(Data Augmentation)的方式有问题
- loss后面降不动了(plateau):可以尝试减小学习率,如果不行,有可能是网络太深了,尝试减小网络深度。
- 盲目使用Warmup等学习率调整技巧:请先保证正常学习率的情况下可以收敛,然后再增加学习率调整代码。例如:我在写一个Transformer的demo时,上来就是使用了Warmup,但发现怎么都不收敛。最后把Warmup去掉后,就可以正常收敛了。
无法复现之前的结果
如果无法复现之前的结果,可能有以下原因(ChatGPT给出的答案):
- 随机种子:如果没固定随机种子,每次的初始化参数、训练数据的shuffle都不一样。
- 超参数不一样:超参数不一样,会影响最终的结果
- 数据预处理:采用了不同的预处理方式会造成最后结果不一样,例如使用了不同的normalization或数据增强等。
- 软件版本:不同版本的库也会影响,例如不同版本的pytorch、numpy等
- 硬件不同:不同型号的CPU、GPU也会影响最终结果,即使设置了随机种子,可能也会存在差异。
数据处理
平衡数据
数据不平衡:有些类别的数据太多,这些类别称为majority class;而有些类别数据又太少,这些类别称为minority class
常用处理方法:
- Undersample: 直接删除一些 majority class 的数据
- Oversample:自己造一些minority class的数据,例如,①使用SMOTE(Synthetic Minority Over-sampling Technique),②数据增强(Data Augmentation)
- 使用
FocalLoss
损失函数,该函数可以调节交叉熵正负样本损失的权重,并且还能条件难易样本的损失权重。
数据增强(Data Augmentation)
图片增强
-
采用Random image cropping and patching (RICAP) 方法。思路为:随机裁剪多个图片的中一部分,然后把它们拼接为一个图片,同时混合这些图片的标签。
-
Cutout:随机的把图片的一个区域遮挡起来(像素值全部设为0)。用于模拟真实图片的物体可能被其他物体遮挡住了
-
random erasing:类似cutout,区别在于,cutout是把图片中随机抽中的矩形区域的像素值置为0,相当于裁剪掉,random erasing是用随机数或者数据集中像素的平均值替换原来的像素值。而且,cutout每次裁剪掉的区域大小是固定的,Random erasing替换掉的区域大小是随机的。
-
AutoAugment:使用AutoAugment自动选择适合特定数据集的数据增强策略。
使用Embedding数据压缩数据
输入数据尽量不要使用 one-hot 编码,而是使用embedding压缩特征维度。
标准化(Normalization)
对于数值类型的数据,最好将其缩放到 [-1,1] 的范围中,目的是为了加快收敛。
标签平滑(LabelSmoothing)
对于分类问题,可以采用标签平滑(Label-smoothing regularization, LSR) 的方式避免过拟合。它的具体思想是降低我们对于标签的信任,例如我们可以将损失的目标值从1稍微降到0.9,或者将从0稍微升到0.1。
对标签进行平滑操作可以减小模型的overconfidence的情况,进而减小overfitting。
例如:标签为[0, 0, 0, 1, 0],对标签平滑之后,标签变为 [0.05, 0.05, 0.05, 0.8, 0.05]。
Curriculum Learning
将样本从简到难的进行划分,先让网络学习简单的样本,然后再让网络学习难的样本,最后再把所有样本让网络整体学习一遍。类似人学习的思路,先简后难。
超参数
超参调节
最优先要考虑的超参数,或最重要的超参数是学习率(Learning Rate)
Batch Size
选择能被4整除的Batch Size, 或选择 2 n 2^n 2n 。
Dropout
- dropout最好控制在 20%-50%之间。因为太低影响不够,太高又容易让模型结果变差
- Use a larger network. You are likely to get better performance when dropout is used on a larger network, giving the model more of an opportunity to learn independent representations.(TODO,怎么理解这句话)
- Use dropout on incoming (visible) as well as hidden units. Application of dropout at each layer of the network has shown good results.(TODO,怎么理解这句话)
- 最后一层不要加dropout。如果这样的话,会使部分输出直接变为0.
Learning Rate
- Adam据说最好的学习率是 3e-4
- 根据训练情况调整学习率。例如:一开始学习率稍微大一点,当验证集效果变差时,减小学习率,每次可以减小4倍(×1/4)。cite1
- 尝试采用 Warmup 的方式来调节学习率,原理为:一开始模型不太稳定,所以学习率小一点,然后慢慢增大学习率,在一定epoches后,然后再慢慢降低学习率
- 学习率应该随着batch size进行变化。batch size越大,梯度中的噪声越小,所以当batch大的时候,学习率就可以大一点,而batch size小的时候,学习率就应该小一点。例如,batch size为256时选择的学习率是0.1,当我们把batch size变为一个较大的数b时,学习率应该变为 0.1 × b/256
- 采用AdaBound(可以试一下,作者说AdaBound会让你的训练过程像adam一样快,并且像SGD一样好)。
Optimizer
可以到 pytorch_optimizer项目 下找找有没有适合自己的Optimizer,这个项目扩展了Pytorch的optimizer,并提供了图例来表现这些Optimizer的情况。
模型构建
神经元数量
对于密集层(dense layer),输入神经元和输出神经元的数量最好都能被64整除。
卷积层
-
对于卷积层,输入层和输出层的channel最好都是4的倍数,或是 2 n 2^n 2n。
-
对于卷积层,输入和输出的数量最好可以被64整除
-
pad image inputs from 3 (RGB) to 4 channels(TODO,不懂)
-
不同尺度的特征融合(例如 YOLOv3-SPP)。例如,将上面卷积层的输出使用不同大小的maxpooling层进行下采样,并将各自的结果融合在一起送给接下来的网络。
-
低层特征与高层特征进行融合。即将前面卷积层的输出和后面卷积层的输出concat到一起,然后然后进行后续卷积
-
使用残差块(residual block)防止梯度消失。可参考ResNet
-
如果网络太大,可以尝试使用Depthwise Conv(DW)卷积 + Pointwise Conv(PW)卷积 来减小网络参数和计算量。可参考MobileV1
循环层(Recurrent Layer)
对于Recurrent Layer,Batch Size 和 Hidden Size 最好是4的整数,理想情况下,最好是64、128或256的倍数
对于Recurrent Layer,Batch Size最好大一些
使用预训练模型(Pretrained Model)
预训练模型:作为模型的“上游”模型,由牛逼的大厂预训练好,然后用这个模型的结果进行“下游”任务。例如:“BERT”是一个预训练模型,使用Bert可以将对Token(单词)进行Embedding,然后将其接到自己的“下游”模型上,进行特定任务的训练
计算机视觉:
- 使用ImageNet预训练的模型。
- 使用对比学习训练出的模型,例如Moco。(比使用ImageNet训练出的效果好)
NLP:
- 直接使用BERT
- 使用“利用对比学习微调后”的BERT模型。例如:使用SimCSE微调后的BERT模型。(比直接使用BERT效果好)
- 使用与自己任务比较契合的Bert变种,例如RoBERTa,MacBert,PERT等
使用迁移学习(Transfer Learning)
迁移学习:把别人训练好的“针对特定任务的”模型拿过来,然后把“任务”改成自己的特定任务,然后进行训练。
预训练模型和迁移学习的区别(个人理解):
预训练模型是不针对特定任务的,比如“Bert”,它针对NLP领域,但不针对具体任务,你可以拿Bert的输出再接模型进行“情感分析”、“词性标注”什么的都可以。;
但是迁移学习用的是特定任务的模型,比如张三训练了一个模型可以进行情感分析,然后你拿它这个训练好的模型,把最后几层改了,然后用于词性标注,这种是迁移学习。
迁移学习的时候,预训练模型的参数要么使用小的学习率调整,要么不调整。如果预训练模型和下游的预测模型使用同一个学习率的话,看似可以很快地拟合训练数据,但是泛化性会差很多。
线性层
- 据说把Softmax换成log_softmax会更好,详情可参考这个文章
- 多分类问题,如果类别过多,可以不使用Softmax,而是对每一个类别都使用Sigmoid。(YoloV3就是这么干的)
模型训练
- 定期保存:训练过程中,最好定期保存模型,防止训练过程中系统崩溃等问题导致模型丢失,哭都没地方哭,只能从头开始。如果定期保存,就可以从上一个保存点(Checkpoint)开始。
- 提前停止(EarlyStopping) :为了防止发生过拟合,可以提前终止训练。常用的终止策略是:使用验证数据集来验证模型性能,如果在 n n n 个step内,模型性能没有提升,就可以终止了。
- 知识蒸馏(Knowledge Distillation) :如果要训练一个较小的模型(例如要部署在移动设备上),使用一个较大且较高准确率的预训练模型的输出作为标签比使用真实标签效果更好。
- Model EMA(Model Exponentially weighted averages):模型的参数更新不仅会考虑本次的,也会考虑之前的,可以提高模型的性能。参考链接
提高模型泛化性
- 迁移学习的时候,预训练模型的参数要么使用小的学习率调整,要么不调整。如果预训练模型和下游的预测模型使用同一个学习率的话,看似可以很快地拟合训练数据,但是泛化性会差很多。
- 采用不同的网络参数初始化方式,例如:Xavier Initialization、He Initialization、Orthogonal Initialization等。具体哪一种好需要实验。
- 使用Dropout
- 优化器指定Weight Decay,通常用0.001。一般也就0.01,0.001,0.0001这三个选择。bias参数不要使用weight_decay。
内存不足(OOM问题)
- 可以使用**梯度累积(Accumulate Gradients)**来模拟大batch size。原理大概是:使用小的batch size,但在一个step后不更新参数,而是把梯度累加起来,多累计几个batch size的梯度后再一起更新。
- 使用 自动混合精度(Automatic mixed precision,AMP) 降低内存,加速训练。原理大概是:大部分深度学习框架默认使用32位的浮点数。而AMP混合了单精度(FP32)和半精度(FP16)。缺点是:训练会变得稍微不稳定,可能没之前好了。
参考资料
Tips and tricks for Neural Networks: https://towardsdatascience.com/tips-and-tricks-for-neural-networks-63876e3aad1a
Tips For Using Dropout: https://machinelearningmastery.com/dropout-regularization-deep-learning-models-keras/
深度神经网络模型训练中的 tricks(原理与代码汇总):https://mp.weixin.qq.com/s/qw1mDd1Nt1kfU0Eh_0dPGQ
Pytorch Lightning effective training techniques: https://lightning.ai/docs/pytorch/stable/advanced/training_tricks.html#effective-training-techniques