1. 程式人生 > >Pytorch學習(十四) ------- Pytorch 0.4程式碼遷移簡要總結

Pytorch學習(十四) ------- Pytorch 0.4程式碼遷移簡要總結

總說

由於pytorch 0.4版本更新實在太大了, 以前版本的程式碼必須有一定程度的更新. 主要的更新在於 Variable和Tensor的合併., 當然還有Windows的支援, 其他一些就是支援scalar tensor以及修復bug和提升效能吧. Variable和Tensor的合併導致以前的程式碼會出錯, 所以需要遷移, 其實遷移代價並不大.

Tensor和Variable的合併

說是合併, 其實是按照以前(0.1-0.3版本)的觀點是: Tensor現在預設requires_grad=False的Variable了. torch.Tensortorch.autograd.Variable

現在其實是同一個類! 沒有本質的區別! 所以也就是說, 現在已經沒有純粹的Tensor了, 是個Tensor, 它就支援自動求導! 你現在要不要給Tensor包一下Variable, 都沒有任何意義了.

檢視Tensor的型別

使用.isinstance()或是x.type(), 用type()不能看tensor的具體型別.

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

requires_grad 已經是Tensor的一個屬性了

>>> x = torch.ones(1)
>>> x.requires_grad #預設是False
False
>>> y = torch.ones(1)
>>> 
z = x + y >>> # 顯然z的該屬性也是False >>> z.requires_grad False >>> # 所有變數都不需要grad, 所以會出錯 >>> z.backward() RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn >>> >>> # 可以將`requires_grad`作為一個引數, 構造tensor >>> w = torch.ones(1, requires_grad=True) >>> w.requires_grad True >>> total = w + z >>> total.requires_grad True >>> # 現在可以backward了 >>> total.backward() >>> w.grad tensor([ 1.]) >>> # x,y,z都是不需要梯度的,他們的grad也沒有計算 >>> z.grad == x.grad == y.grad == None True

通過.requires_grad()來進行使得Tensor需要梯度.

不要隨便用.data

以前.data是為了拿到Variable中的Tensor,但是後來, 兩個都合併了. 所以 .data返回一個新的requires_grad=False的Tensor! 然而新的這個Tensor與以前那個Tensor是共享記憶體的. 所以不安全, 因為

y = x.data # x需要進行autograd
# y和x是共享記憶體的,但是這裡y已經不需要grad了, 
# 所以會導致本來需要計算梯度的x也沒有梯度可以計算.從而x不會得到更新!

所以, 推薦用x.detach(), 這個仍舊是共享記憶體的, 也是使得y的requires_grad為False, 但是,如果x需要求導, 仍舊是可以自動求導的!

scalar的支援

這個非常重要啊! 以前indexing一個一維Tensor,返回的是一個number型別,但是indexing一個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, 可以將返回值的型別進行統一.
重點:
1. 取得一個tensor的值(返回number), 用.item()
2. 建立scalar的話,需要用torch.tensor(number)
3. torch.tensor(list)也可以進行建立tensor

累加loss

以前了累加loss(為了看loss的大小)一般是用total_loss+=loss.data[0] , 比較詭異的是, 為啥是.data[0]? 這是因為, 這是因為loss是一個Variable, 所以以後累加loss, 用loss.item().
這個是必須的, 如果直接加, 那麼隨著訓練的進行, 會導致後來的loss具有非常大的graph, 可能會超記憶體. 然而total_loss只是用來看的, 所以沒必要進行維持這個graph!

棄用 volatile

現在這個flag已經沒用了. 被替換成torch.no_grad(), torch.set_grad_enable(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)  # this can also be used as a function
>>> y = x * 2
>>> y.requires_grad
True
>>> torch.set_grad_enabled(False)
>>> y = x * 2
>>> y.requires_grad
False

dypes,devices以及numpy-style的建構函式

dtype是data types, 對應關係如下:
這裡寫圖片描述
通過.dtype可以得到

其他就是以前寫device type都是用.cup()或是.cuda(), 現在獨立成一個函式, 我們可以

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

新的建立Tensor方法

主要是可以指定 dtype以及device.

>>> 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來建立Tensor

這個等價於numpy.array,用途:
1.將python list的資料用來建立Tensor
2. 建立scalar

# 從列表中, 建立tensor
>>> cuda = torch.device("cuda")
>>> torch.tensor([[1], [2], [3]], dtype=torch.half, device=cuda)
tensor([[ 1],
        [ 2],
        [ 3]], device='cuda:0')

>>> torch.tensor(1)               # 建立scalar
tensor(1)

torch.*like以及torch.new_*

第一個是可以建立, shape相同, 資料型別相同.

 >>> 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, 但是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)

書寫 device-agnostic 的程式碼

這個含義是, 不要顯示的指定是gpu, cpu之類的. 利用.to()來執行.

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

遷移程式碼對比

以前的寫法

 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:
          ...
      ...

現在的寫法

  # 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:
          ...