花卉识别深度学习课程设计:MobileNet与CNN实战解析
简介一套基于Python深度学习的花卉识别课程设计源码面向机器学习与计算机视觉初学者围绕花卉图像自动分类识别提供完整工程涵盖数据预处理、数据集划分、卷积神经网络与MobileNet模型构建、训练、测试及结果可视化等关键环节适合课程设计、毕业设计或深度学习入门实践。压缩包共49个文件整体仅3.36MB包含12个Python源文件、15个jpg与13个png花卉样本并配有训练与测试日志、热力图结果、Markdown说明文档及Git忽略文件目录结构清晰便于按模块阅读和调试。该资源已有362人学习具有一定的参考热度。读者可获得可直接运行的MobileNet与CNN双模型代码、数据读取与数据集划分脚本借助热力图和训练日志理解模型关注区域与性能变化从而快速掌握图像分类任务从数据准备到模型评估的完整实践流程。1. 花卉识别课程设计拿到手后先别急着跑模型这套“基于Python深度学习的花卉识别课程设计源码”里真正值钱的不是最终训练好的权重而是它把一条完整链路都摊开了CrawlingData.py 管数据采集getData.py 和 ReadData.py 管数据加载DivideData.py 管训练验证划分MobileNet.py 和 CNN.py 两条模型路线并行heatmap 文件夹里还留着 Grad-CAM 热图train_log 和 test_log 里是训练测试的过程记录。换句话说你拿到的不只是一个“能出结果的模型”而是一套可以拆开来逐段讲解的机器学习项目标本适合课程设计、期末答辩也适合刚入职想接手小数据集图像分类任务的工程师。这套东西最有意思的地方在于数据集本身很小daisy 菊花一类jpg 加 png 也就几十张数据量压不住大模型但代码里却同时给出了“用预训练 MobileNet 做迁移学习”和“从零搭 CNN”两条路子正好能回答「小数据集做深度学习该怎么办」这个高频问题。下方正文按数据准备、MobileNet 迁移学习、CNN 对照实验、热图可解释性四个层面把项目拆开每个环节都给可直接改跑的命令和参数。2. 数据准备链路getData、ReadData 与 DivideData 到底各管哪一段2.1 三个数据脚本的分工边界在动手跑训练之前得先把 getData.py、ReadData.py、DivideData.py 这三个文件的分工理清楚。很多初学者拿到项目后会误以为这三个脚本功能重叠实际上它们是按「采集 → 加载 → 切分」三段式设计的。getData.py 管数据从外部进入本地项目的动作。项目里有 CrawlingData.py说明菊花图像很可能是通过爬虫方式采集的而 getData.py 则负责把散落在不同目录的原始图片统一整理到 data 文件夹下并完成初步过滤比如去掉损坏图片、剔除尺寸异常的样本。ReadData.py 更偏向模型训练前的读取接口它的输出通常是一个可以直接喂给 Keras 或 PyTorch 的 Dataset 对象内部封装了图片解码、resize、归一化。DivideData.py 做的事情最容易被忽视把数据集按固定比例切成训练集、验证集和测试集并且保证每一个子集里类别分布均匀。实际排错时如果训练时发现 val_loss 抖动得很厉害第一步不是改模型而是回头检查 DivideData.py 的执行结果看验证集里是不是出现了类别缺失。我一般会建议把 DivideData.py 抽出来单独跑像下面这样验证切分结果python DivideData.py cat DivideData_result.txt如果 DivideData_result.txt 里输出的各类别数量差距超过 15%就要重新设置随机种子或者在划分时加上按类别采样的逻辑。划分数据这件事看起来不起眼但它直接决定了后续训练日志里 val_accuracy 曲线的可信度。2.2 数据增强参数怎么设置才不破坏小数据集小数据集训练深度学习模型数据增强是做迁移学习和从零训练之前绕不开的一步。常见做法是直接用 tf.keras.preprocessing.image.ImageDataGenerator在读取图片的同时完成随机水平翻转、随机旋转、缩放和亮度扰动。参考项目里 data 文件夹的图像尺寸下面这组参数是小数据集下比较稳的起点配置from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1.0 / 255.0, rotation_range30, # 随机旋转角度范围 width_shift_range0.2, # 水平平移比例 height_shift_range0.2, # 垂直平移比例 shear_range0.2, # 错切变换强度 zoom_range0.2, # 随机缩放范围 horizontal_flipTrue, # 水平翻转 fill_modenearest # 填充新像素的方式 )rotation_range 设 30 而不是更大的 45是因为菊花的花瓣具有明显的放射状对称性过大的旋转角度配合 zoom_range 会让训练样本出现大量形变过度的伪样本。width_shift_range 和 height_shift_range 控制在 0.2 以内防止目标花朵被平移出视野中心。fill_mode 用 nearest 而不是 constant是因为 constant 会用纯色填充边缘区域给模型引入不存在的边界特征。验证集和测试集只做 rescale绝对不做增强否则验证指标会被数据增强带来的分布偏移污染。2.3 图像尺寸选择对 MobileNet 输入层的影响项目里 results 文件夹下的文件命名出现了 results_mobilenet_10_6、results_mobilenet_30_12、results_mobilenet_50_14 这类组合从命名规律推测前面的数字对应当前的训练轮数epoch后面的数字对应 batch size。这种「轮数_批大小」的命名方式在课程设计里很常见它记录的是同一模型在不同训练配置下的表现。但不管是训练多少轮输入图像尺寸首先要满足 MobileNet 的 constraint。Keras 内置的 MobileNet 和 MobileNetV2 都要求输入高度和宽度不小于 32且最好是 32 的倍数常见选择是 128、160、192、224。小数据集下我一般建议用 160 而不是 224因为 224 会让模型在前向传播时计算量陡增而 160x160 的输入在花卉这类前景集中的图像上损失的信息量很小。ReadData.py 里对应的 resize 逻辑可以直接参考其中 target_size 按训练配置保持一致。3. MobileNet 迁移学习为什么是它以及 MobileNet.py 里的训练逻辑3.1 MobileNet 与 ResNet、VGG 的选型权衡花卉识别这个任务里有 15 个 jpg 和 13 个 png 图像总样本量很小这种情况下选择模型的第一原则不是精度上限而是参数量与数据量的匹配。VGG16 的参数量约 1.38 亿ResNet50 约 2500 万MobileNet 在 224x224 输入下参数量仅约 420 万。用 VGG16 在这点数据上微调全连接层非常容易过拟合训练日志里会出现 train_acc 迅速升到 0.98、val_acc 卡在 0.5 上下的分裂曲线。MobileNet 的关键在于深度可分离卷积depthwise separable convolution它把标准卷积拆分成逐通道卷积和 1x1 逐点卷积两步计算量约下降 8 到 9 倍。对课程设计场景MobileNet 的另一个隐性优势是即便不加载预训练权重单靠 CPU 也能完成一轮完整的训练。这是 VGG 做不到的。项目里同时存在 MobileNet.py 和 CNN.py正好可以用来做「预训练模型 vs 从零训练」的对照实验这是答辩时很有说服力的材料。3.2 MobileNet.py 搭建冻结基座与自定义分类头MobileNet.py 走的常见路线是加载在 ImageNet 上预训练过的 MobileNet 权重冻结底层的卷积基座只训练新加的全连接分类头。这种迁移学习策略下训练轮数不用太多10 到 30 轮之间通常就能收敛。参考项目的 results_mobilenet_10_6.png、results_mobilenet_30_12.png这个轮数区间的实验结果是可以直接对比的。下面是一段可直接替换进 MobileNet.py 的核心代码import tensorflow as tf from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout from tensorflow.keras.models import Model base_model MobileNetV2( include_topFalse, # 去掉 ImageNet 自带的 1000 类分类头 weightsimagenet, # 加载预训练权重 input_shape(160, 160, 3), poolingNone ) base_model.trainable False # 冻结基座 x base_model.output x GlobalAveragePooling2D()(x) # 将特征图压缩为 1D 特征向量 x Dropout(0.3)(x) # 抑制全连接层过拟合 predictions Dense(4, activationsoftmax)(x) # 4 个花卉类别 model Model(inputsbase_model.input, outputspredictions) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.0001), losscategorical_crossentropy, metrics[accuracy] )include_topFalse 是为了丢弃 MobileNetV2 顶部的千分类器换上适配本项目类别数的 4 神经元全连接层。GlobalAveragePooling2D 比 Flatten 更适合小数据集它对空间位置信息做了平均显著减少参数量同时增强平移鲁棒性。Dropout(0.3) 放在全连接层之前比增大 L2 正则更直接。learning_rate 用 0.0001 而不是默认的 0.001是因为预训练特征已经很好了过大的学习率会破坏已经学到的底层纹理特征。3.3 训练日志里到底该看什么train_log 的解读顺序项目里有 train_log 文件夹里面存储的是训练过程的损失和准确率记录。用 Keras 训练时常见做法是配合 CSVLogger 回调把每一轮指标写到文件方便后续画曲线。python MobileNet.py 21 | tee train_log/mobilenet_train.log训练完成后先打开日志看 val_loss 是否出现先降后升的转折点。target 是看 val_loss 最低点出现在哪个 epoch那个 epoch 对应的权重就是最佳模型而不是最后一个 epoch 的权重。项目里 heatmap 文件夹有 heatmap_mobilenet_50_14.png 这类文件注意如果 50 轮时热图覆盖区域比 10 轮时更分散说明训练时间过长导致模型学了一些背景噪声这时应该回头用 30 轮左右的权重重新生成热图。保存模型时用model.save(mobilenet_flower.h5)同时保存结构和权重在 Intertation_MobileNet.py 里加载后做单张预测。4. CNN.py 对照实验从零搭建卷积网络看小数据下手工特征提取的边界4.1 四层卷积加全连接的网络结构解析为了和 MobileNet 形成对照项目里用 CNN.py 实现了一个从零训练的浅层卷积网络。这个网络的设计思路值得借鉴输入图像喂入后先经过两层 Conv2D-ReLU-MaxPooling再接两层卷积加全局平均池化最后用全连接层输出。不用 BatchNormalization 而是直接靠小尺寸卷积核堆叠理由是数据量太小BN 层的均值和方差估计本来就不稳定。参考实现如下from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(160, 160, 3)), MaxPooling2D(2, 2), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(2, 2), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Flatten(), Dense(256, activationrelu), Dense(4, activationsoftmax) ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy])第一层 32 个卷积核是对 160x160 输入的合理起点因为菊花图像的大花瓣纹理在浅层就能被有效捕捉不需要像 ImageNet 那样在第一个卷积层就堆到 64 个。第二层和第三层逐步从 64 翻到 128配合三次 MaxPooling特征图从 160 缩到 20x20空间信息被压缩成高层语义。最后一层 256 维全连接是特意留的容量冗余给足拟合空间——但这一层也最危险如果发现 train_acc 高而 val_acc 低第一件事就是把这层砍到 128。4.2 训练超参数对比epoch、batch size 与优化器的配合项目命名里 results_cnn_50.png、results_cnn_4.png 暗示 CNN 路线上跑过 50 轮和 4 轮的不同实验。对从零训练的 CNN 来说50 轮是必要的因为它的卷积核是从随机初始化开始学习不像 MobileNet 有预训练基础。batch size 的选择要看训练设备CPU 环境下 batch size 设为 8 或 16 更稳GPU 环境可以提到 32。history model.fit( train_generator, steps_per_epochlen(train_generator), epochs50, validation_dataval_generator, validation_stepslen(val_generator), callbacks[tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, verbose1 )] )ReduceLROnPlateau 会在 val_loss 连续 3 轮不下降时自动把学习率缩小一半这个回调对从零训练的 CNN 尤其关键。因为手工设计的网络容易在小数据集上陷入局部最优等比降低学习率往往比重新随机初始化更有效。训练结束后的 logs 里如果 val_accuracy 始终低于 0.6说明问题出在网络容量和数据量的匹配上而不是训练技巧这时再调轮数已经没有意义。4.3 MobileNet 与 CNN 结果曲线的横向对比方法把 train_log 和 test_log 组合起来可以画出两条曲线做横向对比。MobileNet 的 val_loss 起点通常比 CNN 低很多这是因为预训练特征对图像纹理有先验知识CNN 的 val_loss 起点高但下降速度在 10 轮前很快。比较时重点看两个指标一是最终 val_accuracy 差值二是达到 80% 精度所需的 epoch 数。大致可以参考下表整理进课程设计报告对比维度MobileNet 迁移学习从零搭建 CNN达到 80% 验证精度的轮数10 轮左右25 轮以上参数量约 260 万冻结基座约 250 万对 CPU 训练的友好度高收敛快中训练时间长过拟合风险低高需配合 Dropout适合答辩展示的卖点迁移学习有效性手工特征提取理解如果 MobileNet 在 30 轮时的 val_accuracy 还不如 CNN 在 50 轮时的结果先不要急着怀疑模型检查预处理是否一致。常见坑是 CNN.py 用了归一化而 MobileNet.py 忘记对输入做 MobileNetV2 预处理器要求的分通道标准化。5. Grad-CAM 热图把黑盒模型拆开看答辩和 debug 都好用5.1 在 Intertation_MobileNet.py 里生成热图项目里 heatmap 文件夹下的 heatmap_mobilenet_10_6.png、heatmap_cnn.png 就是 Grad-CAM 的可视化输出。Grad-CAM 的核心思想是用类别得分对最后一层卷积特征图求梯度梯度大的区域就是模型分类时重点关注的像素区。对花卉识别来说一份合格的 Grad-CAM 热图应当把高亮区域集中在花蕊和花瓣边缘如果高亮区跑到叶片背景上说明模型学到的特征有偏差。在 Intertation_MobileNet.py 中加载训练好的模型后核心代码如下import numpy as np import tensorflow as tf from tensorflow.keras.models import Model last_conv_layer model.get_layer(block_16_project) # MobileNetV2 最后一个卷积层 grad_model Model( inputsmodel.input, outputs[last_conv_layer.output, model.output] ) with tf.GradientTape() as tape: conv_output, preds grad_model(img_array) # img_array 需为 4D 张量 class_idx np.argmax(preds[0]) class_score preds[:, class_idx] grads tape.gradient(class_score, conv_output) pooled_grads tf.reduce_mean(grads, axis(0, 1, 2)) heatmap tf.reduce_sum(tf.multiply(pooled_grads, conv_output[0]), axis-1) heatmap np.maximum(heatmap, 0) # ReLU 截断过滤负贡献区域 heatmap / np.max(heatmap) # 归一化到 0~1 区间block_16_project 是 MobileNetV2 中倒数第二个特征提取层之所以不选最后一层卷积层是因为越靠近输出的特征图语义越强但空间分辨率越低热图会显得过于模糊。代码先将 forward 过程包进 GradientTape 记录梯度然后针对预测概率最大的类别计算 loss 对特征图的梯度pooled_grads 是全局平均后的梯度权重代表每个通道对分类结果的贡献度最后用 ReLU 截断只保留正贡献区域。5.2 通过热图反推训练日志中的异常如果 MobileNet 在 10 轮时生成的热图高亮区域还很集中到了 50 轮反而变得零散同时 train_log 里 val_acc 缓慢下滑这是过拟合的典型信号。模型在后半段训练中记住了训练集里的背景纹理这些纹理分布在花盆、桌面和水印上正好被 Grad-CAM 捕获。应对策略是回到 MobileNet.py把 Dropout 从 0.3 调到 0.5或者提前停止在热图最集中的那个 epoch。另一个高频问题是 CNN 的热图区域比 MobileNet 明显更大覆盖了整朵花而不只是花蕊。这是因为自建 CNN 的感受野在 160x160 输入下只堆了三层每层 3x3 卷积加池化最终特征图上的每个像素对应原图约 20x20 的区域模型缺乏对细节纹理的敏感度。想要热图更聚焦做法是在 CNN.py 里把第二层卷积核数从 64 提到 96让中间层特征更丰富。最后建议在测试集上重新生成热图而不是只有训练集的可视化结果。把 heatmap_mobilenet_50_14.png 这类文件名对应好图像 ID、轮数、batch size 三个信息交给答辩老师时能直接说清楚模型在哪一类菊花上容易混淆、热图特征指向哪里这份可视化比一堆精度数字更能体现对深度学习模型行为的理解。本文还有配套的精品资源点击获取