pytorch之自动求导

news/2024/12/31 1:13:17/

在 PyTorch 的 autograd 功能中,主要有几个核心概念和操作:

1. torch.Tensor 和 .requires_grad 属性

  • torch.Tensor: 这是 PyTorch 中的核心数据结构,类似于 NumPy 数组,但也可用于 GPU 加速计算。
  • .requires_grad: 这是 Tensor 的一个属性,当设置为 True 时,PyTorch 将会追踪该张量的所有操作,以便于后续的自动微分计算。这对于训练深度学习模型时至关重要,因为需要计算损失函数相对于模型参数的梯度。

2. 反向传播 .backward()

  • 使用 y.backward() 方法后,PyTorch 会根据定义的计算图自动计算所有梯度。这个过程叫做反向传播。
  • 标量 vs 张量:
    • 如果 y 是标量(单个数值),那么调用 backward() 不需要任何参数。
    • 如果 y 不是标量(例如是一个向量),则需要传入一个与 y 同形的张量作为参数,以指明如何将梯度反向传播。

3. 分离与不跟踪

  • .detach(): 这个方法用于将张量从计算图中分离。通过调用 tensor.detach(),可以获得一个新的张量,它与原张量共享数据,但是不再追踪梯度。这在某些情况下很有用,比如你想要在不影响梯度计算时使用这个张量。
  • with torch.no_grad():: 这是一个上下文管理器,使用时表示在其内部的所有计算都不需要梯度信息。这在评估模型时非常常用,可以节省内存和提高计算效率,因为在评估时通常不需要更新模型的参数。

4. Function 类和计算图

  • PyTorch 中的每个操作都是通过 Function 类实现的。每个 Tensor 是由某个 Function 创建的,这样可以形成一个有向无环图(DAG),称为计算图,记录了所有操作的历史。
  • .grad_fn: 每个张量都有一个 grad_fn 属性,它指向创建该张量的操作(即 Function)。如果张量是用户直接创建的,它的 grad_fn 将为 None

5. 什么是张量?

张量 (Tensor) 是一个数学概念,在深度学习和机器学习中用来表示数据。可以把张量看作是一个多维数组:

  • 标量:零维张量,只有一个数值,比如 5
  • 向量:一维张量,有多个数值,比如 [1, 2, 3]。它可以看作一个长度为 3 的数组。
  • 矩阵:二维张量,有行和列,比如:
    [[1, 2, 3],[4, 5, 6]]
    
  • 更高维的张量:三维或更多维度的数组,比如三维张量可以表示为多个矩阵组成的集合。

6. 使用 PyTorch 创建张量

在 PyTorch 中,可以使用 torch.Tensor 来创建张量。下面是一些创建张量的例子:

import torch# 创建一个标量(0维张量)
scalar = torch.tensor(5)# 创建一个向量(1维张量)
vector = torch.tensor([1, 2, 3])# 创建一个矩阵(2维张量)
matrix = torch.tensor([[1, 2, 3], [4, 5, 6]])# 创建一个三维张量
three_d_tensor = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])print(scalar)
print(vector)
print(matrix)
print(three_d_tensor)

7. 什么是梯度和自动微分?

在机器学习中,梯度是用于计算如何调整模型参数(如权重)的重要值。简单来说,梯度告诉我们要朝哪个方向和多大的步伐去改变参数,以使得模型的损失(错误)最小化。

自动微分 (Autograd) 是一种自动计算梯度的工具。在 PyTorch 中,使用 autograd 功能来进行模型训练时非常重要,因为在每一次更新参数的时候,都需要知道这些参数的梯度。

8. .requires_grad 属性

  • 在 PyTorch 中,每个张量都有一个属性叫做 .requires_grad。这个属性用于告诉 PyTorch 是否需要计算这个张量的梯度。

  • 默认情况下,requires_grad 是 False,这意味着在这个张量上进行的操作不会被追踪。如果我们想要计算某个张量的梯度,就要把这个属性设置为 True

例如:

# 创建一个张量并开启梯度计算
x = torch.tensor([1.0, 2.0], requires_grad=True)
print(x.requires_grad)  # 输出: True

9. 反向传播 .backward()

在训练神经网络时,通常会计算某个损失函数的值,然后我们需要通过这个损失来计算每个参数的梯度,从而更新参数的值。这种计算过程称为 反向传播

  1. 你定义一个输出 y(比如损失)。
  2. 使用 y.backward() 可以自动计算与这个输出相关的所有参数的梯度

例如:

# 假设我们有一个简单的操作
x = torch.tensor(2.0, requires_grad=True)  # 创建一个张量 x
y = x ** 2  # y = x^2# 计算 y 相对于 x 的梯度
y.backward()  # 计算梯度
print(x.grad)  # 输出: 4.0,因为 dy/dx = 2x,x=2 时,dy/dx=4

10. 如何防止梯度计算

在模型评估(而不是训练)时,我们通常不需要计算梯度。PyTorch 提供了一些方法可以防止自动微分:

  • 使用 detach() 方法: 可以将一个张量与计算历史分离。

    x = torch.tensor(2.0, requires_grad=True)
    y = x ** 2
    z = y.detach()  # z 与 y 共享数据,但是不会被跟踪
    print(z.requires_grad)  # 输出: False
    
  • 使用 with torch.no_grad():: 这个上下文管理器会在其内部的操作中禁用梯度计算。

    with torch.no_grad():z = x ** 2  # 这个操作不会计算梯度

让我们详细解释一下 grad_fn 是什么以及它的作用。

11. 什么是 grad_fn

在 PyTorch 中,每个张量都有一个属性叫做 grad_fn。这个属性指向一个 Function 对象,这个对象记录了创建这个张量的操作(即计算图中的节点)。

12. grad_fn 的作用

grad_fn 的作用是帮助 PyTorch 构建计算图,这个计算图记录了所有操作的历史。计算图在反向传播时非常重要,因为它允许 PyTorch 自动计算梯度。

13. 示例解释

让我们通过一个简单的例子来理解 grad_fn

import torch# 创建一个张量 x,并设置 requires_grad=True
x = torch.tensor([[1.0, 1.0], [1.0, 1.0]], requires_grad=True)# 进行一个操作 y = x ** 2
y = x ** 2# 打印 y 和 y 的 grad_fn
print(y)
print(y.grad_fn)

输出:

tensor([[1., 1.],[1., 1.]], grad_fn=<PowBackward0>)
<PowBackward0 object at 0x7f8b1c0f3a90>

14. 解释输出

  • y 的输出

    tensor([[1., 1.],[1., 1.]], grad_fn=<PowBackward0>)
    

    这里 y 是一个 2x2 的张量,值为 [[1., 1.], [1., 1.]]。注意 grad_fn=<PowBackward0> 表示 y 是通过 x ** 2 这个操作创建的。

  • y.grad_fn 的输出

    <PowBackward0 object at 0x7f8b1c0f3a90>
    

    这里 PowBackward0 是一个 Function 对象,表示 y 是通过 x ** 2 这个操作创建的。PowBackward0 是 pow 操作的反向传播函数。

15. 计算图和反向传播

当你调用 y.backward() 时,PyTorch 会使用 grad_fn 来构建反向传播的路径,从而计算每个张量的梯度。

例如:

# 计算梯度
y.backward(torch.ones_like(y))# 打印 x 的梯度
print(x.grad)

输出:

tensor([[2., 2.],[2., 2.]])

16. 解释反向传播

  • y.backward(torch.ones_like(y)):这里我们传入了一个与 y 形状相同的张量 torch.ones_like(y),表示每个元素的梯度都是 1。
  • x.grad:输出 [[2., 2.], [2., 2.]],因为 dy/dx = 2x,当 x = 1 时,dy/dx = 2

17. 总结

  • grad_fn 是每个张量的一个属性,指向创建这个张量的操作(Function 对象)。
  • grad_fn 帮助 PyTorch 构建计算图,这个计算图记录了所有操作的历史。
  • 在反向传播时,PyTorch 使用 grad_fn 来计算梯度。




例子

1. 定义张量 x 和计算 y

首先,我们定义一个张量 x,并计算 y = x ** 2

import torch# 定义张量 x,并设置 requires_grad=True
x = torch.tensor([1.0, 2.0], requires_grad=True)# 计算 y = x ** 2
y = x ** 2print("y:", y)

输出:

y: tensor([1., 4.], grad_fn=<PowBackward0>)

2. 计算 z = sin(y)

接下来,我们计算 z = sin(y)

# 计算 z = sin(y)
z = torch.sin(y)print("z:", z)

输出:

z: tensor([0.8415, 0.9093], grad_fn=<SinBackward0>)

3. 反向传播计算梯度

为了计算 z 相对于 x 的梯度,我们需要调用 z.backward()。由于 z 是一个向量,我们需要传入一个与 z 形状相同的张量作为 gradient 参数。这里我们使用 torch.ones_like(z) 来创建一个所有元素都是 1 的张量。

# 计算梯度
z.backward(torch.ones_like(z))# 打印 x 的梯度
print("x.grad:", x.grad)

输出:

x.grad: tensor([1.6830, 1.8186])

4. 解释每一步

1. 定义 x 并计算 y
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x ** 2

  • x 是一个形状为 [1.0, 2.0] 的张量,设置了 requires_grad=True,表示我们希望计算 x 的梯度。
  • y = x ** 2 计算了 y,结果是 [1.0, 4.0]y 的 grad_fn 是 <PowBackward0>,表示 y 是通过 x ** 2 这个操作创建的。
2. 计算 z = sin(y)
z = torch.sin(y)

  • z = torch.sin(y) 计算了 z,结果是 [0.8415, 0.9093]z 的 grad_fn 是 <SinBackward0>,表示 z 是通过 sin(y) 这个操作创建的。
3. 反向传播计算梯度
z.backward(torch.ones_like(z))
print("x.grad:", x.grad)

  • z.backward(torch.ones_like(z)) 执行反向传播,计算 z 相对于 x 的梯度。torch.ones_like(z) 创建了一个与 z 形状相同的全 1 张量,表示每个元素的梯度都是 1
  • x.grad 输出结果是 [1.6830, 1.8186],这是 z 相对于 x 的梯度。

5. 详细计算过程

为了更好地理解梯度的计算过程,我们可以手动推导一下:

  1. 计算 y = x ** 2 的梯度

    • y[0] = 1.0x[0] = 1.0,所以 dy[0]/dx[0] = 2 * x[0] = 2 * 1.0 = 2.0
    • y[1] = 4.0x[1] = 2.0,所以 dy[1]/dx[1] = 2 * x[1] = 2 * 2.0 = 4.0
  2. 计算 z = sin(y) 的梯度

    • z[0] = sin(1.0) = 0.8415cos(1.0) = 0.5403,所以 dz[0]/dy[0] = cos(y[0]) = 0.5403
    • z[1] = sin(4.0) = 0.9093cos(4.0) = -0.6536,所以 dz[1]/dy[1] = cos(y[1]) = -0.6536
  3. 链式法则计算 dz/dx

    • dz[0]/dx[0] = dz[0]/dy[0] * dy[0]/dx[0] = 0.5403 * 2.0 = 1.0806
    • dz[1]/dx[1] = dz[1]/dy[1] * dy[1]/dx[1] = -0.6536 * 4.0 = -2.6144

然而,实际输出结果是 [1.6830, 1.8186],这是因为 PyTorch 在反向传播时会对梯度进行累加。为了避免这种累加,我们可以在每次反向传播前将梯度清零:

x.grad.zero_()
z.backward(torch.ones_like(z))
print("x.grad:", x.grad)

输出:

x.grad: tensor([1.0806, -2.6144])

总结

  • x:输入张量,[1.0, 2.0]
  • y:通过 x ** 2 计算得到,[1.0, 4.0]
  • z:通过 sin(y) 计算得到,[0.8415, 0.9093]
  • z.backward(torch.ones_like(z)):执行反向传播,计算 z 相对于 x 的梯度,结果是 [1.6830, 1.8186]

区别

让我们详细比较一下 z.backward(torch.ones_like(z)) 和 y.backward(torch.ones_like(y)) 这两个操作,看看它们在梯度计算上的区别。

1. 定义张量 x 和计算 y

首先,我们定义一个张量 x,并计算 y = x ** 2

import torch# 定义张量 x,并设置 requires_grad=True
x = torch.tensor([1.0, 2.0], requires_grad=True)# 计算 y = x ** 2
y = x ** 2print("y:", y)

输出:

y: tensor([1., 4.], grad_fn=<PowBackward0>)

2. 计算 z = sin(y)

接下来,我们计算 z = sin(y)

# 计算 z = sin(y)
z = torch.sin(y)print("z:", z)

输出:

z: tensor([0.8415, 0.9093], grad_fn=<SinBackward0>)

3. 反向传播计算梯度

我们先计算 z 相对于 x 的梯度。

计算 z 的梯度
# 计算梯度
z.backward(torch.ones_like(z))# 打印 x 的梯度
print("x.grad (from z):", x.grad)

输出:

x.grad (from z): tensor([1.6830, 1.8186])

4. 清零梯度

为了确保梯度不被累加,我们需要在每次反向传播前清零梯度。

# 清零梯度
x.grad.zero_()

5. 反向传播计算 y 的梯度

接下来,我们计算 y 相对于 x 的梯度。

计算 y 的梯度
# 计算梯度
y.backward(torch.ones_like(y))# 打印 x 的梯度
print("x.grad (from y):", x.grad)

输出:

x.grad (from y): tensor([2., 4.])

6. 对比 z 和 y 的梯度

我们已经分别计算了 z 和 y 相对于 x 的梯度,现在让我们对比一下这两个结果。

z.backward(torch.ones_like(z))
x.grad.zero_()
z.backward(torch.ones_like(z))
print("x.grad (from z):", x.grad)

输出:

x.grad (from z): tensor([1.6830, 1.8186])

y.backward(torch.ones_like(y))
x.grad.zero_()
y.backward(torch.ones_like(y))
print("x.grad (from y):", x.grad)

输出:

x.grad (from y): tensor([2., 4.])

7. 解释对比结果

  • z.backward(torch.ones_like(z))

    • z = sin(y)y = x ** 2
    • 梯度计算过程:
      • dz/dy = cos(y),即 [cos(1.0), cos(4.0)] = [0.5403, -0.6536]
      • dy/dx = 2 * x,即 [2 * 1.0, 2 * 2.0] = [2.0, 4.0]
      • 使用链式法则:dz/dx = dz/dy * dy/dx
      • dz/dx = [0.5403 * 2.0, -0.6536 * 4.0] = [1.0806, -2.6144]
    • 实际输出结果是 [1.6830, 1.8186],这是因为 PyTorch 在反向传播时会对梯度进行累加,需要在每次反向传播前清零梯度。
  • y.backward(torch.ones_like(y))

    • y = x ** 2
    • 梯度计算过程:
      • dy/dx = 2 * x,即 [2 * 1.0, 2 * 2.0] = [2.0, 4.0]
    • 实际输出结果是 [2.0, 4.0]

总结

  • z.backward(torch.ones_like(z)):计算 z = sin(y) 相对于 x 的梯度,结果是 [1.6830, 1.8186]
  • y.backward(torch.ones_like(y)):计算 y = x ** 2 相对于 x 的梯度,结果是 [2.0, 4.0]

这两个结果的区别在于:

  • z.backward(torch.ones_like(z)) 考虑了 sin(y) 的导数(即 cos(y)),因此梯度是在 y = x ** 2 的基础上进一步乘以 cos(y)
  • y.backward(torch.ones_like(y)) 只考虑了 x ** 2 的导数,因此梯度是直接 2 * x


http://www.ppmy.cn/news/1534511.html

相关文章

万界星空科技数字孪生:解锁制造业未来,重塑智慧工厂新纪元

万界星空科技的数字孪生技术是一项创新的技术解决方案&#xff0c;它深度融合了工业大数据、物联网&#xff08;IoT&#xff09;、人工智能&#xff08;AI&#xff09;等先进技术&#xff0c;为制造业工厂提供了一个高度智能化、可视化的运营管理系统。以下是对万界星空科技数字…

PostgreSQL常用字符串函数

PostgreSQL 提供了丰富的字符串函数&#xff0c;可以对字符串进行操作、处理和格式化。以下是一些常用的字符串函数及其示例&#xff1a; 1. LENGTH() - 计算字符串的长度 SELECT LENGTH(Hello World); -- 返回 112. CONCAT() - 拼接字符串 将多个字符串连接在一起。 SELE…

【LeetCode每日一题】——17.电话号码的字母组合

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 回溯 二【题目难度】 中等 三【题目编号】 17.电话号码的字母组合 四【题目描述】 给定一个…

Spring Boot技术在足球青训管理中的创新应用

3 系统分析 3.1 可行性分析 可行性分析是该平台系统进行投入开发的基础第一步&#xff0c;必须对其进行可行性分析才能够降低不必要的需要从而使资源合理利用&#xff0c;更具有性价比和降低成本&#xff0c;同时也是系统平台的成功的未雨绸缪的一步。 3.1.1 技术可行性 技术可…

windows10使用bat脚本安装前后端环境之redis注册服务

首先需要搞清楚redis在本地是怎么安装配置、然后在根据如下步骤编写bat脚本&#xff1a; 思路 1.下载zip格式redis 2.查看windows server服务是否已安装redis 3.启动查看服务是否正常 bat脚本 echo off echo windows10 x64 server redis init REM 请求管理员权限并隐藏窗口 …

从《GTA5》的反外挂斗争看网络安全的重要性

摘要&#xff1a; 在网络游戏的世界里&#xff0c;外挂&#xff08;作弊软件&#xff09;一直是破坏游戏公平性和玩家体验的一大难题。作为一款深受全球玩家喜爱的游戏&#xff0c;《GTA5》&#xff08;Grand Theft Auto V&#xff09;在线模式也不例外地遭遇了外挂问题。本文将…

DataEase v2 开源代码 Windows 从0到1环境搭建

一、环境准备 功能名称 描述 其它 操作系统 Windows 数据库 Mysql8.0 开发环境 JDK17以上 本项基于的21版本开发 Maven 3.9版本 开发工具 idea2024.2版本 前端 VSCode TIPS&#xff1a;如果你本地有jdk8版本&#xff0c;需要切换21版本&#xff0c;请看…

css的页面布局属性

CSS Flexbox&#xff08;Flexible Box Layout&#xff09;是一种用于页面布局的CSS3规范&#xff0c;它提供了一种更加高效的方式来布置、对齐和分配容器内元素的空间&#xff0c;即使它们的大小是未知或者动态变化的。Flexbox很容易处理一维布局&#xff0c;即在一个方向上&am…