Pytorch Autograd (自動求導機制)
Introduce
Pytorch Autograd庫 (自動求導機制) 是訓練神經網絡時,反向誤差傳播(BP)算法的核心。
本文通過logistic回歸模型來介紹Pytorch的自動求導機制。首先,本文介紹了tensor與求導相關的屬性。其次,通過logistic回歸模型來幫助理解BP算法中的前向傳播以及反向傳播中的導數計算。
以下均為初學者筆記。
Tensor Attributes Related to Derivation
note: 以下用x代表創建的tensor張量。
- x.requires_grad:True or False,用來指明該張量在反向傳播過程中是否需要求導。
- with torch.no_grad()::當我們在做模型評估的時候是不需要求導的,可以嵌套一層with torch.no_grad()以減少可能的計算和內存開銷。
- x.grad:返回損失函數對該張量求偏導的值,在調用backward()之后才有。
- x.grad_fn:存儲計算圖上某中間節點進行的操作,如加減乘除等,用於指導反向傳播時loss對該節點的求偏導計算。
- x.is_leaf:True or False,用於判斷某個張量在計算圖中是否是葉子張量。葉子張量我個人認為可以理解為目標函數中非中間因變量(中間函數、一般是運算得到的張量),如神經網絡中的權值參數w就是葉子張量(一般是手動創建的張量)。
- x.detach():返回tensor的數據以及requires_grad屬性,且返回的tensor與原始tensor共享存儲空間,即一個改變會導致另外一個改變。因此,如果我們在backward之前對x.detach()返回的張量進行改變會導致原始x的改變,從而導致求導錯誤,但是這時系統會報錯提醒。
(note:雖然x.data也與x.detach()作用相似,但是x.data不被Autograd系統追蹤,因此如果遇到上述問題並不會報錯。推薦使用x.detach()) - x.item():如果張量只包含一個元素,即標量(0維張量,scalar),可以用x.item()返回,通常loss只包含一個數值,因此常用loss.item()。
- x.tolist():如果張量只包含多個元素,可以用x.tolist()轉換成python list返回。
Build Logistic regression Model
假設有一個損失函數如下(Logistic回歸):
\[z = w1x1+w2x2+b \]
\[y_p = sigmoid(z) \]
\[Loss(y_p,y_t) = -{1\over n}\sum_{i=1}^n (y_tlog(y_p)+(1-y_t)log(1-y_p)) \]
由損失函數構建簡單計算圖模型如下:
現在我們通過上述例子來理解前向傳播和反向傳播。在上述簡單的神經網絡模型中,我們需要對權值參數w1,w2以及閾值參數b進行更新。神經網絡訓練的總體過程如下:先由輸入層逐級前向傳播計算loss輸出,再有輸出層loss反向計算各層梯度傳播誤差,以此更新各層的權值參數w和閾值參數b。
在該模型中我們需要求出loss對w1、w2以及b的偏導,以此利用SGD更新各參數。對於根據鏈式法則的逐級求導過程不再贅述,吳恩達機器學習SGD部分有詳細的計算過程以及解釋。
現在我們利用pytorch實現logistic回歸模型,並手動實現參數更新。
import torch
import numpy as np
# 可能網絡上有許多資料在Tensor外再額外封裝一層Variable,然而從Pytorch0.4版本以后,Variable已經合並進Tensor了,因此以后的代碼可以直接用Tensor,不必要再封裝一層Variable。
# 讀入數據 x_t,y_t
x_t = torch.tensor(np.array([[1,1],[1,0],[0,1],[0,0]]),requires_grad=False,dtype=torch.float)
y_t = torch.tensor([[0],[1],[0],[1]],requires_grad=False,dtype=torch.float)
print(x_t.size())
# 定義權值參數w和閾值參數b
w = torch.randn([2,1], requires_grad=True,dtype=torch.float)
b = torch.zeros(1, requires_grad=True,dtype=torch.float)
print(w.size())
# 構建邏輯回歸模型
def logistic_model(x_t):
a = torch.matmul(x_t,w) + b
return torch.sigmoid(a)
y_p = logistic_model(x_t)
# 計算誤差
def get_loss(y_p, y_t):
return -torch.mean(y_t * torch.log(y_p)+(1-y_t) * torch.log(1-y_p))
loss = get_loss(y_p, y_t)
print(loss)
# 自動求導
loss.backward()
# note:如果loss為一個標量(一般都是),那么loss.backward()不需要指定任何參數。然而如果有多個損失,即loss為一個向量tensor([loss1, loss2,loss3]),則需要指定一個gradient參數,它是與loss形狀匹配的張量,如tensor([1.0,1.0,0.1]),里面數字我個人理解為代表各損失的權重。
# 查看 w 和 b 的梯度
print(w.grad)
print(b.grad)
# 更新一次參數
w.data = w.data - 1e-2 * w.grad.data
b.data = b.data - 1e-2 * b.grad.data
'''
note:
存在兩個問題:
1. 如果沒有前面先更新一次參數,后面直接進行迭代更新的話,會報錯,具體原因也沒搞懂。
2. 利用pycharm運行pytorch代碼,調用了backward()之后,程序運行完成進程並不會終止,需要手動到任務管理器中kill進程,具體原因也不清楚。
'''
# epoch
for e in range(10000): # 進行 10000 次更新
y_p = logistic_model(x_t)
loss = get_loss(y_p, y_t)
w.grad.zero_() # 歸零梯度
b.grad.zero_() # 歸零梯度
loss.backward()
w.data = w.data - 1e-2 * w.grad.data # 更新 w
b.data = b.data - 1e-2 * b.grad.data # 更新 b
print('epoch: {}, loss: {}'.format(e, loss.data.item()))
print(w)
print(b)
'''
每500次迭代打印出輸出結果,我們看到損失函數在迭代中逐步下降:
epoch: 0, loss: 0.9426676034927368
epoch: 500, loss: 0.5936437249183655
epoch: 1000, loss: 0.4318988025188446
epoch: 1500, loss: 0.33194077014923096
epoch: 2000, loss: 0.265964150428772
epoch: 2500, loss: 0.22003984451293945
epoch: 3000, loss: 0.18663322925567627
epoch: 3500, loss: 0.1614413857460022
epoch: 4000, loss: 0.14187511801719666
epoch: 4500, loss: 0.12630191445350647
epoch: 5000, loss: 0.11365044862031937
epoch: 5500, loss: 0.10319262742996216
epoch: 6000, loss: 0.09441888332366943
epoch: 6500, loss: 0.08696318417787552
epoch: 7000, loss: 0.08055643737316132
epoch: 7500, loss: 0.07499672472476959
epoch: 8000, loss: 0.07013023644685745
epoch: 8500, loss: 0.06583743542432785
epoch: 9000, loss: 0.06202460825443268
epoch: 9500, loss: 0.05861698091030121
至此,手動實現梯度下降,logistic模型搭建完成,之后將嘗試利用pytorch框架搭建神經網絡。
'''