Auto Byte

专注未来出行及智能汽车科技

微信扫一扫获取更多资讯

Science AI

关注人工智能与其他前沿技术、基础学科的交叉研究与融合发展

微信扫一扫获取更多资讯

PyTorch推出0.2版本:加入分布式机器学习功能

正值 ICML 2017 期间,我们发行了下一代主版本 PyTorch V0.2.0,现在你可从官网 http://pytorch.org 安装它,该版本的软件包文档可从 http://pytorch.org/docs/0.2.0/ 获取 。PyTorch V0.2 新增了期待已久的功能,比如广播、高级索引、高阶梯度以及最重要的分布式 PyTorch。


由于引入了广播功能,特定可广播情景的代码行为不同于 V0.1.12 中的行为。这可能导致你的现有代码中存在不易发现的误差。在「重要的破损量与工作区」这一章节中,我们将提供识别这些模糊代码的简单方法。


  • 张量广播(numpy 风格)
  • 张量和变量的高级索引
  • 高阶梯度(Higher-order gradients)
  • 分布式 PyTorch(多结点训练等)
  • 神经网络层与特征: SpatialTransformers、WeightNorm、EmbeddingBag 等
  • torch 和 autograd 中的新东西:矩阵乘法、矩阵的逆等
  • 更简单的调试,更好的错误信息
  • 漏洞修复
  • 重要的破损量和工作区

张量广播(numpy 风格)



简单来说,如果一个 PyTorch 操作支持广播,那么其张量参数可自动扩展为相同大小(无需拷贝数据)。 PyTorch 广播的语义严格遵守 numpy 风格的广播;如果你很熟悉 numpy 广播,事情就会按照你的预期发展。


通用语义


如果以下规则成立,则两个张量是「可广播的」:

  • 每个张量至少有一个维度。
  • 当开始迭代维度大小时,从后面的维度开始,维度大小必须相同,它们中的一个是 1,或者其中一个不存在

例如:


>>> x=torch.FloatTensor(5,7,3)
>>> y=torch.FloatTensor(5,7,3)
# same shapes are always broadcastable (i.e. the above rules always hold)
# can line up trailing dimensions
>>> x=torch.FloatTensor(5,3,4,1)
>>> y=torch.FloatTensor( 3,1,1)
# x and y are broadcastable.
# 1st trailing dimension: both have size 1
# 2nd trailing dimension: y has size 1
# 3rd trailing dimension: x size == y size
# 4th trailing dimension: y dimension doesn't exist
# but:>>> x=torch.FloatTensor(5,2,4,1)
>>> y=torch.FloatTensor( 3,1,1)
# x and y are not broadcastable, because in the 3rd trailing dimension 2 != 3

如果两个张量 x、y 是可广播的,得到的张量大小计算如下:


如果维度 x 和 y 的数量不相同,则把带有更少维度的张量的维度设为 1 以使它们的长度相等。


接着,对于每一个维度大小,所得到的维度大小是该维度上的 x 和 y 大小的最大值。



例如:


# can line up trailing dimensions to make reading easier>>> x=torch.FloatTensor(5,1,4,1)>>> y=torch.FloatTensor(  3,1,1)>>> (x+y).size()
torch.Size([5, 3, 4, 1])
# error case>>> x=torch.FloatTensor(5,2,4,1)>>> y=torch.FloatTensor( 3,1,1)>>> (x+y).size()RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 1

更多细节请参见 PyTorch 文档网站 http://pytorch.org/docs/0.2.0/notes/broadcasting.html。同样,每个 torch 函数列举了其在文档中的广播语义。



张量和变量的高级索引



PyTorch 现在支持 NumPy 风格的高级索引的一个子集,这允许用户使用相同的

[]风格的操作在张量的每一个维度上选择任意的索引,包括非相邻索引和重复索引;这也使得无需调用 PyTorchIndex[Select, Add, ...]函数即可获得一个更加灵活的索引策略。

让我们看一些实例:


x = torch.Tensor(5, 5, 5)

纯整数组索引 - 在每个维度上指定任意的索引


x[[1, 2], [3, 2], [1, 0]]--> yields a 2-element Tensor (x[1][3][1], x[2][2][0])

同样支持广播、复制


x[[2, 3, 2], [0], [1]]--> yields a 3-element Tensor (x[2][0][1], x[3][0][1], x[2][0][1])

允许任意的分度器(indexer)形状


x[[[1, 0], [0, 1]], [0], [1]].shape--> yields a 2x2 Tensor [[x[1][0][1], x[0][0][1]],
[x[0][0][1], x[1][0][1]]]

可以使用冒号、椭圆


x[[0, 3], :, :]
x[[0, 3], ...]--> both yield a 2x5x5 Tensor [x[0], x[3]]

也可以使用张量来索引!


y = torch.LongTensor([0, 2, 4])
x[y, :, :]--> yields a 3x5x5 Tensor [x[0], x[2], x[4]]

选择小于 n维,请注意逗号的使用。


x[[1, 3], ]--> yields a 2x5x5 Tensor [x[1], x[3]]

高阶梯度



现在你可以评估 PyTorch 中的高阶微分。例如,你可以计算 Hessian-Vector积,以模型的梯度的范数为罚项,实现展开的 GAN 和提升的 WGAN 等。在0.2版本中,我们使得所有torch.XXX

函数和最流行的n层具备了计算高阶梯度的能力。其余的将会在下一个版本中介绍。


下面是一个简短的实例,它以 Resnet-18 模型权重梯度的范数为罚项,因此权重数量的变化比较缓慢。


import torchfrom torchvision.models import resnet18from torch.autograd import Variable

model = resnet18().cuda()
# dummy inputs for the exampleinput = Variable(torch.randn(2,3,224,224).cuda(), requires_grad=True)
target = Variable(torch.zeros(2).long().cuda())
# as usual
output = model(input)
loss = torch.nn.functional.nll_loss(output, target)

grad_params = torch.autograd.grad(loss, model.parameters(), create_graph=True)# torch.autograd.grad does not accumuate the gradients into the .grad attributes# It instead returns the gradients as Variable tuples.# now compute the 2-norm of the grad_params
grad_norm = 0for grad in grad_params:
grad_norm += grad.pow(2).sum()
grad_norm = grad_norm.sqrt()
# take the gradients wrt grad_norm. backward() will accumulate# the gradients into the .grad attributes
grad_norm.backward()
# do an optimization step
optimizer.step()

这里我们看两个新的概念:

  • torch.autograd.grad 是一个函数,它包含 [输出、输入列表(为了它你需要梯度)],并且返回梯度 wrt。这些输入作为元组,而不是将梯度累加到 .grad 属性中。如果你想要进一步在梯度上操作,这很有帮助。
  • 你可以在梯度上操作,并在其上调用backward()。

支持高阶梯度的n层列表是:

  • AvgPool*d、 BatchNorm*d、 Conv*d、MaxPool1d,2d、Linear、 Bilinear
  • pad、ConstantPad2d、ZeroPad2d、LPPool2d、PixelShuffle
  • ReLU6、LeakyReLU、PReLU、Tanh、Tanhshrink、Threshold、Sigmoid、HardTanh、ELU、Softsign、SeLU
  • L1Loss、NLLLoss、 PoissonNLLLoss、LogSoftmax、Softmax2d

其余的将在下一个版本中启用。


为了高阶梯度,我们引入了一种编写autograd.Function的新风格。更多函数新风格的信息请参阅http://pytorch.org/docs/0.2.0/notes/extending.html。你们中的大多数并不亲自写

autograd.Functions ,因为它们是把新操作引入到 autograd 引擎的低级基元,其中你指定了前向与后向调用。


分布式PyTorch



我们还介绍了torch.distributed包,它允许我们在多机器间交换Tensors。使用该软件包,我们能够将神经网络放在多机器和使用较大批量进行训练。例如,给定一些基元,我们就能实现《精确的、大批量随机梯度下降:在1小时内训练ImageNet》。


该distributed包遵循MPI风格的程序设计模型。这意味着该软件包会提供像send、recv、all_reduce那样的函数以允许在结点(机器)之间交换Tensors。


对于每个机器最开始识别彼此并分配唯一的编码(排级),我们提供了简单的初始方法:


  • 共享文件系统(要求所有进程都能访问一个单一的文件系统)
  • IP 组播传输(要求所有的进程都在一个网络中)
  • 环境变量(要求我们手动配置等级,并且需要知道从所有进程中可获得结点的地址)

我们的软件包文档包含更多初始化和可用后端的信息,现在我们了解一下使用组播地址初始化的案例:


import torch.distributed as dist

dist.init_process_group(backend='tcp',
init_method='tcp://[ff15:1e18:5d4c:4cf0:d02d:b659:53ba:b0a7]:23456',
world_size=4)
print('Hello from process {} (out of {})!'.format(
dist.get_rank(), dist.get_world_size()))

该代码片段将从第三个机器的process 2中打印“Hello”。


World size就是将参与整个工作的进程数量。每一个进程都将分配一个等级,即从0到world_size^( - 1)的数字,这些代号在整个工作中都是唯一的。它将成为处理识别符,并用于替代地址,例如指定哪一个进程将发送张量。


下面的代码片段展示了点到点的传输是如何简单地实现的:


# All processes (receiving ones too!) need to have tensors of appropriate# size preallocated.
x = torch.Tensor(10)if dist.get_rank() == 0:
x.normal_()
# Send x to process with rank 1
dist.send(x, dst=1)else: # rank == 1# Receive data from process with rank 0 and save result in x
dist.recv(x, src=0)

异步p2p函数(isend、irecv)同样是可用的。


然而,一些通信模式经常出现,并且已经开发了更高效的集合调用。它们一般占用整个进程组,并且比使用send/recv的朴素算法要更快一些。例如all_reduce:


x = torch.Tensor([dist.get_rank()])# Add tensors from all processes such that they all receive the result.# x is an input and output to this operation.
dist.all_reduce(x)

该分布式包是相当低级的,所以它允许实现更高级的算法和剪切代码以适应特定的任务目标,但是数据并行训练更为通用,所以我们为它构建了高级助手。因此,我们将介绍DistributedDataParallel,该函数基本上是nn.DataParallel的普适性替代。


下面的代码展示了添加它到已存代码的必要修改:


# Wrap model in DistributedDataParallel (CUDA only for the moment)
model = torch.nn.parallel.DistributedDataParallel(model.cuda())
# Use a DistributedSampler to restrict each process to a distinct subset# of the dataset.
train_dataset = ...
train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)
train_loader = torch.utils.data.DataLoader(
train_dataset, batch_size=args.batch_size, num_workers=args.workers,
pin_memory=True, sampler=train_sampler)
for epoch in range(args.num_epochs):
# Use .set_epoch() method to reshuffle the dataset partition at every iteration
train_sampler.set_epoch(epoch)
# training loop...

我们可以从以下查看全部的ImageNet训练案例:

https://github.com/pytorch/examples/tree/master/imagenet

新型神经网络层:SpatialTransformers、WeightNorm、EmbeddingBag等



新特征

  • 引入forward_pre_hook,以在前向函数(forward function)被调用之前执行用户指定的闭包。
  • 易于获得非叶梯度(non-leaf gradient):目前,我们必须使用hooks获取和检查中间值的梯度。这种做法对于简单的检查并不方便。因此,我们引入了retain_grad。以下示例可充分解释该方法:
input = Variable(torch.rand(1, 3), requires_grad=True)
h1 = input * 3
out = (h1 * h1).sum()

h1.retain_grad()
out.backward()
print(h1.grad)# without calling retain_grad(), h1.grad is None
  • DataParallel now supports dicts as inputs
  • 现在,DataParallel作为输入支持dicts。

新型层


使用F.grid_sampleF.affine_grid的空间变换网络


论文《自正则化神经网络(Self-Normalizing Neural Networks)》提出nn.SeLUnn.AlphaDropout。论文《从卷积序列到序列学习(Convolutional Sequence to Sequence Learning)》提出nn.GLU(线性门控单元)。


通过torch.utils.weight_norm实现权值归一化(Weight Normalization)。


计算cross_entropy_lossnll_loss时,可以使用ignore_index参数来

忽略特定的目标索引(target indice)。这是实现掩码的一种廉价、有用的方式,你可以获取计算损失时所忽略的mask指数。


F.normalize实现各维度的重归一化。

F.upsamplenn.Upsample将多个上采样层合并成一个函数。该函数可实现第二次和第三次双线性/三线性/最近上采样(bilinear/trilinear/nearest upsampling)。

nn.EmbeddingBag:在构建词袋模型(bag-of-words model)时,在SumMean之后执行Embedding是一种常见做法。对于不同长度的序列,计算词袋嵌入涉及到掩码。我们提供一个单独的nn.EmbeddingBag,它能够更高效、快捷地计算词袋嵌入,尤其是不同长度的序列。

使用bce_with_logits的数值稳定的二元交叉熵参数(Binary Cross-Entropy loss)。


使用PoissonNLLLoss的带有目标泊松分布的负对数似然损失。


cosine_similarity:沿维度计算并返回x1和x2之间的余弦相似度(cosine similarity)。


训练工具



学习率调度器:torch.optim.lr_scheduler提供多个简单或聪明的方法来调整当前的学习率。在实验过程中,这些方法都很方便,i为用户可能想要做的事提供代理。



提供多种策略,可根据具体情况使用,详见

http://pytorch.org/docs/master/optim.html#how-to-adjust-learning-rate:

ReduceLROnPlateau, LambdaLR, StepLR, MultiStepLR, ExponentialLR

ConcatDataset是一种可以合并和连接两个单独的数据集的数据集元类。


torch 和 autograd 中的新特性

现在,所有reduce函数如sum和mean默认为挤压降维。例如,

torch.sum(torch.randn(10, 20))返回一个1D 张量。

x.shape,与numpy类似。一种等价于x.size()的便捷属性。

torch.matmul,与np.matmul类似。

bitwise and、or、xor、lshift、rshiftinverse、gesv、cumprod、atan2的autograd支持


通过关键字参数选项(keyword argument option)可获取无偏varstd。

torch.scatter_add - torch.scatter,除了重复指数的情况,这些值都可以汇总。

无给定参数时,torch.median与torch.sum类似,即它减少所有维度,并返回扁平张量(flattened Tensor)的单个median值。

masked_copy_被重命名为masked_scatter_(因为对masked_copy_有反对声)。

torch.manual_seed现在也对所有的CUDA设备播种。

你现在可以通过关键字参数torch.rand(1000, generator=gen)

指定随机数生成器对象(random number generator object)。



修正和小提升

  • 现在,当变量转为布尔类型时,会出现一个错误,例如:
b = Variable(torch.zeros(1))
if b[0]: # errors now
  • 修正了CUDA中qr分解的正确性问题。
  • 现已支持 IBM PowerPC64 平台。
  • 现在在运行时会检查 CuDNN 版本是否相同。
  • 改进了CUDA子进程中的错误消息。
  • 现在,Pytorch在CPU上可以更快地转置。
  • 改进了InstanceNorm上的错误信息。
  • 为各种例程添加了更多的参数检查,特别是BatchNorm和Convolution例程。
  • 在CPU后端时报告可以提出更好的错误信息。
  • 支持每台机器超过 8 块 GPU (仍有 CUDA p2p 限制)
  • 当访问不存在的属性时,错误消息获得了改进。
  • 变量的T()与Tensor一致。
  • 防止被零除时dropout p=1
  • 修复在非当前设备上共享CUDA张量的问题。
  • 当 BNε < 允许 CuDNN 值时,回退到THNN
  • 修正了当使用不同线程数量的MKL和OMP时线程破坏问题。
  • 在使用CuDNN RNN时提升了内存使用效率。
  • 使用负填充修正了ZeroPad2d后向的问题。
  • 加入了虚拟tensor.data属性,为用户提供可解释的错误消息。
  • 修正了Python3原位分配。
  • 在0-dim数组上调用from_numpy时生成错误。
  • 现在,空张量在多处理器共享时不会发生错误了。
  • 修复扩展张量的baddbmm
  • 现在,parallel_apply 可以接受任意输入了。
  • Tensor 和 Variable 中的关键字参数现在是一致的。
  • 修正了Magma不可用时的fix torch.inverse
  • 为ByteTensor添加逻辑非运算符。
  • 在分散/集中核心里加入设备声明。

重要问题和解决方法



值得注意的是,有两个重要的变化是无法向下兼容的:

  • Numpy风格的广播
  • 缩减函数,如sum(1)现在默认为keepdim=False

我们提供不同级别的Python警告,它们可以在代码变更、使用错误操作时提醒用户。


例子


以下是一个代码片段,你可以将其添加到脚本的顶部。


添加此代码会高亮不合适的代码,并生成警告。


修复代码,警告就会消除。


# insert this to the top of your scripts (usually main.py)import sys, warnings, traceback, torchdef warn_with_traceback(message, category, filename, lineno, file=None, line=None):
sys.stderr.write(warnings.formatwarning(message, category, filename, lineno, line))
traceback.print_stack(sys._getframe(2))
warnings.showwarning = warn_with_traceback; warnings.simplefilter('always', UserWarning);
torch.utils.backcompat.broadcast_warning.enabled = True
torch.utils.backcompat.keepdim_warning.enabled = True

在所有警告消失后,你就可以删除此代码片段了。


细节


下面将介绍三种不兼容的变化和例子。


使用(现已弃用)一维视图点态函数


早期版本的PyTorch允许点态函数在不同形状的张量上执行,只要每个张量中的元素数量和相等即可。旧的框架可以将每个张量视为一维来执行点操作。新版PyTorch支持广播。“一维”点操作被认为是不推荐的,并且在张量不可广播但具有相同数量元素的情况下会产生Python警告。


>>> torch.add(torch.ones(4), torch.ones(2,2))
__main__:1: UserWarning: self and other not broadcastable, but have the same
number of elements. Falling back to deprecated pointwise behavior.2222
[torch.FloatTensor of size 4]

在实现没有出现过的代码中进行广播


在两个张量尺寸不同的情况下,广播的引入可能导致向后不兼容的变化,但是可以广播并具有相同数量的元素,例如:


>>> torch.add(torch.ones(4,1), torch.randn(4))

可以预先生成一个特定尺寸的张量:torch.Size([4,1])


现在则生成张量尺寸:torch.Size([4,4])


为了帮助你识别代码中广播可能造成的后向不兼容情况,你可能需要将

torch.utils.backcompat.broadcast_warning.enabled

设置为True,这样就会在相应的问题发生时生成python警告。例如:


>>> torch.utils.backcompat.broadcast_warning.enabled=True>>> torch.add(torch.ones(4,1), torch.ones(4))
__main__:1: UserWarning: self and other do not have the same shape, but are broadcastable, and have the same number of elements.

注意:此设置会触发广播有效性(包含库代码)的警告,所以你或许会希望在迁移代码后关闭这个警告。


减少函数:使用Keepdim=False


如需在默认Keepdim参数时使用维度缩减获函数得警告,请将

torch.utils.backcompat.keepdim_warning.enabled

设置为True。例如:


>>> torch.sum(torch.ones(2,3), 1)
__main__:1: UserWarning: backwards compatibility: call to "sum" uses default value for keepdim which has changed default to False. Consider passing as kwarg.33
[torch.FloatTensor of size 2]

可能会出现torch.utils.backcompat.broadcast_warning.enabled

,这一警告会被有效代码出发,所以你肯定希望在代码迁移后屏蔽它。


同时需注意:用keepdim=False可以使你的已有代码和广播“可以工作”。例如:


# behavior with (old) keepdim=True, causes accidental broadcast>>> torch.add(torch.ones(4), torch.ones(4,4).sum(dim=1, keepdim=True))5  5  5  55  5  5  55  5  5  55  5  5  5
[torch.FloatTensor of size 4x4]
# new behavior with keepdim=False is equivalent to non-broadcasted result>>> torch.add(torch.ones(4), torch.ones(4,4).sum(dim=1, keepdim=False))5555
[torch.FloatTensor of size 4]


下载


入门PyTorch机器学习工程
1
暂无评论
暂无评论~