基于RenNet迁移学习的花卉识别

花卉数据集链接

概要

本文引入PyTorch框架的ResNet残差网络迁移学习来更加准确的识别102种花卉,首先获取花卉数据集:其中包含102类6552个花卉图像的训练集和102类812个花卉图像的验证集;接着对数据集进行预处理:将训练集和验证集中的不同大小,不同规格的花卉图像统一裁剪为224*224的尺寸规格,并对训练集的数据集进行数据增强;然后构建ResNet迁移学习模型以及设置各种超参数;接下来开始训练模型,并保存最优模型参数;最后,通过随机的花卉图像来测试模型的精度。实验表明,经过20轮的全连接层训练和10轮的整体模型训练,基于ResNet的迁移学习在识别花卉的类别上最优模型的准确率达到了94%,最终模型的测试准确率也为90%,证明了对基于ResNet的迁移学习模型搭建的有效性,进一步提出了随着机器算力和训练轮次的增加,模型对花卉的识别的准确度也更高的未来预期。

整体架构流程

1 导入相关库

import numpy as np
import time
from matplotlib import pyplot as plt
import json
import copy
import os
import torch
from torch import nn
from torch import optim
from torchvision import transforms, models, datasets

2 数据预处理

  • 数据增强:trochvision中transforms模块自带功能,比较实用
  • 数据预处理:trochvision中transforms也帮我们实现好了,直接调用即可
  • DataLoader模块直接读取batch数据

2.1 加载数据集

data_dir = 'D:\\PycharmProjects\\pythonProject\\flower_data'
train_dir = data_dir + '/train'  # 训练集
valid_dir = data_dir + '/valid'  # 验证集

2.2 制作数据源

# 制作数据源 因为数据集里的图片大小都不一 一般网络都需要224*244的数据输入
data_transforms = {
    'train': transforms.Compose([  # Compose()函数将所有步骤放在一起打包进行
        transforms.Resize(256),  # 首先要缩小成256*256的大小再进行裁剪
        transforms.CenterCrop(224),  # 从中心开始裁剪成224*224的图片
        # 数据增强 验证集不需要此步骤
        transforms.RandomRotation(45),  # 随机旋转,-45到45度之间随机选
        transforms.RandomHorizontalFlip(p=0.5),  # 随机水平翻转 选择一个概率概率
        transforms.RandomVerticalFlip(p=0.5),  # 随机垂直翻转
        transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),  # 参数1为亮度, 参数2为对比度,参数3为饱和度,参数4为色相
        transforms.RandomGrayscale(p=0.025),  # 概率转换成灰度率, 3通道就是R=G=B
        transforms.ToTensor(),  # 图片数据转换为tensor格式
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 标准差标准化 平衡各特征的贡献
    ]),
    'valid': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}
batch_size = 256
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'valid']}  # 制作数据集
data_loaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}  # 制作batch数据
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}  # 获取数据大小 训练集6552个 验证集818个
class_names = image_datasets['train'].classes  # 获取类别名(数字标签)
dataset_sizes

在这里插入图片描述

2.3 获取标签真实姓名

# 获取标签对应的实际名字
name_dir = data_dir + '\cat_to_name.json'  # 实际名字文件
with open(name_dir, 'r') as f:
    cat_to_name = json.load(f)
cat_to_name

在这里插入图片描述

2.4 展示数据预处理效果

def im_convert(tensor):
    """ 展示数据"""

    image = tensor.to("cpu").clone().detach()
    image = image.numpy().squeeze()
    image = image.transpose(1, 2, 0)  # 将H W C还原回去
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))  # 还原标准化,先乘再加
    image = image.clip(0, 1)

    return image
fig = plt.figure(figsize=(20,8))  # 显示图像的行与宽
columns = 5  # 每行显示图像的个数
rows = 2  # 每列显示图像的个数

dataiter = iter(data_loaders['valid'])  # iter 迭代器
inputs, classes = dataiter.next()

for idx in range(columns*rows):
    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])
    ax.set_title(cat_to_name[str(int(class_names[classes[idx]]))])
    plt.imshow(im_convert(inputs[idx]))
plt.show()

在这里插入图片描述

3 模型构建

3.1 下载resnet152模型

# 查看resnet152: 注意最后的全连接层是1000分类
model_resnet = models.resnet152()  # 这里使用的是resnet152
model_resnet

在这里插入图片描述
……
在这里插入图片描述

3.2 构建resnet12的迁移学习模型

# 设置需要训练的网络
def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False
# 初始化resnet模型 冻结卷积层 构建全连接层
def initialize_resnet_model(feature_extract, use_pretrained=True):
    model = models.resnet152(weights=models.ResNet152_Weights.DEFAULT)  # 是否要下载更准更快更强更新的预训练模型
    set_parameter_requires_grad(model, feature_extract)  # 冻结层不进行梯度更新
    num_ftrs = model.fc.in_features  # 模型全连接层的输入特征个数 2048
    model.fc = nn.Sequential(nn.Linear(num_ftrs, 102), nn.LogSoftmax(dim=1))  # 构建模型全连接层

    return model
# 训练三大件 初始化模型 初始化损失函数 初始化优化器
model_resnet = initialize_resnet_model(feature_extract=True, use_pretrained=True)  # 加载resnet模型 冻结卷积层
model_resnet

在这里插入图片描述
……
在这里插入图片描述

4 超参数设置

# 优化器设置
optimizer_ft = optim.Adam(model_resnet.parameters(), lr=1e-2)
#学习率衰减策略
scheduler_ft = optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)  # 学习率每7个epoch衰减成原来的1/10
# 初始化损失函数
criterion = nn.NLLLoss()  # nn.CrossEntropyLoss()相当于logSoftmax()和nn.NLLLoss() 最后一层构建全连接层时已经用了LogSoftmax()

5 模型训练

# 使用GPU训练
if not torch.cuda.is_available():
    print('CUDA is not available.  Training on CPU ...')
else:
    print('CUDA is available.  Training on GPU ...')
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

在这里插入图片描述

# 训练模块
def train_model(model, dataloaders, criterion, optimizer, scheduler, num_epochs, filename):
    since = time.time()  # 开始计时

    best_acc = 0  # 保存最好精度
    val_acc_history = []  # 保存验证集精度
    train_acc_history = []  # 保存训练集精度
    train_losses = []  # 保存训练集损失
    valid_losses = []  # 保存验证集损失
    best_model_wts = copy.deepcopy(model.state_dict())  # 加载预训练模型参数
    LRs = [optimizer.param_groups[0]['lr']]  # 获取学习率

    model = model.to(device)  # 模型传入GPU

    # 开始训练
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch + 1, num_epochs))
        print('-' * 10)

        # 训练和验证
        for phase in ['train', 'valid']:
            if phase == 'train':
                model.train()  # 训练
            else:
                model.eval()  # 验证

            running_loss = 0.0
            running_corrects = 0

            # 把数据都取个遍
            for inputs, labels in dataloaders[phase]:

                # 数据传入GPU
                inputs = inputs.to(device)
                labels = labels.to(device)

                optimizer.zero_grad()  # 梯度清零
                with torch.set_grad_enabled(phase == 'train'):  # 只有训练的时候计算和更新梯度

                    # 预测三大件 计算结果 计算损失 梯度下降更新权重
                    outputs = model(inputs)  # 计算预测结果
                    loss = criterion(outputs, labels)  # 计算损失
                    if phase == 'train':  # 训练阶段更新权重
                        loss.backward()
                        optimizer.step()

                    _, preds = torch.max(outputs, 1)  # 返回模型计算概率最大的值及其索引 计算精度使用

                # 计算损失
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss / len(dataloaders[phase].dataset)
            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)

            time_elapsed = time.time() - since
            print('Time elapsed {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            # 得到最好那次的模型 针对验证集计算
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())  # 拷贝模型的参数
                state = {
                    'state_dict': model.state_dict(),
                    'best_acc': best_acc,
                    'optimizer': optimizer.state_dict(),
                }
                torch.save(state, filename)  # 保存模型
            if phase == 'valid':
                val_acc_history.append(epoch_acc)
                valid_losses.append(epoch_loss)
                # scheduler.step(epoch_loss) # 注意新版的学习率更新里面并不需要参数
            if phase == 'train':
                train_acc_history.append(epoch_acc)
                train_losses.append(epoch_loss)

        print('Optimizer learning rate : {:.7f}'.format(optimizer.param_groups[0]['lr']))
        LRs.append(optimizer.param_groups[0]['lr'])
        print()
        # ppt上学习率的更新放在了这里,解决了新版的先更新优化器,在更新学习率的顺序------------------------------
        scheduler.step() 

    # 训练的总时间
    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # 训练完后用最好的一次当做模型最终的结果
    model.load_state_dict(best_model_wts)
    return model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs

5.1 训练全连接层

# 预训练
filename = 'checkpoint.pth'  # 模型保存路径
epoch_ft = 20
model_ft, val_acc_history_ft, train_acc_history_ft, valid_losses_ft, train_losses_ft, LRs_ft = train_model(
    model_resnet, data_loaders, criterion, optimizer_ft, scheduler_ft, epoch_ft, filename)

在这里插入图片描述
……
在这里插入图片描述

# 创建第一张画布
plt.figure(0)
plt.xlabel("Epoches")
plt.ylabel("Loss")
plt.plot(train_losses_ft, label = 'Train loss')
plt.plot(valid_losses_ft, 'red', label = 'Valid loss')
plt.legend(loc =0)  # 通过参数loc指定曲线说明,若在左上方:loc='upper left'
plt.savefig("./softmaxloss.png")

train_acc = []
val_acc = []
for i in train_acc_history_ft :
    train_acc.append(i.tolist())
for j in val_acc_history_ft :
    val_acc.append(j.tolist())
    
# 创建第二张画布
plt.figure(1)
plt.xlabel("Epoches")
plt.ylabel("Accuracy")
plt.plot(train_acc, label = 'Train acc')
plt.plot(val_acc, 'red', label = 'Valid acc')
plt.legend(loc =0)  # 通过参数loc指定曲线说明,若在左上方:loc='upper left'
# 保存图片
plt.savefig("./softmaxacc.png")

在这里插入图片描述
在这里插入图片描述

5.2 训练整个模型

# 再次训练 训练三大件 初始化模型 初始化损失函数 初始化优化器 此处损失函数不需要再初始化了
set_parameter_requires_grad(model_ft, False)  # 不冻结卷积层
optimizer = optim.Adam(model_ft.parameters(), lr=1e-4)  # 再继续训练所有的参数 学习率调小一点
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)
# 训练整个模型
epoch = 10
model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs = train_model(
    model_ft, data_loaders, criterion, optimizer, scheduler, epoch, filename)

在这里插入图片描述
在这里插入图片描述

# 创建第一张画布
plt.figure(0)
plt.xlabel("Epoches")
plt.ylabel("Loss")
plt.plot(train_losses, label = 'Train loss')
plt.plot(valid_losses, 'red', label = 'Valid loss')
plt.legend(loc =0)  # 通过参数loc指定曲线说明,若在左上方:loc='upper left'
plt.savefig("./Resnet152loss.png")

train_acc = []
val_acc = []
for i in train_acc_history :
    train_acc.append(i.tolist())
for j in val_acc_history :
    val_acc.append(j.tolist())
    
# 创建第二张画布
plt.figure(1)
plt.xlabel("Epoches")
plt.ylabel("Accuracy")
plt.plot(train_acc, label = 'Train acc')
plt.plot(val_acc, 'red', label = 'Valid acc')
plt.legend(loc =0)  # 通过参数loc指定曲线说明,若在左上方:loc='upper left'
# 保存图片
plt.savefig("./Resnet152acc.png")

在这里插入图片描述
在这里插入图片描述

6 模型预测

# 测试训练好的模型
model.eval()
# 加载模型
checkpoint = torch.load('checkpoint.pth')
best_acc = checkpoint['best_acc']
model.load_state_dict(checkpoint['state_dict'])
# 加载数据 用batch传入数据进行测试
dataiter = iter(data_loaders['valid'])
images, labels = dataiter.next()
# 开始测试
if torch.cuda.is_available():
    output = model(images.cuda())
else:
    output = model(images)
_, preds_tensor = torch.max(output, 1)  # 得出概率最大的值以及下标

preds = np.squeeze(preds_tensor.numpy()) if not torch.cuda.is_available() else np.squeeze(preds_tensor.cpu().numpy())
# 展示结果
fig = plt.figure(figsize=(20, 8))
columns = 5
rows = 2

for idx in range(columns*rows):
    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])
    plt.imshow(im_convert(images[idx]))
    ax.set_title("{} ({})".format(cat_to_name[str(preds[idx])], cat_to_name[str(labels[idx].item())]),
                 color=("green" if cat_to_name[str(preds[idx])] == cat_to_name[str(labels[idx].item())] else "red"))
plt.show()

在这里插入图片描述

小结

本文通过基于ResNet的迁移学习在花卉品种识别中的研究。首先,学习了ResNet残差网络的残差结构,理解了残差结构的原理,以及不同残差网络使用不同残差结构的原因。接着,学习了迁移学习的目的和工作原理,以及如何对ResNet、VGG等其他网络进行迁移学习。其次,学会了数据集的划分与多类别命名、以及数据集的预处理和批量训练。
本文在实验中对ResNet152的迁移学习实现花卉识别,经过20轮全连接层的训练,最优模型的准确率为94.25%;经过50轮整体模型的训练,最优模型的准确率为94%;最终模型的测试准确率也为90%。虽然通过模型测试证明了本次模型的搭建基本成功。

引用

大佬链接:https://blog.csdn.net/qq_51935319/article/details/126351864

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值