1. 程式人生 > >小白學PyTorch 動態圖與靜態圖的淺顯理解

小白學PyTorch 動態圖與靜態圖的淺顯理解

**文章來自公眾號【機器學習煉丹術】,回覆“煉丹”即可獲得海量學習資料哦!** [TOC] 本章節縷一縷PyTorch的動態圖機制與Tensorflow的靜態圖機制(最新版的TF也支援動態圖了似乎)。 ## 1 動態圖的初步推導 - 計算圖是用來描述運算的**有向無環圖** - 計算圖有兩個主要元素:結點(Node)和邊(Edge); - **結點表示資料** ,如向量、矩陣、張量; - **邊表示運算** ,如加減乘除卷積等; ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d00f62e95260466ea34455acc7ce98d3~tplv-k3u1fbpfcp-zoom-1.image) 上圖是用計算圖表示: $y=(x+w)∗(w+1)y=(x+w)∗(w+1)$ 其中呢,$a=x+w$ ,$b=w+1$ , $y=a∗b$. (a和b是類似於中間變數的那種感覺。) Pytorch在計算的時候,就會把計算過程用上面那樣的動態圖儲存起來。現在我們計算一下y關於w的梯度: $\frac{\partial y}{\partial w} = \frac{\partial y}{\partial a} \frac{\partial a}{\partial w} + \frac{\partial y}{\partial b} \frac{\partial b}{\partial w}$ $=2\times w + x + 1=5$ (上面的計算中,w=1,x=2) 現在我們用Pytorch的程式碼來實現這個過程: ```python import torch w = torch.tensor([1.],requires_grad = True) x = torch.tensor([2.],requires_grad = True) a = w+x b = w+1 y = a*b y.backward() print(w.grad) ``` 得到的結果: ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4db29e2ac17a4021a4f05ffff090283a~tplv-k3u1fbpfcp-zoom-1.image) ## 2 動態圖的葉子節點 ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d00f62e95260466ea34455acc7ce98d3~tplv-k3u1fbpfcp-zoom-1.image) 這個圖中的葉子節點,是w和x,是整個計算圖的根基。之所以用葉子節點的概念,是為了**減少記憶體,在反向傳播結束之後,非葉子節點的梯度會被釋放掉** , 我們依然用上面的例子解釋: ```python import torch w = torch.tensor([1.],requires_grad = True) x = torch.tensor([2.],requires_grad = True) a = w+x b = w+1 y = a*b y.backward() print(w.is_leaf,x.is_leaf,a.is_leaf,b.is_leaf,y.is_leaf) print(w.grad,x.grad,a.grad,b.grad,y.grad) ``` 執行結果是: ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9e3a35fe92aa470abea0dfaa55de7655~tplv-k3u1fbpfcp-zoom-1.image) 可以看到只有x和w是葉子節點,然後反向傳播計算完梯度後(```.backward()```之後),只有葉子節點的梯度儲存下來了。 當然也可以通過```.retain_grad()```來保留非任意節點的梯度值。 ```python import torch w = torch.tensor([1.],requires_grad = True) x = torch.tensor([2.],requires_grad = True) a = w+x a.retain_grad() b = w+1 y = a*b y.backward() print(w.is_leaf,x.is_leaf,a.is_leaf,b.is_leaf,y.is_leaf) print(w.grad,x.grad,a.grad,b.grad,y.grad) ``` 執行結果: ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5db408ab4b664a8fa2e858c0a13e8b76~tplv-k3u1fbpfcp-zoom-1.image) ## 3. grad_fn ```torch.tensor```有一個屬性```grad_fn```,```grad_fn```的作用是記錄建立該張量時所用的函式,這個屬性反向傳播的時候會用到。例如在上面的例子中,```y.grad_fn=MulBackward0```,表示y是通過乘法得到的。所以求導的時候就是用乘法的求導法則。同樣的,```a.grad=AddBackward0```表示a是通過加法得到的,使用加法的求導法則。 ```python import torch w = torch.tensor([1.],requires_grad = True) x = torch.tensor([2.],requires_grad = True) a = w+x a.retain_grad() b = w+1 y = a*b y.backward() print(y.grad_fn) print(a.grad_fn) print(w.grad_fn) ``` 執行結果是: ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cb18da096ab4a2d951330b117daf205~tplv-k3u1fbpfcp-zoom-1.image) 葉子節點的```.grad_fn```是None。 ## 4 靜態圖 兩者的區別用一句話概括就是: - 動態圖:pytorch使用的,運算與搭建同時進行;靈活,易調節。 - 靜態圖:老tensorflow使用的,先搭建圖,後運算;高效,不靈活。 靜態圖我們是需要先定義好運算規則流程的。比方說,我們先給出 $a = x+w$ , $b=w+1$ , $y=a\times b$ 然後把上面的運算流程儲存下來,然後把w=1,x=2放到上面運算框架的入口位置進行運算。而動態圖是直接對著已經賦值的w和x進行運算,然後變運算變構建運算圖。 在一個課程http://cs231n.stanford.edu/slides/2018/cs231n_2018_lecture08.pdf中的第125頁,有這樣的一個對比例子: ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6382a7f3cba2491090c858fc6902f346~tplv-k3u1fbpfcp-zoom-1.image) 這個程式碼是Tensorflow的,構建運算的時候,先構建運算框架,然後再把具體的數字放入其中。整個過程類似於訓練神經網路,我們要構建好模型的結構,然後再訓練的時候再吧資料放到模型裡面去。又類似於在旅遊的時候,我們事先定要每天的行程路線,然後每天按照路線去行動。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/994c5368ed5a43c09fbd70e680792ce9~tplv-k3u1fbpfcp-zoom-1.image) 動態圖呢,就是直接對資料進行運算,然後動態的構建出運算圖。很符合我們的運算習慣。 兩者的區別在於,靜態圖先說明資料要怎麼計算,然後再放入資料。假設要放入50組資料,運算圖因為是事先構建的,所以每一次計算梯度都很快、高效;動態圖的運算圖是在資料計算的同時構建的,假設要放入50組資料,那麼就要生成50次運算圖。這樣就沒有那麼高效。所以稱為**動態圖**。 動態圖雖然沒有那麼高效,但是他的優點有以下: 1. 更容易除錯。 2. 動態計算更適用於自然語言處理。(這個可能是因為自然語言處理的輸入往往不定長?) 3. 動態圖更面向物件程式設計,我們會感覺更加