2025-04-20 李沐深度学习4 —— 自动求导

文章目录

  • 1 导数拓展
    • 1.1 标量导数
    • 1.2 梯度:向量的导数
    • 1.3 扩展到矩阵
    • 1.4 链式法则
  • 2 自动求导
    • 2.1 计算图
    • 2.2 正向模式
    • 2.3 反向模式
  • 3 实战:自动求导
    • 3.1 简单示例
    • 3.2 非标量的反向传播
    • 3.3 分离计算
    • 3.4 Python 控制流

硬件配置:

  • Windows 11
  • Intel®Core™i7-12700H
  • NVIDIA GeForce RTX 3070 Ti Laptop GPU

软件环境:

  • Pycharm 2025.1
  • Python 3.12.9
  • Pytorch 2.6.0+cu124

1 导数拓展

1.1 标量导数

基本公式

  • 常数: d ( a ) / d x = 0 d(a)/dx = 0 d(a)/dx=0
  • 幂函数: d ( x n ) / d x = n ⋅ x n − 1 d(x^n)/dx = n·x^{n-1} d(xn)/dx=nxn1
  • 指数/对数:
    • d ( e x ) / d x = e x d(e^x)/dx = e^x d(ex)/dx=ex
    • d ( ln ⁡ x ) / d x = 1 / x d(\ln x)/dx = 1/x d(lnx)/dx=1/x
  • 三角函数:
    • d ( sin ⁡ x ) / d x = cos ⁡ x d(\sin x)/dx = \cos x d(sinx)/dx=cosx
    • d ( cos ⁡ x ) / d x = − sin ⁡ x d(\cos x)/dx = -\sin x d(cosx)/dx=sinx
image-20250419111921569

求导法则
d ( u + v ) d x = d u d x + d v d x d ( u v ) d x = u d v d x + v d u d x d f ( g ( x ) ) d x = f ′ ( g ( x ) ) ⋅ g ′ ( x ) \begin{aligned}&\frac{d(u+v)}{dx}=\frac{du}{dx}+\frac{dv}{dx}\\&\frac{d(uv)}{dx}=u\frac{dv}{dx}+v\frac{du}{dx}\\&\frac{df(g(x))}{dx}=f^{\prime}(g(x))\cdotp g^{\prime}(x)\end{aligned} dxd(u+v)=dxdu+dxdvdxd(uv)=udxdv+vdxdudxdf(g(x))=f(g(x))g(x)
不可微函数的导数:亚导数

  • ∣ x ∣ |x| x x = 0 x=0 x=0 时的亚导数: [ − 1 , 1 ] [-1,1] [1,1] 区间任意值。
  • ReLU 函数:max(0,x) x = 0 x=0 x=0 时导数可取 [ 0 , 1 ] [0,1] [0,1]
image-20250419112225706

1.2 梯度:向量的导数

形状匹配规则

函数类型自变量类型导数形状示例
标量 y y y标量 x x x标量 d y / d x = 2 x dy/dx = 2x dy/dx=2x
标量 y y y向量 x \mathbf{x} x行向量 d y / d x = [ 2 x 1 , 4 x 2 ] dy/d\mathbf{x} = [2x_1,4x_2] dy/dx=[2x1,4x2]
向量 y \mathbf{y} y标量 x x x列向量 d y / d x = [ cos ⁡ x , − sin ⁡ x ] T d\mathbf{y}/dx = [\cos x, -\sin x]^T dy/dx=[cosx,sinx]T
向量 y \mathbf{y} y向量 x \mathbf{x} x雅可比矩阵 d y / d x = [ [ 1 , 0 ] , [ 0 , 1 ] ] d\mathbf{y}/d\mathbf{x} = [[1,0],[0,1]] dy/dx=[[1,0],[0,1]]
image-20250419112819630

案例 1

  • y y y x 1 2 + 2 x 2 2 x_1^2 + 2x_2^2 x12+2x22(第一个元素的平方与第二个元素平方的 2 倍之和)
  • x \mathbf{x} x:向量。

d y d x = [ 2 x 1 , 4 x 2 ] \frac{dy}{d\mathbf{x}}=\begin{bmatrix}2x_1,&4x_2\end{bmatrix} dxdy=[2x1,4x2]

  • 几何解释:梯度向量 [2, 4] 指向函数值增长最快方向。
image-20250419113359974
  • 其他情况

    image-20250419113541593

案例 2

  • y \mathbf{y} y:向量。
  • x x x:标量。
image-20250419113648965

案例 3

  • y \mathbf{y} y:向量。
  • x \mathbf{x} x:向量。
image-20250419113904878
  • 其他情况
image-20250419113933471

1.3 扩展到矩阵

image-20250419114117161

1.4 链式法则

标量链式法则的向量化

​ 当 y = f ( u ) , u = g ( x ) y = f(u), u = g(x) y=f(u),u=g(x) 时:
d y d x = d y d u ⋅ d u d x \frac{dy}{d\mathbf{x}}=\frac{dy}{du}\cdot\frac{du}{d\mathbf{x}} dxdy=dudydxdu

  • d y / d u dy/du dy/du:标量 → 形状不变
  • d u / d x du/dx du/dx:若 u u u 是向量, x x x 是向量 → 雅可比矩阵(形状 [ d i m ( u ) , d i m ( x ) ] [dim(u), dim(x)] [dim(u),dim(x)]
image-20250419114750475

多变量链式法则
d z d w = d z d b ⋅ d b d a ⋅ d a d w = 2 b ⋅ 1 ⋅ x T \frac{dz}{d\mathbf{w}}=\frac{dz}{db}\cdot\frac{db}{da}\cdot\frac{da}{d\mathbf{w}}=2b\cdot1\cdot\mathbf{x}^T dwdz=dbdzdadbdwda=2b1xT

  • 示例:线性回归 z = ( x T w − y ) 2 z = (x^Tw - y)^2 z=(xTwy)2 的梯度计算
image-20250419114839290

2 自动求导

​ 自动求导计算一个函数在指定值上的导数,它有别于

  • 符号求导
  • 数值求导
image-20250420134058142

2.1 计算图

构建原理

  • 将代码分解成操作子

  • 将计算表示成一个无换图

    • 节点:输入变量(如 x , w , y x,w,y x,w,y)或基本操作(如 + , − , × +,-,× +,,×

    • 边:数据流向

image-20250420134206599

显式 vs 隐式构造

类型代表框架特点
显式TensorFlow,Mxnet,Theano先定义计算图,后喂入数据
隐式PyTorch,Mxnet动态构建图,操作即记录
image-20250420134557212

2.2 正向模式

​ 从输入到输出逐层计算梯度,每次计算一个输入变量对输出的梯度,通过链式法则逐层传递梯度。

​ 以 z = ( x ⋅ w − y ) 2 z = (x \cdot w - y)^2 z=(xwy)2 为例(线性回归损失函数):

# 正向计算过程
a = x * w    # a对x的梯度:∂a/∂x = w
b = a - y    # b对a的梯度:∂b/∂a = 1
z = b ** 2   # z对b的梯度:∂z/∂b = 2b
  • 特点:每次只能计算一个输入变量(如 xw)的梯度,需多次计算。

  • 计算复杂度:O(n)n 为输入维度)

  • 内存复杂度:O(1)(不需要存储中间结果)

  • 适用场景:输入维度低(如参数少)、输出维度高的函数。

2.3 反向模式

​ 从输出到输入反向传播梯度,一次性计算所有输入变量对输出的梯度。

数学原理

  • 前向计算

    计算所有中间值(a,b,z)并存储。

  • 反向传播(Back Propagation,也称反向传递)

    从输出z开始,按链式法则逐层回传梯度。

    先计算 ∂z/∂b = 2b,再计算 ∂b/∂a = 1,最后计算 ∂a/∂x = w

image-20250420134829609

​ 同样以 z = ( x ⋅ w − y ) 2 z = (x \cdot w - y)^2 z=(xwy)2 为例:

  1. 前向计算

    a = x * w    # 存储 a
    b = a - y    # 存储 b
    z = b ** 2
    
  2. 反向传播

    dz_db = 2 * b          # ∂z/∂b
    db_da = 1              # ∂b/∂a
    da_dx = w              # ∂a/∂x
    dz_dx = dz_db * db_da * da_dx  # 最终梯度
    
image-20250420135917081
  • 计算复杂度:O(n)(与正向模式相同)
  • 内存复杂度:O(n)(需存储所有中间变量)
  • 适用场景:深度学习(输入维度高,输出为标量损失函数)。

3 实战:自动求导

3.1 简单示例

​ 以函数 y = 2 x ⊤ x y=2\mathbf{x}^{\top}\mathbf{x} y=2xx 为例,关于列向量 x \mathbf{x} x 求导。

  1. 首先,创建变量x并为其分配一个初始值。

    import torchx = torch.arange(4.0)
    x
    
    image-20250420214507130
  2. 在计算 y y y 关于 x \mathbf{x} x 的梯度之前,需要一个地方来存储梯度。

    我们不会在每次对一个参数求导时都分配新的内存。

    因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。

    注意,一个标量函数关于向量 x \mathbf{x} x 的梯度是向量,并且与 x \mathbf{x} x 具有相同的形状。

    x.requires_grad_(True)  # 等价于x=torch.arange(4.0,requires_grad=True)
    x.grad  # 默认值是None
    
  3. 现在计算 y y y

    y = 2 * torch.dot(x, x)
    y
    
    image-20250420214911500
  4. x是一个长度为 4 的向量,计算xx的点积,得到了我们赋值给y的标量输出。
    接下来,通过调用反向传播函数来自动计算y关于x每个分量的梯度,并打印这些梯度。

    y.backward()
    x.grad
    
    image-20250420215014257
  5. 函数 y = 2 x ⊤ x y=2\mathbf{x}^{\top}\mathbf{x} y=2xx 关于 x \mathbf{x} x 的梯度应为 4 x 4\mathbf{x} 4x。让我们快速验证这个梯度是否计算正确。

    x.grad == 4 * x
    
    image-20250420215153504
  6. 探究x的另一个函数。

    # 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
    x.grad.zero_()
    y = x.sum()
    y.backward()
    x.grad
    
    image-20250420215427676

3.2 非标量的反向传播

​ 当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。

​ 对于高阶和高维的yx,求导的结果可以是一个高阶张量。

​ 虽然这些更奇特的对象确实出现在高级机器学习中(包括[深度学习中]),但当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。
​ 这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。

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

理解

  1. x.grad.zero_()

    • 作用:清空 x 的梯度(grad)缓存。

    • 为什么需要清零?

      • PyTorch 会累积梯度(grad),如果之前已经计算过 x 的梯度(比如在循环中多次 backward()),新的梯度会加到旧的梯度上。
      • 调用 zero_() 可以避免梯度累积,确保每次计算都是新的梯度。

  1. y = x \* x

    • 计算 y = x²(逐元素相乘)。

    • 例如:

      • 如果 x = [1, 2, 3],那么 y = [1, 4, 9]

  1. y.backward(torch.ones(len(x)))

    • backward() 的作用:计算 yx 的梯度(即 dy/dx)。
    • 为什么需要 gradient 参数?
      • 如果 y 是 标量(单个值),可以直接调用 y.backward(),PyTorch 会自动计算 dy/dx
      • 但如果 y 是 非标量(向量/矩阵),PyTorch 不知道如何计算梯度,必须传入一个 gradient 参数(形状和 y 相同),表示 y 的梯度权重。
    • gradient=torch.ones(len(x)) 的含义:
      • 这里 gradient 是一个全 1 的张量,表示我们希望计算 y 的所有分量对 x 的 梯度之和(相当于 sum(y)x 的梯度)。
      • 数学上:
        • y = [y₁, y₂, y₃] = [x₁², x₂², x₃²]
        • sum(y) = x₁² + x₂² + x₃²
        • d(sum(y))/dx = [2x₁, 2x₂, 2x₃](这就是 x.grad 的结果)

  1. 结果 x.grad

    • 由于 y = x²dy/dx = 2x

    • 由于 gradient=torch.ones(len(x)),PyTorch 计算的是 sum(y) 的梯度:

      • x.grad = [2x₁, 2x₂, 2x₃](即 2 * x)。
    • 例如:

      • 如果 x = [1, 2, 3],那么 x.grad = [2, 4, 6]

3.3 分离计算

​ 有时,我们希望将某些计算移动到记录的计算图之外。例如,假设y是作为x的函数计算的,而z则是作为yx的函数计算的。

​ 想象一下,我们想计算z关于x的梯度,但由于某种原因,希望将y视为一个常数,并且只考虑到xy被计算后发挥的作用。这里可以分离y来返回一个新变量u,该变量与y具有相同的值,但丢弃计算图中如何计算y的任何信息?

​ 换句话说,梯度不会向后流经ux。因此,下面的反向传播函数计算z = u * x关于x的偏导数,同时将u作为常数处理,而不是z = x * x * x关于x的偏导数。

x.grad.zero_()
y = x * x
u = y.detach()
z = u * xz.sum().backward()
x.grad == u
image-20250420222006669

​ 由于记录了y的计算结果,我们可以随后在y上调用反向传播,得到y = x * x关于的x的导数,即2 * x

x.grad.zero_()
y.sum().backward()
x.grad == 2 * x
image-20250420225127299

3.4 Python 控制流

​ 使用自动微分的一个好处是:即使构建函数的计算图需要通过 Python 控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。
​ 在下面的代码中,while循环的迭代次数和if语句的结果都取决于输入a的值。

def f(a):# type: (torch.Tensor)->torch.Tensorb = a * 2while b.norm() < 1000:b = b * 2if b.sum() > 0:c = belse:c = 100 * breturn 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, a.grad == d / a
image-20250420225434814

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/76932.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis的使用总结

Redis 核心使用场景 缓存加速 高频访问数据缓存&#xff08;如商品信息、用户信息&#xff09; 缓解数据库压力&#xff0c;提升响应速度 会话存储 分布式系统共享 Session&#xff08;替代 Tomcat Session&#xff09; 支持 TTL 自动过期 排行榜/计数器 实时排序&#x…

富文本编辑器实现

&#x1f3a8; 富文本编辑器实现原理全解析 &#x1f4dd; 基本实现路径图 #mermaid-svg-MO1B8a6kAOmD8B6Y {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MO1B8a6kAOmD8B6Y .error-icon{fill:#552222;}#mermaid-s…

LeetCode热题100——283. 移动零

给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0] 输出:…

与Ubuntu相关命令

windows将文件传输到Ubuntu 传输文件夹或文件 scp -r 本地文件夹或文件 ubuntu用户名IP地址:要传输到的文件夹路径 例如&#xff1a; scp -r .\04.py gao192.168.248.129:/home/gao 如果传输文件也可以去掉-r 安装软件 sudo apt-get update 更新软件包列表 sudo apt insta…

Kafka 在小流量和大流量场景下的顺序消费问题

一、低流量系统 特点 消息量较少&#xff0c;吞吐量要求低。系统资源&#xff08;如 CPU、内存、网络&#xff09;相对充足。对延迟容忍度较高。 保证顺序消费的方案 单分区 单消费者 将消息发送到单个分区&#xff08;例如固定 Partition 0&#xff09;&#xff0c;由单个…

小程序 GET 接口两种传值方式

前言 一般 GET 接口只有两种URL 参数和路径参数 一&#xff1a;URL 参数&#xff08;推荐方式&#xff09; 你希望请求&#xff1a; https://serve.zimeinew.com/wx/products/info?id5124接口应该写成这样&#xff0c;用 req.query.id 取 ?id5124&#xff1a; app.get(&…

小白学习java第14天(中):数据库

1.DML data manage language数据库管理语言 外键:外键是什么&#xff1f;就是对其进行表与表之间的联系&#xff0c;就是使用的键进行关联&#xff01; 方法一&#xff1a;我们在数据库里面就对其进行表与表之间的连接【这种是不建议的&#xff0c;我不太喜欢就是将数据里面弄…

NO.95十六届蓝桥杯备战|图论基础-单源最短路|负环|BF判断负环|SPFA判断负环|邮递员送信|采购特价产品|拉近距离|最短路计数(C++)

P3385 【模板】负环 - 洛谷 如果图中存在负环&#xff0c;那么有可能不存在最短路。 BF算法判断负环 执⾏n轮松弛操作&#xff0c;如果第n轮还存在松弛操作&#xff0c;那么就有负环。 #include <bits/stdc.h> using namespace std;const int N 2e3 10, M 3e3 1…

K8s pod 应用

/** 个人学习笔记&#xff0c;如有问题欢迎交流&#xff0c;文章编排和格式等问题见谅&#xff01; */ &#xff08;1&#xff09;编写 pod.yaml 文件 pod 是 kubernetes 中最小的编排单位&#xff0c;一个 pod 里包含一个或多个容器。 apiVersion: v1 # 指定api版本 kind…

Oracle创建触发器实例

一 创建DML 触发器 DML触发器基本要点&#xff1a; 触发时机&#xff1a;指定触发器的触发时间。如果指定为BEFORE&#xff0c;则表示在执行DML操作之前触发&#xff0c;以便防止某些错误操作发生或实现某些业务规则&#xff1b;如果指定为AFTER&#xff0c;则表示在执行DML操作…

Filename too long 错误

Filename too long 错误表明文件名超出了文件系统或版本控制系统允许的最大长度。 可能的原因 文件系统限制 不同的文件系统对文件名长度有不同的限制。例如&#xff0c;FAT32 文件名最长为 255 个字符&#xff0c;而 NTFS 虽然支持较长的文件名&#xff0c;但在某些情况下也…

网络不可达network unreachable问题解决过程

问题&#xff1a;访问一个环境中的路由器172.16.1.1&#xff0c;发现ssh无法访问&#xff0c;ping发现回网络不可达 C:\Windows\System32>ping 172.16.1.1 正在 Ping 172.16.1.1 具有 32 字节的数据: 来自 172.16.81.1 的回复: 无法访问目标网。 来自 172.16.81.1 的回复:…

Python设计模式:备忘录模式

1. 什么是备忘录模式&#xff1f; 备忘录模式是一种行为设计模式&#xff0c;它允许在不暴露对象内部状态的情况下&#xff0c;保存和恢复对象的状态。备忘录模式的核心思想是将对象的状态保存到一个备忘录对象中&#xff0c;以便在需要时可以恢复到之前的状态。这种模式通常用…

Python基础语法3

目录 1、函数 1.1、语法格式 1.2、函数返回值 1.3、变量作用域 1.4、执行过程 1.5、链式调用 1.6、嵌套调用 1.7、函数递归 1.8、参数默认值 1.9、关键字参数 2、列表 2.1、创建列表 2.2、下标访问 2.3、切片操作 2.4、遍历列表元素 2.5、新增元素 2.6、查找元…

JavaEE学习笔记(第二课)

1、好用的AI代码工具cursor 2、Java框架&#xff1a;Spring(高级框架)、Servelt、Struts、EJB 3、Spring有两层含义&#xff1a; ①Spring Framework&#xff08;原始框架&#xff09; ②Spring家族 4、Spring Boot(为了使Spring简化) 5、创建Spring Boot 项目 ① ② ③…

基于Flask与Ngrok实现Pycharm本地项目公网访问:从零部署

目录 概要 1. 环境与前置条件 2. 安装与配置 Flask 2.1 创建虚拟环境 2.2 安装 Flask 3. 安装与配置 Ngrok 3.1 下载 Ngrok 3.2 注册并获取 Authtoken 4. 在 PyCharm 中创建 Flask 项目 5. 运行本地 Flask 服务 6. 启动 Ngrok 隧道并获取公网地址 7. 完整示例代码汇…

Ragflow、Dify、FastGPT、COZE核心差异对比与Ragflow的深度文档理解能力​​和​​全流程优化设计

一、Ragflow、Dify、FastGPT、COZE核心差异对比 以下从核心功能、目标用户、技术特性等维度对比四款工具的核心差异&#xff1a; 核心功能定位 • Ragflow&#xff1a;专注于深度文档理解的RAG引擎&#xff0c;擅长处理复杂格式&#xff08;PDF、扫描件、表格等&#xff09;的…

LeetCode[232]用栈实现队列

思路&#xff1a; 一道很简单的题&#xff0c;就是栈是先进后出&#xff0c;队列是先进先出&#xff0c;用两个栈底相互对着&#xff0c;这样一个队列就产生了&#xff0c;右栈为空的情况&#xff0c;左栈栈底就是队首元素&#xff0c;所以我们需要将左栈全部压入右栈&#xff…

postman 删除注销账号

一、删除账号 1.右上角找到 头像&#xff0c;view profile https://123456-6586950.postman.co/settings/me/account 二、找回账号 1.查看日志所在位置 三、postman更新后只剩下history 在 Postman 中&#xff0c;如果你发现更新后只剩下 History&#xff08;历史记录&…

微服务相比传统服务的优势

这是一道面试题&#xff0c;咱们先来分析这道题考察的是什么。 如果分析面试官主要考察以下几个方面&#xff1a; 技术理解深度 你是否清楚微服务架构&#xff08;Microservices&#xff09;和传统单体架构&#xff08;Monolithic&#xff09;的本质区别。能否从设计理念、技术…