PyTorch基础——迁移学习⼀、介绍
内容
使机器能够“举⼀反三”的能⼒
知识点
使⽤ PyTorch 的数据集套件从本地加载数据的⽅法
迁移训练好的⼤型神经⽹络模型到⾃⼰模型中的⽅法
迁移学习与普通深度学习⽅法的效果区别
两种迁移学习⽅法的区别
⼆、从图⽚⽂件中加载训练数据
引⼊相关包
import torch
as nn
import torch.optim as optim
from torch.autograd import Variable
functional as F
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
resizedimport copy
import os
从硬盘⽂件夹中加载图像数据集
使⽤ datasets 的 ImageFolder ⽅法就可以实现⾃动加载以上数据
# 数据存储总路径
data_dir = 'transfer-data'
# 图像的⼤⼩为224*224
image_size = 224
# 从data_dir/train加载⽂件
# 加载的过程将会对图像⾃动作如下的图像增强操作:
# 1. 随机从原始图像中切下来⼀块224*224⼤⼩的区域
# 2. 随机⽔平翻转图像
# 3. 将图像的⾊彩数值标准化
train_dataset = datasets.ImageFolder(os.path.join(data_dir, 'train'),
transforms.Compose([
transforms.RandomResizedCrop(image_size),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
)
# 加载校验数据集,对每个加载的数据进⾏如下处理:
# 1. 放⼤到256*256像素
# 2. 从中⼼区域切割下224*224⼤⼩的图像区域
# 3. 将图像的⾊彩数值标准化
val_dataset = datasets.ImageFolder(os.path.join(data_dir, 'val'),
transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(image_size),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
)
数据集的作⽤并不是进⾏数据的读取和迭代,所以下⾯要为每个数据集创建数据加载器。
# 创建相应的数据加载器
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size = 4, shuffle = True, num_workers=4)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size = 4, shuffle = True, num_workers=4)
# 读取得出数据中的分类类别数
# 如果只有蜜蜂和蚂蚁,那么是2
num_classes = len(train_dataset.classes)
num_classes
关于 GPU 运算
# 检测本机器是否安装GPU,将检测结果记录在布尔变量use_cuda中
use_cuda = torch.cuda.is_available()
# 当可⽤GPU的时候,将新建⽴的张量⾃动加载到GPU中
dtype = torch.cuda.FloatTensor if use_cuda else torch.FloatTensor
itype = torch.cuda.LongTensor if use_cuda else torch.LongTensor
查看并绘制数据集中的图⽚
def imshow(inp, title=None):
# 将⼀张图打印显⽰出来,inp为⼀个张量,title为显⽰在图像上的⽂字
# ⼀般的张量格式为:channels * image_width * image_height
# ⽽⼀般的图像为 image_width * image_height * channels
# 所以,需要将张量中的 channels 转换到最后⼀个维度
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
inp = np.clip(inp, 0, 1)
#将图像绘制出来
plt.imshow(inp)
if title is not None:
plt.title(title)
plt.pause(0.001) # 暂停⼀会是为了能够将图像显⽰出来。
将训练数据集的第⼀个 batch 绘制出来
#获取第⼀个图像batch和标签
images, labels = next(iter(train_loader))
# 将这个batch中的图像制成表格绘制出来
out = torchvision.utils.make_grid(images)
imshow(out, title=[train_dataset.classes[x] for x in labels])
三、参照:训练⼀个普通的卷积神经⽹络
判断蚂蚁还是蜜蜂,这是个简单任务吗?
下⾯开始搭建⼀个简单的卷积神经⽹络模型
# ⽤于⼿写数字识别的卷积神经⽹络
depth = [4, 8]
class ConvNet(nn.Module):
def __init__(self):
super(ConvNet, self).__init__()
#输⼊通道为1,输出通道为4,窗⼝⼤⼩为5,padding为2
#⼀个窗⼝为2*2的pooling运算
self.pool = nn.MaxPool2d(2, 2)
#第⼆层卷积,输⼊通道为depth[0], 输出通道为depth[1],窗⼝wei15,padding为2
#⼀个线性连接层,输⼊尺⼨为最后⼀层⽴⽅体的平铺,输出层512个节点
self.fc1 = nn.Linear(image_size // 4 * image_size // 4 * depth[1] , 512)
#最后⼀层线性分类单元,输⼊为
self.fc2 = nn.Linear(512, num_classes)
def forward(self, x):
#神经⽹络完成⼀步前馈运算的过程,从输⼊到输出
x = F.v1(x))
x = self.pool(x)
x = F.v2(x))
x = self.pool(x)
# 将⽴体的Tensor全部转换成⼀维的Tensor。两次pooling操作,所以图像维度减少了1/4 x = x.view(-1, image_size // 4 * image_size // 4 * depth[1])
x = F.relu(self.fc1(x)) #全链接,激活函数
x = F.dropout(x, aining) #以默认为0.5的概率对这⼀层进⾏dropout操作 x = self.fc2(x) #全链接,激活函数
x = F.log_softmax(x, dim=1) #log_softmax可以理解为概率对数值
return x
准备训练
batch_size ⾏ num_classes 列的矩阵,labels 是数据中的正确答案
def rightness(predictions, labels):
# 对于任意⼀⾏(⼀个样本)的输出值的第1个维度,求最⼤,得到每⼀⾏的最⼤元素的下标 pred = torch.max(predictions.data, 1)[1]
# 将下标与labels中包含的类别进⾏⽐较,并累计得到⽐较正确的数量
rights = pred.eq(labels.data.view_as(pred)).sum()
# 返回正确的数量和这⼀次⼀共⽐较了多少元素
return rights, len(labels)
实例化模型,定义损失函数,优化器
# 加载⽹络
net = ConvNet()
# 如果有GPU就把⽹络加载到GPU中
net = net.cuda() if use_cuda else net
criterion = nn.CrossEntropyLoss() #Loss函数的定义
optimizer = optim.SGD(net.parameters(), lr = 0.0001, momentum=0.9)
把训练模型和验证模型的语句封装成函数
# 参数:
# data : Variable
# target: Variable
def train_model(data, target):
# 给⽹络模型做标记,标志说模型正在训练集上训练
# 这种区分主要是为了打开 net 的 training 标志
# 从⽽决定是否运⾏ dropout 与 batchNorm
output = net(data) # 神经⽹络完成⼀次前馈的计算过程,得到预测输出output
loss = criterion(output, target) # 将output与标签target⽐较,计算误差
<_grad() # 清空梯度
loss.backward() # 反向传播
optimizer.step() # ⼀步随机梯度下降算法
# 计算准确率所需数值,返回数值为(正确样例数,总样本数)
right = rightness(output, target)
# 如果计算在 GPU 中,打印的数据再加载到CPU中
loss = loss.cpu() if use_cuda else loss
return right, loss
下⾯是验证模型的⽅法
# Evaluation Mode
def evaluation_model():
# net.eval() 给⽹络模型做标记,标志说模型现在是验证模式
# 此⽅法将模型 net 的 training 标志设置为 False
# 模型中将不会运⾏ dropout 与 batchNorm
net.eval()
vals = []
#对测试数据集进⾏循环
for data, target in val_loader:
data, target = Variable(data, requires_grad=True), Variable(target)
# 如果GPU可⽤,就把数据加载到GPU中
if use_cuda:
data, target = data.cuda(), target.cuda()
output = net(data) #将特征数据喂⼊⽹络,得到分类的输出
val = rightness(output, target) #获得正确样本数以及总样本数
vals.append(val) #记录结果
return vals
开始训练模型
record = [] #记录准确率等数值的容器
#开始训练循环
num_epochs = 20
best_model = net
best_r = 0.0
for epoch in range(num_epochs):
#optimizer = exp_lr_scheduler(optimizer, epoch)
train_rights = [] #记录训练数据集准确率的容器
train_losses = []
#针对容器中的每⼀个批进⾏循环
for batch_idx, (data, target) in enumerate(train_loader):
#将Tensor转化为Variable,data为图像,target为标签
data, target = Variable(data), Variable(target)
# 如果有GPU就把数据加载到GPU上
if use_cuda:
data, target = data.cuda(), target.cuda()
# 调⽤训练函数
right, loss = train_model(data, target)
train_rights.append(right) #将计算结果装到列表容器中
train_losses.append(loss.data.numpy())
# train_r 为⼀个⼆元组,分别记录训练集中分类正确的数量和该集合中总的样本数
train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
#在测试集上分批运⾏,并计算总的正确率
vals = evaluation_model()
#计算准确率
val_r = (sum([tup[0] for tup in vals]), sum([tup[1] for tup in vals]))
val_ratio = 1.0*val_r[0].numpy()/val_r[1]
if val_ratio > best_r:
best_r = val_ratio
best_model = copy.deepcopy(net)
#打印准确率等数值,其中正确率为本训练周期Epoch开始后到⽬前撮的正确率的平均值
print('训练周期: {} \tLoss: {:.6f}\t训练正确率: {:.2f}%, 校验正确率: {:.2f}%'.format(
epoch, np.mean(train_losses), 100. * train_r[0].numpy() / train_r[1], 100. * val_r[0].numpy()/val_r[1]))
record.append([np.mean(train_losses), 1. * train_r[0].data.numpy() / train_r[1], 1. * val_r[0].data.numpy() / val_r[1]])
训练效果展⽰
#在测试集上分批运⾏,并计算总的正确率
net.eval() #标志模型当前为运⾏阶段
test_loss = 0
correct = 0
vals = []
#对测试数据集进⾏循环
for data, target in val_loader:
data, target = Variable(data, requires_grad=True), Variable(target)
if use_cuda:
data, target = data.cuda(), target.cuda()
output = net(data) #将特征数据喂⼊⽹络,得到分类的输出
val = rightness(output, target) #获得正确样本数以及总样本数
vals.append(val) #记录结果
#计算准确率
rights = (sum([tup[0] for tup in vals]), sum([tup[1] for tup in vals]))
right_rate = 1.0 * rights[0].data.numpy() / rights[1]
right_rate
事实表明,这种简单结构的卷积神经⽹络并不能将蚂蚁和蜜蜂这种复杂的图⽚分类正确,正确率勉强能达到 50% 上下,和瞎猜差别不⼤。为什么模型预测的效果那么差?究其原因,是在于:
1、蚂蚁和蜜蜂的图像数据极其复杂,⼈类⾁眼都不太容易⼀下⼦区分,因此简单的 CNN ⽆法应付这个分类任务
2、整个训练数据集仅仅有 244 个训练样本,这么⼩的数据量是⽆法训练⼤的卷积神经⽹络的
观察模型的训练误差曲线。
# 绘制误差率曲线
x = [x[0] for x in record]
y = [1 - x[1] for x in record]
z = [1 - x[2] for x in record]
#plt.plot(x)
plt.figure(figsize = (10, 7))
plt.plot(y)
plt.plot(z)
plt.xlabel('Epoch')
plt.ylabel('Error Rate')
四、加载已训练好的 ResNet 进⾏迁移学习
加载已训练的⼤型神经⽹络 ResNet
下⾯将加载 ResNet 模型,并观察模型的组成部分。
如果是第⼀次运⾏,那么模型会被下载到~/.torch/models/⽂件夹中。
del_zoo.load_url('labfile.oss.aliyuncs/courses/1073/resnet18-5c106cde.pth')
# 加载模型库中的residual network,并设置pretrained为true,这样便可加载相应的权重
net = snet18(pretrained=True)
#如果存在GPU,就将⽹络加载到GPU上
net = net.cuda() if use_cuda else net
# 将⽹络的架构打印出来
net
构建迁移⽹络
下⾯把 ResNet18 中的卷积模块作为特征提取层迁移过来,⽤于提取局部特征。
同时,将 ResNet18 中最后的全连接层(fc)替换,构建⼀个包含 512 个隐含节点的全连接层,后接两个结点的输出层,⽤于最后的分类输出。最终构建⼀个 20 层的深度⽹络。
# 读取最后线性层的输⼊单元数,这是前⾯各层卷积提取到的特征数量
num_ftrs = net.fc.in_features
# 重新定义⼀个全新的线性层,它的输出为2,原本是1000
net.fc = nn.Linear(num_ftrs, 2)
#如果存在GPU则将⽹络加载到GPU中
net.fc = net.fc.cuda() if use_cuda else net.fc
criterion = nn.CrossEntropyLoss() #Loss函数的定义
# 将⽹络的所有参数放⼊优化器中
optimizer = optim.SGD(net.parameters(), lr = 0.0001, momentum=0.9)
迁移学习的两种模式
否要更新这些旧模块的权重参数完全取决于我们采取的迁移学习⽅式,它主要包括有两种:
预训练模式和固定值模式。
预训练模式
在这种模式下,从 ResNet 迁移过来的权重视作新⽹络的初始权重,但是在训练的过程中则会被梯度下降算法改变数值。
record = [] #记录准确率等数值的容器
#开始训练循环
num_epochs = 20
best_model = net
best_r = 0.0
for epoch in range(num_epochs):
#optimizer = exp_lr_scheduler(optimizer, epoch)
train_rights = [] #记录训练数据集准确率的容器
train_losses = []
#针对容器中的每⼀个批进⾏循环
for batch_idx, (data, target) in enumerate(train_loader):
#将Tensor转化为Variable,data为图像,target为标签
data, target = Variable(data), Variable(target)
#如果存在GPU则将变量加载到GPU中
if use_cuda:
data, target = data.cuda(), target.cuda()
output = net(data) #完成⼀次预测
loss = criterion(output, target) #计算误差
<_grad() #清空梯度
loss.backward() #反向传播
optimizer.step() #⼀步随机梯度下降
#计算准确率所需数值,返回正确的数值为(正确样例数,总样本数)
right = rightness(output, target)
train_rights.append(right) #将计算结果装到列表容器中
loss = loss.cpu() if use_cuda else loss
train_losses.append(loss.data.numpy())
#if batch_idx % 20 == 0: #每间隔100个batch执⾏⼀次
#train_r为⼀个⼆元组,分别记录训练集中分类正确的数量和该集合中总的样本数
train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
#在测试集上分批运⾏,并计算总的正确率
net.eval() #标志模型当前为运⾏阶段
test_loss = 0
correct = 0
vals = []
#对测试数据集进⾏循环
for data, target in val_loader:
#如果存在GPU则将变量加载到GPU中
if use_cuda:
data, target = data.cuda(), target.cuda()
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论