基于迁移学习的花卉识别系统

本文探讨了在花卉识别领域使用深度学习迁移学习的方法,通过ResNet-18、AlexNet和VGG-16在Oxford-102数据集上的实验,对比了三种模型的性能,并展示了数据预处理和增强技术在提升模型泛化能力中的作用。
摘要由CSDN通过智能技术生成
本文以OXford102花卉为例。
1. 引言(项目背景)
随着社会科技的飞速发展,图像识别技术逐渐成为人工智能领域的研究热点 之一。在这个数字化时代,图像识别的应用无处不在,其中花卉识别作为植物学、生态学等领域的一个重要分支,逐渐引起了研究者的广泛关注。我国的花卉研究历史悠久,是世界上研究较早的国家之一。花卉是我国重要的物产资源,除美化了环境、调养身心外,它还具有药用价值,并且在医学领域为保障人们的健康起着重要作用。然而,传统的花卉识别方法通常依赖于手动设计的特征提取器,并且需要繁琐的标注和分类过程,这在大规模数据集和复杂场景下显得尤为低效和 不准确。同时,由于花卉种类繁多、姿态多变,以及环境和生长条件的影响,同一种花卉形状、颜色和大小也可能存在差异,所以传统方法的泛化性能和泛化能力都不理想。深度学习技术的崛起为花卉识别领域带来了全新的机遇。深度学习是一种能够自动学习图像中的抽象特征的机器学习方法,它在图像分类等任务上取得了显著的进展。特别是自 2012 年以来,以 AlexNet、VGG、ResNet 等为代表的经典深度学习模型,在大规模图像数据集上学习了通用的特征表示,为后续的图像识别任务提供了强大的基础。迁移学习是一种利用已有的知识和经验来提高对新数据的学习效率和准确性的技术,它在深度学习领域有着广泛的应用。通过使用迁移学习,我们可以将预训练的深度学习模型微调到花卉识别的任务上,从而减少了数据标注的工作量,提高了模型的泛化能力。
本文旨在使用迁移学习来实现花卉识别,选择了三种经典的深度学习模型:ResNet-18,AlexNet 和 VGG-16,并在两个花卉数据集上进行了实验。一个是 Oxford-102 数据集,包含 102 种不同的花卉类别,每种类别有 40-258 张图像;另一个是自制的花卉数据集,包含 51 种不同的花卉类别,每种类别有 40-120张图像。本实验对每种模型进行了微调和训练,比较了它们的性能,并使用在自制花卉数据集上训练性能最好的模型来识别花卉。
经典的花卉识别设计如图:
本文的花卉识别设计如图
数据处理(1)数据预处理:将数据转换为适合模型训练的格式的过程。
①图像尺寸调整:所有的图像都被调整为相同的尺寸(224x224 像素),这
是因为深度学习模型需要输入具有相同尺寸的图像。
②归一化:图像的像素值被归一化到[-1, 1]的范围内。这是通过减去 0.5
(均值)并除以 0.5(标准差)来实现的。归一化可以帮助模型更好地学习,因
为它确保了不同的特征具有相似的尺度。
(2)数据增强:通过对原始数据进行一些随机变换(如旋转、缩放、翻转等)
来增加数据量和多样性的技术,从而帮助模型更好地泛化。
①随机裁剪:在训练集中,图像被随机裁剪到预设的尺寸(224x224 像素)。
这是一种常见的数据增强技术,可以增加模型的泛化能力。
②随机水平翻转:图像有 50%的概率被水平翻转。这可以帮助模型学习到水
平方向的不变性。
③随机垂直翻转:图像有 50%的概率被垂直翻转。这可以帮助模型学习到垂
直方向的不变性。
④随机旋转:图像被随机旋转一个在[-40, 40]度之间的角度。这可以帮助
模型学习到旋转不变性。
⑤随机亮度调整:图像的亮度被随机调整,调整的幅度为原亮度的 20%。这
可以帮助模型学习到亮度变化的不变性
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, random_split
from torchvision import datasets, transforms, models
import copy
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score
import seaborn as sns


# Oxford102数据集
data_dir = '/content/drive/MyDrive/Colab Notebooks/train'
img_width, img_height = 224, 224
batch_size = 20
num_classes = 102

full_dataset = datasets.ImageFolder(root=data_dir)

train_size = int(0.8 * len(full_dataset))
val_size = len(full_dataset) - train_size

train_dataset, valid_dataset = random_split(full_dataset, [train_size, val_size])

# 数据增强和预处理
transform = {
    "train": transforms.Compose([
        transforms.RandomResizedCrop(img_width),
        transforms.RandomHorizontalFlip(),
        transforms.RandomVerticalFlip(),
        transforms.RandomRotation(40),
        transforms.ColorJitter(brightness=0.2),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ]),
    "val": transforms.Compose([
        transforms.Resize((img_width, img_height)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])
}

train_dataset.dataset.transform = transform["train"]
valid_dataset.dataset.transform = transform["val"]

train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)

可查看数据处理后的图片:

example_loader = DataLoader(train_dataset, batch_size=8, shuffle=True)
data_iter = iter(example_loader)
images, labels = next(data_iter)

class_names = full_dataset.classes  
plt.figure(figsize=(10, 10))
for i in range(8):
    plt.subplot(2, 4, i + 1)
    image = np.clip(np.transpose(images[i].numpy(), (1, 2, 0)), 0, 1)
    plt.imshow(image)
    plt.title("{}".format(class_names[labels[i].item()]))
    plt.axis('off')

plt.show()

迁移学习的常见工作流程如下:
①从之前训练的模型中获取层。
②冻结这些层,以避免在后续训练轮次中破坏它们包含的任何信息。
③在已冻结层的顶部添加一些新的可训练层。这些层会学习将旧特征转换为
对新数据集的预测。
⑥在新数据集上训练新层。
最后一个可选步骤是微调(Fine-Tuning),包括解冻上面获得的整个模型
(或模型的一部分),然后在新数据上以极低的学习率对该模型进行重新训练。
这种方法可以使预训练特征适应新数据,实现有意义的改进。
本文使用预训练模型 ResNet、AlexNet、VGG 作为初始模型来实现花卉识别。
为了适应新任务的类别数,对于 ResNet,用一个新的线性层代替了全连接层
model_ft.fc,输出维度为 num_classes,并添加了 nn.LogSoftmax(dim=1) 作 为激活函数。对于 AlexNet 和 VGG,用两个线性层代替了 model_ft.classifier,
同样,输出维度为 num_classes。
本文使用的是模型ResNet18,AlexNet, VGG16。
ResNet18是 ResNet 网络的一种变体,其中的 18 代表网络的深度,主要指的是带有权重的层,包括卷积层和全连接层,不包括池化层和 BN 层
AlexNet 首次使用了修正线性单元(ReLU)作为非线性激活函数,能够有效地解决梯度消失问题,提高训练效率。它在卷积层和池化层之间添加了一种归一 化操作,能够抑制邻近神经元的响应,增强神经元的较大响应,提高网络的泛化能力。它还使用了两个 GPU 进行并行计算,加快了训练速度,也使得网络能够使用更多的参数和更大的卷积核。

vgg16 总共有 16 层,13 个卷积层和 3 个全连接层,第一次经过 64 个卷积核的两次卷积后,采用一次 pooling,第二次经过两次 128 个卷积核卷积后,再采用 pooling,再重复两次三个 512 个卷积核卷积后,再 pooling,最后经过三次全连接。

使用 initialize_model 函数初始化深度学习模型,其中包括加载预训练权重并进行全连接层的修改。在模型训练过程中,选择冻结模型参数,分为训练和 验证两个阶段进行迭代。对于每个阶段,遍历训练或验证集,执行前向传播、损失计算、反向传播和优化等关键步骤,同时记录训练和验证的损失以及准确率。每个模型都经过训练,并保存训练好的模型权重。最终,通过比较验证集准确率,选择最佳模型,并保存其权重。训练过程中采用了交叉熵损失函数和 Adam 优化器来优化模型参数。最后,训练完成后,模型的最佳权重被上传到云盘以便进一步的使用。
# 迁移模型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
def requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False

def initialize_model(model_name, num_classes, use_pretrained):
    model_ft = None
    if model_name == "resnet":
        model_ft = models.resnet18(pretrained=use_pretrained)
        requires_grad(model_ft, False)
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Sequential(nn.Linear(num_ftrs, num_classes), nn.LogSoftmax(dim=1))

    elif model_name == "alexnet":
        model_ft = models.alexnet(pretrained=use_pretrained)
        requires_grad(model_ft, False)
        model_ft.classifier = nn.Sequential(
          nn.Linear(9216, 4096),
          nn.ReLU(),
          nn.Dropout(0.5),
          nn.Linear(4096, 102)
        )

    elif model_name == "vgg":
        model_ft = models.vgg16(pretrained=use_pretrained)
        requires_grad(model_ft, False)
        model_ft.classifier = nn.Sequential(
            nn.Linear(25088, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, num_classes)
        )
    return model_ft

# 训练模型
def train_model(model, train_loader, valid_loader, criterion, optimizer, num_epochs):
    model.to(device)
    val_acc = []
    train_acc = []
    train_loss = []
    valid_loss = []
    best_acc = 0.0
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)
        for i in ['train', 'valid']:
            if i == 'train':
                model.train()
            else:
                model.eval()

            running_loss = 0.0
            running_corrects = 0

            for inputs, labels in train_loader if i == 'train' else valid_loader:
                inputs, labels = inputs.to(device), labels.to(device)
                optimizer.zero_grad()#清零梯度,防止梯度累加

                with torch.set_grad_enabled(i == 'train'):#如果当前阶段为训练阶段,启用梯度计算
                    outputs = model(inputs)#前向传播
                    loss = criterion(outputs, labels)#计算损失
                    _, preds = torch.max(outputs, 1)
                    if i == 'train':#训练阶段反向传播和优化
                        loss.backward()
                        optimizer.step()

                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)
            epoch_loss = running_loss / len(train_loader.dataset) if i == 'train' else running_loss / len(valid_loader.dataset)
            epoch_acc = running_corrects.double() / len(train_loader.dataset) if i == 'train' else running_corrects.double() / len(valid_loader.dataset)
            print('Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss, epoch_acc))

            if i == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())#保存验证准确率最好的模型权重
            #各阶段记录各阶段的准确率和损失
            if i == 'valid':
                val_acc.append(epoch_acc)
                valid_loss.append(epoch_loss)

            if i == 'train':
                train_acc.append(epoch_acc)
                train_loss.append(epoch_loss)
    model.load_state_dict(best_model_wts)
    print('Best val_acc: {:4f}'.format(best_acc))
    return model, val_acc, train_acc, valid_loss, train_loss


model_names = ['resnet', 'alexnet', 'vgg']
best_models102 = {}
#  训练各模型并保存其验证准确率最高的模型的权重
for model_name in model_names:
    print(f"Training {model_name} model...")
    model_ft = initialize_model(model_name, num_classes, True)
    model_ft = model_ft.to(device)
    if model_name == "resnet":
        params = model_ft.parameters()
    else:
        params = model_ft.classifier.parameters()
    optimizer = optim.Adam(params, lr=0.00001)
    model_ft, val_acc, train_acc, valid_loss, train_loss = train_model(model_ft, train_loader,valid_loader, nn.CrossEntropyLoss(), optimizer, num_epochs=20)
    best_models102[model_name] = {
        'model': copy.deepcopy(model_ft),
        'val_acc': val_acc,
        'train_acc': train_acc,
        'valid_loss': valid_loss,
        'train_loss': train_loss
    }
    del optimizer  

# 选择最佳模型
best_model_name = max(best_models102, key=lambda k: max(best_models102[k]['val_acc']))
best_model102 = best_models102[best_model_name]['model']
torch.save(best_model102.state_dict(), 'best_model102.pth')
print(f"The best model is: {best_model_name}")
针对这三个模型,通过损失曲线、准确率曲线、精确率、召回率以及 F1 值等指标来全面评估模型的性能。损失曲线直观地展示了模型在训练和验证过程中的学习情况,帮助我们了解模型是否过拟合或欠拟合。准确率曲线则反映了模型在不同阶段的准确性,提供了对模型性能的直观认识。进一步使用精确率、召回率和 F1 值等评价指标,这些指标能够更详细地揭示模型在正类别和负类别上的性能表现。精确率强调了模型对正类别的分类准确性,召回率衡量了模型正确检测正类别的能力,而 F1 值则综合考虑了精确率和召回率,是一个综合性能指标。最终,将之前保存的在验证集上表现最好的模型应用于识别花卉。
for model_name, model_data in best_models102.items():
    model = model_data['model']
    model.to('cpu') 
    torch.save(model.state_dict(), f'{model_name}_model.pth')

    val_acc = [item.cpu() for item in model_data['val_acc']]
    train_acc = [item.cpu() for item in model_data['train_acc']]
    valid_loss = [item for item in model_data['valid_loss']]  
    train_loss = [item for item in model_data['train_loss']]  

    # 绘制准确率和损失曲线
    plt.figure(figsize=(12, 6))
    plt.subplot(1, 2, 1)
    plt.plot(train_acc, label=f'Train Accuracy - {model_name}', marker='o')
    plt.plot(val_acc, label=f'Validation Accuracy - {model_name}', marker='o')
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy')
    plt.title(f'Train and Validation Accuracy - {model_name}')
    plt.legend()

    # 绘制训练损失和验证损失曲线
    plt.subplot(1, 2, 2)
    plt.plot(train_loss, label=f'Train Loss - {model_name}', marker='o')
    plt.plot(valid_loss, label=f'Validation Loss - {model_name}', marker='o')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title(f'Train and Validation Loss - {model_name}')
    plt.legend()

    plt.tight_layout()
    plt.show()

for model_name, model_data in best_models102.items():
    model = model_data['model']
    model.to('cpu')  
    
    true_labels = []
    predicted_labels = []

    model.eval()
    with torch.no_grad():
        for inputs, labels in valid_loader:
            inputs, labels = inputs.to('cpu'), labels.to('cpu')
            outputs = model(inputs)
            _, preds = torch.max(outputs, 1)

            true_labels.extend(labels.numpy())
            predicted_labels.extend(preds.numpy())

    conf_matrix = confusion_matrix(true_labels, predicted_labels)

    # 计算精确率、召回率和 F1 值
    precision = precision_score(true_labels, predicted_labels, average='weighted')
    recall = recall_score(true_labels, predicted_labels, average='weighted')
    f1 = f1_score(true_labels, predicted_labels, average='weighted')

    # 打印结果
    print(f'Metrics for {model_name}:')
    print(f'Precision: {precision:.4f}')
    print(f'Recall: {recall:.4f}')
    print(f'F1 Score: {f1:.4f}')
    print('\n')

ResNet18 在两个数据集上都表现出了最好的性能,Precision, Recall,和 F1 Score 的数值都是最高的,说明它的分类能力和泛化能力都很强。AlexNet 和 VGG16 在两个数据集上的性能都比ResNet18 差,Precision, Recall, 和 F1 Score 的数值都较低,说明它们的分类能力和泛化能力都有待提高。
花卉测试
from PIL import Image
# 加载模型权重
best_model = initialize_model(best_model_name, num_classes, use_pretrained=False)
best_model.load_state_dict(torch.load('best_model102.pth'))

def predict_flower(image_path):
    # 读取图像并进行预处理
    img = Image.open(image_path).convert("RGB")
    transform = transforms.Compose([
        transforms.Resize((img_width, img_height)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])
    img = transform(img)
    img = img.unsqueeze(0)

    # 进行预测
    best_model.eval()
    with torch.no_grad():
        prediction = best_model(img)
        
    predicted_class = torch.argmax(prediction).item()
    class_names = train_loader.dataset.dataset.classes

    predicted_class_name = class_names[predicted_class]
    print("Predicted class name:", predicted_class_name)

image_path = '/content/drive/MyDrive/Colab Notebooks/1.jpg'
predict_flower(image_path)
最后可设计一个简单的花卉识别系统的图形用户界面。例如:
  • 26
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值