PyTorch在前两天官方发布了0.4.0版本。这个版本与之前相比,API发生了较大的变化,所以官方也出了一个转换指导,这篇博客是这篇指导的中文翻译版。归结起来,对我们代码影响最大的地方主要有:
Tensor
和Variable
合并,autograd
的机制有所不同,变得更简单,使用requires_grad
和上下文相关环境管理。- Numpy风格的
Tensor
构建。 - 提出了
device
,更简单地在cpu和gpu中移动数据。
概述
在0.4.0版本中,PyTorch引入了许多令人兴奋的新特性和bug fixes。为了方便以前版本的使用者转换到新的版本,我们编写了此指导,主要包括以下几个重要的方面:
Tensors
和Variables
已经merge到一起了- 支持0维的Tensor(即标量scalar)
- 弃用了
volatile
标志 dtypes
,devices
, 和 Numpy 风格的 Tensor构造函数- (更好地编写)设备无关代码
下面分条介绍。
Tensor
和 Variable
合并
在PyTorch以前的版本中,Tensor
类似于numpy
中的ndarray
,只是对多维数组的抽象。为了能够使用自动求导机制,必须使用Variable
对其进行包装。而现在,这两个东西已经完全合并成一个了,以前Variable
的使用情境都可以使用Tensor
。所以以前训练的时候总要额外写的warpping语句用不到了。
1 | for data, target in data_loader: |
Tensor
的类型type()
以前我们可以使用type()
获取Tensor
的data type(FloatTensor,LongTensor等)。现在需要使用x.type()
获取类型或isinstance()
判别类型。
1 | 1, 1, 1]) x = torch.DoubleTensor([ |
autograd
现在如何追踪计算图的历史
Tensor
和Variable
的合并,简化了计算图的构建,具体规则见本条和以下几条说明。
requires_grad
, 这个autograd
中的核心标志量,现在成了Tensor
的属性。之前的Variable
使用规则可以同样应用于Tensor
,autograd
自动跟踪那些至少有一个input的requires_grad==True
的计算节点构成的图。
1 | 1) ## 默认requires_grad = False x = torch.ones( |
操作 requires_grad
标志
除了直接设置这个属性,你可以使用my_tensor.requires_grad_()
就地修改这个标志(还记得吗,以_
结尾的方法名表示in-place的操作)。或者就在构造的时候传入此参数。
1 | existing_tensor.requires_grad_() |
.data
怎么办?What about .data?
原来版本中,对于某个Variable
,我们可以通过x.data
的方式获取其包装的Tensor
。现在两者已经merge到了一起,如果你调用y = x.data
仍然和以前相似,y
现在会共享x
的data,并与x
的计算历史无关,且其requires_grad
标志为False
。
然而,.data
有的时候可能会成为代码中不安全的一个点。对x.data
的任何带动都不会被aotograd
跟踪。所以,当做反传的时候,计算的梯度可能会不对,一种更安全的替代方法是调用x.detach()
,仍然会返回一个共享x
data的Tensor,且requires_grad=False
,但是当x
需要bp的时候,会报告那些in-place的操作。
However, .data can be unsafe in some cases. Any changes on x.data wouldn’t be tracked by autograd, and the computed gradients would be incorrect if x is needed in a backward pass. A safer alternative is to use x.detach(), which also returns a Tensor that shares data with requires_grad=False, but will have its in-place changes reported by autograd if x is needed in backward.
这里有些绕,可以看下下面的示例代码:
1 | # 一个简单的计算图:y = sum(x**2) |
支持0维(scalar)的Tensor
原来的版本中,对Tensor vector(1D Tensor)做索引得到的结果是一个python number,但是对一个Variable vector来说,得到的就是一个size(1,)
的vector!对于reduction function(如torch.sum
,torch.max
)也有这样的问题。
所以我们引入了scalar(0D Tensor)。它可以使用torch.tensor()
函数来创建,现在你可以这样做:
1 | 3.1416) # 直接创建scalar torch.tensor( |
累积losses
我们在训练的时候,经常有这样的用法:total_loss += loss.data[0]
。loss
通常都是由损失函数计算出来的一个标量,也就是包装了(1,)
大小Tensor
的Variable
。在新的版本中,loss
则变成了0D的scalar。对一个scalar做indexing是没有意义的,应该使用loss.item()
获取python number。
注意,如果你在做累加的时候没有转换为python number,你的程序可能会出现不必要的内存占用。因为autograd
会记录调用过程,以便做反向传播。所以,你现在应该写成 total_loss += loss.item()
。
弃用volatile
标志
volatile
标志被弃用了,现在没有任何效果。以前的版本中,一个设置volatile=True
的Variable
表明其不会被autograd
追踪。现在,被替换成了一个更灵活的上下文管理器,如torch.no_grad()
,torch.set_grad_enable(grad_mode)
等。
1 | 1, requires_grad=True) x = torch.zeros( |
dtypes
, devices
和NumPy风格的构建函数
以前的版本中,我们需要以”tensor type”的形式给出对data type(如float
或double
),device type(如cpu或gpu)以及layout(dense或sparse)的限定。例如,torch.cuda.sparse.DoubleTensor
用来构造一个data type是double
,在GPU上以及sparse的tensor。
现在我们引入了torch.dtype
,torch.device
和torch.layout
来更好地使用Numpy风格的构建函数。
torch.dtype
下面是可用的 torch.dtypes
(data types) 和它们对应的tensor types。可以使用x.dtype
获取。
data type | torch.dtype | Tensor types |
32-bit floating point | torch.float32 or torch.float | torch.*.FloatTensor |
64-bit floating point | torch.float64 or torch.double | torch.*.DoubleTensor |
16-bit floating point | torch.float16 or torch.half | torch.*.HalfTensor |
8-bit integer (unsigned) | torch.uint8 | torch.*.ByteTensor |
8-bit integer (signed) | torch.int8 | torch.*.CharTensor |
16-bit integer (signed) | torch.int16 or torch.short | torch.*.ShortTensor |
32-bit integer (signed) | torch.int32 or torch.int | torch.*.IntTensor |
64-bit integer (signed) | torch.int64 or torch.long | torch.*.LongTensor |
torch.device
torch.device
包含了device type(如cpu或cuda)和可能的设备id。使用torch.device('{device_type}')
或torch.device('{device_type}:{device_ordinal}')
的方式来初始化。
如果没有指定device ordinal
,那么默认是当前的device。例如,torch.device('cuda')
相当于torch.device('cuda:X')
,其中,X
是torch.cuda.current_device()
的返回结果。
使用x.device
来获取。
torch.layout
torch.layout
代表了Tensor
的data layout。 目前支持的是torch.strided
(dense,也是默认的) 和 torch.sparse_coo
(COOG格式的稀疏tensor)。
使用x.layout
来获取。
创建Tensor
(Numpy风格)
你可以使用dtype
,device
,layout
和requires_grad
更好地控制Tensor
的创建。
1 | "cuda:1") device = torch.device( |
torch.tensor(data, ...)
torch.tensor
是新加入的Tesnor
构建函数。它接受一个”array-like”的参数,并将其value copy到一个新的Tensor
中。可以将它看做numpy.array
的等价物。不同于torch.*Tensor
方法,你可以创建0D的Tensor(也就是scalar)。此外,如果dtype
参数没有给出,它会自动推断。推荐使用这个函数从已有的data,如Python List创建Tensor
。
1 | "cuda") cuda = torch.device( |
我们还加了更多的Tensor
创建方法。其中有一些torch.*_like
,tensor.new_*
这样的形式。
torch.*_like
的参数是一个input tensor, 它返回一个相同属性的tensor,除非有特殊指定。1
2
3
4
53, dtype=torch.float64) x = torch.randn(
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_*
类似,不过它通常需要接受一个指定shape的参数。1
2
3
4
53, dtype=torch.float64) x = torch.randn(
2) x.new_ones(
tensor([ 1., 1.], dtype=torch.float64)
4, dtype=torch.int) x.new_ones(
tensor([ 1, 1, 1, 1], dtype=torch.int32)
为了指定shape参数,你可以使用tuple
,如torch.zeros((2, 3))
(Numpy风格)或者可变数量参数torch.zeros(2, 3)
(以前的版本只支持这种)。
Name | Returned Tensor | torch.*_likevariant | tensor.new_*variant |
torch.empty | unintialized memory | ✔ | ✔ |
torch.zeros | all zeros | ✔ | ✔ |
torch.ones | all ones | ✔ | ✔ |
torch.full | filled with a given value | ✔ | ✔ |
torch.rand | i.i.d. continuous Uniform[0, 1) | ✔ | |
torch.randn | i.i.d. Normal(0, 1) | ✔ | |
torch.randint | i.i.d. discrete Uniform in given range | ✔ | |
torch.randperm | random permutation of {0, 1, ..., n - 1} | ||
torch.tensor | copied from existing data (list, NumPy ndarray, etc.) | ✔ | |
torch.from_numpy* | from NumPy ndarray (sharing storage without copying) | ||
torch.arange, torch.range and torch.linspace | uniformly spaced values in a given range | ||
torch.logspace | logarithmically spaced values in a given range | ||
torch.eye | identity matrix |
注:torch.from_numpy
只接受NumPy ndarray
作为输入参数。
书写设备无关代码(device-agnostic code)
以前版本很难写设备无关代码。我们使用两种方法使其变得简单:
Tensor
的device
属性可以给出其torch.device
(get_device
只能获取CUDA tensor)- 使用
x.to()
方法,可以很容易将Tensor
或者Module
在devices间移动(而不用调用x.cpu()
或者x.cuda()
。
推荐使用下面的模式:
1 | # 在脚本开始的地方,指定device |
在nn.Module
中对于submodule,parameter和buffer名字新的约束
当使用module.add_module(name, value)
, module.add_parameter(name, value)
或者 module.add_buffer(name, value)
时候不要使用空字符串或者包含.
的字符串,可能会导致state_dict
中的数据丢失。如果你在load这样的state_dict
,注意打补丁,并且应该更新代码,规避这个问题。
一个具体的例子
下面是一个code snippet,展示了从0.3.1跨越到0.4.0的不同。
0.3.1 version
1 | model = MyRNN() |
0.4.0 version
1 | # torch.device object used throughout this script |