跳转到主要内容
博客

PyTorch 0.4.0 迁移指南

作者: 2018 年 4 月 22 日2024 年 11 月 16 日暂无评论

欢迎阅读 PyTorch 0.4.0 迁移指南。在此版本中,我们引入了许多令人兴奋的新功能和重要的错误修复,旨在为用户提供更好、更简洁的界面。在本指南中,我们将介绍从旧版本迁移现有代码时最重要的更改。

  • TensorsVariables 已合并
  • 支持 0 维(标量)Tensors
  • volatile 标志已弃用
  • dtypesdevices 和 NumPy 风格的 Tensor 创建函数
  • 编写与设备无关的代码
  • nn.Module 中子模块、参数和缓冲区名称的新边缘情况约束

合并 TensorVariable

torch.Tensortorch.autograd.Variable 现在是同一个类。更准确地说,torch.Tensor 能够跟踪历史记录并表现得像旧的 VariableVariable 包装仍然像以前一样工作,但返回的类型是 torch.Tensor 的对象。这意味着您不再需要在代码的每个地方都使用 Variable 包装器。

Tensortype() 已更改

另请注意,Tensor 的 type() 不再反映数据类型。请改用 isinstance()x.type()

>>> x = torch.DoubleTensor([1, 1, 1])
>>> print(type(x))  # was torch.DoubleTensor
"<class 'torch.Tensor'>"
>>> print(x.type())  # OK: 'torch.DoubleTensor'
'torch.DoubleTensor'
>>> print(isinstance(x, torch.DoubleTensor))  # OK: True
True

现在 autograd 何时开始跟踪历史记录?

requires_grad,作为 autograd 的核心标志,现在是 Tensors 的一个属性。以前用于 Variables 的相同规则现在适用于 Tensors;当操作的任何输入 Tensor 具有 requires_grad=True 时,autograd 开始跟踪历史记录。例如:

>>> x = torch.ones(1)  # create a tensor with requires_grad=False (default)
>>> x.requires_grad
False
>>> y = torch.ones(1)  # another tensor with requires_grad=False
>>> z = x + y
>>> # both inputs have requires_grad=False. so does the output
>>> z.requires_grad
False
>>> # then autograd won't track this computation. let's verify!
>>> z.backward()
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
>>>
>>> # now create a tensor with requires_grad=True
>>> w = torch.ones(1, requires_grad=True)
>>> w.requires_grad
True
>>> # add to the previous result that has require_grad=False
>>> total = w + z
>>> # the total sum now requires grad!
>>> total.requires_grad
True
>>> # autograd can compute the gradients as well
>>> total.backward()
>>> w.grad
tensor([ 1.])
>>> # and no computation is wasted to compute gradients for x, y and z, which don't require grad
>>> z.grad == x.grad == y.grad == None
True

操作 requires_grad 标志

除了直接设置属性外,您还可以使用 my_tensor.requires_grad_() 更改此标志,或者,如上例所示,在创建时将其作为参数传入(默认为 False),例如:

>>> 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 获取底层 Tensor 的主要方式。合并后,调用 y = x.data 仍然具有相似的语义。因此,y 将是一个与 x 共享相同数据、与 x 的计算历史无关且 requires_grad=FalseTensor

但是,.data 在某些情况下可能不安全。对 x.data 的任何更改都不会被 autograd 跟踪,并且如果反向传播中需要 x,则计算的梯度将不正确。一个更安全的替代方法是使用 x.detach(),它也返回一个与 requires_grad=False 共享数据的 Tensor,但如果反向传播中需要 x,它的原地更改将被 autograd 报告。

以下是 .datax.detach() 之间区别的示例(以及为什么我们通常推荐使用 detach)。

如果您使用 Tensor.detach(),梯度计算将保证是正确的。

>>> a = torch.tensor([1,2,3.], requires_grad = True)
>>> out = a.sigmoid()
>>> c = out.detach()
>>> c.zero_()
tensor([ 0.,  0.,  0.])

>>> out  # modified by c.zero_() !!
tensor([ 0.,  0.,  0.])

>>> out.sum().backward()  # Requires the original value of out, but that was overwritten by c.zero_()
RuntimeError: one of the variables needed for gradient computation has been modified by an

但是,使用 Tensor.data 可能不安全,并且当计算梯度需要张量但其被原地修改时,很容易导致不正确的梯度。

>>> a = torch.tensor([1,2,3.], requires_grad = True)
>>> out = a.sigmoid()
>>> c = out.data
>>> c.zero_()
tensor([ 0.,  0.,  0.])

>>> out  # out  was modified by c.zero_()
tensor([ 0.,  0.,  0.])

>>> out.sum().backward()
>>> a.grad  # The result is very, very wrong because `out` changed!
tensor([ 0.,  0.,  0.])

支持 0 维(标量)张量

以前,对 Tensor 向量(1 维张量)进行索引会得到一个 Python 数字,但对 Variable 向量进行索引会(不一致地!)得到一个大小为 (1,) 的向量!约简函数也存在类似的行为,例如 tensor.sum() 会返回一个 Python 数字,但 variable.sum() 会返回一个大小为 (1,) 的向量。

幸运的是,此版本在 PyTorch 中引入了对适当标量(0 维张量)的支持!标量可以使用新的 torch.tensor 函数创建(稍后将详细解释;目前只需将其视为 NumPy 的 numpy.array 的 PyTorch 等效项)。现在您可以执行以下操作:

>>> torch.tensor(3.1416)         # create a scalar directly
tensor(3.1416)
>>> torch.tensor(3.1416).size()  # scalar is 0-dimensional
torch.Size([])
>>> torch.tensor([3]).size()     # compare to a vector of size 1
torch.Size([1])
>>>
>>> vector = torch.arange(2, 6)  # this is a vector
>>> vector
tensor([ 2.,  3.,  4.,  5.])
>>> vector.size()
torch.Size([4])
>>> vector[3]                    # indexing into a vector gives a scalar
tensor(5.)
>>> vector[3].item()             # .item() gives the value as a Python number
5.0
>>> mysum = torch.tensor([2, 3]).sum()
>>> mysum
tensor(5)
>>> mysum.size()
torch.Size([])

累积损失

考虑广泛使用的模式 total_loss += loss.data[0]。在 0.4.0 之前,loss 是一个包裹着大小为 (1,) 的张量的 Variable,但在 0.4.0 中,loss 现在是一个标量,并且有 0 维。对标量进行索引没有意义(现在会给出警告,但在 0.5.0 中将是一个硬错误)。使用 loss.item() 从标量获取 Python 数字。

请注意,如果您在累积损失时不转换为 Python 数字,您可能会发现程序中的内存使用量增加。这是因为上述表达式的右手边以前是一个 Python 浮点数,而现在它是一个零维张量。因此,总损失正在累积张量及其梯度历史,这可能会使大型自动梯度图保留的时间比必要的时间长得多。

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)  # this can also be used as a function
>>> y = x * 2
>>> y.requires_grad
True
>>> torch.set_grad_enabled(False)
>>> y = x * 2
>>> y.requires_grad
False

dtypesdevices 和 NumPy 风格的创建函数

在 PyTorch 之前的版本中,我们通常将数据类型(例如 float 与 double)、设备类型(cpu 与 cuda)和布局(密集与稀疏)一起指定为“张量类型”。例如,torch.cuda.sparse.DoubleTensor 是表示 double 数据类型、存在于 CUDA 设备上并具有COO 稀疏张量布局的 Tensor 类型。

在此版本中,我们引入了 torch.dtypetorch.devicetorch.layout 类,以允许通过 NumPy 风格的创建函数更好地管理这些属性。

torch.dtype

以下是可用 torch.dtype(数据类型)及其相应张量类型的完整列表。

数据类型 torch.dtype张量类型
32 位浮点torch.float32torch.floattorch.*.FloatTensor
64 位浮点torch.float64torch.doubletorch.*.DoubleTensor
16 位浮点torch.float16torch.halftorch.*.HalfTensor
8 位整数(无符号)torch.uint8torch.*.ByteTensor
8 位整数(有符号)torch.int8torch.*.CharTensor
16 位整数(有符号)torch.int16torch.shorttorch.*.ShortTensor
32 位整数(有符号)torch.int32torch.inttorch.*.IntTensor
64 位整数(有符号)torch.int64torch.longtorch.*.LongTensor

张量的数据类型可以通过其 dtype 属性访问。

torch.device

一个 torch.device 包含一个设备类型('cpu''cuda')以及可选的设备类型序号 (id)。它可以通过 torch.device('{device_type}')torch.device('{device_type}:{device_ordinal}') 初始化。

如果不存在设备序号,则表示该设备类型的当前设备;例如,torch.device('cuda') 等效于 torch.device('cuda:X'),其中 Xtorch.cuda.current_device() 的结果。

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

torch.layout

torch.layout 表示 Tensor 的数据布局。目前支持 torch.strided(密集张量,默认)和 torch.sparse_coo(COO 格式的稀疏张量)。

张量的布局可以通过其 layout 属性访问。

创建张量

创建 Tensor 的方法现在也接受 dtypedevicelayoutrequires_grad 选项,以指定返回的 Tensor 所需的属性。例如:

>>> 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 是新添加的张量创建方法之一。它接受各种类似数组的数据,并将包含的值复制到一个新的 Tensor 中。如前所述,torch.tensor 是 NumPy 的 numpy.array 构造函数的 PyTorch 等效项。与 torch.*Tensor 方法不同,您也可以通过这种方式创建零维 Tensor(即标量)(单个 Python 数字在 torch.*Tensor 方法中被视为大小)。此外,如果没有给出 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)               # scalar
tensor(1)
>>> torch.tensor([1, 2.3]).dtype  # type inferece
torch.float32
>>> torch.tensor([1, 2]).dtype    # type inferece
torch.int64

我们还添加了更多的张量创建方法。其中一些具有 torch.*_like 和/或 tensor.new_* 变体。

  • torch.*_like 接受一个输入 Tensor 而不是形状。它默认返回一个与输入 Tensor 具有相同属性的 Tensor,除非另有指定。
>>> 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)
  • tensor.new_* 也可以创建与 tensor 具有相同属性的 Tensors,但它总是接受一个形状参数。
 >>> 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))。

名称返回的 Tensortorch.*_like 变体tensor.new_* 变体
torch.empty未初始化内存
torch.zeros全零
torch.ones全一
torch.full填充给定值
torch.randi.i.d. 连续均匀分布 [0, 1) 
torch.randni.i.d. Normal(0, 1) 
torch.randinti.i.d. 离散均匀分布在给定范围内 
torch.randperm{0, 1, ..., n - 1} 的随机排列  
torch.tensor从现有数据(列表、NumPy ndarray 等)复制 
torch.from_numpy*从 NumPy ndarray(共享存储,不复制)  
torch.arangetorch.rangetorch.linspace给定范围内的均匀间隔值  
torch.logspace给定范围内的对数间隔值  
torch.eye单位矩阵  

*: torch.from_numpy 只接受 NumPy ndarray 作为其输入参数。

编写与设备无关的代码

以前版本的 PyTorch 难以编写与设备无关的代码(即无需修改即可在启用 CUDA 和仅限 CPU 的机器上运行的代码)。

PyTorch 0.4.0 通过两种方式使其更容易:

  • 张量的 device 属性为所有张量提供 torch.deviceget_device 仅适用于 CUDA 张量)。
  • TensorsModulesto 方法可用于轻松将对象移动到不同的设备(而不是根据上下文调用 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)

nn.Module 中子模块、参数和缓冲区的名称的新边缘情况约束

module.add_module(name, value)module.add_parameter(name, value)module.add_buffer(name, value) 中不再允许 name 为空字符串或包含 ".",因为此类名称可能导致 state_dict 中的数据丢失。如果您正在加载包含此类名称的模块的检查点,请在加载之前更新模块定义并修补 state_dict

代码示例(综合运用)

为了了解 0.4.0 中推荐的总体更改,让我们快速看一个 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:
        ...

感谢您的阅读!更多详情请参阅我们的文档发行说明

祝您 PyTorch 愉快!