关于在循环中backward()的注意事项

在PyTorch训练过程中遇到'尝试通过图进行第二次反向传播'的错误,通常由重复调用loss.backward()引起。不应简单地设置retain_graph=True,这会导致内存消耗过大。正确的做法是查找代码问题并使用detach()。本文以一个在执行CW攻击时遇到的实例说明,解释了如何处理在旧计算图中的变量,确保它们在循环外detach(),以防止错误发生。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

许多人会在训练时遇到这样一个报错:

trying to backward through the graph a second time (or directly access saved tensors after they have already been freed)

一般原因是在每个epoch中调用loss.backward()时 触发这个错误。此时一般不要按照报错代码中官方给出的推荐一样去做retain_graph=True,因为这样每次循环都会增大memory,导致out of menmory. 而是应该去寻找自己代码的问题,做detach()。参考python - RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed) - Stack Overflow

我的问题和他不太一样,更为简单易懂,但是原理差不多。触发这个的场景是在执行cw attack的时候。 报错代码大致如下


target_feature = model(target_image)

def forward(self,images,labels):
    images = images.clone().detach().to(self
<think>好的,我现在需要介绍一下在PyTorch中如何实现混合精度训练。混合精度这个概念我之前有听说过,主要是利用半精度浮点数(FP16)和单精度浮点数(FP32)结合来加速训练,同时保持模型的准确性。不过具体的实现步骤和需要注意的地方,我还需要仔细回忆和整理一下。 首先,PyTorch中混合精度的实现主要依赖于`torch.cuda.amp`模块,这个模块提供了自动混合精度(Automatic Mixed Precision,AMP)的功能。我记得这个模块中有两个主要的类:`autocast`和`GradScaler`。可能是在前向传播时使用`autocast`上下文管理器自动将部分运算转换为FP16,以减少计算量和内存占用,而反向传播时则用FP32来保持数值稳定性。同时,`GradScaler`用于缩放梯度,防止梯度下溢,因为FP16的表示范围较小,梯度值过小的话可能会变成零。 接下来,具体的步骤应该包括几个部分:初始化模型和优化器、创建`autocast`上下文、前向传播时使用`autocast`、计算损失、使用`GradScaler`缩放梯度、反向传播、更新优化器等。可能还需要在训练循环中调整scaler的更新策略,比如每隔几个step更新一次。 不过,有些细节需要注意。比如,并不是所有的层都适合使用FP16,有些操作可能在FP16下会出现数值不稳定,这时候可能需要手动将这些操作保持在FP32中。另外,使用混合精度时,模型参数通常还是以FP32存储,但前向和反向传播中的计算使用FP16,这样可以节省内存并加速计算。此外,梯度缩放是为了防止梯度在FP16中下溢,scaler会根据梯度的大小自动调整缩放因子,避免梯度消失或爆炸。 可能的实现步骤大概是这样的: 1. 导入必要的模块:`torch.cuda.amp`中的`autocast`和`GradScaler`。 2. 初始化模型和优化器,确保模型参数在CUDA设备上。 3. 创建`GradScaler`实例。 4. 在训练循环中,对每个batch的数据: a. 将数据转移到GPU。 b. 开启`autocast`上下文,进行前向传播。 c. 计算损失。 d. 使用`scaler.scale(loss).backward()`进行反向传播。 e. 使用`scaler.step(optimizer)`更新参数。 f. 调用`scaler.update()`调整缩放因子。 g. 优化器梯度清零。 需要确认这些步骤是否正确,是否有遗漏或者错误的地方。比如,是否每次反向传播后都需要scaler.step和scaler.update?是的,因为scaler的作用是管理梯度缩放,每次更新参数后需要更新scaler的状态。还有,优化器在scaler.step中被调用,是否应该像常规训练一样先进行梯度清零?是的,应该在scaler.step之后清零梯度,或者在scaler.step之前?应该是之后,常规步骤是loss.backward(),然后optimizer.step(),然后optimizer.zero_grad()。这里用scaler的话,应该是scaler.step(optimizer)代替optimizer.step(),之后调用scaler.update(),然后再optimizer.zero_grad()。或者scaler.step内部是否已经处理了optimizer的step?是的,scaler.step会执行优化器的step,然后scaler.update更新缩放因子。所以顺序应该是:反向传播之后scaler.step(optimizer),然后scaler.update(),然后optimizer.zero_grad()。或者是否需要zero_grad在scaler.step之前?可能顺序是:每次迭代开始的时候,先optimizer.zero_grad(),然后前向,计算loss,反向传播,scaler.step,scaler.update。或者可能是在scaler.step之后调用optimizer.zero_grad()?需要再确认。 例如,常规流程是: for inputs, labels in data: optimizer.zero_grad() outputs = model(inputs) loss = loss_fn(outputs, labels) loss.backward() optimizer.step() 而在混合精度中: scaler = GradScaler() for inputs, labels in data: optimizer.zero_grad() with autocast(): outputs = model(inputs) loss = loss_fn(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() 这样看来,optimizer.zero_grad()在每次迭代开始时调用,scaler.step会执行optimizer.step(),然后scaler.update调整缩放因子。因此,顺序是对的。 另外,关于哪些操作会被自动转换到FP16,哪些需要保持FP32。PyTorch的autocast会自动处理大部分常见的运算,比如卷积、矩阵乘法等,这些在FP16下更快且内存占用更少。但有些操作,比如Softmax、BatchNorm等可能需要更高的精度,所以autocast会自动将它们保持在FP32中。如果用户自定义了一些操作,可能需要手动指定其精度,或者让autocast处理。 如果遇到某些操作在FP16下不稳定,可能需要将某些模块强制转为FP32。例如,在模型定义中,将某些层用`.float()`方法转换为FP32,或者在autocast上下文中使用`torch.cuda.amp.custom_fwd`或`torch.cuda.amp.custom_bwd`装饰器来指定前向或反向传播的精度。 另外,使用混合精度训练时,模型可能会占用更少的内存,从而允许更大的batch size或者更大的模型。但需要注意梯度缩放是否正确,否则可能导致训练不稳定。 还有一个需要注意的点是,在评估模型时(如验证或测试阶段),是否需要使用混合精度?通常来说,在推理时也可以使用autocast来加速计算,但不需要进行梯度缩放,因为不需要反向传播。所以在验证阶段,可以仅使用autocast上下文,而不需要GradScaler的部分。 总结一下,PyTorch中实现混合精度的步骤是: 1. 导入必要的模块。 2. 初始化模型、优化器、GradScaler。 3. 在训练循环中,对每个batch: a. 清空梯度。 b. 在autocast上下文中进行前向计算。 c. 计算损失。 d. 使用scaler缩放损失并执行反向传播。 e. 使用scaler.step更新参数。 f. 更新scaler的缩放因子。 g. 清空优化器的梯度。 可能还需要注意数据类型的转换,比如确保输入数据是FP32还是FP16?通常输入数据是FP32,在autocast上下文中,模型会自动将部分运算转换为FP16。模型参数通常以FP32存储,但在计算时会转换为FP16。因此,在混合精度训练中,模型的主参数是FP32,这样可以更稳定地进行参数更新,而计算使用FP16加速。 另外,使用混合精度时,可能会遇到NaN或者Inf的问题,这时候需要检查是否梯度缩放适当,或者是否有某些操作在FP16下不稳定。这时候可以尝试禁用某些层的自动转换,或者在代码中加入梯度检查。 现在,我需要将这些思考整理成一个清晰的中文回答,分步骤说明如何在PyTorch中实现混合精度训练,同时指出需要注意的事项和常见问题。确保步骤准确,并且涵盖关键点,比如autocast的使用、GradScaler的作用和使用方法,以及可能的注意事项。</think>在 PyTorch 中实现混合精度训练可以显著加速训练并减少显存占用,同时保持模型精度。以下是具体实现步骤和注意事项: --- ### **核心工具:`torch.cuda.amp` 模块** PyTorch 通过 `torch.cuda.amp` 提供自动混合精度(AMP)功能,主要依赖两个类: 1. **`autocast`**:自动将部分运算转换为 FP16,加速计算。 2. **`GradScaler`**:动态缩放梯度,防止 FP16 下梯度下溢。 --- ### **实现步骤** #### **1. 导入模块** ```python import torch from torch.cuda.amp import autocast, GradScaler ``` #### **2. 初始化模型、优化器和 GradScaler** ```python model = MyModel().cuda() # 模型需在 GPU 上 optimizer = torch.optim.Adam(model.parameters()) scaler = GradScaler() # 梯度缩放器 ``` #### **3. 修改训练循环** ```python for inputs, labels in dataloader: inputs = inputs.cuda() labels = labels.cuda() # 清空梯度 optimizer.zero_grad() # 前向传播:在 autocast 上下文中自动混合精度 with autocast(): outputs = model(inputs) loss = loss_fn(outputs, labels) # 损失计算 # 反向传播与梯度缩放 scaler.scale(loss).backward() # 缩放损失并反向传播 scaler.step(optimizer) # 缩放梯度并更新参数 scaler.update() # 调整缩放因子 # 梯度清零(通常在 optimizer.step() 之后) optimizer.zero_grad() ``` --- ### **关键细节说明** 1. **`autocast` 上下文**: - 自动将部分运算(如卷积、矩阵乘法)转换为 FP16,其他操作(如 Softmax)保持 FP32。 - 仅需包裹前向传播过程(模型计算和损失计算)。 2. **`GradScaler` 的作用**: - 缩放损失值(`scaler.scale(loss)`),避免反向传播时梯度因 FP16 精度不足而下溢。 - 在 `scaler.step(optimizer)` 中,先反缩放梯度,再更新参数(仍以 FP32 存储)。 3. **优化器与梯度清零**: - 使用 `scaler.step(optimizer)` 替代常规的 `optimizer.step()`。 - 梯度清零操作(`optimizer.zero_grad()`)应在 `scaler.update()` 之后进行。 --- ### **注意事项** 1. **不兼容操作处理**: - 若某些操作在 FP16 下不稳定(如某些自定义层),需强制使用 FP32: ```python with autocast(): # 大部分层自动转为 FP16 x = model.layer1(x) # 手动指定某层使用 FP32 x = x.float() x = model.sensitive_layer(x) # 假设此层需 FP32 ``` 2. **梯度检查**: - 若出现 NaN/Inf,可通过 `scaler.unscale_(optimizer)` 和 `torch.nn.utils.clip_grad_norm_` 检查或裁剪梯度。 3. **验证/推理阶段**: - 推理时仍可使用 `autocast` 加速,但无需 `GradScaler`: ```python with torch.no_grad(): with autocast(): outputs = model(inputs) ``` 4. **性能权衡**: - 混合精度通常能提升训练速度 1.5-3 倍,并减少显存占用,但需测试特定模型的稳定性。 --- ### **示例代码片段** ```python scaler = GradScaler() for epoch in range(epochs): for inputs, labels in train_loader: inputs, labels = inputs.cuda(), labels.cuda() optimizer.zero_grad() with autocast(): outputs = model(inputs) loss = loss_fn(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() ``` --- 通过合理使用混合精度,可在几乎不损失模型精度的情况下显著提升训练效率。建议在复杂模型(如 Transformer、CNN)中优先尝试此技术。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值