04.25 PyTorch 0.4.0遷移指南

PyTorch 0.4.0帶來了許多激動人心的新特性,同時也修正了許多bug.

PyTorch 0.4.0遷移指南

0.4.0 主要變動

  • Tensor(張量)和Variable(變量)合併。

  • 0維張量(即標量)。

  • volatile現在已經deprecate了(現在加了volatile沒有任何效果)。

  • dtypes、 devices和Numpy風格的張量創建函數。

  • 編寫平臺無關代碼。

  • 官方Windows支持。

Tensor和Variable合併

torch.Tensor和torch.autograd.Variable現在是同一個類。更準確地說, torch.Tensor具備了舊版的Variable一樣的追蹤歷史的能力。再也不用把Tensor包成Variable了。

注意,Tensor的type()也做了相應調整。

>>> x = torch.DoubleTensor([1, 1, 1]) 

>>> print(type(x)) # 以前是 torch.DoubleTensor
"<class>"
>>> print(x.type()) # 想要得到'torch.DoubleTensor'的話,要這樣調用
'torch.DoubleTensor'
>>> print(isinstance(x, torch.DoubleTensor)) # 或者直接這樣判斷
True/<class>

前面已經提到,現在Tensor具有追蹤歷史的能力,不用再包成Variable了。那麼,如何啟用追蹤歷史呢?很簡單,設置requires_grad=True就好了。

>>> x.requires_grad
False
>>> y = torch.ones(1) # 同樣requires_grad=False
>>> z = x + y
>>> # 兩個張量都是requires_grad=False,它們相加所得也一樣
>>> z.requires_grad
False
>>> # 驗證autograd未曾追蹤計算
>>> z.backward()
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
>>>
>>> # 現在使用requires_grad=True創建張量
>>> w = torch.ones(1, requires_grad=True)
>>> w.requires_grad
True
>>> # 加上之前的require_grad=False張量
>>> total = w + z
>>> # 結果requires_grad=True
>>> total.requires_grad
True
>>> # autograd同時可以計算梯度
>>> total.backward()
>>> w.grad
tensor([ 1.])
>>> # x、y、z的梯度require_grad=False,因此不會造成算力浪費
>>> z.grad == x.grad == y.grad == None
True

除了在創建Tensor時設定require_grad=False外,還可以為現有的Tensor開啟require_grad!

>>> existing_tensor.requires_grad_()
>>> existing_tensor.requires_grad
True

此外,在舊版中,可以通過.data方法得到Variable底層的Tensor。現在Tensor和Variable合二為一了,因此推薦改用.detach方法。x.detach()返回一個requires_grad=False的共享數據的Tensor,並且,如果反向傳播中需要x,那麼x.detach返回的Tensor的變動會被autograd追蹤。相反,x.data()返回的Tensor,其變動不會被autograd追蹤,如果反向傳播需要用到x的話,值就不對了。

0維張量(標量)

舊版中,Tensor向量(一維張量)和Variable向量的行為不怎麼一致。比如,tensor.sum()會返回Python數字,而variable.sum()則會返回一個尺寸為(1, )的向量!

隨著0.4.0中Tensor和Variable的合併,這個不一致的問題自然也就不存在了。此外,標量也可以視作0維張量,通過torch.tensor函數創建。

>>> torch.tensor(3.1416) # 直接創建變量
tensor(3.1416)
>>> torch.tensor(3.1416).size() # 標量是0維的
torch.Size([])
>>> torch.tensor([3]).size() # 和向量比較
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([])

注意,舊版PyTorch的一個慣用法是

total_loss += loss.data[0]

其中loss是一個Variable(在舊版中)。

這個慣用法利用了Variable和Tensor的不一致性,是不好的寫法。在0.4.0中,loss是一個0維張量(標量),因此loss.data[0]或loss[0]無意義(舊版中.data返回Variable底層的張量)。應當改用loss.item()。

平臺無關代碼

舊版PyTorch充斥著torch.cuda.sparse.DoubleTensor之類硬編碼的平臺相關代碼,很不優雅。PyTorch 0.4.0終於引入了torch.dtype、 torch.device、torch.layout類。

PyTorch 0.4.0遷移指南

相應的,torch.device包含設備類型(cpu或cuda),torch.layout目前支持torch.strided(密集張量,此為默認值)和torch.sparse_coo(COO格式的稀疏張量)。

相應的創建張量代碼實例:

>>> device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
>>> 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=device)
>>> x.requires_grad # 默認值
False
>>> x = torch.zeros(3, requires_grad=True)
>>> x.requires_grad
True

當然,我們也可以使用0.4.0新引入的torch.tensor函數,相當於NumPy的numpy.array。

>>> device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
>>> torch.tensor([[1], [2], [3]], dtype=torch.half, device=device)
tensor([[ 1],
[ 2],
[ 3]], device=device)
>>> torch.tensor(1) # 標量
tensor(1)
>>> torch.tensor([1, 2.3]).dtype # 類型推斷
torch.float32
>>> torch.tensor([1, 2]).dtype # 類型推斷
torch.int64

此外,0.4.0還加入了torch.*_like和tensor.new_。

torch.*_like接受一個Tensor:

>>> 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_*類似torch.*_like,不過接受shape參數。

>>> 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)

PyTorch 0.4.0遷移指南

新舊代碼對照

0.3.1

 model = MyRNN()
if use_cuda:
model = model.cuda()
# 訓練
total_loss = 0
for input, target in train_loader:

input, target = Variable(input), Variable(target)
hidden = Variable(torch.zeros(*h_shape)) # 初始化隱藏層
if use_cuda:
input, target, hidden = input.cuda(), target.cuda(), hidden.cuda()
... # 獲取損失並優化
total_loss += loss.data[0]
# 評估
for input, target in test_loader:
input = Variable(input, volatile=True)
if use_cuda:
...
...

0.4.0

 device = torch.device("cuda" if use_cuda else "cpu")
model = MyRNN().to(device)
# 訓練
total_loss = 0
for input, target in train_loader:
input, target = input.to(device), target.to(device)
hidden = input.new_zeros(*h_shape) # device和dtype與`input`一致
... # 獲取損失並優化
total_loss += loss.item() # 得到Python數字
# 評估
with torch.no_grad(): # 其中的操作不追蹤歷史
for input, target in test_loader:
...

本文基於PyTorch 0.4.0官方遷移指南編寫。


分享到:


相關文章: