1. 程式人生 > >pytorch系列 --4 pytorch 0.4改動後Variable和Tensor合併問題data和.detach

pytorch系列 --4 pytorch 0.4改動後Variable和Tensor合併問題data和.detach

本文主要講述pytorch0.4更新後相關的程式碼遷移問題

Tensor和Variable合併

torch.Tensor 和torch.autograd.Variable現在是同一個類。torch.Tensor 能夠像之前的Variable一樣追蹤歷史和反向傳播。Variable仍能夠正常工作,但是返回的是Tensor。所以在0.4的程式碼中,不需要使用Variable了。

可以看出使用Variable封裝Tensor之後返回的也是Tensor。

import torch
from torch.autograd import Variable

x_tensor = torch.
Tensor(3,4) x_var = Variable(x_tensor,requires_grad=True) print(x_var)

out:

tensor([[1.7753e+28, 4.5601e+30, 1.8465e+25, 1.0901e+27],
[1.8289e+34, 7.4103e+28, 1.6443e+19, 4.9172e+33],
[9.2246e-39, 6.8383e-43, 1.4013e-45, 0.0000e+00]], requires_grad=True)

接下來看一下,合併Tensor和Variable之後autograd是如何實現歷史追蹤和反向傳播的

作為能否autograd的標籤,requires_grad現在是Tensor的屬性,所以,只要當一個操作(operation)的任何輸入Tensor具有requires_grad = True的屬性,autograd就可以自動追蹤歷史和反向傳播了。

官方的例項程式碼:

# 預設建立requires_grad = False的Tensor
 x = torch.ones(1)   # create a tensor with requires_grad=False (default)
 x.requires_grad
 # out: False
 
 # 建立另一個Tensor,同樣requires_grad = False
y = torch.ones(1) # another tensor with requires_grad=False # both inputs have requires_grad=False. so does the output z = x + y # 因為兩個Tensor x,y,requires_grad=False.都無法實現自動微分, # 所以操作(operation)z=x+y後的z也是無法自動微分,requires_grad=False z.requires_grad # out: False # then autograd won't track this computation. let's verify! # 因而無法autograd,程式報錯 z.backward() # out:程式報錯:RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn # now create a tensor with requires_grad=True w = torch.ones(1, requires_grad=True) w.requires_grad # out: True # add to the previous result that has require_grad=False # 因為total的操作中輸入Tensor w的requires_grad=True,因而操作可以進行反向傳播和自動求導。 total = w + z # the total sum now requires grad! total.requires_grad # out: True # autograd can compute the gradients as well total.backward() w.grad #out: tensor([ 1.]) # and no computation is wasted to compute gradients for x, y and z, which don't require grad # 由於z,x,y的requires_grad=False,所以並沒有計算三者的梯度 z.grad == x.grad == y.grad == None # True

那接下來看一下如何在已經建立的tensor上新增requires_grad的屬性呢?

相比於在建立tensor是就設定requires_grad的值,可以 使用my_tensor=requires_grad_()實現in-place的操作來改變requires_grad的屬性

existing_tensor.requires_grad_()
existing_tensor.requires_grad
# out:True

也可以在建立時就設定屬性值

my_tensor = torch.zeros(3, 4, requires_grad=True)
my_tensor.requires_grad
# out: True

重點介紹一下.data和detach()函式的區別

推薦在經網路網訓練或測試時訪問tensor的資料時使用.detach()方法。

簡單的說就是使用y=x.data屬性來訪問資料時,pytorch不會記錄資料是否改變,此時改變了y的值,意味著也要改變x的值,而在自動求導時會使用更改後的值,這回導致錯誤求導結果。

a = torch.tensor([1,2,3.], requires_grad = True)
out = a.sigmoid()
c = out.data
c.zero_()
#out: tensor([ 0.,  0.,  0.])

out  # out  was modified by c.zero_()
#out: tensor([ 0.,  0.,  0.])

out.sum().backward()
a.grad  # The result is very, very wrong because `out` changed!
#out: tensor([ 0.,  0.,  0.])

而使用y=x.detach()時,如果了y值,也意味著改變了x值,此時呼叫x.backword()會報錯。也就是說.detach()方法會記錄資料的變化狀態。
所以,推薦使用x.detach()來訪問資料,更加安全。

a = torch.tensor([1,2,3.], requires_grad = True)
out = a.sigmoid()
c = out.detach()
 c.zero_()
 #out: tensor([ 0.,  0.,  0.])

out  # modified by c.zero_() !!
#out:  tensor([ 0.,  0.,  0.])

out.sum().backward()  # Requires the original value of out, but that was overwritten by c.zero_()
RuntimeError: one of the variables needed for gradient computation has been modified by an

Tensor的type()方法

type(tensor)將不在返回tensor的資料型別,返回的是class,使用isinstance()tensor.type()來得到tensor的資料型別.

 x = torch.DoubleTensor([1, 1, 1])
 print(type(x))  # was torch.DoubleTensor
# out: "<class 'torch.Tensor'>"

 print(x.type())  # OK: 'torch.DoubleTensor'
# out: 'torch.DoubleTensor'

print(isinstance(x, torch.DoubleTensor))  # OK: True
# out: True

支援0維向量(標量)

在0.4之前,索引1維的Tensor但會python number, 而索引Variable返回的是size為(1,)的向量;兩者在行為上不一致,同樣的對於一些降維函式,比如Tensor.sum(),返回的也是Python number,而variable.sum()返回的是size為(1,)的向量。

在0.4的版本release中,pytorch支援標量(0-維tensor).可以使用torch.tensor建立標量。而sum(),loss()等返回一個數的降維函式也將返回標量(0-維 tensor)

對於0-維 tensor, .item()方法,返回Python number。

torch.tensor(3.1416)         # create a scalar directly
# out: tensor(3.1416)

torch.tensor(3.1416).size()  # scalar is 0-dimensional
#out: torch.Size([])

torch.tensor([3]).size()     # compare to a vector of size 1
#out: torch.Size([1])
ector = torch.arange(2, 6)  # this is a vector
vector
#out: tensor([ 2.,  3.,  4.,  5.])

vector.size()
#out: torch.Size([4])

vector[3]                    # indexing into a vector gives a scalar
#out: tensor(5.)

vector[3].item()             # .item() gives the value as a Python number
#out: 5.0

mysum = torch.tensor([2, 3]).sum() # sum()返回一個數,在此返回標量
mysum
#out: tensor(5)

mysum.size()
#out: torch.Size([])
 
mysum[0] # 使用此會出現警告
#out: User Warning: invalid index of a 0-dim tensor. This will be an error in PyTorch 0.5. Use tensor.item() to convert a 0-dim tensor to a Python number

losses

在0.4之前,廣泛的loss使用是:total_loss += loss.data[0], 這是因為loss是variable,其中size為(1,),而在0.4之後,loss是一個0-維的向量(標量)。可以使用loss.item()從標量中獲得Python number。

注意,當不轉換為Python number來計算loss的累加值時,程式將會有大量的記憶體使用,這是因為total_loss += loss.data[0]式子的右邊不是Python float而變成了0維tensor。這楊,總損失就會包含loss和梯度歷史,這些梯度歷史會在大的autograd graph中儲存更長時間,帶來大量的記憶體消耗。

上述loss積累的式子可以變為:
total_loss += loss.item()

在本系列的程式碼中,將遵循新版本語言規範。