【譯】pytorch遷移學習

寫在前面:本文僅是自己的學習筆記,還有待完善的地方,如有不當,還請指正。【侵刪】

在這篇文章,你將學習如何使用遷移學習的方法來訓練網路。你可以在cs231n筆記里學到更多關於遷移學習的知識。

引用cs231n中的幾句話:

在實踐中,很少有人重新訓練一個全新的卷積網路(參數隨機初始化),因為很難有足夠的數據集。相反,使用在大數據集(ImageNet,它有1.2百萬個數據,1000類)上預訓練的網路,之後使用convnet作為初始化或者特徵提取器,來完成你感興起的任務。

兩種主要的遷移學習方法如下:

  • 微調convnet:我們使用預訓練的網路進行參數初始化(如在ImageNet數據集訓練的網路)而不是參數隨機初始化。之後的訓練就和平常的訓練一樣了。
  • convnet作為特徵提取器:這裡,我們將凍結所有網路層的權重,除了最後一層全連接層。最後一層是一個參數隨機初始化的新層,並且只有這一層被訓練。

(譯者注:先載入我們接下來所需要的模塊)

from __future__ import print_function, divisionnnimport torchnimport torch.nn as nnnimport torch.optim as optimnfrom torch.optim import lr_schedulernfrom torch.autograd import Variablenimport numpy as npnimport torchvisionnfrom torchvision import datasets, models, transformsnimport matplotlib.pyplot as pltnimport timenimport osnnplt.ion()n

載入數據

我們將使用torchvision和torch.utils.data.packages模塊來載入數據。

我們今天要解決的問題是訓練一個能夠分類螞蟻和蜜蜂的模型。每一類(螞蟻和蜜蜂)下我們有大約120張訓練圖片和75張驗證圖片。通常來看,這是一個很小的數據集,如果重新訓練的話,很難收斂。因此我們使用遷移學習的方法,這樣模型就可以具有不錯的泛化能力。

這份數據是ImageNet的一小部分。

Note:從這裡下載數據集,將它保存在當前的文件夾中。

# 訓練集做了數據增強和歸一化n# 驗證集僅僅做了歸一化ndata_transforms = {n train: transforms.Compose([n transforms.RandomSizedCrop(224),n transforms.RandomHorizontalFlip(),n transforms.ToTensor(),n transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])n ]),n val: transforms.Compose([n transforms.Scale(256),n transforms.CenterCrop(224),n transforms.ToTensor(),n transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])n ]),n}nndata_dir = hymenoptera_datanimage_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),n data_transforms[x])n for x in [train, val]}ndataloders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,n shuffle=True, num_workers=4)n for x in [train, val]}ndataset_sizes = {x: len(image_datasets[x]) for x in [train, val]}nclass_names = image_datasets[train].classesnnuse_gpu = torch.cuda.is_available()n

譯者注:使用這種載入數據的方式,需要數據按照下面的格式

|--hymenoptera_datan |--trainn |--antsn |--.jpgn |--beesn |--.jpgn |--valn |--antsn |--.jpgn |--beesn |--.jpgn

其中,ants和bees表示類別)

可視化一些圖片

為了能夠理解數據增強,我們來可視化一些訓練集圖片。

def imshow(inp, title=None):n """顯示Tensor類型的圖片"""n inp = inp.numpy().transpose((1, 2, 0))n mean = np.array([0.485, 0.456, 0.406])n std = np.array([0.229, 0.224, 0.225])n inp = std * inp + meann plt.imshow(inp)n if title is not None:n plt.title(title)n plt.imshow() nnn#一批訓練集ninputs, classes = next(iter(dataloders[train]))nn#對圖片製作網格nout = torchvision.utils.make_grid(inputs)nnimshow(out, title=[class_names[x] for x in classes])n

結果:

訓練模型

現在,我們來寫一個訓練模型的函數。這裡,我們將定義:

  • 調整學習率
  • 保存最好的模型

下文中的參數 scheduler 是一個調整學習率的對象,在torch.optim.lr_scheduler中。

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):n since = time.time()nn best_model_wts = model.state_dict()n best_acc = 0.0nn for epoch in range(num_epochs):n print(Epoch {}/{}.format(epoch, num_epochs - 1))n print(- * 10)nn # 每一輪都有一次訓練和驗證n for phase in [train, val]:n if phase == train:n scheduler.step()n model.train(True) # 將模型設置為訓練模式n else:n model.train(False) # 將模型設置為驗證模式nn running_loss = 0.0n running_corrects = 0nn # 迭代數據n for data in dataloders[phase]:n # 得到輸入數據n inputs, labels = datann # 將他們包裝在Variablen if use_gpu:n inputs = Variable(inputs.cuda())n labels = Variable(labels.cuda())n else:n inputs, labels = Variable(inputs), Variable(labels)nn # 梯度歸零n optimizer.zero_grad()nn # 前向傳播n outputs = model(inputs) #1n _, preds = torch.max(outputs.data, 1) #2n loss = criterion(outputs, labels)nn # 反向傳播+參數優化,如果是處於訓練時期n if phase == train:n loss.backward()n optimizer.step()nn # 對每次迭代的loss和accuracy求和n running_loss += loss.data[0] n running_corrects += torch.sum(preds == labels.data)n # 統計每一輪的平均loss和accuracy n epoch_loss = running_loss / dataset_sizes[phase]n epoch_acc = running_corrects / dataset_sizes[phase]nn print({} Loss: {:.4f} Acc: {:.4f}.format(n phase, epoch_loss, epoch_acc))nn # 保存最好的模型n if phase == val and epoch_acc > best_acc:n best_acc = epoch_accn best_model_wts = model.state_dict()nn print()nn time_elapsed = time.time() - sincen print(Training complete in {:.0f}m {:.0f}s.format(n time_elapsed // 60, time_elapsed % 60))n print(Best val Acc: {:4f}.format(best_acc))nn # 載入模型權重n model.load_state_dict(best_model_wts) # 3n return modeln

(譯者注:#1模型輸出,其size為(batchsize,num_classes) ,num_classes對應每一張圖片經過網路預測的得分(或概率),這裡batchsize=4,num_classes=2;#2 _表示每個圖片對應每一類的最大得分(或最大概率),preds表示每張圖片最可能的類別(即概率最大的idx);# 3 model里保存的參數是最新但不一定是最好,這裡是將最好的參數載入到model)

可視化模型的預測

寫一個展示圖片的預測結果。

def visualize_model(model, num_images=6):n images_so_far = 0n fig = plt.figure()nn for i, data in enumerate(dataloders[val]):n inputs, labels = datan if use_gpu:n inputs, labels = Variable(inputs.cuda()), Variable(labels.cuda())n else:n inputs, labels = Variable(inputs), Variable(labels)nn outputs = model(inputs)n _, preds = torch.max(outputs.data, 1)nn for j in range(inputs.size()[0]):n images_so_far += 1n ax = plt.subplot(num_images//2, 2, images_so_far)n ax.axis(off)n ax.set_title(predicted: {}.format(class_names[preds[j]]))n imshow(inputs.cpu().data[j])nn if images_so_far == num_images:n returnn

1.微調convnet

載入預訓練的模型,重寫最後的全連接層。

model_ft = models.resnet18(pretrained=True)nnum_ftrs = model_ft.fc.in_featuresnmodel_ft.fc = nn.Linear(num_ftrs, 2) #在resnet18之後增加一個(1000,2)的全連接層nnif use_gpu:n model_ft = model_ft.cuda()nncriterion = nn.CrossEntropyLoss()nn# 參數優化noptimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)nn# 每隔7輪將學習率變為原來的0.1倍nexp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)n

訓練和評估

在CPU上可能要花15-25min。然而,在GPU上一分鐘都不到。

model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,n num_epochs=25)n

譯者輸出結果

training complete in 4m13s(GPU: GTX960M)

best val acc : 0.941176

visualize_model(model_ft)n

2.convet作為特徵提取器

這裡,我們需要凍結除了最後一層外的所有層。我們需要設置requires_grads == False來凍結參數,保證反向傳播時梯度不會被計算。

你可以在文檔中看到更多關於這個函數的信息。

model_conv = torchvision.models.resnet18(pretrained=True)nfor param in model_conv.parameters():n param.requires_grad = Falsenn# 新加的層默認設置 requires_grad=True nnum_ftrs = model_conv.fc.in_featuresnmodel_conv.fc = nn.Linear(num_ftrs, 2) # 在resnet10之後增加一個全連接層nnif use_gpu:n model_conv = model_conv.cuda()nncriterion = nn.CrossEntropyLoss()nn# 僅僅優化最後一層的參數noptimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)nn# 每隔7輪學習率變為原來的0.1倍nexp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)n

訓練和評估

在CPU上,只需要花費第一種方法一半的時間(譯者注:GTX960M 的GPU training complete in 2m17s)。這裡大部分網路的梯度是不需要計算的。然而,卻需要計算前向傳播。

model_conv = train_model(model_conv, criterion, optimizer_conv,n exp_lr_scheduler, num_epochs=25)n

譯者輸出結果

training complete in 2m17s(GPU: GTX960M)

best val acc : 0.947712

visualize_model(model_conv)n

怎能忘本,原文在這裡


推薦閱讀:

人工智慧會如何改變世界?
遷移學習中的domain adaptation的定義說特徵空間相同,這裡指的是什麼意思?

TAG:PyTorch | 计算机视觉 | 迁移学习TransferLearning |