Предварительное исследование трансферного обучения в PyTorch

машинное обучение глубокое обучение PyTorch компьютерное зрение
Предварительное исследование трансферного обучения в PyTorch

Предисловие:

Что такое трансферное обучение? Простое понимание заключается в том, чтобы использовать некоторые уже обученные модели для переноса на аналогичные новые задачи для использования без необходимости повторного моделирования новых задач, обучения и оптимизации параметров с нуля. Эти обученные модели также содержат оптимизированные параметры, которые можно применять к новым задачам с помощью лишь некоторых простых настроек при их использовании.

Задача, которую нужно решить в этой статье, использует мигрированную модель VGG16. Эта статья в конечном итоге получит CNN (сверточная нейронная сеть), которая может распознавать изображения кошек и собак. Набор тестов используется для проверки того, может ли моя модель работать хорошо.

Используйте PyTorch для создания модели трансферного обучения:

VGG — это модель CNN (Convolutional Neural Network), созданная К. Симоняном и А. Зиссерманом в статье «Очень глубокие сверточные сети для крупномасштабного распознавания изображений». Модель есть на ImageNet:ImageNet(Конкурс на классификацию миллионов изображений) добился блестящих результатов в этой задаче.

Структура модели VGG16 следующая:

Структура модели VGG16

Как видно из рисунка, модель включает в себя несколько сверточных слоев, объединенных слоев и полностью связанных слоев.Вход представляет собой изображение 224 * 224 * 3 (разрешение 224 * 224 бит, 3 канала RGB3), выход результат, содержащий 1000 классификаций (в этой статье применяются только две классификации, поэтому последний слой необходимо переписать). Очень удобно использовать PyTorch для загрузки моделей и параметров следующим образом:

from torchvision import models
model = models.vgg16(pretrained=True)

Если для pretrained установлено значение True, программа автоматически загрузит обученные параметры.

Это должно использовать трансферное обучение для классификации изображений кошек и собак.Набор данных получен из конкурса Kaggle:Dogs vs. Cats Redux: Kernels Edition.

Сначала выполните импорт и предварительный просмотр изображения, код выглядит следующим образом:

path = "dog_vs_cat"
transform = transforms.Compose([transforms.CenterCrop(224),
                                transforms.ToTensor(),
                                transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])])

data_image = {x:datasets.ImageFolder(root = os.path.join(path,x),
                                     transform = transform)
              for x in ["train", "val"]}

data_loader_image = {x:torch.utils.data.DataLoader(dataset=data_image[x],
                                                batch_size = 4,
                                                shuffle = True)
                     for x in ["train", "val"]}

Поскольку входное изображение должно иметь разрешение 224*224, используйте transforms.CenterCrop(224), чтобы обрезать исходное изображение. Загруженный обучающий набор изображений равен 20000, а проверочный набор — 5000 (исходные изображения — это все обучающие наборы, вам нужно разделить часть проверочного набора самостоятельно), выходная метка, 1 представляет собаку, 0 представляет кошку.

X_train, y_train = next(iter(data_loader_image["train"]))
mean = [0.5,0.5,0.5]
std  = [0.5,0.5,0.5]
img = torchvision.utils.make_grid(X_train)
img = img.numpy().transpose((1,2,0))
img = img*std+mean

print([classes[i] for i in y_train])
plt.imshow(img)

['cat', 'dog', 'cat', 'dog']

Предварительный просмотр фотографий кошек и собак

Как видно из рисунка выше, все изображения для обучения имеют размер 224*224*3.

Перенесите модель, а затем распечатайте структуру модели:

model = models.vgg16(pretrained=True)
print(model)
VGG (
  (features): Sequential (
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU (inplace)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU (inplace)
    (4): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU (inplace)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU (inplace)
    (9): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU (inplace)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU (inplace)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU (inplace)
    (16): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU (inplace)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU (inplace)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU (inplace)
    (23): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU (inplace)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU (inplace)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU (inplace)
    (30): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
  )
  (classifier): Sequential (
    (0): Linear (25088 -> 4096)
    (1): ReLU (inplace)
    (2): Dropout (p = 0.5)
    (3): Linear (4096 -> 4096)
    (4): ReLU (inplace)
    (5): Dropout (p = 0.5)
    (6): Linear (4096 -> 1000)
  )
)

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

for parma in model.parameters():
    parma.requires_grad = False

model.classifier = torch.nn.Sequential(torch.nn.Linear(25088, 4096),
                                       torch.nn.ReLU(),
                                       torch.nn.Dropout(p=0.5),
                                       torch.nn.Linear(4096, 4096),
                                       torch.nn.ReLU(),
                                       torch.nn.Dropout(p=0.5),
                                       torch.nn.Linear(4096, 2))

for index, parma in enumerate(model.classifier.parameters()):
    if index == 6:
        parma.requires_grad = True
    
if use_gpu:
    model = model.cuda()


cost = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.classifier.parameters())

Назначение parma.requires_grid = False — заморозить параметры, даже если произойдет новое обучение, параметры не будут обновляться.

Здесь также переписан последний слой полносвязного слоя, torch.nn.Linear(4096, 2) делает конечный результат вывода всего двумя (только надо различать кошек и собак).

optimizer = torch.optim.Adam(model.classifier.parameters()) только обновляет и оптимизирует параметры полносвязного слоя, а при расчете потерь по-прежнему используется кросс-энтропия.

Ознакомьтесь с переписанной моделью:

VGG (
  (features): Sequential (
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU (inplace)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU (inplace)
    (4): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU (inplace)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU (inplace)
    (9): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU (inplace)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU (inplace)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU (inplace)
    (16): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU (inplace)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU (inplace)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU (inplace)
    (23): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU (inplace)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU (inplace)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU (inplace)
    (30): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
  )
  (classifier): Sequential (
    (0): Linear (25088 -> 4096)
    (1): ReLU ()
    (2): Dropout (p = 0.5)
    (3): Linear (4096 -> 4096)
    (4): ReLU ()
    (5): Dropout (p = 0.5)
    (6): Linear (4096 -> 2)
  )
)

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

Epoch0/1
----------
Batch 500, Train Loss:0.8073, Train ACC:88.4500
Batch 1000, Train Loss:1.0141, Train ACC:89.9500
Batch 1500, Train Loss:0.8976, Train ACC:91.2333
Batch 2000, Train Loss:0.8154, Train ACC:91.9500
Batch 2500, Train Loss:0.7552, Train ACC:92.3500
Batch 3000, Train Loss:0.6801, Train ACC:92.8083
Batch 3500, Train Loss:0.6457, Train ACC:93.0500
Batch 4000, Train Loss:0.6467, Train ACC:93.1875
Batch 4500, Train Loss:0.6263, Train ACC:93.3722
Batch 5000, Train Loss:0.5983, Train ACC:93.4950
train  Loss:0.5983,  Correct93.4950
val  Loss:0.4096,  Correct95.8400
Training time is:32m 11s

Учитывая, что потеря при обучении составляет 0,5983, точность составляет 93,495%. Потеря проверочного набора составляет 0,4096, а точность — 95,84%. Поскольку это только одна тренировка (одна тренировка длится 32 минуты), большее количество тренировок может дать лучший результат.

Случайным образом введите тестовый набор, чтобы увидеть результаты прогноза:

Pred Label: ['dog', 'cat', 'cat', 'dog', 'dog', 'cat', 'cat', 'dog', 'cat', 'cat', 'cat', 'cat', 'cat', 'cat', 'cat', 'dog']

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

ссылка на полный код:JaimeTang/PyTorch-and-TransferLearning

резюме:

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