ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于CNN的花朵品种识别:用迁移学习与数据增强搞定细粒度分类

基于CNN的花朵品种识别:用迁移学习与数据增强搞定细粒度分类 简介这是一份基于卷积神经网络CNN实现花卉品种自动识别的学术论文PDF适合深度学习、图像识别领域的研究者、学生及竞赛与毕业设计开发者使用。资源围绕图像识别中的细粒度分类难点提出使用CNN模型自动提取特征并分类结合BP算法优化参数以Relu激活函数提升稀疏性在牛津大学102种花卉数据集基础上额外增加5类样本最终取得83.01%的准确率随机抽取5种花卉分类时最高可达85%。PDF全文内容涵盖数据来源与预处理、CNN与BP算法原理、模型构建及实验分析等完整环节可直接用于复现实验或作为项目论文范文。压缩包内仅含该PDF文件大小5.59MB文档结构清晰前后衔接完整。目前已有239人学习浏览适合需要快速掌握CNN花卉识别方法、撰写课程报告或开展相关课题研究的读者下载参考。1. 卷积神经网络做花朵品种识别为什么值得自己跑一遍我是从手机里那个“一拍识花”功能反推来学这块的。基于卷积神经网络的花朵品种识别本质是一个细粒度图像分类任务把月季、蔷薇、牡丹这些长得像、又不像的品种按类别分开。和 MNIST 那种“数字分得清清楚楚”不同花朵识别的难点在于类间差异小、类内差异大——同一株花换个角度拍差别可能比两个品种还大。这套方案能落地到植物科普 App、农业植保、花田质检这些场景适合刚啃完分类原理、想在真实数据集上验证 CNN 能力的工程师。2. 准备花朵数据集类别分布比网络结构更先决定成败2.1 常见公开数据集怎么选Oxford Flower 102 和自采数据的取舍花朵识别最常用的公开基准是 Oxford Flower 102102 个类别每类 40 到 258 张图像不等总量在八千多张。还有个简化版 Oxford Flower 17类别少、混淆也少适合先验证代码。对刚入手的人来说我推荐直接跑 102 类版本——细粒度混淆的情况足够丰富正好能暴露 CNN 在这种任务上的真实短板。选数据集的标准不是“大”而是“和目标场景同分布”。如果要做的是农田里的植保识别训练数据里那些背景干净、花朵居中的图和真实场景里杂草乱入、逆光拍摄的照片差距很大。常见做法是先用公开数据集跑通整个流程再按目标场景标注一批自采数据做微调。自采数据最大的坑在于同一个品种在不同生长阶段、不同光照下差异极大采集时要刻意覆盖多角度、多天气、多背景而不是找一个晴天拍一版就完事。对比维度Oxford Flower 102自采数据类别数102自定义每类样本量40~258 张建议起步 100 张背景复杂度相对干净贴合真实场景标注成本无需要人工核对适合阶段跑通流程、调参数落地上线、领域适配2.2 用 ImageFolder 组织数据最小可跑的目录结构与代码PyTorch 的 torchvision.datasets.ImageFolder 是图像分类最省事的加载方式。目录结构只要按“类别名当文件夹名”组织好它会自动把文件夹名解析成标签索引。以 Oxford Flower 102 为例下载解压后先按比例划分训练和验证集目录长这样data/ ├── train/ │ ├── 01_blue_sun/ │ │ ├── 001.jpg │ │ └── 002.jpg │ ├── 02_california_poppy/ │ └── ... └── val/ ├── 01_blue_sun/ │ └── 010.jpg └── ...from torchvision import datasets, transforms transform_base transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset datasets.ImageFolder(data/train, transformtransform_base) val_dataset datasets.ImageFolder(data/val, transformtransform_base) print(train_dataset.classes) # 类别名列表 print(train_dataset.class_to_idx) # 类别名到索引的映射segments 逻辑很简单ImageFolder 扫描 data/train 下所有子目录每个子目录名是一个类别目录里的图片是样本返回 (图片张量, 标签索引) 对。Resize((224, 224)) 统一输入尺寸ToTensor() 把 HWC 的 0~255 图像转成 CHW 的 0~1 张量Normalize 再用 ImageNet 统计的 mean/std 做标准化。两个易错参数值得注意。第一Resize 默认双线性插值对花瓣边缘不算敏感但后面要做 Grad-CAM 可视化的话建议改成 Resize CenterCrop 的组合避免拉伸畸变影响热力图判断。第二Normalize 的 mean/std 一般沿用 ImageNet 默认值只有你用完全不同的数据域从头预训练时才需要重算。数据划分时还有个大坑原始 Oxford 数据集的图片排列可能和类别顺序相关直接全局随机 split 会让同一株花的不同视角同时出现在训练和验证里指标虚高。我习惯在每个类别文件夹内部按固定种子划分import os import random import shutil random.seed(42) for cls in os.listdir(data/origin): images os.listdir(fdata/origin/{cls}) random.shuffle(images) split int(len(images) * 0.8) for img in images[:split]: shutil.copy(fdata/origin/{cls}/{img}, fdata/train/{cls}/{img}) for img in images[split:]: shutil.copy(fdata/origin/{cls}/{img}, fdata/val/{cls}/{img})按类别划分的意义是保证验证集里出现的类别在训练集里见过但见过的具体个体不同。花朵识别里同一株花的多个角度如果同时进训练和验证模型等于开卷考试验证集数字完全不可信。2.3 数据增强的度切几刀、转几度、归一化参数怎么设花朵品种识别的数据增强核心是兼顾“类内多样性”和“细粒度特征保留”。水平翻转对大多数花是安全的多数花朵左右对称向日葵这类朝向性强的花则要慎用翻转会让模型学到错误方向。随机旋转我一般限制在 ±30°超过 45° 花瓣结构会出现不自然的倾斜模型更容易去学“转过后的样子”而不是“花本身的样子”。transform_train transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.6, 1.0), ratio(0.75, 1.33)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])RandomResizedCrop 的 scale 是这组增强里最需要动脑的参数0.6 到 1.0 意味着每张图只保留原图 60% 到 100% 的区域网络必须学会从局部判断品种。ratio 控制在 0.75 到 1.33避免裁剪出离谱的细长条。ColorJitter 三项都设 0.2用于模拟不同光照下的色偏——对花色敏感的品种饱和度抖动过大会让模型把颜色深度当成品种特征这点在蓝色系和紫色系花卉上尤其明显。还有一个容易被忽略的点增强只用在训练侧推理侧必须禁用。RandomResizedCrop 在推理时用会每次预测出不同结果正确做法是推理时只用 Resize CenterCrop 固定裁剪。这部分最好把训练和推理的 transform 写成两个独立函数避免改了一个影响另一个。3. 选 CNN 主干网络从 ResNet 到轻量级模型的取舍3.1 为什么不用自己攒的 CNN 而用预训练模型做迁移学习很多入门教程让你从零搭一个两层卷积加池化加全连接的玩具 CNN跑出来准确率六七十然后就没有下文了。真实的花朵品种识别里自己攒的 CNN 很难在 102 类、每类只有几十张图的数据上学到足够的判别力。原因很直白卷积核参数从随机初始化开始需要大量数据才能收敛出有用的边缘和纹理特征花朵数据集通常没有这个量级。常见做法是直接用 ImageNet 预训练的 ResNet、EfficientNet、ConvNeXt 作为特征提取器做迁移学习。预训练模型已经学会了通用的边缘、颜色、纹理、局部形状特征微调只需要让网络把“这些通用特征如何组合成某个品种的判别特征”学出来。这个差异在花朵这种细粒度任务上比通用物体分类更明显。从工程角度看ResNet18 是稳妥的起点显存占用小、训练快、torchvision 里一行代码就能加载预训练权重。EfficientNet-B0 精度略高但训练时对 batch size 敏感在小显存上容易翻车。如果目标设备是 Jetson 或手机端最后还得看 MobileNetV3——精度比 ResNet18 低两三个点但推理速度快一个量级。我的建议是先用 ResNet18 跑通全流程确认数据没问题后再换轻量模型做部署。3.2 动手改最后一层把 ResNet18 的输出换成 102 类ResNet18 最后一层 fc 原本是 ImageNet 的 1000 类输出把它换成数据集的类别数就行。torchvision 加载预训练权重后替换 model.fcimport torch import torch.nn as nn from torchvision import models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 数据集类别数由 ImageFolder 自动给出 num_classes len(train_dataset.classes) # 替换最后一层全连接 model.fc nn.Linear(model.fc.in_features, num_classes) # 放到 GPU device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)逻辑说明model.fc 的输入维度是 512来自 ResNet18 最后一个残差块输出的特征图经过全局池化后的特征长度原本输出 1000。nn.Linear(model.fc.in_features, num_classes) 保留输入维度只把输出换成你自己的类别数等价于拿预训练学到的 512 维特征重新映射到目标类别空间。参数上只提醒一点num_classes 别手写死。用 len(train_dataset.classes) 自动取以后换数据集或增减类别时就不会漏改。如果主干换成 ResNet50fc 输入维度变成 2048这段代码因为用了 in_features 自适应同样不用改。3.3 冻结还是微调不同数据量下的两个可复现策略迁移学习的参数策略分两派只训练最后一层或者全部微调。数据量少的时候全微调容易过拟合数据量充足时只训练最后一层又学不到任务相关的深层语义。我一般用两条经验线每类样本少于 50 张时冻结主干只训练新加的 fc 层学习率设 3e-3每类样本超过 100 张时解冻主干后几层和 BN 层做全参数微调学习率从 1e-4 起。花朵识别大多落在第二种情况。# 策略一完全冻结主干只训练新的分类头 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True # 策略二解冻最后两个残差块layer3、layer4和分类头 for name, param in model.named_parameters(): if name.startswith(layer3) or name.startswith(layer4) or name.startswith(fc): param.requires_grad True else: param.requires_grad False # 只把需要更新的参数传给优化器 trainable_params [p for p in model.parameters() if p.requires_grad] optimizer torch.optim.Adam(trainable_params, lr1e-4, weight_decay1e-4) # 验证集进入平台期后把学习率降到原来的十分之一 scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.1)两个策略的共同点是只把 requires_gradTrue 的参数传给优化器避免冻结参数白占 Adam 的动量缓存。策略二里 layer3 和 layer4 是 ResNet 中语义最强的两个残差块解冻它们能在保留通用纹理特征的同时适配花朵特有的细粒度差异。BN 层在 PyTorch 里的行为要特别说一下即使冻结了参数只要在训练模式下BN 的均值和方差仍会随输入更新这本身没有问题。真正要防的是 eval 模式下推理时 BN 改用累积统计量如果训练和推理的图分辨率差太远预测概率分布会明显漂移。4. 训练避坑花朵品种识别最常见的 5 个翻车现场4.1 过拟合loss 一降到底但 val_acc 原地踏步现象训练 loss 掉到 0.1 以下验证集准确率却停在 60% 上下每过一个 epoch训练和验证的差距越拉越大。原因每类样本太少模型把训练集里的背景、拍摄角度、光照当成了品种特征记住没学到花瓣形态。花朵类别内差异本来就大增强做得弱的话网络很快就能把训练集“背”下来。解决先看数据增强强度是否不够把 RandomResizedCrop 的 scale 下限从 0.6 降到 0.4ColorJitter 的 saturation 从 0.2 提到 0.3同时给 fc 层加 Dropout(0.3)loss 里加 weight_decay1e-4。如果两三个 epoch 后训练 loss 还在降而验证不涨就是增强和正则的配比问题这时候不要急着加数据先把增强强度提上去再看。4.2 类别不平衡部分品种准确率高、部分全挂现象混淆矩阵里出现一整行零某个品种一张都没预测对而这个品种在训练集里往往只有十几张图。原因Oxford Flower 102 每类最少 40 张、最多 258 张长尾现象在真实场景里更严重。交叉熵在多类别不平衡下会天然偏向样本多的类别尾部类别根本拿不到足够的梯度。解决第一选择是换 WeightedRandomSampler按样本数倒数给采样权重让每个 batch 里尾部类别出现的概率提高from torch.utils.data import WeightedRandomSampler # 统计每类样本数 class_counts torch.bincount(train_dataset.targets) # 样本权重取该类样本数的倒数 weights 1.0 / class_counts[train_dataset.targets].double() sampler WeightedRandomSampler(weights, num_sampleslen(weights), replacementTrue) train_loader torch.utils.data.DataLoader( train_dataset, batch_size32, samplersampler, num_workers4)注意 WeightedRandomSampler 开了 replacementTrue 后同一个样本在一个 epoch 里可能重复出现训练轮数要相应缩短。采样之后尾部类别上来了但头部类可能开始掉点这时可以在 loss 里加 label smoothing 0.1 兜底让模型别对头部类过于自信。4.3 细粒度混淆月季和蔷薇分不清是网络问题还是数据问题现象两个品种在混淆矩阵里互相串某个品种被预测成另一个品种的比例特别高具体是哪两个品种一查混淆矩阵就知道。原因细粒度分类的死结是网络把最表层的颜色、花瓣数量当成主要区分依据而这些特征在近缘品种之间高度相似。另一个隐藏原因是数据集里这两个类别的图片来源相似——很可能同一个拍摄者、同一批花朵素材被分到了不同类别。解决先做 Grad-CAM 热力图看模型在预测这两类时到底关注什么位置。如果热力高亮集中在整片花瓣中央说明判别特征不够局部化需要加强随机裁剪逼网络看局部细节如果高亮区域集中在花蕊和花瓣边缘但模型还是分错说明这两个类别本身边界就模糊需要用分层分类思路——先分大科再去区分变种而不是硬训一个 102 类分类器。4.4 显存不够batch size 一调大就 CUDA out of memory现象训练到第二个 batch 直接爆显存换小 batch 能跑但收敛慢得离谱。原因细粒度分类常用 224x224 甚至更高的分辨率ResNet50 在 FP32 下 batch 32 大约要 7GB 显存再叠加随机裁剪的中间缓存和 Adam 优化器的动量缓存8GB 的卡很容易就满了。解决三个手段按顺序用。第一把输入分辨率降到 192x192花朵品种识别的精度损失通常小于 1%是性价比最高的操作。第二用 torch.utils.checkpoint 给 resnet 的 layer3、layer4 开启激活重计算能省出 1~2GB 显存代价是训练时间多 20% 左右。第三开启 PyTorch 自带的混合精度训练torch.cuda.amp 下 batch size 不变时显存占用可以砍掉接近一半。混合精度下 BN 层默认仍用 FP32 计算保证稳定性不需要额外处理。4.5 训练和推理的预处理不一致颜色都对不上现象训练时验证集准确率 90%导出模型后拿一张正常照片测试预测结果完全不对肉眼看图也没问题。原因大概率是推理时忘了做 normalize。训练时用的是 Normalize(mean[0.485, ...])推理时直接用 PIL 读出的 0~255 数组喂给模型输入分布差了好几倍CNN 对输入分布漂移极敏感。解决推理脚本里必须复刻训练时同一套 transform包括 Resize、ToTensor、Normalize 的顺序和参数。稳妥做法是把预处理封装成独立函数训练和推理共用避免两边各写一遍导致参数漂移def preprocess(image_path): image Image.open(image_path).convert(RGB) transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) return transform(image).unsqueeze(0)这个坑在 ONNX 导出和手机端部署时最容易出现。注意如果训练时用了 RandomResizedCrop推理时不要也用随机裁剪——应该用 Resize CenterCrop 固定裁剪否则同一张图每次预测结果不一样。5. 用混淆矩阵和 Grad-CAM 验证你的模型到底认没认出花训练完只看一个总准确率远远不够。花朵品种识别的价值在于搞清楚“具体哪两个品种被搞混”所以要先把混淆矩阵打出来再对典型案例做 Grad-CAM 可视化。import numpy as np from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay all_preds, all_labels [], [] model.eval() with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) preds outputs.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) ConfusionMatrixDisplay(cm, display_labelsval_dataset.classes).plot()逻辑说明confusion_matrix 的行是真实类别、列是预测类别对角线是正确预测。把 val_loader 所有 batch 的预测收齐再一次性算矩阵比每个 batch 单独算高效ConfusionMatrixDisplay 的 display_labels 可以直接传 ImageFolder 的 classes 列表图上显示的类名可读性更高。Grad-CAM 的做法是让目标类别的 logit 对最后一个卷积层的特征图求梯度把梯度加权的特征图放大到输入尺寸叠加显示。PyTorch 里不需要额外依赖用 hook 捕捉 forward 输出和 backward 梯度就能实现。核心参数是选择哪一层作为 CAM 目标层ResNet18 我一般取 layer4[-1]也就是最后一个残差块里的 3x3 卷积层它同时保留了空间位置语义和足够深的特征抽象。如果热力图高亮集中在花瓣核心和花蕊附近说明模型学到了花朵本身的判别特征如果高亮打在叶片和背景土壤上说明训练的类别区分逻辑有偏差回去查数据增强和标注而不是继续在网络上折腾。我做花朵品种识别最大的教训是第一版用 224x224 硬训 ResNet18过拟合严重但总准确率到了八成以为能用了结果混淆矩阵打出来才发现有 5 对品种几乎完全分不开而这些品种在真实场景里恰恰是常见品种。后来老老实实回到数据层面重做增强、解冻最后两层微调才把混淆率压到可用范围。希望帮到你记住换网络是最后一个动作不是第一个。本文还有配套的精品资源点击获取
返回列表