news 2026/10/10 23:28:31

CNN食物图像识别工程实战:从数据预处理到模型部署的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CNN食物图像识别工程实战:从数据预处理到模型部署的完整指南

简介:面向深度学习初学者与计算机视觉爱好者的CNN食物图像识别项目,基于Python和TensorFlow,覆盖数据预处理、模型搭建、训练到分类预测全流程,可应用于餐饮、健康监测等场景。资源共32个文件,17.28MB,以12个Python脚本为核心,涵盖模型定义、图像变换、数据加载、训练与结果对比,并附tfrecords数据集、模型权重及配置说明,目录清晰便于复现。已有6643人学习下载。通过实际操作,可掌握卷积层、池化层、全连接层及激活函数的协同机制,体验数据增强与迁移学习等进阶方法,深入理解CNN原理,提升图像识别实战能力。

1. 从“拍了不知道吃了啥”说起:基于CNN的食物图像识别工程好在哪

你在食堂结算台端着餐盘排队,摄像头扫一眼,账单自动列出番茄炒蛋和红烧排骨;手机里打开饮食记录App,拍一张午饭照片,识别出这是三杯鸡套餐还是黄焖鸡米饭。这些场景背后是同一个技术问题:把二维像素矩阵映射到一个菜品类目,而目前主流解法就是基于卷积神经网络(CNN)的食物图像识别。

食物图像识别比普通物体分类更麻烦:同一道宫保鸡丁,不同厨师做出来色泽和装盘差异巨大,同一家店换个灯光色温,整张照片的颜色分布就换了。早年靠颜色直方图和边缘特征手工搭流水线的方案,在真实餐盘照片里几乎撑不过验证集。

这份工程围绕卷积神经网络的食物图像识别整理了一条完整可跑链路:数据集目录怎么组织、增强参数怎么设、模型怎么迁移学习、训练完怎么单张推理,还附了一组我从实际项目里沉淀下来的踩坑记录。适合刚接触CNN想做出第一个可用识别模型的开发者,也适合想做菜品识别演示或想迁移学习快速出版本的从业者,拿来改改数据就能跑通。

2. 先立理论再选型:食物分类为什么躲不开卷积神经网络

2.1 传统特征工程在食物图像上的三个死穴

早几年做食物识别,常见套路是SIFT或ORB提取关键点描述子,再用HOG统计梯度方向,最后把特征喂给SVM或随机森林分类器。这套流程在工业零件、车牌这类几何结构稳定的目标上效果不错,但一碰到食物照片就开始失灵。根本原因不是分类器不行,而是手工特征表达不了食物外观的复杂变化。

第一个死穴是形状不稳定。一份红烧肉被切成五块还是八块,边缘响应完全不一样;米饭被勺子压成球形还是摊在盘子里,HOG特征直接变成两种东西。传统特征隐含着一个前提,目标有相对稳定的几何结构,食物恰恰是最不满足这个前提的类别之一。第二个死穴是颜色受光照支配。黄色灯光下拍的炸鸡和偏暖灯光下拍的煎蛋,肤色非常接近;白平衡一变,整个颜色直方图跟着漂移,特征分布立刻乱掉。第三个死穴是特征维度膨胀后难以泛化。几千维的SIFT词袋特征,换个餐厅、换块桌布,特征分布就变了,换一组数据验证集性能直接打回原形。

CNN改变的是特征这件事本身:卷积核自动从像素里学边缘、纹理、局部形状,再逐层组合出适合当前任务的语义特征,不需要人工先定义什么叫“红烧”、什么叫“酥脆”。这也是为什么现在食物识别很少再有人用传统特征搭流水线,除非类别只有个位数且拍摄条件被严格控制。食物识别比一般物体分类更接近细粒度图像分类,类内的颜色和纹理变化甚至比类间差异还大,这条分界线让手工特征越发难以画出来。

2.2 网络结构怎么定:从ResNet到MobileNet的取舍

先破一个容易绕弯路的地方:有人搜“卷积神经网络”时,会搜到大量一维卷积网络的文献。一维卷积更适合语音、文本这类序列信号,输入是一维数组,卷积核只在时间轴上滑动。食物图像是一张二维的像素矩阵,空间相邻关系极其重要,强行把图像展平成序列喂给一维卷积,会打散邻近像素的局部结构,效果大概率比一个普通二维小网络还差。所以这份工程全部以二维CNN为主线,下面也只在二维模型里挑。

选型时我习惯先问部署目标。如果只是本地实验,类别超过50个,用ResNet50,残差结构在ImageNet上训出来的预训练权重好找,微调时不容易退化;如果后面要接到手机小程序、嵌入式摄像头或者无GPU的服务器上,用MobileNetV3,深度可分离卷积能把计算量压到很低。食物识别的类别差异大,不太需要极致细粒度模型,ResNet和MobileNet在验证集上的差距常常被数据噪声淹没,真正的短板通常出在数据质量而不是网络结构。

结构参数量量级预训练权重适合场景
ResNet50约25M常见服务端训练、类别较多、对精度要求高
MobileNetV3约4M~5M常见端侧推理、CPU部署、内存吃紧
EfficientNetB0约5.3M常见精度与推理速度都需要兼顾时

表里的数字只是常用实现的参数量量级,具体跟版本有关,不必纠结到个位。还有一个细节值得注意:ResNet50默认输入224×224,MobileNetV3可以跑160或192的分辨率,分辨率一降推理速度快一截,代价是验证集准确率通常会掉一点。我一般先按224把流程跑通,最后再测一遍降分辨率到底损失多少,如果损失在一个百分点以内,就果断用低分辨率换速度。另外,网络输入尺寸必须和预训练权重匹配,输入尺寸不匹配时预处理阶段的resize逻辑会跟着错位,这是很多复现失败跑到一半才暴露的隐性原因。

2.3 这份工程的目录结构与训练主线

这份工程按一条完整主线组织:图像数据 → 预处理 → 训练模型 → 保存权重 → 单张推理。拿到工程包先看目录结构,不用逐行读代码,搞清楚每个文件负责什么再动手,避免后面改错位置。

目录/文件作用
data/train、data/val、data/test划分后的图像数据集,子目录名就是类别名
scripts/build_dataset.py把原始图片按比例划分到train/val/test
scripts/train.py数据增强、构建模型、执行训练全流程
net/model.py模型结构定义,默认ResNet50迁移学习
weights/训练过程中自动保存的最佳权重
inference.py加载权重,对单张图片做预测
requirements.txt依赖清单

训练主线的调用顺序固定如下,我在工程里把参数都设了默认值,先跑通再改:

python scripts/build_dataset.py --data_dir raw_images --out_dir data python scripts/train.py --model resnet50 --epochs 50 python inference.py --image test.jpg --weights weights/food_resnet50.h5

每个脚本都支持--help查看参数。build_dataset.py会把raw_images里每个子目录当作一个类别,按比例随机分配到三个目录;train.py负责加载增强后的数据并启动训练;inference.py只做推理,不依赖训练环境。--data_dir指向原始图片根目录,根目录下每一层目录名都要保持英文;--model支持resnet50和mobilenetv3两个选项;--epochs只设上限,实际由EarlyStopping决定。这套工程解决的是从零搭建一条可运行的CNN食物分类流水线的问题,拿到手先把默认参数过一遍,再替换成自己要识别的菜品。

3. 数据集组织与预处理:目录结构决定训练命运

Keras的ImageDataGenerator和PyTorch的ImageFolder都有一个共同约定:目录名就是类别标签。这意味着数据组织的正确性直接影响训练能否开始,而这一步恰恰是最容易在复现时翻车的。

3.1 数据目录规范与划分方式

工程里要求数据集按如下目录结构摆放,子目录名是类别名,不能出现中文、空格或括号:

data/ train/ apple/ rice/ chicken/ val/ apple/ rice/ chicken/ test/ apple/ rice/ chicken/

不少新手直接把所有jpg堆在一个文件夹里,再用一个csv写标签,Keras这边就得多写一层数据加载逻辑,而且csv错一行整批标签错位,排查起来非常痛苦。目录名即标签的方案虽然老派,但最简单可靠。

数据划分我习惯用脚本而不是手动拖拽,手动划分无法复现,分了三次每次都不同,后面做对比实验没有公平基础。下面是划分脚本的核心逻辑:

import os import random import shutil def split_dataset(src_root, out_root, train_ratio=0.7, val_ratio=0.2, seed=42): random.seed(seed) for class_name in os.listdir(src_root): class_dir = os.path.join(src_root, class_name) if not os.path.isdir(class_dir): continue images = sorted(os.listdir(class_dir)) random.shuffle(images) n_total = len(images) n_train = int(n_total * train_ratio) n_val = int(n_total * val_ratio) for phase, idx_range in [("train", range(n_train)), ("val", range(n_train, n_train + n_val)), ("test", range(n_train + n_val, n_total))]: dest_dir = os.path.join(out_root, phase, class_name) os.makedirs(dest_dir, exist_ok=True) for i in idx_range: src = os.path.join(class_dir, images[i]) shutil.copy2(src, os.path.join(dest_dir, images[i]))

代码逻辑:先对每个类别单独shuffle,再按比例切三段,确保每个类别在三个集合里都有样本,而不是全局混在一起切,避免某个类别在测试集里一个样本都没有。train_ratio、val_ratio分别控制训练集和验证集比例,剩余全部作为测试集。seed固定为42,别人复现时用同一个seed得到完全相同的结果。如果你只有几千张图片,建议不要把val设到0.3,验证集太小会让EarlyStopping误判,7:2:1是食物识别场景里比较稳的默认值。

提示:目录名保持英文或拼音,中文映射放到后端的label_map里,不要在文件名阶段引入编码风险。

3.2 数据增强:不是越多越好,要看场景

数据增强是训练时让模型“多看几种拍法”的手段,但食物照片有自己的特殊性。我见过不少新项目直接套ImageNet分类的增强参数,rotation_range设90,vertical_flip开True,结果验证集准确率一直在低位徘徊。

原因其实不难理解:一张俯拍的餐盘照片,水平翻转后还是人眼能理解的摆盘,但上下翻转后盘子里的菜倒过来了,这在真实拍摄中几乎不会出现。模型被迫学到“倒着的菜”这种不存在于真实分布的特征,反而干扰正常识别。水平翻转可以开,因为手机横着竖着拍餐盘都很常见;垂直翻转我默认关掉,除非你的数据集里确实包含上下颠倒的拍摄角度。

增强参数推荐值理由
rotation_range15允许小幅旋转模拟手抖,太大破坏方向信息
width_shift_range0.1模拟餐盘偏移
height_shift_range0.1模拟餐盘偏移
zoom_range0.1模拟距离变化,幅度控制在10%
horizontal_flipTrue手机横竖屏拍餐盘都合理
vertical_flipFalse俯拍图的上下颠倒不真实
brightness_range[0.9, 1.1]模拟餐厅灯光色温变化

如果发现验证集损失一骑绝尘、训练集还很低,优先增强这一栏里的brightness和rotation,而不是无脑把rotation调到45。食物识别的真实难点是“同一个菜在不同餐厅、不同灯光下长什么样”,灯光变化的模拟比大幅旋转更有价值。

3.3 预处理代码与参数说明

确定好增强参数后,工程里用Keras的ImageDataGenerator完成预处理,完整配置如下:

from tensorflow.keras.preprocessing.image import ImageDataGenerator train_gen = ImageDataGenerator( rescale=1.0 / 255, rotation_range=15, width_shift_range=0.1, height_shift_range=0.1, zoom_range=0.1, horizontal_flip=True, vertical_flip=False, brightness_range=[0.9, 1.1] ).flow_from_directory( "data/train", target_size=(224, 224), batch_size=32, class_mode="categorical", shuffle=True )

rescale把像素从0-255归一化到0-1,让数值尺度稳定,训练更容易收敛。严格来说ImageNet预训练模型更推荐用per-channel的均值和标准差做归一化,但工程里用1/255缩放在绝大多数食物识别任务上效果差异很小,可以先用默认值跑通,最后再回头测要不要换更严格的归一化。

target_size必须与模型输入一致,ResNet50默认224×224,不要设成256后去加载224的预训练权重,resize逻辑一乱整个特征就错位。batch_size在单卡6G显存配置下建议32,显存紧张可以先降到16,但不要低于8,否则BatchNorm层的统计量会不稳定。class_mode设categorical,因为食物分类是多类互斥任务,标签做one-hot编码;如果后面改成二分类,就改成binary。

ImageDataGenerator是在训练过程中实时做增强,每个epoch都会生成不同的变换版本,好处是能覆盖更多拍摄场景,坏处是CPU会成为瓶颈,所以建议把worker数调高,这块在避坑章节还会细说。

4. 模型训练与调参:让损失真正降下来

数据准备好就能开始训练,但训练不等于直接跑model.fit。食物识别工程里最值得先想清楚的是迁移学习策略,因为绝大多数项目没有几十万张食物图片的数据基础,从零训练在可操作性上基本是负数。

4.1 迁移学习与冻结层策略

用ImageNet预训练权重做初始化,是食物识别工程里最常见的正确做法。ResNet50在ImageNet上学到的边缘、纹理、局部形状特征,对食物照片同样有效,因为食物图像也是真实世界照片,底层视觉特征高度复用。关键决策是冻结多少层,我的默认流程分两步。

第一步全冻结,只训练新增的全连接层,跑3到5个epoch,把随机初始化的分类头先稳定下来。第二步解冻backbone后几十层,用很小的学习率微调。不要一开始就全层解冻,那样随机初始化的分类头会产生很大梯度,反向传播直接冲击预训练权重,损失曲线会先冲高再慢慢爬回来,白白浪费几个epoch。

from tensorflow.keras.applications import ResNet50 from tensorflow.keras import layers, models base = ResNet50( include_top=False, weights="imagenet", input_shape=(224, 224, 3), pooling="avg" ) base.trainable = False model = models.Sequential([ base, layers.Dropout(0.5), layers.Dense(256, activation="relu"), layers.Dropout(0.3), layers.Dense(num_classes, activation="softmax") ])

include_top=False表示去掉原模型的1000类分类头,只保留特征提取部分;weights="imagenet"加载预训练权重,如果当前环境无法联网下载,这一步会卡住,需要先离线准备好权重文件放到缓存目录,再设weights为权重路径。pooling="avg"把最后一层特征图做全局平均池化,直接压成一维向量,比Flatten参数更少,也不容易过拟合。中间全连接层设256、Dropout设0.5,是平衡能力和泛化的常用起点;如果类别数很少小于10个,256可以改成128,避免Dense层成为过拟合的温床。

第二步解冻时,我会把base.trainable设为True,并且只解冻后半段,通常做法是让ResNet50的conv4、conv5可训练,前面的conv1到conv3继续冻结。这一步的学习率必须从1e-3降到1e-5量级,否则预训练权重会被破坏。

4.2 训练主循环与关键超参数

训练超参数不是拍脑袋定的,我按下面的默认配置起步,跑几个epoch看曲线再调:

from tensorflow.keras import optimizers from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau model.compile( optimizer=optimizers.Adam(learning_rate=1e-3), loss="categorical_crossentropy", metrics=["accuracy"] ) callbacks = [ EarlyStopping(monitor="val_loss", patience=8, restore_best_weights=True), ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=3, min_lr=1e-6), ModelCheckpoint( "weights/food_resnet50.h5", monitor="val_loss", save_best_only=True ) ] model.fit( train_gen, validation_data=val_gen, steps_per_epoch=200, validation_steps=50, epochs=50, callbacks=callbacks )

compile里loss用categorical_crossentropy,对应多类别互斥分类;Adam初始学习率1e-3是分类头阶段的设置,微调阶段要手动改成1e-5再compile一次。EarlyStopping监控val_loss,连续8个epoch没有下降就停止,restore_best_weights=True保证停止时恢复到最优权重,而不是停在最后一个epoch。ReduceLROnPlateau每3次停滞就把学习率减半,最低到1e-6,这个机制比固定epoch数更省心。ModelCheckpoint里我建议监控val_loss而不是val_acc,因为accuracy对分类边界敏感,偶尔一跳容易保存到假的最优值。

steps_per_epoch和validation_steps这里设的是固定值,实际工程里用train_gen.samples除以train_gen.batch_size去算更保险,数字不用背,代码里直接取属性就行。epochs设50只是个上限,早停机制会提前结束,不用怕设大了。

超参数默认值调整建议
初始学习率1e-3微调阶段改1e-5
batch_size32显存不够降到16,不要低于8
Dropout(分类头)0.5 / 0.3过拟合时往0.6提,欠拟合往0.3降
EarlyStopping patience8数据量小时降到5,防止训练太短
ReduceLROnPlateau factor0.5loss平台期明显时用0.3更狠

4.3 训练日志怎么读:loss不降时先按顺序排查

训练启动后不要只盯着终端滚屏,我习惯直接开TensorBoard看两条曲线,train_loss和val_loss。判别方向很简单:train_loss在降、val_loss不动,先怀疑过拟合,优先加Dropout和增强强度,而不是去加深网络;train_loss和val_loss都不降,先减学习率或确认预处理是否正常,很多所谓“模型不收敛”其实是学习率太大导致loss来回震荡;第一个epoch就出现NaN,先查数据里有没有损坏图片和NaN标签,再查是否开了非常大的学习率。

这里有一个原则:同一时间只改一个变量。加了增强就不要同时改网络结构,改了学习率就不要顺手换模型,否则两个变量互相干扰,根本定位不到问题根源。我见过太多人把Dropout、学习率、网络深度一起改,最后模型训坏了也不知道是哪一步造成的。

5. 避坑手册:食物识别最容易翻车的五个现场

避坑部分放在训练代码之后是有意的,很多问题必须真正训过一轮才能对上号。下面五条是我在实际跑食物识别项目时踩过、并且至少见过三次以上的翻车现场,统一按“现象→原因→解决”展开,排查时可以直接对照。

5.1 训练集acc到0.99、验证集只有0.70:过拟合在作怪

现象:训练集准确率快速冲到0.99,验证集准确率始终在0.70附近,早停怎么等都提不上去,训练和验证的差距越来越大。

原因:模型容量大、数据量相对少,把训练集里的背景特征也学进去了。食物识别尤其容易过拟合到背景,因为很多爬虫抓来的同一家店多张图片,桌布、盘边反光、灯光色温都是一样的,模型记住这些比记住“这是红烧排骨”容易得多。

解决:顺序不能反。第一步加Dropout、加增强强度,重点加brightness_range模拟不同餐厅灯光;第二步检查数据集里有没有重复图片,同一张图既进了训练集又进了验证集,会造成验证假象;最后才考虑缩小Dense层节点。不要上来就换网络结构,ResNet50在食物分类上基本够用,过拟合绝大多数时候是数据侧和正则侧的问题。

5.2 loss在降、acc卡在50%左右:类别不均衡还是标签错位

现象:loss一路下降看着很正常,准确率却卡在50%以下,或者某一类几乎从来不被预测出来。

原因:第一种是类别样本悬殊,有的类几百张、有的类只有几十张,模型偏向多数的类;第二种是标签错位,比如某个目录下混入了别的菜,或者class_indices的映射和你的预期顺序对不上。

解决:先统计每类样本数,低于均值三分之一就考虑过采样;再打印class_indices核实目录到标签的映射。我习惯在训练开始前跑下面这段确认标签没有错位:

class_indices = train_gen.class_indices print(class_indices)

如果打印出来的映射顺序与你预期不一致,不要靠记忆硬编,直接按class_indices里的顺序读取即可。确认标签没有问题后,再看分类头输出节点数是否与类别数一致,Keras里flow_from_directory会按目录总数自动生成节点数,但如果你手动改了模型结构,这里很容易多一个或少一个。

5.3 图片加载慢到怀疑人生:IO才是真瓶颈

现象:GPU利用率很低,训练进度条在数据加载那一步长时间停顿,整个epoch的时间比模型计算时间还长。

原因:机械硬盘或海量小文件拖慢了IO。几百个类别、每个类别几千张jpg,直接从目录逐个读取文件,操作系统在打开文件上浪费的时间远超图像解码的时间。

解决:先给flow_from_directory设置workers=4和use_multiprocessing=True,这个改动最直接。还慢的话就把图片打包成TFRecord或HDF5格式,一次性读入内存,这两类格式在工程里没有默认启用,但它们解决的问题是真切的。另外把数据集放到SSD上,比换一块更贵的GPU更有效,IO瓶颈不在算力。

5.4 薯条和炸鸡块老是互相认错:细粒度特征不够

现象:混淆矩阵里两个高相似类别交叉严重,比如炸鸡块和薯条、面包和蛋糕,准确率被这两类拖下去一大截。

原因:模型拿到的判别信息不够细。食物识别里,炸鸡块和薯条颜色接近、尺寸接近,真正的区分点在表皮纹理和形状,如果数据增强里的zoom和rotation过猛,这些细微纹理被抹掉了,模型只能依赖颜色这种粗粒度特征,当然分不开。

解决:针对容易混淆的类别做专项验证。我一般用Grad-CAM看一下模型在分类时把注意力放在哪里,如果它盯着盘子边缘反光而不是食物主体,说明训练数据里主体被背景干扰了。这种情况下无脑增强没用,正确的做法是对这两个类单独补拍数据,或者降低zoom_range避免破坏纹理细节。

注意:细粒度混淆问题靠增加网络深度解决的概率不大,模型深度带来的收益通常被数据纹理损失抵消,优先从数据和增强侧找原因。

5.5 显存不足OOM:分辨率、batch和尺寸的取舍

现象:训练跑了一两个step后报CUDA out of memory,进程被杀,前面所有进度全部作废。

原因:输入224×224、batch32、ResNet50在6G显存的卡上刚好在内存边界,一旦数据增强或验证阶段多开销一部分显存就爆了。

解决:先做最保守的改动,把batch_size降到8,通常立刻缓解;还爆就把target_size从224降到192,因为Keras应用模型对输入尺寸有一定弹性,降分辨率后池化层可以吸收尺寸变化,模型结构不需要变。最后一步是开启混合精度:

from tensorflow.keras import mixed_precision mixed_precision.set_global_policy("mixed_float16")

混合精度用半精度计算,显存占用直接降三分之一左右,但对精度有微小影响,一般食物识别任务可以接受。注意开启混合精度后学习率可能需要略调低,否则个别层会不稳定。最后一招才是换小模型,从ResNet50切到MobileNetV3,这不是首选方案,因为模型切换会影响整个实验基线。

6. 进阶验证:用混淆矩阵把模型的短板挖出来

6.1 单张推理与置信度检查

模型训练完成后,最直接的一个验证动作是拿没见过的图片做单张推理。这里最容易犯的错误是忘了预处理对齐:训练时用了rescale=1/255,推理时也要做完全相同的归一化,否则模型输入的数值分布和训练时不一致,置信度会整体漂移。

import numpy as np from tensorflow.keras.preprocessing import image from tensorflow.keras.models import load_model model = load_model("weights/food_resnet50.h5") img = image.load_img("test/rice_001.jpg", target_size=(224, 224)) x = image.img_to_array(img) / 255.0 x = np.expand_dims(x, axis=0) probs = model.predict(x)[0] top3 = np.argsort(probs)[::-1][:3] for idx in top3: print(label_map[idx], round(probs[idx], 4))

predict返回的是softmax概率分布,argsort取前三个是因为食物识别里目标类别经常有近义类,比如三杯鸡和照烧鸡排在第二第三很正常,只看argmax会造成误杀。这个动作我每次替换数据集后都会跑五张样本图,检查人工主观判断和模型top3是否一致。

6.2 用混淆矩阵定位系统性问题

全局acc只能告诉你整体水平,告诉不了你哪类在拖后腿。我见过全局acc到了0.91的项目,某类“粥”的召回率只有0.31,这通常意味着这一类在训练集里被压得极薄或质量混乱。用混淆矩阵可以快速看到这个差异:

from sklearn.metrics import confusion_matrix, classification_report conf = confusion_matrix(y_true, y_pred) report = classification_report( y_true, y_pred, target_names=list(label_map.values()), digits=3 ) print(report)

y_true来自验证集的真实标签,y_pred是模型对验证集的预测结果。看classification_report时重点盯recall列,凡是低于0.5的类都是下一次迭代要补数据或调增强优先处理的类。全局acc高但某一类recall很低,属于典型的“平均性能掩盖局部失灵”,这类问题在真实部署中往往比错误率本身更危险,因为用户可能专点那个菜来识别。

6.3 让验证成为一种习惯

有一次我训练完模型,全局准确率到了0.91,觉得差不多了,直接拿去一个demo里跑。结果换成餐厅真实照片,同一道清炒时蔬换几个角度拍,识别结果一直在换。最后用混淆矩阵一查才发现,“清炒时蔬”类里混了沙拉、生菜、西蓝花三种风格完全不同的子类,对应类的召回率只有0.43,全局acc带着水分骗了我一整轮迭代。

从那以后我每次迭代都强制跑一遍固定流程:打印class_indices核对标签,统计每类样本数,跑一次验证集混淆矩阵,再单张推理五张新图。这套流程加起来十几分钟,但能挡掉所有“我以为可以上线、一上线就露馅”的情况。代码和工程包我整理在本次资源里,拿到手先按默认参数完整跑一遍,再替换成自己的数据集,祝你能把第一个食物识别模型顺利跑上线。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 23:26:53

Plate 开源仓库的 Agent 协作规范与工程化开发工作流指南

前端富文本UI组件 【免费下载链接】plate Rich-text editor with AI and shadcn/ui 项目地址: https://gitcode.com/GitHub_Trending/pl/plate 点击查看 免费下载 本篇指南以 Plate 仓库根目录下的 .agents/AGENTS.md 为骨架,系统讲解这套面向 AI Agent…

作者头像 李华
网站建设 2026/10/10 23:14:25

带差分隐私的协同过滤推荐:Python毕设资源与实验解析

简介:面向计算机相关专业学生与推荐系统入门研究者的毕业设计资源包,基于Python实现带差分隐私的协同过滤推荐系统,聚焦推荐流程中的用户隐私保护。从差分隐私与协同过滤的理论背景入手,梳理国内外研究现状,并阐述差分…

作者头像 李华
网站建设 2026/10/10 22:54:57

给任意一首歌做逐词卡拉OK高亮,误差控制在 5ms 级

给任意一首歌做逐词卡拉OK高亮,误差控制在 5ms 级 【免费下载链接】pdoom-video Code-rendered music video for "Im Upping My P(doom)" 项目地址: https://gitcode.com/gh_mirrors/pd/pdoom-video 卡拉OK逐词高亮看起来简单——词到了就亮、唱完…

作者头像 李华
网站建设 2026/10/10 22:51:54

两数之和算法详解:从暴力双循环到哈希表最优解与面试避坑

如果你打开力扣准备开始刷题,第一道题大概率就是《两数之和》。这道题看起来简单,但我见过太多人第一遍写的时候翻车:有人忘了处理重复元素,有人把返回下标写成了返回值,有人只会双重循环被面试官一问复杂度就卡壳。这…

作者头像 李华
网站建设 2026/10/10 22:50:48

电影知识图谱问答系统实战:从数据爬取到语义解析的完整落地路径

简介:这份资源面向自然语言处理、知识图谱与智能问答方向的学习者和开发者,聚焦电影领域,提供从数据爬取、实体关系抽取、知识存储到语义解析的完整工程实践。包内共438个文件,约67.55MB,以Java与JavaScript源码为主体…

作者头像 李华