深度学习水果识别系统:PyTorch图像分类与迁移学习实战

发布时间:2026/10/4 20:16:46
深度学习水果识别系统:PyTorch图像分类与迁移学习实战 简介面向计算机类专业毕业生与深度学习初学者一份可运行的水果识别系统毕业设计资源以水果图像分类为场景完整覆盖从数据处理、模型训练到界面展示的实践链路。源码均已在本地编译通过评审分达九十五分以上内容经助教审定难度适中适合课程设计、毕业设计或项目复现。压缩包共277个文件以Python源码、网页前端HTML/CSS/JS、模型文件、数据集图片及说明文档为主体积约17.6MB目录结构清晰便于按模块查阅。其中大量JavaScript与GIF资源用于构建动态交互界面CSS负责视觉样式模型与数据集可直接用于训练验证。目前已累计398人学习下载借助这套资料可快速理解深度学习分类项目的组织方式节省环境搭建与排错时间同时参考文档说明完成论文撰写与答辩准备。1. 基于深度学习的水果识别系统先搞清它到底是个什么项目如果你在找“Python毕业设计-基于深度学习的水果识别系统”大概率是要交付一套能跑得起来的图像分类项目输入一张水果照片模型告诉你这是苹果、香蕉、橙子还是别的。这类项目看起来像标准的深度学习图像分类任务但做成毕业设计时真正的坑不在网络结构而在数据怎么组织、训练怎么收敛、识别结果怎么和论文里的指标对齐。我拆过不少这类课题先说一句反直觉的结论模型的代码往往最短文档说明和数据集整理才是最耗时间的部分。这个方向适合想做计算机视觉入门、又需要完整交付物的在校生也适合想快速验证训练流程的从业者。下面按一条我能复现的路线讲透。2. 选型决定成败水果识别用哪条技术路线2.1 为什么是深度学习而不是传统视觉水果识别不是新问题。十年前常见的做法是颜色直方图加支持向量机或者提取纹理特征后做分类。传统方法的麻烦在于水果的形状、颜色、成熟度在不同光照下差异巨大一个红苹果在暖光灯下拍出来可能像橙子。你需要手工设计特征而且换一批数据就要重新调特征这在实际项目里会让人抓狂。深度学习的逻辑是完全端到端输入原始像素网络自己学习该看颜色、纹理还是轮廓。CNN 对局部特征的提取能力天然适合这类问题。特别是用迁移学习时在 ImageNet 上预训练过的模型已经学会了边缘、纹理、形状这样的通用视觉特征你只需要在水果数据上做微调就能用少量数据拿到很好的精度。我一般把这个问题定义为图像分类任务用 PyTorch 或 TensorFlow 实现。哪个框架更合适如果追求调参直观、调试方便PyTorch 的生态和代码可读性更符合毕业设计的节奏如果以后想转部署和工业落地TensorFlow 的 SavedModel 和 TFLite 链路更顺。我个人做这类课题默认选 PyTorch下面所有代码也按它写。2.2 网络怎么选ResNet、MobileNet 还是自己搭有些同学喜欢从零搭一个 CNN比如三层卷积加两层全连接。这种模型在简单场景能跑通但问题有两个一是参数量并不少二是没有预训练权重收敛很慢准确率上限低。毕业设计答辩时老师问“为什么用这个结构”你很难说清楚。常见做法是在成熟主干网络上微调。我整理了一个选型对比表供参考网络参数量推理速度准确率适合场景自建小型 CNN约 1~5M快中低演示原理、框架入门ResNet18约 11M中高精度优先的毕设主线ResNet50约 25M中慢很高数据量大时再用MobileNetV3约 4~6M快中高后续想部署到手机或树莓派EfficientNet-B0约 5M中高平衡精度和体量我的建议很简单默认 ResNet18 或者 MobileNetV3-Large。ResNet18 在 CPU 上训练速度尚可在 GPU 上几分钟一个 epoch而且预训练权重到处都有MobileNetV3 更适合你后续答辩时加一句“本系统在边缘设备上也能运行”这是加分项不是负担。2.3 数据集要按什么格式组织很多项目翻车都是因为数据集文件夹结构有问题。PyTorch 的 torchvision.datasets.ImageFolder 要求数据按类别分文件夹且文件夹名字就是类别名。最省事的目录结构如下data/ ├── train/ │ ├── apple/ │ │ ├── 001.jpg │ │ └── 002.jpg │ ├── banana/ │ │ ├── 001.jpg │ │ └── 002.jpg │ └── orange/ │ ├── 001.jpg │ └── 002.jpg ├── val/ │ ├── apple/ │ └── banana/ └── test/ ├── apple/ └── orange/train 用于训练val 用于每个 epoch 结束后的验证和模型选择test 是最后评估泛化能力的“裸数据”。注意 test 在训练过程中绝不能碰这是防止你对着测试集调参的黑匣子。如果你用的公开数据集把图片全放在一个文件夹里只有标注文件那就需要自己写划分脚本。这一步看着简单却是数据泄漏频发的重灾区划分时如果先打乱所有图片再划分可能同一张图既进了训练集又进了验证集。正确做法是基于文件路径或索引做随机划分保证互斥。3. 环境搭建与数据准备最容易被低估的一步3.1 Python 环境与深度学习框架怎么配环境装不好后面每一步都在填坑。我建议先装 Python 3.9~3.11 的 64 位版本然后新建虚拟环境不要直接往系统 Python 里装包。用 venv 或 conda 都可以我的习惯是 conda因为换 CUDA 版本时不会把系统搞乱。安装 PyTorch 时最容易翻车的是选了和本机 CUDA 不匹配的版本。如果你不确定显卡驱动支持哪个 CUDA 版本先在命令行里跑 nvidia-smi看右上角的 CUDA Version。那只是驱动支持的最高版本不代表你必须按它装。PyTorch 的官方安装命令会给出 cpu、cuda 11.8、cuda 12.1 等选项我一般选 cuda 11.8兼容性最稳。# 创建虚拟环境并激活 conda create -n fruit python3.10 -y conda activate fruit # 安装 PyTorch以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 装训练和可视化相关依赖 pip install matplotlib tensorboard scikit-learn jupyter装完验证一下 GPU 是否可用python -c import torch; print(torch.cuda.is_available()); print(torch.__version__)输出第一行是 True说明 PyTorch 能调用 GPU是 False 也不要慌先检查驱动再检查是不是装了 CPU 版。CPU 版也能完成整个毕设只是训练慢不少ResNet18 在纯 CPU 上一个 epoch 可能要十分钟以上而 GPU 只需要几十秒。3.2 数据集划分脚本三分钟写对别犯数据泄漏我见过太多人手动把图片拖进文件夹结果验证集和训练集里出现同一张照片的不同副本——这会让最后的准确率虚高答辩时经不起问。写一个划分脚本按固定随机种子执行保证任何人都能复现你的数据划分。import os import random import shutil # 原始数据路径每个类别一个文件夹 source_dir raw_fruit train_dir data/train val_dir data/val test_dir data/test # 固定随机种子保证结果可复现 random.seed(42) split_ratio (0.8, 0.1, 0.1) # 训练/验证/测试比例 for class_name in os.listdir(source_dir): class_path os.path.join(source_dir, class_name) if not os.path.isdir(class_path): continue images [f for f in os.listdir(class_path) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(images) n_train int(len(images) * split_ratio[0]) n_val int(len(images) * split_ratio[1]) # 余下全归测试集避免因四舍五入丢数据 n_test len(images) - n_train - n_val for dest_dir, subset in [(train_dir, images[:n_train]), (val_dir, images[n_train:n_train n_val]), (test_dir, images[n_train n_val:])]: os.makedirs(os.path.join(dest_dir, class_name), exist_okTrue) for img in subset: shutil.copy( os.path.join(class_path, img), os.path.join(dest_dir, class_name, img) )这段脚本的逻辑是按类别先打乱再切三段。注意这里用的是 shutil.copy 而不是 move目的是保留原始数据万一后续想调整类别或比例还有“后悔药”。参数说明split_ratio 可以改成你需要的比例比如 0.7/0.15/0.15random.seed 的值可以随意但一旦定下后不要改改了数据划分就变了。3.3 预处理和数据增强让模型学会“看什么”采集到的水果照片有各种角度、光照和背景。直接拿原始图片训练模型容易记住背景颜色而不是水果本身。数据增强的作用是人为制造变化逼模型学到不变的特征。PyTorch 里的 torchvision.transforms 是标配。from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees15), 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]) ]) val_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]) ])训练集和验证集的预处理必须不同训练集有随机翻转、旋转、颜色扰动验证集只做缩放和归一化。这里有两个容易踩的点一是忘了对验证集做 Normalize导致验证时像素分布和训练时不一致准确率莫名其妙掉几个点二是 Normalize 的均值和标准差用了自己的统计值但预训练模型要求的是 ImageNet 的标准值——迁移学习时不要改这两个数。3.4 DataLoader 参数怎么设数据加载器用 ImageFolder 最省事它会自动读取 train 目录下的类别文件夹并按字母顺序给类别编号。这一点非常关键因为它的编号顺序直接决定后面对应关系。from torchvision import datasets from torch.utils.data import DataLoader batch_size 32 num_workers 4 # Windows 上建议设 0见避坑章节 train_dataset datasets.ImageFolder(rootdata/train, transformtrain_transform) val_dataset datasets.ImageFolder(rootdata/val, transformval_transform) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue, num_workersnum_workers) val_loader DataLoader(val_dataset, batch_sizebatch_size, shuffleFalse, num_workersnum_workers) # 查看类别到索引的映射 print(train_dataset.class_to_idx)参数说明里最容易忽略的是 shuffle训练集必须 True验证集必须 False。验证集打乱没有意义而且会让评估结果随机波动。batch_size 的选择要看显存我建议从 32 开始如果 OOM 就减半到 16。num_workers 在 Windows 下经常因为多进程问题报错一个字设 0损失的只是加载速度换来了稳定。4. 训练与模型保存怎么把准确率练上去4.1 训练脚本的主体框架训练脚本是整个项目的心脏。这里采用迁移学习加载在 ImageNet 上预训练好的 ResNet18只替换最后的全连接层。这样做的原因很简单——水果图片的底层特征边缘、纹理和 ImageNet 里的物体是通用的你不需要从零学。import torch import torch.nn as nn from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_classes len(train_dataset.classes) # 替换最后一层输出类别数 model.fc nn.Linear(model.fc.in_features, num_classes) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) best_acc 0.0 for epoch in range(20): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f}) # 验证 model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc 100 * correct / total print(fVal Acc: {val_acc:.2f}%) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth)几个关键参数说明optimizer 我偏爱 Adam 而不是 SGD因为 Adam 对学习率没那么敏感不适合写论文时用 SGD 加动量调参比较麻烦。如果你追求最终精度更高可以在基准跑通后再切到带动量的 SGD学习率从 0.005 往下调。CrossEntropyLoss 内部已经包含 softmax所以不要在模型输出后面再接一层 softmax否则梯度计算会出问题。best_model.pth 只在验证集准确率创新高时保存而不是每个 epoch 覆盖——这样哪怕后面训练跑偏手里始终有最好的版本。4.2 学习率衰减别高估网络的“适应能力”训练到十几个 epoch 时损失下降会明显变慢。如果学习率保持不变loss 会在一个平台期来回震荡像心跳图一样看着心烦。这时候需要降低学习率让权重更新步长变小在损失曲面里钻到更优的位置。PyTorch 的常见做法有两种StepLR 和 ReduceLROnPlateau。我更推荐后者因为它不是机械地每隔几步降学习率而是“损失不降了才降”更符合实际情况。from torch.optim.lr_scheduler import ReduceLROnPlateau scheduler ReduceLROnPlateau( optimizer, modemin, factor0.1, patience3, verboseTrue )用法是在每个 epoch 的验证集 loss 计算完后调用 scheduler.step(val_loss_mean)传进去的是验证损失。modemin 表示监测的指标越小越好patience3 表示容忍连续 3 个 epoch 没有下降第 4 个 epoch 才降学习率。这里有个细节如果你没有计算验证集 loss而是整个脚本报错了要记得先确认 val 阶段有没有把梯度关闭也就是有没有用 torch.no_grad()。没用的话验证时会额外消耗显存而且把 BN 层的统计量带偏。4.3 模型保存不只是存一个文件毕设要求“源代码文档说明数据集模型”很多新手把 torch.save(model.state_dict(), model.pth) 一存就完事。但模型文件和代码是强耦合的如果别人加载这个 .pth 文件时不知道类别数、不知道用的什么网络结构、不知道预处理方式这个文件就只能是个黑匣子。我习惯在项目根目录放一个 requirements.txt同时把类别映射写进一个 JSON 文件和模型放在一起import json class_names train_dataset.classes # 按 ImageFolder 的顺序 index_to_name {i: name for i, name in enumerate(class_names)} with open(fruit_labels.json, w, encodingutf-8) as f: json.dump(index_to_name, f, ensure_asciiFalse, indent2)这样任何拿到项目的人打开 fruit_labels.json 就知道“模型的第 0 类是 apple第 1 类是 banana”不需要反编译代码去猜。这个文件在你的文档说明里也应该被提到它是连接源码和模型的桥梁。训练完成后再看一眼模型参数量和计算量from torchsummary import summary summary(model, input_size(3, 224, 224), devicecpu)输出会告诉你总参数量和模型大小写毕业论文时直接引用这个数比你瞎估准确得多。5. 水果识别项目的常见坑与排查手册5.1 数据加载时报 FileNotFoundError目录结构被系统排序打乱现象ImageFolder 能读取但训练时偶尔报找不到图片尤其是从网上下载的数据集带一些隐藏文件。原因Mac 下数据集文件夹里经常有 .DS_StoreWindows 下可能有 desktop.iniImageFolder 在遍历时会把它们当作图片文件处理后面读取到这些文件自然失败。解决在数据划分脚本里先过滤掉非图片后缀。如果问题已经发生直接在命令行清理find data/ -name .DS_Store -delete find data/ -name Thumbs.db -delete更稳妥的办法是在划分脚本里只保留明确后缀的文件这是我写代码时的一个习惯能省不少麻烦。5.2 损失不降或验证集准确率一直不涨现象训练了好几个 epochloss 在前两轮从 2.3 掉到 1.9 后就不动了验证集准确率一直在 30% 左右徘徊和随机猜测差不多。原因最常见的有两类。第一类是数据集的图片本身有问题比如标签和图片内容对不上第二类是预训练模型加载失败实际是在从头训练特征还没学到。解决先排除数据问题——随机挑几组 9 张图片的网格图用 matplotlib 画出来看一下。我遇到过数据集里“苹果”文件夹混入了一堆西红柿图片肉眼根本发现不了。网格可视化不需要额外写复杂脚本两分钟就够import matplotlib.pyplot as plt # 从 train_loader 取一个 batch 展示 images, labels next(iter(train_loader)) grid torchvision.utils.make_grid(images[:9], nrow3) grid grid.permute(1, 2, 0) plt.imshow(grid.numpy()) plt.axis(off) plt.show()如果图片没问题再检查预训练权重。weightsmodels.ResNet18_Weights.IMAGENET1K_V1 这段代码在断网环境下会下载失败但 PyTorch 可能不会报错而是直接在没有预训练权重的情况下新建模型。所以训练日志里第一个 epoch 的 loss 如果接近 log(类别数)基本可以断定就是没加载上权重这时去看模型文件是否下载完整。5.3 显存不足 OOM程序直接崩溃现象训练刚开始没几分钟弹出 CUDA out of memory进程被杀。原因没有立刻崩在第一个 batch说明显存被逐步占满。通常是 batch_size 太大或者输入图片的分辨率太高。解决我最常用的处理是先把 batch_size 减半还崩就再减半。另一个很容易被忽略的坑是 num_workers 在 Windows 下每开一个进程都会复制一份内存如果机器内存不够也会被误报成显存问题。验证方法很简单把 num_workers 改成 0同时 batch_size 改 16再跑一次。如果问题消失就是机器资源上限问题不是代码问题。5.4 训练到中途 Loss 变成 NaN现象loss 在某个 epoch 突然打印出 nan之后所有指标全部变成 nan。原因最常见的是学习率太大梯度爆炸权重更新后变成非数值少数情况是数据里有损坏的图片读取出来是空像素没法计算梯度。解决先降低学习率Adam 场景下从 0.001 降到 0.0001多数能救回来。如果降完还是 nan就去检查数据集里是否有 0 字节的损坏图片find data/ -type f -size 0 -delete还有一个血泪经验不要在模型输出层之前用 torch.nan_to_num 去隐藏问题这是掩耳盗铃训练过程照样会发散。真正要做的是定位是数据问题还是超参问题。5.5 训练、验证都正常但推理时识别结果全错现象模型在测试集上的准确率 95%但是拿手机拍一张图片预测每次都输出错误类别而且置信度很高。原因这是项目里最常见的场景偏置。训练数据大多是电商图或实验室采集图——背景干净、光线均匀、水果在画面正中央。你手机拍的照片背景复杂、曝光不同模型学到的特征里混入了背景信息。解决不要指望模型自己适应。我一般会给自己的推理脚本增加一个置信度阈值低于阈值就拒绝识别把图片输出到一个 unknown 文件夹。比如prob, idx torch.max(torch.nn.functional.softmax(outputs, dim1), dim1) if prob.item() 0.8: return 不确定请重新拍摄这样能过滤掉相当一部分乱猜的情况。要进一步提升智商就得做数据增强里的青椒等扰动或者在背景上做随机抠图替换。这已经属于进阶调优毕业论文里写出来反而是亮点。6. 把模型用起来单张图片推理脚本与最终校验训练完的模型最终要落到一个可展示的推理功能上。我习惯写一个 inference.py让答辩演示时一句命令就能出结果import torch import json from PIL import Image from torchvision import transforms, models # 加载映射和模型 with open(fruit_labels.json, r, encodingutf-8) as f: idx_to_name {int(k): v for k, v in json.load(f).items()} model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, len(idx_to_name)) model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(test_apple.jpg).convert(RGB) x preprocess(img).unsqueeze(0) with torch.no_grad(): output model(x) prob, idx torch.max(torch.softmax(output, dim1), dim1) print(f类别{idx_to_name[idx.item()]}置信度{prob.item():.2f})这段代码值得反复看的点有两个一是构造模型时必须手动重建 fc 层把类别数对上否则加载权重时维度对不上二是 map_locationcpu 让这个脚本在没有 GPU 的演示机器上也能跑。我会刻意把最终演示的电脑显卡驱动卸掉测试一遍保证答辩现场不依赖 GPU 环境。进一步验证我会在测试集上生成混淆矩阵看看哪些类别之间在互相打架。我的经验是芒果和木瓜、绿苹果和梨这种外观相近的组合最容易混。把混淆矩阵写进论文附录这类项目本身的实验部分就比较完整了。最后的检验标准是把整个项目目录拷到一台全新的机器上删掉虚拟环境按 requirements.txt 重新安装跑通训练脚本和推理脚本。这个动作能暴露出所有环境依赖问题也是我交付前必做的一步。我见过太多项目在作者电脑上一切正常换一台机器就爬不起来。因为吃过大亏现在我对文档说明的唯一要求是照着它做新人也能完整复现。如果你走到这一步这个方向基本就不虚了希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询