ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

水果识别实战:从CNN基线到MobileNetV2迁移学习

水果识别实战:从CNN基线到MobileNetV2迁移学习 简介这是一份面向计算机专业学生的深度学习期末大作业完整项目聚焦利用CNN与MobileNetV2搭建水果识别模型可满足课程设计、期末大作业或项目实战练习需求。资源内含多个相关子项目包括源代码、文档说明、水果图像数据集与实验报告完整覆盖数据准备、模型构建、训练评估到结果分析的流程。压缩包共2000个文件以1667个jpg和260个jpeg图像数据集为主另有少量png图片、xml标注文件、4个py脚本与md说明文档整体约660MB目录结构清晰便于按模块查阅。已有377人学习下载该项目曾获导师指导并取得98分评审成绩具备较高完成度和参考价值。通过研读该项目读者能快速理解CNN与MobileNetV2在图像分类任务中的具体实现掌握数据增强、迁移学习等关键技巧并借鉴高分项目的实验设计思路与文档撰写方法。1. 这是一份把「会调参」和「真懂」区分开来的大作业水果识别是深度学习入门阶段性价比最高的实战题目数据好找、类别直观、单张图片处理压力小但想拿高分并不容易。同样的MobileNetV2有人调出来准确率93%有人只有78%差别往往不在网络结构而在数据怎么切、学习率怎么退火、实验对比怎么做。这篇博文从一份典型的大作业标题出发把我自己搭这套系统时的完整思路写出来包括从零训练的CNN基线、预训练MobileNetV2的迁移学习、两套模型的对比方法和实验报告的写法。适合正在做课程设计、毕业设计或者想把分类任务从「能跑」做到「有说服力」的从业者。文中所有代码基于TensorFlow/Keras数据集用常见水果分类数据集即可复现。2. 数据集的划分与预处理先定好评估规则再谈模型精度2.1 水果识别数据集最常见的两个坑类别不均衡和图像尺寸不统一自己收集或者下载的水果数据集几乎必然存在两个问题。第一个是类别不均衡比如苹果有2000张而杨桃只有300张如果直接拿来训练模型会倾向于把不确定的样本都预测成苹果。第二个是原始图片尺寸差异极大手机拍摄的图片从几百像素到几千万像素都有直接喂给网络会导致批量填充padding时出现大量无效区域。这两个问题不解决后面做再多的模型对比都是无效的。因为准确率的差距可能只是数据划分方式造成的而不是模型结构带来的。正确做法是先定一套固定的预处理流水线所有模型包括CNN基线和MobileNetV2都走同一套流程这样最后对比实验才有意义。2.2 用tf.keras.preprocessing.image_dataset_from_directory搭建标准化数据管线的具体命令我一般会先用image_dataset_from_directory按目录结构自动加载图像数据。目录结构要求每个类别一个文件夹比如dataset/train/apple/、dataset/train/banana/这样写起来最省事。import tensorflow as tf IMG_SIZE (224, 224) BATCH_SIZE 32 train_ds tf.keras.preprocessing.image_dataset_from_directory( dataset/train, validation_split0.2, subsettraining, seed42, image_sizeIMG_SIZE, batch_sizeBATCH_SIZE, ) val_ds tf.keras.preprocessing.image_dataset_from_directory( dataset/train, validation_split0.2, subsetvalidation, seed42, image_sizeIMG_SIZE, batch_sizeBATCH_SIZE, )这里validation_split0.2表示从训练集里切出20%作为验证集seed42保证每次切分结果一致。image_size(224, 224)是MobileNetV2的标准输入尺寸当然如果只跑CNN基线用128×128就够了但在同一套流程里统一成224可以少一套代码。逻辑说明image_dataset_from_directory会自动把子目录名映射为类别标签标签按字母序编码。它还内置了cache()和prefetch()的调用时机问题——注意这里没有加因为如果加了cache()数据增强层要在cache()之前执行否则增强只作用于第一批数据。数据增强是另一个关键点。data_augmentation tf.keras.Sequential([ tf.keras.layers.RandomFlip(horizontal), tf.keras.layers.RandomRotation(0.1), tf.keras.layers.RandomZoom(0.1), ]) def prepare(ds, augmentFalse): ds ds.map(lambda x, y: (tf.image.resize(x, IMG_SIZE) / 255.0, y), num_parallel_callstf.data.AUTOTUNE) if augment: ds ds.map(lambda x, y: (data_augmentation(x), y), num_parallel_callstf.data.AUTOTUNE) return ds.prefetch(tf.data.AUTOTUNE) train_ds prepare(train_ds, augmentTrue) val_ds prepare(val_ds)参数说明RandomFlip(horizontal)只做水平翻转对水果来说垂直翻转会破坏「悬挂」的方向特征没必要用。RandomRotation(0.1)是±10%弧度的旋转再大可能让圆形水果和椭圆水果混淆。数据增强在训练集上做验证集只做缩放和归一化这样才能反映模型在真实场景下的表现。2.3 类别不均衡的处理class_weight的计算与设置如果确认数据不均衡最简单的办法是为损失函数按类别加权。import numpy as np from collections import Counter counts Counter() for _, labels in train_ds.unbatch(): counts.update(labels.numpy()) total sum(counts.values()) class_weight {i: total / (len(counts) * c) for i, c in counts.items()} print(class_weight)在model.fit()里传入这个字典即可。需要注意的是class_weight会在反向传播时放大少数类的梯度但也会让模型对多数类的召回率下降。如果用了这个之后验证集整体准确率反而掉了说明数据不均衡对当前任务影响不大可以去掉。3. 从零搭建CNN基线把手写卷积核的过程讲清楚3.1 为什么先写一个不完美的CNN基线直接上MobileNetV2准确率轻松到90%以上但老师问「MobileNetV2比基础CNN好在哪里」时没有基线数据是答不上来的。CNN基线的意义不是追求精度而是作为对比参照系你用相同的训练策略跑基线CNN和MobileNetV2得到的差距就是网络结构本身带来的增益这是实验报告里最有说服力的一页。基线的结构不必复杂3组「卷积池化」加一个全连接层就够。3.2 水果分类CNN基线的Keras实现与层参数选择def build_cnn_baseline(input_shape(224, 224, 3), num_classes6): model tf.keras.Sequential([ tf.keras.layers.Conv2D(32, (3, 3), activationrelu, input_shapeinput_shape), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Conv2D(64, (3, 3), activationrelu), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Conv2D(128, (3, 3), activationrelu), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Flatten(), tf.keras.layers.Dense(128, activationrelu), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(num_classes, activationsoftmax) ]) return model逻辑说明第一层卷积核数量32对224×224的输入来说感受野已经足够捕捉苹果的轮廓、香蕉的弧形这类大尺度特征。如果输入改成128×128第一层用16个卷积核就够了因为图像信息量少。每个池化层把特征图尺寸缩小一半经过3组卷积池化后224×224变成28×28。这里有个值得注意的参数Dropout(0.5)放在全连接层前面。这个0.5的含义是训练时随机让50%的神经元失活。对水果分类这种类别数少、特征明显的任务0.5偏大有时会欠拟合改成0.3更稳妥。我的习惯是先用0.5跑一版如果验证loss在训练集准确率还在涨的时候就停滞了就把Dropout调小到0.3。3.3 训练策略SGD的动量设置为什么对CNN很重要基线模型参数少用SGD加动量就能训好不必上Adam。model.compile( optimizertf.keras.optimizers.SGD(learning_rate0.01, momentum0.9), losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( train_ds, epochs30, validation_dataval_ds, )learning_rate0.01是经验值适用于小规模CNN。如果loss前5轮不下降把它降到0.001重试。momentum0.9是标准配置它让梯度更新在方向一致时叠加能够有效穿过局部极小值。对水果识别这类图像背景相对简单的任务30轮足够epoch数再大就过拟合了——验证loss会回升训练准确率接近100%。训练完成后保存模型和训练日志。后面对比MobileNetV2时在相同epoch下比较两者的训练曲线和最终验证准确率。4. MobileNetV2迁移学习的参数细节冻结、解冻与微调4.1 MobileNetV2的深度可分离卷积在水果识别上的计算优势MobileNetV2的核心思路是把标准卷积拆成深度卷积和逐点卷积两步参数数量约为标准卷积的1/9。在水果识别这种类别间纹理差异不算大的任务上这种结构不会显著损失精度但训练和推理速度快很多。没有GPU的实验室机器上MobileNetV2也能在几分钟内完成一个epoch。另一个关键设计是倒残差结构Inverted Residual Block先用1×1卷积升维再用3×3深度卷积最后用1×1卷积降维。这和我们手写的CNN正好相反——手写的CNN先降维再升维。倒残差结构之所以有效是因为在升维后的高维空间做深度卷积能保留更多细粒度特征——苹果表面的高光、橙子表皮的颗粒感这类特征在低维空间容易被丢弃。4.2 用tf.keras.applications加载预训练权重并替换分类头的完整实现base_model tf.keras.applications.MobileNetV2( input_shape(224, 224, 3), include_topFalse, weightsimagenet ) base_model.trainable False model tf.keras.Sequential([ base_model, tf.keras.layers.GlobalAveragePooling2D(), tf.keras.layers.Dense(128, activationrelu), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(num_classes, activationsoftmax) ])参数说明include_topFalse表示不要ImageNet的1000类分类头只保留卷积基。GlobalAveragePooling2D()把7×7×1280的特征图压缩成1280维向量相比Flatten()它没有新增参数量而且天然抗过拟合。加了一个128维的全连接层目的是让模型在水果类别上学习一个中间的语义表示而不是直接从1280维映射到6类——后者往往导致模型只依赖某个单一特征通道泛化能力差。这个阶段要用较大学习率快速训练分类头。model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), losssparse_categorical_crossentropy, metrics[accuracy] ) history_frozen model.fit( train_ds, epochs10, validation_dataval_ds, )4.3 解冻部分层微调时的学习率策略和层选择冻结训练10轮后验证准确率通常会稳定在85%~92%之间。此时解冻base_model的部分层做微调让预训练权重适应水果图像的分布。base_model.trainable True for layer in base_model.layers[:100]: layer.trainable False model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-5), losssparse_categorical_crossentropy, metrics[accuracy] ) history_finetune model.fit( train_ds, epochs15, validation_dataval_ds, )解冻层数是个关键权衡。MobileNetV2一共154层左右前100层学到的是通用边缘、纹理特征这些在自然图像上得到的能力可以直接迁移到水果识别但最后20多层的特征已经高度面向ImageNet的类别比如动物、交通工具需要重点微调。如果将所有层都解冻低层的学习率1e-5太小高层又不够效果往往不如只微调最后几十层。这里有一个常见误区把学习率从0.001直接降到1e-5之后如果验证准确率反而下降不要急着改学习率先检查是否忘记把base_model.trainable设为True。很多人解冻了但没设置训练标记模型实际还在用冻结状态训练。通过model.summary()可以查看各层Trainable params是否为非零。另外微调阶段epochs不宜过多。15轮以后如果验证loss不再下降继续训练只会过拟合到训练集的背景和光照对测试集没有帮助。5. 实验对比的可视化方法与实验报告的数据组织5.1 用混淆矩阵定位错误类别而不是只看准确率两个模型训练完成后准确率只是一个数字实验报告更需要展示模型在哪里犯错。混淆矩阵是标准工具。import numpy as np from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt y_true [] y_pred [] for images, labels in val_ds: preds model.predict(images) y_pred.extend(np.argmax(preds, axis1)) y_true.extend(labels.numpy()) cm confusion_matrix(y_true, y_pred) class_names train_ds.class_names plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(Predicted) plt.ylabel(True) plt.savefig(confusion_matrix.png)打印混淆矩阵后重点关注两类错误一是对角线数字明显偏小表示该类别本身难识别二是某个非对角线数字偏大表示两个类别持续混淆。水果数据集里最典型的是「青苹果」和「梨」互相误判因为它们形状接近、颜色重叠。如果出现这种情况应该去检查验证集图片看是不是两类图片拍摄背景都是木桌、光照条件相似——模型可能学的是背景而非水果本身。如果确认背景干扰需要用数据增强里的RandomContrast或裁剪去掉背景。5.2 训练曲线对比图体现过拟合与迁移学习优势实验报告里必须有两张图训练集/验证集的准确率曲线和loss曲线。画图时把CNN基线和MobileNetV2的曲线画在一张图里标注清晰。plt.figure() plt.plot(history.history[accuracy], labelCNN train) plt.plot(history.history[val_accuracy], labelCNN val) plt.plot(history_frozen.history[val_accuracy], labelMobileNetV2 (frozen)) plt.plot(history_finetune.history[val_accuracy], labelMobileNetV2 (finetune)) plt.legend() plt.xlabel(Epoch) plt.ylabel(Accuracy) plt.savefig(training_comparison.png)这个对比能直观看出MobileNetV2在前10轮内准确率就超过CNN基线30轮的水平这是预训练特征的有效性。同时观察CNN基线的训练曲线是否出现训练集准确率继续上升而验证集持平甚至下降的「开口」——如果开口存在就是过拟合的实证。此时在报告里写「训练30轮后验证loss上升模型开始记忆训练集中的背景特征」比空泛地写「存在过拟合」要有说服力得多。5.3 实验报告的数据呈现定量的准确率对比表报告最后放一个汇总表把关键实验数字列清楚字段至少包括模型、参数数量、训练时长、验证集准确率、模型大小。一张表可以把「为什么最终选MobileNetV2」说透。模型参数量(万)单epoch耗时(s)验证准确率(%)CNN Baseline (3 Conv 2 FC)约80约2586.2MobileNetV2 (frozen)约246约4090.8MobileNetV2 (finetune)约246约9594.5填写时注意口径统一所有实验用同一训练集划分、同一数据增强配置、同一batch_size。如果某个实验因OOM改小了batch_size在表格下方注明否则两个模型的对比就不公平。训练时长受GPU型号影响很大标注具体的GPU型号比如GTX 1660或Tesla T4这是实验报告的标准做法。5.4 最后一招对最容易出错的「源代码可复现性」做一次排查大作业交付时代码能跑通比准确率数字更重要。常见坑有两个一是image_dataset_from_directory在Windows系统下如果数据集路径包含中文会报编码错误建议全部改成英文字母命名二是代码里写了绝对路径如C:\Users\张三\导致评分老师换机器就跑不了统一改成相对路径。交付前删掉所有.ipynb_checkpoints和__pycache__把数据集分割脚本放到split_data.py里确保别人拿到仓库后能按顺序执行完得到一个和实验报告一致的结果。本文还有配套的精品资源点击获取
返回列表