「深度学习一遍过」必修6:迁移学习让你快人一步

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

目录

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

2 代码详解

2.1 加载 ResNet18

2.2 修改模型结构

2.3 修改 resnet18 的最后一层

2.4 冻结部分层 

3 总观整体代码

3.1 Create Dateset 

3.2 加载预训练模型

补充:修改网络输出类别数

Ⅰ 添加一层

① 添加到 Classifier 外部

 ② 添加到 Classifier 内部

Ⅱ 修改最后一层 


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

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

补充:修改网络输出类别数

若想在 vgg16 网络结构中修改最后一层输出由 1000 变为 10,有两种方法:

  1. 添加一层:设置其输入通道数为 1000,输出通道数为 10
  2. 修改原有网络:将最后一层输出由 1000 变为 10 

原有 vgg16 网络

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 版权协议。
         转载请在醒目位置附上原文出处链接及本声明。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

荣仔!最靓的仔!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值