Pytorch學習(一)---Tensor與Autograd

前言

在神經網絡中,一個重要的內容就是進行參數學習,而參數學習離不開求導,那麼pytorch是如何進行求導的呢?
現在大部分深度學習架構都有自動求導的功能,pytorch也不例外,torch.autograd包就是用來自動求導的。Autograd包爲張量上的所有操作提供了自動求導功能,torch.Tensor 和torch.Function爲Autograd的兩個核心類,它們相互連接並生成一個有向非循環圖

自動求導要點

爲了實現對Tensor自動求導,需考慮如下事項:

  1. 創建葉子節點的Tensor,使用requires_grad參數指定是否記錄對其的操作,以便之後利用backward()方法進行梯度求解。requires_grad參數的缺省值爲False,如果要對其求導需設置爲true,然後與之有依賴關係的節點會自動變爲True。
  2. 可利用requires_grad()方法修改Tensor的requires_grad屬性。
    可以調用.detach()或者with torch.no_grad:,將不再計算張量的梯度、跟蹤張量的歷史記錄。這點在評估模型、測試模型階段中常常用到。
  3. 通過運算創建的Tensor(即非葉子節點),會被自動賦予grad_fn屬性,該屬性表示梯度函數。葉子節點的grad_fn爲None。
  4. 最後得到的Tensor執行backward(),此時自動計算各變量的梯度,並將結果保存的grad屬性中。計算完成後,非葉子節點的梯度自動釋放。
  5. backward()函數接收參數,該參數應和調用backward()函數的Tensor的維度相同,或者是可broadcast的維度。如果求導的Tensor爲標量(即是一個數字),backward中的參數可省略。
  6. 反向傳播的中間緩存會被清空,如果需要多次反向傳播,需要指定backward中的參數retain_graph=True。多次反向傳播,梯度是累加的。
  7. 非葉子節點的梯度backward調用後即被清空
  8. 可以通過 torch.no_grad()包裹代碼塊的形式,來阻止autograd去跟蹤那些標記爲.requires_grad=True的張量的歷史記錄。這步在測試階段經常使用。
    在整個過程中,pytorch採用計算圖的形式進行組織,該計算圖爲動態圖,且在每次前向傳播時將重新構建。其他深度學習框架,如tensorflow,keras一般爲靜態圖。

計算圖

計算圖是一種有向無環圖像,用圖形方式來表示算子與變量之間的關係,直觀高效。如圖所示,圓形表示變量,矩形表示算子。
在這裏插入圖片描述如表達式:z=wx+b{\rm{z}} = wx + b,可寫成兩個表達式:y=wxy = wx,則z=y+bz = y + b,其中x、w、b爲變量,是用戶創建的變量,不依賴與其他變量,故又稱爲葉子節點。爲了計算各葉子節點的梯度,需要把對應的張量參數requires_grad屬性設置爲True,這樣就可以自動跟蹤其歷史記錄。y、z是計算得到的變量,非葉子節點,z爲根節點。mul和add是算子(或操作或函數)。由這些變量及算子,就構成一個完整的計算過程或前向傳播過程。

我們的目標是更新各葉子節點的梯度,根據符合函數鏈式求導法則,不難算出各葉子節點的梯度。
zx=zyyx=w\frac{{\partial z}}{{\partial x}} = \frac{{\partial z}}{{\partial y}}\frac{{\partial y}}{{\partial x}} = w
zw=zyyw=x\frac{{\partial z}}{{\partial w}} = \frac{{\partial z}}{{\partial y}}\frac{{\partial y}}{{\partial w}} = x
zb=1\frac{{\partial z}}{{\partial b}} = 1
pyorch調用 backward()方法,將自動計算個節點的梯度,這是一個反向傳播過程,從當前根節點z反向溯源,利用倒數鏈式法則,計算所有葉子節點的梯度,其梯度值將累加到grad屬性中。對非葉子節點的操作或function記錄在grad_fn屬性中,葉子節點的grad_fn爲None。

標量反向傳播

假設x、w、b都是標量,z=wx+b{\rm{z}} = wx + b,對標量z調用backward()方法,此時無需對backward()傳入參數。以下是實現自動求導的主要步驟:

  1. 定義葉子節點及算子節點
import torch

# 定義輸入張量
x = torch.Tensor([2])
# 初始化權重參數w 偏移量b 並設置require_grad的屬性爲True,爲自動求導
w = torch.randn(1,requires_grad=True)
b = torch.randn(1,requires_grad=True)
# 實現前向傳播
y = torch.mul(w,x)
z = torch.add(y,b)
# 查看x,w,b葉子節點的requires_grad屬性
print("x,w,b的requires_grad屬性分別爲:{},{},{}".format(x.requires_grad,w.requires_grad,b.requires_grad))
# 運行結果:x,w,b的requires_grad屬性分別爲:False,True,True
  1. 查看葉子節點、非葉子節點的其他屬性
# 查看葉子節點、非葉子節點的其他屬性
print("y,z的requires_grad屬性分別爲:{},{}".format(y.requires_grad,z.requires_grad))

# 查看各節點是否爲葉子節點
print("x,w,b,y,z的是否爲葉子節點{},{},{},{},{}".format(x.is_leaf,w.is_leaf,b.is_leaf,y.is_leaf,z.is_leaf))
# 查看葉子節點的grad_fn屬性
print("x,w,b的grad_fn屬性:{},{},{}".format(x.grad_fn,w.grad_fn,b.grad_fn))
# 查看非葉子節點的grad_fn屬性
print("y,z的grad_fn屬性:{},{}".format(y.grad_fn,z.grad_fn))
  1. 自動求導,實現梯度反向傳播
z.backward()
# 查看葉子節點的梯度,x是葉子節點,但他無需求導,所以其梯度爲none
print("w,b的梯度分別爲{},{},{}".format(w.grad,b.grad,x.grad))
# 查看非葉子節點的梯度,執行backward後,會自動清空
print("y,z的梯度分別爲{},{}".format(y.grad,z.grad))

# 輸出爲
#x,w,b,y,z的是否爲葉子節點True,True,True,False,False
#x,w,b的grad_fn屬性:None,None,None
#y,z的grad_fn屬性:<MulBackward0 object at 0x000002545C745BE0>,<AddBackward0 object at 0x000002545C745C88>
#w,b的梯度分別爲tensor([2.]),tensor([1.]),None
#y,z的梯度分別爲None,None
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章