使用已经训练好的神经网络,通过更改最后的全连接层,移植至新的分类任务中去。
训练集包含了244张蜜蜂与蚂蚁的图片,由于训练集数量较少,我们很难实现一个理想的网络训练。而迁移学习使得这个目的成为可能。
使用18层残差网络,去除最后的全连接层,使其适合当前的分类任务。
net = models.resnet18(pretrained=True)
# 存储了fc层输入神经元个数
num_ftrs = net.fc.in_features
# 替换网络的fc层
net.fc = nn.Linear(num_ftrs, 2)
直接使用244张照片训练,在验证集上的正确率仅为52%左右,分类器完全不具备实用价值。
在使用了残差网络进行迁移学习之后,分类准确率达到了94%左右,迁移学习的效果明显。
附代码:
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch import optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms, models
# 定义超参数
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
epoches = 20
batch_size = 4
train_path = 'HGD/train'
val_path = 'HGD/val'
# pipline
train_pipline = transforms.Compose(
[
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]
)
val_pipline = transforms.Compose(
[
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]
)
# 读入图片
train_data = datasets.ImageFolder(train_path, transform=train_pipline)
val_data = datasets.ImageFolder(val_path, transform=val_pipline)
# 数据加载器,num_worker表示同时进行的子进程数量
train_loader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle=True, num_workers=4)
val_loader = DataLoader(val_data, batch_size=batch_size, shuffle=False, num_workers=4)
net = models.resnet18(pretrained=True)
# 存储了fc层输入神经元个数
num_ftrs = net.fc.in_features
# 替换网络的fc层
net.fc = nn.Linear(num_ftrs, 2)
def train(model, device, epoch, train_loader, optimizer):
model.train()
for batch_index, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
result = model(data)
loss = F.cross_entropy(result, target)
loss.backward()
optimizer.step()
if batch_index % 61 == 0:
print('train in epoch {} \t loss is {:.3f}'.format(epoch, loss))
def val(model, device, val_loader):
model.eval()
acc = 0.0
val_loss = 0.0
with torch.no_grad():
for (data, target) in val_loader:
data, target = data.to(device), target.to(device)
out = model(data)
val_loss += F.cross_entropy(out, target).item()
pred = out.argmax(dim=1)
acc += pred.eq(target.view_as(pred)).sum().item()
val_loss /= len(val_loader.dataset)
print('test average loss:{:.4f}, accuracy:{:.3f}\n'.format(val_loss, 100.0 * acc / len(val_loader.dataset)))
net.to(device)
optimizer = optim.SGD(net.parameters(), lr=0.0001, momentum=0.9)
criterion = nn.CrossEntropyLoss()
if __name__ == '__main__':
for epoch in range(epoches):
epoch += 1
train(net, device, epoch, train_loader, optimizer)
val(net, device, val_loader)
1、从torchvision中调用的model模块中的网络,和我们实现常规的神经网络所定义的类十分相似。作为一个封装好的网络,可以直接通过net.fc来访问类中的属性值并将其修改。
2、若要实现仅仅针对最后的全连接层进行反向传播,应该添加下列命令:
for param in net.parameters():
param.requires_grad = False
其中net.parameters()包含了网络中所有的可训练参数,包括权重及偏置,该命令将网络的梯度设置为不需要,因此无法修改网络参数值。
在这里两种迁移学习方法性能差距不大。
手写数字加法器——使用自己训练的网络进行模型迁移
模型迁移,就是将我们训练好的特征图、权重等参数去初始化另一个模型的对应参数。
我们使用之前训练过的手写数字识别器的网络参数。
(1)保存模型参数以及网络结构
使用命令
module = net()
:
:训练过程
:
torch.save(module, pathname)
保存模型,注意,这里的module是代码中进行过实例化的模型名,而不是定义过的网络对应的类名。
(2)使用保存好的模型
使用保存好的模型进行训练:
在使用前,需要将原来的网络类重新定义,但无需写初始化函数__init__()内的内容,forward()函数直接复制过来。
再将其实例化,以及load即可:
origin_net = Convnet()
origin_net = torch.load('minst_checkpoint')