(附代碼)詳解使用Pytorch和預訓練數據(pretrained)進行遷移學習(Transfer Learning)

  • 遷移學習的藝術可能會改變您構建機器學習和深度學習模型的方式
  • 瞭解如何使用PyTorch進行遷移學習以及如何與預訓練的模型聯繫在一起
  • 我們將處理一個真實的數據集,並比較使用卷積神經網絡(CNN)構建的模型與使用轉移學習構建的模型的性能

本文代碼源:https://gist.github.com/PulkitS01/
原作者github:https://github.com/PulkitS01
文章所屬課程Computer Vision using Deep Learning 2.0

翻譯辛苦,如對您有一些幫助,請不吝賜贊 😉

介紹

去年,我在一個計算機視覺項目中工作,我們必須建立一個強大的人臉檢測模型。其背後的概念非常簡單–我始終牢記執行部分。

考慮到我們擁有的數據集的大小,從頭開始構建模型是一個真正的挑戰。這將可能非常耗時,並且會壓縮我們的計算資源。我們必須儘快找到解決方案,因爲我們的工作期限很緊。

這是強大的遷移學習概念浮出水面的時候。這對您的數據科學家來說是一個非常有用的工具,尤其是在您的時間和計算能力有限的情況下。

因此,在本文中,我們將學習有關轉移學習以及如何在使用Python的真實項目中利用轉移學習的所有知識。我們還將討論預訓練模型在此領域中的作用,以及它們將如何改變構建機器學習的方式。
本文適合對pytorch有一定了解的人

遷移學習簡介

讓我用一個例子來說明轉移學習的概念。想象一下–您想從一個全新的領域中學習一個主題。選擇任何領域和任何主題–您也可以想到深度學習和神經網絡。

您將採用什麼不同的方法來理解該主題?

  • 在線搜索資源
  • 閱讀文章和博客
  • 參考書
  • 尋找視頻教程,依此類推
    所有這些都將幫助您熟悉該主題。在這種情況下,您是唯一投入全部精力的人。

但是還有另一種方法,它可能在短時間內產生更好的結果。

您可以諮詢對您要學習的主題有紮實瞭解的領域/主題專家。此人將把他/她的知識轉移給您。從而加快您的學習過程。
在這裏插入圖片描述
第一種方法,就是您全力以赴,是從頭開始學習的一個示例。第二種方法稱爲轉移學習。從該領域的專家到新手的知識轉移正在發生。

是的,轉移學習背後的想法很簡單!

神經網絡和卷積神經網絡(CNN)是從頭開始學習的示例。這兩個網絡都從給定的一組圖像中提取特徵(在與圖像有關的任務的情況下),然後基於這些提取的特徵將圖像分類爲各自的類別。

在這裏,轉移學習和預訓練模型非常有用。在下一節中,讓我們對後一個概念有所瞭解。

如何選擇正確的預訓練模型

預訓練模型在您將要從事的任何深度學習項目中都非常有用。並非所有人都擁有頂尖科技龐然大物的無限計算能力。比如BERT並非每個人都可以在自己的機器上訓練出來

正如您可能已經猜想的那樣,預訓練模型是由某個人或團隊爲解決特定問題而設計和訓練的模型。

回想一下,我們在訓練神經網絡和CNN等模型時會學習權重和偏置項。這些權重和偏差與圖像像素相乘時,有助於生成特徵。

預訓練的模型通過將其權重和偏差矩陣傳遞給新模型來共享他們的學習成果。因此,無論何時進行遷移學習,我們都將首先選擇正確的預訓練模型,然後將其權重和偏差矩陣傳遞給新模型。

那裏有n個預訓練模型。我們需要確定哪種模型最適合我們的問題。現在,讓我們考慮一下我們有三個可用的預訓練網絡-BERT,ULMFiT和VGG16。
在這裏插入圖片描述
我們的任務是對圖像進行分類。那麼,您會選擇哪些預訓練模型?首先讓我簡要介紹一下這些預訓練的網絡,這將幫助我們確定正確的預訓練模型。

BERT和ULMFiT用於語言建模,而VGG16用於圖像分類任務。而且,如果您看一下眼前的問題,那就是圖像分類之一。因此,我們選擇VGG16是有道理的。

現在,VGG16可以具有不同的權重,即在ImageNet上訓練的VGG16或在MNIST上訓練的VGG16:
在這裏插入圖片描述
現在,要確定適合我們問題的正確預訓練模型,我們應該探索這些ImageNet和MNIST數據集。ImageNet數據集包括1000個類別和總共120萬張圖像。此數據中的某些類別是動物,汽車,商店,狗,食物,樂器等

在這裏插入圖片描述
另一方面,MNIST是手寫數字集合

我們需要將圖像分類爲緊急和非緊急車輛。該數據集包含車輛圖像,因此在ImageNet數據集上訓練的VGG16模型對我們而言將更爲有用,因爲它具有車輛圖像。

簡而言之,這就是我們應該根據問題決定正確的預訓練模型的方式。

案例研究:緊急與非緊急車輛分類

理想情況下,本文將使用“識別服裝”問題。我們在本系列的前兩篇文章中已經對此進行了研究,這將有助於比較我們的進度。

不幸的是,這在這裏是不可能的,因爲VGG16要求圖像的形狀應爲(224,224,3)(另一個問題中的圖像的形狀應爲(28,28))。解決此問題的一種方法可能是將這些(28,28)圖像的大小調整爲(224,224,3),但這在直觀上是沒有意義的。

這是很好的部分–我們將致力於一個全新的項目!在這裏,我們的目的是將車輛分類爲緊急或非緊急車輛。

該項目也是Analytics Vidhya的“使用深度學習的計算機視覺”課程的一部分。要從事更多此類有趣的項目並更詳細地學習計算機視覺的概念,請隨時查看本課程。

現在讓我們開始理解問題並可視化一些示例。您可以使用此鏈接下載圖像。首先,導入所需的庫:

# importing the libraries
import pandas as pd
import numpy as np
from tqdm import tqdm

# for reading and displaying images
from skimage.io import imread
from skimage.transform import resize
import matplotlib.pyplot as plt
%matplotlib inline

# for creating validation set
from sklearn.model_selection import train_test_split

# for evaluating the model
from sklearn.metrics import accuracy_score

# PyTorch libraries and modules
import torch
from torch.autograd import Variable
from torch.nn import Linear, ReLU, CrossEntropyLoss, Sequential, Conv2d, MaxPool2d, Module, Softmax, BatchNorm2d, Dropout
from torch.optim import Adam, SGD

# torchvision for pre-trained models
from torchvision import models

接下來,我們將讀取包含圖像名稱和相應標籤的.csv文件:

# loading dataset
train = pd.read_csv('emergency_train.csv')
train.head()

在這裏插入圖片描述

  • image_names:代表數據集中所有圖像的名稱
  • Emergency_or_no:指定特定圖像屬於緊急類別還是非緊急類別。0表示圖像是非緊急車輛,1表示緊急車輛

接下來,我們將加載所有圖像並將其以數組格式存儲:
load all the images and store them in an array format

# loading training images
train_img = []
for img_name in tqdm(train['image_names']):
    # defining the image path
    image_path = 'images/' + img_name
    # reading the image
    img = imread(image_path)
    # normalizing the pixel values
    img = img/255
    # resizing the image to (224,224,3)
    img = resize(img, output_shape=(224,224,3), mode='constant', anti_aliasing=True)
    # converting the type of pixel to float 32
    img = img.astype('float32')
    # appending the image into the list
    train_img.append(img)

# converting the list to numpy array
train_x = np.array(train_img)
train_x.shape

在這裏插入圖片描述
加載這些圖像大約花費了12秒鐘。我們的數據集中有1,646張圖像,由於VGG16需要所有此特定形狀的圖像,因此我們將所有圖像重塑爲(224,224,3)。現在讓我們可視化來自數據集的一些圖像:

# Exploring the data
index = 10
plt.imshow(train_x[index])
if (train['emergency_or_not'][index] == 1):
    print('It is an Emergency vehicle')
else:
    print('It is a Non-Emergency vehicle')

在這裏插入圖片描述
這是一輛警車,因此帶有緊急車輛標籤。現在,我們將目標存儲在單獨的變量中:

# defining the target
train_y = train['emergency_or_not'].values

讓我們創建一個驗證集來評估我們的模型:

# create validation set
train_x, val_x, train_y, val_y = train_test_split(train_x, train_y, test_size = 0.1, random_state = 13, stratify=train_y)
(train_x.shape, train_y.shape), (val_x.shape, val_y.shape)

在這裏插入圖片描述
訓練集中有1,481張圖像,而驗證集中有165張圖像。現在,我們必須將數據集轉換爲torch格式:

# converting training images into torch format
train_x = train_x.reshape(1481, 3, 224, 224)
train_x  = torch.from_numpy(train_x)

# converting the target into torch format
train_y = train_y.astype(int)
train_y = torch.from_numpy(train_y)

# shape of training data
train_x.shape, train_y.shape

在這裏插入圖片描述
同樣,我們將轉換驗證集:

# converting validation images into torch format
val_x = val_x.reshape(165, 3, 224, 224)
val_x  = torch.from_numpy(val_x)

# converting the target into torch format
val_y = val_y.astype(int)
val_y = torch.from_numpy(val_y)

# shape of validation data
val_x.shape, val_y.shape

在這裏插入圖片描述

我們的數據已經準備好!在下一部分中,我們將使用預訓練模型來解決此問題之前,將建立卷積神經網絡(CNN)。

使用CNN構建base-line

我們終於到了模型構建的一部分!在使用遷移學習解決問題之前,讓我們使用CNN模型併爲自己設置base-line。

我們將構建一個非常簡單的CNN架構,該架構具有兩個卷積層以從圖像中提取特徵,最後是一個密集層以對這些特徵進行分類:

class Net(Module):   
    def __init__(self):
        super(Net, self).__init__()

        self.cnn_layers = Sequential(
            # Defining a 2D convolution layer
            Conv2d(3, 4, kernel_size=3, stride=1, padding=1),
            BatchNorm2d(4),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2, stride=2),
            # Defining another 2D convolution layer
            Conv2d(4, 8, kernel_size=3, stride=1, padding=1),
            BatchNorm2d(8),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2, stride=2),
        )

        self.linear_layers = Sequential(
            Linear(8 * 56 * 56, 2)
        )

    # Defining the forward pass    
    def forward(self, x):
        x = self.cnn_layers(x)
        x = x.view(x.size(0), -1)
        x = self.linear_layers(x)
        return x

現在,爲模型定義優化器,學習率和損失函數,並使用GPU訓練模型:

# defining the model
model = Net()
# defining the optimizer
optimizer = Adam(model.parameters(), lr=0.0001)
# defining the loss function
criterion = CrossEntropyLoss()
# checking if GPU is available
if torch.cuda.is_available():
    model = model.cuda()
    criterion = criterion.cuda()

print(model)

在這裏插入圖片描述
這就是模型的架構。最後,我們將訓練模型15個epoch。我將模型的batch_size設置爲128

# batch size of the model
batch_size = 128

# number of epochs to train the model
n_epochs = 15

for epoch in range(1, n_epochs+1):

    # keep track of training and validation loss
    train_loss = 0.0
        
    permutation = torch.randperm(train_x.size()[0])

    training_loss = []
    for i in tqdm(range(0,train_x.size()[0], batch_size)):

        indices = permutation[i:i+batch_size]
        batch_x, batch_y = train_x[indices], train_y[indices]
        
        if torch.cuda.is_available():
            batch_x, batch_y = batch_x.cuda(), batch_y.cuda()
        
        optimizer.zero_grad()
        # in case you wanted a semi-full example
        outputs = model(batch_x)
        loss = criterion(outputs,batch_y)

        training_loss.append(loss.item())
        loss.backward()
        optimizer.step()
        
    training_loss = np.average(training_loss)
    print('epoch: \t', epoch, '\t training loss: \t', training_loss)

在這裏插入圖片描述
training loss一直在減少,這是個好兆頭。
讓我們檢測一下 training accuracy

# prediction for training set
prediction = []
target = []
permutation = torch.randperm(train_x.size()[0])
for i in tqdm(range(0,train_x.size()[0], batch_size)):
    indices = permutation[i:i+batch_size]
    batch_x, batch_y = train_x[indices], train_y[indices]

    if torch.cuda.is_available():
        batch_x, batch_y = batch_x.cuda(), batch_y.cuda()

    with torch.no_grad():
        output = model(batch_x.cuda())

    softmax = torch.exp(output).cpu()
    prob = list(softmax.numpy())
    predictions = np.argmax(prob, axis=1)
    prediction.append(predictions)
    target.append(batch_y)
    
# training accuracy
accuracy = []
for i in range(len(prediction)):
    accuracy.append(accuracy_score(target[i],prediction[i]))
    
print('training accuracy: \t', np.average(accuracy))

在這裏插入圖片描述
還不錯,接下來檢測一下validation accuracy

# prediction for validation set
prediction_val = []
target_val = []
permutation = torch.randperm(val_x.size()[0])
for i in tqdm(range(0,val_x.size()[0], batch_size)):
    indices = permutation[i:i+batch_size]
    batch_x, batch_y = val_x[indices], val_y[indices]

    if torch.cuda.is_available():
        batch_x, batch_y = batch_x.cuda(), batch_y.cuda()

    with torch.no_grad():
        output = model(batch_x.cuda())

    softmax = torch.exp(output).cpu()
    prob = list(softmax.numpy())
    predictions = np.argmax(prob, axis=1)
    prediction_val.append(predictions)
    target_val.append(batch_y)
    
# validation accuracy
accuracy_val = []
for i in range(len(prediction_val)):
    accuracy_val.append(accuracy_score(target_val[i],prediction_val[i]))
    
print('validation accuracy: \t', np.average(accuracy_val))

在這裏插入圖片描述
76%,這是我們的base-line,讓我們繼續吧。

使用遷移學習解決挑戰

讓我們看一下將要使用遷移學習訓練模型的步驟:

首先,我們將加載預訓練模型的權重-在本例中爲VGG16
然後我們將根據手頭的問題對模型進行微調
接下來,我們將使用這些預先訓練的權重並提取圖像特徵
最後,我們將使用提取的特徵來訓練微調模型
因此,讓我們開始加載模型的權重:

# loading the pretrained model
model = models.vgg16_bn(pretrained=True)

現在,我們將微調模型。我們不會訓練VGG16模型的各個層,因此讓我們凍結這些層的權重:

# Freeze model weights
for param in model.parameters():
    param.requires_grad = False

由於我們只有2個類別可以預測,並且VGG16在ImageNet上有1000個類別,因此我們需要根據問題更新最後一層:

# Add on classifier
model.classifier[6] = Sequential(
                      Linear(4096, 2))
for param in model.classifier[6].parameters():
    param.requires_grad = True

由於我們將只訓練的最後一層,我已經設置了requires_grad爲True作爲最後一層。讓我們將訓練設置爲GPU:

# checking if GPU is available
if torch.cuda.is_available():
    model = model.cuda()

現在,我們將使用模型並提取訓練圖像和驗證圖像的特徵。我將batch_size設置爲128(同樣,您可以根據需要增加或減少該batch_size):

# batch_size
batch_size = 128

# extracting features for train data
data_x = []
label_x = []

inputs,labels = train_x, train_y

for i in tqdm(range(int(train_x.shape[0]/batch_size)+1)):
    input_data = inputs[i*batch_size:(i+1)*batch_size]
    label_data = labels[i*batch_size:(i+1)*batch_size]
    input_data , label_data = Variable(input_data.cuda()),Variable(label_data.cuda())
    x = model.features(input_data)
    data_x.extend(x.data.cpu().numpy())
    label_x.extend(label_data.data.cpu().numpy())

同樣,讓我們​​爲驗證圖像提取功能:

# extracting features for validation data
data_y = []
label_y = []

inputs,labels = val_x, val_y

for i in tqdm(range(int(val_x.shape[0]/batch_size)+1)):
    input_data = inputs[i*batch_size:(i+1)*batch_size]
    label_data = labels[i*batch_size:(i+1)*batch_size]
    input_data , label_data = Variable(input_data.cuda()),Variable(label_data.cuda())
    x = model.features(input_data)
    data_y.extend(x.data.cpu().numpy())
    label_y.extend(label_data.data.cpu().numpy())

接下來,我們將這些數據轉換爲torch格式:

# converting the features into torch format
x_train  = torch.from_numpy(np.array(data_x))
x_train = x_train.view(x_train.size(0), -1)
y_train  = torch.from_numpy(np.array(label_x))
x_val  = torch.from_numpy(np.array(data_y))
x_val = x_val.view(x_val.size(0), -1)
y_val  = torch.from_numpy(np.array(label_y))

我們還必須爲模型定義優化器和損失函數:

import torch.optim as optim

# specify loss function (categorical cross-entropy)
criterion = CrossEntropyLoss()

# specify optimizer (stochastic gradient descent) and learning rate
optimizer = optim.Adam(model.classifier[6].parameters(), lr=0.0005)

現在該訓練模型了。我們將其訓練30個紀元,並將batch_size設置爲128:


# batch size
batch_size = 128

# number of epochs to train the model
n_epochs = 30

for epoch in tqdm(range(1, n_epochs+1)):

    # keep track of training and validation loss
    train_loss = 0.0
        
    permutation = torch.randperm(x_train.size()[0])

    training_loss = []
    for i in range(0,x_train.size()[0], batch_size):

        indices = permutation[i:i+batch_size]
        batch_x, batch_y = x_train[indices], y_train[indices]
        
        if torch.cuda.is_available():
            batch_x, batch_y = batch_x.cuda(), batch_y.cuda()
        
        optimizer.zero_grad()
        # in case you wanted a semi-full example
        outputs = model.classifier(batch_x)
        loss = criterion(outputs,batch_y)

        training_loss.append(loss.item())
        loss.backward()
        optimizer.step()
        
    training_loss = np.average(training_loss)
    print('epoch: \t', epoch, '\t training loss: \t', training_loss)

在這裏插入圖片描述
讓我們檢測一下train acc和valid acc

# prediction for training set
prediction = []
target = []
permutation = torch.randperm(x_train.size()[0])
for i in tqdm(range(0,x_train.size()[0], batch_size)):
    indices = permutation[i:i+batch_size]
    batch_x, batch_y = x_train[indices], y_train[indices]

    if torch.cuda.is_available():
        batch_x, batch_y = batch_x.cuda(), batch_y.cuda()

    with torch.no_grad():
        output = model.classifier(batch_x.cuda())

    softmax = torch.exp(output).cpu()
    prob = list(softmax.numpy())
    predictions = np.argmax(prob, axis=1)
    prediction.append(predictions)
    target.append(batch_y)
    
# training accuracy
accuracy = []
for i in range(len(prediction)):
    accuracy.append(accuracy_score(target[i],prediction[i]))
    
print('training accuracy: \t', np.average(accuracy))

在這裏插入圖片描述

# prediction for validation set
prediction_val = []
target_val = []
permutation = torch.randperm(x_val.size()[0])
for i in tqdm(range(0,x_val.size()[0], batch_size)):
    indices = permutation[i:i+batch_size]
    batch_x, batch_y = x_val[indices], y_val[indices]

    if torch.cuda.is_available():
        batch_x, batch_y = batch_x.cuda(), batch_y.cuda()

    with torch.no_grad():
        output = model.classifier(batch_x.cuda())

    softmax = torch.exp(output).cpu()
    prob = list(softmax.numpy())
    predictions = np.argmax(prob, axis=1)
    prediction_val.append(predictions)
    target_val.append(batch_y)
    
# validation accuracy
accuracy_val = []
for i in range(len(prediction_val)):
    accuracy_val.append(accuracy_score(target_val[i],prediction_val[i]))
    
print('validation accuracy: \t', np.average(accuracy_val))

在這裏插入圖片描述
模型的驗證準確性也相似,即83%。**訓練和驗證的準確性幾乎是同步的,因此可以說該模型是 generalized的。**以下是我們的結果摘要:
在這裏插入圖片描述
參考鏈接
Deep Learning for Everyone: Master the Powerful Art of Transfer Learning using PyTorch: https://www.analyticsvidhya.com/blog/2019/10/how-to-master-transfer-learning-using-pytorch/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章