【李沐深度学习笔记】自动求导实现

课程地址和说明

自动求导实现p2
本系列文章是我学习李沐老师深度学习系列课程的学习笔记,可能会对李沐老师上课没讲到的进行补充。

自动求导

【李沐深度学习笔记】自动求导实现_第1张图片

# 创建变量
import torch
x = torch.arange(4, dtype=torch.float32) #只有浮点数才能求导
# 计算y关于x的梯度之前,需要一个地方存储梯度
x.requires_grad_(True)
print("x为:",x)
# 计算y
y = 2 * torch.dot(x,x) # y=2倍的x向量的内积,即y=2x^2
print("y为:",y)
# 通过调用反向传播函数来自动计算y关于x每个分量的梯度
y.backward() # 求导,y'=4x
print("x的梯度为:",x.grad)
print("y的导数是否为y'=4x:",x.grad == 4 * x)

运行结果:

x为: tensor([0., 1., 2., 3.], requires_grad=True)
y为: tensor(28., grad_fn=)
x的梯度为: tensor([ 0., 4., 8., 12.])
y的导数是否为y’=4x: tensor([True, True, True, True])

默认情况下PyTorch会把梯度累积起来

所以我们在算下一步的时候需要讲梯度清零

  • 举一个例子,之前的章节说过,当 y = s u m ( x → ) = ∑ i = 1 m x i = x 1 + x 2 + ⋯ + x m y=sum(\overrightarrow x)=\sum\limits_{i=1}^{m} x_{i}=x_{1}+x_{2}+\dots +x_{m} y=sum(x )=i=1mxi=x1+x2++xm a a a为任意常数, u = g ( x → ) u=g(\overrightarrow x) u=g(x )时,有:
    ∂ y ∂ x → = [ ∂ f ( x → ) ∂ x 1 ∂ f ( x → ) ∂ x 2 ⋮ ∂ f ( x → ) ∂ x m ] m × 1 = [ 1 1 ⋮ 1 ] m × 1 \frac{\partial {y}}{\partial\overrightarrow x}=\begin{bmatrix} \frac{\partial {f(\overrightarrow x)}}{\partial{x_{1}}}\\ \frac{\partial {f(\overrightarrow x)}}{\partial{x_{2}}}\\ \vdots \\ \frac{\partial {f(\overrightarrow x)}}{\partial{x_{m}}} \end{bmatrix}_{m\times 1}=\begin{bmatrix} 1\\ 1\\ \vdots \\ 1 \end{bmatrix}_{m\times 1} x y= x1f(x )x2f(x )xmf(x ) m×1= 111 m×1最终结果是单位向量。
    用PyTorch复现为:
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
# 重新定义,y=x1+x2+...+xn
y = sum(x)
# 向后传播求导
y.backward()
# 打印求导结果
print(x.grad)

运行结果:
tensor([1., 1., 1., 1.])

深度学习中,我们的目的不是计算微分矩阵,而是批量中每个样本单独计算的偏导数之和。

【李沐深度学习笔记】自动求导实现_第2张图片

# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
# 重新定义,y=x1+x2+...+xn
y = x * x # 变为了标量
print("y=",y)
# 向后传播求导
y.sum().backward() # 变量需要先让y求和再向后传播求导
print("y.sum()=",y.sum())
# 打印求导结果
print(x.grad)

运行结果:
y= tensor([0., 1., 4., 9.], grad_fn=)
y.sum()= tensor(14., grad_fn=)
tensor([0., 2., 4., 6.])

将某些计算移动到记录的计算图之外

# 清空累积的梯度
x.grad.zero_()
# 定义y = x * x
y = x* x
# 定义u为常数,不再是y = x * x这样的函数,其常数值为x * x
u = y.detach()
print(u)
# z = u * x,相当于常数u乘以向量x
z = u * x
# z = [u1,u2,u3,u4](常数向量)×[x1,x2,x3,x4]^T(变量向量)
# z = u1x1+u2x2+u3x3+u4x4
# 所以z'x1=u1,以此类推,其偏导数的向量最终就和u相等
z.sum().backward()
print(x.grad  == u)

运行结果:
tensor([0., 1., 4., 9.])
tensor([True, True, True, True])

# 清空累积的梯度
x.grad.zero_()
# 现在y为 x^2(x1^2+x2^2+x3^2+x4^2),y'=2x(y'x1=2x1,以此类推)
# 对y求导
y.sum().backward()
# 证明y的导数是否是2x
print(x.grad ==2 *x)

运行结果:
tensor([True, True, True, True])

【提问】请用通俗的语言告诉我在PyTorch中, y.sum().backward()与y.backward()这两种向后求导方式有什么区别?
【chatGPT回答】在PyTorch中,y.sum().backward()和y.backward()是两种不同的向后求导方式,它们在计算梯度的方式上有一些区别。

  1. y.sum().backward()
  • 这个方式首先对y进行了求和操作,将一个张量中的所有元素相加得到一个标量(一个单一的数值)。
  • 然后对这个标量进行反向传播,计算相对于y的梯度。
  • 这种方式通常用于处理损失函数的梯度计算,因为损失函数通常返回一个标量,你希望计算相对于模型参数的梯度。
  1. y.backward():
  • 这个方式直接对张量y进行反向传播,计算相对于y的梯度。
  • 这种方式通常用于计算中间变量的梯度,而不是损失函数的梯度。
    总的来说,主要区别在于
  • y.sum().backward()用于计算损失函数相对于模型参数的梯度,因为损失函数通常返回一个标量值。
  • y.backward()用于计算中间变量相对于其他变量的梯度,通常在计算模型的中间层时使用。
    选择哪种方式取决于你的具体需求和计算图的结构。通常,当你需要计算损失函数相对于模型参数的梯度时,你会使用y.sum().backward(),而在其他情况下,你会使用y.backward()来计算相对于中间变量的梯度。

即使构建函数的计算图需要通过Python控制流(例如,条件循环或任意函数调用),我们仍然可以计算得到的变量的梯度

# 清空累积的梯度
x.grad.zero_()
# 设定一个函数
def f(a):
    b = a * 2
    # 如果b向量的模长(L2范式)小于1000,就不断给它乘以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)
print("a为:",a)
d = f(a)
print("f(a)为:",d)
d.backward()
# 此函数相当于f(a)=ba,b是系数,如果是向量也是如此
# 所以其导数(或向量对应梯度向量)一定是斜率b
print(a.grad == d/a)

运行结果:
a为: tensor(1.6346, requires_grad=True)
f(a)为: tensor(1673.8451, grad_fn=)
tensor(True)

你可能感兴趣的:(李沐深度学习,深度学习,笔记,人工智能)