在本教程中,你将会学到如何训练你的神经网络学会迁移学习。你可以在cs231n的课程上学到更多关于迁移学习的知识。
引用它的笔记:
在实践中,很少有人使用随机的初始化来训练整个神经网络。因为拥有足够大小的数据集是相对罕见的。相反,在更大的数据集上(比如说imageNet,它包含了1000个类别共120万幅图片)预先训练ConvNet,然后使用ConvNet作为感兴趣的任务的初始化或固定特征的提取器是一种常见的做法。
这两个主要的迁移学习的场景如下:
- 精细化调整ConvNet:比起随机的初始化,我们使用预先训练的神经网络——比如说在ImageNet上训练过的网络,来初始化神经网络。其它的训练正常进行即可。
- 作为固定特征提取器的ConvNet:在这种场景下,我们会冻结除了最后的全连接层外的所有网络的权重。然后把全连接层置换为一个新的有着随机权重的网络层,然后只训练就好。
from __future__ import print_function,division
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
## lr_scheduler提供了基于opoch数量的几种调整学习率的方法
## 更详细的见 https://pytorch.org/docs/stable/optim.html
from torch.optim import lr_scheduler
import torchvision
## datasets里包含了各种数据集,models中包含了各种模型
## 见 https://pytorch-cn.readthedocs.io/zh/latest/torchvision/torchvision-datasets/
from torchvision import datasets,models,transforms
import matplotlib as plt
## 包含了许多对时间进行处理的包
## 见https://www.runoob.com/python/python-date-time.html
import time
import os
## copy模块提供深拷贝和浅拷贝两种复制方法,前者复制可变(dict、列表)和不变对象
## 后者复制不变对象,引用可变对象,即对可变对象的改变也会同步改变它的值
## 见 https://www.cnblogs.com/MrFiona/p/5958911.html
import copy
plt.ion()
载入数据
我们将会使用torchvision和torch.utils.data包来载入数据。
我们今天要解决的问题是分类蚂蚁和蜜蜂。每一类我们都有大概120张图片。每个类都有75个验证图片。通常,如果从头训练,这是个非常小的数据集。但因为我们使用了迁移学习,我们应该能够很好地做到。
这个数据集是ImageNet的一个小的子集。
data_transforms = {
'train':transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
]),
'val':transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
}
data_dir = 'C:/Users/majx1/Music/hymenoptera_data'
image_datasets = {x:datasets.ImageFolder(os.path.join(data_dir,x),data_transforms[x])
for x in ['train','val']}
dataloaders = {x:torch.utils.data.DataLoader(image_datasets[x],batch_size=4,shuffle=True,num_workers=4)
for x in ['train','val']}
dataset_sizes = {x:len(image_datasets[x])for x in ['train','val']}
## 用list保存类名,见 https://www.cnblogs.com/wanghui-garcia/p/10649364.html
class_names = image_datasets['train'].classes
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
可视化一些图像
让我们来可视化一些训练图像来理解数据增强。
## Visualize Train Images
def imshow(inp,title=None):
inp = inp.numpy().transpose((1,2,0))
mean = np.array([0.485,0.456,0.406])
std = np.array([0.229,0.224,0.225])
inp = std * inp + mean
## clip()将inp中小于0的数变为0,大于1的数变为1,类似Tensor的clamp()
## 见 https://blog.csdn.net/qq1483661204/article/details/78150203
inp = np.clip(inp,0,1)
plt.imshow(inp)
if title is not None:
plt.title(title)
plt.pause(0.001)
inputs,classes = next(iter(dataloaders['train']))
out = torchvision.utils.make_grid(inputs)
imshow(out,title=[class_names[x] for x in classes])
其结果为:
训练模型
现在,我们可以写一个一般函数来训练模型。在这里,我们会说明:
- 规划学习率
- 保存最好的模型
在下面的代码里,参数scheduler是个torch.optim.lr_scheduler的一个对象。
## Training The Model
def train_model(model,criterion,optimizer,scheduler,num_epochs=25):
## time.time()返回距1970年1月1日的秒数
since = time.time()
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch,num_epochs - 1))
print('-' * 10)
for phase in ['train','val']:
if phase == 'train':
scheduler.step()
model.train()
else:
model.eval()
running_loss = 0.0
running_correct = 0
for inputs,labels in dataloaders[phase]:
inputs = inputs.to(device)
labels = labels.to(device)
optimizer.zero_grad()
## 在参数值为True时,容许记录导数;否则不能记录。是为了在测试时减少运算量
## 见 https://blog.csdn.net/zzzpy/article/details/88873109
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_,preds = torch.max(outputs,1)
loss = criterion(outputs,labels)
if phase == 'train':
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() / dataset_sizes[phase]
print('{} loss: {:.4f} Acc: {:.4f}'.format(phase,epoch_loss,epoch_acc))
if phase == 'val' and epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
print()
time_elapased = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format(time_elapased // 60,time_elapased % 60))
print('Best val Acc:{:4f}'.format(best_acc))
model.load_state_dict(best_model_wts)
return model
可视化模型预测
用于展示一些图片的预测值的一般函数。
def visualize_model(model,num_images=6):
was_training = model.training
model.eval()
images_so_far = 0
fig = plt.figure()
with torch.no_grad():
for i,(inputs,label) in enumerate(dataloaders['val']):
inputs = inputs.to(device)
labels = labels.to(device)
outputs = model(inputs)
_,preds = torch.max(outputs,1)
for j in range(inputs.size()[0]):
images_so_far += 1
ax = plt.subplot(num_images//2,2,images_so_far)
ax.axis('off')
ax.set_title('predicted:{}'.format(class_names[preds[j]]))
imshow(inputs.cpu().data[j])
if images_so_far == num_images:
model.train(mode=was_training)
return
model.train(mode=was_training)
Finetuning the convnet
载入预先训练好的模型,并重制最后一层全连接层。
model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs,2)
model_ft = model_ft.to(device)
criterion = nn.CrossEntropyLoss()
optimizer_ft = optim.SGD(model_ft.parameters(),lr=0.001,momentum=0.9)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft,step_size=7,gamma=0.1)
训练及评估
这在cpu上大概要运行15-24分钟,但在GPU上,只需要运行不到1分钟。
model_ft = train_model(model_ft,criterion,optimizer_ft,exp_lr_scheduler,num_epochs=25)
其结果为:
visualize_model(model_ft)
其结果为:
作为固定特征提取器的ConvNet
在这里,我们需要冻结除了最后一层外所有的神经网络。我们需要设置requires_grad == False来冻结参数,从而使backward()时不计算梯度。
model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
param.requires_grad = False
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs,2)
model_conv = model_conv.to(device)
criterion = CrossEntropyLoss()
optimizer_conv = optim.SGD(model_conv.fc.parameters(),lr=0.001,momentum=0.9)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv,step_size=7,gamma=0.1)
训练和评估
在CPU上这大概会花之前一半的时间。这是预期的结果,因为大多数网络并不需要计算梯度。然而,前向传播需要计算。
model_conv = train_model(model_conv,criterion,optimizer_conv,exp_lr_scheduler,num_epochs=25)
visualize_model(model_conv)
plt.ioff()
plt.show()