PyTorch Урок за трансферно обучение с примери

⚡ Умно обобщение

Трансферното обучение използва повторно мрежа, вече обучена върху голям набор от данни, така че нова, свързана задача може да бъде решена с много по-малко етикетирани изображения и част от първоначалното време за обучение.

  • 🔘 Основна идея: Теглата, научени в ImageNet, вече кодират ръбове, текстури и форми, които повечето задачи за зрение използват повторно.
  • ☑️ Две стратегии: Функция extracНастройката замразява гръбнака, докато фината настройка продължава да тренира някои или всички оригинални слоеве.
  • ✅ Разработен пример: Приблизително 700 снимки на Alien и Predator са достатъчни, за да се преквалифицира последният слой на VGG19.
  • 🧪 PyTorпарчета от ch: ImageFolder, transforms.Compose и DataLoader сглобяват пакетите, консумирани от мрежата.
  • 🛠️ Замръзващи слоеве: Задаването на requires_grad на False спира градиентите, така че само заменяният класификатор се учи.
  • ⚠️ Докладван резултат: Двадесет и пет епохи завършват за по-малко от три минути върху примерния набор от данни.

PyTorch Урок по трансферно обучение с разработени примери

Какво е трансферно обучение?

Прехвърляне на обучение е техника за използване на обучен модел за решаване на друга свързана задача. Това е Machine Learning Метод на изследване, който съхранява знанията, придобити при решаването на конкретен проблем, и използва същите знания за решаване на друг, различен, но свързан проблем. Това подобрява ефективността чрез повторно използване на информацията, събрана от предишно заучената задача.

Популярно е повторното използване на теглата от друг мрежов модел, тъй като обучението на мрежа от нулата изисква много голямо количество данни. За да се намали времето за обучение, се взема съществуваща мрежа и нейните тегла и се модифицира последният слой, за да се реши собственият проблем. Предимството е, че този последен слой може да бъде обучен с малък набор от данни.

Преди да напишете каквото и да е PyTorch код, е полезно да знаете към кое семейство от трансферно обучение принадлежи вашият проблем, защото това определя колко етикетирани данни са ви необходими.

Видове трансферно обучение

В научната литература техниката се разделя на три семейства. Кое от тях е приложимо за вас зависи от това коя страна на проблема носи етикети, а не от рамката, която използвате.

Тип Изходен домейн Target домейн Типична употреба
индуктивен Етикетирано Етикетирано, но различна задача Пренасочване на гръбнака на ImageNet към проблем с два класа „Пришълец срещу Хищник“
Трансдуктивна Етикетирано Неетикетирано, същата задача, различно разпределение на данните Адаптиране на домейн, като например преместване на модел от студийни снимки към телефонни снимки
Без надзор Неетикетирано Неетикетирано Clusterнамаляване на размерността, където етикетирането на всеки запис е непрактично

Примерът, изграден в този урок, е индуктивно трансферно обучение. VGG19 пристига с етикетирани знания от ImageNet и след това е насочен към етикетирана двукласова задача, която никога преди не е виждал.

Функция Extracция срещу фина настройка

След като бъде избрана предварително обучена мрежа, има два начина за нейното адаптиране. Разликата е просто колко слоя позволявате, за да продължи обучението.

Аспект Функция extracАЦИ Фина настройка
Слоеве, които тренират Само замененият класификатор Класификаторът плюс някои или всички конволюционни блокове
requires_grad на гръбнака Фалшив Вярно за блоковете, които се актуализират
Необходими данни Малки, често няколкостотин изображения на клас По-големи, обикновено хиляди
Цена на обучението Най-нисък, работи на процесор По-високо, толкова по-ценно е да имате графичен процесор
Типична точност Добре е, когато изходното и целевото изображение си приличат Обикновено е по-добре, когато двата домейна се различават

Стъпките по-долу използват функцията extracция: всеки VGG19 параметър е замразен и само новият последен линеен слой се обучава. Преминаването към фина настройка е малка промяна, а именно оставяне на requires_grad на True за блоковете, които искате да актуализирате, и намаляване на скоростта на обучение, така че заимстваните тегла да не бъдат унищожени.

Зареждане на набор от данни

Преди да започнете да използвате трансферното обучение с PyTorch, трябва да разберете набора от данни, който ще използвате. В този Transfer Learning PyTorНапример, ще класифицирате извънземен и хищник от близо 700 изображения. За тази техника всъщност не е необходимо голямо количество данни за обучение. Можете да изтеглите набора от данни от Kaggle: Пришълец срещу Хищник.

Колекцията е умишлено малка, а по-долу е показана извадка от снимките, които съдържа.

Набор от изображения „Alien vs Predator“ от Kaggle, използван в този PyTorпример за обучение по трансфер на ch

Източник: Alien vs. хищник Kaggle

Следващо в този PyTorch урок за трансферно обучение, ще научите как да прилагате трансферно обучение с PyTorстъпка по стъпка.

Как да използваме трансферното обучение?

Ето стъпка по стъпка процес за това как да използвате трансферно обучение за дълбоко обучение с PyTorch:

Стъпка 1) Заредете данните

Първата стъпка е да се заредят данните и да се приложат някои трансформации към изображенията, така че те да отговарят на изискванията на мрежата.

Ще заредите данните от папка с torchvision.datasets. Модулът итерира през папката, за да раздели данните на набори за влакове и валидиране. Използваният тук канал за трансформация изрязва изображенията от центъра, преобразува ги в тензор и ги нормализира за Дълбоко обучение.

from __future__ import print_function, division
import os
import time
import torch
import torchvision
from torchvision import datasets, models, transforms
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

data_dir = "alien_pred"
input_shape = 224
mean = [0.5, 0.5, 0.5]
std = [0.5, 0.5, 0.5]

#data transformation
data_transforms = {
   'train': transforms.Compose([
       transforms.CenterCrop(input_shape),
       transforms.ToTensor(),
       transforms.Normalize(mean, std)
   ]),
   'validation': transforms.Compose([
       transforms.CenterCrop(input_shape),
       transforms.ToTensor(),
       transforms.Normalize(mean, std)
   ]),
}

image_datasets = {
   x: datasets.ImageFolder(
       os.path.join(data_dir, x),
       transform=data_transforms[x]
   )
   for x in ['train', 'validation']
}

dataloaders = {
   x: torch.utils.data.DataLoader(
       image_datasets[x], batch_size=32,
       shuffle=True, num_workers=4
   )
   for x in ['train', 'validation']
}

dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'validation']}

print(dataset_sizes)
class_names = image_datasets['train'].classes

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

Сега визуализирайте набора от данни. Стъпката за визуализация взема следващата партида изображения и етикети от зареждащия механизъм за обучителни данни и ги показва с Matplotlib.

images, labels = next(iter(dataloaders['train']))

rows = 4
columns = 4
fig=plt.figure()
for i in range(16):
   fig.add_subplot(rows, columns, i+1)
   plt.title(class_names[labels[i]])
   img = images[i].numpy().transpose((1, 2, 0))
   img = std * img + mean
   plt.imshow(img)
plt.show()

Изпълнението на този фрагмент чертае мрежа от тренировъчни изображения четири на четири, всяка от които е озаглавена с името на класа, върнато от зареждащия механизъм.

Партида от шестнадесет тренировъчни изображения, нарисувани в мрежа четири на четири в Matplotlib със заглавия на класове

Стъпка 2) Дефиниране на модел

В този процес на дълбоко обучение ще използвате VGG19 от модула torchvision.

Ще използвате torchvision.models, за да заредите vgg19 с активирани предварително обучени тегла. След това замразявате слоевете, така че да не могат да се обучават. След това модифицирате последния слой с линеен слой, който отговаря на проблема, което тук означава 2 класа. CrossEntropyLoss се използва като функция на загуба, а оптимизаторът е SGD със скорост на обучение 0.001 и импулс 0.9, както е показано на диаграмата Py по-долу.Torch Пример за трансферно обучение.

## Load the model based on VGG19
vgg_based = torchvision.models.vgg19(pretrained=True)

## freeze the layers
for param in vgg_based.parameters():
   param.requires_grad = False

# Modify the last layer
number_features = vgg_based.classifier[6].in_features
features = list(vgg_based.classifier.children())[:-1] # Remove last layer
features.extend([torch.nn.Linear(number_features, len(class_names))])
vgg_based.classifier = torch.nn.Sequential(*features)

vgg_based = vgg_based.to(device)

print(vgg_based)

criterion = torch.nn.CrossEntropyLoss()
optimizer_ft = optim.SGD(vgg_based.parameters(), lr=0.001, momentum=0.9)

Бележка към версията: - предварително обучено=Вярно аргументът все още работи, но е заменен от версия 0.13 насам от torchvision тежести аргумент, така че по-новите инсталации очакват torchvision.models.vgg19(тегла=VGG19_Тегла.ПО СТАНДАРТНО) и в противен случай да се отпечата предупреждение за отхвърляне. И двата формуляра зареждат едни и същи тегла на ImageNet.

Структурата на изходния модел

Отпечатването на модела връща пълната VGG19 графика. Прочетете последния ред на класификаторния блок, за да потвърдите, че размяната е проработила: сега се получават 2 изхода вместо 1,000 ImageNet класа.

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(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(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(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(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): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(17): ReLU(inplace)
	(18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(19): Conv2d(256, 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): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(24): ReLU(inplace)
	(25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(26): ReLU(inplace)
	(27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(29): ReLU(inplace)
	(30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(31): ReLU(inplace)
	(32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(33): ReLU(inplace)
	(34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(35): ReLU(inplace)
	(36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
	(0): Linear(in_features=25088, out_features=4096, bias=True)
	(1): ReLU(inplace)
	(2): Dropout(p=0.5)
	(3): Linear(in_features=4096, out_features=4096, bias=True)
	(4): ReLU(inplace)
	(5): Dropout(p=0.5)
	(6): Linear(in_features=4096, out_features=2, bias=True)
  )
)

Стъпка 3) Обучете и тествайте модела

Ще използваме някои от функциите от това PyTorch Урок за да ни помогне да обучим и оценим нашия модел.

def train_model(model, criterion, optimizer, num_epochs=25):
   since = time.time()

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

       #set model to trainable
       # model.train()

       train_loss = 0

       # Iterate over data.
       for i, data in enumerate(dataloaders['train']):
           inputs , labels = data
           inputs = inputs.to(device)
           labels = labels.to(device)

           optimizer.zero_grad()
          
           with torch.set_grad_enabled(True):
               outputs  = model(inputs)
               loss = criterion(outputs, labels)

           loss.backward()
           optimizer.step()

           train_loss += loss.item() * inputs.size(0)

           print('{} Loss: {:.4f}'.format(
               'train', train_loss / dataset_sizes['train']))
          
   time_elapsed = time.time() - since
   print('Training complete in {:.0f}m {:.0f}s'.format(
       time_elapsed // 60, time_elapsed % 60))

   return model

def visualize_model(model, num_images=6):
   was_training = model.training
   model.eval()
   images_so_far = 0
   fig = plt.figure()

   with torch.no_grad():
       for i, (inputs, labels) in enumerate(dataloaders['validation']):
           inputs = inputs.to(device)
           labels = labels.to(device)

           outputs = model(inputs)
           _, preds = torch.max(outputs, 1)

           for j in range(inputs.size()[0]):
               images_so_far += 1
               ax = plt.subplot(num_images//2, 2, images_so_far)
               ax.axis('off')
               ax.set_title('predicted: {} truth: {}'.format(class_names[preds[j]], class_names[labels[j]]))
               img = inputs.cpu().data[j].numpy().transpose((1, 2, 0))
               img = std * img + mean
               ax.imshow(img)

               if images_so_far == num_images:
                   model.train(mode=was_training)
                   return
       model.train(mode=was_training)

Накрая в това трансферно обучение в PyTorВ пример ch, започнете процеса на обучение с броя епохи, зададен на 25, и след това оценете мрежата. На всяка стъпка от обучението моделът приема входните данни и прогнозира изхода. Прогнозата се предава на критерия за изчисляване на загубата, обратното разпространение изчислява градиентите, а оптимизаторът актуализира теглата с autograd.

Във функцията за визуализация, обучената мрежа се тества с партида изображения, за да се предскажат етикетите, а резултатът се изобразява с Matplotlib.

vgg_based = train_model(vgg_based, criterion, optimizer_ft, num_epochs=25)

visualize_model(vgg_based)

plt.show()

Стъпка 4) Резултати

Отчетената точност за това бягане е 92%. Дневникът, отпечатан в края на тренировката, показва загубата на бягане за последните две епохи, заедно с общото време за тренировка.

Epoch 23/24
----------
train Loss: 0.0044
train Loss: 0.0078
train Loss: 0.0141
train Loss: 0.0221
train Loss: 0.0306
train Loss: 0.0336
train Loss: 0.0442
train Loss: 0.0482
train Loss: 0.0557
train Loss: 0.0643
train Loss: 0.0763
train Loss: 0.0779
train Loss: 0.0843
train Loss: 0.0910
train Loss: 0.0990
train Loss: 0.1063
train Loss: 0.1133
train Loss: 0.1220
train Loss: 0.1344
train Loss: 0.1382
train Loss: 0.1429
train Loss: 0.1500
Epoch 24/24
----------
train Loss: 0.0076
train Loss: 0.0115
train Loss: 0.0185
train Loss: 0.0277
train Loss: 0.0345
train Loss: 0.0420
train Loss: 0.0450
train Loss: 0.0490
train Loss: 0.0644
train Loss: 0.0755
train Loss: 0.0813
train Loss: 0.0868
train Loss: 0.0916
train Loss: 0.0980
train Loss: 0.1008
train Loss: 0.1101
train Loss: 0.1176
train Loss: 0.1282
train Loss: 0.1323
train Loss: 0.1397
train Loss: 0.1436
train Loss: 0.1467
Training complete in 2m 47s

След това прогнозите на модела се визуализират с Matplotlib, както е показано по-долу.

Валидационни снимки, обозначени с прогнозирания клас и истинския клас след обучение

Често срещани грешки при трансферно обучение и как да ги поправим

Повечето грешки в скрипт за трансферно обучение са механични, а не математически. Това са онези, които спират изпълнението на горния код, и какво означава всяка от тях.

  • Несъответствие в размерите на крайния слой: Замененият линеен слой трябва да приема in_features, докладвани от оригиналния класификатор, и да генерира точно len(class_names) изходи. Отпечатването на модела, както в стъпка 2, е най-бързият начин за потвърждаване и на двете числа.
  • Гръбнакът никога не е бил замръзнал: Ако requires_grad е оставен на True, всеки параметър на VGG19 се актуализира и изпълнението се забавя до пълно зареждане на процесора. Задайте го на False, преди класификаторът да бъде заменен.
  • Несъответствие в нормализацията: Средната стойност и стандартното отклонение, използвани в трансформациите. Нормализирането трябва да са едни и същи стойности по време на обучение и извод, в противен случай прогнозите се отклоняват без видима причина.
  • Аргумент за остарели тегла: В torchvision 0.13 и по-нови версии, pretrained=True поражда предупреждение за отхвърляне и изброяването на теглата е за предпочитане.
  • брой_работници на Windows и тетрадки: DataLoader с num_workers=4 се нуждае от защитена входна точка, така че задайте num_workers=0, ако Python поражда грешка при спаун или мариноване.
  • Оценяване в режим на обучение: Извикайте model.eval() преди оценяване, така че Dropout и BatchNorm да се държат детерминистично, и след това превключете обратно с model.train().

Въпроси и Отговори

Няколкостотин изображения на клас обикновено са достатъчни, когато се обучава само последният слой; този пример използва общо приблизително 700 изображения. Фината настройка на по-дълбоките блокове изисква повече - често няколко хиляди - защото се актуализират много повече параметри.

Гръбначните мрежи ResNet, VGG, EfficientNet и Vision Transformer се доставят с тегловни характеристики за Torchvision. ResNet50 е често срещаната стойност по подразбиране, защото балансира точността с размера. VGG19, използваната тук, е по-тежка. конволюционна мрежа но е лесно да се анализира слой по слой.

Не. Замразяването на гръбначната мрежа и обучението на един слой работи приемливо на процесор, поради което горното изпълнение завършва за по-малко от три минути. Фината настройка на цяла мрежа или обучението върху хиляди изображения е мястото, където графичният процесор престава да бъде опционален.

Автоматизираното търсене на модели сравнява няколко предварително обучени мрежи върху извадка от вашите данни и ги класира по точност, латентност и размер. Това елиминира догадките при избора на гръбначна мрежа и се съчетава добре с търсенето на хиперпараметри в scikit-научите.

Той добре изготвя шаблона, защото трансформациите, настройката на DataLoader и циклите за обучение следват познати модели. Проверете частите, които не може да изведе: броя на изходните класове, статистиката за нормализиране и дали requires_grad действително е изключен.

Отрицателно прехвърляне се случва, когато изходният домейн е твърде различен от целевия, така че заимстваните тегла по-скоро вредят, отколкото помагат. Внимавайте за загуба на валидност, която се стабилизира в началото или която е над малка мрежа, обучена от нулата.

Да. Езиковите модели са предварително обучени върху големи текстови корпуси и след това адаптирани за класификация или отговаряне на въпроси, като същата идея се пренася и в модели на последователности, аудио и таблични вграждания. Променя се само гръбнакът.

Много по-малко от мрежа, обучена от нулата. Тук се използват двадесет и пет епохи, но десет до двадесет често са достатъчни за характеристики, например...tracция. Спрете, когато загубата на валидност спре да се подобрява, вместо да пускате фиксирано броене.

Обобщете тази публикация с: