简介:基于AI的智能饮食图像识别系统.zip 是一套完整可运行的毕业设计或课程设计项目,面向计算机相关专业学生以及初级AI开发者,主要解决食物图像识别、营养成分估算与日常饮食管理场景中的模型训练、接口调用和前端交互等关键问题。压缩包内共包含57个文件,压缩后大小约11MB,其中包含35张食物图像样本、6个Python源码文件(如app.py、models.py、access.py、api/baidu_food_recognition.py等)、5个HTML页面模板、XML配置文件、依赖清单及Git忽略规则;各文件分工明确,既便于理解整体架构,也能直接启动调试。系统在深度学习模型基础上接入百度食物识别API接口,支持本地推理与云端识别互补,同时整合用户注册登录、饮食目标设置、营养记录存储与结果展示等功能,形成一套可闭环运行的应用原型。目前已有48人学习下载,适合需要参考完整项目结构、前后端联动、模型部署和API对接方式的毕业设计、课程设计或期末大作业开发者,也可为营养健康类应用开发提供实用范例。
1. 基于AI的智能饮食图像识别系统:拍照就能知道吃了什么的落地逻辑
很多人第一次接触饮食识别,会以为它和“识别猫狗”是同一件事:给模型看一万张图,它就能认出来。真做进去才发现,饮食识别是图像识别里最“刁钻”的一类细粒度任务——宫保鸡丁和鱼香肉丝在视觉上就差在几片辣椒和几根葱段上,同一个菜换个食堂师傅炒,颜色、形状、摆盘全变。基于AI的智能饮食图像识别系统,本质上就是一套把拍照输入变成“菜名 + 热量 + 营养信息”的深度学习图像识别流水线,它在食堂自助结算、减脂打卡、健康管理、营养科粗略评估这些场景里都有实际需求。
这套系统能解决的核心问题,是把过去靠人工记账、靠肉眼估热量的环节自动化。适合谁?刚接触深度学习图像识别、想用现成数据跑通一个完整应用的开发者;也适合手里有垂直场景(比如团餐、健身App)但不知道怎么选模型和踩坑的产品型工程师。本文按我做这个方向的习惯拆开讲:数据怎么来、模型怎么选、服务怎么部署、真实场景里会在哪翻车,最后给两个把demo推到生产级的关键技巧。
2. 饮食图像识别的数据从哪来:公开数据集与自采标注方案
2.1 三类可用的公开数据集及适用边界
做饮食识别,第一件事不是选模型,是先确认数据形态。公开数据集能帮你快速跑通基线,但直接拿去上线基本都会出问题,原因是餐饮场景高度区域化,中餐、日餐、西餐的类别分布天差地别。
| 数据集 | 类别数 | 规模 | 标注粒度 | 适合做什么 |
|---|---|---|---|---|
| Food-101 | 101类 | 10.1万张 | 单标签(整图分类) | 预训练、基线试验、分类头迁移 |
| ETHZ Food-101 | 101类 | 基于Food-101二次标注 | 带边界框 | 检测任务基线、可迁移到YOLO训练 |
| VIREO Food-251 | 251类 | 约15万张 | 单标签+部分食材标签 | 细粒度分类预训练、跨类别迁移 |
| UEC-FOOD100 / 256 | 100/256类 | 每类约100张以上 | 带分割掩码 | 分割任务、份量估算试验 |
用公开数据集时要注意两点。第一,Food-101每一类固定1000张,分布太均匀,和真实食堂场景“头部菜占60%、长尾菜占30%、背景干扰占10%”的分布完全不同,拿它训完直接上线,长尾菜基本识别不出来。第二,公开数据集以欧美日餐为主,你如果做中餐场景,红油、酱油、勾芡这些视觉特征在里面覆盖很少,必须自采。
我的做法是:公开数据集只用来做两件事——验证代码链路是通的,以及给模型做“预热身”迁移。真正决定线上效果的,永远是自建的那几千张图。饮食识别里,数据的规范性比数据量更值钱,因为类别之间视觉差异太小,标注稍微不一致,模型学到的东西就是乱的。
2.2 自采数据的采集与标注规范
自采数据最忌“随手拍”。一类菜如果只在同一个食堂、同一种光源、同一种摆盘下拍500张,模型学到的其实是环境,不是菜。我一般按这个规范来采集:
- 每类基础量建议800张以上,细粒度类别(比如不同炒肉)最好到1200张,否则混淆矩阵会很难看。
- 每类至少覆盖3个场景:不同食堂/家庭厨房、不同盛具(白盘、花盘、塑料袋)、不同光源(自然光、暖光、冷光)。
- 必须加背景类或“其他”类,收集桌面、手、餐具、饮料、空盘这些“不是菜但经常入镜”的样本。没有这个类,模型会把一切非目标框都当成菜。
- 多菜同框时按面积策略标注:面积占比小于整图5%的菜不标,避免模型被极小目标带偏。
标注工具我用过LabelImg和X-AnyLabeling。LabelImg是老牌VOC标注器,稳定但效率低;X-AnyLabeling可以调SAM做半自动预标注,人工只修正边界,能省一半时间。标注格式上,如果你确定走YOLO路线,就直接标YOLO格式;如果还在对比检测模型,先标VOC格式再转换,兼容性最稳。
标注规范里最重要的一个约定是:框住“菜的主体”,不要框餐盘。很多标注员会把整个盘子圈进去,导致模型学到的特征是盘子边缘,换个碗就失效。这个约定要在动工前写进标注文档,并且前50张由你亲自复核。
2.3 用Python把VOC标注批量转成YOLO训练格式
如果你手里已经有一部分VOC格式的标注数据,转成YOLO训练格式是最常见的第一步。下面这个脚本的核心逻辑,是把XML里的绝对坐标归一化到0到1之间,并按类别表映射成类别ID。我贴的是加了边界防护的版本,因为实际跑的时候,坐标越界、类别未注册、size字段缺失这三类脏数据几乎一定会碰到。
import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, class_names, out_dir): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") if size is None: return False # 缺尺寸信息,无法归一化,跳过 img_w = int(size.findtext("width")) img_h = int(size.findtext("height")) if img_w == 0 or img_h == 0: return False lines = [] for obj in root.findall("object"): name = obj.findtext("name") if name not in class_names: # 未在类别表注册的标签,直接跳过,避免污染训练集 continue cls_id = class_names.index(name) bndbox = obj.find("bndbox") xmin = float(bndbox.findtext("xmin")) ymin = float(bndbox.findtext("ymin")) xmax = float(bndbox.findtext("xmax")) ymax = float(bndbox.findtext("ymax")) # 边界防护:把越界坐标截断到图像范围内 xmin = max(0, min(xmin, img_w)) ymin = max(0, min(ymin, img_h)) xmax = max(0, min(xmax, img_w)) ymax = max(0, min(ymax, img_h)) if xmax <= xmin or ymax <= ymin: continue # 坐标异常,丢弃这一框 x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if not lines: return False out_path = Path(out_dir) / (Path(xml_path).stem + ".txt") out_path.write_text("\n".join(lines)) out_dir.mkdir(parents=True, exist_ok=True) return True这段代码有三个关键点。第一是类别映射用了class_names.index(name),这个写法简单但有个坑:如果类别名写错一个字母,它会被静默跳过,所以转换完一定要统计每个类别的框数量,和原始XML对账。第二是坐标归一化,YOLO格式的txt里存的是中心点x、中心点y、宽、高,全部除以图片宽高,单位是比例而不是像素。第三是边界截断,标注工具偶尔会画出超出图片范围的框,如果不截断,训练时损失会异常跳动。
转换脚本写完后,我强烈建议你抽20张图做可视化验证:把转换出的txt坐标叠回原图,看框是否贴合菜品主体。不要信任纯统计结果,因为坐标偏移0.05,在640分辨率下就是32个像素的误差,模型学到的边界会全是噪声。
3. 模型选型与训练:从“能跑”到“餐饮场景靠谱”
3.1 选型:先看硬件和延迟,再谈精度
饮食识别系统的模型选型,要区分两类任务:检测还是分类。如果你的输入是“一整个餐盘的俯拍照”,需要先检测出每个菜的区域再识别类别,这是检测+分类的二阶段链路;如果输入已经被裁切好(比如用户拍单一食物),那就是纯分类任务。这两个任务的模型选择完全不同。
| 模型 | 参数量级 | 推理端 | 适合场景 | 细粒度表现 |
|---|---|---|---|---|
| YOLOv8n | 约3M | CPU可跑 | 移动端、嵌入式 | 一般,易混淆近似菜 |
| YOLOv8s / m | 11M / 22M | 单GPU实时 | 食堂结算、视频流 | 中等,依赖数据质量 |
| EfficientNet-B4 | 19M | GPU批量 | 裁切后的单菜分类 | 较好 |
| ConvNeXt-T | 28M | GPU批量 | 离线高精度分类 | 较好,数据量要够 |
| ViT-S / B + 迁移 | 22M / 86M | GPU批量 | 大样本细粒度分类 | 上限高,但易过拟合 |
我做这类系统时,如果现场是视频流或自助结算台,首选YOLOv8s做检测,因为它能在单张1080Ti级别显卡上跑到实时,且自带NMS后处理,出框方便。如果只是App内拍照识别,我倾向用“YOLOv8s检测出餐盘区域 + EfficientNet-B4做单菜分类”的二阶段方案,前一个负责定位,后一个负责细粒度区分,各干各的活,替换其中任何一段都不影响另一段。
一个容易犯的错是:一上来就迷信大模型。ViT或ConvNeXt确实在细粒度分类上限更高,但饮食数据集的规模通常只够支撑中小模型微调。数据量只有几千张时,大模型的表现往往不如数据增强做好的小模型,这是图像识别里反复出现的规律。先把YOLOv8s这条链路跑通,再谈用更大模型换精度。
3.2 用YOLOv8在自定义饮食数据集上跑通训练
假设你已经把数据整理成了YOLO格式,目录结构是images/train、images/val、labels/train、labels/val。第一步是先写数据配置文件,类别顺序必须和转换脚本里的类别表完全一致,否则标签全错。
# food.yaml path: /data/food train: images/train val: images/val names: 0: gongbao_jiding 1: yuxiang_rousi 2: tomato_egg 3: mapo_tofu 4: background然后用Ultralytics的YOLO训练接口直接开跑。下面的参数不是默认值,而是饮食识别场景下比较稳的一组起点。
from ultralytics import YOLO model = YOLO("yolov8s.pt") # 用COCO预训练权重做迁移 results = model.train( data="food.yaml", epochs=100, imgsz=640, batch=16, patience=15, cos_lr=True, lr0=0.001, augment=True, device=0, )这里逐个说参数为什么这么设。epochs=100配合patience=15表示:训练最多100轮,但如果连续15轮验证集mAP没有提升就早停,这个组合能避免过拟合,也比一上来就跑300轮省时间。imgsz=640是速度和精度的折中,如果你想抓小目标(比如餐盘角落的配菜),可以提到768,但训练时间会涨约40%。batch=16是按8G显存设的,如果你的卡是24G,可以调到32,收敛更稳。lr0=0.001对微调场景是安全值,从COCO预训练权重起步不需要大的学习率。cos_lr=True让学习率余弦退火,饮食数据类别差异小,余弦退火相比固定学习率能多挤几个点的精度。
训练完以后,不要急着一键运行验证命令,先看训练输出目录里的曲线图。重点看results.png里的val/box_loss和val/cls_loss,这两个曲线如果在训练后期还在下降,说明还没到拟合极限,可以加大epochs或调大imgsz;如果训练loss降但验证loss反弹,这是过拟合信号,需要加数据增强或减少epochs。
验证命令本身很短:
yolo predict model=runs/detect/train/weights/best.pt \ source=./val_images imgsz=640 conf=0.253.3 训练完怎么判断有没有过拟合或欠拟合
看指标也有层次。第一层是mAP50和mAP50-95。mAP50衡量的是“框大概对了就算对”,mAP50-95要求在多个IOU阈值下都准,后者更严格。饮食识别这种细粒度任务,mAP50看着不错但mAP50-95很低,说明模型找得到位置但框的边界不稳,通常和标注框不齐有关。
第二层是混淆矩阵。训练输出目录里的confusion_matrix.png是这个系统最诚实的体检报告。盯着你业务里最像的几对菜看:宫保鸡丁和鱼香肉丝之间、番茄炒蛋和番茄蛋汤之间,如果混淆率超过15%,就得考虑把这两类合并成一个业务类别(比如直接叫“番茄鸡蛋”),或者增加这一类别的样本量和标注一致性。
第三层是类别分布检查。训练集里background如果只有几十张,干扰类的recall会很难看。我踩过的坑是背景类太少,导致部署时桌面、餐盘、手都被框成“菜”。解决方法是省着用数据增强里的随机裁剪,同时把背景类样本补到每类平均量的20%以上。这个数字不是拍脑袋,而是来自对误检样本的分析:背景干扰样本占比低于这个值,模型就会用“看起来像菜”来代替真正的类间判别。
4. 把模型变成服务:接口设计、推理链路与热量估算
4.1 用FastAPI包一个最小可用的识别接口
模型训完,下一步是让业务方调得动它。我用FastAPI包推理接口比较多,原因很简单:异步原生、自动生成Swagger文档、团队联调时省事。下面是一个能直接启动的接口代码,输入一张图片,输出每个检测框的类别、置信度、坐标和热量估算值。
from fastapi import FastAPI, UploadFile import numpy as np import cv2 from ultralytics import YOLO app = FastAPI() model = YOLO("food_best.pt") # 加载训练好的best.pt # 营养表:每100克的可食用部分热量和蛋白质,按训练类别名映射 NUTRITION = { "gongbao_jiding": {"kcal": 212, "protein": 14.2}, "yuxiang_rousi": {"kcal": 188, "protein": 12.5}, "mapo_tofu": {"kcal": 132, "protein": 8.8}, "background": {"kcal": 0, "protein": 0}, } @app.post("/recognize") async def recognize(file: UploadFile): data = await file.read() img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR) result = model(img, conf=0.25, verbose=False)[0] foods = [] for box in result.boxes: cls_id = int(box.cls[0]) name = model.names[cls_id] if name == "background": continue # 背景类在训练时参与,在业务输出时过滤 conf = float(box.conf[0]) nutrition = NUTRITION.get(name, {"kcal": 0, "protein": 0}) foods.append({ "name": name, "confidence": round(conf, 3), "bbox": [int(v) for v in box.xyxy[0].tolist()], "kcal": nutrition["kcal"], "protein": nutrition["protein"], }) total_kcal = round(sum(f["kcal"] for f in foods), 1) return {"foods": foods, "total_kcal": total_kcal}这个接口里有几个细节值得说明。cv2.imdecode接收的是内存中的字节流,不是文件路径,所以UploadFile读完后直接解就可以了,不用先存盘。model(img, conf=0.25)里这个conf=0.25是推理阈值,它的作用和训练时的置信度阈值不同,后者在验证阶段会影响mAP计算,前者在业务阶段影响误检率,这两个值要分开调。background类在训练时必须有、在输出时过滤,这个设计的好处是模型知道“什么是非菜”,但业务侧不需要把它展示给用户。
启动命令也简单:
uvicorn recognize_server:app --host 0.0.0.0 --port 80004.2 热量估算:为什么不能只靠一个类别名
很多第一次做饮食识别的人会以为:识别出“宫保鸡丁”,查表返回212千卡就完事了。这里有个坑:营养表的热量单位是“每100克”,而模型看到的是“一盘菜”。一盘宫保鸡丁可以是150克也可以是300克,热量差一倍。真正可用的热量估算,必须加上份量这个变量。
份量估算的常见做法是走分割而不是检测。用YOLOv8-seg训练同一个数据集,拿到菜的mask像素面积,再拿餐盘或参照物(比如一只拳头、一张餐巾纸)的面积做比例换算。下面是一个简化版的估算逻辑。
# 假设已经通过分割模型拿到了菜和餐盘的mask import numpy as np def estimate_weight(mask_area, plate_area, plate_capacity_gram=300): """ mask_area: 菜品分割mask的像素数 plate_area: 餐盘分割mask的像素数 plate_capacity_gram: 该餐盘装满时的食物可容纳克数,需按实物标定 """ ratio = mask_area / max(plate_area, 1) gram = ratio * plate_capacity_gram return gram # 调用示例 weight_gram = estimate_weight(food_mask.sum(), plate_mask.sum()) kcal = NUTRITION["gongbao_jiding"]["kcal"] / 100 * weight_gram这个方案有两个已知误差源。一是俯视角度下,堆叠的菜会把投影面积放大,一颗丸子从正上方拍,面积可能比实际截面大30%。二是菜品密度不同,同等面积的炒蔬菜和炖肉,重量差一倍以上。所以热量估算在工程上的定位是“区间参考”,不是计量仪器,我一般在接口文档里注明误差约±30%,让业务方不要把单次数值当作医学数据。真要做得更准,就得加深度摄像头或者用户输入份量等级(少、中、多),这是系统边界问题。
4.3 并发与性能:单机能扛多少路
接口上线后,第一个被问的问题通常是“能扛多少并发”。图像识别系统的吞吐瓶颈有个固定顺序:图像解码 > 预处理 > 模型推理 > 后处理。很多人只优化模型推理,但实际压测时,cv2.imdecode在高并发下占的CPU时间往往比GPU推理还多。
优化路径按性价比排:先把输入尺寸固定(比如统一resize到640x640),再用TensorRT导出模型并固定batch size,GPU推理能快2到4倍;然后处理图像解码瓶颈,常见做法是进程内维护一个线程池专门做图片解码,或者让调用方先压缩图片再上传。以一张1080Ti级别的卡、输入640分辨率为例,不优化的YOLOv8s接口大约能支撑十几路低并发;做好TensorRT固定shape和预热后,通常能支撑几十路近似实时调用。这个数字只用来做容量预估,具体以你的压测脚本为准。
压测工具我不喜欢用重量级的平台,写个几十行的Python脚本用asyncio并发发图,观察P95延迟就够了。P95超过500ms就要警惕,餐饮结算场景的体验红线一般定在300ms以内,减脂打卡App可以放宽到1秒上下。
5. 饮食图像识别在真实场景翻车的五个高频坑
5.1 测试集涨点,现场漏检
现象:训练时验证集mAP50涨到了0.9,很满意,部署到食堂后发现每隔十几次识别就会出现一次漏检或错检,而且总是发生在特定灯源下。
原因:自采数据太“干净”了。训练集里每一张都是规矩的俯拍、光线均匀、菜没被动过。实际情况是:菜被筷子翻动过、餐盘上有水渍反光、蒸汽在镜头前飘过、光源是偏色的暖黄灯。模型学到的特征和现实现场对不上。
解决:这是数据采集策略问题,不是模型问题。我当时补了两类数据:一是现场连续录半小时视频,按帧抽图加入训练集;二是做针对性的数据增强,模拟现场噪声——随机加高斯噪声、随机调整色温、模拟蒸汽模糊。记住一个原则:训练集里没有的干扰,测试时一定会在线上出现。
5.2 高混淆类别:近似菜怎么都分不开
现象:宫保鸡丁经常被识别成鱼香肉丝,番茄炒蛋偶尔变成木须肉,混淆矩阵里这几对类别的交叉值一直压在15%以上。
原因:这两类菜的视觉差异只在配料颜色分布上,而且不同师傅的做法让类内差异比类间差异还大。更隐蔽的原因是标注不一致:甲标注员把带花生的炒鸡丁标成宫保鸡丁,乙标注员可能把同样的图标成别的。
解决:两条腿走路。第一条,核对标注一致性,把混淆严重的类别各抽100张原图拉出来人工重标,统一判别基准;第二条,如果重标之后还压不下去,就在业务上合并类别,把“宫保鸡丁/鱼香肉丝”统一映射成“辣炒鸡丁类”,牺牲粒度换准确率。细粒度分类不是越细越好,而是用户能接受多细才做多细。
5.3 一份菜多个类别:一碗盖浇饭怎么标
现象:一碗番茄鸡蛋盖浇饭,番茄炒蛋算一类,米饭算一类,模型输出两个框、两个热量,业务方不知道该怎么展示和入账。
原因:标注规范没有定义“复合菜品”的处理方式。盖浇饭、咖喱饭、砂锅这类“边界模糊”的样本,不同标注员完全凭感觉。
解决:提前定死标注策略。我的约定是:单一菜名能覆盖的复合餐品,整碗只标一个主类,不拆分;只有自助餐这种分格餐盘,才按格框分别标注。另外,业务输出层要做后处理规则,比如“同一餐盘内检测到主食类+菜品类时,展示合并结果”,而不是把多个检测框平铺给用户。
5.4 部署后显存与延迟不稳,越跑越慢
现象:接口刚启动时延迟正常,跑了几个小时后延迟逐渐升高,偶尔还报CUDA out of memory。
原因:每个请求进来的图片尺寸不固定,模型被迫动态shape推理。PyTorch的CUDA缓存分配器在动态shape下会产生碎片,碎片越积越多,最终表现为“变慢”和“偶发OOM”。
解决:固定输入尺寸,推理前统一resize到训练尺寸;用TensorRT导出引擎时把输入shape固定成[1, 3, 640, 640];如果还是不稳,就加一个“定时重启”的兜底策略,每天凌晨低峰期重启一次服务。这是生产环境现实的选择,不要指望代码层面把所有碎片都处理好。
5.5 增量新菜导致旧模型性能回退
现象:上线一个月后要加5个新菜,收集数据重新训练100轮,新菜识别正常,但旧菜的mAP掉了一截,尤其是之前表现最好的几个类别。
原因:这就是经典的灾难性遗忘。模型在适应新数据分布时,把旧类别的判别边界挤掉了。如果旧数据在训练时被过度采训,回退会更明显。
解决:增量训练时,旧类别数据必须按不低于原训练集的比例混入,不能只丢新菜数据进去。我在做这个步骤时会给旧类别加一个“保底采样权重”,让它们在每个epoch里出现的比例不低于历史训练时的80%。如果新菜类别数据特别少(少于200张),先不要混入训练,而是收集够了再统一更新。短期内最稳妥的策略是“全量重训”,虽然耗时,但不会出现回退。
6. 进阶:把识别系统从demo拉到生产级的两个关键技巧
6.1 用“检测 + 细粒度分类”二阶段代替单模型硬扛
如果你已经跑通了单模型识别,又想进一步提升细粒度准确率而不增加部署成本,建议改成二阶段结构:第一段用YOLOv8s只负责“在哪里”,把检测框裁出来;第二段用一个轻量分类模型(比如EfficientNet-B4)负责“是什么”。这个改动的好处是,检测模型不需要同时承担定位和细分类双重压力,分类模型可以专注学类间微小差异。
第二阶段的分类模型训练里,有个技巧很实用:用大模型当教师做知识蒸馏。大模型(比如ViT-B)在同样数据上训练后,输出softmax前的logits里带有“宫保鸡丁和鱼香肉丝有点像”这类软信息,把小模型同时向真实标签和教师logits学习,可以在不加大模型的前提下挤出一部分细粒度性能。简化版的蒸馏loss长这样:
import torch.nn.functional as F def distill_loss(student_logits, teacher_logits, labels, alpha=0.7, T=3): # student_logits, teacher_logits: 模型输出的原始logits # labels: 真实类别索引 hard_loss = F.cross_entropy(student_logits, labels) soft_target = F.log_softmax(student_logits / T, dim=-1) soft_label = F.softmax(teacher_logits.detach() / T, dim=-1) soft_loss = F.kl_div(soft_target, soft_label, reduction="batchmean") * (T * T) return alpha * hard_loss + (1 - alpha) * soft_loss蒸馏里两个关键参数:T是温度,一般在3到5之间,温度越高,软标签里的类间相似度信息越平滑;alpha是硬标签和软标签的权重比例,我一般从0.7起步,如果学生模型训练后期过拟合就调高到0.85。注意teacher_logits必须detach()掉梯度,否则教师模型会被反向传播更新。
6.2 自建“现场回归集”,给每次模型更新上保险
训练一个模型很容易,难的是每次更新后都保证线上不劣化。我现在除了标准验证集,还会额外建一个“现场回归集”:从部署的食堂或目标用户环境里,不经过筛选地拍200到300张原始照片,每张标注好真实情况,这组图片不参与训练,只做每次模型更新后的回归测试。规则是:新模型必须在回归集上的坏案例数不高于旧模型,否则不允许上线。这个集合规模不大,但能拦住很多“验证集涨点、现场翻车”的问题。
我用一个非常朴素的脚本做回归,把每张图片跑一遍推理,打印低于置信度阈值或标签与预期不符的样本,人工扫一遍结果即可。每次更新模型时,把新旧两版模型在回归集上的输出并排放在一个目录里对比,谁翻车多一目了然。这套流程不花哨,但它是把模型迭代从一个黑匣子变成工程流程的关键——至少你不用再靠“感觉”来决定要不要上线一个新模型。
我现在每个饮食识别项目,第一件事永远是确认数据分布和现场回归集,而不是先选模型。模型救不了脏数据,但规范的数据和回归集能兜住模型的每一次漂移。系统跑久了你会发现,护城河从来不是某个网络结构,而是你手里那套“数据怎么采、标注怎么定、更新怎么验”的规则。希望帮到你。
本文还有配套的精品资源,点击获取