Pytorch筆記01-Variable和Function(自動梯度計算)

0. 本章內容

pytorch的自動梯度計算是基於其中的Variable類和Function類構建計算圖,在本章中將介紹如何生成計算圖,以及pytorch是如何進行反向傳播求梯度的,主要內容如下:

  1. pytorch如何構建計算圖(`Variable`與`Function`類)

  2. `Variable`與`Tensor`的差別

  3. 動態圖機制是如何進行的(`Variable`與`Function`如何建立計算圖)

  4. Variable的基本操作
  5. Variable的require_grad與volatile參數
  6. 對計算圖進行可視化

所有源碼都能在XavierLinNow/pytorch_note_CN這裡獲取

1. pytorch如何構建計算圖(`Variable`與`Function`)

  • 一般一個神經網路都可以用一個有向無環圖圖來表示計算過程,在pytorch中也是構建計算圖來實現forward計算以及backward梯度計算。

  • 計算圖由節點和邊組成。

  • 計算圖中邊相當於一種函數變換或者說運算,節點表示參與運算的數據。邊上兩端的兩個節點中,一個為函數的輸入數據,一個為函數的輸出數據。

  • 而`Variable`就相當於計算圖中的節點。

  • `Function`就相當於是計算圖中的邊,它實現了對一個輸入`Variable`變換為另一個輸出的`Variable`

  • 因此,`Variable`需要保存forward時計算的激活值。這個值是一個`Tensor`,可以通過`.data`來得到這個Variable所保存的forward時的計算值。

  • 同時反向傳導時,一個`Variable`還需要保存其梯度。該梯度也是一個`Variable`,可以通過`.grad`來得到。

2. Variable與Tensor差別

  • `Tensor`只是一個類似`Numpy array`的數據格式,它可以進行多種運行,但無法構建計算圖

  • `Variable`不僅封裝了`Tensor`作為對應的激活值,還保存了產生這個`Variable`的`Function`(即計算圖中的邊),可以通過`.creator`(見圖)來看是哪個Function輸出了這個Variable

  • 在forward時,`Variable`和`Function`構建一個計算圖。只有得到了計算圖,構建了`Variable`與`Function`與Variable的輸入輸出關係,才能在backward時,計算各個節點的梯度。

  • `Variable`可以進行`Tensor`的大部分計算
  • 對`Variable`使用`.backward()`方法,可以得到該Variable之前計算圖所有Variable的梯度
  • Variable.data是該Variable前向傳播的激活值,為一個Tensor
  • Variable.grad是該Variable後向傳播的值,為一個Variable

3. 動態圖機制是如何進行的(Variable和Function的關係)

  • Variable與Function組成了計算圖

  • Function是在每次對Variable進行運算生成的,表示的是該次運算

  • 動態圖是在每次forward時動態生成的。具體說,假如有Variable x,Function *。他們需要進行運算y = x * x,則在運算過程時生成得到一個動態圖,該動態圖輸入是x,輸出是y,y的`.creator`是*

  • 一次forward過程將有多個Function連接各個Variable,Function輸出的Variable將保存該Function的引用(即.creator),從而組成計算圖

  • 在backward時,將利用生成的計算圖,根據求導的鏈式法則得到每個Variable的梯度值

4. Variable的基本操作

生成Variable,Variable計算

from torch.autograd import Variable, Functionnimport torchnn# 生成Variablenx = Variable(torch.ones(2, 2), requires_grad=True) # requires_grad表示在backward是否計算其梯度nprint(x)nnprint(-----------------------)nn# 查看Variable的屬性.data, .grad, .creatornprint(x.data) # Variable保存的值nprint(x.grad) # 由於目前還未進行.backward()運算,因此不存在梯度nprint(x.creator) # 對於手動申明的Variable,不存在creator,即在計算圖中,該Variable不存在父節點nnprint(-----------------------)nn# Variable進行運算ny = x + 2nprint(y)nprint(y.creator) # y存在x這個父節點,並且通過+這個Function進行連接,因此y.creator是運算+n

Variable計算梯度

在引入Variable後,在forward時,我們生成了計算圖,而backward就不需要我們計算了,pytorch將根據計算圖自動計算梯度

# 生成計算圖nx = Variable(torch.ones([1]), requires_grad=True)ny = 0.5 * (x + 1).pow(2)nz = torch.log(y)nn# 進行backwardn# 注意這裡等價於z.backward(torch.Tensor([1.0])),參數表示的是後面的輸出對Variable z的梯度nz.backward() nprint(x.grad)nn# 此時y.grad為None,因為backward()只求圖中葉子的梯度(即無父節點),如果需要對y求梯度,則可以使用`register_hook`nprint(y.grad)n

5. Variable的require_grad與volatile參數

  • 在創建一個Variable是,有兩個bool型參數可供選擇,一個是requires_grad,一個是Volatile
  • requires_grad不是十分對該Var進行計算梯度,一般在finetune是可以用來固定某些層的參數,減少計算。只要有一個葉節點是True,其後續的節點都是True
  • volatile=True,一般用在訓練好網路,只進行inference操作時使用,其不建立Variable與Function的關係。只要有一個葉子節點是True,其後節點都是True

6. 對計算圖進行可視化

生成了計算圖,如何才能知道自己的計算圖是否正確。可以利用graphviz包對計算圖進行可視化。

步驟如下:

1. 安裝graphviz包

2. 新建visualizer.py,編寫如下代碼

from graphviz import Digraphnimport renimport torchnimport torch.nn.functional as Fnfrom torch.autograd import Variablenfrom torch.autograd import Variablenimport torchvision.models as modelsnndef make_dot(var):n node_attr = dict(style=filled,n shape=box,n align=left,n fontsize=12,n ranksep=0.1,n height=0.2)n dot = Digraph(node_attr=node_attr, graph_attr=dict(size="12,12"))n seen = set()nn def add_nodes(var):n if var not in seen:n if isinstance(var, Variable):n value = (+(, ).join([%d% v for v in var.size()])+)n dot.node(str(id(var)), str(value), fillcolor=lightblue)n else:n dot.node(str(id(var)), str(type(var).__name__))n seen.add(var)n if hasattr(var, previous_functions):n for u in var.previous_functions:n dot.edge(str(id(u[0])), str(id(var)))n add_nodes(u[0])n add_nodes(var.creator)nn return dot n

3. 用如下方式進行調用

from utils.visualizer import make_dotnn# 生成一個計算圖y = 0.5*(x + 1)^2; z = ln(y)nx = Variable(torch.ones([1]), requires_grad=True)ny = 0.5 * (x + 1).pow(2)nz = torch.log(y)nprint(x)nprint(y)nn# 產生可視化計算圖ng = make_dot(z)ng.view()n

就能產生對應的計算圖了:

7. 例子

我們同樣用一個簡單的例子來說明如何使用Variable,在引入Variable後,我們已經不需要自己手動計算梯度了

from sklearn.datasets import load_bostonnfrom sklearn import preprocessingnfrom torch.autograd import Variablenn# dtype = torch.FloatTensorndtype = torch.cuda.FloatTensornX, y = load_boston(return_X_y=True)nX = preprocessing.scale(X[:100,:])ny = preprocessing.scale(y[:100].reshape(-1, 1))nndata_size, D_input, D_output, D_hidden = X.shape[0], X.shape[1], 1, 50nX = Variable(torch.Tensor(X).type(dtype), requires_grad=False)ny = Variable(torch.Tensor(y).type(dtype), requires_grad=False)nw1 = Variable(torch.randn(D_input, D_hidden).type(dtype), requires_grad=True)nw2 = Variable(torch.randn(D_hidden, D_output).type(dtype), requires_grad=True)nnlr = 1e-5nepoch = 200000nfor i in range(epoch):n n # forwardn h = torch.mm(X, w1)n h_relu = h.clamp(min=0)n y_pred = torch.mm(h_relu, w2)n loss = (y_pred - y).pow(2).sum()n if i % 10000 == 0:n print(epoch: {} loss: {}.format(i, loss.data[0])) # 使用loss.data[0],可以輸出Tensor的值,而不是Tensor信息n n # backward 我們直接使用Variable.backward(),就能根據forward構建的計算圖進行反向傳播n loss.backward()n nn w1.data -= lr * w1.grad.data n w2.data -= lr * w2.grad.datann w1.grad.data.zero_()n w2.grad.data.zero_()n

推薦閱讀:

Kickstarter 上的「可視化編程」項目 NoFlo 能寫出怎樣的程序?可以使編程更容易么?
卡通渲染(下)
Python · cv2(一)· 神經網路的可視化
Python數據分析之簡書七日熱門數據分析
Matplotlib 蠟燭圖教程

TAG:PyTorch | 可视化 | 深度学习DeepLearning |