Pytorch反向傳播中的細節-計算梯度時的默認累加操作
Pytorch反向傳播計算梯度默認累加
今天學習pytorch實現簡單的線性回歸,發現瞭pytorch的反向傳播時計算梯度采用的累加機制, 於是百度來一下,好多博客都說瞭累加機制,但是好多都沒有說明這個累加機制到底會有啥影響, 所以我趁著自己練習的一個例子正好直觀的看一下以及如何解決:
pytorch實現線性回歸
先附上試驗代碼來感受一下:
torch.manual_seed(6) lr = 0.01 # 學習率 result = [] # 創建訓練數據 x = torch.rand(20, 1) * 10 y = 2 * x + (5 + torch.randn(20, 1)) # 構建線性回歸函數 w = torch.randn((1), requires_grad=True) b = torch.zeros((1), requires_grad=True) # 這裡是迭代過程,為瞭看pytorch的反向傳播計算梯度的細節,我先迭代兩次 for iteration in range(2): # 前向傳播 wx = torch.mul(w, x) y_pred = torch.add(wx, b) # 計算 MSE loss loss = (0.5 * (y - y_pred) ** 2).mean() # 反向傳播 loss.backward() # 這裡看一下反向傳播計算的梯度 print("w.grad:", w.grad) print("b.grad:", b.grad) # 更新參數 b.data.sub_(lr * b.grad) w.data.sub_(lr * w.grad)
上面的代碼比較簡單,迭代瞭兩次, 看一下計算的梯度結果:
w.grad: tensor([-74.6261])
b.grad: tensor([-12.5532])
w.grad: tensor([-122.9075])
b.grad: tensor([-20.9364])
然後我稍微加兩行代碼, 就是在反向傳播上面,我手動添加梯度清零操作的代碼,再感受一下結果:
torch.manual_seed(6) lr = 0.01 result = [] # 創建訓練數據 x = torch.rand(20, 1) * 10 #print(x) y = 2 * x + (5 + torch.randn(20, 1)) #print(y) # 構建線性回歸函數 w = torch.randn((1), requires_grad=True) #print(w) b = torch.zeros((1), requires_grad=True) #print(b) for iteration in range(2): # 前向傳播 wx = torch.mul(w, x) y_pred = torch.add(wx, b) # 計算 MSE loss loss = (0.5 * (y - y_pred) ** 2).mean() # 由於pytorch反向傳播中,梯度是累加的,所以如果不想先前的梯度影響當前梯度的計算,需要手動清0 if iteration > 0: w.grad.data.zero_() b.grad.data.zero_() # 反向傳播 loss.backward() # 看一下梯度 print("w.grad:", w.grad) print("b.grad:", b.grad) # 更新參數 b.data.sub_(lr * b.grad) w.data.sub_(lr * w.grad)
w.grad: tensor([-74.6261])
b.grad: tensor([-12.5532])
w.grad: tensor([-48.2813])
b.grad: tensor([-8.3831])
從上面可以發現,pytorch在反向傳播的時候,確實是默認累加上瞭上一次求的梯度, 如果不想讓上一次的梯度影響自己本次梯度計算的話,需要手動的清零。
但是, 如果不進行手動清零的話,會有什麼後果呢? 我在這次線性回歸試驗中,遇到的後果就是loss值反復的震蕩不收斂。下面感受一下:
torch.manual_seed(6) lr = 0.01 result = [] # 創建訓練數據 x = torch.rand(20, 1) * 10 #print(x) y = 2 * x + (5 + torch.randn(20, 1)) #print(y) # 構建線性回歸函數 w = torch.randn((1), requires_grad=True) #print(w) b = torch.zeros((1), requires_grad=True) #print(b) for iteration in range(1000): # 前向傳播 wx = torch.mul(w, x) y_pred = torch.add(wx, b) # 計算 MSE loss loss = (0.5 * (y - y_pred) ** 2).mean() # print("iteration {}: loss {}".format(iteration, loss)) result.append(loss) # 由於pytorch反向傳播中,梯度是累加的,所以如果不想先前的梯度影響當前梯度的計算,需要手動清0 #if iteration > 0: # w.grad.data.zero_() # b.grad.data.zero_() # 反向傳播 loss.backward() # 更新參數 b.data.sub_(lr * b.grad) w.data.sub_(lr * w.grad) if loss.data.numpy() < 1: break plt.plot(result)
上面的代碼中,我沒有進行手動清零,迭代1000次, 把每一次的loss放到來result中, 然後畫出圖像,感受一下結果:
接下來,我把手動清零的註釋打開,進行每次迭代之後的手動清零操作,得到的結果:
可以看到,這個才是理想中的反向傳播求導,然後更新參數後得到的loss值的變化。
總結
這次主要是記錄一下,pytorch在進行反向傳播計算梯度的時候的累加機制到底是什麼樣子? 至於為什麼采用這種機制,我也搜瞭一下,大部分給出的結果是這樣子的:
但是如果不想累加的話,可以采用手動清零的方式,隻需要在每次迭代時加上即可
w.grad.data.zero_() b.grad.data.zero_()
另外, 在搜索資料的時候,在一篇博客上看到兩個不錯的線性回歸時pytorch的計算圖在這裡借用一下:
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Pytorch中的backward()多個loss函數用法
- PyTorch 如何自動計算梯度
- PyTorch梯度下降反向傳播
- pytorch_detach 切斷網絡反傳方式
- pytorch 如何打印網絡回傳梯度