本文介绍了使用PyTorch训练深度模型的17种最省力和最有效的方法。本文中提到的方法假设您是在GPU环境中训练模型。详情如下。01.考虑另一种学习率调度学习率调度的选择对模型的收敛速度和泛化能力影响很大。莱斯利N.史密斯等人。在论文《Cyclical Learning Rates for Training Neural Networks》、《Super-Convergence: Very Fast Training of Neural Networks Using Large Learning Rates 》中提出了循环(Cyclical)学习率和1Cycle学习率调度。后来由fast.ai的JeremyHoward和SylvainGugger推广。下图是1Cycle学习率调度的图解:Sylvain写道:1Cycle由两步等长组成,一步是从较低的学习率到较高的学习率,另一步是回到最低水平。最大值来自学习率查找器选取的值,较小的值可以低十倍。那么这个周期的长度应该略小于epoch的总数,并且在训练的最后阶段,我们应该让学习率比最小值小几个数量级。在最好的情况下,与传统的学习率计划相比,这个计划实现了巨大的加速(Smith称之为超级收敛)。例如,使用1Cycle策略在ImageNet数据集上训练ResNet-56,训练迭代次数减少到原来的1/10,但模型性能仍然与原论文中的水平相当。在常见的架构和优化器上,这个时间表似乎运作良好。Pytorch已经实现了这两个方法:“torch.optim.lr_scheduler.CyclicLR”和“torch.optim.lr_scheduler.OneCycleLR”。参考文档:https://pytorch.org/docs/stable/optim.html02。在DataLoader中使用多个worker和页锁定内存使用torch.utils.data.DataLoader时,设置num_workers>0而不是默认值0,并设置pin_memory=True而不是默认值False。参考文档:https://pytorch.org/docs/stable/data.htmlNVIDIA高级CUDA深度学习算法软件工程师SzymonMicacz使用四个worker和page-lockedmemory(pinnedmemory)在一个epoch中实现了它).2倍加速。人们选择工作人员数量的经验法则是将其设置为可用GPU数量的四倍,任何多于或少于此数量都会减慢训练速度。请注意,增加num_workers会增加CPU内存消耗。03.将batch调到最大将batch调到最大是一个有争议的观点。一般来说,如果您在GPU内存允许的范围内最大化批量大小,您的训练会更??快。但是,您还必须调整其他超参数,例如学习率。一个好的经验法则是,当批量大小加倍时,学习率也会加倍。OpenAI论文《An Empirical Model of Large-Batch Training》很好地展示了不同批量大小需要多少步才能收敛。在《How to get 4x speedup and better generalization using the right batch size》中,作者DanielHuynh进行了一些不同批量大小的实验(也使用上面讨论的1Cycle策略)。最终,他将批量大小从64增加到512,实现了4倍的加速。然而,使用大批量的缺点是,这可能导致解决方案的泛化效果比使用小批量更差。04.使用自动混合精度(AMP)PyTorch1.6版本包括PyTorch自动混合精度训练的本机实现。这里的要点是,某些操作在半精度(FP16)下比在单精度(FP32)下运行得更快,而不会损失精度。AMP会自动决定应以何种精度执行哪些操作。这既可以加快训练速度,又可以减少内存使用。在最好的情况下,AMP是这样使用的:#Scalestheloss,andcallsbackward()#tocreatescaledgradientsscaler.scale(loss).backward()#Unscalesgradientsandcalls#orskipsoptimizer.step()scaler.step(optimizer)#Updatethescaleforexiterationscaler.update()05。考虑使用另一个优化器AdamW是的,由fast.ai推广的具有权重衰减(而不是L2正则化)的Adam,在PyTorch中实现为torch.optim.AdamW。AdamW似乎在错误率和训练时间方面始终优于Adam。Adam和AdamW都适用于上述1Cycle策略。还有一些非局部优化器现在受到了很多关注,最著名的是LARS和LAMB。NVIDA的APEX实现了一些常见优化器的融合版本,例如Adam。与PyTorch中的Adam实现相比,该实现避免了多次往返GPU内存,速度提高了5%。06.cudNNbenchmark如果您的模型架构保持不变并且输入大小保持不变,请设置torch.backends.cudnn.benchmark=True。07.小心CPU和GPU之间频繁的数据传输当频繁使用tensor.cpu()将张量从GPU传输到CPU时(或者使用tensor.cuda()将张量从CPU传输到GPU),代价是非常昂贵的。item()和.numpy()也是如此。您可以改用.detach()。如果您创建一个新的张量,您可以使用关键字参数device=torch.device(cuda:0)将其分配给GPU。如果需要传输数据,可以使用.to(non_blocking=True),只要传输后没有同步点即可。08.使用gradient/activationcheckpointingCheckpointing的工作原理是用计算换取内存,不存储整个计算图的所有中间activations用于backwardpass,而是重新计算这些activations。我们可以将它应用到模型的任何部分。具体来说,在前向传递中,函数使用torch.no_grad()运行并且不存储中间激活。相反,前向传递将保存输入元组和函数参数。在向后传递中,检索输入和函数,并在函数上再次评估前向传递。然后跟踪中间激活并使用这些激活值计算梯度。因此,虽然对于给定的批处理大小,这可能会略微增加运行时间,但它会显着减少内存占用。这反过来将允许进一??步增加使用的批量大小,从而提高GPU的利用率。尽管检查点是以torch.utils.checkpoint方式实现的,但仍需要一些思考和努力才能使其正确。PriyaGoyal写了一篇很好的教程,涵盖了检查点的关键方面。PriyaGoyal教程地址:https://github.com/prigoyal/pytorch_memonger/blob/master/tutorial/Checkpointing_for_PyTorch_models.ipynb09。使用梯度累积增加批量大小的另一种方法是在.backward()传递中的多个累积梯度之前调用optimizer.step()。HuggingFace的ThomasWolf文章《Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups》介绍了如何使用梯度累积。梯度累加可以这样实现:model.zero_grad()#Resetgradientstensorsfori,(inputs,labels)inenumerate(training_set):predictions=model(inputs)#Forwardpassloss=loss_function(predictions,labels)#Computelossfunctionlosloss=loss/accumulation_steps#Normalizeourloss(ifaveraged)loss.backward()#Backwardpassif(i+1)%accumulation_steps==0:#Waitforseveralbackwardstepsoptimizer.step()#Nowwecandoanoptimizerstepmodel.zero_grad()#Resetgradientstensorsif(i+1)%evaluation_steps==0:#Evaluatethemodelwhenwe...evaluate_model()#...havenogradientsaccumulate这个方法主要是为了避免GPU内存的限制而开发的。10.使用分布式数据并行的多GPU训练加速分布式训练的方法可能有很多,但最简单的方法是使用torch.nn.DistributedDataParallel而不是torch.nn.DataParallel。这样,每个GPU将由专用CPU内核驱动,避免了DataParallel的GIL问题。分布式训练文档地址:https://pytorch.org/tutorials/beginner/dist_overview.html11。将梯度设置为None而不是0。将梯度设置为.zero_grad(set_to_none=True)而不是.zero_grad()。这样做可以让内存分配器处理梯度,而不是将它们设置为0。正如文档所说,将梯度设置为None会产生适度的加速,但不要指望奇迹。请注意,这也有缺点,有关详细信息,请参阅文档。文档地址:https://pytorch.org/docs/stable/optim.html12.使用.as_tensor()代替.tensor()torch.tensor()会一直复制数据。如果要转换numpy数组,请使用torch.as_tensor()或torch.from_numpy()以避免复制数据。13.必要时打开调试工具PyTorch提供了很多调试工具,比如autograd.profiler、autograd.grad_check、autograd.anomaly_detection。请确保需要调试时打开调试器,不需要时关闭调试器,因为调试器会减慢你的训练速度。14、使用梯度裁剪关于RNN中避免梯度爆炸的问题,一些实验和理论已经证实梯度裁剪(gradient=min(gradient,threshold))可以加速收敛。HuggingFace的Transformer实现是如何使用渐变裁剪的一个非常清楚的例子。也可以使用本文中提到的其他一些方法,例如AMP。在PyTorch中,这可以使用torch.nn.utils.clip_grad_norm_来实现。15.TurnoffbiasbeforeBatchNorm在开始BatchNormalization层之前关闭偏置层。对于二维卷积层,bias关键字可以设置为False:torch.nn.Conv2d(...,bias=False,...)。16.验证时关闭梯度计算要在验证时关闭梯度计算,设置:torch.no_grad()。17.使用input和batchnormalization双重检查输入是否被归一化?是否使用批量归一化?
