之前在用TF,目前想嘗試Pytorch,剛接觸不久感覺兩者的建模流程基本一致,並沒有感覺哪裡體現了動態計算圖,網上的一些介紹也不是特別清除,希望大家可以解釋下這一點,或者有相關的比較好的資源也可以分享下,哈哈


可以看看stanford的Fei-Fei Li大佬的課件,裡面詳細介紹了PyTorch和TensorFlow,並比較了兩者的不同。課件的126-134頁是關於靜態和動態圖的區別,主要的區別如下:

TensorFlow: Build graph once, then run many times (static)

PyTorch: Each forward pass defines a new graph (dynamic)

http://cs231n.stanford.edu/slides/2018/cs231n_2018_lecture08.pdf?

cs231n.stanford.edu


請參考 @墨雨蕭軒 的回答


說一些在實際應用中自己的感受:torch&>=0.4 tensorflow&>=1.4 未使用2.0

第一個區別:

TF中在class裡面就通過名稱定義好了forward的流程,並通過placeholder留下輸入介面

之後run起來只要feed數據進去就可以了,如果網路shape不match, class init的時候就會報錯。

torch不同,他留下了函數介面,用戶自己實現輸入是什麼,如果網路shape不match, 在調用forward時才會報錯。

第二個區別:

因為tensorflow靜態圖,只留下feed介面,而圖建立完畢之後,本身的數據流向無法觀測,無法在任何一個name node debug(1.4),只能通過print列印shape諸如此類。

torch是動態生產的數據流向,自然可以隨時隨地在某一個時刻停下來debug。


1、計算圖

計算圖是用來描述運算的有向無環圖;

計算圖有兩個主要元素:結點(Node)和邊(Edge);

結點表示數據,如向量、矩陣、張量,邊表示運算,如加減乘除卷積等;

用計算圖表示:y = ( x + w ) ? ( w + 1 ) y = (x + w) * (w + 1)y=(x+w)?(w+1)

令a = x + w a=x+wa=x+w,b = w + 1 b=w+1b=w+1,y = a ? b y=a*by=a?b,那麼得到的計算圖如下所示:

採用計算圖來描述運算的好處不僅僅是讓運算更加簡潔,還有一個更加重要的作用是使梯度求導更加方便。舉個例子,看一下y對w求導的一個過程。

計算圖與梯度求導

y = ( x + w ) ? ( w + 1 ) y = (x + w) * (w + 1)y=(x+w)?(w+1)

a = x + w a=x+wa=x+w b = w + 1 b=w+1b=w+1y = a ? b y=a*by=a?b

? y ? w = ? y ? a ? a ? w + ? y ? b ? b ? 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}?w?y?=?a?y??w?a?+?b?y??w?b?

= b ? 1 + a ? 1 =b*1+a*1=b?1+a?1= b + a =b+a=b+a= ( w + 1 ) + ( x + w ) =(w+1)+(x+w)=(w+1)+(x+w)= 2 ? w + x + 1 =2*w+x+1=2?w+x+1= 2 ? 1 + 2 + 1 =2*1+2+1=2?1+2+1= 5 =5=5

通過鏈式求導可以知道,利用計算圖推導得到的推導結果如下圖所示:

通過分析可以知道,y對w求導就是在計算圖中找到所有y到w的路徑,把路徑上的導數進行求和。

利用代碼看一下y對w求導之後w的梯度是否是上面計算得到的。具體的代碼如下所示:

import torch

w = torch.tensor([1.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True
x = torch.tensor([2.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True

a = torch.add(w, x) # a = w + x
b = torch.add(w, 1) # b = w + 1
y = torch.mul(a, b) # y = a * b

y.backward() #對y進行反向傳播
print(w.grad) #輸出w的梯度

得到的結果為5,證明了上面的結論。

在第一篇博文中講張量的屬性的時候,講到與梯度相關的四個屬性的時候,有一個is_leaf,也就是葉子節點,葉子節點的功能是指示張量是否是葉子節點。

葉子節點:用戶創建的結點稱為葉子結點,如X與W;

is_leaf:指示張量是否為葉子節點;

葉子節點是整個計算圖的根基,例如前面求導的計算圖,在前向傳導中的a、b和y都要依據創建的葉子節點x和w進行計算的。同樣,在反向傳播過程中,所有梯度的計算都要依賴葉子節點。

設置葉子節點主要是為了節省內存,在梯度反向傳播結束之後,非葉子節點的梯度都會被釋放掉。可以根據代碼分析一下非葉子節點a、b和y的梯度情況。

import torch

w = torch.tensor([1.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True
x = torch.tensor([2.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True

a = torch.add(w, x) # a = w + x
b = torch.add(w, 1) # b = w + 1
y = torch.mul(a, b) # y = a * b

y.backward() #對y進行反向傳播
print(w.grad) #輸出w的梯度

#查看葉子結點
print("is_leaf:
", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf) #輸出為True True False False False,只有前面兩個是葉子節點

#查看梯度
print("gradient:
", w.grad, x.grad, a.grad, b.grad, y.grad) #輸出為tensor([5.]) tensor([2.]) None None None,因為非葉子節點都被釋放掉了

如果想使用非葉子結點梯度,可以使用pytorch中的retain_grad()。例如對上面代碼中的a執行相關操作a.retain_grad(),則a的梯度會被保留下來,具體的代碼如下所示:

import torch

w = torch.tensor([1.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True
x = torch.tensor([2.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True

a = torch.add(w, x) # a = w + x
a.retain_grad() #保存非葉子結點a的梯度,輸出為tensor([5.]) tensor([2.]) tensor([2.]) None None
b = torch.add(w, 1) # b = w + 1
y = torch.mul(a, b) # y = a * b

y.backward() #對y進行反向傳播
print(w.grad) #輸出w的梯度

#查看葉子結點
print("is_leaf:
", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)

#查看梯度
print("gradient:
", w.grad, x.grad, a.grad, b.grad, y.grad)

torch.Tensor中還有一個屬性為grad_fn,grad_fn的作用是記錄創建該張量時所用的方法(函數),該屬性在梯度反向傳播的時候用到。例如在上面提到的例子中,y.grad_fn = ,y在反向傳播的時候會記錄y是用乘法得到的,所用在求解a和b的梯度的時候就會用到乘法的求導法則去求解a和b的梯度。同樣,對於a有a.grad_fn=,對於b有b.grad_fn=,由於a和b是通過加法得到的,所以grad_fn都是AddBackword0。可以通過代碼觀看各個變數的屬性。

import torch

w = torch.tensor([1.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True
x = torch.tensor([2.], requires_grad=True) #由於需要計算梯度,所以requires_grad設置為True

a = torch.add(w, x) # a = w + x
a.retain_grad()
b = torch.add(w, 1) # b = w + 1
y = torch.mul(a, b) # y = a * b

y.backward() #對y進行反向傳播
print(w.grad) #輸出w的梯度

# 查看 grad_fn
print("grad_fn:
", w.grad_fn, x.grad_fn, a.grad_fn, b.grad_fn, y.grad_fn)

#上面代碼的輸出結果為
grad_fn:
None None & & &

可以看到w和x的grad_fn都是None,因為w和x都是用戶創建的,沒有通過任何方法任何函數去生成這兩個張量,所以兩個葉子節點的屬性為None,這些屬性都是在梯度求導中用到的。

2、pytorch的動態圖機制

動態圖 vs 靜態圖

動態圖:pytorch使用的,運算與搭建同時進行;靈活,易調節。

靜態圖:tensorflow使用的,先搭建圖,後運算;高效,不靈活。

根據計算圖搭建方式,可將計算圖分為動態圖和靜態圖。

為了儘快理解靜態圖和動態圖的區別,這裡舉一個例子。假如我們去新馬泰旅遊,如果我們是跟團的話就是靜態圖,如果是自駕游的話就是動態圖。跟團的意思是路線都已經計劃好了,也就是先建圖後運算。自駕游的話可以根據實際情況實際調整。下面分別列舉tensorflow的靜態圖例子和pytorch的動態圖實例進行簡單理解。

在上面這個圖中,框框代表的就是節點,帶箭頭的線代表邊。tensorflow使用的是靜態圖,是先將圖搭建好之後,再input數據進去。

pytorch使用的是動態圖,具體的操作如下代碼:

W_h = torch.randn(20, 20, requires_grad=True) #先創建四個張量
W_x = torch.randn(20, 10, requires_grad=True)
x = torch.randn(1, 10)
prev_h = torch.randn(1, 20)

h2h = torch.mm(W_h, prev_h.t()) #將W_h和prev_h進行相乘,得到一個新張量h2h
i2h = torch.mm(W_x, x.t()) #將W_x和x進行相乘,等到一個新張量i2h
next_h = h2h + i2h #創建加法操作
next_h = next_h.tanh() #使用激活函數

loss = next_h.sum() #計算損失函數
loss.backward() #梯度反向傳播

上面代碼對應的動態圖過程就是下面的圖

動態圖的搭建是根據每一步的計算搭建的,而tensorflow是先搭建所有的計算圖之後,再把數據輸入進去。這就是動態圖和靜態圖的區別。

如果有朋友需要用GPU,可以在智星雲租用GPU,性價比很高。現在還可分時租用,3080現在也有了

發佈於 2020-10-22繼續瀏覽內容知乎發現更大的世界打開Chrome繼續匿名用戶匿名用戶

計算流與輸入內容相關為動態圖,只與輸入類型相關是靜態圖。

PyTorch可以看做解釋型語言,以python本身為runtime,按步就班地執行每一個語句(運算元),沒有全局的圖信息,從而損失了很多優化的機會(顯存優化類比於寄存器分配,需要做一遍全圖的liveness analysis)。優勢在於靈活,比如根據矩陣某一個位置的值決定接下來進行哪一個分支;1.0之後的推出torchscript則是靜態化的一個嘗試,對於python的某個子集,支持編譯到torchscript,其上可以進行一些編譯優化。

Tensorflow (1.0)是編譯型語言,相當於在Python里定義一個tensor上的domain specific language,會根據輸入的類型和計算圖編譯出相應的執行程序(中間可能進行了大量的編譯器優化),理論上性能(速度和顯存)都可以達到更優,但是損失了靈活性。

可以類比於寫python和寫C++。


計算流與輸入內容相關為動態圖,只與輸入類型相關是靜態圖。

PyTorch可以看做解釋型語言,以python本身為runtime,按步就班地執行每一個語句(運算元),沒有全局的圖信息,從而損失了很多優化的機會(顯存優化類比於寄存器分配,需要做一遍全圖的liveness analysis)。優勢在於靈活,比如根據矩陣某一個位置的值決定接下來進行哪一個分支;1.0之後的推出torchscript則是靜態化的一個嘗試,對於python的某個子集,支持編譯到torchscript,其上可以進行一些編譯優化。

Tensorflow (1.0)是編譯型語言,相當於在Python里定義一個tensor上的domain specific language,會根據輸入的類型和計算圖編譯出相應的執行程序(中間可能進行了大量的編譯器優化),理論上性能(速度和顯存)都可以達到更優,但是損失了靈活性。

可以類比於寫python和寫C++。


前向傳播時建立圖,後向傳播時銷毀圖,可以叫GraphFlow : )


謝邀。。

動態計算圖在RNN循環網路中使用比較廣泛。區別比較大的地方就是如果我們需要使用網路中間層作為判斷依據來構建後面的網路。動態圖就可以用類似Python執行順序來編程,而靜態圖演算法則需要使用特殊的框架語句,執行效率和方便程度都較動態圖差很多。


計算的時候可以列印。


推薦閱讀:
相关文章