文章目录
- 前言
- 自动求导实现
- 非标量变量的反向传播
- 分离计算
- 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₁², x₂², x₃², x₄²]
)。
如果直接调用 y.backward()
,PyTorch 会报错,因为 y
不是标量。
解决方法:y.sum().backward()
为了计算 y
对 x
的梯度,我们需要:
将 y
变成一个标量(通过 sum()
、mean()
或其他聚合操作)。
y.sum()
计算 x₁² + x₂² + x₃² + x₄²
,得到一个标量。
调用 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
。