自定义DataLoader返回张量列表引发AttributeError:'list'对象无'to'属性的问题咨询
看起来你的核心问题是训练循环中尝试对DataLoader返回的整个batch(元组/列表)调用.to()方法失败,因为元组或列表本身没有to属性。我们先分析问题根源,再给出具体解决方案和代码优化建议:
问题根源分析
你的自定义Dataset的__getitem__方法返回的是元组(img_tensor, class_id),当使用DataLoader迭代时,默认的collate_fn会将多个样本的img_tensor堆叠成一个批量张量,class_id堆叠成另一个批量张量,最终返回的是包含两个张量的元组(而非你误以为的“列表”)。直接对这个元组调用.to()自然会报错,因为元组没有该方法。
快速解决方案:拆分批量张量并分别移动到设备
这是最直接且高效的修复方式,只需在训练循环中分别处理图像张量和标签张量:
# 错误的写法(引发报错) for samples in dataloader: samples = samples.to(device, non_blocking=True) # 后续逻辑 # 正确的写法 for imgs, labels in dataloader: # 分别将图像和标签移动到目标设备 imgs = imgs.to(device, non_blocking=True) labels = labels.to(device, non_blocking=True) # 执行前向传播、损失计算等逻辑 outputs = model(imgs) # 如果你的标签是形状为(batch_size,1)的张量,需要squeeze转换为(batch_size,) loss = criterion(outputs, labels.squeeze())
你的Dataset代码的优化建议
除了核心问题,你的Dataset实现还有几个可以改进的地方,能避免潜在问题并提升性能:
1. 移除多余的np.stack调用
cv2.imread已经返回形状为(H, W, 3)的3通道numpy数组,img = np.stack(img, axis=0)这一行完全多余,甚至可能导致数据维度错误,直接删除即可。
2. 优化标签张量的创建
当前你用class_id = torch.tensor([class_id])创建了形状为(1,)的张量,堆叠后会得到(batch_size, 1)的标签张量,大多数PyTorch损失函数(如CrossEntropyLoss)需要形状为(batch_size,)的长整型标量张量,建议修改为:
class_id = torch.tensor(class_id, dtype=torch.long) # 直接创建标量长张量
3. 可选:将BGR图像转换为RGB
cv2.imread默认返回BGR格式的图像,如果你的模型是基于RGB数据预训练的(比如大多数TorchVision模型),需要添加颜色空间转换:
img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 新增这一行 img = cv2.resize(img, self.img_dim)
4. 确认cv2.resize的维度顺序
cv2.resize的第二个参数是(width, height),你的img_dim = (4896, 3280)对应宽4896、高3280,这会导致resize后的图像形状为(3280, 4896, 3)(H, W, C),后续的permute(2,0,1)会将其转换为PyTorch要求的(C, H, W)格式,这部分是正确的,但建议在注释中明确说明维度含义,避免混淆。
可选进阶方案:自定义Batch类实现一键移动设备
如果你希望保持“一次性移动整个batch到设备”的写法,可以定义一个简单的Batch类,让它支持to方法:
class Batch: def __init__(self, imgs, labels): self.imgs = imgs self.labels = labels def to(self, device, non_blocking=False): self.imgs = self.imgs.to(device, non_blocking=non_blocking) self.labels = self.labels.to(device, non_blocking=non_blocking) return self # 自定义collate_fn def custom_collate_fn(batch): imgs = torch.stack([item[0] for item in batch]) labels = torch.stack([item[1] for item in batch]) return Batch(imgs, labels) # 创建DataLoader时指定collate_fn dataloader = DataLoader(Data_db(root="your_root_path"), batch_size=8, collate_fn=custom_collate_fn) # 训练循环中可以这样写 for batch in dataloader: batch = batch.to(device, non_blocking=True) outputs = model(batch.imgs) loss = criterion(outputs, batch.labels)
总结
最推荐的是快速解决方案,它无需修改DataLoader的配置,代码改动最小且清晰易懂。如果你已经找到的workaround涉及自定义collate_fn,也可以结合上面的优化建议调整实现。
内容来源于stack exchange

