PyTorch: ускорение загрузки данных - PullRequest
2 голосов
/ 23 апреля 2020

Я использую densenet121 для обнаружения кошек / собак из набора данных Kaggle. Я включил Cuda, и кажется, что обучение очень быстро. Тем не менее, загрузка данных (или, возможно, обработка), кажется, очень медленно. Есть ли способы ускорить его? Я пытался играть с размером партии ведьмы, но это не помогло. Я также изменил num_workers с 0 на несколько положительных чисел. Переход от 0 до 2 сокращает время загрузки, возможно, на 1/3, увеличение на больше не имеет дополнительного эффекта. Есть ли другие способы ускорить загрузку?

Это мой грубый код (я сосредоточен на обучении, поэтому он не очень организован):

import matplotlib.pyplot as plt

import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torchvision import datasets, transforms, models

data_dir = 'Cat_Dog_data'

train_transforms = transforms.Compose([transforms.RandomRotation(30),
                                       transforms.RandomResizedCrop(224),
                                       transforms.RandomHorizontalFlip(),
                                       transforms.ToTensor(),
                                       transforms.Normalize([0.5, 0.5, 0.5],
                                                            [0.5, 0.5, 0.5])])
test_transforms = transforms.Compose([transforms.Resize(255),
                                      transforms.CenterCrop(224),
                                      transforms.ToTensor()])

# Pass transforms in here, then run the next cell to see how the transforms look
train_data = datasets.ImageFolder(data_dir + '/train',
                                  transform=train_transforms)
test_data = datasets.ImageFolder(data_dir + '/test', transform=test_transforms)

trainloader = torch.utils.data.DataLoader(train_data, batch_size=64,
                                          num_workers=16, shuffle=True,
                                          pin_memory=True)
testloader = torch.utils.data.DataLoader(test_data, batch_size=64,
                                         num_workers=16)

model = models.densenet121(pretrained=True)

# Freeze parameters so we don't backprop through them
for param in model.parameters():
    param.requires_grad = False

from collections import OrderedDict

classifier = nn.Sequential(OrderedDict([
    ('fc1', nn.Linear(1024, 500)),
    ('relu', nn.ReLU()),
    ('fc2', nn.Linear(500, 2)),
    ('output', nn.LogSoftmax(dim=1))
]))

model.classifier = classifier
model.cuda()
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.003)

epochs = 30
steps = 0

import time

device = torch.device('cuda:0')

train_losses, test_losses = [], []
for e in range(epochs):
    running_loss = 0
    count = 0
    total_start = time.time()
    for images, labels in trainloader:
        start = time.time()
        images = images.cuda()
        labels = labels.cuda()

        optimizer.zero_grad()

        log_ps = model(images)
        loss = criterion(log_ps, labels)
        loss.backward()
        optimizer.step()
        elapsed = time.time() - start

        if count % 20 == 0:
            print("Optimized elapsed: ", elapsed, "count:", count)
            print("Total elapsed ", time.time() - total_start)
            total_start = time.time()
        count += 1

        running_loss += loss.item()
    else:
        test_loss = 0
        accuracy = 0
        for images, labels in testloader:
            images = images.cuda()
            labels = labels.cuda()
            with torch.no_grad():
                model.eval()
                log_ps = model(images)
                test_loss += criterion(log_ps, labels)
                ps = torch.exp(log_ps)
                top_p, top_class = ps.topk(1, dim=1)
                compare = top_class == labels.view(*top_class.shape)
                accuracy += compare.type(torch.FloatTensor).mean()
        model.train()
        train_losses.append(running_loss / len(trainloader))
        test_losses.append(test_loss / len(testloader))

        print("Epoch: {}/{}.. ".format(e + 1, epochs),
              "Training Loss: {:.3f}.. ".format(
                  running_loss / len(trainloader)),
              "Test Loss: {:.3f}.. ".format(test_loss / len(testloader)),
              "Test Accuracy: {:.3f}".format(accuracy / len(testloader)))

1 Ответ

4 голосов
/ 23 апреля 2020

Увеличение batch_size не поможет, поскольку torchvision выполняет преобразование для одного изображения, когда оно загружено с вашего диска.

Существует несколько способов ускорения загрузки данных с повышением уровня сложности. :

  • Улучшение времени загрузки изображений
  • Загрузка и нормализация изображений и кэша в ОЗУ (или на диске)
  • Создание преобразований и сохранение их на диск
  • Применение преобразований без кэширования (повороты, переворачивания, кадрирование) пакетным способом
  • Предварительная выборка

1. Улучшение загрузки изображения

Простые улучшения можно получить, установив Pillow-SIMD вместо оригинального pillow. Это замена для замены и может быть быстрее (или так востребовано по крайней мере для Resize, который вы используете).

В качестве альтернативы, вы можете создать свою собственную загрузку и обработку данных с помощью OpenCV как говорят некоторые, быстрее или проверяют альбуминаций (хотя не могу сказать, улучшат ли они производительность и могут ли они потратить много времени впустую, не получая никакой выгоды, кроме опыта обучения).

2. Загрузка и нормализация изображений и кэша

Вы можете использовать функциональность Python LRU Cache для кэширования некоторых выходных данных.

Вы также можете использовать torchdata, который действует почти так же, как torch.utils.data.Dataset PyTorch, но позволяет кэшировать на диск или в ОЗУ (или смешанные режимы) с простыми cache() на torchdata.Dataset (см. github репозиторий , отказ от ответственности: я автор ).

Помните: вы должны загружать и нормализовать изображения, кэш и после этого использовать RandomRotation , RandomResizedCrop и RandomHorizontalFlip (поскольку они меняются при каждом запуске).

3. Произведите преобразования и сохраните их на диск

Вам потребуется выполнить много преобразований изображений, сохранить их на диск и впоследствии использовать этот расширенный набор данных. Еще раз, это может быть сделано с torchdata, но это действительно расточительно, когда дело доходит до ввода-вывода и жесткого диска и очень не элегантного решения. Кроме того, это «stati c», поэтому данные будут длиться только для X эпох, это не будет «бесконечный» генератор с дополнениями.

4. Пакетные преобразования

torchvision не поддерживают его, поэтому вам придется писать эти функции самостоятельно. См. эту проблему для обоснования. AFAIK никакая другая третья сторона не предоставляет это также. Для больших партий это должно ускорить процесс, но я думаю, что реализация - это открытый вопрос (поправьте меня, если я ошибаюсь).

5. Предварительная выборка

ИМО будет сложнее всего реализовать (хотя очень хорошая идея для проекта - подумать об этом). В основном вы загружаете данные для следующей итерации, когда ваша модель тренируется. torch.utils.data.DataLoader действительно предоставляет его , хотя есть некоторые проблемы (например, рабочие, останавливающиеся после загрузки своих данных). Вы можете прочитать ветку PyTorch об этом (не уверен, так как я не проверял самостоятельно). Кроме того, много ценного понимания , предоставленного этим комментарием и этого сообщения в блоге (хотя не уверен, насколько они актуальны).

Все в все, чтобы существенно улучшить загрузку данных, вам нужно сильно испачкать руки (или, может быть, есть библиотеки, делающие это для PyTorch, если да, я бы хотел узнать о них).

Также не забудьте профилировать свои изменения, см. torch.nn.bottleneck

РЕДАКТИРОВАТЬ: DALI проект может быть стоит проверить Хотя у AFAIK есть некоторые проблемы с тем, что объем оперативной памяти растет линейно с числом эпох.

...