1. 程式人生 > 其它 >pytorch深度學習-微調(fine tuning)

pytorch深度學習-微調(fine tuning)

技術標籤:DeepLearning學習機器學習深度學習python

微調(fine tuning)

首先舉一個例子,假設我們想從影象中識別出不同種類的椅子,然後將購買連結推薦給使用者。一種可能的方法是先找出100種常見的椅子,為每種椅子拍攝1,000張不同角度的影象,然後在收集到的影象資料集上訓練一個分類模型。這個椅子資料集雖然可能比Fashion-MNIST資料集要龐大,但樣本數仍然不及ImageNet資料集中樣本數的十分之一。這可能會導致適用於ImageNet資料集的複雜模型在這個椅子資料集上過擬合。同時,因為資料量有限,最終訓練得到的模型的精度也可能達不到實用的要求

為了應對上述問題,一個顯而易見的解決辦法是收集更多的資料

。然而,收集和標註資料會花費大量的時間和資金。例如,為了收集ImageNet資料集,研究人員花費了數百萬美元的研究經費。雖然目前的資料採集成本已降低了不少,但其成本仍然不可忽略。

另外一種解決辦法是應用遷移學習(transfer learning),將從源資料集學到的知識遷移到目標資料集上。例如,雖然ImageNet資料集的影象大多跟椅子無關,但在該資料集上訓練的模型可以抽取較通用的影象特徵,從而能夠幫助識別邊緣、紋理、形狀和物體組成等。這些類似的特徵對於識別椅子也可能同樣有效。

接下來就需要介紹遷移學習中的一種常用技術:微調(fine tuning)。如下圖所示,微調由以下4步構成。

  • 在源資料集(如ImageNet資料集)上預訓練一個神經網路模型
    ,即源模型。
  • 建立一個新的神經網路模型,即目標模型。它複製了源模型上除了輸出層外的所有模型設計及其引數。我們假設這些模型引數包含了源資料集上學習到的知識,且這些知識同樣適用於目標資料集。我們還假設源模型的輸出層跟源資料集的標籤緊密相關,因此在目標模型中不予採用。
  • 為目標模型新增一個輸出大小為目標資料集類別個數的輸出層,並隨機初始化該層的模型引數。
  • 在目標資料集(如椅子資料集)上訓練目標模型。我們將從頭訓練輸出層而其餘層的引數都是基於源模型的引數微調得到的

值得注意,但是並不難理解的是,當目標資料集遠小於源資料集時,微調有助於提升模型的泛化能力。

fine tuning的具體例子

接下來我們來實踐一個具體的例子:熱狗識別。我們將基於一個小資料集對在ImageNet資料集上訓練好的ResNet模型進行微調。該小資料集含有數千張包含熱狗和不包含熱狗的影象。我們將使用微調得到的模型來識別一張影象中是否包含熱狗。

首先,匯入實驗所需的包或模組。torchvision的models包提供了常用的預訓練模型。如果希望獲取更多的預訓練模型,可以使用使用pretrained-models.pytorch倉庫。

%matplotlib inline
import torch
from torch import nn, optim
from torch.utils.data import Dataset, DataLoader
import torchvision
from torchvision.datasets import ImageFolder
from torchvision import transforms
from torchvision import models
import os
from matplotlib import pyplot as plt


device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
獲取資料集

點選下載熱狗資料集,它含有1400張包含熱狗的正類影象,和同樣多包含其他食品的負類影象。各類的1000張影象被用於訓練,其餘則用於測試。

我們首先將壓縮後的資料集下載到路徑data_dir之下,然後在該路徑將下載好的資料集解壓,得到兩個資料夾hotdog/train和hotdog/test。這兩個資料夾下面均有hotdog和not-hotdog兩個類別資料夾,每個類別資料夾裡面是影象檔案。

data_dir = './data_set'
os.listdir(os.path.join(data_dir, "hotdog")) # ['train', 'test']

建立兩個ImageFolder例項來分別讀取訓練資料集和測試資料集中的所有影象檔案。

train_imgs = ImageFolder(os.path.join(data_dir, 'hotdog/train'))
test_imgs = ImageFolder(os.path.join(data_dir, 'hotdog/test'))

畫出前8張正類影象和最後8張負類影象。可以看到,它們的大小和高寬比各不相同。

def show_images(imgs, num_rows, num_cols, scale=2):
    figsize = (num_cols * scale, num_rows * scale)
    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    for i in range(num_rows):
        for j in range(num_cols):
            axes[i][j].imshow(imgs[i * num_cols + j])
            axes[i][j].axes.get_xaxis().set_visible(False)
            axes[i][j].axes.get_yaxis().set_visible(False)
    return axes


hotdogs = [train_imgs[i][0] for i in range(8)]
not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
  • 在訓練時,我們先從影象中裁剪出隨機大小和隨機高寬比的一塊隨機區域,然後將該區域縮放為高和寬均為224畫素的輸入。
  • 測試時,我們將影象的高和寬均縮放為256畫素,然後從中裁剪出高和寬均為224畫素的中心區域作為輸入。
  • 此外,我們對RGB(紅、綠、藍)三個顏色通道的數值做標準化:每個數值減去該通道所有數值的平均值,再除以該通道所有數值的標準差作為輸出。
    指定RGB三個通道的均值和方差來將影象通道歸一化
# 指定RGB三個通道的均值和方差來將影象通道歸一化
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
train_augs = transforms.Compose([
        transforms.RandomResizedCrop(size=224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        normalize
    ])

test_augs = transforms.Compose([
        transforms.Resize(size=256),
        transforms.CenterCrop(size=224),
        transforms.ToTensor(),
        normalize
    ])

需要注意的是:在使用預訓練模型時,一定要和預訓練時作同樣的預處理

定義和初始化模型

使用在ImageNet資料集上預訓練的ResNet-18作為源模型。這裡指定pretrained=True來自動下載並載入預訓練的模型引數。在第一次使用時需要聯網下載模型引數。

pretrained_net = models.resnet18(pretrained=True)

不管你是使用的torchvision的models還是pretrained-models.pytorch倉庫,預設都會將預訓練好的模型引數下載到你的home目錄下.torch資料夾。你可以通過修改環境變數TORCH_MODEL_ZOO來更改下載目錄: export TORCH_MODEL_ZOO="/local/pretrainedmodels" 。

另外比較常使用的方法是,在其原始碼中找到下載地址直接瀏覽器輸入地址下載,下載好後將其放到環境變數$TORCH_MODEL_ZOO所指資料夾即可,這樣比較快。)

列印源模型的成員變數fc。作為一個全連線層,它將ResNet最終的全域性平均池化層輸出變換成ImageNet資料集上1000類的輸出。

print(pretrained_net.fc)

如果你使用的是其他模型,那可能沒有成員變數fc(比如models中的VGG預訓練模型),所以正確做法是檢視對應模型原始碼中其定義部分,這樣既不會出錯也能加深我們對模型的理解。

可見此時pretrained_net最後的輸出個數等於目標資料集的類別數1000。所以我們應該將最後的fc成修改我們需要的輸出類別數:

pretrained_net.fc = nn.Linear(512, 2)
print(pretrained_net.fc)

此時,pretrained_net的fc層就被隨機初始化了,但是其他層依然儲存著預訓練得到的引數。由於是在很大的ImageNet資料集上預訓練的,所以引數已經足夠好,因此一般只需使用較小的學習率來微調這些引數,而fc中的隨機初始化引數一般需要更大的學習率從頭訓練

PyTorch可以方便的對模型的不同部分設定不同的學習引數,我們在下面程式碼中將fc的學習率設為已經預訓練過的部分的10倍。

output_params = list(map(id, pretrained_net.fc.parameters()))
feature_params = filter(lambda p: id(p) not in output_params, pretrained_net.parameters())

lr = 0.01
optimizer = optim.SGD([{'params': feature_params},
                       {'params': pretrained_net.fc.parameters(), 'lr': lr * 10}],
                       lr=lr, weight_decay=0.001)
微調模型

先定義一個使用微調的訓練函式train_fine_tuning以便多次呼叫。

def train(train_iter, test_iter, net, loss, optimizer, device, num_epochs):
    net = net.to(device)
    print("training on ", device)
    batch_count = 0
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y) 
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            train_l_sum += l.cpu().item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
              % (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
def train_fine_tuning(net, optimizer, batch_size=128, num_epochs=5):
    train_iter = DataLoader(ImageFolder(os.path.join(data_dir, 'hotdog/train'), transform=train_augs),
                            batch_size, shuffle=True)
    test_iter = DataLoader(ImageFolder(os.path.join(data_dir, 'hotdog/test'), transform=test_augs),
                           batch_size)
    loss = torch.nn.CrossEntropyLoss()
    train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
開始訓練(微調)
train_fine_tuning(pretrained_net, optimizer)
  • 遷移學習將從源資料集學到的知識遷移到目標資料集上。微調是遷移學習的一種常用技術。
  • 目標模型複製了源模型上除了輸出層外的所有模型設計及其引數,並基於目標資料集微調這些引數。而目標模型的輸出層需要從頭訓練。
  • 一般來說,微調引數會使用較小的學習率,而從頭訓練輸出層可以使用較大的學習率。