4月25日,PyTorch团队正式发布了0.4.0的release版本
<https://github.com/pytorch/pytorch/releases/tag/v0.4.0>
。这是在与caffe2合并后的首个稳定版本。其中核心的变化有:
* Tensor/Variable合并
* 零维张量(标量)
* volatile标志的弃用
* dtypes,devices和Numpy型Tensor的创建函数
* 写设备无关的代码
下面对其中的升级注意的问题进行说明。
原文参考:PyTorch 0.4.0 Migration Guide
<http://pytorch.org/2018/04/22/0_4_0-migration-guide.html>
合并Tensor <http://pytorch.org/docs/stable/tensors.html>与Variable类
torch.Tensor和torch.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 <http://pytorch.org/docs/stable/autograd.html>开始追踪历史?
autograd的核心标志位:requires_grad,目前是Tensor的一个属性。之前应用于Variables的规则现在也适用于Tensors;因此
autograd在当任何输入的Tensor操作的requires_grad=True时开始追踪历史。
>>> x = torch.ones(1) # 创建一个requires_grad属性为False的Tensor (默认属性) >>>
x.requires_gradFalse # 默认为FALSE >>> y = torch.ones(1) #
另一个requires_grad属性为False的Tensor >>> z = x + y >>> #
两个输入Tensor的requires_grad属性都是False因此其输出的Tensor requires_grad属性也为False >>>
z.requires_gradFalse >>> # 接下来尝试验证追踪计算(反向传播) >>> z.backward() RuntimeError:
element0 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_gradTrue >>> # 与另一个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,但如果在后向需要x,autograd将记录它的就地(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=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) # 同样可作为函数使用 >>> y = x * 2 >>> y.requires_grad True
>>>torch.set_grad_enabled(False) >>> y = x * 2 >>> y.requires_grad False
dtypes,devices和Numpy型Tensor的创建函数
在以前的版本中,我们使用特定的数据类型(如float与double),设备类型(如cpu与cuda)以及布局(dense和sparse)作为“Tensor
的类型”。例如,torch.cuda.sparse.DoubleTensor代表了使用double数据类型,运行于CUDA设备,采用COO稀疏表示的张量。
在这个版本中,我们介绍了torch.dtype,torch.device和torch.layout类,以便通过NumPy风格的创建函数更好地管理这些属性。
torch.dtype <http://pytorch.org/docs/stable/tensor_attributes.html>
下表为可用的torch.dtypes(数据类型)及相应张量类型的完整列表:
Tensor的dtype可通过其dtype属性访问。
torch.device <http://pytorch.org/docs/stable/tensor_attributes.html>
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()的结果。
张量的设备可以通过其设备属性访问。
torch.layout <http://pytorch.org/docs/stable/tensor_attributes.html>
torch.layout表示张量的数据分布。 目前支持torch.strided(稠密张量,默认值)和torch.sparse_coo
(具有COO格式的稀疏张量)。
创建Tensor
创建张量的方法现在还可以使用dtype,device,layout和requires_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.*_like或tensor.new_*的变体。
*
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)
*
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有两种更简单的方法:
* Tensor的device属性为所有张量提供了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)
代码样例
下面是0.3.1与0.4.0的对比:
0.3.1
model = MyRNN() if use_cuda: model = model.cuda() # train total_loss = 0 for
input, targetin 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, targetin 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累计中出现普遍的问题。在平时的编写代码过程中,还是要多参考手册。
热门工具 换一换