本章代碼:https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson1/computational_graph.py
計(jì)算圖
深度學(xué)習(xí)就是對(duì)張量進(jìn)行一系列的操作离唐,隨著操作種類和數(shù)量的增多问窃,會(huì)出現(xiàn)各種值得思考的問題。比如多個(gè)操作之間是否可以并行贮竟,如何協(xié)同底層的不同設(shè)備咕别,如何避免冗余的操作写穴,以實(shí)現(xiàn)最高效的計(jì)算效率,同時(shí)避免一些 bug啊送。因此產(chǎn)生了計(jì)算圖 (Computational Graph)。
計(jì)算圖是用來描述運(yùn)算的有向無環(huán)圖昔逗,有兩個(gè)主要元素:節(jié)點(diǎn) (Node) 和邊 (Edge)勾怒。節(jié)點(diǎn)表示數(shù)據(jù),如向量笔链、矩陣腮猖、張量。邊表示運(yùn)算澈缺,如加減乘除卷積等。
用計(jì)算圖表示:姐赡,如下所示:
<div align="center"><img src="https://image.zhangxiann.com/20200515221509.png"/></div>
可以看作, 锁施,其中
,
肩狂。
計(jì)算圖與梯度求導(dǎo)
這里求 對(duì)
的導(dǎo)數(shù)姥饰。根復(fù)合函數(shù)的求導(dǎo)法則列粪,可以得到如下過程。
體現(xiàn)到計(jì)算圖中态蒂,就是根節(jié)點(diǎn) 到葉子節(jié)點(diǎn)
有兩條路徑
y -> a -> w
和y ->b -> w
钾恢。根節(jié)點(diǎn)依次對(duì)每條路徑的孩子節(jié)點(diǎn)求導(dǎo)鸳址,一直到葉子節(jié)點(diǎn)w
,最后把每條路徑的導(dǎo)數(shù)相加即可疹瘦。
<div align="center"><img src="https://image.zhangxiann.com/20200515221816.png"/></div>
代碼如下:
import torch
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
# y=(x+w)*(w+1)
a = torch.add(w, x) # retain_grad()
b = torch.add(w, 1)
y = torch.mul(a, b)
# y 求導(dǎo)
y.backward()
# 打印 w 的梯度巡球,就是 y 對(duì) w 的導(dǎo)數(shù)
print(w.grad)
結(jié)果為tensor([5.])
辕漂。
我們回顧前面說過的 Tensor 中有一個(gè)屬性is_leaf
標(biāo)記是否為葉子節(jié)點(diǎn)吴超。
<div align="center"><img src="https://image.zhangxiann.com/20200515145801.png"/></div>
在上面的例子中, 和
是葉子節(jié)點(diǎn)跋涣,其他所有節(jié)點(diǎn)都依賴于葉子節(jié)點(diǎn)鸟悴。葉子節(jié)點(diǎn)的概念主要是為了節(jié)省內(nèi)存细诸,在計(jì)算圖中的一輪反向傳播結(jié)束之后,非葉子節(jié)點(diǎn)的梯度是會(huì)被釋放的利赋。
代碼示例:
# 查看葉子結(jié)點(diǎn)
print("is_leaf:\n", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)
# 查看梯度
print("gradient:\n", w.grad, x.grad, a.grad, b.grad, y.grad)
結(jié)果為:
is_leaf:
True True False False False
gradient:
tensor([5.]) tensor([2.]) None None None
非葉子節(jié)點(diǎn)的梯度為空,如果在反向傳播結(jié)束之后仍然需要保留非葉子節(jié)點(diǎn)的梯度中燥,可以對(duì)節(jié)點(diǎn)使用retain_grad()
方法塘偎。
而 Tensor 中的 grad_fn 屬性記錄的是創(chuàng)建該張量時(shí)所用的方法 (函數(shù))。而在反向傳播求導(dǎo)梯度時(shí)需要用到該屬性咱扣。
示例代碼:
# 查看梯度
print("w.grad_fn = ", w.grad_fn)
print("x.grad_fn = ", x.grad_fn)
print("a.grad_fn = ", a.grad_fn)
print("b.grad_fn = ", b.grad_fn)
print("y.grad_fn = ", y.grad_fn)
結(jié)果為
w.grad_fn = None
x.grad_fn = None
a.grad_fn = <AddBackward0 object at 0x000001D8DDD20588>
b.grad_fn = <AddBackward0 object at 0x000001D8DDD20588>
y.grad_fn = <MulBackward0 object at 0x000001D8DDD20588>
PyTorch 的動(dòng)態(tài)圖機(jī)制
PyTorch 采用的是動(dòng)態(tài)圖機(jī)制 (Dynamic Computational Graph)偏窝,而 Tensorflow 采用的是靜態(tài)圖機(jī)制 (Static Computational Graph)。
動(dòng)態(tài)圖是運(yùn)算和搭建同時(shí)進(jìn)行祭往,也就是可以先計(jì)算前面的節(jié)點(diǎn)的值,再根據(jù)這些值搭建后面的計(jì)算圖硼补。優(yōu)點(diǎn)是靈活熏矿,易調(diào)節(jié),易調(diào)試褪储。PyTorch 里的很多寫法跟其他 Python 庫的代碼的使用方法是完全一致的鲤竹,沒有任何額外的學(xué)習(xí)成本昔榴。
靜態(tài)圖是先搭建圖,然后再輸入數(shù)據(jù)進(jìn)行運(yùn)算吱肌。優(yōu)點(diǎn)是高效仰禽,因?yàn)殪o態(tài)計(jì)算是通過先定義后運(yùn)行的方式,之后再次運(yùn)行的時(shí)候就不再需要重新構(gòu)建計(jì)算圖规揪,所以速度會(huì)比動(dòng)態(tài)圖更快粒褒。但是不靈活。TensorFlow 每次運(yùn)行的時(shí)候圖都是一樣的祥款,是不能夠改變的月杉,所以不能直接使用 Python 的 while 循環(huán)語句,需要使用輔助函數(shù) tf.while_loop 寫成 TensorFlow 內(nèi)部的形式桨昙。
參考資料
如果你覺得這篇文章對(duì)你有幫助蛙酪,不妨點(diǎn)個(gè)贊,讓我有更多動(dòng)力寫出好文章翘盖。
我的文章會(huì)首發(fā)在公眾號(hào)上桂塞,歡迎掃碼關(guān)注我的公眾號(hào)張賢同學(xué)。
<div align="center"><img src="https://image.zhangxiann.com/QRcode_8cm.jpg"/></div>