自动求导
自动求导的作用
1.自动计算梯度:深度学习模型的训练依赖于梯度下降法优化损失函数。只需要定义好模型和损失函数,调用 .backward(),框架就能算出每个参数的梯度。
2.支持反向传播算法:自动求导是反向传播的基础,它构建计算图并应用链式法则来自动求出每一层的误差梯度,这正是神经网络能够端到端训练的关键。
3.加速模型开发和调试:有了自动求导,研究者和工程师可以专注于设计模型结构,而不是浪费时间在复杂的数学推导上。能够更快速地迭代和调试模型。
4.适应复杂模型结构:现代神经网络越来越复杂(如循环结构、分支、条件语句等)。 自动求导能在**动态图(如 PyTorch、MXNet Gluon)**中即时构建计算图,灵活地处理这些结构。
5.提高代码可复用性和模块化:自动求导使得代码更清晰、模块化、复用性更强。可以轻松组合各种层、损失函数和优化器,而不必担心手动实现梯度。
自动求导是深度学习模型训练过程中自动计算梯度的机制,使得模型训练变得高效、灵活且易于实现,是现代深度学习框架的基石。
向量链式法则
向量:
例子:
X,W∈ Rn , y∈ R z = (<x,w> - y)2 ,计算z对w求偏导
等于 2(<x,w> - y)xT
计算图
将计算表示为一个无环图
将代码分解成操作子
from mxnet import autograd, ndwith autograd.record():a = nd.ones((2,1))b = nd.ones((2,1))c = 2*a + b
#a 和 b 是两个 shape 为 (2,1) 的张量(2 行 1 列),值全为 1。
a = [[1.0],[1.0]]b = [[1.0],[1.0]]#执行计算 c = 2*a + b
#这是实际的计算操作,会被 autograd 记录。
#2*a 是张量 a 乘以标量 2,结果是 [[2.0], [2.0]]。
#再加上 b,得到 c = [[3.0], [3.0]]。
自动求导的两种模式
链式法则
和线性代数一样
正向累积
反向累积(反向传递)
复杂度
计算复杂度:O(n),n是操作子的个数。
表示在进行前向传播和反向传播时所需的计算量。 比如,一个神经网络的前向传播是 𝑂 ( 𝑛 ) O(n),反向传播可能也是 𝑂 ( 𝑛 ) O(n),因为每一步都需要用到链式法则传播梯度。 在一些研究中,也会研究 memory complexity(内存复杂度)。
内存复杂度:O(n),因为需要存储正向的所有中间结果。在运行一个算法或模型时,占用的内存空间随输入规模的增长情况。
在训练深度神经网络时,内存消耗主要来自以下几个方面:
1. 模型参数 每一层的权重和偏置都会占用内存。 比如,一个全连接层有 𝑛 × 𝑚 n×m 个参数,如果用 float32 表示,每个参数占 4 字节。
2. 中间激活值(forward activations) 为了反向传播,自动求导需要保留前向传播过程中每一层的输出(激活)。 这通常是内存消耗最大的部分。
3. 梯度信息 每个参数和每个中间变量都需要保存梯度。 若使用反向传播,几乎所有中间变量都需要记录其梯度。
4. 临时张量、缓存、优化器状态等 例如 Adam 优化器会为每个参数维护两个额外的状态变量(动量和方差),所以内存需求是参数的 3 倍。 一些框架也会有额外缓存机制。
跟正向累积对比
O(n)计算复杂度用来计算一个变量的梯度
O(1)内存复杂度
自动求导的实现
怎么存梯度?
计算y
pytorch中自动求微分(autograd),点积和矩阵乘法
import torchx = torch.tensor([1.0, 2.0], requires_grad=True)
y = torch.tensor([3.0, 4.0], requires_grad=True)z = torch.dot(x, y) # 点积:1*3 + 2*4 = 11
print(z) # tensor(11., grad_fn=<DotBackward0>)z.backward() # 反向传播计算梯度
print(x.grad) # tensor([3., 4.]) # ∂z/∂x = y
print(y.grad) # tensor([1., 2.]) # ∂z/∂y = x
梯度累积
1.梯度累积的默认行为
PyTorch 的 Autograd 引擎在默认情况下会累加(accumulate)梯度到 .grad 属性中,而不是替换原有值。意味着:
·每次调用backward(),计算出的梯度都会加到之前存的梯度上。
·如果不手动清零,梯度会随着训练步骤不断累积,导致错误更新。
梯度存在哪里?
叶子张量(Leaf Tensors):用户直接创建的张量(如模型参数 nn.Parameter),其梯度存储在 .grad 属性中。
非叶子张量:默认不保留梯度(除非显式调用 retain_grad()),以节省内存。
2.为什么需要梯度累积?
·小批量训练(Mini-Batch Training):
当显存不足时,可以通过多次小前向传播累积梯度,模拟大批量的效果。
# 模拟大批量(batch_size=8)分为4个小步(batch_size=2)
for i in range(4):outputs = model(inputs[i*2 : (i+1)*2])loss = criterion(outputs, labels[i*2 : (i+1)*2])loss.backward() # 梯度累积optimizer.step() # 更新参数(使用累积的梯度)
optimizer.zero_grad() # 清零梯度
·多任务学习: 同时计算多个损失的梯度并累加。
3.如何正确管理梯度?
部分清零梯度 如果只想清零特定变量的梯度:x.grad.zero_()
非标量变量的反向传播
非标量:向量、矩阵、高维张量
backward() 默认只能对标量(scalar)进行梯度计算,PyTorch 的 backward() 默认要求张量是标量(即 shape=()),因为梯度是标量对张量的导数。如果直接对非标量调用 backward(),PyTorch 无法确定如何将梯度传播回输入张量,非标量变量的反向传播显示传入梯度权重(gradient参数)
分离计算
将某些计算移动到记录的计算图之外
Python控制流的梯度计算
计算梯度