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 | x = torch.DoubleTensor([1, 1, 1]) |
autograd现在如何追踪计算图的历史
Tensor和Variable的合并,简化了计算图的构建,具体规则见本条和以下几条说明。
requires_grad, 这个autograd中的核心标志量,现在成了Tensor的属性。之前的Variable使用规则可以同样应用于Tensor,autograd自动跟踪那些至少有一个input的requires_grad==True的计算节点构成的图。
1 | x = torch.ones(1) ## 默认requires_grad = False |
操作 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(),仍然会返回一个共享xdata的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 | torch.tensor(3.1416) # 直接创建scalar |
累积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 | x = torch.zeros(1, requires_grad=True) |
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 | device = torch.device("cuda:1") |
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 = torch.device("cuda") |
我们还加了更多的Tensor创建方法。其中有一些torch.*_like,tensor.new_*这样的形式。
torch.*_like的参数是一个input tensor, 它返回一个相同属性的tensor,除非有特殊指定。1
2
3
4
5x = 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_*类似,不过它通常需要接受一个指定shape的参数。1
2
3
4
5x = 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)
为了指定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 |