新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于迁移学习的水果识别系统:从PyTorch到Flask部署

发布时间:2026/9/15 15:55:28来源:尧图网络
基于迁移学习的水果识别系统:从PyTorch到Flask部署
简介这是一份面向Python毕业设计场景的深度学习水果识别系统项目包适合计算机相关专业学生完成课程设计、毕业设计或作为实战练手项目。项目经本地编译验证评审得分95分以上助教审定难度适中。压缩包共277个文件、17.6MB主要包含Python源码、HTML/CSS/JS前端页面、数据集与模型文件及文档说明可支撑从环境配置到模型训练、效果展示的完整流程已有397人学习下载。资源不仅提供可运行的源码还配有数据集和训练好的模型便于快速复现实验结果前端页面基于Layui等框架构建交互清晰适合参考其界面设计与系统集成方式对需要高效完成高质量毕业设计的学生而言省时省力。1. 水果识别系统为什么值得自己动手做一份一个水果识别系统放在毕业设计里恰好卡在“理论有难度但又不至于做不完”的位置。它表面是图像分类实际要处理数据清洗、模型训练、前后端衔接、部署环境四层问题。拿到的这份源代码自带可运行的前后端、训练好的模型权重和说明文档评审分达到 95 分以上意味着在答辩演示时可以直接跑出识别效果而不是停留在准确率数字上。读者可以把它当成一个完整的深度学习交付样板替换数据集就能改造成其他物品识别保留训练脚本就能换模型重训。刚入门深度学习的同学能从这里建立端到端认知工作过几年的工程师则可以快速复用它搭一个演示 Web 应用。2. 数据准备与迁移学习模型的选型逻辑2.1 水果识别为什么不要上来就选目标检测网络很多做过目标检测的读者会把水果识别理解成“在图像里把水果框出来”然后下意识想用 YOLOv5、YOLOv8 训练自己的数据集。但对干线上的毕业生来说这是不必要的复杂度。水果识别系统的核心任务是判断“图里是苹果还是香蕉”属于图像分类输出的是类别标签而不是边界框。分类网络结构更轻部署时占用的显存和内存都小前端等待时间也短。除非你的题目里明确写了“检测”或者“定位”否则优先用分类方案。这个项目里提供的数据集和模型就是按分类任务组织的模型权重直接加载即可。若强行把图片标注成检测格式反而要额外处理标注工具和 anchor 参数周期会拉长。这也是为什么很多高分毕业设计选类似路径用 ResNet 或 MobileNet 做分类网络微调后接一个 Python Web 框架整个链路可解释性强。2.2 数据集的目录结构和加载方式拿到资源包后首先要确认数据集格式。比较合理的目录结构是data/fruits/ ├── train/ │ ├── apple/ │ ├── banana/ │ ├── orange/ │ └── ... ├── val/ └── test/train/下每个子目录名就是类别名。PyTorch 的ImageFolder会按子目录自动生成标签省去手写 CSV。加载之前要先把图片尺寸统一常见用 224x224 作为输入与 ImageNet 预训练模型保持一致。下面是最基础的数据加载代码from torchvision import datasets, transforms train_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) train_ds datasets.ImageFolder( data/fruits/train, transformtrain_transform ) print(train_ds.classes) # 类别名列表 print(len(train_ds)) # 图片总数Resize(256)先把短边拉到 256CenterCrop(224)再裁剪到 224这是 ImageNet 系列模型的常见输入尺寸。Normalize的 mean 和 std 是 ImageNet 统计值如果模型是用 ImageNet 预训练的这套参数必须保留换成自己数据集的均值标准差反而会降低预训练权重效果。资源包里通常已经区分了训练、验证目录如果没区分建议按sklearn.model_selection.train_test_split划分比例控制在 8:1:1 附近。验证集必须从训练阶段就独立出来不然你不知道模型是在记忆训练数据还是真正在归纳水果的纹理特征。2.3 模型选型ResNet、MobileNet、EfficientNet 怎么挑模型选择不必追求最重因为水果识别的类别间差异比 ImageNet 的精细粒度分类更容易。我通常先跑 MobileNetV3 或 ResNet18 看基线再用 ResNet50 或 EfficientNet-B0 做对比。下面这张表是一个可以直接套用的选型参考模型参数量输入尺寸CPU 推理耗时参考适用的训练环境MobileNetV3-Small约 2.5M224x224很短无 GPU 也能跑ResNet18约 11M224x224中等显存 4G 即可ResNet50约 25M224x224较长推荐 GPUEfficientNet-B0约 5M224x224中等显存 4G 即可MobileNetV3 在 CPU 上速度最快适合答辩时用笔记本现场演示ResNet50 的精度通常更高适合离线调参数。两者在水果数据集上Top-1 准确率差距经常不到 2%所以不要只看论文指标要看你的硬件和实时性需求。接入预训练权重时只需要替换全连接层from torchvision import models model models.resnet18(pretrainedTrue) num_classes len(train_ds.classes) model.fc torch.nn.Linear(model.fc.in_features, num_classes) # 替换全连接层pretrainedTrue加载的是 ImageNet 上训练好的卷积层权重冻结后的浅层能提取边缘、颜色、纹理这些通用特征。替换model.fc是因为 ImageNet 是 1000 类输出我们改成自己的水果类别数。2.4 数据增强让模型对光照和角度不敏感果园里拍摄的照片光照差异很大桌面上的果蔬照片还会有阴影。直接拿原始图片训练模型会把阴影边缘误当作特征。我的做法是在训练集上增加随机翻转、旋转和颜色抖动验证集保持纯净train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), 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] ) ])RandomResizedCrop让模型看到不同比例的目标区域模拟目标距离变化RandomRotation(15)控制在 15 度内避免把香蕉横竖变换学成别的语义。颜色抖动不要太激进不然苹果和番茄的红色会被拉得太近导致类别混淆。这些增强操作只在训练时启用验证和测试要用与之前相同的 224x224 缩放。3. 训练脚本与迁移学习微调实现3.1 一个可以直接跑的 PyTorch 微调脚本项目资源里的源码通常已经包含了训练入口但我建议自己从头写一遍训练循环这样可以完全掌控参数。以下脚本覆盖了数据加载、模型构建、训练轮次循环、模型保存是当前资源场景下的标准写法import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models train_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] ) ]) train_ds datasets.ImageFolder(data/fruits/train, train_transform) val_ds datasets.ImageFolder(data/fruits/val, train_transform) train_loader DataLoader( train_ds, batch_size32, shuffleTrue, num_workers2, pin_memoryTrue ) val_loader DataLoader( val_ds, batch_size32, shuffleFalse, num_workers2 ) device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, len(train_ds.classes)) # 替换为水果类别数 model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) 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() * images.size(0) model.eval() correct, total 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) preds model(images).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total print(fepoch {epoch1:02d}, loss {running_loss/len(train_ds):.4f}, val_acc {val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), fruit_model.pt) # 保存最佳权重这个脚本的逻辑分为三部分先构建 DataLoader然后替换模型输出层最后循环训练并在验证集上挑最优权重。关键点在optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3)这里只更新新换上的全连接层参数卷积层的预训练权重保持不变或者只做微调好处是训练快且不容易过拟合。当你希望全网络都参与更新时改成model.parameters()学习率同步调低到 1e-4。CrossEntropyLoss内部已经把 softmax 和 log 合并输出层不需要单独加 softmax。如果你的代码里在最后一层额外挂了一个nn.Softmax训练时 loss 会异常推理时才需要 softmax 得到概率。3.2 学习率、batch size 和 epoch 的配合关系水果识别数据量不大我把默认超参数放在下面方便直接对照参数推荐值说明batch_size32显存不够就降到 16lr1e-3只训练全连接层时用 1e-3lr1e-4整网络微调时用 1e-4epochs20早停到 val_acc 不再上升即可weight_decay1e-4开正则防止过拟合batch size 直接影响 BN 层的统计量。水果图片本身不大32 是一个比较稳的起点。如果训练集只有几千张epoch 太多反而会记住个别图片背景我一般会保存每次验证集准确率最佳的权重而不是用最后一个 epoch 的结果。整网络微调比只训练全连接层慢很多但精度上限高。实践中的做法是先冻结卷积层跑 5 个 epoch再把requires_grad打开用 1e-4 的学习率继续微调。PyTorch 里可以这样冻结for param in model.parameters(): param.requires_grad False model.fc.weight.requires_grad True model.fc.bias.requires_grad True这里冻结了所有卷积层只让fc层可训练结构上类似逻辑回归加预训练特征提取器。继续微调时再把requires_grad全部恢复即可。3.3 如何判断模型真的学好了控制台输出的val_acc只看总体准确率不能暴露哪些水果类别混在一起。更好的做法是生成混淆矩阵观察“苹果”是否经常被误判成“梨”“橘子”是不是和“橙子”纠缠。以下代码用 sklearn 生成混淆矩阵并可以在答辩时作为结果展示。from sklearn.metrics import confusion_matrix import numpy as np all_preds, all_labels [], [] with torch.no_grad(): for images, labels in val_loader: images images.to(device) preds model(images).argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) print(cm)confusion_matrix的行是真值列是预测值对角线上的数字是分类正确的样本。如果非对角线位置出现明显聚集说明这两类特征太接近。返回去把对应类别的图片单独拉出来看通常会发现标注错误或背景干扰。4. 用 Flask Layui 把模型封装成在线识别系统4.1 资源包里的前端样式说明了什么资源包里的 CSS 文件出现了bootstrap.css、layui.css、layui_old.css、main.css、admin.css、swiper-bundle.min.css这说明系统前端不是单页面硬编码而是用了基础 UI 框架叠加业务页面的方式。layui.css是 Layui 栅格和组件库admin.css是后台管理布局swiper-bundle.min.css用于轮播图整体结构符合“上传图片 - 识别结果展示”的信息流设计。这几个 CSS 文件在页面里各司其职可以按下面的分工去理解CSS 文件作用使用注意bootstrap.css栅格布局与基础按钮组件与 Layui 混用需注意覆盖layui.css上传组件、表单、弹层建议保留前端交互依赖它admin.css后台管理侧边栏布局配合 manage 页面使用swiper-bundle.min.css轮播图组件仅首页展示需要layer.css弹层和消息提示与 Layui 联动删除会报错用这种组合的实际好处是Layui 的表单和上传组件能少写很多 JavaScriptBootstrap 负责响应式布局笔记本投影时页面不会乱。后端只需要把识别结果返回成 JSON前端拼接到界面即可前后端联调压力很小。4.2 Flask 接口图片上传与推理逻辑在 Python 毕业设计里Flask 是使用频率最高的 Web 框架因为它可以在一个文件内同时完成路由、静态页面和模型推理。下面是识别接口的完整示例import os import torch from flask import Flask, request, jsonify, render_template from PIL import Image from torchvision import transforms, models app Flask(__name__) device torch.device(cuda if torch.cuda.is_available() else cpu) class_names [apple, banana, orange, pear] model models.resnet18(pretrainedFalse) model.fc torch.nn.Linear(model.fc.in_features, len(class_names)) model.load_state_dict(torch.load(fruit_model.pt, map_locationdevice)) model.to(device) model.eval() 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] ) ]) def allowed_file(filename): return . in filename and filename.rsplit(., 1)[1].lower() in {jpg, jpeg, png} app.route(/) def index(): return render_template(index.html) app.route(/predict, methods[POST]) def predict(): file request.files.get(image) if file is None or not allowed_file(file.filename): return jsonify({error: 请上传 jpg/png 图片}), 400 image Image.open(file.stream).convert(RGB) image transform(image).unsqueeze(0).to(device) with torch.no_grad(): logits model(image) probs torch.softmax(logits, dim1) conf, idx probs.max(dim1) return jsonify({ label: class_names[idx.item()], confidence: round(conf.item(), 4) }) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)这段代码要注意三个细节。第一pretrainedFalse之后必须用load_state_dict载入我们自己训练好的权重否则模型输出的是随机初始化结果。map_locationdevice保证了在纯 CPU 机器上也能读 GPU 保存的权重。第二Image.open(file.stream)直接读取上传的文件流避免先保存再读取带来的临时文件残留.convert(RGB)会把带透明通道的 PNG 转成三通道否则transform会报通道数不匹配。第三torch.softmax把 logits 转为概率probs.max(dim1)同时拿到最高概率和对应类索引。前端路由/渲染templates/index.html静态 CSS 放在static/目录下Layui 的表单文件域绑定到这个接口即可。答辩时如果想看中间层结果可以在推理函数里增加model.features或指定层输出的 hook但记住演示现场不要依赖打印信息异常信息直接返回给用户会显得不够专业。4.3 前端页面里经常要改的几处参数用 Layui 做上传页常见配置是把accept限制为images并给file元素加一个onchange事件发送 AJAX 请求。前端脚本里需要知道后端接口、请求方式和返回字段这些字段必须和 Flask 端一致。以下是前端一段可以直接复用的 fetch 逻辑const fileInput document.getElementById(imageInput); fileInput.addEventListener(change, async (event) { const formData new FormData(); formData.append(image, event.target.files[0]); // 字段名必须为 image const resp await fetch(/predict, { method: POST, body: formData }); const data await resp.json(); document.getElementById(result).innerText ${data.label} ${(data.confidence * 100).toFixed(2)}%; });FormData.append(image, ...)中的字段名必须与 Flask 里的request.files.get(image)完全一致大小写、下划线都不能错。resp.json()读取的键名要对应后端返回的label和confidence。如果发现浏览器返回 400 或接口报错最优先检查这个字段名是否匹配。资源包里的layer.css和layui.css都是为了弹层提示和按钮样式服务的如果删除其中一个页面会出现按钮错位但不会影响推理接口。调试时可以先不开debugTrue因为 Flask 的 debug 模式会加载额外调试页面和学生写的自定义异常处理冲突。5. 识别效果不达标时的四个排查方向5.1 第一步看置信度分布而不是单张结果模型在每张图上都返回置信度高概率不意味着可信。把一批图片的置信度收集起来观察是否集中在 0.9 以上如果全部大于 0.95很可能模型只学到了背景差异。更合理的做法是对每个类别分别统计平均置信度引入下面的验证函数def inspect_probs(model, loader, class_names): model.eval() all_probs {name: [] for name in class_names} with torch.no_grad(): for images, labels in loader: images images.to(device) probs torch.softmax(model(images), dim1) for i, label in enumerate(labels): all_probs[class_names[label]].append( probs[i].max().item() # 当前样本的最高置信度 ) for cls in all_probs: print(cls, sum(all_probs[cls]) / len(all_probs[cls]))平均置信度低的类别大概率是参与训练的图片太少或者类别间纹理过于接近这时回到数据增强给该类单独增加随机遮挡和亮度变化。5.2 第二步检查数据泄露和标签路径如果训练集和验证集来自同一批源目录的连续文件且未随机打散那么相似图片会同时出现在两边导致验证指标虚高。排查方法是查看train_loader和val_loader的采样顺序确认没有使用同一个ImageFolder实例。另外注意资源包里test/目录下的图片名如果出现在train/目录里也属于数据泄露。5.3 第三步模型权重路径和前端调试缓存提交作业或部署新环境时常会遇到FileNotFoundError或权重和类别数不匹配。训练时class_names的顺序由ImageFolder自动生成大概率是按文件夹名排序部署端手动写的列表顺序必须和训练顺序一致否则label会出错。前端修改后不生效时先强制刷新浏览器并清掉静态文件缓存再检查static/下 CSS 引用路径是否带了url_prefix。这些是答辩前最容易翻车的位置。5.4 第四步用 ONNX 导出替代 PyTorch 依赖如果答辩电脑上没装 PyTorch或者显卡驱动有问题可以把模型导出为 ONNX 格式在 CPU 上用onnxruntime推理pip install onnxruntime正确的导出脚本使用 PyTorch 自带工具dummy_input torch.randn(1, 3, 224, 224, devicedevice) torch.onnx.export( model, dummy_input, fruit_model.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}} )导出后再用 onnxruntime 验证输出偏差控制在同一数量级即可。动态 batch 允许一次传入多张图片但会牺牲少量推理速度答辩演示只传单张时建议去掉。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

LangChain4j 集成 Jina Embedding 模型实战指南:文本/多模态嵌入、query/passage 非对称检索与监听器配置 2026/9/15 16:37:35

LangChain4j 集成 Jina Embedding 模型实战指南:文本/多模态嵌入、query/passage 非对称检索与监听器配置

LangChain4j 集成 Jina Embedding 模型实战指南:文本/多模态嵌入、query/passage 非对称检索与监听器配置 【免费下载链接】langchain4j LangChain4j is an idiomatic, open-source Java library for building LLM-powered applications on the JVM. It offers a un…

阅读更多 →
ai-memory 单写者 SQLite Actor:并发写入是如何做到零冲突的 2026/9/15 16:37:35

ai-memory 单写者 SQLite Actor:并发写入是如何做到零冲突的

ai-memory 单写者 SQLite Actor:并发写入是如何做到零冲突的 【免费下载链接】ai-memory Solution for long term memory for agent coding CLIs and to facilitate handoff between different agent vendors 项目地址: https://gitcode.com/GitHub_Trending/ai/a…

阅读更多 →
Cloudflare TURN 实战指南:WebRTC 长通话不掉线的 5 个关键设计 2026/9/15 16:37:35

Cloudflare TURN 实战指南:WebRTC 长通话不掉线的 5 个关键设计

Cloudflare TURN 实战指南:WebRTC 长通话不掉线的 5 个关键设计 【免费下载链接】skills Skills Catalog for Codex 项目地址: https://gitcode.com/GitHub_Trending/skills4/skills 想象这样一个场景:用户开了一场 WebRTC 视频会议,前…

阅读更多 →
deck.gl × Google Maps 集成指南:使用 GoogleMapsOverlay 构建自定义叠加层 2026/9/15 16:37:35

deck.gl × Google Maps 集成指南:使用 GoogleMapsOverlay 构建自定义叠加层

deck.gl Google Maps 集成指南:使用 GoogleMapsOverlay 构建自定义叠加层 【免费下载链接】deck.gl WebGL2 powered visualization framework 项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl 本文以 deck.gl 仓库中的 deck.gl/google-maps 模块…

阅读更多 →
QMK 固件中的 1upkeyboards pi60_hse 键盘支持:基于 RP2040 的 60% 热插拔 PCB 配置与刷写实战 2026/9/15 16:37:35

QMK 固件中的 1upkeyboards pi60_hse 键盘支持:基于 RP2040 的 60% 热插拔 PCB 配置与刷写实战

QMK 固件中的 1upkeyboards pi60_hse 键盘支持:基于 RP2040 的 60% 热插拔 PCB 配置与刷写实战 【免费下载链接】qmk_firmware Open-source keyboard firmware for Atmel AVR and Arm USB families 项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmwar…

阅读更多 →
2026年9月流量卡选购指南:看懂套餐真实成本避坑 2026/9/15 16:34:35

2026年9月流量卡选购指南:看懂套餐真实成本避坑

每年9月都是运营商调整套餐政策的高峰期。2026年这波调整,比往年更值得关注——因为市场上能打的短期“神卡”基本绝迹了,取而代之的是一批挂着“长期”名头、看似划算、实则暗藏各种附加条件的合约套餐。我这两周集中测了市面上二十多个省市的线上办卡渠…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞