注:本代码在jupyter notebook上运行
封面图片来源
作为一个演示例子,假设我们想对函数y=2xTX关于列向量求导。 首先,我们创建变量x并为其分配一个初始值。
import torch
x = torch.arange(4.0)
x

在我们计算y关于x的梯度之前,需要一个地方来存储梯度。 重要的是,我们不会在每次对一个参数求导时都分配新的内存。 因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。 注意,一个标量函数关于向量x的梯度是向量,并且与x具有相同的形状。
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
# 这行代码的作用是设置变量 x 需要梯度计算
print(x.grad) # 默认值是None

# 计算y
y = 2 * torch.dot(x, x)# 点积
y

x是一个长度为4的向量,计算x和x的点积,得到了我们赋值给y的标量输出。 接下来,通过调用反向传播函数来自动计算y关于x每个分量的梯度,并打印这些梯度。
y.backward() # 反向传播时,如果x的requires_grad为True,则会计算y关于x的梯度
# 假设我们想要查看x的梯度(注意:在实际操作中,梯度会在调用backward()后被存储)
print(x.grad)

函数y=2x^T*X关于x的梯度应为4x。 让我们快速验证这个梯度是否计算正确。
x.grad == 4 * x

现在计算x的另一个函数。
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()# 在梯度反向传播之前清除(即归零)模型参数的梯度
y = x.sum()
y.backward()
x.grad

# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad

有时,我们希望将某些计算移动到记录的计算图之外。 例如,假设y是作为x的函数计算的,而z则是作为y和x的函数计算的。 想象一下,我们想计算z关于x的梯度,但由于某种原因,希望将y视为一个常数, 并且只考虑到x在y被计算后发挥的作用。
这里可以分离y来返回一个新变量u,该变量与y具有相同的值, 但丢弃计算图中如何计算y的任何信息。 换句话说,梯度不会向后流经u到x。 因此,下面的反向传播函数计算z=u*x关于x的偏导数,同时将u作为常数处理, 而不是z=x^3关于x的偏导数。
x.grad.zero_()# 梯度归零
y = x * x
u = y.detach()# detach() 方法的作用是从计算图中分离出一个张量。
# 这意味着返回的新张量不再依赖于原来的计算图,因此不会参与任何后续的梯度计算。
z = u * x
z.sum().backward()
x.grad == u

def f(a):
b = a * 2
while b.norm() < 1000: # 弗罗贝尼乌斯范数
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
# 计算梯度
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
我们现在可以分析上面定义的f函数。 请注意,它在其输入a中是分段线性的。 换言之,对于任何a,存在某个常量标量k,使得f(a)=k*a,其中k的值取决于输入a,因此可以用d/a验证梯度是否正确。
a.grad == d / a

这一篇有点难度,加入自己理解的比较少。
本人学习地址https://zh-v2.d2l.ai/
恳请大佬批评指正。