pytorch backward 求梯度 累计 样式

PyTorch的backward函数在计算梯度时采用累计方式,因此需要optimizer.zero_grad()重置梯度。当批量大小过大导致显存不足时,使用gradientaccumulation将大批次拆分为小批次,平均损失并更新权重。在每个小批次的累积迭代次数达到预设值或最后一个批次后,执行optimizer.step()更新模型参数。

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

pytorch backwad 函数计算梯度是 累计式的

关于 pytorch 的 backward()函数反向传播计算梯度是 累计式的,见下图(主要是图中用黑框框出来的部分内容)。
因为这样,所以才需要 optimizer.zero_grad()
在这里插入图片描述

利用 gradient accumulation 的框架

一般的优化框架是:

# loop through batches
for (inputs, labels) in data_loader:

    # extract inputs and labels
    inputs = inputs.to(device)
    labels = labels.to(device)

    # passes and weights update
    with torch.set_grad_enabled(True):
        
        # forward pass 
        preds = model(inputs)
        loss  = criterion(preds, labels)

        # backward pass
        loss.backward() 

        # weights update
        optimizer.step()
        optimizer.zero_grad()

利用 gradient accumulation 的框架是这样的。
为什么需要 gradient accumulation 呢?
因为可能会出现 训练集 batch size 比较大,电脑 显存吃不下的情况,这样就需要将一个 batch 分为几个小 batch训练,但是同时又都利用它们的gradient 信息。

# batch accumulation parameter
accum_iter = 4  

# loop through enumaretad batches
for batch_idx, (inputs, labels) in enumerate(data_loader):

    # extract inputs and labels
    inputs = inputs.to(device)
    labels = labels.to(device)

    # passes and weights update
    with torch.set_grad_enabled(True):
        
        # forward pass 
        preds = model(inputs)
        loss  = criterion(preds, labels)

        # normalize loss to account for batch accumulation
        loss = loss / accum_iter 

        # backward pass
        loss.backward()

        # weights update
        if ((batch_idx + 1) % accum_iter == 0) or (batch_idx + 1 == len(data_loader)):
            optimizer.step()
            optimizer.zero_grad()
<think>好的,用户之前遇到了PyTorch中的NameError: name 'history' is not defined的问题,我之前已经给出了一些解决方案,但用户现在回复说“不是很懂,能详细的解释吗”,说明用户可能对之前的解释还存在困惑,需要更详细的指导。 首先,我需要回顾之前的回答,确保自己理解用户的问题所在。用户在使用PyTorch时试图访问`history`对象,但PyTorch并没有像Keras那样的内置历史记录功能,所以导致了NameError。之前的回答中,我提到了手动记录训练指标、初始化容器、自定义类等方法,但用户仍然需要更详细的解释。 接下来,我需要分析用户可能的困惑点。用户可能对PyTorch和Keras的差异不太清楚,特别是训练循环和指标记录方面。他们可能习惯了Keras的简洁API,不熟悉PyTorch需要手动管理训练过程的特点。此外,用户可能对如何在代码中具体实现这些手动记录步骤感到困惑,比如变量的初始化位置、循环中的具体操作等。 用户可能需要更基础的讲解,比如逐步解释训练循环的结构,如何在每个epoch结束后计算并保存损失和准确率,以及如何将这些保存的数据可视化。此外,他们可能对自定义类的使用不太熟悉,需要更详细的代码示例和解释。 我还需要考虑用户可能的编程水平。他们可能对PyTorch的基础知识有一定了解,但对更高级的封装或自定义类不太熟悉。因此,在详细解释时,应该避免使用过于复杂的术语,而是用更直白的语言和示例来说明。 接下来,我需要组织回答的结构。首先,应该比较PyTorch和Keras在训练流程上的差异,明确PyTorch需要手动记录的原因。然后,分步骤详细说明如何手动初始化变量、在训练循环中更新这些变量,以及如何可视化结果。接着,可以提供一个完整的代码示例,让用户能够直接复制并运行,观察效果。最后,解释自定义类的方法,并给出常见错误的排查方法,帮助用户避免典型问题。 在解释过程中,要使用具体的代码示例,并逐行注释,确保用户理解每一部分的作用。例如,在训练循环中,需要说明如何计算每个batch的损失,累加后平均,并添加到列表中。对于验证阶段,同样需要详细说明评估的步骤,包括设置模型为评估模式、禁用梯度计算等。 此外,用户可能对如何将记录的数据可视化不太清楚,所以需要展示使用matplotlib绘制损失曲线的具体代码,并解释如何调整图表样式,添加标签和图例等。 最后,要确保回答中的代码没有语法错误,并且符合PyTorch的最佳实践,例如使用`torch.no_grad()`来禁用梯度计算,使用`model.eval()`切换模型模式等。同时,提醒用户在实际应用中可能需要调整的部分,比如数据加载器的名称、模型的结构等。 总结来说,用户需要的是从基础到进阶的逐步指导,强调PyTorch与Keras的不同之处,并通过详细的代码示例和解释来帮助他们理解如何手动记录训练指标,避免NameError,并成功可视化训练过程。</think>好的!我将用最详细的方式解释这个问题,并给出完整的实现步骤。让我们从零开始理解这个问题。 --- ### **为什么PyTorch会报错 `NameError: name 'history' is not defined`?** #### 1. **PyTorch和Keras的设计差异** - **Keras/TensorFlow**:提供高级API,自动记录训练历史。当你调用 `model.fit()` 时,它会返回一个 `history` 对象,包含所有训练指标(如损失、准确率)。 - **PyTorch**:是一个低级框架,训练过程需要手动编写循环,**不会自动保存任何训练数据**。如果你尝试直接使用 `history`,而从未定义过这个变量,就会触发 `NameError`。 --- ### **逐步解决方案:从零开始记录训练历史** #### 步骤1:理解训练循环的基本结构 PyTorch的训练流程通常如下: ```python # 1. 定义模型、优化器、损失函数 model = MyModel() optimizer = torch.optim.Adam(model.parameters()) criterion = torch.nn.CrossEntropyLoss() # 2. 训练循环 for epoch in range(num_epochs): # 训练阶段 model.train() for inputs, labels in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # 验证阶段(可选) model.eval() with torch.no_grad(): # 在验证集上计算损失和准确率 ``` #### 步骤2:添加记录变量 我们需要在代码中**显式定义变量**来保存训练过程中的指标: ```python # 在训练循环外初始化记录容器 train_losses = [] # 记录每个epoch的训练损失 val_losses = [] # 记录每个epoch的验证损失 train_accuracies = [] # 记录训练准确率 val_accuracies = [] # 记录验证准确率 ``` #### 步骤3:在每个epoch结束时保存数据 在训练循环内部,计算每个epoch的平均损失并保存到列表中: ```python for epoch in range(num_epochs): model.train() running_loss = 0.0 # 临时变量,累计一个epoch的总损失 # 遍历所有训练数据批次 for inputs, labels in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() # 累加损失 # 计算本epoch的平均训练损失 epoch_train_loss = running_loss / len(train_loader) train_losses.append(epoch_train_loss) # 保存到列表 # 验证阶段(以验证损失为例) model.eval() val_running_loss = 0.0 with torch.no_grad(): for inputs, labels in val_loader: outputs = model(inputs) loss = criterion(outputs, labels) val_running_loss += loss.item() epoch_val_loss = val_running_loss / len(val_loader) val_losses.append(epoch_val_loss) # 保存验证损失 # 打印进度 print(f"Epoch {epoch+1}/{num_epochs} | Train Loss: {epoch_train_loss:.4f} | Val Loss: {epoch_val_loss:.4f}") ``` --- ### **完整代码示例** ```python import torch import matplotlib.pyplot as plt # 假设已有 train_loader 和 val_loader 数据加载器 model = torch.nn.Linear(10, 2) # 示例模型 optimizer = torch.optim.Adam(model.parameters()) criterion = torch.nn.CrossEntropyLoss() # 初始化记录容器 train_losses = [] val_losses = [] num_epochs = 10 for epoch in range(num_epochs): # 训练阶段 model.train() running_loss = 0.0 for inputs, labels in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() epoch_train_loss = running_loss / len(train_loader) train_losses.append(epoch_train_loss) # 验证阶段 model.eval() val_running_loss = 0.0 with torch.no_grad(): for inputs, labels in val_loader: outputs = model(inputs) loss = criterion(outputs, labels) val_running_loss += loss.item() epoch_val_loss = val_running_loss / len(val_loader) val_losses.append(epoch_val_loss) # 打印进度 print(f"Epoch {epoch+1}/{num_epochs} | Train Loss: {epoch_train_loss:.4f} | Val Loss: {epoch_val_loss:.4f}") # 绘制损失曲线 plt.figure(figsize=(10, 5)) plt.plot(train_losses, label='Training Loss') plt.plot(val_losses, label='Validation Loss') plt.title('Training and Validation Loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show() ``` --- ### **关键点解释** 1. **`running_loss`的作用**:用于累计一个epoch内所有batch的损失值,最后除以batch数量得到平均损失。 2. **`model.train()`和`model.eval()`**: - `model.train()`:启用Dropout和BatchNorm的训练模式。 - `model.eval()`:关闭Dropout和BatchNorm的推理模式。 3. **`with torch.no_grad()`**:在验证阶段禁用梯度计算,节省内存并加速计算。 4. **`.item()`方法**:将单元素的Tensor转换为Python数值(例如 `loss` 是Tensor,需要用 `loss.item()` 获取标量值)。 --- ### **常见问题排查** 1. **变量未定义错误**: - 确保 `train_losses = []` 在训练循环之前定义。 - 检查变量名拼写是否一致(例如 `train_losses` vs `train_loss`)。 2. **列表内容为空**: - 确认在循环内部调用了 `train_losses.append(...)`。 - 检查数据加载器是否正常工作(例如 `len(train_loader)` 是否大于0)。 3. **验证损失没有下降**: - 检查模型是否在训练阶段更新了参数(是否漏了 `optimizer.step()`)。 - 检查学习率是否合理。 --- ### **进阶:封装成Keras风格的历史记录** 如果你希望像Keras一样直接访问 `history.history`,可以自定义一个类: ```python class TrainingHistory: def __init__(self): self.history = { 'train_loss': [], 'val_loss': [], 'train_acc': [], 'val_acc': [] } def add_train_loss(self, value): self.history['train_loss'].append(value) def add_val_loss(self, value): self.history['val_loss'].append(value) # 其他指标类似... # 使用示例 history = TrainingHistory() history.add_train_loss(0.5) history.add_val_loss(0.4) # 绘制损失曲线 plt.plot(history.history['train_loss']) ``` --- ### **总结** - **PyTorch需要手动记录**:所有训练指标必须通过代码显式保存。 - **核心步骤**: 1. 初始化空列表(如 `train_losses`)。 2. 在每个epoch结束时计算指标并添加到列表。 3. 最后用Matplotlib等库可视化数据。 - **避免NameError**:确保你访问的变量已经正确定义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

培之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值