PyTorch 0.4 升级指南
Posted Yan_Joy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyTorch 0.4 升级指南相关的知识,希望对你有一定的参考价值。
4月25日,PyTorch团队正式发布了0.4.0
的release版本。这是在与caffe2合并后的首个稳定版本。其中核心的变化有:
- Tensor/Variable合并
- 零维张量(标量)
volatile
标志的弃用dtypes
,devices
和Numpy型Tensor
的创建函数- 写设备无关的代码
下面对其中的升级注意的问题进行说明。
合并Tensor
与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
开始追踪历史?
autograd
的核心标志位:requires_grad
,目前是Tensor
的一个属性。之前应用于Variables
的规则现在也适用于Tensors
;因此autograd
在当任何输入的Tensor
操作的requires_grad=True
时开始追踪历史。
>>> x = torch.ones(1) # 创建一个requires_grad属性为False的Tensor (默认属性)
>>> x.requires_grad
False # 默认为FALSE
>>> y = torch.ones(1) # 另一个requires_grad属性为False的Tensor
>>> z = x + y
>>> # 两个输入Tensor的requires_grad属性都是False因此其输出的Tensor requires_grad属性也为False
>>> z.requires_grad
False
>>> # 接下来尝试验证追踪计算(反向传播)
>>> z.backward()
RuntimeError: element 0 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_grad
True
>>> # 与另一个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
下表为可用的torch.dtypes
(数据类型)及相应张量类型的完整列表:
Tensor
的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()
的结果。
张量的设备可以通过其设备属性访问。
torch.layout
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, 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:
...
感谢阅读,这次的更新很大程度是简化了数据结构,并让创建Tensor更加便捷。但要注意0维张量的出现会在loss累计中出现普遍的问题。在平时的编写代码过程中,还是要多参考手册。
以上是关于PyTorch 0.4 升级指南的主要内容,如果未能解决你的问题,请参考以下文章
我更改了标量类型 float 的预期对象,但在 Pytorch 中仍然得到 Long
[pytorch]pytorch loss function 总结
年度最大更新!PyTorch 0.4:完全改变API,官方支持Windows