微調
前面的一些章節介紹了如何在只有6萬張圖像的Fashion-MNIST訓練數據集上訓練模型。
我們還描述了學術界當下使用最廣泛的大規模圖像數據集ImageNet,它有超過1000萬的圖像和1000類的物體。
然而,我們平常接觸到的數據集的規模通常在這兩者之間。
假如我們想識別圖片中不同類型的椅子,然后向用戶推薦購買鏈接。
一種可能的方法是首先識別100把普通椅子,為每把椅子拍攝1000張不同角度的圖像,然后在收集的圖像數據集上訓練一個分類模型。
盡管這個椅子數據集可能大于Fashion-MNIST數據集,但實例數量仍然不到ImageNet中的十分之一。
適合ImageNet的復雜模型可能會在這個椅子數據集上過擬合。
此外,由于訓練樣本數量有限,訓練模型的準確性可能無法滿足實際要求。
為了解決上述問題,一個顯而易見的解決方案是收集更多的數據。
但是,收集和標記數據可能需要大量的時間和金錢。
例如,為了收集ImageNet數據集,研究人員花費了數百萬美元的研究資金。
盡管目前的數據收集成本已大幅降低,但這一成本仍不能忽視。
另一種解決方案是應用遷移學習(transfer learning)將從源數據集學到的知識遷移到目標數據集。
例如,盡管ImageNet數據集中的大多數圖像與椅子無關,但在此數據集上訓練的模型可能會提取更通用的圖像特征,這有助于識別邊緣、紋理、形狀和對象組合。
這些類似的特征也可能有效地識別椅子。
步驟
本節將介紹遷移學習中的常見技巧:微調(fine-tuning)。如 fig_finetune
所示,微調包括以下四個步驟。
- 在源數據集(例如ImageNet數據集)上預訓練神經網絡模型,即源模型。
- 創建一個新的神經網絡模型,即目標模型。這將復制源模型上的所有模型設計及其參數(輸出層除外)。我們假定這些模型參數包含從源數據集中學到的知識,這些知識也將適用于目標數據集。我們還假設源模型的輸出層與源數據集的標簽密切相關;因此不在目標模型中使用該層。
- 向目標模型添加輸出層,其輸出數是目標數據集中的類別數。然后隨機初始化該層的模型參數。
- 在目標數據集(如椅子數據集)上訓練目標模型。輸出層將從頭開始進行訓練,而所有其他層的參數將根據源模型的參數進行微調。
fig_finetune
當目標數據集比源數據集小得多時,微調有助于提高模型的泛化能力。
熱狗識別
讓我們通過具體案例演示微調:熱狗識別。
我們將在一個小型數據集上微調ResNet模型。該模型已在ImageNet數據集上進行了預訓練。
這個小型數據集包含數千張包含熱狗和不包含熱狗的圖像,我們將使用微調模型來識別圖像中是否包含熱狗。
%matplotlib inline
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l
獲取數據集
我們使用的[熱狗數據集來源于網絡]。
該數據集包含1400張熱狗的“正類”圖像,以及包含盡可能多的其他食物的“負類”圖像。
含著兩個類別的1000張圖片用于訓練,其余的則用于測試。
解壓下載的數據集,我們獲得了兩個文件夾hotdog/train
和hotdog/test
。
這兩個文件夾都有hotdog
(有熱狗)和not-hotdog
(無熱狗)兩個子文件夾,
子文件夾內都包含相應類的圖像。
#@save
d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip','fba480ffa8aa7e0febbb511d181409f899b9baa5')data_dir = d2l.download_extract('hotdog')
Downloading ../data/hotdog.zip from http://d2l-data.s3-accelerate.amazonaws.com/hotdog.zip...
我們創建兩個實例來分別讀取訓練和測試數據集中的所有圖像文件。
train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))
下面顯示了前8個正類樣本圖片和最后8張負類樣本圖片。正如所看到的,[圖像的大小和縱橫比各有不同]。
hotdogs = [train_imgs[i][0] for i in range(8)]
not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
?
?
在訓練期間,我們首先從圖像中裁切隨機大小和隨機長寬比的區域,然后將該區域縮放為 224 × 224 224 \times 224 224×224輸入圖像。
在測試過程中,我們將圖像的高度和寬度都縮放到256像素,然后裁剪中央 224 × 224 224 \times 224 224×224區域作為輸入。
此外,對于RGB(紅、綠和藍)顏色通道,我們分別標準化每個通道。
具體而言,該通道的每個值減去該通道的平均值,然后將結果除以該通道的標準差。
[數據增廣]
# 使用RGB通道的均值和標準差,以標準化每個通道
normalize = torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])train_augs = torchvision.transforms.Compose([torchvision.transforms.RandomResizedCrop(224),torchvision.transforms.RandomHorizontalFlip(),torchvision.transforms.ToTensor(),normalize])test_augs = torchvision.transforms.Compose([torchvision.transforms.Resize([256, 256]),torchvision.transforms.CenterCrop(224),torchvision.transforms.ToTensor(),normalize])
[定義和初始化模型]
我們使用在ImageNet數據集上預訓練的ResNet-18作為源模型。
在這里,我們指定pretrained=True
以自動下載預訓練的模型參數。
如果首次使用此模型,則需要連接互聯網才能下載。
pretrained_net = torchvision.models.resnet18(pretrained=True)
預訓練的源模型實例包含許多特征層和一個輸出層fc
。
此劃分的主要目的是促進對除輸出層以外所有層的模型參數進行微調。
下面給出了源模型的成員變量fc
。
pretrained_net.fc
Linear(in_features=512, out_features=1000, bias=True)
在ResNet的全局平均匯聚層后,全連接層轉換為ImageNet數據集的1000個類輸出。
之后,我們構建一個新的神經網絡作為目標模型。
它的定義方式與預訓練源模型的定義方式相同,只是最終層中的輸出數量被設置為目標數據集中的類數(而不是1000個)。
在下面的代碼中,目標模型finetune_net
中成員變量features
的參數被初始化為源模型相應層的模型參數。
由于模型參數是在ImageNet數據集上預訓練的,并且足夠好,因此通常只需要較小的學習率即可微調這些參數。
成員變量output
的參數是隨機初始化的,通常需要更高的學習率才能從頭開始訓練。
假設Trainer
實例中的學習率為 η \eta η,我們將成員變量output
中參數的學習率設置為 10 η 10\eta 10η。
finetune_net = torchvision.models.resnet18(pretrained=True)
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2)
nn.init.xavier_uniform_(finetune_net.fc.weight);
[微調模型]
首先,我們定義了一個訓練函數train_fine_tuning
,該函數使用微調,因此可以多次調用。
# 如果param_group=True,輸出層中的模型參數將使用十倍的學習率
def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5,param_group=True):train_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=train_augs),batch_size=batch_size, shuffle=True)test_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'), transform=test_augs),batch_size=batch_size)devices = d2l.try_all_gpus()loss = nn.CrossEntropyLoss(reduction="none")if param_group:params_1x = [param for name, param in net.named_parameters()if name not in ["fc.weight", "fc.bias"]]trainer = torch.optim.SGD([{'params': params_1x},{'params': net.fc.parameters(),'lr': learning_rate * 10}],lr=learning_rate, weight_decay=0.001)else:trainer = torch.optim.SGD(net.parameters(), lr=learning_rate,weight_decay=0.001)d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,devices)
我們[使用較小的學習率],通過微調預訓練獲得的模型參數。
train_fine_tuning(finetune_net, 5e-5)
loss 0.220, train acc 0.915, test acc 0.939
999.1 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
[為了進行比較,]我們定義了一個相同的模型,但是將其(所有模型參數初始化為隨機值)。
由于整個模型需要從頭開始訓練,因此我們需要使用更大的學習率。
scratch_net = torchvision.models.resnet18()
scratch_net.fc = nn.Linear(scratch_net.fc.in_features, 2)
train_fine_tuning(scratch_net, 5e-4, param_group=False)
loss 0.374, train acc 0.839, test acc 0.843
1623.8 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
意料之中,微調模型往往表現更好,因為它的初始參數值更有效。
小結
- 遷移學習將從源數據集中學到的知識遷移到目標數據集,微調是遷移學習的常見技巧。
- 除輸出層外,目標模型從源模型中復制所有模型設計及其參數,并根據目標數據集對這些參數進行微調。但是,目標模型的輸出層需要從頭開始訓練。
- 通常,微調參數使用較小的學習率,而從頭開始訓練輸出層可以使用更大的學習率。