ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

「深度学习一遍过」必修6:利用迁移学习快速提升模型性能

2021-08-01 13:03:20  阅读:222  来源: 互联网

标签:必修 模型 fc 学习 train dict test 迁移 model


本专栏用于记录关于深度学习的笔记,不光方便自己复习与查阅,同时也希望能给您解决一些关于深度学习的相关问题,并提供一些微不足道的人工神经网络模型设计思路。
专栏地址:「深度学习一遍过」必修篇

目录

1 迁移学习如何提升模型性能

2 代码详解(以 resnet18 与训练模型为例)

2.1 加载 ResNet18

2.2 修改模型结构

2.3 修改 resnet18 的最后一层

2.4 冻结部分层 

3 总观整体代码

3.1 Create Dateset 

3.2 加载预训练模型


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!")

很明显可以看出,使用预训练模型,正确率更高! 

欢迎大家交流评论,一起学习

希望本文能帮助您解决您在这方面遇到的问题

感谢阅读
END

版权声明:本文为CSDN博主「荣仔!最靓的仔!」的原创文章,遵循 CC 4.0 BY-SA 版权协议。
         转载请在醒目位置附上原文出处链接及本声明。

标签:必修,模型,fc,学习,train,dict,test,迁移,model
来源: https://blog.csdn.net/IT_charge/article/details/119296183

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有