本专栏用于记录关于深度学习的笔记,不光方便自己复习与查阅,同时也希望能给您解决一些关于深度学习的相关问题,并提供一些微不足道的人工神经网络模型设计思路。
专栏地址:「深度学习一遍过」必修篇
目录
1 迁移学习如何提升模型性能
我们知道,神经网络需要用数据来训练,它从数据中获得信息,进而把它们转换成相应的权重。这些权重能够被提取出来,迁移到其他的神经网络中,我们“迁移”了这些学来的特征,就不需要从零开始训练一个神经网络了 。
迁移学习就像站在巨人的肩膀上,借助已有的高性能模型训练自己的算法,它可以为你节省大量功夫,使得自己模型性能快速飙升。
2 代码详解(以 resnet18 与训练模型为例)
2.1 加载 ResNet18
pretrain_model = resnet18(pretrained=False) # 加载ResNet
print(pretrain_model)
这里可以看到,resnet18 有 512 个输入和 1000 个输出。而在我们的数据集中只有 5 种类别,显然 1000 个输出太多了,不符合我们的要求,那么接下来要做的就是修改模型结构。
2.2 修改模型结构
num_ftrs = pretrain_model.fc.in_features # 获取全连接层的输入
pretrain_model.fc = nn.Linear(num_ftrs, 5) # 全连接层改为不同的输出
print(pretrain_model)
这时输出模型可以看到 1000 类别的输出已经改为了 5 个类别输出。这就说明模型框架已经改好了。
下一步加载预先训练好的模型。
避免报 “超时” 错误,提前下载好 resnet18 的与训练模型到本地;
下载地址: https://download.pytorch.org/models/resnet18-5c106cde.pth
pretrained_dict = torch.load('./resnet18_pretrain.pth')
print(pretrained_dict)
这些权重就是加载进来的一些数据;当然,我们还可以看到都是一些全连接层。
事实上,我们要做的就是实现一个 y = kx + b 的操作,其中:
- x:图片对应输入
- y:整个模型输出
- k:权重
- b:偏置
2.3 修改 resnet18 的最后一层
接下来首先弹出 fc 层参数,为后面修改 fc 层权重做准备:
pretrained_dict.pop('fc.weight')
pretrained_dict.pop('fc.bias')
print(pretrained_dict)
这样我们就可以看到已经去掉了前面未弹出 fc 层时显示的全连接层。
model_dict = pretrain_model.state_dict()
print(model_dict)
我们可以看到,这是自己的模型参数变量,在开始时里面参数处于初始状态,所以很多 0 和 1。
稍前部分提到:输出 1000 类要变为 5 类,模型权重也是需要修改的。
下面就是要去除一些不需要的参数:
{k: v for k, v in pretrained_dict.items() if k in model_dict}
然后对模型参数列表进行参数更新,并加载参数:
model_dict.update(pretrained_dict)
print(model_dict)
至此,就拿到一个预训练好的模型了。
这时加载了除了修改后的 fc 层以外的预训练参数,于是现在只需单独训练最后这一层即可。
单独训练 fc 层需要把前面层固定下来,也就是冻结掉前面层
2.4 冻结部分层
将满足条件的参数的 requires_grad 属性设置为 False。
for name, value in pretrain_model.named_parameters():
if (name != 'fc.weight') and (name != 'fc.bias'):
value.requires_grad = False
requires_grad 为 true 进行更新,为 False 时权重和偏置不进行更新。
将模型中属性 requires_grad = True 的参数选出来(要更新的参数在 parms_conv 当中)
filter(lambda p: p.requires_grad, pretrain_model.parameters())
定义损失函数(分类常用交叉熵),计算相差多少
loss_fn = nn.CrossEntropyLoss()
控制优化器只更新需要更新的层,这里使用随机梯度下降优化器
optimizer = torch.optim.SGD(params_conv, lr=1e-3) # lr:初始学习率
3 总观整体代码
3.1 Create Dateset
'''
生成训练集和测试集,保存在txt文件中
'''
# 相当于模型的输入。后面做数据加载器dataload的时候从里面读他的数据
import os
import random#打乱数据用的
# 百分之60用来当训练集
train_ratio = 0.6
# 用来当测试集
test_ratio = 1-train_ratio
rootdata = r"data"#数据的根目录
train_list, test_list = [],[]#读取里面每一类的类别
data_list = []
#生产train.txt和test.txt
class_flag = -1
for a,b,c in os.walk(rootdata):
print(a)
for i in range(len(c)):
data_list.append(os.path.join(a,c[i]))
for i in range(0,int(len(c)*train_ratio)):
train_data = os.path.join(a, c[i])+'\t'+str(class_flag)+'\n'
train_list.append(train_data)
for i in range(int(len(c) * train_ratio),len(c)):
test_data = os.path.join(a, c[i]) + '\t' + str(class_flag)+'\n'
test_list.append(test_data)
class_flag += 1
print(train_list)
random.shuffle(train_list)#打乱次序
random.shuffle(test_list)
with open('train.txt','w',encoding='UTF-8') as f:
for train_img in train_list:
f.write(str(train_img))
with open('test.txt','w',encoding='UTF-8') as f:
for test_img in test_list:
f.write(test_img)
3.2 加载预训练模型
'''
加载预训练模型,冻结层
'''
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData
from torchvision.models import resnet18
# 定义训练函数,需要
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
# 从数据加载器中读取batch(一次读取多少张,即批次数),X(图片数据),y(图片真实标签)。
for batch, (X, y) in enumerate(dataloader):
# 将数据存到显卡
X, y = X.cuda(), y.cuda()
# 得到预测的结果pred
pred = model(X)
# 计算预测的误差
loss = loss_fn(pred, y)
# 反向传播,更新模型参数
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每训练100次,输出一次当前信息
if batch % 10 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test(dataloader, model):
size = len(dataloader.dataset)
print("size = ", size)
# 将模型转为验证模式
model.eval()
# 初始化test_loss 和 correct, 用来统计每次的误差
test_loss, correct = 0, 0
# 测试时模型参数不用更新,所以no_gard()
# 非训练, 推理期用到
with torch.no_grad():
# 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
for X, y in dataloader:
# 将数据转到GPU
X, y = X.cuda(), y.cuda()
# 将图片传入到模型当中就,得到预测的值pred
pred = model(X)
# 计算预测值pred和真实值y的差距
test_loss += loss_fn(pred, y).item()
# 统计预测正确的个数
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= size
correct /= size
print("correct = ", correct)
print(f"Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
if __name__ == '__main__':
batch_size = 8
# 给训练集和测试集分别创建一个数据集加载器
train_data = LoadData("train.txt", True)
valid_data = LoadData("test.txt", False)
train_dataloader = DataLoader(dataset=train_data, num_workers=4, pin_memory=True, batch_size=batch_size, shuffle=True)
test_dataloader = DataLoader(dataset=valid_data, num_workers=4, pin_memory=True, batch_size=batch_size)
# 如果显卡可用,则用显卡进行训练
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
'''
修改ResNet18模型的最后一层
'''
pretrain_model = resnet18(pretrained=False) # 加载ResNet
num_ftrs = pretrain_model.fc.in_features # 获取全连接层的输入
pretrain_model.fc = nn.Linear(num_ftrs, 5) # 全连接层改为不同的输出
pretrained_dict = torch.load('./resnet18_pretrain.pth')
# 弹出fc层的参数
pretrained_dict.pop('fc.weight')
pretrained_dict.pop('fc.bias')
# 自己的模型参数变量,在开始时里面参数处于初始状态,所以很多0和1
model_dict = pretrain_model.state_dict()
# 去除一些不需要的参数
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
# 模型参数列表进行参数更新,加载参数
model_dict.update(pretrained_dict)
# 改进过的预训练模型结构,加载刚刚的模型参数列表
pretrain_model.load_state_dict(model_dict)
'''
冻结部分层
'''
# 将满足条件的参数的 requires_grad 属性设置为False
for name, value in pretrain_model.named_parameters():
if (name != 'fc.weight') and (name != 'fc.bias'):
value.requires_grad = False
# filter 函数将模型中属性 requires_grad = True 的参数选出来
params_conv = filter(lambda p: p.requires_grad, pretrain_model.parameters()) # 要更新的参数在parms_conv当中
model = pretrain_model.to(device)
# # 定义损失函数,计算相差多少,交叉熵,
loss_fn = nn.CrossEntropyLoss()
''' 控制优化器只更新需要更新的层 '''
optimizer = torch.optim.SGD(params_conv, lr=1e-3) # 初始学习率
# 一共训练5次
epochs = 5
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model)
print("Done!")
# 保存训练好的模型
torch.save(model.state_dict(), "model_resnet18.pth")
print("Saved PyTorch Model Success!")
很明显可以看出,使用预训练模型,正确率更高!
补充:修改网络输出类别数
若想在 网络结构中修改最后一层输出由 变为 ,有两种方法:
- 添加一层:设置其输入通道数为 ,输出通道数为
- 修改原有网络:将最后一层输出由 变为
原有 网络:
import torchvision
from torch import nn
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print(vgg16_true)
Ⅰ 添加一层
① 添加到 Classifier 外部
train_data = torchvision.datasets.CIFAR10('./dataset', train=True, transform=torchvision.transforms.ToTensor(), download=True)
vgg16_true.add_module('add_Linear', nn.Linear(1000, 10))
print(vgg16_true)
② 添加到 Classifier 内部
train_data = torchvision.datasets.CIFAR10('./dataset', train=True, transform=torchvision.transforms.ToTensor(), download=True)
vgg16_true.classifier.add_module('add_Linear', nn.Linear(1000, 10))
print(vgg16_true)
Ⅱ 修改最后一层
vgg16_false.classifier[6] = nn.Linear(4096, 10)
print(vgg16_false)
欢迎大家交流评论,一起学习
希望本文能帮助您解决您在这方面遇到的问题
感谢阅读
END
版权声明:本文为CSDN博主「荣仔!最靓的仔!」的原创文章,遵循 CC 4.0 BY-SA 版权协议。
转载请在醒目位置附上原文出处链接及本声明。