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

为何遍历test_loader会影响PyTorch模型训练的loss值?

问题:遍历测试DataLoader导致训练Loss异常变化(已固定随机种子)

我发现只要添加for data in test_loader循环(哪怕循环内没有任何逻辑),模型训练输出的loss值就会发生变化。已经设置了所有相关随机种子,未添加该循环时重复运行程序,loss值完全一致。使用的PyTorch版本为1.9.0+cu111。

以下是复现代码:

import torch
import numpy as np
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets

batch_size = 64
learning_rate = 0.01
momentum = 0.5
EPOCH = 10
torch.manual_seed(0)
torch.cuda.manual_seed_all(0)
torch.cuda.manual_seed(0)
np.random.seed(0)

transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
train_dataset = datasets.MNIST(root='./data/mnist', train=True, transform=transform,download=True)
test_dataset = datasets.MNIST(root='./data/mnist', train=False, transform=transform,download=True)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Sequential(
            torch.nn.Conv2d(1, 10, kernel_size=5),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2),)
        self.conv2 = torch.nn.Sequential(
            torch.nn.Conv2d(10, 20, kernel_size=5),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2),)
        self.fc = torch.nn.Sequential(
            torch.nn.Linear(320, 50),
            torch.nn.Linear(50, 10),)
    def forward(self, x):
        batch_size = x.size(0)
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(batch_size, -1)
        x = self.fc(x)
        return x
model = Net()

criterion = torch.nn.CrossEntropyLoss() 
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum) 

def train(epoch):
    running_loss = 0.0
    running_total = 0
    running_correct = 0
    for batch_idx, data in enumerate(train_loader, 0):
        optimizer.zero_grad()

        inputs, target = data
        outputs = model(inputs)
        loss = criterion(outputs, target)

        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        _, predicted = torch.max(outputs.data, dim=1)
        running_total += inputs.shape[0]
        running_correct += (predicted == target).sum().item()

    print(epoch + 1, running_loss / len(train_loader))

def test():
    with torch.no_grad():    
        for data in test_loader:         # 注释/保留此行会导致loss变化
            a=0
    return 0

if __name__ == '__main__':
    acc_list_test = []
    for epoch in range(EPOCH):
        train(epoch)
        acc_test = test()
原因分析

这是PyTorch 1.9版本中多进程DataLoader的随机状态污染问题:

  • 默认情况下,DataLoader使用多进程(num_workers>0)加载数据,遍历DataLoader时会启动子进程。
  • 子进程会继承主进程的随机状态,但旧版本中,子进程的初始化会意外修改主进程的随机数生成器状态。
  • 你的训练DataLoader设置了shuffle=True,主进程随机状态被改变后,训练数据的打乱顺序会发生变化,最终导致训练loss改变。
解决方案

方法1:固定子进程的随机种子

给DataLoader添加worker_init_fn,确保每个子进程的随机种子独立且固定,避免污染主进程状态:

def worker_init_fn(worker_id):
    # 给每个子进程分配独立的固定种子
    np.random.seed(0 + worker_id)
    torch.manual_seed(0 + worker_id)

# 修改DataLoader定义
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, worker_init_fn=worker_init_fn)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, worker_init_fn=worker_init_fn)

方法2:禁用多进程数据加载

将num_workers设为0,避免子进程带来的随机状态干扰(适合调试场景):

train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=0)

方法3:升级PyTorch版本

PyTorch在1.10及以后版本中修复了多进程DataLoader的随机种子继承问题,升级后该现象会自动消失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:31:13