You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

滑动窗口样本加权平均损失计算与反向传播问题排查

问题分析

核心矛盾在于重叠滑动窗口会重复复用旧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

关键改进说明

  1. 彻底避免计算图复用:每次仅计算单个样本的加权梯度,计算后立即清空参数的grad,不会保留整个窗口的计算图,从根源解决反向传播的计算图冲突问题。
  2. 精准处理滑动窗口梯度:窗口滑动时,重新计算最旧样本的加权梯度并从累积值中减去,确保累积梯度始终对应当前窗口的50个样本。
  3. 修正原代码错误:原代码中best_state = torch.save(...)是无效赋值(torch.save返回None),已直接改为保存模型状态。

内容的提问来源于stack exchange,提问作者SlinkyPlatypus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 12:43:14