学习Pytorch官方文档的自动微分一章,有许多细节没有看懂。查了一些资料,汇总在这里。
PS:建议大家上手Pytorch深度学习还是直接看官方文档吧。我之前东看西看了许多教程,但是脑子还是一团浆糊,耐下心来五六个小时就能读完官方文档,脑子清楚多了。
Pytorch官方文档Automatic Differentiation的链接
Automatic Differentiation
反向传播(back propagation)根据损失函数对某个参数的梯度(gradient)来调整参数。
pytorch使用torch.autograd()
来计算梯度。
为了进行参数优化,我们需要计算损失函数相对于这些参数的梯度(gradients)。梯度告诉我们在当前参数值下,沿着哪个方向以及多大程度上调整参数可以减小损失函数的值。通过梯度下降等优化算法,我们可以根据梯度的方向调整参数的值,逐渐接近最优解。
在PyTorch等深度学习框架中,张量(tensors)是计算图中的关键组件,用于表示数据和参数。默认情况下,张量的requires_grad属性被设置为False,表示它们不需要计算梯度。然而,对于需要优化的参数,我们需要将其requires_grad属性设置为True,以便在后续的反向传播过程中计算梯度。
通过将参数的requires_grad属性设置为True,框架会跟踪这些参数的计算历史,并在需要时自动计算它们的梯度。这使得我们可以使用自动微分(automatic differentiation)技术来计算损失函数相对于参数的梯度,从而进行优化。
# 创建一个张量
x = torch.tensor([1.0, 2.0, 3.0])
# 将requires_grad属性设置为True
x.requires_grad_(True)
# 或者可以使用下面的方式
x.requires_grad = True
# 创建一个张量并设置requires_grad为True
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
Tensor.backward(gradient=None, retain_graph=None, create_graph=False, inputs=None)
用于计算当前张量相对于计算图的叶节点的梯度,使用链式求导法则。
梯度累积
-
叶节点(Graph Leaves):计算图中没有进一步后续计算的节点,通常是输入数据或模型的参数。
-
梯度累积:每次调用反向传播算法,梯度值会累加到叶节点的
.grad
属性中。-
批量梯度下降等优化算法有意义:可以在多个样本上累积梯度,从而更好地更新参数
-
不恰当的梯度累积
- 梯度爆炸:梯度绝对值非常大,数值不稳定、参数更新难以预测
- 梯度消失:梯度绝对值非常小,参数更新缓慢
-
清除梯度累积的方法
-
optimizer.zero_grad() # 清零所有参数的梯度
-
leaf_tensor.grad = None # 将特定叶节点的梯度设置为None
-
-
叶节点和非叶节点
- 叶节点是
requires_grad = True
的节点 - 只有叶节点的
.grad
属性可以直接访问 - 非叶节点的梯度值没办法直接获取,但是在梯度传播、参数更新和梯度裁剪中有重要作用
Disabling Gradient Tracking
# torch.no_grad()的上下文中执行的操作不会跟踪梯度
with torch.no_grad():
z = torch.matmul(x, w)+b
z = torch.matmul(x, w)+b
# z_det不会跟踪梯度, 但是z还会跟踪
z_det = z.detach()
禁用梯度跟踪与非叶节点
- 禁用梯度跟踪,节点将不会出现在计算图中
- requires_grad=False,节点在计算图中作为非叶节点出现。不会计算和存储梯度,梯度值为0.
- 某个节点设置为禁止梯度跟踪,可能导致与该节点相关的叶子节点无法连接到计算图中
禁用梯度跟踪的情况
- 冻结参数(frozen parameters):训练中这些参数不动。
- 参数固定性:参数在预训练中已经得到比较好的初始值。为保留先前的知识或防止过拟合,不再修改。
- 计算效率:冻结参数的梯度不会计算与更新,以节省反向传播的计算资源
- 防止权重漂移:某些参数对训练数据比较敏感,为了防止过度适应训练数据,冻结他们以提高泛化性
- 加速推理:只进行前向传递,不进行反向传播的情况。
Computational Graphs
这是一张有向无环图(DAG),叶子节点是输入张量,根是输出张量。在这张图上做前向传递和后向传播两件事。
Forward Pass
- 运行要求的操作以生成结果张量
- 在DAG图上维护梯度函数(Gradient Function)
- 计算图上的每个操作节点都有一个梯度函数,它定义了操作节点对于输出值的梯度计算方式
- 保存每个节点的输出。有了梯度函数和输出可以保证在反向传播时可以计算梯度。
Backward Pass
- DAG的根(通常是损失函数)调用
.backward()
- 将梯度累计在
.grad
属性中 - 使用链式法则,一直传播到叶节点
Note:
计算图是动态变化的,每次都在前向传递时生成。
每张计算图只能调用一次后向传播(.backward()
),计算完毕后该计算图会被自动清除。
如果想要在一张计算图上计算多次后向传播,需要在调用.backward()
时,使用参数retain_graph=True
。
需要在一张计算图上进行多次后向传播的情况:
- 多次梯度累计:在一个小批量数据上进行多次前向传播和反向传播,然后将梯度累积起来,而不是在每个批次上立即更新模型参数。这种情况下,我们需要在同一张计算图上进行多次反向传播,并在每次反向传播后累积梯度。最后,根据累积的梯度来更新模型参数。
- 多任务学习:在多任务学习中,我们可能有多个任务共享同一个模型。每个任务都有自己的损失函数,需要计算对应的梯度。这样,模型可以同时学习多个任务,并共享参数。