代码资源和数据集资源使用进阶学习(四)中的代码,大家可以配合食用哟~
pytorch进阶学习(四):使用不同分类模型进行数据训练(alexnet、resnet、vgg等)_好喜欢吃红柚子的博客-CSDN博客
数据集:花朵数据集,一共五个类。
目录
一、未使用预训练前的模型训练准确率
1. CreateDataset.py生成自己的数据集
2. TrainModal.py 训练神经网络
3. 运行结果
4. 改进方法:使用迁移学习
二、迁移学习
1. 概念
2. 目标
3. 具体步骤
3.1 查看预训练网络结构
3.2 修改fc层输出类别数
3.3 下载resnet18的预训练参数
3.4 删除预训练参数中的fc层参数
3.5 查看自己搭建的模型的框架
3.6 更新自己的模型参数
3.7 对除了fc层以外的参数进行冻结
3.8 定义损失函数和优化器,对fc层参数进行梯度更新
3.9 加载模型并进行训练
三、完整代码
'''
生成训练集和测试集,保存在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)
使用的是resnet18神经网络,不使用与训练参数,epoch=5,此时准确率会很低。
'''
加载pytorch自带的模型,从头训练自己的数据
'''
import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData
# 设置显卡型号为1
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '1'
from torchvision.models import alexnet #最简单的模型
from torchvision.models import vgg11, vgg13, vgg16, vgg19 # VGG系列
from torchvision.models import resnet18, resnet34,resnet50, resnet101, resnet152 # ResNet系列
from torchvision.models import inception_v3 # Inception 系列
# 定义训练函数,需要
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)
# 计算预测的误差
# print(pred,y)
loss = loss_fn(pred, y)
# 反向传播,更新模型参数
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每训练10次,输出一次当前信息
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)
# 将模型转为验证模式
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(f"correct = {correct}, 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")
'''
随着模型的加深,需要训练的模型参数量增加,相同的训练次数下模型训练准确率起来得更慢
'''
model = resnet18(weights=False, num_classes=5).to(device) # 43.6%
print(model)
# 定义损失函数,计算相差多少,交叉熵,
loss_fn = nn.CrossEntropyLoss()
# 定义优化器,用来训练时候优化模型参数,随机梯度下降法
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3) # 初始学习率
# 一共训练1次
epochs = 5
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
time_start = time.time()
train(train_dataloader, model, loss_fn, optimizer)
time_end = time.time()
print(f"train time: {(time_end-time_start)}")
test(test_dataloader, model)
print("Done!")
# 保存训练好的模型
torch.save(model.state_dict(), "model_resnet18.pth")
print("Saved PyTorch Model Success!")
给出5个epoch的准确率,未使用预训练模型进行训练时,5个epoch训练后的准确率为57%。
Epoch 1
-------------------------------
train time: 21.131392002105713
correct = 0.48411322934719814, Test Error:
Accuracy: 48.4%, Avg loss: 0.163418Epoch 2
-------------------------------
train time: 17.17208242416382
correct = 0.4927787406123628, Test Error:
Accuracy: 49.3%, Avg loss: 0.150533Epoch 3
-------------------------------
train time: 17.276950359344482
correct = 0.5343731946851531, Test Error:
Accuracy: 53.4%, Avg loss: 0.138617Epoch 4
-------------------------------
train time: 16.926883220672607
correct = 0.5557481224725592, Test Error:
Accuracy: 55.6%, Avg loss: 0.135120Epoch 5
-------------------------------
train time: 17.293556213378906
correct = 0.5678798382437897, Test Error:
Accuracy: 56.8%, Avg loss: 0.136653Done!
Saved PyTorch Model Success!
若想要尽快达到一个较高的准确率,则需要使用已经网上已经训练好的预训练模型参数,但是预训练模型训练时使用的是类别数为1000的dataset,因此最后一层全连接层的输出类别为1000,而我们这里数据集类别数为5,所以需要修改全连接层类别个数,把模型改成我们需要的框架,这就是迁移学习。
迁移学习(Transfer)_Sonhhxg_柒的博客-CSDN博客
迁移学习——冻结部分参数,修改全连接层_迁移学习冻结_冲冲冲鸭鸭鸭~的博客-CSDN博客
Pytorch 加载部分预训练模型并冻结某些层_预训练模型冻结批归一化_csdn_1HAO的博客-CSDN博客
迁移学习(Transfer Learning)是一种机器学习方法,就是把为任务 A 开发的模型作为初始点,重新使用在为任务 B 开发模型的过程中。
我们这里主要使用的是基于模型的迁移 (Parameter based TL):利用源域和目标域的参数共享模型。指从源域和目标域中找到他们之间共享的参数信息,以实现迁移的方法。这种迁移方式要求的假设条件是: 源域中的数据与目标域中的数据可以共享一些模型的参数。
我们使用预训练模型的训练参数,但是需要把原来全连接(fc)层输出的类别1000修改为5,我们在这个例子的迁移学习主要涉及到两部分的修改:
- 神经网络框架的修改:把resnet18神经网络的fc层的输出从1000 -> 5
- 预训练参数权重的修改:把fc层的1000个权重删除,替换成新训练的5个参数
我们对未修改框架前的resnet18网络起名为pretrain_model,对其进行打印,可以看到fc层输出类别数是1000。
pretrain_model = resnet18(weights=False) # 加载ResNet
print(pretrain_model)
num_ftrs = pretrain_model.fc.in_features # 获取全连接层的输入
pretrain_model.fc = nn.Linear(num_ftrs, 5) # 全连接层改为不同的输出,5
print(pretrain_model)
网址为:url="https://download.pytorch.org/models/resnet18-f37072fd.pth"
# 预先训练好的参数, 'https://download.pytorch.org/models/resnet18-5c106cde.pth'
pretrained_dict = torch.load('./resnet18_pretrain.pth')
# pretrained_dict = torch.load('./resnet34_pretrain.pth')
print(pretrained_dict)
使用pop弹出不需要的参数,打印查看。
# # 弹出fc层的参数
pretrained_dict.pop('fc.weight')
pretrained_dict.pop('fc.bias')
print(pretrained_dict)
可以看到参数中已经没有了fc层的参数,这就是我们一会要应用给我们自己搭建的神经网络pretrain_model的参数。
我们自己的pretrain_model已经完成了fc输出类别的修改,但是由于还没有加载上面的预训练参数,因此此时我们自己的神经网络的参数处于初始状态,很多0和1,我们可以打印看一下。
# 自己的模型参数变量,在开始时里面参数处于初始状态,所以很多0和1
model_dict = pretrain_model.state_dict()
print(model_dict)
此时已经完成的工作有:
- model_dict是pretrain_model的初始化参数,我们在前面以及把他最后的全连接层的类别数量修改成了5,因此fc层的参数也是5个;
- pretrained_dict是我们下载好的resnet的预训练参数,我们已经删除了最后fc层的权重。
现在,我们需要将预训练模型中与当前模型形状相同的参数提取出来,存储在pretrained_dict字典中。其中,k表示参数名,v表示参数值。
# # 去除一些不需要的参数,加载预训练参数
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)
print(pretrain_model)
在做完这个操作后,除了最后fc层的参数还没有更新,其他之前层的参数就都被替换成了预训练模型中的参数。
我们需要单独训练最后fc层的参数。
需要把前面已经加载好了但是现在用不到的其他层数中的参数给冻结。
冻结方法:如果参数名不是fc.weights和fc.bias,就把requires_grad设置为false,requires_grad的意思即为是否需要参数更新,如果为true,则进行更新。把除了fc层的其他所有参数进行冻结。
# 将满足条件的参数的 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 的参数选出来,然后使用随机梯度下降算法SDG对fc层参数进行梯度更新,控制优化器只更新需要更新的层 。
# filter 函数将模型中属性 requires_grad = True 的参数选出来
params_conv = filter(lambda p: p.requires_grad, pretrain_model.parameters()) # 要更新的参数在parms_conv当中
# 定义损失函数,计算相差多少,交叉熵,
loss_fn = nn.CrossEntropyLoss()
# 对参数进行更新
optimizer = torch.optim.SGD(params_conv, lr=1e-3) # 初始学习率
epoch=5,训练结果如下所示。
model = pretrain_model.to(device)
# 一共训练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_resnet34.pth")
print("Saved PyTorch Model Success!")
可以看到epoch=5时的准确率达到了82.4%,比较高,达到了我们的要求。
'''
加载pytorch自带的模型,从头训练自己的数据
'''
import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData
# 设置显卡型号为1
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '1'
from torchvision.models import alexnet #最简单的模型
from torchvision.models import vgg11, vgg13, vgg16, vgg19 # VGG系列
from torchvision.models import resnet18, resnet34,resnet50, resnet101, resnet152 # ResNet系列
from torchvision.models import inception_v3 # Inception 系列
# 定义训练函数,需要
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)
# 计算预测的误差
# print(pred,y)
loss = loss_fn(pred, y)
# 反向传播,更新模型参数
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每训练10次,输出一次当前信息
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)
# 将模型转为验证模式
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(f"correct = {correct}, 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")
''' ResNet系列 '''
pretrain_model = resnet18(weights=False) # 43.6%
num_ftrs = pretrain_model.fc.in_features # 获取全连接层的输入
pretrain_model.fc = nn.Linear(num_ftrs, 5) # 全连接层改为不同的输出,5
# print(pretrain_model)
# 加载预训练模型
pretrained_dict = torch.load('./resnet18_pretrain.pth')
# 对模型进行输出
# print(pretrained_dict)
# # 删除预训练模型跟当前模型层名称相同,层结构却不同的元素;,弹出fc层的参数
pretrained_dict.pop('fc.weight')
pretrained_dict.pop('fc.bias')
# print(pretrained_dict)
# 自己的模型参数变量,在开始时里面参数处于初始状态,所以很多0和1
model_dict = pretrain_model.state_dict()
# print(model_dict)
# 将预训练模型中与当前模型形状相同的参数提取出来,存储在pretrained_dict字典中。其中,k表示参数名,v表示参数值
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)
# print(pretrain_model)
'''
冻结部分层
'''
# 将满足条件的参数的 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当中
# 定义损失函数,计算相差多少,交叉熵,
loss_fn = nn.CrossEntropyLoss()
# 对参数进行更新
optimizer = torch.optim.SGD(params_conv, lr=1e-3) # 初始学习率
model = pretrain_model.to(device)
# 一共训练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_resnet34.pth")
print("Saved PyTorch Model Success!")