Предисловие:
Что такое трансферное обучение? Простое понимание заключается в том, чтобы использовать некоторые уже обученные модели для переноса на аналогичные новые задачи для использования без необходимости повторного моделирования новых задач, обучения и оптимизации параметров с нуля. Эти обученные модели также содержат оптимизированные параметры, которые можно применять к новым задачам с помощью лишь некоторых простых настроек при их использовании.
Задача, которую нужно решить в этой статье, использует мигрированную модель VGG16. Эта статья в конечном итоге получит CNN (сверточная нейронная сеть), которая может распознавать изображения кошек и собак. Набор тестов используется для проверки того, может ли моя модель работать хорошо.
Используйте PyTorch для создания модели трансферного обучения:
VGG — это модель CNN (Convolutional Neural Network), созданная К. Симоняном и А. Зиссерманом в статье «Очень глубокие сверточные сети для крупномасштабного распознавания изображений». Модель есть на ImageNet:ImageNet(Конкурс на классификацию миллионов изображений) добился блестящих результатов в этой задаче.
Структура модели 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
резюме:
Преимущество метода трансферного обучения заключается в быстром решении подобных задач, и подобные задачи не требуют оптимизации и обучения параметров модели от начала до конца. Обучение параметров и оптимизация сложных моделей могут занять недели, поэтому эта идея значительно экономит время. Если результаты обучения модели не идеальны, вы также можете заморозить меньше слоев и обучить больше слоев вместо слепого обучения с нуля. Возможно, именно эти преимущества и определяют причину, по которой трансферное обучение широко используется на практике.