文章目錄
- 前言
- 自動求導實現
- 非標量變量的反向傳播
- 分離計算
- Python控制流的梯度計算
前言
關于走動求導的理論知識個人有點難以理解,推薦大家去看https://blog.csdn.net/weixin_42831564/article/details/135658138這篇文章,講的很好。
自動求導實現
import torchx = torch.arange(4.0)
print(x)
x.requires_grad_(True)
print(x.grad)
#計算y標量
y = 2 * torch.dot(x, x)
print(y)
y.backward()
print(x.grad)
print(x.grad == 4 * x)
x.grad.zero_(),x
y = x.sum()
y
y.backward(),y
print(x.grad)
為什么 y = x.sum()
的梯度是全 1?
非標量變量的反向傳播
x.grad.zero_()
y = x * x
y.sum().backward()
print(x.grad)
x.grad == 2 * x
為什么 y = x * x
后需要 y.sum().backward()
?
在 PyTorch 中,backward() 只能對標量(scalar,即 0 維張量)進行反向傳播,而不能直接對向量/矩陣進行反向傳播。因此:
y = x * x
是一個逐元素乘法,得到的 y
仍然是和 x
形狀相同的張量(如 [x?2, x?2, x?2, x?2]
)。
如果直接調用 y.backward()
,PyTorch 會報錯,因為 y
不是標量。
解決方法:y.sum().backward()
為了計算 y
對 x
的梯度,我們需要:
將 y
變成一個標量(通過 sum()
、mean()
或其他聚合操作)。
y.sum()
計算 x?2 + x?2 + x?2 + x?2
,得到一個標量。
調用 backward()
,PyTorch 會自動計算梯度并存儲到 x.grad
。
分離計算
在某層網絡需要把參數固定的時候,會用到這個功能
在PyTorch中,y.detach()
是一個用于從計算圖中分離張量的方法。計算圖是PyTorch用于自動微分的關鍵概念,用于構建和跟蹤張量之間的操作。在計算圖中,張量的計算歷史被記錄下來,以便在反向傳播時計算梯度。但有時我們希望從計算圖中分離出某個張量,使其不再與原始的計算歷史關聯。這在某些情況下是很有用的,例如當我們只關心使用該張量進行正向傳播的結果,并且不需要計算梯度時。
當調用y.detach()
時,將返回一個與y相同數據但不再與計算圖關聯的新張量。這意味著對返回的張量進行操作不會對原始計算圖產生任何影響,也不會計算任何梯度。該方法可用于將張量作為輸入傳遞給不允許梯度傳播的函數或模型。
x.grad.zero_()
y = x * x
u = y.detach() # 把y看成一個常數賦給u u與x無關,是一個常數
print(u)
z = u * x # z對x的導數
z.sum().backward()
print(x.grad)
print(u == x.grad)
# u是常數不是x的函數,y還是x的函數,還是可以對y求x的導數
x.grad.zero_()
y.sum().backward()
print(x.grad)
print(x.grad == 2*x)
Python控制流的梯度計算
def f(a):b = a * 2 # (1) b = 2awhile b.norm() < 1000:b = b * 2 # (2) 循環翻倍,直到 ||b|| ≥ 1000if b.sum() > 0: # (3) 如果 b 的和 > 0,c = b;否則 c = 100*bc = belse:c = 100 * breturn c # (4) 返回 ca = torch.randn(size=(), requires_grad=True) # 隨機初始化一個標量 a
d = f(a) # 計算 d = f(a)
d.backward() # 反向傳播計算梯度
print(a) # 打印 a 的值
print(d) # 打印 d 的值
print(a.grad) # 打印 a 的梯度
print(d / a) # 計算 d / a
print(a.grad == d / a) # 判斷梯度是否等于 d / a
torch.randn()
是一個用于生成服從標準正態分布(均值為0,標準差為1)的隨機數的函數
為什么 a.grad == d / a?
關鍵在于 d
和 a
的關系:
d
是 a
的線性函數
無論 while
循環執行多少次,b
都是 a
的倍數(b = a * 2 * 2 * ... * 2
)。
if-else
分支只是決定 c = b
或 c = 100 * b
,所以 c
(即 d
)仍然是 a
的倍數。
因此,d
可以表示為:d = k?a
,其中 k 是一個常數(由 while
循環和 if-else
分支決定)。
梯度計算:
對 d = k * a
求導:
由于 d = k * a
,所以 k = d / a
。
因此:
PyTorch 的 a.grad
存儲的就是這個梯度值,所以 a.grad == d / a
。