2025, Nov 12 21:01

Ошибка 'list' object has no attribute 'to' в PyTorch при S3MapDataset: как нормализовать батч

PyTorch и S3: решаем ошибку 'list' object has no attribute 'to' при работе с S3MapDataset — нормализуйте батч и применяйте .to(device) только к тензору.

Обучение напрямую из S3 удобно, пока первый батч не падает с непонятной ошибкой атрибута. Если конвейер ожидает один Tensor на батч, а источник данных возвращает списокоподобную структуру, простое перенесение на устройство вроде samples.to(device) завершится неудачей. Именно так может случиться при использовании s3torchconnector.S3MapDataset.from_prefix в окружении SageMaker.

Воспроизведение: когда батч — не Tensor

Ниже показана настройка, которая инициализирует датасет по префиксу в S3 и подает его в стандартный DataLoader. Модель и цикл типичные, но важный момент — входной сэмпл не всегда Tensor; при работе с S3 он может прийти как списокоподобный объект, из‑за чего возникает ошибка 'list' object has no attribute 'to'.

from PIL import Image
import torch
import torchvision
from torchvision import transforms
import s3torchconnector

# преобразование изображения, возвращающее идентификатор и тензор float32
def fetch_img(obj_ref):
    pic = Image.open(obj_ref)
    resizer = transforms.Resize(size=(224, 224))
    pic = resizer(pic)
    pic = transforms.functional.pil_to_tensor(pic)
    return (obj_ref.key, torchvision.transforms.functional.convert_image_dtype(pic, dtype=torch.float32))

# датасет на базе S3
train_ds = s3torchconnector.S3MapDataset.from_prefix(
    cfg.IMAGES_URI,
    region=cfg.REGION,
    transform=fetch_img,
)

# DataLoader
train_loader = torch.utils.data.DataLoader(
    train_ds,
    sampler=train_sampler,
    batch_size=cfg.batch_size,
    num_workers=cfg.num_workers,
    pin_memory=cfg.pin_mem,
    drop_last=True,
)

# Модель
model = models_mae.__dict__[cfg.model](norm_pix_loss=cfg.norm_pix_loss)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# Цикл обучения (фрагмент)
for ep in range(cfg.start_epoch, cfg.epochs):
    if cfg.distributed:
        train_loader.sampler.set_epoch(ep)
    for batch in train_loader:
        samples = batch
        samples = samples.to(device, non_blocking=True)  # ошибка: у объекта 'list' нет атрибута 'to'

Что происходит

Конвейер данных смешивает две разные предпосылки. Шаг обучения рассчитывает получить Tensor, чтобы вызвать .to(device, non_blocking=True). При загрузке через s3torchconnector.S3MapDataset.from_prefix сэмпл может прийти как списокоподобный контейнер вместо Tensor. Как только к этому списку применяется .to, возникает ошибка атрибутов. Тот же цикл работает с локальным датасетом, где сэмпл уже является Tensor, поэтому проблема проявляется только после перехода на S3.

Исправление: нормализуйте батч перед переносом на устройство

Практичное решение — привести входной батч к ожидаемому Tensor. Когда батч — список, нужный Tensor находится по индексу 1; в противном случае это уже Tensor. Такое условное приведение позволяет без изменений запускать обучение как с локальными данными, так и с S3.

for ep in range(cfg.start_epoch, cfg.epochs):
    if cfg.distributed:
        train_loader.sampler.set_epoch(ep)
    for batch in train_loader:
        inputs = batch
        if type(inputs) == list:
            inputs = inputs[1].to(device, non_blocking=True)
        else:
            inputs = inputs.to(device, non_blocking=True)
        # продолжайте прямой/обратный проход, используя `inputs`

Почему это важно

Единообразие интерфейса батча критично при смене источников данных. Шаг обучения обычно зашивает ожидания по форме и типу входа. Если один источник возвращает Tensor, а другой — списокоподобную обертку, тот же код модели будет вести себя по‑разному. Нормализация батча в точке передачи гарантирует, что цикл будет столь же надежно работать с крупным датасетом на S3, как и с локальными файлами в SageMaker.

Выводы

Если после перехода на s3torchconnector обучение падает с ошибкой 'list' object has no attribute 'to', проверьте, что именно приходит в батче, и преобразуйте его к одному Tensor перед вызовом .to(...). Небольшая проверка типа, которая выбирает нужный элемент из списка и переносит его на устройство, возвращает совместимость и для локальных датасетов, и для загрузки из S3, позволяя модели читать и обучаться на выборке, упакованной в список, без дальнейших изменений остального тренировочного цикла.