这不是Pytorch的官方风格指南。本文档总结了使用Pytorch框架一年多的深度学习经验中的最佳实践。请注意,我们分享的学习主要来自研究和创业的角度。
这是一个开放项目,其他合作者非常欢迎编辑和改进文档。
您会发现此文档的三个主要部分。首先,快速回顾一下Python的最佳实践,然后使用Pytorch提出一些提示和建议。最后,我们使用其他框架分享了一些见解和经验,这些框架通常帮助我们改善了工作流程。
更新20.12.2020
更新30.4.2019
经过如此多的积极反馈,我还添加了我们项目中常用的构建块的摘要:您会发现(自我注意,使用VGG,频谱归一化,自适应实例归一化的构建块),...)
损失,层和其他构件的代码段
根据我们的经验,我们建议使用Python 3.6+,因为具有以下功能,这些功能非常方便,用于简单而简单的代码:
我们尝试遵循Python的Google StyleGuide。请参阅Google提供的有关Python代码的备受证明的样式指南。
我们在这里提供了最常用的规则的摘要:
从3.16.4起
| 类型 | 习俗 | 例子 |
|---|---|---|
| 软件包和模块 | lower_with_under | 来自prefetch_generator导入buckddenterator |
| 课程 | capwords | 类数据载体 |
| 常数 | caps_with_under | batch_size = 16 |
| 实例 | lower_with_under | 数据集=数据集 |
| 方法和功能 | lower_with_under() | def Visualize_tensor() |
| 变量 | lower_with_under | background_color ='蓝色' |
通常,我们建议使用IDE,例如Visual Studio Code或Pycharm。而VS代码在相对轻巧的编辑器Pycharm中提供语法突出显示和自动完成,具有许多用于使用远程簇的高级功能。通过快速增长的扩展生态系统,VS代码变得非常强大。
确保已安装以下扩展名:
如果正确设置,则可以执行以下操作:
通常,我们建议使用Jupyter笔记本电脑进行初始探索/播放新型号和代码。一旦您想在更大的数据集上训练模型,应立即使用Python脚本,在该模型中也更重要。
我们推荐的工作流程:
| Jupyter笔记本 | Python脚本 |
|---|---|
| +探索 | +在不中断的情况下运行更长的作业 |
| +调试 | +易于跟踪git的变化 |
| - 可以成为一个巨大的文件 | - 调试主要意味着重新整个脚本 |
| - 可以中断(不要用于长期培训) | |
| - 容易出错,变得一团糟 |
常用库:
| 姓名 | 描述 | 用于 |
|---|---|---|
| 火炬 | 使用神经网络的基础框架 | 使用Backprop创建张量,网络和培训它们 |
| 火炬 | Pytorch计算机视觉模块 | 图像数据预处理,增强,后处理 |
| 枕头(PIL) | Python成像库 | 加载图像并存储它们 |
| numpy | Python的科学计算包 | 数据预处理和后处理 |
| prefetch_generator | 背景处理库 | 在计算过程中将下一批加载在背景中 |
| TQDM | 进度栏 | 每个时期训练期间的进展 |
| Torchinfo | 打印pytorch的类似凯拉斯的模型摘要 | 显示网络,每一层的参数和大小 |
| TORCH.UTILS.TENSORBOARD | pytorch中的张板 | 记录实验并将其显示在张板中 |
不要将所有图层和模型放入同一文件中。最好的做法是将最终网络分开为单独的文件( networks.py ),并将各个层,损失和操作保留在相应的文件中( layers.py.py , loss.py , ops.py )。完成的模型(由一个或多个网络组成)应在其名称的文件中参考(例如yolov3.py , dcgan.py )
主要例程,各自的火车和测试脚本应仅从具有模型名称的文件中导入。
我们建议将网络分解成其较小的可重复使用的部分。网络是一个NN.模块,由操作或其他nn.模块作为构建块组成。损耗功能也是nn.模块,因此可以直接集成到网络中。
从NN.模块继承的类必须具有实现相应层或操作的正向通过的正向方法。
使用self.net(输入)可以将nn.模块用于输入数据。这只需使用对象的呼叫()方法即可通过模块输入输入。
output = self . net ( input )对于具有单个输入和单个输出的简单网络使用以下模式:
class ConvBlock ( nn . Module ):
def __init__ ( self ):
super ( ConvBlock , self ). __init__ ()
self . block = nn . Sequential (
nn . Conv2d (...),
nn . ReLU (),
nn . BatchNorm2d (...)
)
def forward ( self , x ):
return self . block ( x )
class SimpleNetwork ( nn . Module ):
def __init__ ( self , num_resnet_blocks = 6 ):
super ( SimpleNetwork , self ). __init__ ()
# here we add the individual layers
layers = [ ConvBlock (...)]
for i in range ( num_resnet_blocks ):
layers += [ ResBlock (...)]
self . net = nn . Sequential ( * layers )
def forward ( self , x ):
return self . net ( x )注意以下内容:
class ResnetBlock ( nn . Module ):
def __init__ ( self , dim , padding_type , norm_layer , use_dropout , use_bias ):
super ( ResnetBlock , self ). __init__ ()
self . conv_block = self . build_conv_block (...)
def build_conv_block ( self , ...):
conv_block = []
conv_block += [ nn . Conv2d (...),
norm_layer (...),
nn . ReLU ()]
if use_dropout :
conv_block += [ nn . Dropout (...)]
conv_block += [ nn . Conv2d (...),
norm_layer (...)]
return nn . Sequential ( * conv_block )
def forward ( self , x ):
out = x + self . conv_block ( x )
return out在这里, RESNET块的跳过连接已直接在正向通行证中实现。 Pytorch允许在正向通行证期间进行动态操作。
对于需要多个输出的网络,例如使用验证的VGG网络构建感知损失,我们使用以下模式:
class Vgg19 ( nn . Module ):
def __init__ ( self , requires_grad = False ):
super ( Vgg19 , self ). __init__ ()
vgg_pretrained_features = models . vgg19 ( pretrained = True ). features
self . slice1 = torch . nn . Sequential ()
self . slice2 = torch . nn . Sequential ()
self . slice3 = torch . nn . Sequential ()
for x in range ( 7 ):
self . slice1 . add_module ( str ( x ), vgg_pretrained_features [ x ])
for x in range ( 7 , 21 ):
self . slice2 . add_module ( str ( x ), vgg_pretrained_features [ x ])
for x in range ( 21 , 30 ):
self . slice3 . add_module ( str ( x ), vgg_pretrained_features [ x ])
if not requires_grad :
for param in self . parameters ():
param . requires_grad = False
def forward ( self , x ):
h_relu1 = self . slice1 ( x )
h_relu2 = self . slice2 ( h_relu1 )
h_relu3 = self . slice3 ( h_relu2 )
out = [ h_relu1 , h_relu2 , h_relu3 ]
return out在此处注意以下内容:
即使Pytorch已经具有许多标准损失功能,有时也有必要创建自己的损失功能。为此,创建nn.Module单独的文件losses.py 。
class CustomLoss ( nn . Module ):
def __init__ ( self ):
super ( CustomLoss , self ). __init__ ()
def forward ( self , x , y ):
loss = torch . mean (( x - y ) ** 2 )
return loss 该存储库的CIFAR10-示例文件夹中提供了一个完整示例。
请注意,我们使用以下模式:
# import statements
import torch
import torch . nn as nn
from torch . utils import data
...
# set flags / seeds
torch . backends . cudnn . benchmark = True
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )
...
# Start with main code
if __name__ == '__main__' :
# argparse for additional flags for experiment
parser = argparse . ArgumentParser ( description = "Train a network for ..." )
...
opt = parser . parse_args ()
# add code for datasets (we always use train and validation/ test set)
data_transforms = transforms . Compose ([
transforms . Resize (( opt . img_size , opt . img_size )),
transforms . RandomHorizontalFlip (),
transforms . ToTensor (),
transforms . Normalize (( 0.5 , 0.5 , 0.5 ), ( 0.5 , 0.5 , 0.5 ))
])
train_dataset = datasets . ImageFolder (
root = os . path . join ( opt . path_to_data , "train" ),
transform = data_transforms )
train_data_loader = data . DataLoader ( train_dataset , ...)
test_dataset = datasets . ImageFolder (
root = os . path . join ( opt . path_to_data , "test" ),
transform = data_transforms )
test_data_loader = data . DataLoader ( test_dataset ...)
...
# instantiate network (which has been imported from *networks.py*)
net = MyNetwork (...)
...
# create losses (criterion in pytorch)
criterion_L1 = torch . nn . L1Loss ()
...
# if running on GPU and we want to use cuda move model there
use_cuda = torch . cuda . is_available ()
if use_cuda :
net = net . cuda ()
...
# create optimizers
optim = torch . optim . Adam ( net . parameters (), lr = opt . lr )
...
# load checkpoint if needed/ wanted
start_n_iter = 0
start_epoch = 0
if opt . resume :
ckpt = load_checkpoint ( opt . path_to_checkpoint ) # custom method for loading last checkpoint
net . load_state_dict ( ckpt [ 'net' ])
start_epoch = ckpt [ 'epoch' ]
start_n_iter = ckpt [ 'n_iter' ]
optim . load_state_dict ( ckpt [ 'optim' ])
print ( "last checkpoint restored" )
...
# if we want to run experiment on multiple GPUs we move the models there
net = torch . nn . DataParallel ( net )
...
# typically we use tensorboardX to keep track of experiments
writer = SummaryWriter (...)
# now we start the main loop
n_iter = start_n_iter
for epoch in range ( start_epoch , opt . epochs ):
# set models to train mode
net . train ()
...
# use prefetch_generator and tqdm for iterating through data
pbar = tqdm ( enumerate ( BackgroundGenerator ( train_data_loader , ...)),
total = len ( train_data_loader ))
start_time = time . time ()
# for loop going through dataset
for i , data in pbar :
# data preparation
img , label = data
if use_cuda :
img = img . cuda ()
label = label . cuda ()
...
# It's very good practice to keep track of preparation time and computation time using tqdm to find any issues in your dataloader
prepare_time = start_time - time . time ()
# forward and backward pass
optim . zero_grad ()
...
loss . backward ()
optim . step ()
...
# udpate tensorboardX
writer . add_scalar (..., n_iter )
...
# compute computation time and *compute_efficiency*
process_time = start_time - time . time () - prepare_time
pbar . set_description ( "Compute efficiency: {:.2f}, epoch: {}/{}:" . format (
process_time / ( process_time + prepare_time ), epoch , opt . epochs ))
start_time = time . time ()
# maybe do a test pass every x epochs
if epoch % x == x - 1 :
# bring models to evaluation mode
net . eval ()
...
#do some tests
pbar = tqdm ( enumerate ( BackgroundGenerator ( test_data_loader , ...)),
total = len ( test_data_loader ))
for i , data in pbar :
...
# save checkpoint if needed
...Pytorch中有两种不同的模式用于使用多个GPU进行训练。根据我们的经验,两种模式都是有效的。但是,第一个结果在更好且更少的代码中结果。由于GPU之间的沟通较少,第二个似乎具有略有性能优势。我在官方Pytorch论坛上问了一个问题,涉及这里的两种方法
最常见的是,简单地将所有网络的批处理分为单个GPU。
因此,在1 GPU上运行的具有批量64的模型将在2 GPU上运行,每个GPU的批量大小为32。这可以通过NN.Dataparallear(模型)包裹模型来自动完成。
这种模式不太常用。 NVIDIA的Pix2PixHD实现中显示了实现此方法的存储库
Numpy在CPU上运行,并且比火炬代码慢。由于已经开发了割炬,因此,大多数numpy功能已经得到了pytorch的支持。
数据加载管道应独立于您的主要培训代码。 Pytorch使用背景工人更有效地加载数据,而不会干扰主训练过程。
通常,我们将模型训练成千上万个步骤。因此,足以记录损失和其他结果,以减少开销。特别是,保存中介结果是在培训期间图像的昂贵。
使用命令行参数在代码执行期间设置参数非常方便(批处理大小,学习率等)。跟踪实验论点的一种简单方法是打印从parse_args收到的字典:
...
# saves arguments to config.txt file
opt = parser . parse_args ()
with open ( "config.txt" , "w" ) as f :
f . write ( opt . __str__ ())
...Pytorch跟踪所有涉及自动差异张量的操作。使用.detach()防止记录不必要的操作。
您可以直接打印变量,但是建议使用valiable.detach()或variable.item() 。在早期的Pytorch版本<0.4中,您必须使用.data访问变量的张量。
这两种方法在这里的问题之一中指出的两种方式并不相同:
output = self . net . forward ( input )
# they are not equal!
output = self . net ( input )我们建议在代码开头设置以下种子:
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )在NVIDIA GPU上,您可以在代码开头添加以下行。这将允许CUDA后端在第一次执行时优化图形。但是,请注意,如果您更改网络输入/输出张量尺寸,则每次更改时,图表都将被优化。这可能会导致运行时间非常缓慢,并且不会出现内存错误。仅当您的输入和输出始终具有相同的形状时,仅设置此标志。通常,这会提高约20%。
torch . backends . cudnn . benchmark = True这取决于所使用的机器,预处理管道和网络大小。在1080TI GPU上运行SSD,我们看到的计算效率几乎为1.0,这是一个理想的情况。如果使用浅(小)网络或慢速硬盘,则数字可能会根据您的设置下降到0.1-0.2左右。
在Pytorch中,我们可以很容易地实现虚拟批次大小。我们只是防止优化器对参数进行更新,并总结批次循环的渐变。
...
# in the main loop
out = net ( input )
loss = criterion ( out , label )
# we just call backward to sum up gradients but don't perform step here
loss . backward ()
total_loss += loss . item () / batch_size
if n_iter % batch_size == batch_size - 1 :
# here we perform out optimization step using a virtual batch size
optim . step ()
optim . zero_grad ()
print ( 'Total loss: ' , total_loss )
total_loss = 0.0
...我们可以使用实例化优化器直接访问学习率,如下所示:
...
for param_group in optim . param_groups :
old_lr = param_group [ 'lr' ]
new_lr = old_lr * 0.1
param_group [ 'lr' ] = new_lr
print ( 'Updated lr from {} to {}' . format ( old_lr , new_lr ))
...如果您想使用验证的模型(例如VGG)来计算损失,而不是训练它(例如,样式转移/ gans/ auto-编码器的感知损失),则可以使用以下模式:
...
# instantiate the model
pretrained_VGG = VGG19 (...)
# disable gradients (prevent training)
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = False
...
# you don't have to use the no_grad() namespace but can just run the model
# no gradients will be computed for the VGG model
out_real = pretrained_VGG ( input_a )
out_fake = pretrained_VGG ( input_b )
loss = any_criterion ( out_real , out_fake )
...这些方法用于设置从训练到推理模式的batchnorm2d或dropout2d之类的层。从NN.模块继承的每个模块都有一个称为ISTRAINing的属性。 .eval()和.train()只需将此属性设置为true/ false即可。有关如何实现此方法的更多信息,请查看Pytorch中的模块代码
确保在执行代码期间没有计算和存储梯度。您可以简单地使用以下模式确保:
with torch . no_grad ():
# run model here
out_tensor = net ( in_tensor )在Pytorch中,您可以冷冻层。这将阻止他们在优化步骤中进行更新。
# you can freeze whole modules using
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = False由于Pytorch 0.4 *变量和张量已合并。我们不必明确创建一个变量对象。
C ++版本的速度约为10%
待...
根据我们的经验,您可以获得约20%的速度。但是,第一次运行模型时,构建优化的图表需要很长时间。在某些情况下(向前循环,没有固定输入形状,如果/其他方向等。)此标志可能会导致内存或其他错误。
待...
如果从计算图中释放张量。这里显示了一个不错的插图
请提供有关我们如何改善此样式指南的反馈!您可以通过创建拉动请求来打开问题或提出更改。
如果您喜欢此存储库,请不要忘记查看我们的其他框架: