24小时新闻关注

西南医科大学,维生素ad,王海燕-得力智选内容聚合平台

PyTorch是最优异的深度学习结构之一,它简略高雅,十分合适入门。本文将介绍PyTorch的最佳实践和代码风格都是怎样的。

尽管这是一个非官方的 PyTorch攻略,但本文总结了一年多运用 PyTorch 结构的经历,尤其是用它开发深度学习相关作业的最优解决方案。请注意,咱们共享的经历大多是从研讨和实践视点动身的。

这是一个开发的项目,欢迎其它读者改善该文档:https://github.com/IgorSusmelj/pytorch-styleguide。

本文档主要由三个部分构成:首要,本文会扼要清点 Python 中的最好配备。接着,本文会介绍一些运用 PyTorch 的技巧和主张。终究,咱们共享了一些运用其它结构的见地和经历,这些结构一般协助咱们改善作业流。

清点 Python 配备

主张运用 Python 3.6 以上版别

依据咱们的经历,咱们引荐运用 Python 3.6 以上的版别,因为它们具有以下特性,这些特功能够使咱们很简略写出简练的代码:

  • 自 Python 3.6 今后支撑「typing」模块
  • 自 Python 3.6 今后支撑格式化字符串(f string)

Python 风格攻略

咱们企图遵从 Google 的 Python 编程风格。请参阅 Google 供给的优异的 python 编码风格攻略:

地址:https://github.com/google/styleguide/blob/gh-pages/pyguide.md。

在这里,咱们会给出一个最常用命名规范小结:

集成开发环境

一般来说,咱们主张运用 visual studio 或 PyCharm这样的集成开发环境。而 VS Code 在相对轻量级的编辑器中供给语法高亮和主动补全功用,PyCharm 则具有许多用于处理长途集群使命的高档特性。

Jupyter Notebooks VS Python 脚本

一般来说,咱们主张运用 Jupyter Notebook 进行开端的探究,或测验新的模型和代码。假如你想在更大的数据集上练习该模型,就应该运用 Python 脚本,因为在更大的数据集上,复现性愈加重要。

咱们引荐你采纳下面的作业流程:

  • 在开端的阶段,运用 Jupyter Notebook
  • 对数据和模型进行探究
  • 在 notebook 的单元中构建你的类/办法
  • 将代码移植到 Python 脚本中
  • 在服务器上练习/布置

开发常备库

常用的程序库有:

文件安排

不要将一切的层和模型放在同一个文件中。最好的做法是将终究的网络别离到独立的文件(networks.py)中,并将层、丢失函数以及各种操作保存在各自的文件中(layers.py,losses.py,ops.py)。终究得到的模型(由一个或多个网络组成)应该用该模型的称号命名(例如,yolov3.py,DCGAN.py),且引证各个模块。

主程序、独自的练习和测验脚本应该只需求导入带有模型姓名的 Python 文件。

PyTorch 开发风格与技巧

咱们主张将网络分解为更小的可复用的片段。一个 nn.Module 网络包括各种操作或其它构建模块。丢失函数也是包括在 nn.Module 内,因而它们能够被直接整合到网络中。

承继 nn.Module 的类有必要具有一个「forward」办法,它完结了各个层或操作的前向传导。

一个 nn.module 能够经过「self.net(input)」处理输入数据。在这里直接运用了目标的「call()」办法将输入数据传递给模块。

output = self.net(input)

PyTorch 环境下的一个简略网络

运用下面的形式能够完结具有单个输入和输出的简略网络:

class ConvBlock(nn.Module):
def __init__(self):
super(ConvBlock, self).__init__()
block = [nn.Conv2d(...)]
block += [nn.ReLU()]
block += [nn.BatchNorm2d(...)]
self.block = nn.Sequential(*block)
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)

请注意以下几点:

  • 咱们复用了简略的循环构建模块(如卷积块 ConvBlocks),它们由相同的循环形式(卷积、
  • 激活函数
  • 、归一化)组成,并装入独立的 nn.Module 中。
  • 咱们构建了一个所需求层的列表,并终究运用「nn.Sequential()」将一切层级组合到了一个模型中。咱们在 list 目标前运用「*」操作来打开它。
  • 在前向传导进程中,咱们直接运用输入数据运转模型。

PyTorch 环境下的简略残差网络

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 ou

在这里,ResNet 模块的跳动衔接直接在前向传导进程中完结了,PyTorch 答应在前向传导进程中进行动态操作。

PyTorch 环境下的带多个输出的网络

关于有多个输出的网络(例如运用一个预练习好的 VGG 网络构建感知丢失),咱们运用以下形式:

class Vgg19(torch.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

请注意以下几点:

  • 咱们运用由「torchvision」包供给的预练习模型
  • 咱们将一个网络切分红三个模块,每个模块由预练习模型中的层组成
  • 咱们经过设置「requires_grad = False」来固定网络权重
  • 咱们回来一个带有三个模块输出的 list

自定义丢失函数

即便 PyTorch 现已具有了很多规范丢失函数,你有时也或许需求创立自己的丢失函数。为了做到这一点,你需求创立一个独立的「losses.py」文件,而且经过扩展「nn.Module」创立你的自定义丢失函数:

class CustomLoss(torch.nn.Module):
def __init__(self):
super(CustomLoss,self).__init__()
def forward(self,x,y):
loss = torch.mean((x - y)**2)
return loss

练习模型的最佳代码结构

关于练习的最佳代码结构,咱们需求运用以下两种形式:

  • 运用 prefetch_generator 中的 BackgroundGenerator 来加载下一个批量数据
  • 运用 tqdm 监控练习进程,并展现核算功率,这能协助咱们找到数据加载流程中的瓶颈
# 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 练习

PyTorch 中有两种运用多 GPU 进行练习的形式。

依据咱们的经历,这两种形式都是有用的。但是,榜首种办法得到的成果更好、需求的代码更少。因为第二种办法中的 GPU 间的通讯更少,好像具有细微的功能优势。

对每个网络输入的 batch 进行切分

最常见的一种做法是直接将一切网络的输入切分为不同的批量数据,并分配给各个 GPU。

这样一来,在 1 个 GPU 上运转批量巨细为 64 的模型,在 2 个 GPU 上运转时,每个 batch 的巨细就变成了 32。这个进程能够运用「nn.DataParallel(model)」包装器主动完结。

将一切网络打包到一个超级网络中,并对输入 batch 进行切分

这种形式不太常用。下面的代码库房向咱们展现了 Nvidia 完结的 pix2pixHD,它有这种办法的完结。

地址:https://github.com/NVIDIA/pix2pixHD

PyTorch 中该做和不应做的

在「nn.Module」的「forward」办法中避免运用 Numpy 代码

Numpy 是在 CPU 上运转的,它比 torch 的代码运转得要慢一些。因为 torch 的开发思路与 numpy 类似,所以大多数

Numpy

中的函数现已在 PyTorch 中得到了支撑。

将「DataLoader」从主程序的代码中别离

载入数据的作业流程应该独立于你的主练习程序代码。PyTorch 运用「background」进程愈加高效地载入数据,而不会搅扰到主练习进程。

不要在每一步中都记载成果

一般而言,咱们要练习咱们的模型好几千步。因而,为了减小核算开支,每隔 n 步对丢失和其它的核算成果进行记载就足够了。尤其是,在练习进程中将中心成果保存成图画,这种开支是十分大的。

运用命令行参数

运用命令行参数设置代码执行时运用的参数(batch 的巨细、学习率等)十分便利。一个简略的试验参数盯梢办法,即直接把从「parse_args」接收到的字典(dict 数据)打印出来:

# saves arguments to config.txt file
opt = parser.parse_args()with open("config.txt", "w") as f:
f.write(opt.__str__())

假如或许的话,请运用「Use .detach()」从核算图中开释张量

为了完结主动微分,PyTorch 会盯梢一切触及张量的操作。请运用「.detach()」来避免记载不必要的操作。

运用「.item()」打印出标量张量

你能够直接打印变量。但是,咱们主张你运用「variable.detach()」或「variable.item()」。在前期版别的 PyTorch(< 0.4)中,你有必要运用「.data」拜访变量中的张量值。

运用「call」办法替代「nn.Module」中的「forward」办法

这两种方法并不完全相同,正如下面的 GitHub 问题单所指出的:https://github.com/IgorSusmelj/pytorch-styleguide/issues/3

output = self.net.forward(input)
# they are not equal!
output = self.net(input)

原文链接:https://github.com/IgorSusmelj/pytorch-styleguide

推荐新闻