答案

在《动手学深度学习》的 autograd 章节末尾,通常会有几个练习题来巩固你对自动微分的理解。这些问题设计得非常巧妙,旨在让你理解计算图的边界情况。
以下是针对该章节常见问题的详细解析和参考答案:
问题 1:为什么计算二阶导数比一阶导数开销更大?
解析:

  • 一阶导数: 当我们进行前向传播时,PyTorch 会记录计算图。执行 .backward() 时,它会遍历这个图来计算梯度。
  • 二阶导数: 二阶导数是对“梯度的梯度”求导。为了计算它,PyTorch 不仅要保留原始的计算图,还要构建一个新的计算图来描述一阶梯度是如何计算出来的。
    结论: 计算二阶导数需要维护更复杂的计算图,消耗更多的内存和算力。在 PyTorch 中,如果你需要计算高阶导数,必须在 backward() 中指定 create_graph=True。
    问题 2:在运行反向传播函数之后,立即再次运行它,会发生什么?
    解析:
    在默认情况下,PyTorch 在执行完 .backward() 后,为了节省内存,会**立即释放(销毁)**计算图。
  • 结果: 如果你尝试第二次运行 .backward(),程序会报错,提示计算图已经不存在了。
  • 解决方法: 如果你确实需要多次运行反向传播(例如在某些复杂的 GAN 网络中),你需要在第一次调用时使用 y.backward(retain_graph=True)。
    问题 3:在控制流的例子中,如果变量 a 是一个随机矩阵或向量,会发生什么?
    解析:
    网页中的例子通常使用标量 a。如果 a 变成了一个向量,计算 f(a) 得到的通常也是一个向量。
  • 问题所在: PyTorch 的 .backward() 默认只能对标量(Scalar)求导。
  • 结果: 如果 f(a) 是向量,直接调用 .backward() 会报错。
  • 解决方法: 你需要提供一个“梯度向量”作为参数,或者先对结果求和(例如 f(a).sum().backward()),将其转化为标量后再求导。
    问题 4:重新设计一个求导函数,但在该函数中包含控制流(如 if 或 while),看看梯度是否正确。
    代码练习示例:
    你可以尝试运行以下代码,验证 PyTorch 强大的动态图能力:
    import torch

def my_func(a):
if a.norm() > 10:
b = a * 2
else:
b = a ** 2
return b.sum()

x = torch.randn(size=(3,), requires_grad=True)
y = my_func(x)
y.backward()

print(x.grad) # 即使有 if 分支,PyTorch 也能根据 x 的取值自动走对路径并求导

练习建议

  • 观察梯度清零: 在实验时,记得多次运行代码前使用 x.grad.zero_()。否则,PyTorch 会默认累加之前的梯度,这会导致结果错误。
  • 理解计算图: 自动微分的核心是 DAG(有向无环图)。
    你需要我针对其中某一个特定的数学证明题,给出更详细的推导步骤吗?

更多推荐