Pytorch 0.4.0 新版本特性

概述

在新版本中,PyTorch引入了许多令人兴奋的新特性,主要的更新在于 Variable和Tensor的合并 。为了方便代码的迁移到新的版本下,对新版本特性做一些记录(翻译),主要是以下几个部分。

  • TensorVariable合并,autograd的机制有所不同,变得更简单,使用requires_grad和上下文相关环境管理,弃用了 volatile 标志。
  • 支持 scalar 即0维的Tensor
  • Numpy风格的Tensor构建。
  • 提出了device,更简单地在cpu和gpu中移动数据。

1. Tensor 和 Variable 合并

在PyTorch以前的版本中,Tensor类似于numpy中的ndarray,只是对多维数组的抽象。为了能够使用自动求导机制,必须使用Variable对其进行包装。而现在,这两个东西已经完全合并成一个了,以前Variable的使用情境都可以使用Tensor。所以以前训练的时候总要额外写的warpping语句用不到了。

#老版本的写法
for data, label in data_loader:
  data, label = Variable(data), Variable(label)
  loss = criterion(model(data), target)
# 新版本不再需要再对Tensor进行包装成 Variable的操作
# device 后面会讲到
device = torch.device('cuda' if torch.cuda.is_availabel() else 'cpu')
for data, label in data_loader:  
  data, lalel = data.to(device), label.to(device)

2. Tensor 的类型 type()

以前我们可以使用 type() 获取Tensor的data type(FloatTensor,LongTensor等)。现在需要使用x.type() 获取类型或 isinstance()判别类型。

>>> x = torch.DoubleTensor([1,2,3])
>>> print type(x)   #曾经会给出 torch.DoubleTensor
<class 'torch.Tensor'>
>>>print (x.type())
'torch.DoubleTensor'
>>>print isinstance(x, torch.DoubleTensor)
True

3. autograd 现在如何追踪计算图的历史

Tensor 和 Variable 的合并,简化了计算图的构建,但在操作过程中会和以前有一些不同,具体如下:

  • requires_grad, 这个 Variable 中的核心标志, 现在成了 Tensor 的属性。

    之前的 Variable 的使用规则可以同样使用于 Tensor , autograd 自动跟踪求导 那些至少有一个 input 的 requires_grad==True 的计算节点构成的图。

    >>> x = torch.ones(1)  ## 默认requires_grad = False
    >>> x.requires_grad
    False
    >>> y = torch.ones(1)  ## 同样,y的requires_grad标志也是False
    >>> z = x + y
    >>> ## 所有的输入节点都不要求梯度,所以z的requires_grad也是False
    >>> z.requires_grad
    False
    >>> ## 所以如果试图对z做梯度反传,会抛出Error
    >>> 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的节点相加,得到输出
    >>> total = w + z
    >>> ## 由于w需要梯度,所以total也需要
    >>> total.requires_grad
    True
    >>> ## 可以做bp
    >>> total.backward()
    >>> w.grad
    tensor([ 1.])
    >>> ## 不用有时间浪费在求取 x y z的梯度上,因为它们没有 require grad,它们的grad == None
    >>> z.grad == x.grad == y.grad == None
    True
    
  • 操作 requires_grad 标志

    除了直接设置这个属性,你可以使用my_tensor.requires_grad_()就地置这个标志为 True(还记得吗,以_结尾的方法名表示in-place的操作)。或者就在构造的时候传入此参数。

    >>>exisiting_tensor.requires_grad_( )
    >>>existing_tensor.requires_grad
    True
    >>>my_tensor  = torch.tensor(3, 4, requires_grad=True)
    >>>my_tensor.requries_grad
    True
    
  • .data 怎么办?

    原来版本中,对于某个Variable,我们可以通过x.data的方式获取其包装的Tensor。现在两者已经merge到了一起,如果你调用y = x.data仍然和以前相似,y现在会共享x的data,并与x的计算历史无关,且其requires_grad标志为False。这会导致不安全。

    >>> y = x.data  #以前的方式,取出Variable中的Tensor
    >>> y 和 x 是共享内存的,但是这里y已经不需要grad了,新版本中此时y返回的是 requires_grad = False 的Tensor
    >>> 此时对y的任何操作都不会被autograd所追踪,所以如果反向传播时需要计算x的梯度,导致出现错误
    >>> #解决办法
    >>> y = x.detach()  #对y的操作仍然会被autograd捕捉
    

4. 支持标量 0维(scalar) 的Tensor

以前索引一个一维Tensor,返回的是一个number类型,但是索引一个Variable确实返回一个size为(1,)的vector。 再比如一些reduction操作, 比如tensor.sum()返回一个number, 但是variable.sum()返回的是一个size为(1,)的vector。
scalar 是维度为0 的Tensor,用 torch.tensor( )(小写)进行创建。

  >>> torch.tensor(3.1416)         # 用torch.tensor来创建scalar
  tensor(3.1416)     # 注意 scalar是打印出来是没有[]的
  >>> torch.tensor(3.1416).size()  # size是0
  torch.Size([])
  >>> torch.tensor([3]).size()     # compare to a vector of size 1
  torch.Size([1])  # 如果是tensor, 打印出来会用`[]`包上
  >>>
  >>> vector = torch.arange(2, 6)  # this is a vector
  >>> vector
  tensor([ 2.,  3.,  4.,  5.])
  >>> vector[3]                    # 现在, indexing一个一维tensor返回的是一个tensor了!
  tensor(5.)
  >>> vector[3].item()             # 需要额外加上.item() 来获得里面的值
  5.0
  >>> mysum = torch.tensor([2, 3]).sum() # 而这种reduction操作, 返回的是一个scalar了(0-dimension            的tensor)
  >>> mysum
  tensor(5)
  >>> mysum.size()
  torch.Size([])

从上面的例子可以看出,通过引入 scalar,可以将返回值类型进行统一。

  • 取得一个tensor的值,用 .item()
  • 创建 scalar,用 torch.tensor(number)
  • torch.tensor(list), 可以接受 list 或者 array类型进行创建Tensor

5. 累加 loss

以前了累加loss(为了看loss的大小)一般是用 total_loss+=loss.data[0] , 比较诡异的是, 为啥是.data[0]? 这是因为, loss通常都是由损失函数计算出来的一个标量,也就是包装了(1,)大小Tensor的Variable。在新的版本中,loss则变成了0D的scalar。对一个scalar做indexing是没有意义的,应该使用 loss.item() 获取 python number。
total_loss 只是我们用来查看训练过程的一个量,没有必要去维护一个计算图Graph,给显存造成负担。

6. 弃用 volatile

volatile 标志被弃用了,现在没有任何效果。以前的版本中,一个设置volatile=True的Variable 表明其不会被autograd追踪。现在,被替换成了一个更灵活的上下文管理器,如torch.no_grad(),torch.set_grad_enable(grad_mode)等。

  >>> x = torch.zeros(1, requires_grad=True)
  >>> with torch.no_grad():    # 使用 torch,no_grad()构建不需要track的上下文环境
  ...     y = x * 2
  >>> y.requires_grad
  False
  >>>
  >>> is_train = False
  >>> with torch.set_grad_enabled(is_train):   # 在inference的时候,设置不要track
  ...     y = x * 2
  >>> y.requires_grad
  False
  >>> torch.set_grad_enabled(True)  # 当然也可以不用with构建上下文环境,而单独这样用
  >>> y = x * 2
  >>> y.requires_grad
  True
  >>> torch.set_grad_enabled(False)
  >>> y = x * 2
  >>> y.requires_grad
  False

7. dtype, devices 和 Numpy 风格的构建函数

以前的版本中,我们需要以”tensor type”的形式给出对data type(如float或double),device type(如cpu或gpu)以及layout(dense或sparse)的限

torch.dtype

下面是可用的 torch.dtypes (data types) 和他们对应的 tensor types。可以用 x.dtype获取


dtype.png

torch.device

torch.device 包含了 device type( 如 cpu 或 gpu) 和可能的设备 id。 使用 torch.device('{device_tyep}') 或者 torch.device('{device_type}:{device_ordinal}')的方式来初始化。
如果没有制定 device ordinal, 那么默认的是当前的device. 例如 , torch.device('cuda') 相当于 torch.device('cuda:X'),其中 X是 torch.cuda.current_device() 的返回结果。

8. 创建 Tensor(Numpy 风格)

你可以使用dtype,device,layout和requires_grad更好地控制Tensor的创建。

  >>> device = torch.device("cuda : 1")
  # 现在创建 Tensor 时候可以制定类型dtype, 创建位置device,以及是否需要求导requires_grad
  >>> x = torch.randn(3, 3, dtype = torch.float64, device=device, requires_grad=True)
torch.tensor(data, ...)

torch.tensor 是新加入的Tensor 构建函数。它接受一个“array-like”的参数, 并将其value 复制到一个新的Tensor中。可以将它看成是 numpy.array 的等价物。 不同于torch.*Tensor 方法,你可以创建0D的Tensor,也就是scalar。此外,如果dtype参数没有给出,它会自动推断。
推荐使用这个函数从已有的data里面,如 Python List 或者 Numpy ndarray 创建 Tensor

torch.*_like, torch.new_*形式的创建Tensor, 返回相同属性的tensor,除非有特殊指定。

为了指定shape参数,可以使用tuple,如 torch.zeros((2,3))(Numpy风格),也可以使用可变量参数torch.zeros(2,3)(以前的pytorch版本只支持这种风格)。

推荐阅读更多精彩内容