PyTorch 0.4 升级指南

Posted Yan_Joy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyTorch 0.4 升级指南相关的知识,希望对你有一定的参考价值。

4月25日,PyTorch团队正式发布了0.4.0的release版本。这是在与caffe2合并后的首个稳定版本。其中核心的变化有:

  • Tensor/Variable合并
  • 零维张量(标量)
  • volatile标志的弃用
  • dtypes,devices和Numpy型Tensor的创建函数
  • 写设备无关的代码

下面对其中的升级注意的问题进行说明。

原文参考:PyTorch 0.4.0 Migration Guide

合并TensorVariable


torch.Tensortorch.autograd.Variable目前是同一类了。更确切地说,是torch.Tensor有了像Variable一样追踪历史与行为的能力。Variable像之前一样工作,但返回类型为torch.Tensor的对象。这意味着你不用再像之前在代码中到处使用Variable wrapper。

type()Tensor作用发生改变


type()函数不再反映Tensor类的具体数据格式。可以使用isinstance() 或者x.type() 代替。

>>> x = torch.DoubleTensor([1, 1, 1])
>>> print(type(x))  # 原本会输出 torch.DoubleTensor
"<class 'torch.Tensor'>" # 但现在只会输出是一个torch.Tensor
>>> print(x.type())  # 正确: 'torch.DoubleTensor'
'torch.DoubleTensor'
>>> print(isinstance(x, torch.DoubleTensor))  # 正确: True
True

什么时候autograd开始追踪历史?


autograd的核心标志位:requires_grad,目前是Tensor的一个属性。之前应用于Variables的规则现在也适用于Tensors;因此autograd在当任何输入的Tensor操作的requires_grad=True时开始追踪历史。

>>> x = torch.ones(1)  # 创建一个requires_grad属性为False的Tensor (默认属性)
>>> x.requires_grad
False # 默认为FALSE
>>> y = torch.ones(1)  # 另一个requires_grad属性为False的Tensor
>>> z = x + y
>>> # 两个输入Tensor的requires_grad属性都是False因此其输出的Tensor requires_grad属性也为False
>>> z.requires_grad
False
>>> # 接下来尝试验证追踪计算(反向传播)
>>> z.backward()
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
>>>
>>> # 现在创建一个requires_grad属性为True的Tensor
>>> w = torch.ones(1, requires_grad=True)
>>> w.requires_grad
True
>>> # 与另一个requires_grad属性为False的Tensor相加
>>> total = w + z
>>> # 这样输出的requires_grad属性为True
>>> total.requires_grad
True
>>> # autograd 便可以计算其梯度
>>> total.backward()
>>> w.grad
tensor([ 1.])
>>> # 对于不需要计算grad的Tensor,其.grad 为None,这样也节省了计算资源
>>> z.grad == x.grad == y.grad == None
True

requires_grad标志位的操作

除了直接设置requires_grad为真或假,也可以使用[my_tensor.requires_grad_()](http://pytorch.org/docs/stable/tensors.html),或者在创建Tensor时作为参数传入:

>>> existing_tensor.requires_grad_()
>>> existing_tensor.requires_grad
True
>>> my_tensor = torch.zeros(3, 4, requires_grad=True)
>>> my_tensor.requires_grad
True

至于.data


.data是从Variable中获得张量的方法。在这次合并后,调用y = x.data依然有相同的效果。y将是一个与x共享相同数据的Tensor,不包括x的历史,并且requires_grad=False

但是在某些情况下.data可能不够安全。对x.data的任何修改都不会被autograd记录跟踪。如果x在反向传播中使用,则会导致梯度的不正确。另一种更安全的方法是使用x.detach()。它还返回一个与x共享数据且requires_grad = False的Tensor,但如果在后向需要xautograd将记录它的就地(in-place)更改操作。

对0-维张量(标量)的支持


之前对Tensor矢量(1维)的索引需要给Python一个数字,但对Variable矢量的索引需要一个大小为(1,)的矢量。类似的情况也出现在归约函数中。如tensor.sum()将会返回一个Python的数,但variable.sum()将会返回大小为(1,)的矢量。

幸运的是在这个版本中加入了对标量(0维张量)的支持。标量可以由torch.tensor创建(可以看做是numpy.array):

>>> torch.tensor(3.1416)         # 直接创建标量
tensor(3.1416)
>>> torch.tensor(3.1416).size()  # 标量是0维的
torch.Size([])
>>> torch.tensor([3]).size()     # 与大小为1的矢量对比
torch.Size([1])
>>>
>>> vector = torch.arange(2, 6)  # 这是一个矢量
>>> vector
tensor([ 2.,  3.,  4.,  5.])
>>> vector.size()
torch.Size([4])
>>> vector[3]                    # 通过标量对矢量进行索引
tensor(5.)
>>> vector[3].item()             # .item() 会以Python数的形式给出值
5.0
>>> mysum = torch.tensor([2, 3]).sum()
>>> mysum
tensor(5)
>>> mysum.size()
torch.Size([])

损失的累计


考虑广泛使用的模式:total_loss += loss.data[0]。在0.4.0之前,损失是一个以Variable包装的大小为(1,)的张量。但是在0.4.0中,损失的维度为0(标量),因此再使用索引是没有意义的(目前会给出警告,但在0.5.0中会成为错误)。使用loss.item()来从标量中获得Python数字。

请注意:如果在累计损失时没有转为Python数字,可能会发现内存使用量的增加。这是因为上面表达式的结果之前是一个Python浮点数,而现在是一个0维张量。因此总损失会累加张量和梯度历史,从而产生了一个很大的autograd图。

volatile标志位的废除


volatile标志位现在已经废除并不会产生效果。之前任何涉及volatile=TrueVariable都不会被autograd所追踪。目前这已经被更灵活的上下午管理器如 torch.no_grad(), torch.set_grad_enabled(grad_mode),等所替代。

>>> x = torch.zeros(1, requires_grad=True)
>>> with torch.no_grad():
...     y = x * 2
>>> y.requires_grad
False
>>>
>>> is_train = False
>>> with torch.set_grad_enabled(is_train):
...     y = x * 2
>>> y.requires_grad
False
>>> torch.set_grad_enabled(True)  # 同样可作为函数使用
>>> y = x * 2
>>> y.requires_grad
True
>>> torch.set_grad_enabled(False)
>>> y = x * 2
>>> y.requires_grad
False

dtypes,devices和Numpy型Tensor的创建函数


在以前的版本中,我们使用特定的数据类型(如floatdouble),设备类型(如cpucuda)以及布局(densesparse)作为“Tensor的类型”。例如,torch.cuda.sparse.DoubleTensor代表了使用double数据类型,运行于CUDA设备,采用COO稀疏表示的张量。

在这个版本中,我们介绍了torch.dtypetorch.devicetorch.layout类,以便通过NumPy风格的创建函数更好地管理这些属性。

torch.dtype


下表为可用的torch.dtypes(数据类型)及相应张量类型的完整列表:

Tensordtype可通过其dtype属性访问。

torch.device


torch.device包含设备类型(cpucuda)和可选设备序号(id)。它可以通过torch.device('device_type')torch.device('device_type:device_ordinal')来初始化。

如果设备序号不存在,则表示目前可用的设备;如torch.device('cuda')等同于torch.device('cuda:X'),其中X是torch.cuda.current_device()的结果。

张量的设备可以通过其设备属性访问。

torch.layout


torch.layout表示张量的数据分布。 目前支持torch.strided(稠密张量,默认值)和torch.sparse_coo(具有COO格式的稀疏张量)。

创建Tensor


创建张量的方法现在还可以使用dtypedevicelayoutrequires_grad选项,在返回的张量中指定所需的属性。例如:

>>> device = torch.device("cuda:1")
>>> x = torch.randn(3, 3, dtype=torch.float64, device=device)
tensor([[-0.6344,  0.8562, -1.2758],
        [ 0.8414,  1.7962,  1.0589],
        [-0.1369, -1.0462, -0.4373]], dtype=torch.float64, device='cuda:1')
>>> x.requires_grad  # default is False
False
>>> x = torch.zeros(3, requires_grad=True)
>>> x.requires_grad
True

torch.tensor(data, ...)

torch.tensor(data, ...)是新添加的张量创建方法之一。它采用各种类似数组的数据并将包含的值复制到新的张量中。 如前所述,torch.tensor是PyTorch中与NumPy的numpy.array等价的类型。 与torch.*Tensor方法不同,也可以通过这种方式(单个python数字在该对象中被视为Size 在torch.*Tensor方法中)创建0维张量(标量)。 此外,如果没有给出dtype参数,它会根据数据推断出合适的dtype。 这是从现有数据(如Python列表)创建张量的推荐方法。例如:

>>> cuda = torch.device("cuda")
>>> torch.tensor([[1], [2], [3]], dtype=torch.half, device=cuda)
tensor([[ 1],
        [ 2],
        [ 3]], device='cuda:0')
>>> torch.tensor(1)               # 标量
tensor(1)
>>> torch.tensor([1, 2.3]).dtype  # type inferece
torch.float32
>>> torch.tensor([1, 2]).dtype    # type inferece
torch.int64

除此之外,也添加了更多的创建方法。其中有torch.*_liketensor.new_*的变体。

  1. torch.*_like除非另有说明,它默认返回一个与输入张量相同属性的张量:

    >>> x = torch.randn(3, dtype=torch.float64)
    >>> torch.zeros_like(x)
    tensor([ 0.,  0.,  0.], dtype=torch.float64)
    >>> torch.zeros_like(x, dtype=torch.int)
    tensor([ 0,  0,  0], dtype=torch.int32)
  2. tensor.new_*也可以创建与张量具有相同属性的张量,但它总是需要一个形状参数:

    >>> x = torch.randn(3, dtype=torch.float64)
    >>> x.new_ones(2)
    tensor([ 1.,  1.], dtype=torch.float64)
    >>> x.new_ones(4, dtype=torch.int)
    tensor([ 1,  1,  1,  1], dtype=torch.int32)

要指定所需的形状,在大多数情况下,可以使用元组(例如 torch.zeros((2,3)))或可变参数(例如torch.zeros(2,3))。

写设备无关的代码


先前版本的PyTorch使得编写设备不可知的代码变得困难(即:可以在没有修改的情况下在CUDA-enabled的和CPU-only的计算机上运行)。

PyTorch 0.4.0有两种更简单的方法:

  • Tensordevice属性为所有张量提供了torch.deviceget_device仅适用于CUDA张量)
  • Tensors和Modules的to方法可用于将对象轻松移动到不同的设备(而不必根据上下文调用cpu()或`cuda())

推荐的模式如下:

# at beginning of the script
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

...

# then whenever you get a new Tensor or Module
# this won't copy if they are already on the desired device
input = data.to(device)
model = MyModule(...).to(device)

代码样例

下面是0.3.1与0.4.0的对比:

0.3.1

model = MyRNN()
if use_cuda:
    model = model.cuda()

# train
total_loss = 0
for input, target in train_loader:
    input, target = Variable(input), Variable(target)
    hidden = Variable(torch.zeros(*h_shape))  # init hidden
    if use_cuda:
        input, target, hidden = input.cuda(), target.cuda(), hidden.cuda()
    ...  # get loss and optimize
    total_loss += loss.data[0]

# evaluate
for input, target in test_loader:
    input = Variable(input, volatile=True)
    if use_cuda:
        ...
    ...

0.4.0

# torch.device object used throughout this script
device = torch.device("cuda" if use_cuda else "cpu")

model = MyRNN().to(device)

# train
total_loss = 0
for input, target in train_loader:
    input, target = input.to(device), target.to(device)
    hidden = input.new_zeros(*h_shape)  # has the same device & dtype as `input`
    ...  # get loss and optimize
    total_loss += loss.item()           # get Python number from 1-element Tensor

# evaluate
with torch.no_grad():                   # operations inside don't track history
    for input, target in test_loader:
        ...

感谢阅读,这次的更新很大程度是简化了数据结构,并让创建Tensor更加便捷。但要注意0维张量的出现会在loss累计中出现普遍的问题。在平时的编写代码过程中,还是要多参考手册。

以上是关于PyTorch 0.4 升级指南的主要内容,如果未能解决你的问题,请参考以下文章

我更改了标量类型 float 的预期对象,但在 Pytorch 中仍然得到 Long

[pytorch]pytorch loss function 总结

年度最大更新!PyTorch 0.4:完全改变API,官方支持Windows

超简单!pytorch入门教程:Autograd

在 MESS 中使用 pytorch 的 Tensorboard

Keras:为啥损失函数必须为每个批次项目返回一个标量,而不仅仅是一个标量?