Классификация с помощью PyTorch намного медленнее, чем Tensorflow: 42 минуты против 11 минут

#tensorflow #machine-learning #pytorch

Вопрос:

Я был пользователем Tensorflow и начал использовать Pytorch. В качестве пробной версии я реализовал простые задачи классификации с обеими библиотеками.
Однако PyTorch работает намного медленнее, чем Tensorflow: Pytorch занимает 42 минуты, а TensorFlow-11 минут. Я сослался на официальный учебник PyTorch и внес в него лишь небольшие изменения.

Может ли кто-нибудь поделиться некоторыми советами по этой проблеме?

Вот краткое изложение того, что я пробовал.

среда: Colab Pro
набор данных: Cifar10
классификатор: VGG16
оптимизатор: Adam
потеря:
кроссэнтропия размер пакета: 32

Пыторч
Код:

 import torch, torchvision
from torch import nn
from torchvision import transforms, models
from tqdm import tqdm
import time, copy

trans = transforms.Compose([transforms.Resize((224, 224)),
                            transforms.ToTensor(),])

data = {phase: torchvision.datasets.CIFAR10('./', train = (phase=='train'),  transform=trans, download=True) for phase in ['train', 'test']}
dataloaders = {phase: torch.utils.data.DataLoader(data[phase], batch_size=32, shuffle=True) for phase in ['train', 'test']}

def train_model(model, criterion, optimizer, dataloaders, device, num_epochs=5):
    since = time.time()

    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # Each epoch has a training and validation phase
        for phase in ['train', 'test']:
            if phase == 'train':
                model.train()  # Set model to training mode
            else:
                model.eval()   # Set model to evaluate mode

            running_loss = 0.0
            running_corrects = 0

            # Iterate over data.
            for inputs, labels in tqdm(iter(dataloaders[phase])):
                inputs = inputs.to(device)
                labels = labels.to(device)

                # zero the parameter gradients
                optimizer.zero_grad()

                # forward
                # track history if only in train
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)

                    # backward   optimize only if in training phase
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                # statistics
                running_loss  = loss.item() * inputs.size(0)
                running_corrects  = torch.sum(preds == labels.data)

            epoch_loss = running_loss / len(dataloaders[phase])
            epoch_acc = running_corrects.double() / len(dataloaders[phase])

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(
                phase, epoch_loss, epoch_acc))

            # deep copy the model
            if phase == 'test' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())

        print()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(
        time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # load best model weights
    model.load_state_dict(best_model_wts)
    return model

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

model = models.vgg16(pretrained=False)
model = model.to(device)

model = train_model(model=model,
                    criterion=nn.CrossEntropyLoss(), 
                    optimizer=torch.optim.Adam(model.parameters(), lr=0.001),
                    dataloaders=dataloaders,
                    device=device,
                    )
 

Результат:

 Epoch 0/4
----------
  0%|          | 0/1563 [00:00<?, ?it/s]/usr/local/lib/python3.7/dist-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  /pytorch/c10/core/TensorImpl.h:1156.)
  return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
100%|██████████| 1563/1563 [07:50<00:00,  3.32it/s]
train Loss: 75.5199 Acc: 3.2809
100%|██████████| 313/313 [00:38<00:00,  8.11it/s]
test Loss: 73.7274 Acc: 3.1949

Epoch 1/4
----------
100%|██████████| 1563/1563 [07:50<00:00,  3.33it/s]
train Loss: 73.8162 Acc: 3.2514
100%|██████████| 313/313 [00:38<00:00,  8.13it/s]
test Loss: 73.6114 Acc: 3.1949

Epoch 2/4
----------
100%|██████████| 1563/1563 [07:49<00:00,  3.33it/s]
train Loss: 73.7741 Acc: 3.1369
100%|██████████| 313/313 [00:38<00:00,  8.11it/s]
test Loss: 73.5873 Acc: 3.1949

Epoch 3/4
----------
100%|██████████| 1563/1563 [07:49<00:00,  3.33it/s]
train Loss: 73.7493 Acc: 3.1331
100%|██████████| 313/313 [00:38<00:00,  8.12it/s]
test Loss: 73.6191 Acc: 3.1949

Epoch 4/4
----------
100%|██████████| 1563/1563 [07:49<00:00,  3.33it/s]
train Loss: 73.7289 Acc: 3.1939
100%|██████████| 313/313 [00:38<00:00,  8.13it/s]test Loss: 73.5955 Acc: 3.1949

Training complete in 42m 22s
Best val Acc: 3.194888
 

Тензорный поток
Код:

 import tensorflow_datasets as tfds
from tensorflow.keras import applications, models
import tensorflow as tf
import time

ds_test, ds_train = tfds.load('cifar10', split=['test', 'train'])

def resize(ip):
    image = ip['image']
    label = ip['label']
    image = tf.image.resize(image, (224, 224))
    image = tf.expand_dims(image,0)
    label = tf.one_hot(label,10)
    label = tf.expand_dims(label,0)
    return (image, label)

ds_train_ = ds_train.map(resize)
ds_test_ = ds_test.map(resize)


model = applications.vgg16.VGG16(input_shape = (224, 224, 3), weights=None, classes=10)
model.compile(optimizer='adam', loss = 'categorical_crossentropy', metrics= ['accuracy'])

batch_size = 32
since = time.time()
history = model.fit(ds_train_,
                    batch_size = batch_size,
                    steps_per_epoch = len(ds_train)//batch_size,
                    epochs = 5,
                    validation_steps = len(ds_test),
                    validation_data = ds_test_,
                    shuffle = True,)
time_elapsed = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format( time_elapsed // 60, time_elapsed % 60 ))
 

Результат:

 Epoch 1/5
1562/1562 [==============================] - 125s 69ms/step - loss: 36.9022 - accuracy: 0.1069 - val_loss: 2.3031 - val_accuracy: 0.1000
Epoch 2/5
1562/1562 [==============================] - 129s 83ms/step - loss: 2.3031 - accuracy: 0.1005 - val_loss: 2.3033 - val_accuracy: 0.1000
Epoch 3/5
1562/1562 [==============================] - 129s 83ms/step - loss: 2.3035 - accuracy: 0.1069 - val_loss: 2.3031 - val_accuracy: 0.1000
Epoch 4/5
1562/1562 [==============================] - 129s 83ms/step - loss: 2.3038 - accuracy: 0.1024 - val_loss: 2.3030 - val_accuracy: 0.1000
Epoch 5/5
1562/1562 [==============================] - 129s 83ms/step - loss: 2.3028 - accuracy: 0.1024 - val_loss: 2.3033 - val_accuracy: 0.1000
Training complete in 11m 23s
 

Комментарии:

1. Вы убедились, что действительно использовали графический процессор во время этого тренинга?

2. В tf , вы используете tf. data api, который должен быть быстрее. Для справедливого сравнения вы должны выполнить одно и то же условие, т. е. загрузку данных, моделирование и пользовательское обучение.

3. В дополнение к, вероятно, более эффективному конвейеру данных, использование fit для моделей keras компилирует модель (действительно, весь шаг обучения) в графе вычислений, который может выполняться более эффективно, чем построчное выполнение Python (как в вашей версии pytorch). Возможно, вам захочется посмотреть, есть ли возможность «компилировать» функции pytorch.

Ответ №1:

Это связано с тем, что в ваших кодах tensorflow конвейер данных на каждом шаге загружает в модель пакет из 1 изображения вместо пакета из 32 изображений.

Переход batch_size в на самом model.fit деле не влияет на размер пакета, когда данные представлены в виде наборов данных. Причина, по которой он показывал, казалось бы, правильные шаги за эпоху из журнала, заключается в том, что вы перешли steps_per_epoch в model.fit .

Чтобы правильно установить размер пакета:

 ds_test, ds_train = tfds.load('cifar10', split=['test', 'train'])

def resize(ip):
    image = ip['image']
    label = ip['label']
    image = tf.image.resize(image, (224, 224))
    label = tf.one_hot(label,10)
    return (image, label)

train_size=len(ds_train)
test_size=len(ds_test)
ds_train_ = ds_train.shuffle(train_size).batch(32).map(resize)
ds_test_ = ds_test.shuffle(test_size).batch(32).map(resize)
 

model.fit вызов:

 history = model.fit(ds_train_,
                    epochs = 1,
                    validation_data = ds_test_)
 

После устранения проблемы tensorflow получил аналогичную скорость работы с pytorch. В моей машине pytorch занимал ~27 минут за эпоху, в то время как tensorflow занимал ~24 минуты за эпоху.

Согласно тестам NVIDIA, pytorch и tensorflow имели схожую производительность по скорости в большинстве популярных приложений для глубокого обучения с реальными наборами данных и размером проблемы. (Ссылка: https://developer.nvidia.com/deep-learning-performance-training-inference)

Комментарии:

1. Спасибо за ваше четкое объяснение, это действительно помогает мне лучше понять Tensorflow. Кстати, я использовал генератор, который выдает пакет (X, y) в model.fit() вместо наборов данных. В этом случае управляет ли передача batch_size в model.fit() размером пакета?

2. Я нашел на официальном сайте следующее определение: «Не указывайте размер пакета, если ваши данные представлены в виде наборов данных, генераторов или keras.utils. Экземпляры последовательности (так как они генерируют пакеты).» Итак, я, возможно, допустил ошибки в своих старых кодах, используя генераторы. Благодаря вашей помощи я заметил свои предыдущие проблемы. Большое спасибо.