欢迎阅读 PyTorch 0.4.0 迁移指南。在此版本中,我们引入了许多激动人心的新功能和关键的错误修复,旨在为用户提供更好、更简洁的接口。在本指南中,我们将涵盖将现有代码迁移至此版本时最重要的变更点:
Tensors(张量)与Variables(变量)合并- 支持 0 维(标量)
Tensors - 弃用
volatile标志 dtypes、devices以及 Numpy 风格的Tensor创建函数- 编写设备无关的代码
nn.Module中子模块、参数和缓冲器命名的新边缘情况约束
合并 Tensor 和 Variable 类
torch.Tensor 和 torch.autograd.Variable 现在是同一个类。更准确地说,torch.Tensor 能够跟踪历史记录并表现得像旧版的 Variable;Variable 的封装仍然可以像以前一样工作,但会返回一个 torch.Tensor 类型的对象。这意味着您不再需要在代码中到处使用 Variable 封装器。
Tensor 的 type() 已更改
还要注意,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_() 对该标志进行“原地”(in-place)更改,或者像上面的示例一样,在创建时通过参数传递(默认为 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 共享相同数据的 Tensor,它与 x 的计算历史无关,并且 requires_grad=False。
然而,.data 在某些情况下是不安全的。对 x.data 的任何更改都不会被 autograd 跟踪。如果 x 在反向传播中被需要,那么计算出的梯度将是不正确的。更安全的替代方案是使用 x.detach(),它同样返回一个与 x 共享数据且 requires_grad=False 的 Tensor,但如果 x 在反向传播中被需要,其原地更改将会被 autograd 报告。
以下是一个示例,展示了 .data 和 x.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 维(标量)Tensors
以前,索引 Tensor 向量(一维张量)会得到一个 Python 数字,但索引 Variable 向量(不一致!)会得到一个大小为 (1,) 的向量!缩减函数也存在类似行为,例如 tensor.sum() 会返回一个 Python 数字,但 variable.sum() 会返回一个大小为 (1,) 的向量。
幸运的是,此版本在 PyTorch 中引入了真正的标量(0 维张量)支持!标量可以使用新的 torch.tensor 函数创建(稍后会详细解释;现在只需将其视为 PyTorch 对应于 numpy.array 的函数)。现在您可以执行如下操作:
>>> 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 浮点数,而现在是一个 0 维 Tensor。因此,总损失累积的是 Tensors 及其梯度历史,这可能会导致大型 autograd 计算图的保留时间比预期的长得多。
弃用 volatile 标志
volatile 标志现已弃用且不再起作用。以前,任何涉及 volatile=True 的 Variable 的计算都不会被 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
dtypes、devices 和 NumPy 风格的创建函数
在以前的 PyTorch 版本中,我们将数据类型(例如 float vs double)、设备类型(cpu vs cuda)和布局(dense vs sparse)统一指定为“张量类型”。例如,torch.cuda.sparse.DoubleTensor 是代表 double 数据类型、驻留在 CUDA 设备上并具有 COO 稀疏张量布局的 Tensor 类型。
在此版本中,我们引入了 torch.dtype、torch.device 和 torch.layout 类,以便通过 NumPy 风格的创建函数更好地管理这些属性。
torch.dtype
以下是可用 torch.dtype(数据类型)及其相应张量类型的完整列表。
| 数据类型 | torch.dtype | 张量类型 |
|---|---|---|
| 32 位浮点数 | torch.float32 或 torch.float | torch.*.FloatTensor |
| 64 位浮点数 | torch.float64 或 torch.double | torch.*.DoubleTensor |
| 16 位浮点数 | torch.float16 或 torch.half | torch.*.HalfTensor |
| 8 位整数(无符号) | torch.uint8 | torch.*.ByteTensor |
| 8 位整数(有符号) | torch.int8 | torch.*.CharTensor |
| 16 位整数(有符号) | torch.int16 或 torch.short | torch.*.ShortTensor |
| 32 位整数(有符号) | torch.int32 或 torch.int | torch.*.IntTensor |
| 64 位整数(有符号) | torch.int64 或 torch.long | torch.*.LongTensor |
张量的 dtype 可以通过其 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'),其中 X 是 torch.cuda.current_device() 的结果。
张量的设备可以通过其 device 属性访问。
torch.layout
torch.layout 表示 Tensor 的数据布局。目前支持 torch.strided(稠密张量,默认)和 torch.sparse_coo(COO 格式的稀疏张量)。
张量的布局可以通过其 layout 属性访问。
创建 Tensors
创建 Tensor 的方法现在也接受 dtype、device、layout 和 requires_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 是 PyTorch 对应于 NumPy 的 numpy.array 构造函数。与 torch.*Tensor 方法不同,您也可以通过这种方式创建 0 维 Tensor(即标量)(单个 Python 数字在 torch.*Tensor 方法中被视为 Size)。此外,如果未提供 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))。
| 名称 | 返回的 Tensor | torch.*_like 变体 | tensor.new_* 变体 |
|---|---|---|---|
torch.empty | 未初始化的内存 | ✔ | ✔ |
torch.zeros | 全为 0 | ✔ | ✔ |
torch.ones | 全为 1 | ✔ | ✔ |
torch.full | 填充给定值 | ✔ | ✔ |
torch.rand | 独立同分布的连续均匀分布 [0, 1) | ✔ | |
torch.randn | 独立同分布的 Normal(0, 1) | ✔ | |
torch.randint | 给定范围内的独立同分布离散均匀分布 | ✔ | |
torch.randperm | {0, 1, ..., n - 1} 的随机排列 | ||
torch.tensor | 从现有数据(列表、NumPy ndarray 等)复制 | ✔ | |
torch.from_numpy* | 来自 NumPy ndarray(共享存储而不复制) | ||
torch.arange、torch.range 和 torch.linspace | 给定范围内的均匀间隔值 | ||
torch.logspace | 给定范围内的对数间隔值 | ||
torch.eye | 单位矩阵 |
*: torch.from_numpy 仅接收 NumPy ndarray 作为其输入参数。
编写设备无关的代码
以前版本的 PyTorch 难以编写设备无关的代码(即无需修改即可同时在支持 CUDA 和仅 CPU 的机器上运行)。
PyTorch 0.4.0 通过两种方式使其变得更容易:
- Tensor 的
device属性为所有 Tensor 提供了 torch.device(get_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)
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 使用愉快!