# 2.3 自動求梯度
在深度學習中,我們經常需要對函數求梯度(gradient)。PyTorch提供的[autograd](https://pytorch.org/docs/stable/autograd.html)包能夠根據輸入和前向傳播過程自動構建計算圖,并執行反向傳播。本節將介紹如何使用autograd包來進行自動求梯度的有關操作。
## 2.3.1 概念
上一節介紹的`Tensor`是這個包的核心類,如果將其屬性`.requires_grad`設置為`True`,它將開始追蹤(track)在其上的所有操作(這樣就可以利用鏈式法則進行梯度傳播了)。完成計算后,可以調用`.backward()`來完成所有梯度計算。此`Tensor`的梯度將累積到`.grad`屬性中。
> 注意在`y.backward()`時,如果`y`是標量,則不需要為`backward()`傳入任何參數;否則,需要傳入一個與`y`同形的`Tensor`。解釋見 2.3.2 節。
如果不想要被繼續追蹤,可以調用`.detach()`將其從追蹤記錄中分離出來,這樣就可以防止將來的計算被追蹤,這樣梯度就傳不過去了。此外,還可以用`with torch.no_grad()`將不想被追蹤的操作代碼塊包裹起來,這種方法在評估模型的時候很常用,因為在評估模型時,我們并不需要計算可訓練參數(`requires_grad=True`)的梯度。
`Function`是另外一個很重要的類。`Tensor`和`Function`互相結合就可以構建一個記錄有整個計算過程的有向無環圖(DAG)。每個`Tensor`都有一個`.grad_fn`屬性,該屬性即創建該`Tensor`的`Function`, 就是說該`Tensor`是不是通過某些運算得到的,若是,則`grad_fn`返回一個與這些運算相關的對象,否則是None。
下面通過一些例子來理解這些概念。
## 2.3.2 `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`。
~~~
?print(x.is_leaf, y.is_leaf) # True False
~~~
再來點復雜度運算操作:
~~~
?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>
~~~
## 2.3.2 梯度
因為`out`是一個標量,所以調用`backward()`時不需要指定求導變量:
~~~
out.backward() # 等價于 out.backward(torch.tensor(1.))
~~~
我們來看看`out`關于`x`的梯度 \\frac{d(out)}{dx}:
~~~
print(x.grad)
~~~
輸出:
~~~
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
~~~
我們令`out`為 o , 因為
所以
所以上面的輸出是正確的。
數學上,如果有一個函數值和自變量都為向量的函數 \\vec{y}=f(\\vec{x}), 那么 \\vec{y} 關于 \\vec{x} 的梯度就是一個雅可比矩陣(Jacobian matrix):
而`torch.autograd`這個包就是用來計算一些雅克比矩陣的乘積的。例如,如果 v 是一個標量函數的 l=g\\left(\\vec{y}\\right) 的梯度:
那么根據鏈式法則我們有 l 關于 \\vec{x} 的雅克比矩陣就為:
注意:grad在反向傳播過程中是累加的(accumulated),這意味著每一次運行反向傳播,梯度都會累加之前的梯度,所以一般在反向傳播之前需把梯度清零。
~~~
# 再來反向傳播一次,注意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.]])
~~~
> 現在我們解釋2.3.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`的導數。 [參考](https://zhuanlan.zhihu.com/p/29923090)
來看一些實際例子。
~~~
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>)
~~~
現在 `y` 不是一個標量,所以在調用`backward`時需要傳入一個和`y`同形的權重向量進行加權求和得到一個標量。
~~~
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 + y2
print(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呢? y\_3 = y\_1 + y\_2 = x^2 + x^3,當 x=1 時 \\frac {dy\_3} {dx} 不應該是5嗎?事實上,由于 y\_2 的定義是被`torch.no_grad():`包裹的,所以與 y\_2 有關的梯度是不會回傳的,只有與 y\_1 有關的梯度才會回傳,即 x^2 對 x 的梯度。
上面提到,`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官方文檔](https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py),與[原書同一節](https://zh.d2l.ai/chapter_prerequisite/autograd.html)有很大不同。
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯