深度学习Pytorch--梯度与反向传播笔记
Pytorch梯度與反向傳播
- 相關概念
- 導數
- 偏導數
- 方向導數
- 梯度
- 梯度下降法
- 自動求梯度
- 概念
- Tensor
- 反向傳播求梯度
相關概念
先來理解一下從導數到梯度的相關概念。
導數
一元函數中導數就是該函數所代表的曲線在這一點上的切線斜率。
多元函數的導數可以稱為全導數,可以得到無數條曲線,每條曲線都有一條切線,每條切線與一個全導數相互對應,全導數可以通過偏導數計算。
偏導數
多元函數中的概念,通俗地講就是固定其他自變量所得到平面曲線的某一點的切線斜率。
方向導數
顧名思義,就是某一方向的導數。偏導數是對于水平和垂直平面來說的,而方向導數是針對各個不同方向的平面來說的。
關于以上導數如果還不懂的話可看:全導數、偏導數、方向導數
梯度
梯度是一個矢量(向量),方向與最大方向導數的方向一致,也就是使得導數最大的方向。
計算和表示方法如下圖:
梯度的值(模)為方向導數的最大值,感興趣可以直接在百度百科上看到。
強烈推薦文章:梯度與方向導數的關系(鏈接中有動態效果,可以更好地理解方向導數的幾何意義)
梯度下降法
深度學習通過最小化損失函數來尋找到模型的最優參數,通過梯度下降法來一步步的迭代來尋找損失函數的最小值。具體思想像上圖一樣,一次次沿著梯度的反方向就能達到最低點(即損失函數最小值),此時的參數就是我們要的最優參數(即最優解)。
自動求梯度
正片開始。
PyTorch提供的autograd包能夠根據輸入和前向傳播過程自動構建計算圖,并執行反向傳播。下面將介紹如何使用autograd包來進行自動求梯度的有關操作。
概念
Tensor是這個包的核心類,如果將其屬性.requires_grad設置為True,它將開始追蹤(track)在其上的所有操作(這樣就可以利用鏈式法則進行梯度傳播了)。完成計算后,可以調用.backward()來完成所有梯度計算。此Tensor的梯度將累積到.grad屬性中。
注意在y.backward()時,如果y是標量,則不需要為backward()傳入任何參數;否則,需要傳入一個與y同形的Tensor。后面會詳細解釋。
如果不想要被繼續追蹤,可以調用.detach()將其從追蹤記錄中分離出來,這樣就可以防止將來的計算被追蹤,這樣梯度就傳不過去了。此外,還可以用with torch.no_grad()將不想被追蹤的操作代碼塊包裹起來,這種方法在評估模型的時候很常用,因為在評估模型時,我們并不需要計算可訓練參數(requires_grad=True)的梯度。
Function是另外一個很重要的類。Tensor和Function互相結合就可以構建一個記錄有整個計算過程的有向無環圖(DAG)。每個Tensor都有一個.grad_fn屬性,該屬性即創建該Tensor的Function, 就是說該Tensor是不是通過某些運算得到的,若是,則grad_fn返回一個與這些運算相關的對象,否則是None。
下面通過一些例子來理解這些概念。
Tensor
創建一個Tensor并設置requires_grad=True:
x = torch.ones(2, 2, requires_grad=True) print(x) print(x.grad_fn)輸出:
tensor([[1., 1.],[1., 1.]], requires_grad=True) None再做一下運算操作:
y = x + 2 print(y) print(y.grad_fn)輸出:
tensor([[3., 3.],[3., 3.]], grad_fn=<AddBackward>) <AddBackward object at 0x1100477b8>注意x是直接創建的,所以它沒有grad_fn, 而y是通過一個加法操作創建的,所以它有一個為<AddBackward>的grad_fn。
像x這種直接創建的稱為葉子節點,葉子節點對應的grad_fn是None。
x = torch.ones(2, 2, requires_grad=True) y = x + 2 print(x.is_leaf, y.is_leaf) # True False再來點復雜度運算操作:
x = torch.ones(2, 2, requires_grad=True) y = x + 2 z = y * y * 3 out = z.mean() #計算平均值,返回一個標量 print(z, out)輸出:
tensor([[27., 27.],[27., 27.]], grad_fn=<MulBackward>) tensor(27., grad_fn=<MeanBackward1>)通過.requires_grad_()來用in-place的方式改變requires_grad屬性:
a = torch.randn(2, 2) # 缺失情況下默認 requires_grad = False a = ((a * 3) / (a - 1)) print(a.requires_grad) # False a.requires_grad_(True) print(a.requires_grad) # True b = (a * a).sum() print(b.grad_fn)輸出:
False True <SumBackward0 object at 0x118f50cc0>反向傳播求梯度
因為out是一個標量,所以調用backward()時不需要指定求導變量:
x = torch.ones(2, 2, requires_grad=True) y = x + 2 z = y * y * 3 out = z.mean() #計算平均值,返回一個標量 out.backward() # 等價于 out.backward(torch.tensor(1.))我們來看看out關于x的梯度 d(out)dx\frac{d(out)}{dx}dxd(out)?:
print(x.grad)輸出:
tensor([[4.5000, 4.5000],[4.5000, 4.5000]])我們令out為 ooo , 因為
o=14∑i=14zi=14∑i=143(xi+2)2o=\frac14\sum_{i=1}^4z_i=\frac14\sum_{i=1}^43(x_i+2)^2 o=41?i=1∑4?zi?=41?i=1∑4?3(xi?+2)2
所以
?o?xi∣xi=1=92=4.5\frac{\partial{o}}{\partial{x_i}}\bigr\rvert_{x_i=1}=\frac{9}{2}=4.5 ?xi??o?∣∣?xi?=1?=29?=4.5
所以上面的輸出是正確的。
數學上,如果有一個函數值和自變量都為向量的函數 y?=f(x?)\vec{y}=f(\vec{x})y?=f(x), 那么 y?\vec{y}y? 關于 x?\vec{x}x 的梯度就是一個雅可比矩陣(Jacobian matrix):
J=(?y1?x1??y1?xn????ym?x1??ym?xn)J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right) J=?????x1??y1????x1??ym????????xn??y1????xn??ym???????
而torch.autograd這個包就是用來計算一些雅克比矩陣的乘積的。例如,如果 vvv 是一個標量函數的 l=g(y?)l=g\left(\vec{y}\right)l=g(y?) 的梯度:
v=(?l?y1??l?ym)v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right) v=(?y1??l?????ym??l??)
那么根據鏈式法則我們有 lll 關于 x?\vec{x}x 的雅克比矩陣就為:
vJ=(?l?y1??l?ym)(?y1?x1??y1?xn????ym?x1??ym?xn)=(?l?x1??l?xn)v J=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right) \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)=\left(\begin{array}{ccc}\frac{\partial l}{\partial x_{1}} & \cdots & \frac{\partial l}{\partial x_{n}}\end{array}\right) vJ=(?y1??l?????ym??l??)?????x1??y1????x1??ym????????xn??y1????xn??ym???????=(?x1??l?????xn??l??)
注意:grad在反向傳播過程中是累加的(accumulated),這意味著每一次運行反向傳播,梯度都會累加之前的梯度,所以一般在反向傳播之前需把梯度清零。
x = torch.ones(2, 2, requires_grad=True) y = x + 2 z = y * y * 3 out = z.mean() #計算平均值,返回一個標量# 再來反向傳播一次,注意grad是累加的 out2 = x.sum() out2.backward() print(x.grad)out3 = x.sum() x.grad.data.zero_() out3.backward() print(x.grad)輸出:
tensor([[5.5000, 5.5000],[5.5000, 5.5000]]) tensor([[1., 1.],[1., 1.]])現在我們解釋之前留下的問題,為什么在y.backward()時,如果y是標量,則不需要為backward()傳入任何參數;否則,需要傳入一個與y同形的Tensor?
簡單來說就是為了避免向量(甚至更高維張量)對張量求導,而轉換成標量對張量求導。舉個例子,假設形狀為 m x n 的矩陣 X 經過運算得到了 p x q 的矩陣 Y,Y 又經過運算得到了 s x t 的矩陣 Z。那么按照前面講的規則,dZ/dY 應該是一個 s x t x p x q 四維張量,dY/dX 是一個 p x q x m x n的四維張量。問題來了,怎樣反向傳播?怎樣將兩個四維張量相乘???這要怎么乘???就算能解決兩個四維張量怎么乘的問題,四維和三維的張量又怎么乘?導數的導數又怎么求,這一連串的問題,感覺要瘋掉……
為了避免這個問題,我們不允許張量對張量求導,只允許標量對張量求導,求導結果是和自變量同形的張量。所以必要時我們要把張量通過將所有張量的元素加權求和的方式轉換為標量,舉個例子,假設y由自變量x計算而來,w是和y同形的張量,則y.backward(w)的含義是:先計算l = torch.sum(y * w),則l是個標量,然后求l對自變量x的導數。
參考
來看一些實際例子。
x = torch.tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True) y = 2 * x z = y.view(2, 2) print(z)輸出:
tensor([[2., 4.],[6., 8.]], grad_fn=<ViewBackward>)現在 z 不是一個標量,所以在調用backward時需要傳入一個和z同形的權重向量進行加權求和得到一個標量。
v = torch.tensor([[1.0, 0.1], [0.01, 0.001]], dtype=torch.float) z.backward(v) print(x.grad)輸出:
tensor([2.0000, 0.2000, 0.0200, 0.0020])注意,x.grad是和x同形的張量。
再來看看中斷梯度追蹤的例子:
x = torch.tensor(1.0, requires_grad=True) y1 = x ** 2 with torch.no_grad():y2 = x ** 3 y3 = y1 + y2print(x.requires_grad) print(y1, y1.requires_grad) # True print(y2, y2.requires_grad) # False print(y3, y3.requires_grad) # True輸出:
True tensor(1., grad_fn=<PowBackward0>) True tensor(1.) False tensor(2., grad_fn=<ThAddBackward>) True可以看到,上面的y2是沒有grad_fn而且y2.requires_grad=False的,而y3是有grad_fn的。如果我們將y3對x求梯度的話會是多少呢?
y3.backward() print(x.grad)輸出:
tensor(2.)為什么是2呢?y3=y1+y2=x2+x3y_3 = y_1 + y_2 = x^2 + x^3y3?=y1?+y2?=x2+x3,當 x=1x=1x=1 時 dy3dx\frac {dy_3} {dx}dxdy3?? 不應該是5嗎?事實上,由于 y2y_2y2? 的定義是被torch.no_grad():包裹的,所以與 y2y_2y2? 有關的梯度是不會回傳的,只有與 y1y_1y1? 有關的梯度才會回傳,即 x2x^2x2 對 xxx 的梯度。
上面提到,y2.requires_grad=False,所以不能調用 y2.backward(),會報錯:
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn此外,如果我們想要修改tensor的數值,但是又不希望被autograd記錄(即不會影響反向傳播),那么我么可以對tensor.data進行操作。
x = torch.ones(1,requires_grad=True)print(x.data) # 還是一個tensor print(x.data.requires_grad) # 但是已經是獨立于計算圖之外y = 2 * x x.data *= 100 # 只改變了值,不會記錄在計算圖,所以不會影響梯度傳播y.backward() print(x) # 更改data的值也會影響tensor的值 print(x.grad)輸出:
tensor([1.]) False tensor([100.], requires_grad=True) tensor([2.])本文參考鏈接:
動手學習深度學習Pytorch
梯度與方向導數的關系
全導數、偏導數、方向導數
梯度-百度百科
總結
以上是生活随笔為你收集整理的深度学习Pytorch--梯度与反向传播笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中parse.add_arg
- 下一篇: 黑化网名80个