ARTICLE DETAIL

资讯详情

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

花卉识别课程设计:MobileNet与CNN图像分类源码解析

花卉识别课程设计:MobileNet与CNN图像分类源码解析 简介面向深度学习初学者与课程设计开发者该项目以Python实现花卉图像自动分类识别覆盖数据预处理、模型构建、训练与测试完整流程可应用于园艺、生态监测等场景。资源共49个文件包含15个jpg与13个png花卉图像样本、12个Python源码、训练测试日志及模型文件等压缩包整体3.36MB结构清晰便于按模块学习。源码中同时提供MobileNet与CNN两套卷积神经网络实现采用迁移学习方式并配有数据划分、读取与预处理脚本以及heatmap热图可视化结果可帮助读者直观理解模型关注区域与训练动态。训练日志完整记录了损失与准确率变化便于分析模型收敛情况和识别错误。目前已有363人学习适合作为课程设计参考或深度学习图像识别入门实践。1. 花卉识别课程设计为什么我推荐直接拆这套源码花卉识别是深度学习图像分类里最典型的入门题目但很多人的课程设计翻车不是死在模型上而是死在数据处理和训练日志看不懂上。这套基于Python深度学习的花卉识别源码把MobileNet和CNN两条技术路线都跑通了还附带完整的数据集、训练日志和热力图课程设计需要的“数据预处理—模型搭建—训练测试—可视化分析”闭环一次给齐。你不需要从零造轮子把它的数据管道和训练参数吃透替换成自己的数据集就能复现。适合正在做图像分类课设的本科生以及想搞懂迁移学习和CNN到底怎么落地的新手。2. 数据准备与预处理从原始图像到训练集划分2.1 数据集构成与读取逻辑这个项目的data目录结构很直白按花卉类别建子文件夹每类下面放若干jpg和png图像。源码包里15个jpg加13个png数量不多但足够走通整个流程也直接暴露了小数据集过拟合的问题——这反而是课程设计里最值得写的分析点。ReadData.py和getData.py两个文件负责读取和增强。这里用的是TensorFlow/Keras的ImageDataGenerator好处是不用手动写循环读图它会自动从目录结构生成标签还能在训练时做实时数据增强不占额外磁盘空间。from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1.0/255.0, rotation_range20, width_shift_range0.2, height_shift_range0.2, shear_range0.2, zoom_range0.2, horizontal_flipTrue, fill_modenearest ) val_datagen ImageDataGenerator(rescale1.0/255.0) train_generator train_datagen.flow_from_directory( data/train, target_size(224, 224), batch_size32, class_modecategorical )rescale把像素从0到255归一化到0到1之间这是所有预训练模型输入的前置要求。rotation_range、width_shift_range这些参数是数据增强的核心作用是把每张图随机旋转、平移、缩放、翻转让模型每次epoch看到的图片都不一样。小数据集必须开增强否则训练集损失降得再低验证集准确率也上不去。flow_from_directory里的target_size注意要和后续模型输入尺寸对齐。MobileNet原版输入是224×224如果你的显卡显存吃紧可以改成192或者160但改了就要连模型输入层一起改否则shape不匹配直接报错。batch_size我给的是32数据集小的时候32没毛病迭代次数少每个epoch能看到更多次参数更新。2.2 数据集划分别把所有图片都丢进训练集DivideData.py解决的是数据划分问题。很多新手把全量数据直接塞给fit训练集验证集混在一起最后画出来的准确率曲线是假的。这个项目的划分逻辑是70%训练、20%验证、10%测试按类别比例分配保证每个类在三个集合里都有样本。import os import random import shutil src_root data train_root data/train val_root data/val test_root data/test for cls_name in os.listdir(src_root): cls_path os.path.join(src_root, cls_name) if not os.path.isdir(cls_path): continue imgs os.listdir(cls_path) random.shuffle(imgs) train_cut int(len(imgs) * 0.7) val_cut int(len(imgs) * 0.9) for img in imgs[:train_cut]: os.makedirs(os.path.join(train_root, cls_name), exist_okTrue) shutil.copy(os.path.join(cls_path, img), os.path.join(train_root, cls_name, img)) for img in imgs[train_cut:val_cut]: os.makedirs(os.path.join(val_root, cls_name), exist_okTrue) shutil.copy(os.path.join(cls_path, img), os.path.join(val_root, cls_name, img)) for img in imgs[val_cut:]: os.makedirs(os.path.join(test_root, cls_name), exist_okTrue) shutil.copy(os.path.join(cls_path, img), os.path.join(test_root, cls_name, img))代码先用random.shuffle打乱图片顺序避免某个类的图片连续排列导致划分偏移。train_cut取70%val_cut取90%剩下10%留给测试。验证集和测试集的数据分布要尽量贴近真实场景所以划分前打乱这一步不能省。这里有个细节很多人忽略划分时要保证每个类别内部都按这个比例切不能把所有图片混在一起再随机分。否则类别A可能大部分落在训练集类别B大部分落在测试集模型对A类见过很多、对B类没见过测试结果完全失真。DivideData_result.txt记录的就是每次划分的统计结果训练前先看一眼这个文件确认每类数量均衡再往下走。2.3 爬虫辅助CrawlingData.py怎么补充数据CrawlingData.py是这个包里容易被忽略但很实用的部分。有些花卉类别图片不够或者类别分布不均它可以按关键词批量抓取图片补充数据集。抓下来的图质量参差不齐我建议用它补数据后多做一步清洗把模糊的、纯色背景的、带水印的图片手动删掉这类噪声图对分类任务干扰很大。爬虫逻辑本身不复杂解析网页里的图片链接然后下载。要注意的是抓取请求别太频繁加个time.sleep(1)控制节奏不然很容易被服务端拒绝。补数据的目标是让每类图片数尽量接近而不是盲目堆数量类间数量差距超过两倍时模型会明显偏向样本多的类别。3. 模型搭建与训练CNN自建网络和MobileNet迁移学习3.1 CNN.py从零搭建的基线模型CNN.py定义了一个经典结构的卷积神经网络四层卷积加池化接全连接层输出分类。这个模型的定位是基线模型用来和MobileNet做对比证明迁移学习的优势。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(224, 224, 3)), MaxPooling2D((2, 2)), Conv2D(64, (3, 3), activationrelu), MaxPooling2D((2, 2)), Conv2D(128, (3, 3), activationrelu), MaxPooling2D((2, 2)), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(5, activationsoftmax) ])input_shape是224×224×3和MobileNet输入保持一致这样两组实验除了模型结构以外没有其他变量。卷积核尺寸统一用3×3这个尺寸在计算量和感受野之间是平衡点。每层卷积后面接MaxPooling2D把特征图尺寸减半通道数逐层翻倍这是CNN提取特征的标准策略。Flatten层把多维特征图拉成一维向量后面接128维全连接层做特征组合Dropout(0.5)是防过拟合的关键。最后一层Dense(5)对应五个花卉类别softmax激活输出每个类别的概率。如果你换成自己的数据集这里5要改成你的类别数其他地方基本不用动。输入224×224对CNN来说计算量不小如果你的训练速度很慢可以把输入降到128×128第一层卷积的input_shape同步改准确率会降几个点但训练时间能省一半。训练这个自建CNN时我用的是Adam优化器初始学习率0.001训练30到50轮。从训练日志能明显看到CNN在15轮左右就出现过拟合苗头训练准确率奔着95%去验证集却卡在75%上下。3.2 MobileNet.py迁移学习的正确打开方式MobileNet.py的核心不是从零训练而是加载ImageNet预训练权重冻结卷积基只训练自己加的全连接层。这种做法叫迁移学习是花卉识别这类小数据集任务的标准解法。from tensorflow.keras.applications import MobileNet from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout from tensorflow.keras.models import Model base_model MobileNet( weightsimagenet, include_topFalse, input_shape(224, 224, 3) ) base_model.trainable False x base_model.output x GlobalAveragePooling2D()(x) x Dropout(0.5)(x) predictions Dense(5, activationsoftmax)(x) model Model(inputsbase_model.input, outputspredictions)MobileNet在ImageNet上训练过已经学会识别边缘、纹理、形状这些通用特征。include_topFalse表示去掉顶部分类层只保留卷积特征提取部分。base_model.trainable False是冻结权重让反向传播不更新这些层的参数这样训练时只更新我们新加的几层参数量小得多速度快且不容易过拟合。GlobalAveragePooling2D替换Flatten是迁移学习的常见做法它对特征图每个通道取全局平均输出一个长度等于通道数的向量。这样做的好处是参数比Flatten少得多而且不容易过拟合。之后接Dropout(0.5)加Dense(5)输出分类。这里要特别强调trainable的设置时机。如果你先写了base_model.trainable False然后编译模型这个冻结是生效的。但如果你在编译之后才改trainable必须重新编译才会生效否则改动不生效也不会报错训练时你会看到大量参数还在更新这个问题排查起来非常隐蔽。3.3 训练参数对比与日志分析train_log和test_log文件夹里保存了训练和测试的完整日志里面有loss、accuracy在每个epoch的变化还有中间保存的权重文件。解读训练日志是课程设计答辩里最容易拿分的地方你要能从曲线判断出模型状态。参数CNN自建模型MobileNet迁移学习输入尺寸224×224224×224初始学习率0.0010.001Batch Size3232Epoch数4050参数量约170万约320万新加部分约2万过拟合速度15轮左右30轮左右验证准确率约75%约90%两组实验除了网络结构训练参数完全一致这样对比出来的差异才公平。MobileNet收敛速度明显更快前10个epoch验证准确率就能冲到85%以上因为预训练权重已经提供了很强的特征提取能力。CNN到后期出现train和val准确率拉开差距而MobileNet这种情况就温和很多。看日志时重点关注loss曲线的走向。如果train loss持续下降但val loss提前回升说明过拟合开始这时候应该提前终止训练或者回去调大Dropout、增强数据增强强度。项目里results_mobilenet_30_12.png这类文件名里的数字是第30轮第12个batch的意思是训练中间过程保存的结果图配合日志能看到模型从乱猜到逐渐稳定的全过程。4. 测试、可视化与避坑热力图能信吗预测结果怎么解读4.1 测试流程与结果解读MobileNet_Test.py和CNN_Test.py做的事情类似加载训练好的模型权重对测试集图片做预测把预测结果和真实标签比对输出准确率、混淆矩阵并生成可视化图片。from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing import image import numpy as np model load_model(mobilenet/mobilenet_model.h5) img image.load_img(data/test/daisy/100.jpg, target_size(224, 224)) img_array image.img_to_array(img) img_array np.expand_dims(img_array, axis0) img_array img_array / 255.0 pred model.predict(img_array) class_names [daisy, dandelion, rose, sunflower, tulip] print(预测结果:, class_names[np.argmax(pred)], 置信度:, np.max(pred))加载模型用load_model一行搞定路径要确认对。预测前图像处理要和你训练时保持一致target_size必须是224×224归一化方式也要一致。训练用1.0/255.0归一化预测就必须用同样的值这里不一致会导致预测结果莫名其妙地差。np.expand_dims把单张图片扩展成四维张量多出来的那一维是batch维。Keras模型接收的输入形状是(batch_size, height, width, channels)单张图片也要补成(1, 224, 224, 3)。class_names的顺序必须和训练时数据生成器自动生成的类别顺序一致这个顺序是按文件夹名称字母排序的你可以在训练时打印train_generator.class_indices确认写错的话结果图上的标签全是乱的。置信度不是越高越可信。如果某张预测置信度95%但结果是错的这说明模型过于自信但判断错了常见于训练样本太少或者类别间外观过于相似的情况。课程设计的测试报告里建议除了准确率再贴几张错分案例分析是什么原因导致模型认错了这部分分析比准确率数字本身更有价值。4.2 热力图可视化理解模型在看什么heatmap文件夹里是Grad-CAM热力图Intertation.py和Intertation_MobileNet.py负责生成。Grad-CAM的作用是告诉你模型做判断时图像的哪个区域贡献最大是深度学习可解释性里最常用的工具。import numpy as np import tensorflow as tf def grad_cam(model, img_array, layer_name): grad_model tf.keras.models.Model( inputs[model.inputs], outputs[model.get_layer(layer_name).output, model.output] ) with tf.GradientTape() as tape: conv_output, predictions grad_model(img_array) true_class np.argmax(predictions[0]) loss predictions[:, true_class] grads tape.gradient(loss, conv_output) weights tf.reduce_mean(grads, axis(1, 2)) cam np.zeros(conv_output.shape[1:3], dtypenp.float32) for i in range(conv_output.shape[-1]): cam weights[0, i] * conv_output[0, :, :, i] return camlayer_name要选最后一层卷积层因为它的空间分辨率和语义特征最平衡。MobileNet的最后一层卷积层叫conv_pw_13_relu自建CNN的最后一层卷积是第3个conv层具体名字用model.summary()查写错层名会直接报错。GradientTape是TensorFlow的自动求导上下文。计算流程是先对输入图做一次前向传播拿到特征图和预测结果确定预测概率最大的类别再对这个类别的loss反向求梯度。梯度的含义是特征图每个位置对预测结果的影响程度。weights是按通道求平均梯度代表每个特征通道的总体贡献然后按通道加权累加得到一张和特征图尺寸相同的响应热力图。热力图不是最终可视化结果后面还要把它resize回原图尺寸叠加到原图上。叠加的透明度alpha建议控制在0.4到0.5之间太透明看不清热区太浓会遮住原图细节。看到模型在花瓣中心区域激活最强说明它学到的是花卉本身的形态特征如果激活区域在背景上说明模型偷懒学到的可能是背景特征而不是花朵特征这时就要考虑增强数据或者换训练集扩充策略。4.3 避坑指南这五个坑我全部踩过第一个坑是数据集太小导致明显过拟合。现象训练准确率99%验证集只有65%train和val曲线越分越开。原因五类花卉总共28张图CNN参数量远超样本量模型把训练集细节背下来了。解决开满数据增强迁移学习用MobileNetDropout提到0.5以上这是这套源码设计好两条路线的原因——用结果对比来论证过拟合。第二个坑是预测时归一化方式和训练不一致。现象训练准确率很高测试时predict结果惨不忍睹。原因训练用1.0/255.0测试时忘了归一化直接拿原像素值0到255的数组输入模型。解决检查测试脚本里有没有除255没有就补上这个错误不会报错只会让结果变差很难排查。第三个坑是层名写错导致热力图生成失败。现象grad_cam调用报错说找不到层。原因不同模型层命名差异很大MobileNet的层名是conv_pw_13_relu这种格式自己写的CNN层名是conv2d_1这种。解决先运行model.summary()打印全量层结构手动确认要选的卷积层名称再传给grad_cam。第四个坑是文件路径中文乱码。现象flow_from_directory报编码错误或者读图失败。原因数据文件夹名或者图片名里有中文TensorFlow在Windows下处理中文路径容易出问题。解决统一改成英文字母和下划线的目录结构图片名也改成纯英文这种问题排查起来特别浪费时间一开始就用英文最省心。第五个坑是GPU显存不足。现象训练时OOM报错。原因224×224输入加32的batch size对显存有一定要求。解决batch_size从32改到8或16或者把target_size降到160×160两个模型都同步改。显存不够时优先降batch size对准确率影响最小。5. 进阶用法与调试技巧冻结层微调、win.py可视化界面与结果图拼比上面已经把两个模型完整跑通接下来这段是我实际调试中比较有用的一层——弄懂权重冻结和微调。前面MobileNet部分把整个卷积基全部冻结这是标准做法但有个更好的思路训练几轮、把全连接层调好后把最后几层卷积解冻用很小的学习率一起微调。base_model.trainable True for layer in base_model.layers[:80]: layer.trainable False model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-5), losscategorical_crossentropy, metrics[accuracy] )解冻后训练必须把学习率降下来1e-5比初始学习率低两个数量级。原因是预训练权重已经很好了学习率太大会把原有特征破坏掉学到的还是之前全连接层学到的决策边界。用model.layers[:80]保留前八十层不动只微调后面的层这些层学到的特征更接近任务本身。win.py是图形界面用了简单的文件选择和模型加载逻辑完成后预测和热力图生成都可以在窗口里操作。对于课程设计答辩来说演示效果比在命令行里跑脚本直观得多。如果你要改这个界面核心逻辑在按钮事件里选择图片、加载模型、调用预测函数、把结果和热力图显示到画布上。注意界面预测和脚本测试必须共用一套预处理函数不然又会出现归一化不一致的问题。最后说一个实用的习惯。每次我训练完一组实验会把results_cnn_4.png和results_mobilenet_50_14.png这类图放到同一个文件夹文件名保留轮数和batch数做一组Excel表格记录每组实验的参数、准确率、过拟合情况。答辩时把几组对比图并排放说明为什么迁移学习在小数据集上表现更好比背一百行代码更能体现你真正理解了深度学习流程。从那以后我每次做图像分类都强制自己先跑通一个小型基线模型记录准确率和训练时间再上迁移学习做对比。有了基线所有后续优化的效果都是可量化的而不是拍脑袋说提升了不少。希望这个项目的源码和这份拆解能帮到你按照文章里的步骤跑一遍再把数据和类别换成自己的一套完整的课设报告素材就到手了。本文还有配套的精品资源点击获取
返回列表