还记得我们之前实现的猫狗分类器吗?在哪里,我们设计了一个网络,这个网络接受一张图片,最后输出这张图片属于猫还是狗。实现分类器的过程比较复杂,准备的数据也比较少。所以我们是否可以使用一种方法,在数据很少的情况下仍然可以训练出较好的模型。
借助已经训练好的模型是个不错的想法。因此我们将学习如何使用预训练好的模型来构建只需要很少数据的先进的猫狗图像分类器。
首先,加载一个预训练的模型,例如ResNet18。
借助torchvision库,我们很容易获得一组已经训练好的模型。这些模型大多数接受一个称为pretrained的参数,当这个参数为True时,它会下载为ImageNet分类问题调整好的权重。就像这样:
from torchvision import models
network1=models.resnet18(pretrained=True)
当代码第一次运行时,需要一点时间...
接着,我们需要冻结所有层,所有权重不会随训练而更新。
for param in network1.parameters():
param.requires_grad=False
当然,这个模型并不是针对2分类问题,所以,我们需要将其最后一层的输出特征从1000改为2。
首先我们要知道最后一层的名字:
network1
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)...
...
...
(fc): Linear(in_features=512, out_features=1000, bias=True)
最后一层是个全连接层,名为fc。
所以,我们就可将最后一层替换为输出特征为2的全连接层
network1.fc=nn.Linear(512,2)
注:此时,因为该层为新的层,所以其requires_grad=True,这样整个网络仅有这一层可以更新权重
打印网络
network1
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)...
...
...
(fc): Linear(in_features=512, out_features=2, bias=True)
此时,network1就是一个符合猫狗分类问题的模型。
最后,既然我们只对最后一层训练,那么我们只需要将最后一层的参数传入优化器
optimizer=optim.SGD(network1.fc.parameters(),lr=...,momentnum=...)
总结一下代码:
from torchvision import models
import torch.nn as nn
import torch.optim as optim
#网络搭建
network1=models.resnet18(pretrained=True)
for param in network1.parameters():
param.requires_grad=False
network1.fc=nn.Linear(512,2)
#损失函数
criterion=nn.CrossEntropyLoss()
#优化器
optimizer=optim.SGD(network1.fc.parameters(),lr=...,momentnum=...)
其实,我们就是利用已经训练好的模型的主要目的就是它已经能够提取出非常好的特征,最后一层接受前面层提取的特征,然后误差反向传播,仅更新这一层的权重,不断迭代,最后达到一个非常好的效果。
我们这里只对最后一层进行了调整,只训练这一层,主要原因就是数据太少;如果数据较多,可以把预训练的前面一些层权重固定住,后面层不固定,修改最后一层以满足任务,然后训练;如果数据很多,算力充沛,那么可以对所有层进行精调,只把预训练的模型的参数作为初始化参数。