很多朋友对于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 监控训练过程,并展示计算效率,这能帮助我们找到数据加载流程中的瓶颈
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 fileopt = 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
用户评论
我一直在用TensorFlow,一直想试试PyTorch,感觉这个标题很吸引人!希望里面有比较生动的实战例子,最好能对比一下PyTorch和TensorFlow的区别,这样更方便我选择哪一个框架使用。
有20位网友表示赞同!
写代码风格优美?这听起来很有意思!有时候为了效率我会直接用简洁的代码,忽略了的可读性和维护性。看这个教程应该能够让我学习到一些提高代码风格的技巧,让我的PyTorch代码更漂亮
有16位网友表示赞同!
想问一下,这篇博客文章针对的是什么类型的读者?是初学者还是已经有一定经验的人? 作为PyTorch初学者,期待能看到基础知识讲解和比较实际的案例。
有10位网友表示赞同!
终于有人写这个主题了!我最近也在学习PyTorch,感觉代码风格确实很重要。不知道有没有探讨一下如何使用工具自动优化代码风格的问题,这比手动修改更省心!
有19位网友表示赞同!
代码风格优美?不就是越简洁越好嘛!太多注释反而看着烦杂,还是相信功能才是最重要的吧。
有13位网友表示赞同!
我有个问题,就是PyTorch里的很多API和函数名都让人感到比较抽象或者难记,写出的代码看起来也比较冗长。这篇文章有没有提解决方案?
有16位网友表示赞同!
我对如何使用变量命名和注释来提高代码可读性特别感兴趣,感觉这些细节往往被忽略了!期待看到一些实际案例,学习学到更高级的PyTorch开发技巧。
有5位网友表示赞同!
我一直觉得写Python代码就应该遵循PEP风格规范,这样能保证代码的可读性和一致性。不知道这篇文章中是否会涉及到这个方面?
有11位网友表示赞同!
我从零开始学习PyTorch,感觉代码写的太乱了,真的希望能看到一些关于风格最佳实践的指导。比如函数命名、变量命名、注释等方面都有明确的一些规范吗?
有19位网友表示赞同!
这篇博客文章非常有帮助!我已经尝试去应用它中的建议,效果的确显著,我的代码看起来更加整洁易懂了。推荐给所有想要提高PyTorch代码风格的朋友!
有20位网友表示赞同!
这个标题就挺吸引人的!我一直觉得代码的风格很重要,不仅要能读明白,还要看得顺眼! 希望这篇文章能给我一些实用的建议。毕竟美观又简洁的代码才能更好地展现自己的水平
有10位网友表示赞同!
作为一名资深开发者,我对Python代码风格已经有一定的心得了。希望这篇博客能给出一些新的想法,让我进一步提升PyTorch代码的质量和可读性!期待看到一些更深入的技术探讨。
有13位网友表示赞同!
学习PyTorch主要是想要把项目落到实地的,所以对美观的代码没有那么大的追求吧?功能才是主要目标!除非项目比较复杂,需要多人协作开发,那代码风格就比较重要了。
有5位网友表示赞同!
我一直觉得PyTorch的API设计有些地方不够完善,这导致写出简洁优雅的代码有点难度.希望作者能提到一些应对这种局限性的技巧。
有5位网友表示赞同!
学习新知识总是让人兴奋!期待这篇博客能让我系统地掌握PyTorch最佳实践,特别是如何写出风格优美的代码。相信它能帮助我提高开发效率和项目质量!
有6位网友表示赞同!
我对函数重载和多态这方面很有兴趣,希望文章能详细阐述如何在PyTorch中实现这些优秀的编码习惯!优雅的代码应该具备良好的可扩展性和复用性
有14位网友表示赞同!
学习AI开发是一个充满挑战的过程,需要不断精进技术和提升代码质量。期待这篇博客能给我提供宝贵的经验和技巧,让我在PyTorch之旅上更加顺利!
有13位网友表示赞同!