PyTorch(五)自动微分

#c 概述 文档概述

涉及到的知识点讲解:「自动微分」,「梯度」,「权重」,「偏置」,「反向传播」,「计算图」。

主要讲解了「自动微分」与「梯度计算」,「计算图」之间的概念关系。

1 基本概念

#c 说明 自动微分的使用

在训练神经网络时,最常用的算法是「反向传播 back propagation」。在这个算法中,参数(「模型权重」)根据「损失函数」的「梯度」进行调整。

为了计算这些梯度,PyTorch有一个名为torch.autograd的内置微分引擎。它支持任何计算图的梯度自动计算 。

#d 自动微分

「自动微分」(Automatic Differentiation)是一种计算机科学技术,用于自动高效地计算函数的导数或梯度。与符号微分不同,它不是通过解析表达式来计算导数,而是通过实际计算过程中的值和操作来获得导数。自动微分利用了链式法则,将复杂函数分解为基本操作的序列,然后计算这些基本操作的导数,最终组合得到整个函数的导数。

自动微分的属性:

  1. 精确性(Precision):自动微分可以提供与手动微分几乎相同的精确结果,不同于数值微分的近似方法。
  2. 效率(Efficiency):通过优化计算过程,自动微分可以高效地计算导数,特别是对于复杂的函数。
  3. 通用性(Generality):自动微分不依赖于函数的具体形式,可以广泛应用于任何可微分的函数。

创造自动微分概念的目的:

  1. 简化梯度计算:自动微分使得对于复杂函数的导数计算变得简单,用户不需要手动推导导数。
  2. 提高计算效率:在机器学习和优化问题中,自动微分可以高效地计算梯度,加速模型的训练过程。
  3. 增强模型设计灵活性:由于自动微分的通用性,研究人员和开发者可以更自由地设计和实验复杂的模型,而不必担心导数计算的复杂性。

没有自动微分会有什么影响:

  1. 导数计算复杂化:在没有自动微分的情况下,对于复杂函数的导数计算将变得非常困难,需要手动推导,容易出错。
  2. 模型训练效率降低:手动计算梯度或使用数值微分方法会大大降低模型训练的效率,特别是对于大规模数据和复杂模型。
  3. 限制模型创新:复杂的导数计算可能限制模型设计的创新性,因为开发者可能会避免使用难以手动计算导数的模型结构。

#e 做蛋糕 自动微分

想象你在做蛋糕,配方需要根据蛋糕的大小来调整原料的比例。你有一个基本配方,但每次做的蛋糕大小不一,所以需要调整配方中的每个原料。这个调整过程就像是自动微分的过程。你知道每个原料对蛋糕整体味道的影响(导数),当蛋糕大小改变时(函数的输入变化),你可以自动计算出需要增加或减少多少原料(函数的导数),以保持蛋糕的味道。这里,自动调整原料的过程就类似于自动微分,帮助你根据输入的变化(蛋糕大小)自动调整输出(原料比例)。

#e 线性回归模型 自动微分

训练神经网络涉及到根据损失函数的梯度来调整模型参数。考虑一个简单的线性回归模型,它试图找到一条直线,最好地拟合输入数据和输出数据之间的关系。

在这个例子中,loss.backward()这一步就是自动微分的过程,它自动计算了损失函数相对于模型参数(w和b)的梯度。这些梯度告诉我们如何调整参数以减少损失,即如何调整直线的斜率和截距,使得直线更好地拟合数据。这个过程无需手动计算导数,大大简化了神经网络的训练过程。

python 复制代码
import torch

# 假设我们有一些输入数据x和输出数据y
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = torch.tensor([2.0, 4.0, 6.0])

# 定义模型参数,初始化权重w和偏置b
w = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)

# 定义模型,线性回归模型y_pred = w * x + b
y_pred = w * x + b

# 定义损失函数,这里使用均方误差
loss = torch.mean((y_pred - y) ** 2)

# 使用自动微分计算损失函数的梯度
loss.backward()

# 打印梯度
print(w.grad)  # 输出w的梯度
print(b.grad)  # 输出b的梯度

#c 关联 相关概念 自动微分

  1. 导数(Derivative): 导数是微积分中的基本概念,表示函数在某一点的切线斜率,即函数值的变化率。

  2. 微分(Differential): 微分是导数的线性主部,用于近似表示函数在小范围内的变化。

  3. 链式法则(Chain Rule): 链式法则是微积分中的一种基本法则,用于计算复合函数的导数。

  4. 雅可比矩阵(Jacobian Matrix): 雅可比矩阵是一个向量函数的导数矩阵,描述了多变量函数的局部线性近似。

  5. 海森矩阵(Hessian Matrix): 海森矩阵是二阶偏导数矩阵,用于描述函数曲率或二阶局部变化率。

  6. 符号微分(Symbolic Differentiation): 符号微分是通过代数操作直接计算函数的解析导数的方法。

  7. 数值微分(Numerical Differentiation): 数值微分是使用数值方法近似计算导数,如有限差分法。

  8. 反向传播(Backpropagation): 反向传播是自动微分在神经网络中的一种应用,用于计算损失函数对网络参数的梯度。

  9. 前向模式自动微分(Forward Mode Automatic Differentiation): 前向模式自动微分在计算过程中,从输入到输出,逐步计算导数。

  10. 后向模式自动微分(Backward Mode Automatic Differentiation,又称为反向模式): 后向模式自动微分从输出开始,逆向计算导数,这种方法在深度学习中更为常用。

  11. 混合自动微分(Hybrid Automatic Differentiation): 混合自动微分结合了前向和后向模式,以优化计算效率。

  12. 梯度(Gradient): 梯度是一个向量,其分量是多变量函数对各个变量的偏导数。

  13. 损失函数(Loss Function): 损失函数衡量模型预测与实际值之间的差异,是优化过程中需要最小化的目标。

  14. 优化算法(Optimization Algorithm): 优化算法用于调整模型参数,以最小化损失函数,自动微分在这一过程中提供必要的梯度信息。

  15. 数值稳定性(Numerical Stability): 数值稳定性指的是数值方法在计算过程中对误差的敏感度,自动微分需要考虑这一点以保证结果的准确性。

#d 梯度

梯度(Gradient)表示的是一个多变量函数在给定点处的方向导数沿着最大上升方向的大小。简单来说,梯度指向最快增加的方向,并且其大小表示在该方向上函数增加的速度。

梯度的属性:

  1. 方向性:梯度有特定的方向,这个方向是函数值增加最快的方向。
  2. 大小:梯度的大小(或长度)表示函数值在该方向上增加的速度。
  3. 依赖性:梯度依赖于函数在某一点的局部行为,即只有在定义了函数的导数的情况下,梯度才有意义。
  4. 多维性:梯度是一个向量,它存在于函数的输入空间中,这意味着对于多变量函数,梯度考虑了所有变量的变化。

创造「梯度」概念的目的:

梯度概念的创造主要是为了解决在多维空间中寻找函数最大增长率方向的问题。在优化问题中,梯度被用来指导搜索过程,以找到函数的局部最小值或最大值。特别是在机器学习和深度学习中,梯度用于指导模型参数的更新,以最小化损失函数。

没有「梯度」概念的影响:

  1. 优化困难:在多维空间中寻找函数的极值(最小值或最大值)将变得非常困难,因为没有明确的方向指导搜索过程。
  2. 学习效率低下:在机器学习和深度学习中,模型参数的更新将缺乏有效的指导,可能导致学习过程非常缓慢,甚至无法收敛到最优解。
  3. 理解局限:梯度不仅仅是优化的工具,它还帮助我们理解函数在多维空间中的行为。没有梯度,我们对这些函数的局部和全局特性的理解将受到限制。

#e 最快下山 梯度

想象你在一座山上,目标是以最快的速度到达山底。在这个情境中,你可以将「梯度」理解为你所在位置的最陡峭方向。如果你总是选择下山的最陡峭路径走,那么你沿着「梯度」下山。在这个例子中,山的高度相当于函数值,而你寻找下山最快路径的过程,就类似于寻找函数最小值的过程。

#d 权重

在神经网络中,权重(Weight)是连接神经元之间的参数,用于影响网络中信息的流动。每个连接都有一个权重,它决定了一个神经元的输出在传递给下一个神经元时的重要性。在训练过程中,通过调整这些权重,神经网络能够学习从输入数据到期望输出的映射。

「权重」的属性:

  1. 可调性(Adjustability):权重可以根据反向传播算法计算得到的梯度进行调整。
  2. 数值性(Numerical):权重是具有实数值的参数,这些值决定了神经网络的行为。
  3. 连接性(Connectivity):每个权重都对应网络中两个神经元之间的一个连接。

创造「权重」概念的目的:

  1. 信息加权:权重允许神经网络对传入的信息进行加权,这意味着网络可以学习哪些信息是重要的,哪些是不重要的。
  2. 模式学习:通过调整权重,神经网络能够学习和识别输入数据中的复杂模式和关系,从而进行准确的预测或分类。
  3. 泛化能力:合适的权重设置使得神经网络不仅能够对训练数据进行建模,还能够对未见过的数据进行准确的预测,即具有泛化能力。

没有「权重」会有什么影响:

  1. 无法学习:没有权重,神经网络将无法调整其行为来适应输入数据,因此也就无法学习数据中的模式和关系。
  2. 无法进行预测或分类:权重是神经网络进行决策的基础。没有权重,网络将无法对新的输入数据进行有效的预测或分类。
  3. 失去深度学习的核心:权重调整是深度学习的核心机制之一。没有权重,我们将失去构建和训练深度神经网络的能力。

#e 菜调料的量 权重

想象你在制作一道菜,其中包括多种调料:盐、糖、醋等。每种调料的添加量(权重)将直接影响最终菜肴的味道。如果盐加得太多(权重太大),菜可能会变得过咸;如果糖加得太少(权重太小),可能就不够甜。通过调整每种调料的添加量(优化权重),你可以使菜肴达到理想的味道。

#e 线性回归问题 权重

考虑一个简单的线性回归问题,目标是根据房屋的大小(平方米)来预测其价格。在这个问题中,有一个模型 价格 = 权重 * 大小 + 偏差。这里的「权重」决定了房屋大小对价格的影响程度。如果权重设置得太高,模型可能会过高估计大房子的价值;如果权重设置得太低,模型可能会低估大房子的价值。通过训练数据来调整这个权重,我们的模型可以更准确地预测不同大小房屋的价格。

#d 偏置

在机器学习和神经网络中,偏置(Bias)是一个额外的参数,用于调整输出以外的模型预测线。偏置允许模型的输出能够不完全依赖于输入数据的权重和,从而增加模型的灵活性。在数学上,偏置通常作为线性方程或神经网络节点的一个常数项,与输入数据无关,但是对输出有直接影响。

偏置的属性:

  1. 调整能力(Adjustability):偏置值是可调整的,通过学习过程优化,以改善模型的预测性能。
  2. 独立性(Independence):偏置与模型的输入数据独立,它不是输入数据的函数。
  3. 增加灵活性(Flexibility Enhancement):偏置增加了模型的灵活性,使模型能够更好地适应数据的偏移。

创造「偏置」概念的目的:

  1. 提高模型适应性:偏置使模型能够适应那些不仅仅通过输入数据的线性组合就能解释的情况,增加了模型处理各种数据的能力。
  2. 增加表达能力:在神经网络中,偏置项增加了网络的表达能力,允许网络学习和表示更复杂的模式。
  3. 解决数据偏移问题:偏置有助于处理数据集中的偏移问题,即使所有输入都为零,模型仍然可以产生非零的输出。

没有「偏置」会有什么影响:

  1. 模型表达能力受限:没有偏置,模型(特别是神经网络)的表达能力将大大受限,可能无法学习数据中的复杂模式或关系。
  2. 适应性降低:模型将无法适应那些不能仅通过输入数据的权重和来解释的情况,降低了模型对数据变化的适应性。
  3. 学习能力受限:在某些情况下,缺乏偏置会导致模型无法学习到有效的解决方案,特别是在处理非线性问题时。

#e 果汁加糖 偏置

想象一下,你在一个果汁店工作,负责制作顾客点的各种果汁。每种果汁的基本成分是水果,但为了确保即使是没有特别要求的普通果汁也能有一定的甜味,你会在每份果汁中加入一小勺糖。这里,水果是输入,果汁是输出,而那一小勺糖就像是偏置,它确保了输出(果汁)即使在没有额外甜味要求(即输入没有特别指定甜度)的情况下也能达到一个基本的甜味标准。偏置在这里调整了果汁的基本味道,使得所有的果汁都有一个共同的起点。

#d 反向传播

反向传播(Back Propagation)是一种在神经网络中用于「优化模型参数」的算法。它的核心思想是通过计算「损失函数」(一个衡量模型预测与实际结果差异的函数)对每个参数的「梯度」(即偏导数),来指导模型参数的更新,以减少损失函数的值,从而提高模型的预测准确性。

「反向传播」的步骤:

  1. 前向传播:在前向传播阶段,输入数据通过网络层传递,每一层对数据进行处理(如加权和、激活函数等),最终产生输出。
  2. 损失计算:使用损失函数(例如均方误差或交叉熵)来衡量预测结果与实际标签之间的差异。
  3. 反向传播:在反向传播阶段,计算损失函数(即模型输出与实际标签之间的差异)的梯度,并将这些梯度传回网络,以便更新模型的权重和偏置。
  4. 参数更新:根据计算得到的梯度,更新网络的权重和偏置,以减少损失。

「反向传播」的属性:

  • 损失函数依赖:反向传播需要一个损失函数来评估模型的性能。
  • 梯度计算:它涉及到对损失函数相对于模型参数的梯度的计算。
  • 参数更新:利用计算出的梯度来更新模型参数,以最小化损失函数。

创造「反向传播」的目的:

  • 优化模型参数:反向传播提供了一种有效的方法来计算损失函数对模型参数的梯度,这对于使用梯度下降(或其他优化算法)来优化这些参数至关重要。
  • 提高训练效率:通过自动化梯度计算,反向传播极大地提高了神经网络训练的效率和可行性,尤其是对于深层网络。

没有「反向传播」的影响:

  • 训练效率低下:在没有反向传播的情况下,我们需要依赖其他不那么有效的方法来计算梯度,这将大大降低训练过程的效率。
  • 优化困难:缺乏一种有效的梯度计算和参数更新机制,使得优化深层神经网络变得非常困难,这可能导致模型训练不充分,难以达到较高的准确率。
  • 应用受限:没有反向传播算法,复杂的神经网络结构(如深度学习模型)的研究和应用将受到极大限制,影响人工智能领域的发展。

#e 烹饪新菜 反向传播

想象你在烹饪一道新菜,但是没有固定的食谱,你的目标是让这道菜尽可能地美味。你尝试了第一次,发现味道太咸了。这里,"太咸"就像是「损失函数」,告诉你结果与期望的差距。为了改进,你决定减少盐的量。这个调整过程就像是「反向传播」,你根据"太咸"这个反馈来调整"盐的量"这个参数。下一次,你可能会发现味道接近理想,或者需要进一步的调整。这个过程会一直持续,直到你觉得这道菜足够美味。

#e 预测房价 反向传播

假设一个简单的神经网络,用于预测房价,它接受「房屋的面积(平方米)」和「卧室数量」作为输入,并预测房价。使用「均方误差(MSE)」作为「损失函数」来衡量预测价格与实际价格之间的差异。

这个例子中,首先进行「前向传播」,计算预测的房价并基于这些预测计算损失。然后,执行loss.backward(),这个操作就是「反向传播」的过程,它计算损失函数关于「权重」和「偏置」的梯度。最后,可以看到权重和偏置的梯度,这些梯度告诉我们应该如何调整这些参数以减少损失,即使我们的预测更接近实际的房价。

python 复制代码
import torch

# 假设的输入和真实房价
inputs = torch.tensor([[50, 2], [60, 3], [40, 1]], dtype=torch.float32)
real_prices = torch.tensor([300000, 350000, 250000], dtype=torch.float32)

# 初始化权重和偏置
weights = torch.randn(2, requires_grad=True)
bias = torch.randn(1, requires_grad=True)

# 前向传播
predicted_prices = inputs @ weights + bias

# 计算损失
loss = torch.mean((predicted_prices - real_prices) ** 2)

# 反向传播
loss.backward()

# 输出梯度
print(weights.grad)
print(bias.grad)

#c 关联 相关概念 反向传播

「反向传播」相关联的概念:

  1. 损失函数(Loss Function):损失函数衡量模型预测值与真实值之间的差异。反向传播算法使用这个差异来计算对每个参数的梯度,以指导模型如何调整其参数以减少损失。

  2. 梯度(Gradient):梯度是损失函数相对于模型参数的导数,指示了损失函数在参数空间中增加最快的方向。反向传播算法计算这些梯度来更新模型的参数。

  3. 优化器(Optimizer):优化器利用计算得到的梯度来更新模型的参数,以减少损失函数的值。常见的优化器包括SGD、Adam等。

  4. 计算图(Computational Graph):计算图是一个图形表示,其中节点表示操作或计算步骤,边表示计算之间的依赖关系(即数据流)。反向传播通过这个图来有效地计算梯度。

  5. 自动微分(Automatic Differentiation):自动微分是一种计算导数的技术,它可以自动、准确地计算函数的导数。在神经网络中,自动微分使得反向传播算法能够实现

2 张量,函数,计算图

#d 计算图

计算图(Computation Graph)是一种图形表示,用于描述数据(如张量)和执行的操作(如数学运算)之间的关系。在这个图中,节点(vertices)通常代表操作(如加法、乘法等),而图的边(edges)代表数据流动的方向,即操作的输入和输出。

计算图的属性:

  1. 节点表示操作:图中的节点代表各种数学运算或函数。
  2. 边表示数据流:图中的边表示数据(如张量)从一个操作流向另一个操作。
  3. 方向性:计算图是有向的,边的方向表示数据流动的方向。
  4. 依赖关系:图中的边还表示操作之间的依赖关系,即一个操作的输出可以是另一个操作的输入。

创造「计算图」概念的目的:

计算图概念的创造主要是为了解决深度学习和其他计算密集型任务中的优化问题。具体来说:

  1. 自动微分:通过计算图,可以自动有效地计算梯度,这对于训练神经网络至关重要。
  2. 优化计算:计算图使得可以对计算过程进行优化,比如合并某些操作,减少不必要的计算。
  3. 可视化和调试:计算图提供了一种直观的方式来理解和调试复杂的计算过程。

没有「计算图」概念的影响:

如果没有计算图这个概念:

  1. 自动微分困难:开发者需要手动计算复杂函数的梯度,这不仅容易出错,而且非常低效。
  2. 优化受限:缺乏对整个计算过程的全局视图会使得优化计算过程变得更加困难,可能导致资源浪费。
  3. 调试和理解挑战:没有计算图,理解和调试复杂的计算过程将变得更加困难,特别是在处理大型深度学习模型时。

#e 一层神经网络 计算图

应用于「张量」以构造「计算图」的「函数」实际上是Function类的对象。该对象知道如何在「正向」计算函数,以及如何在「反向传播」步骤中计算其导数。对反向传播函数的「引用」存储在张量grad_fn属性中。

python 复制代码
import torch
x = torch.ones(5)#创建一个全1张量,形状为5*1
y = torch.zeros(3)#创建一个全0张量
w = torch.rand(5, 3, requires_grad=True)#创建一个随机张量,形状为5*3,需要计算梯度
b = torch.rand(3, requires_grad=True)#创建一个随机张量,形状为3*1,需要计算梯度
z = torch.matmul(x, w)+b#矩阵乘法
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)#损失函数为二进制交叉熵

print(f"Gradient function for z = {z.grad_fn}")#打印z的梯度函数
print(f"Gradient function for loss = {loss.grad_fn}")#打印loss的梯度函数
'''
Gradient function for z = <AddBackward0 object at 0x00000276285A12A0>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x00000276285A12A0>
'''

上述代码的计算图:

#d 计算图与自动微分

从概念上讲,「自动微分(autograd)」保留了「数据(张量)」和所有执行的操作(以及由此产生的新张量)的记录,在由Function对象组成的「有向无环图(DAG)」中。在这个DAG中,叶子是输入张量,根是输出张量。通过从根到叶子追踪这个图,可以使用「链式法则」自动计算梯度。

在前向传播中,「自动微分(autograd)」同时做两件事:

  1. 执行请求的操作来计算结果张量。
  2. 在DAG中维护操作的「梯度函数」。

当在DAG根上调用.backward()时,开始反向传播。然后自动微分有如下操作:

  1. 从每个.grad_fn计算梯度,
  2. 将它们累积在相应张量的.grad属性中,
  3. 使用链式法则,一直传播到叶张量。

在PyTorch中,DAG(有向无环图)是动态的。一个重要的特点是,每次调用.backward()之后,计算图都会从头开始重新创建。这正是允许在模型中使用控制流语句的原因;如果需要,可以在每次迭代中改变形状、大小和操作。

3 计算梯度

#c 目的 计算梯度

为了优化神经网络中参数的权重,需要计算损失函数相对于参数的导数。为了计算这些导数,可以调用loss.backward(),然后从w.gradb.grad中检索梯度值。

python 复制代码
loss.backward()#计算梯度
print(w.grad)#打印w的梯度
print(b.grad)#打印b的梯度
'''
tensor([[0.3175, 0.3126, 0.3231],
        [0.3175, 0.3126, 0.3231],
        [0.3175, 0.3126, 0.3231],
        [0.3175, 0.3126, 0.3231],
        [0.3175, 0.3126, 0.3231]])
tensor([0.3175, 0.3126, 0.3231])
'''

#c 条件 计算梯度的条件

  1. 只能获得「计算图」的「叶节」点的「梯度属性」,这些叶节点requires_grad属性设置为True。对于图中的所有其他节点,梯度将不可用。
  2. 出于性能原因,仅在一个给定的图上执行一次梯度计算。如果需要在同一张图上进行多次反向调用,需要在反向调用时传递retain_graph=True参数。

4 禁用梯度跟踪

#c 说明 禁用的目的

默认情况,张量在tensor.requires_grad=True,都会跟踪它们的计算历史并支持梯度计算。然而,在某些情况下,不需要这样做,例如,当模型已经训练好并且只想将其应用于某些输入数据时,也就是说,只想通过网络执行前向计算。可以通过将计算代码包围在torch.no_grad()块中来停止跟踪计算.

python 复制代码
z = torch.matmul(x, w)+b#矩阵乘法
print(z.requires_grad)#打印z是否需要计算梯度

with torch.no_grad():#禁用梯度跟踪
    z = torch.matmul(x, w)+b#矩阵乘法
print(z.requires_grad)#打印z是否需要计算梯度
'''
True
False
'''

另一种方法是使用detach()方法从计算历史中分离张量。

python 复制代码
z = torch.matmul(x, w)+b#矩阵乘法
z_det = z.detach()#分离张量
print(z_det.requires_grad)#打印z_det是否需要计算梯度
'''
False
'''

#c 场景 禁用梯度跟踪的场景

  1. 将神经网络中的一些参数标记为冻结参数。

  2. 当只进行前向传播时加速计算,因为不跟踪梯度的张量的计算将更加高效。

相关推荐
风象南24 分钟前
Claude Code这个隐藏技能,让我告别PPT焦虑
人工智能·后端
曲幽1 小时前
FastAPI压力测试实战:Locust模拟真实用户并发及优化建议
python·fastapi·web·locust·asyncio·test·uvicorn·workers
Mintopia1 小时前
OpenClaw 对软件行业产生的影响
人工智能
陈广亮2 小时前
构建具有长期记忆的 AI Agent:从设计模式到生产实践
人工智能
会写代码的柯基犬2 小时前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm
Mintopia2 小时前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区2 小时前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两5 小时前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
敏编程5 小时前
一天一个Python库:jsonschema - JSON 数据验证利器
python