【tips-AI】提高模型训练的Pytorch技巧

1. One Cycle学习率策略

 学习率lr很大程度上影响收敛速度和泛化性能。收敛速度很好理解,对泛化性能的影响却不是很直观。
 泛化性指模型经过训练后,应用到新数据并做出准确预测的能力。lr影响收敛,即模型训练不恰当(过拟合/欠拟合),准确率P和召回率R有所下降,影响模型的输出,即模型泛化性能差。
 话回lr,相比于固定学习率,周期性学习率策略被证明是更有效的训练方式,如fastai中的one cycle学习率策略。从最小lr变成最大lr,再回到最小lr,最大值通常为最小值的10倍,且整个循环迭代步长应略小于总的epoch,在训练的最后部分应该允许lr下降超过最小值的几个数量级。此做法有个比较好的解释:有规律的改变学习率可以更快的越过鞍点。
The 1cycle policy
  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中已集成该功能,目前被证明对基于TransformerResNets等架构非常有用

9. BN层卷基层中的bias

 关于BatchNormalization应该知道的是,如果在二维卷积层后紧跟BN层,需要将conv2dbias设置为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()避免这种情况。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2023年12月28日
下一篇 2023年12月28日

相关推荐