
简介这份文档收录了一篇发表于《黑龙江大学工程学报》的学术论文聚焦基于卷积神经网络的花朵品种识别适合深度学习、图像识别方向的研究者及学生参考。文章详细介绍了卷积神经网络的模型构建思路包括利用BP算法优化参数、采用稀疏性较好的ReLU激活函数调整输出并在牛津大学102种花卉数据集基础上扩充5种实验准确率达83.01%随后随机选取5种花卉进行识别分类最高准确率达到85%。资源为单个PDF文件压缩包大小5.59MB内容涵盖数据来源与预处理、网络结构与算法原理、模型训练与结果分析并涉及图像处理、模型优化与数据集质量等技术挑战可帮助读者理解图像识别中从特征自动提取到分类的关键流程也可为植物分类相关应用提供方法参考。目前该文档已有239人浏览学习适合用于课程设计、毕业设计或深度学习算法研究入门。1. 基于卷积神经网络的花朵识别本质是在和类间相似性较劲基于卷积神经网络CNN的花朵品种识别本质上是在和两个问题较劲类间相似性高、类内变异大。世界上大约有36.9万种开花植物不同品种的花可能只差一点花瓣弯折角度而同一品种在强光、背光、雨天、晴天拍出来的照片却可能像两种花。传统的颜色直方图、形状描述子在这种高相似度场景下很难稳定区分。这篇论文在牛津大学102类花卉的基础上额外收集了5类互联网图片构成107类共8688张的数据集用一个小规模CNN模型训练后拿到83.01%的准确率随机抽5类实测最高85%。对想入门深度学习图像识别的人来说这是一个数据准备、模型搭建、调参、评估全流程都能走通的小项目。2. 数据集与预处理从 102 类扩展到 107 类的完整路径数据集是整个实验的地基。这篇论文没有直接拿现成的 Oxford 102 数据一次性跑通而是额外收集了5种、500张互联网花卉图片合成107类共8688张。多出来的5类不只是数字变化它带来两个直接影响类间相似性上升模型需要区分更多近似的视觉模式单类样本数量变得更不均匀有的类只有40张有的接近300张。这种天然的不均衡给训练埋了隐患但论文的处理方式比较直接——不过采样、不加类别权重只依赖4:1的划分来训练和测试。2.1 数据来源与规模每类 40~300 张的不均衡样本数据集的组成是Oxford 102类花卉8188张加上互联网收集的5类共500张总计8688张手工过滤、分类后放进107个文件夹每个文件夹对应一种花。这里的“手工过滤”非常关键网络图片里常混有插画、文字水印、多花拼图这些不清理掉模型学到的是水印和插画风格而不是花的特征。样本数从40到300张跨度很大直接后果是模型在样本多的类别上收敛更充分样本少的类别容易出现欠拟合。这一点在复现时要注意83.01%是一个整体准确率落到个别样本少的类别上准确率可能远低于均值。论文最后的随机5类测试里最低一类只有70%也从侧面印证了不均衡数据对单个类别的影响。不难发现这个环节有几步是绕不开的用目录名作为类别标签遍历每个文件夹生成整数 label。按类别统计图片数量重点检查低于60张的类别。清洗掉水印图、插画图和重复图。将图片路径和 label 配对生成 image_list 和 label_list。按4:1比例划分训练集和测试集并且在划分前保证每个类别都被随机抽样到两边。我一般会先写一个统计脚本把每个类别的图片数量列出来看到明显偏少的类别就先做一次简单筛选把模糊、带水印的图剔除。这个步骤不影响模型结构但能直接影响最终准确率。2.2 图像预处理为什么不直接裁剪花朵不一定在图片中心。直接裁剪到100×100会把大量偏移的完整花朵切断训练样本里就会出现“半朵花”甚至“零朵花”。论文的处理方式是用花朵区域选择算法先确定花朵的位置以这个区域为中心点缩放整张图片再统一到100×100像素。也就是说先找花再 resize而不是盲目居中裁剪。RGB 归一化也是必要步骤。原图每个像素三通道数值范围是0到255卷积层直接面对这么大的输入初始梯度会很大训练一开始就容易震荡。归一化到0~1之后梯度量纲平稳得多。论文没有提图像增强复现时可以不加先保证原始数据能跑出接近的准确率。预处理流程可以分成几步读入原图检测花朵区域。简单做法是先用颜色显著性阈值找到高饱和度区域再取外接矩形。以区域中心为锚点把整张图 resize 到100×100不直接裁剪。RGB 三分量分别除以255完成归一化。按类别文件夹编码整数 label写出 image_list 和 label_list。按4:1划分训练集与测试集两边类别分布尽可能一致。如果原图宽高比差别很大直接 resize 到正方形会带来拉伸变形。论文没提保持比例但实际复现时我会先缩放长边到100短边补零到100这比强行拉伸更稳也不会破坏花朵区域选择算法已经定位好的中心点。2.3 制作 Tfrecord为什么要把图片打包成二进制文件TensorFlow 读取零散小图时频繁磁盘 IO 会拖慢训练。论文把图像和标签打包成 Tfrecord 二进制文件一次读取一整块数据这是 TensorFlow 1.x 时代的标准做法。整个流程是先将 image_list 和 label_list 分别打乱按4:1划分训练测试再传给 get_batch() 函数转换成张量后放入输入队列。队列容量 capacity 设为200batch_size 设为100即每次出队100张图队列最大缓冲200张避免训练时 GPU 空等。代码逻辑近似如下import tensorflow as tf def write_tfrecord(image_paths, labels, out_file): # 把图片字节串和标签写入 tfrecord 文件 with tf.io.TFRecordWriter(out_file) as writer: for path, label in zip(image_paths, labels): img_bytes open(path, rb).read() feature { image: tf.train.Feature(bytes_listtf.train.BytesList(value[img_bytes])), label: tf.train.Feature(int64_listtf.train.Int64List(value[label])) } example tf.train.Example(featurestf.train.Features(featurefeature)) writer.write(example.SerializeToString())这段逻辑不难关键是理解两点一是 image 和 label 必须同步打乱否则图片和标签错位训练时 loss 根本降不下去二是训练集和测试集在打乱之前就按比例分开而不是打乱之后再挑避免同一种花的图片同时出现在两侧造成数据泄露。后面第5章会专门讲这个坑。关于 capacity 和 batch_size 的比例论文里 capacity200、batch_size100队列缓冲正好是两倍 batch。这个比例沿用到了全连接层较大的模型上依然有效因为它能给数据读取留出足够的预取余量又不会占用太多内存。3. CNN 模型结构卷积核数量与网络层数怎么定整条网络链路是输入100×100×3经过 Conv164个3×3×3卷积核→ ReLU → 最大池化3×3步长2→ Conv216个3×3×64卷积核→ ReLU → 最大池化3×3步长2→ 展平 → 全连接层128 → Dropout → 全连接层128 → Dropout → Softmax 输出107类。两层下采样后特征图尺寸从100降到50再降到25。这样一个轻量级CNN参数量大约133万普通显卡几分钟就能跑一个epoch非常适合做复现实验。3.1 卷积层设计64 个 3×3×3 卷积核是经验还是玄学Conv1 用64个3×3×3卷积核步长为1padding方式保持输出尺寸不变。3×3×3里的前两个3是卷积核空间尺寸最后一个3是输入通道数正好对应RGB三个通道。64这个数字基本继承自 VGG 等经典模型的经验。第一层卷积想要的是多种低级特征比如边缘方向、颜色块、纹理周期这些特征在空间上相互独立所以通道数要给足。但也不是越大越好64后面紧接池化和16个卷积核信息会被浓缩通道太多反而增加无谓计算。Conv2 的卷积核尺寸写成3×3×64代表3×3空间尺寸、64个输入通道输出通道16个。特征图从64个通道浓缩到16个通道说明每经过一层卷积网络都在做一次“特征筛选”只把最值得保留的模式送到下一层。与第二层卷积相连的池化把50×50降到25×25展平后向量长度为25×25×1610000。这个数字直接决定全连接层的参数量也是整个网络的主要计算来源。用 Keras 写这个结构方便直接对照from tensorflow.keras import layers, models model models.Sequential([ layers.Conv2D(64, (3, 3), strides1, paddingsame, activationrelu, input_shape(100, 100, 3)), layers.MaxPooling2D(pool_size(3, 3), strides2, paddingsame), layers.Conv2D(16, (3, 3), strides1, paddingsame, activationrelu), layers.MaxPooling2D(pool_size(3, 3), strides2, paddingsame), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(107, activationsoftmax) ])这个结构里每层卷积之后不直接池化而是先过 ReLU这是 CNN 的典型写法。卷积本质上是线性加权求和多个线性层堆叠还是线性变换只有加上非线性激活网络才真正具备拟合复杂分布的能力。3.2 池化层与 ReLU最大池化好在哪池化层的作用是降维和抗噪。论文两层池化都用3×3窗口、步长2的最大池化。这个窗口大小在经典网络里不多见AlexNet 早期用过3×3后来的 VGG 普遍用2×2。3×3步长2会让特征图缩得更快计算量更小、感受野更大。对100×100的输入来说两步下采样把特征压到25×25正好把全连接层的输入控制在一个较小规模避免参数爆炸。最大池化在每个3×3窗口里取最大值隐含假设是“保留最强响应忽略弱响应”。花朵分类中花瓣边缘纹理、花蕊颜色对比往往是最具判别性的区域这些区域在卷积特征图上的响应通常是局部最大值所以最大池化比平均池化更合适。平均池化会把强响应和周围弱响应平均掉特征图平滑了判别性反而变差。对类间相似性高的花朵数据来说保留强响应更重要这也是论文选最大池化的原因。激活函数用 ReLU 有明确理由。Sigmoid 和 Tanh 在输入绝对值大时梯度趋近0深层网络反向传播时梯度多层相乘后会消失。ReLU 在正区间梯度恒为1负区间输出恒为0梯度不衰减收敛明显更快。论文说的“稀疏性较好”也在这里体现负输入被置零神经元不会被无意义的小数值激活特征表示更干净。训练时如果准确率长时间不涨先检查是否用了 ReLU、池化是否生效再考虑调学习率。3.3 全连接层、Dropout 与 Softmax 的分工卷积层和池化层负责特征提取全连接层负责分类。论文用两层全连接每层128个神经元。第一层接收展平后的10000维向量第二层做精细的非线性组合最后 Softmax 输出107维概率分布。Softmax 是多项式回归的泛化输出总和为1每个维度的值就是模型对某一类别的置信度。全连接层的参数主要集中在第一层10000×128约128万占整个网络参数量的大头。参数多意味着容易过拟合尤其在每类只有40到300张图片的情况下。Dropout 在这里起了关键作用训练时随机屏蔽一半神经元每个 batch 相当于训练一个不一样的子网络最终模型对单个神经元的依赖下降泛化能力提升。论文没有明确写 Dropout 比例工程上默认0.5是通用取值复现时从0.5开始调就行。如果遇到 Dropout 放在卷积层还是全连接层的争论记住一点卷积层本身有参数共享过拟合压力不如全连接层大Dropout 优先放在全连接层是常规做法论文也是这么处理的。4. 训练配置与调参学习率、batch_size 和 1000 步的权衡训练环节有三个参数直接决定模型能不能收敛学习率、batch_size、max_step。论文先用5类花卉做调参实验确认 batch_size100、learning_rate0.001 之后再把这组配置搬到107类全量数据上。这种做法值得借鉴小样本试验成本低能快速暴露参数问题不用每次都在全量数据上跑一遍才发现方向错了。4.1 参数表论文里能直接复现的配置论文表1给出了一份简明的参数配置是经过多次试验保存最优模型时确定的参数名取值作用Bias1全连接层偏置初始值Batch_size100每批送入网络的图片数Learning_rate0.001梯度更新步长Capacity200输入队列缓冲容量Max_step1000训练最大迭代步数补充两个没有进表但同样重要的配置训练集与测试集按4:1划分优化器选用 Adam。整个训练流程是先在5类小样本上调参确定 batch_size 和学习率再应用到107类全量数据。Batch_size100 意味着每轮更新前要累计100张图的前向传播结果。batch 太大梯度方向更稳定但模型容易收敛到尖锐极小值泛化可能反而变差batch 太小梯度噪声大训练时间变长。论文做了对比相同迭代次数下 batch_size100 时准确率达到稳定值最快。学习率0.001是 Adam 优化器最常见的起步值对这样一个三层小卷积网络来说既能保证前期快速下降又不会在后期因步长过大而在最优解附近打转。1000步在这个数据集上对应多少轮8688张图按4:1划分后训练集约6950张batch_size100时每个 epoch 约70步1000步相当于14个 epoch。对一个轻量级 CNN 来说这个训练量刚好能让大部分样本被看到一遍以上没有明显过拟合迹象。4.2 误差反向传播与梯度更新为什么带上一轮动量训练的核心是调整权值。论文用 BP 算法计算误差梯度工作方式是前向传播得到输出后根据真实标签和模型预测的差值计算误差再反向逐层传播更新每层权值。论文列出的误差公式里有一项值得注意——权值调整量不是只由当前梯度决定还要加上上一轮的调整量 ΔV(n-1) 和 ΔW(n-1)这实际上是一种动量机制让梯度更新在方向上更平滑避免陷入局部震荡。公式里的 α/(1N) 和 α/(1I) 可以理解为随迭代次数逐渐衰减的学习率N 和 I 分别是输入层和中间层相关参数量起到一定的归一化作用。梯度下降的方向是目标函数的负梯度方向学习率决定每一步走多远。实现层面论文在梯度下降基础上采用 Adam 优化器Adam 给每个参数分配独立的自适应学习率对不同尺度的参数更友好这也是现代复现代码里几乎都用 Adam 而不是手工写 BP 公式的原因。经验上如果直接调用 Keras 的 Adam 优化器初始学习率给0.001其他参数用默认值就能复现出和论文大致一致的收敛行为。动手前先确认训练集做了归一化否则 Adam 的这个初始学习率未必兼容。4.3 训练曲线的解读准确率升、损失率降的背后论文图5和图6给出107类训练时的准确率曲线和损失率曲线。准确率随迭代次数单调上升最初几百步上升最快随后放缓最后稳定在83%附近损失率的走势相反先快速下降再逐渐走平。这两条曲线的形态可以作为训练是否正常的判据如果准确率一直不涨大概率是数据或超参出了问题不是网络结构过于复杂。尾部走平不代表模型已经最优更可能是当前网络容量和数据规模下达到了瓶颈。论文测试阶段随机抽5种花每种20张得到的结果是75%、80%、85%、75%、70%。单看某个品种准确率明显低于整体均值这正是类别不均衡和类间相似性叠加的结果。复现时遇到类似情况不要慌先看整体准确率是否达到80%以上再统计误分类的品种分布通常会有规律——要么是样本特别少的类别要么是视觉上高度相似的类别。5. 避坑合集花朵识别复现中容易翻车的五个细节这部分是我拆解项目时最想强调的内容。论文篇幅有限数据预处理和训练中的很多边界情况不会详细展开而实际操作里最容易出问题的恰恰就是这些细节。5.1 花朵不在图片中心直接裁剪导致的关键区域丢失现象训练准确率始终在60%左右徘徊随机抽几张训练样本看发现大量图片没有完整花朵。 原因预处理阶段对原图做了固定位置裁剪花朵偏离中心时被切掉模型学到的是残花特征。 解决先运行花朵区域选择算法定位花朵中心再整体 resize。如果只是想快速复现练手可以用显著性检测替代读取图片后找到高饱和度区域的外接矩形把该矩形外扩20%再以此区域为基准缩放到100×100。比人工标注100张图成本低得多效果也接近论文做法。import cv2 import numpy as np def center_flower_resize(img, target_size(100, 100), expand_ratio0.2): # 用 HSV 饱和度通道找花朵高亮区域再外扩后缩放 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) s hsv[:, :, 1] _, mask cv2.threshold(s, 60, 255, cv2.THRESH_BINARY) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return cv2.resize(img, target_size) x, y, w, h cv2.boundingRect(max(contours, keycv2.contourArea)) # 按外扩比例扩大包围盒 dx, dy int(w * expand_ratio), int(h * expand_ratio) x0, y0 max(0, x - dx), max(0, y - dy) x1, y1 min(img.shape[1], x w dx), min(img.shape[0], y h dy) crop img[y0:y1, x0:x1] return cv2.resize(crop, target_size)关键点就是先找显著性区域再缩放不要盲目裁。上面代码用 OpenCV 的 HSV 饱和度通道做阈值对大多数颜色鲜艳的花卉有效。5.2 训练集与测试集划分不当数据泄露让准确率虚高现象训练准确率90%以上测试准确率却只有60%二者差距大。 原因同一朵花的原始图和增强图同时分到了训练集和测试集评估时模型其实见过这些特征。另一个常见错误是先 shuffle 再划分shuffle 后某个类别可能全部跑进测试集训练集里反而很难见到。 解决先按类别文件夹分别划分再合并成训练/测试集合。划分时用分层抽样保证每个类别在两侧都有代表性。from sklearn.model_selection import train_test_split # stratifylabels 保证每个类别的比例在训练/测试中一致 train_paths, test_paths, train_labels, test_labels train_test_split( image_paths, labels, test_size0.2, stratifylabels, random_state42 )注意划分数据要在清洗之后做训练过程中不要拿测试集反复调参否则测试集的意义就没了。5.3 学习率与 batch_size 搭配不合理loss 不走或震荡现象loss 初期不降后期上下跳动准确率曲线锯齿严重。 原因学习率太高梯度在最优解附近来回震荡batch_size 太小单 batch 的梯度噪声大。 解决先严格用论文参数复现learning_rate0.001、batch_size100。如果 loss 依然震荡把学习率降到0.0005或0.0003再试同时观察准确率曲线是否变平滑。改学习率时不要同时改 batch_size这两个参数联动时很难判断谁在起作用。复现时可以用早停加学习率衰减但别一开始就加先确保基础配置能跑出83%附近的准确率。加了学习率调度后准确率可能提升1~2个百分点但也会掩盖参数设置的问题。5.4 相似品种误分类类间相似性高时的样本策略现象菊科或蔷薇科的几个品种反复互相误判整体准确率卡住不动。 原因这些品种在纹理、颜色上高度接近小尺寸卷积网络学到的低级特征不足以区分。 解决单独跑一个5类子实验专门放入最相似的几个类别观察在哪一层特征上区分不开。可以适当增加 Conv2 的卷积核数量从16加到32或者把输入分辨率提高到150×150。核心思路是提高特征的精细度而不是盲目加深网络层数。如果子实验显示两个品种的混淆主要来自背景颜色相近那么预处理阶段还可以加一步背景分割把花朵从背景中分离后再送入网络。这个操作对花朵识别很有效因为背景往往是干扰项。5.5 Dropout 在评估时忘记关闭测试结果忽高忽低现象同一个模型在同一批测试图片上连跑两次准确率不稳定。 原因推理阶段没有关闭 Dropout神经元仍然随机失活输出的概率分布每次不同。 解决Keras 里 model.predict() 会自动关闭 Dropout但使用自定义训练循环时必须显式设置 trainingFalse。保存模型时用最佳验证集权重不要用最后一个 step 的权重。# 推理阶段显式关闭 Dropout model.load_weights(best_model.ckpt) predictions model.predict(test_images, trainingFalse)这条坑在 PyTorch 里也常见model.eval() 没调用就直接跑推理结果每次都不一样。养成习惯加载权重后先切到推理模式再跑测试集。6. 复现节奏与改进方向从 5 类起步再推到全量的三个技巧6.1 先跑通 5 类小样本再上全量论文的实验顺序是先在5类花卉上调参再迁移到107类全量数据。这个节奏非常适合复现者模仿。5类训练数据量小一个 epoch 只要几十秒几分钟就能看到训练曲线全貌。如果5类数据上模型都不收敛那问题大概率出在数据预处理或参数配置上直接上全量只会浪费时间。建议的复现顺序是抽取5个视觉差异明显的类别每类20到30张跑通从 Tfrecord 构建到训练、测试的完整链路。确认 loss 下降、准确率上升后再加入几个相似类别验证模型在类间相似性上的表现。最后扩展到107类全量训练用论文的 batch_size100、learning_rate0.001、max_step1000 跑完整流程。这个过程能让每一步的错误都在最小范围内暴露而不是等全量训练跑了几小时后才发现数据划分有问题。我见过太多人一上来就全量训练损失函数卡住找不出原因实际上只是某个文件夹里混入了损坏图片。6.2 想提升准确率迁移学习优先于自己堆层如果复现完还想进一步优化最稳妥的方向是迁移学习。论文的自建CNN是8万样本量级项目的合理选择但 ImageNet 预训练模型在特征提取上明显更有优势。把输入尺寸调到224×224用 MobileNetV3 或 ResNet18 替换掉 Conv1 和 Conv2 部分保留论文的全连接层和分类头只训练最后两层全连接数据量少的时候也能拿到比83.01%更高的准确率。迁移学习的代码改动很小from tensorflow.keras.applications import MobileNetV3Small base_model MobileNetV3Small(input_shape(224, 224, 3), include_topFalse, weightsimagenet) base_model.trainable False然后在 base_model 输出后接两层128全连接和 Softmax输入尺寸统一改为224×224数据预处理阶段的分辨率也随之调整。加上随机水平翻转和数据增强准确率通常能再往上走3到5个百分点。从那以后我每次复现类似项目都会强制先跑一遍5类小样本全流程再碰全量数据。数据预处理、队列读取、训练参数这些环节一步不对后面全是白费。希望帮到你。本文还有配套的精品资源点击获取