pytorch小记(五):pytorch中的求导操作:backward()
- 完整代码
- [代码 1](#代码 1)
- [代码 2](#代码 2)
- [代码 3](#代码 3)
- [代码 4](#代码 4)
- 代码的整体逻辑总结
- [补充:`requires_grad` 与 `backward` 的作用](#补充:
requires_grad
与backward
的作用) - 疑问
-
- 原因分析
- 如何解决
-
- [方法 1:通过 `.sum()` 转为标量](#方法 1:通过
.sum()
转为标量) - [方法 2:通过 `.mean()` 转为标量](#方法 2:通过
.mean()
转为标量)
- [方法 1:通过 `.sum()` 转为标量](#方法 1:通过
- 高级用法:为非标量指定梯度权重
- 总结
完整代码
python
x = torch.tensor([[2., -1.], [1., 1.]], requires_grad=True)
print(x)
out = x.pow(2).sum()
print(out)
out.backward()
print(x.grad)
python
>>>
tensor([[ 2., -1.],
[ 1., 1.]], requires_grad=True)
tensor(9., grad_fn=<SumBackward0>)
tensor([[12., 3.],
[ 3., 3.]])
让我们逐行详细分析代码的执行逻辑及其背后的原理。
代码 1
python
x = torch.tensor([[2., -1.], [1., 1.]], requires_grad=True)
print(x)
解释
-
torch.tensor
:- 创建一个二维张量
x
,内容为[[2., -1.], [1., 1.]]
。 - 数据类型为浮点数(
float32
),因为传入的是小数。
- 创建一个二维张量
-
requires_grad=True
:- 启用自动求导功能。
- 任何以
x
为输入的张量计算图都会记录下来,用于后续的梯度计算。
-
结果 :
打印张量
x
:tensor([[ 2., -1.], [ 1., 1.]], requires_grad=True)
代码 2
python
out = x.pow(2).sum()
print(out)
解释
-
x.pow(2)
:- 计算
x
的每个元素的平方。 - 张量的每个元素被逐元素计算: x i j 2 x_{ij}^2 xij2。
计算结果:
tensor([[4., 1.], [1., 1.]], requires_grad=True)
- 计算
-
.sum()
:- 对张量中所有元素求和,计算结果为:
4 + 1 + 1 + 1 = 7 4 + 1 + 1 + 1 = 7 4+1+1+1=7 requires_grad=True
表示out
依赖于x
,可以反向传播。
- 对张量中所有元素求和,计算结果为:
-
结果 :
打印
out
:tensor(7., grad_fn=<SumBackward0>)
grad_fn=<SumBackward0>
表示这是通过sum()
操作计算得出的,反向传播时会回溯到这个操作。
代码 3
python
out.backward()
解释
-
out.backward()
:- 对
out
调用.backward()
会计算x
的梯度。 - 梯度的计算目标 :计算
out
对x
的偏导数,即 ∂ out ∂ x \frac{\partial \text{out}}{\partial x} ∂x∂out。
- 对
-
计算过程:
-
out = x.pow(2).sum()
,展开公式为:
out = x 11 2 + x 12 2 + x 21 2 + x 22 2 \text{out} = x_{11}^2 + x_{12}^2 + x_{21}^2 + x_{22}^2 out=x112+x122+x212+x222 -
对
x_{ij}
求偏导:
∂ out ∂ x i j = 2 ⋅ x i j \frac{\partial \text{out}}{\partial x_{ij}} = 2 \cdot x_{ij} ∂xij∂out=2⋅xij -
每个元素的梯度计算结果:
[[2 * 2., 2 * -1.], [2 * 1., 2 * 1.]]
-
结果为:
[[ 4., -2.], [ 2., 2.]]
-
-
梯度存储:
- 计算出的梯度会存储在
x.grad
中。
- 计算出的梯度会存储在
代码 4
python
print(x.grad)
解释
- 打印
x
的梯度,即x.grad
。 x.grad
包含了之前通过.backward()
计算的梯度值。
结果:
tensor([[ 4., -2.],
[ 2., 2.]])
代码的整体逻辑总结
-
初始化张量:
pythonx = torch.tensor([[2., -1.], [1., 1.]], requires_grad=True)
创建一个二维张量,并启用梯度跟踪。
-
前向计算:
pythonout = x.pow(2).sum()
计算
x
的平方和,得到标量out
。 -
反向传播:
pythonout.backward()
自动计算
out
对x
的梯度,结果存储在x.grad
中。 -
打印梯度:
pythonprint(x.grad)
查看
x
的梯度,其值为:[[ 4., -2.], [ 2., 2.]]
这表明
out
对x
的变化率是该梯度值。
补充:requires_grad
与 backward
的作用
requires_grad=True
:- 启用张量的自动求导功能,参与计算的操作会记录到计算图中。
backward()
:- 执行反向传播,沿着计算图回溯并计算梯度。
- 梯度存储在
x.grad
中,用于优化或调试。
疑问
在 PyTorch 中,out.backward()
是用来计算张量的梯度的操作。然而,当调用 backward()
时,输出张量必须是标量(即只有一个数值)。如果输出张量不是标量,就会引发以下错误:
RuntimeError: grad can be implicitly created only for scalar outputs
原因分析
-
标量输出的要求:
backward()
会计算输入张量(如x
)对输出张量(如out
)的梯度。- 如果输出张量是标量(0 维张量),梯度是输入张量的每个元素对这个标量的偏导数,梯度张量的形状与输入张量一致。
例子:
pythonout = x.pow(2).sum() out.backward()
- 这里
out
是一个标量,因此可以计算梯度。
-
非标量输出的情况:
-
如果输出张量是多维的,例如
x.pow(2)
的结果:pythonout = x.pow(2)
out
的形状是(2, 2)
:tensor([[4., 1.], [1., 1.]], requires_grad=True)
-
这种情况下,
backward()
不知道如何将梯度传播到输入张量x
,因为输出张量的每个元素都可能有不同的梯度。
-
如何解决
如果想对多维张量调用 backward()
,需要将其 转换为标量,例如通过求和或取均值。
方法 1:通过 .sum()
转为标量
python
out = x.pow(2).sum() # 转为标量
out.backward()
方法 2:通过 .mean()
转为标量
python
out = x.pow(2).mean() # 转为标量
out.backward()
高级用法:为非标量指定梯度权重
如果你需要对非标量张量调用 backward()
,可以通过 out.backward(gradient=...)
指定梯度权重,告诉 PyTorch 如何将梯度聚合到输入张量。
示例
python
out = x.pow(2) # 非标量张量,形状为 (2, 2)
gradient = torch.ones_like(out) # 权重张量,形状必须与 out 相同
out.backward(gradient=gradient)
解释:
gradient=...
指定了out
每个元素在反向传播中的权重。- 例如,对于 o u t = x 2 out = x^2 out=x2,若
gradient=1
,梯度仍然是 ∂ o u t ∂ x = 2 x \frac{\partial out}{\partial x} = 2x ∂x∂out=2x。
总结
backward()
要求输出必须是标量,否则会报错。- 可以通过
.sum()
或.mean()
将多维张量转换为标量。 - 对于特殊情况,可以使用
backward(gradient=...)
指定非标量输出的梯度权重。