【小白學(xué)習(xí)PyTorch教程】十二、遷移學(xué)習(xí):微調(diào)VGG19實(shí)現(xiàn)圖像分類
「@Author:Runsen」
前言:遷移學(xué)習(xí)就是利用數(shù)據(jù)、任務(wù)或模型之間的相似性,將在舊的領(lǐng)域?qū)W習(xí)過或訓(xùn)練好的模型,應(yīng)用于新的領(lǐng)域這樣的一個(gè)過程。從這段定義里面,我們可以窺見遷移學(xué)習(xí)的關(guān)鍵點(diǎn)所在,即新的任務(wù)與舊的任務(wù)在數(shù)據(jù)、任務(wù)和模型之間的相似性。
假設(shè)有兩個(gè)任務(wù)系統(tǒng)A和B,任務(wù)A擁有海量的數(shù)據(jù)資源且已訓(xùn)練好,但并不是我們的目標(biāo)任務(wù),任務(wù)B是我們的目標(biāo)任務(wù),但數(shù)據(jù)量少且極為珍貴,這種場景便是典型的遷移學(xué)習(xí)的應(yīng)用場景
接下來在博客中,我們將學(xué)習(xí)如何將遷移學(xué)習(xí)與 PyTorch 結(jié)合使用。
在這個(gè)遷移學(xué)習(xí) PyTorch 圖像二分類Vgg19 示例中,數(shù)據(jù)來源:https://www.kaggle.com/pmigdal/alien-vs-predator-images/home
這是我在kaggle找到的關(guān)于遷移學(xué)習(xí)的入門案例
1) 加載數(shù)據(jù)
第一步是加載數(shù)據(jù)并對圖像進(jìn)行一些轉(zhuǎn)換,使其符合網(wǎng)絡(luò)要求。
使用 torchvision.dataset ,在文件夾中加載數(shù)據(jù)。該模塊將在文件夾中迭代以拆分?jǐn)?shù)據(jù)以進(jìn)行訓(xùn)練和驗(yàn)證。
轉(zhuǎn)換過程進(jìn)行基本的圖片處理操作。
將從中心裁剪圖像,執(zhí)行水平翻轉(zhuǎn),歸一化,最后使用將其轉(zhuǎn)換為張量。
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)
# {'train': 694, 'validation': 200}
class_names = image_datasets['train'].classes
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
可視化 PyTorch 遷移學(xué)習(xí)數(shù)據(jù)集??梢暬^程將從訓(xùn)練數(shù)據(jù)加載器和標(biāo)簽中獲取下一批圖像,并用 matplot 顯示它。
images, labels = next(iter(dataloaders['train']))
rows = 4
columns = 4
fig=plt.figure(figsize=(15,15))
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()

2) 定義模型

VGG19有兩個(gè)部分,分別是VGG19.features和VGG19.classifier。
vgg19.features有卷積層和池化層 vgg19.features有三個(gè)線性層,最后是softmax分類器
下面將使用 torchvision.models 加載 VGG19,并將預(yù)訓(xùn)練權(quán)重設(shè)置為 True之后,將凍結(jié)層,使這些層不可訓(xùn)練。
對 Linear 層修改最后一層,以滿足我們 2 個(gè)類的需求。
也可以將 CrossEntropyLoss 用于多類損失函數(shù),對于優(yōu)化器,使用學(xué)習(xí)率為 0.0001 和動(dòng)量為 0.9 的 SGD,如下面的 PyTorch 遷移學(xué)習(xí)示例所示。
##加載基于VGG19的模型
vgg_based = torchvision.models.vgg19(pretrained=True)
for param in vgg_based.parameters():
param.requires_grad = False
#修改最后一層
number_features = vgg_based.classifier[6].in_features
features = list(vgg_based.classifier.children())[:-1] # 移除最后一層
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)
vgg_based輸出如下
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) 訓(xùn)練模型
下面使用 PyTorch 中的一些功能來幫助我們訓(xùn)練和評估我們的模型。
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)
# 迭代數(shù)據(jù)
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
最后, epoch 數(shù)設(shè)置為 25 開始我們的模型訓(xùn)練過程,并在訓(xùn)練過程結(jié)束后進(jìn)行評估。
在每個(gè)訓(xùn)練步驟中,模型接受輸入并預(yù)測輸出。之后預(yù)測輸出將傳遞給計(jì)算損失。然后損失將執(zhí)行反向傳播來計(jì)算得到梯度,最后計(jì)算權(quán)重并使用 autograd 不斷的優(yōu)化參數(shù)。
vgg_based = train_model(vgg_based, criterion, optimizer_ft, num_epochs=25)

4) 測試模型
在可視化模型中,將訓(xùn)練好的模型,使用一批圖像進(jìn)行測試和預(yù)測標(biāo)簽
def visualize_model(model, num_images=6):
was_training = model.training
model.eval()
images_so_far = 0
fig=plt.figure(figsize=(15,15))
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)
visualize_model(vgg_based)
plt.show()




