1. 向量链式法则

① 例子1是一个线性回归的例子,如下图所示。

内积是一个标量,相当于对标量求导

标量对行向量求导结果还是行向量

这时,X是一个矩阵

这里统一采用分子布局,分子布局和分母布局只差一个转置。

I为单位矩阵

2. 自动求导

3. 计算图

 MXNet 框架的符号式编程代码,主要用于定义计算图                  

4. 两种模型

① b是之前计算的结果,是一个已知的值。

5. 复杂度

6. 自动求导

① 假设想对函数关于列向量x求导。

import torch
x = torch.arange(4.0)
x
tensor([0., 1., 2., 3.])

② 在外面计算y关于x的梯度之前,需要一个地方来存储梯度。

场景 requires_grad 设置
模型参数(权重、偏置) True
输入数据(图像、文本) False(默认)
标签 False
特征提取时冻结某些层 设为 False 停止梯度

相关操作

python

# 查看是否需要梯度
print(w.requires_grad)  # True

# 修改是否需要梯度
w.requires_grad = False

# 在不需要梯度时临时关闭(加快计算、节省内存)
with torch.no_grad():
    y = model(x)  # 这里不会计算梯度

一句话总结

requires_grad=True 就是告诉 PyTorch:“这个张量是需要学习的参数,请记录它的操作,以便后面计算梯度、更新它。”

import torch
x = torch.arange(4.0)
x.requires_grad_(True) # 等价于 x = torch.arange(4.0,requires_grad=True)
print(x.grad) # 默认为None
None
import torch
x = torch.arange(4.0,requires_grad=True)
print(x.grad) # x.grad是存梯度的地方,默认为None,即还没有求导求出梯度出来
None

③ 现在计算y。

import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
print(y) # grad_fn是隐式的构造了梯度函数


tensor(28., grad_fn=<MulBackward0>)

当你执行 torch.dot(x,x) 时,PyTorch 不仅算了点积,还在背后悄悄做了两件事:

  • 记录了计算图(谁是谁的输入)

  • 给结果张量贴上一个标签 grad_fn,注明“我是通过乘法得到的”

grad_fn=<MulBackward0> 的含义

部分 含义
Mul 乘法操作(Multiplication)
Backward 这个函数知道如何进行反向传播
0 编号(区分多个相同操作)

合起来就是:“这个张量是由一个乘法操作产生的,如果你想对某个变量求导,可以用我这个函数来反向传播。”

④ 通过调用反向传播函数来自动计算y关于x每个分量的梯度。

import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
y.backward() # 反向传播后会有梯度计算出来
print(x.grad) # 访问导数,即访问梯度
print(x.grad == 4 * x) # 4 * x 是 2 * x * x 的导数
tensor([ 0.,  4.,  8., 12.])
tensor([True, True, True, True])

计算关系图

text

x → a = x·x → y = 2a

第1步:从 y 到 a

text

y = 2a
dy/da = 2  # y 对 a 的导数

第2步:从 a 到 x

text

a = x₁² + x₂² + x₃² + x₄²
da/dx = [2x₁, 2x₂, 2x₃, 2x₄]  # 每个位置的导数

第3步:链式法则(核心!)

text

dy/dx = dy/da * da/dx
      = 2 * [2x₁, 2x₂, 2x₃, 2x₄]
      = [4x₁, 4x₂, 4x₃, 4x₄]

代入 x = [0,1,2,3]:

text

x.grad = [0, 4, 8, 12]

正好是你代码输出的结果!


4. 图解反向传播

text

正向传播方向 →→→→→→→→→→→→→→→
   x  ──→  a  ──→  y
   ↓         ↓         ↓
[0,1,2,3] → 14  →  28

反向传播方向 ←←←←←←←←←←←←←←←
   x  ←──  a  ←──  y
   ↓         ↓         ↓
[0,4,8,12] ←  2  ←  1
(梯度)     (dy/da)  (起点)

⑤ 计算x的另一个函数。

import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
y.backward()
# 默认情况下,PyTorch会累积梯度,需要清除之前的值
x.grad.zero_() # y.backward() 后才能产生梯度,才能梯度清零,没有反向传播,无法梯度清零
y = x.sum() # 这里的y是一个标量,sum函数其实就是x_1+x_2+...x_n,求偏导自然是全1啊
y.backward()#反向求导
print(x.grad)
tensor([1., 1., 1., 1.])

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

backward() 只能对标量(一个数)调用,不能直接对向量调用。

PyTorch 的 backward() 要求什么?

backward() 只能对标量(一个数)调用,不能直接对向量调用。

为什么?因为梯度定义的是“输出对输入的导数”

  • 如果输出是标量 y,可以求 dy/dx(一个值对一组变量求导)

  • 如果输出是向量 Y = [y₁, y₂, y₃, y₄],你想求什么?是 dy₁/dx 还是 dy₂/dx?不确定


3. 解决方法:先把向量变成标量

最常见的做法是对向量求和,得到一个标量:

python

y.sum()  # 把向量所有元素加起来

计算过程:

text

y = [0., 1., 4., 9.]
y.sum() = 0 + 1 + 4 + 9 = 14

现在 y.sum() 是一个标量 14,可以求导了。


4. 执行 y.sum().backward() 后发生了什么?

python

y.sum().backward()

PyTorch 计算的是:y.sum() 对 x 的导数

链式推导:

text

y.sum() = y₁ + y₂ + y₃ + y₄
        = x₁² + x₂² + x₃² + x₄²

d(y.sum())/dx₁ = 2x₁
d(y.sum())/dx₂ = 2x₂
d(y.sum())/dx₃ = 2x₃
d(y.sum())/dx₄ = 2x₄

代入 x = [0,1,2,3]:

text

x.grad = [0, 2, 4, 6]
import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
y.backward()
# 默认情况下,PyTorch会累积梯度,需要清除之前的值
# 对非标量调用 'backward' 需要传入一个 'gradient' 参数,该参数指定微分函数
x.grad.zero_()
y = x * x  # 这里的y不是一个标量,这是一个向量
print(y)
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward() # y.sum()后就讲向量转为标量了,对标量求导
x.grad
tensor([0., 1., 4., 9.], grad_fn=<MulBackward0>)
tensor([0., 2., 4., 6.])

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

import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
y.backward()
x.grad.zero_()
y = x * x
print(y)
u = y.detach() # y.detach把y当作一个常数,而不是关于x的一个函数
print(y.detach())
print(u)
z = u * x
z.sum().backward()
x.grad == u
tensor([0., 1., 4., 9.], grad_fn=<MulBackward0>)
tensor([0., 1., 4., 9.])
tensor([0., 1., 4., 9.])
tensor([True, True, True, True])
import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
y.backward()
x.grad.zero_()
y = x * x  # 这里的y是关于x的函数
x.grad.zero_()#这是多余的
y.sum().backward()
x.grad == 2 * x
tensor([True, True, True, True])

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

def f(a):
    b = a * 2
    while b.norm() < 1000: # norm是L2范数
        b = b * 2
    if b.sum() > 0:
        c = b
    else:
        c = 100 * b
    return c
torch.randn 正态分布随机数
size=() 创建标量(0维张量)
requires_grad=True 开启梯度跟踪

a = torch.randn(size=(),requires_grad=True)
print(a)
d = f(a)
d.backward()
print(a.grad)
print(d/a)
a.grad == d/a # d是a的线性函数,所以导数就是斜率d/a
tensor(-0.7712, requires_grad=True)
tensor(204800.)
tensor(204800., grad_fn=<DivBackward0>)
tensor(True)

 参考github作者,我只是用来学习。

更多推荐