1. One Cycle学习率策略
学习率lr
很大程度上影响收敛速度和泛化性能。收敛速度很好理解,对泛化性能的影响却不是很直观。
泛化性指模型经过训练后,应用到新数据并做出准确预测的能力。lr
影响收敛,即模型训练不恰当(过拟合/欠拟合),准确率P
和召回率R
有所下降,影响模型的输出,即模型泛化性能差。
话回lr
,相比于固定学习率,周期性学习率策略被证明是更有效的训练方式,如fastai
中的one cycle
学习率策略。从最小lr
变成最大lr
,再回到最小lr
,最大值通常为最小值的10倍,且整个循环迭代步长应略小于总的epoch,在训练的最后部分应该允许lr
下降超过最小值的几个数量级。此做法有个比较好的解释:有规律的改变学习率可以更快的越过鞍点。
Pytorch中已经有了相应的实现CLASS torch.optim.lr_schduler.OneCycleLR(...)
。此外,优化器也被提及是提高优化效率的有效方式。使用具有权重衰减而不是l2
正则化的AdamW
在训练时间和出现错误的情况都优于Adam
,同样的Pytorch中已集成该功能。
2. Batch size
通常batch-szie
根据数据量设置,使用GPU允许的最大值以加速训练,但这么做可能导致训练效果比小的batch-size
更差。值得一提的是,如果修改了batch-size
,还必须也同步调整lr
等超参。一般batch-size
加倍时,lr
也需要加倍。可以参考OpenAI一篇关于不同大小batch-size
所需收敛步数的论文—《An Empirical Model of Large-Batch Training》
3. num_workers & pin_memory
使用DataLoader时num_workers
默认为0,该参数用来设定DataLoader用于进行数据加载的子进程个数。设置为0则每轮迭代不会有子进程将数据自主加载至内存,找不到batch数据再加载,所以速度会很慢;当不为0时,DtatLoader将一次性创建多个子进程,并分配属于各自的batch。每个子进程负责将batch加载进内存,这也就意味着找寻batch的速度很快,因为后面的迭代数据在前几轮早就已经加载完毕。但是num_workers
设置过大时,内存开销也随之变大,加重CPU的负担。虽然num_workers
与CPU相关,但是一个常用的经验是,设置为可用CPU数量的4倍,过大过小都将导致速度变慢。
此外,如果硬件性能足够好的话,可以将pin_memory
设置为True
。这样将数据加载至GPU上的速度更快
4. 自动混合精度训练
即常说的Automatic Mixed Precision — AMP
,在Pytorch1.6中集成了该功能。
import torch
# Create once at the beginning of training
scaler = torch.cuda.amp.GradScaler()
for data, label in data_iter:
optimizer.zero_grade()
# Casts operations to mixed precision
with torch.cuda.amp.autocast():
loss = model(data)
# Scales the loss, and calls backward() to create scaled gradients
scaler.scale(loss).backward()
# Unscales gradients and calls or skips optimizer.stop()
scaler.step(optimizer)
# Updates the scale for next iteration
scaler.update()
主要思想是在某些操作中使用半精度FP16,而不是一味地使用单精度FP32。AMP
会自动决定应该使用哪种格式执行什么操作,在不损失精度的情况下达到更快的训练速度与占用更小内存的目的。
5. torch.backends.cudnn.benchmark
若模型结构固定且保持输入大小不变,可以将cudnn.benchmark
设置为True
,可以加速训练。因为这会使cudnn中自动调整参数的模块,对不同计算卷积的方式进行benchmark测试,自动选择最优的卷积方法。需要注意的是启动算法前期比较慢但跑起来之后很快,且若输入大小改变,每改变一次则需要重新配置计算,会使得效率变低。
6. torch.nn.parallel.DistributedParallel
Pytorch中虽然DataParallel能够以最低的编码实现单机多卡并行,且仅需要修改一行代码,但通常无法提供最佳性能,因为在每一次前向传播过程中都会复制模型。并且其单进程多线程并行性自然会受到Python中的GIL
(全局解释器)的影响。Pytorch推荐使用DistributedDataParallel—DDP
,使用多进程并行,因此模型副本之间没有GIL
争用。
此外模型在DDP
构建时进行broadcast,而不是在每次传播时进行,这有助于加快模型训练。
7. 梯度累加
这是增加batch的另一种方式。核心思想是在调用optimizer.step
之前,在多个backward中累积梯度。训练时的实现过程如代码所示:
model.zero_grad()
for i, (inputs, labels) in enumerate(training_set):
predictions = models(inputs)
loss = loss_function(preditions, labels)
loss = loss / accumulation_steps
loss.backward()
for (i+1)%accumulation_steps == 0:
optimizer.step()
model.zero_grad()
首先重置梯度,在for循环遍历数据时喂入模型进行前向传播,紧接着计算loss,除以累积步长求取均值,再反响传播,每当到达累计步长再进行优化器更新与重置梯度。这种方法主要是规避GPU内存限制而设计,fastai论坛中提到这样操作可以加速训练。
注意,如果处于验证阶段,千万千万设置torch.no_grad()
。
8. 梯度裁减
gradient = min(gradient, threshold)
torch.nn.utils.clip_grad_norm_
最初用于避免RNN中的梯度爆炸,粗率的可以如上用threshold
截断以达到加速收敛的目的。在Pytorch中已集成该功能,目前被证明对基于Transformer
和ResNets
等架构非常有用。
9. BN层卷基层中的bias
关于BatchNormalization
应该知道的是,如果在二维卷积层后紧跟BN
层,需要将conv2d
中bias
设置为False
。另外,BN层中需要训练的参数有两个γ和
在早期神经网络训练时,只是对输入数据进行了归一化处理却忽视了中间层。虽然对输入数据进行了归一化,但是数据经过矩阵乘法以及非线性运算之后,数据分布很可能被改变。随着网络不断加深,数据改变越来越大。如果能在网络的中间层也进行归一化处理,对网络的训练起到改进作用,这种在神经网络中间层也进行归一化的处理就是批归一化BatchNormalization
。因此BN层作用是将数据调整至标准分布,添加bias并无作用。
此外,单纯的归一化会导致网络表达能力下降,所以,增加两个学习来的参数—γ
和β
,用来对变换后的激活反变换,使得网络表达能力增强
10. 陋习改正
-
torch.cpu() <---> torch.cuda()
将tensor数据频繁地在CPU和GPU之间来回转换是非常昂贵的。 -
torch.tensor(); torch.as_tensor(); torch.from_numpy()
如果经常将numpy数据转化成tensor,有时候会用到torch.tensor()
,这样做可以达到目的,但是该操作会执行复制数据的步骤,效率低下。可以使用torch.as_tensor()
或torch.from_numpy()
避免这种情况。