基於遷移學習的 PyTorch 狗狗分類器
技術標籤:遷移學習機器學習、深度學習python演算法pythontensorflow機器學習人工智慧
你以前聽說過深度學習這個詞嗎? 或者你剛剛開始學習它?
在本文中,我將引導您構建自己的狗狗分類器。在這個專案的最後:
-
您的程式碼將接受任何使用者提供的影象作為輸入
-
如果一隻狗在影象中被檢測到,它將提供對該狗狗品種的預測
我會讓它儘可能的簡單~
實現步驟如下:
-
步驟0:匯入資料集
-
步驟1:影象預處理
-
步驟2:選擇遷移學習的模式
-
步驟3:更改預訓練模型的分類器
-
步驟4:編寫訓練演算法
-
步驟5:訓練模型
-
步驟6:測試模型
-
步驟7:測試你自己的圖片
步驟0:匯入資料集
你可以從下列網址下載你自己的資料集:https://www.kaggle.com/c/dog-breed-identification
然後解壓縮檔案!
由於影象處理在本地機器上需要大量的時間和資源,因此我將使用 colab 的 GPU 來訓練我的模型。所以,如果你沒有自己的 GPU,也可以切換到 colab 來跟進。
匯入必要的庫始終是一個良好的開始,下面程式碼展示了我們訓練所需要的庫。
#Importing Libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image
from PIL import ImageFile
import cv2
# importing Pytorch model libraries
import torch
import torchvision.models as models
import torchvision.transforms as transforms
from torchvision import datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
在下列程式碼的圓括號的雙引號中輸入 dog_images 的路徑。
dog_files = np.array(glob("/data/dog_images/*/*/*"))
# print number of images in each dataset
print('There are %d total dog images.' % len(dog_files))
Output : There are 8351 total dog images.
步驟1:影象預處理
首先,您需要將訓練、驗證和測試集資料的目錄載入到一些變數中。
#Loading images data into the memory and storing them into the variables
data_dir = '/content/dogImages'
train_dir = data_dir + '/train'
valid_dir = data_dir + '/valid'
test_dir = data_dir + '/test'
然後需要對載入的影象進行一些變換,這就是所謂的資料預處理。
為什麼有這個必要?
-
您的影象必須與網路的輸入大小匹配。如果您需要調整影象的大小以匹配網路,那麼您可以將資料重新縮放或裁剪到所需的大小
-
資料增強也使您能夠訓練網路不受影象資料扭曲的影響。為此,我會隨機裁剪和調整影象的大小
-
資料歸一化也是一個重要步驟,確保每個輸入引數(本例中為畫素)具有類似屬性的資料,這使得訓練網路時的收斂速度更快
#Applying Data Augmentation and Normalization on images
train_transforms = transforms.Compose([transforms.RandomRotation(30),
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
valid_transforms = transforms.Compose([transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
test_transforms = transforms.Compose([transforms.Resize(255),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
將影象儲存到資料載入器中
現在我們需要將訓練、驗證和測試目錄載入到資料載入器中。這將使我們能夠將資料分成小批量。
我們將資料以key-value的格式進行儲存,這將有助於以後呼叫它們。
# TODO: Load the datasets with ImageFolder
train_data = datasets.ImageFolder(train_dir, transform=train_transforms)
valid_data = datasets.ImageFolder(valid_dir,transform=valid_transforms)
test_data = datasets.ImageFolder(test_dir, transform=test_transforms)
# TODO: Using the image datasets and the trainforms, define the dataloaders
trainloader = torch.utils.data.DataLoader(train_data, batch_size=20, shuffle=True)
testloader = torch.utils.data.DataLoader(test_data, batch_size=20,shuffle=False)
validloader = torch.utils.data.DataLoader(valid_data, batch_size=20,shuffle=False)
loaders_transfer = {'train':trainloader,
'valid':validloader,
'test':testloader}
data_transfer = {
'train':trainloader
}
步驟2:選擇遷移學習的模式
什麼是預訓練模型,我們為什麼要使用它?
預訓練模型是別人為解決類似問題而建立的模型。
-
與其從零開始構建模型來解決類似的問題,不如使用經過其他問題訓練的模型作為起點
-
一個預訓練模型在應用程式中可能不是100% 準確,但是它節省了重新發明輪子所需的巨大努力
-
遷移學習可以利用在一個問題上學到的特性,並在一個新的、類似的問題上利用它們。例如,一個已經學會識別小浣熊的模型的特徵可能有助於開始訓練一個用於識別貓咪的模型
你可以選擇幾個事先訓練過的模特進行模特訓練。例如 Densenet,Resnet,VGG 模型。我將使用 VGG-16進行模型訓練。
#Loading vgg11 into the variable model_transfer
model_transfer = models.vgg11(pretrained=True)
步驟3:更改預訓練模型的分類器
將採取以下步驟來改變預訓練分類器:
-
從以前訓練過的模型中取出網路層
-
凍結它們,以避免在以後的訓練回合中破壞它們所包含的任何資訊
-
在凍結層上新增一些新的、可訓練的圖層。他們將學習在新的資料集上將舊的特性轉化為預測
-
在你的資料集上訓練新的網路層
由於特徵已經學會了預訓練的模型,所以將凍結他們在訓練期間對狗狗的形象。我們只會改變分類器的尺寸,只會訓練它。
原來的分類器層有25088個維度,但是為了匹配我們的預處理影象大小,我們需要更改為4096。
要在 GPU 上訓練模型,我們需要使用以下命令將其移動到 GPU-RAM 上。
#Freezing the parameters
for param in model_transfer.features.parameters():
param.requires_grad = False
#Changing the classifier layer
model_transfer.classifier[6] = nn.Linear(4096,133,bias=True)
#Moving the model to GPU-RAM space
if use_cuda:
model_transfer = model_transfer.cuda()
print(model_transfer)
現在我們需要選擇一個損失函式和優化器。
利用損失函式計算模型預測值與實際影象的誤差。如果你的預測完全錯誤,你的損失函式將輸出更高的數字。如果它們非常好,那麼輸出的數字就會更低。本文將使用交叉熵損失。
優化器是用來改變神經網路屬性的演算法或方法,比如權重和學習速度,以減少損失。本文將使用 SGD 優化器。
0.001的學習率對於訓練是有好處的,但是你也可以用其他的學習率進行實驗。鼓勵大家多多嘗試不同的引數~
### Loading the Loss-Function
criterion_transfer = nn.CrossEntropyLoss()
### Loading the optimizer
optimizer_transfer = optim.SGD(model_transfer.parameters(), lr=0.001, momentum=0.9)
步驟4:編寫訓練演算法
接下來我們將編寫訓練函式。
我用它編寫了驗證程式碼行。所以在訓練模型時,我們會同時得到兩種損失。
每次Loss減少時,我也會儲存這個模型。這樣我就不必在以後每次開啟一個新例項時都要訓練它。
def train(n_epochs, loaders, model, optimizer, criterion, use_cuda, save_path):
"""returns trained model"""
# initialize tracker for minimum validation loss
valid_loss_min = np.inf #---> Max Value (As the loss decreases and becomes less than this value it gets saved)
for epoch in range(1, n_epochs+1):
#Initializing training variables
train_loss = 0.0
valid_loss = 0.0
# Start training the model
model.train()
for batch_idx, (data, target) in enumerate(loaders['train']):
# move to GPU's memory space (if available)
if use_cuda:
data, target = data.cuda(), target.cuda()
model.to('cuda')
optimizer.zero_grad()
output = model(data)
loss = criterion(output,target)
loss.backward()
optimizer.step()
train_loss = train_loss + ((1 / (batch_idx + 1)) * (loss.data - train_loss))
# validate the model #
model.eval()
for batch_idx, (data, target) in enumerate(loaders['valid']):
accuracy=0
# move to GPU's memory space (if available)
if use_cuda:
data, target = data.cuda(), target.cuda()
## Update the validation loss
logps = model(data)
loss = criterion(logps, target)
valid_loss += ((1 / (batch_idx + 1)) * (loss.data - valid_loss))
# print both training and validation losses
print('Epoch: {} \tTraining Loss: {:.6f} \tValidation Loss: {:.6f}'.format(
epoch,
train_loss,
valid_loss
))
if valid_loss <= valid_loss_min:
print('Validation loss decreased ({:.6f} --> {:.6f}). Saving model ...'.format(
valid_loss_min,
valid_loss))
#Saving the model
torch.save(model.state_dict(), 'model_transfer.pt')
valid_loss_min = valid_loss
# return the trained model
return model
步驟5:訓練模型
現在,我將開始通過在函式中提供引數來訓練模型。我將訓練10個epoch。
# train the model
model_transfer = train(10, loaders_transfer, model_transfer, optimizer_transfer, criterion_transfer, use_cuda, 'model_transfer.pt')
# load the model that got the best validation accuracy
model_transfer.load_state_dict(torch.load('model_transfer.pt'))
OUTPUT:Epoch: 1 Training Loss: 2.443815 Validation Loss: 0.801671
Validation loss decreased (inf --> 0.801671). Saving model ...
Epoch: 2 Training Loss: 1.440627 Validation Loss: 0.591050
Validation loss decreased (0.801671 --> 0.591050). Saving model ...
Epoch: 3 Training Loss: 1.310158 Validation Loss: 0.560950
Validation loss decreased (0.591050 --> 0.560950). Saving model ...
Epoch: 4 Training Loss: 1.200572 Validation Loss: 0.566340
Epoch: 5 Training Loss: 1.160727 Validation Loss: 0.530196
Validation loss decreased (0.560950 --> 0.530196). Saving model ...
Epoch: 6 Training Loss: 1.088659 Validation Loss: 0.560774
Epoch: 7 Training Loss: 1.060936 Validation Loss: 0.503829
Validation loss decreased (0.530196 --> 0.503829). Saving model ...
Epoch: 8 Training Loss: 1.010044 Validation Loss: 0.500608
Validation loss decreased (0.503829 --> 0.500608). Saving model ...
Epoch: 9 Training Loss: 1.054875 Validation Loss: 0.497319
Validation loss decreased (0.500608 --> 0.497319). Saving model ...
Epoch: 10 Training Loss: 1.000547 Validation Loss: 0.545735<All keys matched successfully>
步驟6:測試模型
現在我將在模型以前從未見過的新影象上測試模型,並計算預測的準確性。
def test(loaders, model, criterion, use_cuda):
# Initializing the variables
test_loss = 0.
correct = 0.
total = 0.
model.eval() #So that it doesn't change the model parameters during testing
for batch_idx, (data, target) in enumerate(loaders['test']):
# move to GPU's memory spave if available
if use_cuda:
data, target = data.cuda(), target.cuda()
# Passing the data to the model (Forward Pass)
output = model(data)
loss = criterion(output, target) #Test Loss
test_loss = test_loss + ((1 / (batch_idx + 1)) * (loss.data - test_loss))
# Output probabilities to the predicted class
pred = output.data.max(1, keepdim=True)[1]
# Comparing the predicted class to output
correct += np.sum(np.squeeze(pred.eq(target.data.view_as(pred))).cpu().numpy())
total += data.size(0)
print('Test Loss: {:.6f}\n'.format(test_loss))
print('\nTest Accuracy: %2d%% (%2d/%2d)' % (
100. * correct / total, correct, total))
test(loaders_transfer, model_transfer, criterion_transfer, use_cuda)
我已經訓練了它10個 epoch,得到了83% 的準確率。並且,得到以下輸出!
Output:
Test Loss: 0.542430 Test Accuracy: 83% (700/836)
如何提高這個模型的準確性?
-
通過訓練更多的 epoch(比較訓練和驗證損失)
-
通過改變學習率(比如0.01,0.05,0.1)
-
通過改變預訓練模型(像稠密網路,但需要更多的訓練時間)
-
通過對影象的進一步預處理
步驟7:測試你自己的圖片
現在你已經訓練和測試了你的模型。現在,這是最令人興奮的部分。你能走到這一步真是太好了。
1.將要測試和儲存的新影象載入到記憶體中
#Loading the new image directory
dog_files_short = np.array(glob("/content/my_dogs/*"))
#Loading the model
model_transfer.load_state_dict(torch.load('model_transfer.pt'))
2. 現在我們必須對影象進行預處理,並通過測試我們訓練好的模型來預測類
def predict_breed_transfer(img_path):
#Preprocessing the input image
transform = transforms.Compose([transforms.Resize(255),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
img = Image.open(img_path)
img = transform(img)[:3,:,:].unsqueeze(0)
if use_cuda:
img = img.cuda()
model_transfer.to('cuda')
# Passing throught the model
model_transfer.eval()
# Checking the name of class by passing the index
class_names = [item[4:].replace("_", " ") for item in data_transfer['train'].dataset.classes]
idx = torch.argmax(model_transfer(img))
return class_names[idx]
output = model_transfer(img)
# Probabilities to class
pred = output.data.max(1, keepdim=True)[1]
return pred
3. 現在,通過將影象路徑作為引數傳遞給這個函式,我們可以預測狗狗的品種。
我已經傳遞了下面的影象,並得到了下列輸出。
Output:
Norwegian buhund
總結
這只是一個開始,你可以用這個模型做更多的事情。你可以通過部署它來建立一個應用程式。我嘗試從零開始建立自己的模型,沒有使用遷移學習,但測試的準確率不超過13% 。你也可以嘗試一下,因為這有助於理解概念。