滑动窗口样本加权平均损失计算与反向传播问题排查
问题分析
核心矛盾在于重叠滑动窗口会重复复用旧loss张量对应的计算图:
- 不使用
retain_graph=True时,第一次反向传播后计算图会被自动释放,后续窗口复用旧loss节点时无法再次反向传播,触发“尝试第二次反向传播遍历计算图”错误。 - 使用
retain_graph=True时,旧计算图被保留,但optimizer.step()会原地更新模型参数,导致后续反向传播依赖的参数已被修改,触发“梯度计算所需变量被原地操作修改”错误。
解决方案:维护加权梯度累积值,而非存储原始loss张量
因为窗口步长为1,每次滑动仅移除最旧样本、新增一个样本,我们可以通过维护参数的加权梯度累积值来避免计算图复用问题,同时高效实现滑动窗口的加权损失梯度计算。
修改后完整代码
for epoch in range(100): net.train() best_loss = float('inf') patience_counter = 0 torch.save(net.state_dict(), "cur_state.pth") # 修正原代码中best_state的错误赋值 window_size = 50 # 初始化梯度累积容器:存储每个参数的加权梯度累加值 grad_accum = {name: torch.zeros_like(param) for name, param in net.named_parameters()} current_window = [] # 存储窗口内每个样本的loss和对应权重 for i, data in enumerate(train_set_loader): inputs, label = data # 计算当前样本的输出与loss outputs, _ = net(inputs) loss = criterion(outputs, label) # 确定当前样本在窗口中的权重:窗口未满时权重随位置线性增加,满后新样本权重为1.0 weight = (len(current_window) + 1) / window_size if len(current_window) < window_size else 1.0 current_window.append((loss, weight)) # 计算当前样本的加权梯度并累加到累积容器 loss_weighted = loss * weight loss_weighted.backward() for name, param in net.named_parameters(): if param.grad is not None: grad_accum[name] += param.grad.clone() param.grad.zero_() # 清空当前样本的梯度,避免干扰后续计算 # 窗口满时执行优化步骤 if len(current_window) == window_size: # 将累积梯度赋值给模型参数的grad属性 for name, param in net.named_parameters(): param.grad = grad_accum[name].clone() optimizer.step() # 打印梯度用于调试 for name, param in net.named_parameters(): if param.grad is not None: print(f'{name}: {param.grad.norm()}') # 滑动窗口:移除最旧样本的加权梯度 oldest_loss, oldest_weight = current_window.pop(0) oldest_loss_weighted = oldest_loss * oldest_weight oldest_loss_weighted.backward() for name, param in net.named_parameters(): if param.grad is not None: grad_accum[name] -= param.grad.clone() param.grad.zero_() net.eval() val_loss = evaluate_model(net, criterion, val_set_loader) if val_loss < best_loss: best_loss = val_loss torch.save(net.state_dict(), "cur_state.pth") patience_counter = 0 else: patience_counter += 1 if patience_counter >= patience: print("Early Stopping") print(f"Best loss: {best_loss}") device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") net.load_state_dict(torch.load("cur_state.pth", map_location=device)) print("Model state returned to best performing.") break
关键改进说明
- 彻底避免计算图复用:每次仅计算单个样本的加权梯度,计算后立即清空参数的
grad,不会保留整个窗口的计算图,从根源解决反向传播的计算图冲突问题。 - 精准处理滑动窗口梯度:窗口滑动时,重新计算最旧样本的加权梯度并从累积值中减去,确保累积梯度始终对应当前窗口的50个样本。
- 修正原代码错误:原代码中
best_state = torch.save(...)是无效赋值(torch.save返回None),已直接改为保存模型状态。
内容的提问来源于stack exchange,提问作者SlinkyPlatypus
相关产品推荐
相关产品推荐

