资讯动态

垃圾分类识别系统踩坑实录:CNN只占两成,数据与部署才是关键

发布时间:2026/10/18 14:48:34 来源:尧图企业网站定制
简介面向计算机相关专业学生与Python实战学习者的一套垃圾分类识别系统源码包基于深度学习CNN实现图像分类可用于毕业设计、课程设计或期末大作业。压缩包共包含2000个文件、约564MB其中近2000张jpg/jpeg图片构成完整的训练与测试数据集13个Python脚本覆盖数据加载、模型构建、训练与预测等核心流程另含JSON配置文件和MD说明文档目录结构清晰便于按模块查找和学习。已有254人浏览学习项目经导师指导认可并获评审98分设计完整、注释清晰。通过这套代码读者可系统掌握图像预处理、CNN网络搭建、模型训练调参及结果评估的完整链路也能将其作为高分毕设或课设的参考范例是兼具实战价值与示范意义的优质学习资料。1. 先说结论这个垃圾分类识别系统真正难的不是CNN而是数据坑打开毕设或课设选题库“基于Python与深度学习CNN的垃圾分类识别系统”常年排在前列因为它看起来好写、好答辩、好演示。拿到题目后多数人的第一反应是找个现成分类网络把四个垃圾类别一训练就算完事。真做下来你会发现CNN结构只占整个工作量的两成剩下八成全在数据整理、类别语义界定、推理封装和评估细节上。很多“能做到90%准确率”的项目一被追问就露馅训练集和验证集同源、类别不均、实测一张随手拍的塑料袋就翻车。这篇笔记按我的落地流程讲网络怎么选、数据怎么喂、训练怎么调、界面怎么接以及最容易被答辩问倒的五个细节。适合第一次做完整视觉项目的同学也适合想把这个题目从“能跑”做到“经得起追问”的从业者。2. 选型与网络设计自定义CNN还是预训练模型关键看你的数据集规模2.1 分类粒度与数据规模先定四分类还是细分到几十类垃圾分类识别的第一件事不是选网络而是定分类粒度。主流做法是两种一种是贴近国内生活场景的四分类——可回收垃圾、有害垃圾、厨余垃圾、其他垃圾每类样本好找演示直观答辩时也能结合分类标准讲清楚另一种是把可回收物继续拆成金属、纸张、塑料、玻璃、织物把厨余垃圾拆成果皮、剩菜、骨头等做成二十到四十个细类。细类方案看着更“系统”但坑也更深纸杯、纸盒、牛奶盒这类外观极度相似的物品连人眼都可能标错模型更是学得一头雾水。我的建议是如果总样本量不到一万张老老实实做四分类或八分类以内的方案。分类粒度决定的是系统天花板网络结构决定的是能不能摸到天花板。下表是我常用的选型判断依据分类方案每类最少样本建议工程复杂度适合场景四分类300500张低毕设、课程设计、快速演示八分类以内500800张中带细分要求的课设、竞赛入门二十类以上细化方案每类1000张以上高有充足数据来源或团体项目样本量不足时强行做细分类模型会把表面纹理和背景当分类依据训练曲线好看换几张真实照片立刻现原形。这个阶段把类别定义写进一页文档里也是答辩时非常讨巧的设计说明材料。2.2 自定义CNN与迁移学习的分界线每类低于500张优先用预训练骨干网络选型有一个很粗暴但实用的分界线每类样本低于500张时优先用预训练权重做迁移学习每类超过1000张再考虑从零训练自定义CNN。迁移学习的本质是复用在大规模通用图像上学到的边缘、纹理、形状特征垃圾分类的类别差异又恰恰建立在这些底层特征上所以效果立竿见影。自定义CNN则胜在结构简单、每层都能说清楚适合需要讲解源码的答辩场景。如果走自定义CNN路线我通常按四个卷积块搭骨架。输入统一缩放到224×224每块做一次下采样通道数按16→32→64→128翻倍。下面这个结构在四分类任务上足够稳定import torch.nn as nn class GarbageCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( # 块1输入224x224经池化后变成112x112 nn.Conv2d(3, 16, kernel_size3, padding1), nn.BatchNorm2d(16), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 块2112x112 - 56x56 nn.Conv2d(16, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 块356x56 - 28x28 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 块428x28 - 14x14 nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 14 * 14, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes), ) def forward(self, x): return self.classifier(self.features(x))代码逻辑并不复杂卷积层负责提取局部特征池化层逐步压缩分辨率全连接层把特征图映射成类别得分。两个容易忽略的细节是feature map 展平之后的维度是128 * 14 * 14这个值由输入分辨率、卷积 padding 和池化次数共同决定改输入尺寸时必须同步重算Dropout 放在全连接之前正则化力度取0.5属于常见经验值数据量小可调到0.6但不要超过0.7否则训练收敛会明显变慢。迁移学习方案在 PyTorch 生态里最常用的是 resnet18 这类通用分类骨干。关键代码只有两步加载带预训练权重的模型把最后一层全连接换成输出为类别数的结构。import torchvision.models as models import torch.nn as nn model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 替换最后一层全连接让它输出4类 model.fc nn.Linear(model.fc.in_features, 4)替换全连接时model.fc.in_features会自动读取原结构的输入维度不需要手动写死512或2048。迁移学习的“高分点”不在于这个替换动作而在于训练策略全部参数参与训练时学习率调小通常用 1e-4只训练最后一层时学习率可以放到 1e-3。答辩时能把这两者区别讲清楚比堆一个复杂结构有用得多。2.3 卷积模块参数怎么定卷积核、通道数与BN的搭配经验自定义CNN里最容易被过度设计的是通道数和卷积核尺寸。我见过有同学直接把第一层通道设到128四层下来参数爆炸训练集能冲到近乎100%验证集却一路走低——这就是典型的过拟合。小数据集上的经验法则是第一层从16或32起步每层翻倍最多到256就收住卷积核固定用3×3并加 padding1这样特征图分辨率只会被池化降低卷积本身不改变尺寸维度计算简单很多。BatchNorm 放在卷积之后、ReLU 之前作用是稳定每层输入的分布。垃圾图片的拍摄环境差异很大有的偏亮有的偏暗BN 能显著减少这种光照漂移带来的影响。ReLU 的inplaceTrue是 PyTorch 里的内存优化选项复用输入张量空间省一点显存不会影响结果。MaxPooling 用默认的 stride2把分辨率减半四层下来从224降到14特征图保留的已经是足够抽象的高层语义。这里的每一步选型都值得在文档里记录一句“为什么”因为答辩时老师不一定关心你的代码写得花不花哨但一定关心你能不能解释每个参数为什么是当前这个值。3. 数据准备与训练落地从零把数据集喂进CNN3.1 目录规范与标签映射用统一目录结构少写一半数据代码数据准备阶段我习惯先把数据集按“类别文件夹”组织好而不是用 CSV 记录标签。目录结构定成这样dataset/ ├── 可回收垃圾/ │ ├── 001.jpg │ ├── 002.jpg │ └── ... ├── 有害垃圾/ ├── 厨余垃圾/ └── 其他垃圾/这种结构的最大好处是PyTorch 的ImageFolder可以直接读取类别名即标签名省掉手工维护标签映射表的步骤。拿到外部数据后第一件事不是训练而是写一个几行的检查脚本把每类图片数量统计出来同时验证每一张图能不能被正常打开。这一步能在十分钟内暴露绝大多数数据问题import os from collections import Counter from PIL import Image data_root dataset # 目录名当作类别名按字典序排序保证每次运行标签顺序一致 classes sorted([d for d in os.listdir(data_root) if os.path.isdir(os.path.join(data_root, d))]) print(类别, classes) counts Counter() broken [] for cls in classes: cls_dir os.path.join(data_root, cls) for fn in os.listdir(cls_dir): path os.path.join(cls_dir, fn) counts[cls] 1 try: Image.open(path).verify() except Exception: broken.append(path) print(每类图片数, dict(counts)) print(损坏文件数, len(broken)) for p in broken[:10]: print(p)这段脚本里Image.open().verify()只做完整性校验不会真正解码整张图跑几千张图也就几秒钟。损坏文件一定要在训练前处理掉否则训练到某个 epoch 会突然抛异常而那时你根本不知道是哪张图出了问题。按目录分类还有一个附带好处数据清洗时可以人工抽查某个文件夹里有没有混入标错的图比对着 CSV 的路径找图片方便得多。3.2 数据划分按拍摄批次做分层划分别让同源图片同时进训练和验证集数据划分是垃圾分类项目里最隐蔽的一个坑。很多人用random_split按比例乱序切分代码没错但结果会虚高。原因在于手机拍摄的垃圾图片通常是连拍或一段视频抽帧得到同一批照片的光线、角度、背景都高度相似。这些“同源图片”如果一张进了训练集、一张进了验证集模型验证时等于变相开卷考试——它认出了背景而不是垃圾本身。我一般把拍摄来源或者文件夹批次当作划分单元保证同一个来源的照片只落在训练集、验证集其中一个里。如果数据集本身没有批次信息退一步的做法是按文件名的前缀分组后再划分。划分脚本不长核心是给每张图一个“来源组”字段按组做分层随机分配import random from collections import defaultdict # 假设 filename_list 是 (图片路径, 类别名) 的列表 # 用文件名前6位作为拍摄批次标识按批次划分可避免同源泄漏 groups defaultdict(list) for path, label in filename_list: batch_id os.path.basename(path)[:6] # 根据实际命名规则调整 groups[batch_id].append((path, label)) all_batch_ids list(groups.keys()) random.seed(2024) random.shuffle(all_batch_ids) train_ratio 0.8 split int(len(all_batch_ids) * train_ratio) train_ids set(all_batch_ids[:split]) val_ids set(all_batch_ids[split:]) train_samples [] val_samples [] for batch_id, sample_list in groups.items(): target train_samples if batch_id in train_ids else val_samples target.extend(sample_list)划分逻辑的关键是按批次而不是按单张图片分配。random.seed(2024)固定随机种子保证每次运行结果一致这也是答辩时能复现实验结果的重要习惯。验证集的比例在 0.15 到 0.2 之间通常够用测试集如果样本紧张可以暂缓但至少要预留二三十张完全没参与过训练和验证的图片用于最后的盲测。3.3 数据增强与预处理翻转裁剪安全旋转增强要克制垃圾分类场景里增强策略和普通图像分类不完全一样。水平翻转安全因为一个易拉罐左右翻转后仍然是易拉罐随机裁剪和缩放安全因为垃圾目标通常占图片中心区域颜色抖动适量使用模拟不同光照。但随机旋转要格外小心——一张插头旋转180度后形状特征还在可一旦旋转45度或90度它看起来更像一团破碎的厨余垃圾。塑料瓶、纸盒这类轴对称物品受影响小但电线、树枝、骨头这类不规则物品会被旋转增强彻底毁掉语义。下面是我常用的增强配置训练集和验证集必须分开处理from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(p0.5), 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(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])注意训练集用了RandomResizedCrop而验证集只用Resize(256) CenterCrop(224)这是刻意造成的差异训练时让模型看到裁剪位置和缩放比例不同的样本提升对目标位置和大小的鲁棒性验证时固定居中裁剪保证每个类别都在同样条件下比较。Normalize 的均值和标准差用的是 ImageNet 统计值自定义CNN从零训练时也可以先用这组默认值效果通常比全零初始化好如果你想严格从零训可以先算自己数据集的均值和标准差再做替换。另外提醒一点不要把旋转增强加到train_transform里除非你确认当前类别对旋转不敏感。我见过一个作品加了RandomRotation(30)之后厨余垃圾类准确率掉了近十个百分点去掉旋转后马上回升问题的根源就在形状语义被旋转破坏了。3.4 训练脚本与超参数Adam的学习率、batch size 和早停训练阶段我给一份可以直接跑通的简化脚本核心超参数都写在注释里。训练前把模型放到 GPU 或 CPU 上定义交叉熵损失和 Adam 优化器然后进入标准的三重循环外层 epoch中层 batch内层反向传播。加了早停逻辑后代码看起来比基础版本长一些但能自动保存验证集效果最好的权重避免手动盯训练曲线。import torch import torch.nn as nn from torch.utils.data import DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) model GarbageCNN(num_classes4).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) num_epochs 40 best_acc 0.0 patience 8 wait 0 for epoch in range(num_epochs): 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) # 前向传播输出logits loss criterion(outputs, labels) loss.backward() # 反向传播 optimizer.step() # 更新参数 running_loss loss.item() # 每个epoch结束后在验证集上评估 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 correct / total print(fepoch {epoch1}, loss: {running_loss:.4f}, val_acc: {val_acc:.4f}) # 早停验证集准确率连续多个epoch不涨就停止 if val_acc best_acc: best_acc val_acc wait 0 torch.save(model.state_dict(), best_model.pth) else: wait 1 if wait patience: print(early stopping at epoch, epoch 1) break代码里几个值得记住的参数Adam 默认学习率 1e-3 适合自定义CNN从零训练迁移学习模型调到 1e-4batch size 取 16 或 32这个项目图片分辨率高显存不够就降 batch 而不是降分辨率patience8表示验证集准确率连续8个epoch没有刷新就停止训练时间不会无限制拖长。torch.max(outputs, 1)直接取logits最大值的下标作为预测类别这一行在评估和后续推理里都会用到。保存权重时只保存state_dict()而不是整个模型文件更小加载时也更灵活。4. 识别系统的封装与评估从权重文件到能点按钮的界面4.1 单张图片推理训练时的预处理流程要原样复用训练完成后的第一件事是把模型从训练脚本里解放出来封装成一个独立的预测函数。推理函数需要接收图片路径、模型、类别列表、预处理管道和运行设备内部完成加载图片、预处理、前向计算、softmax转概率四步。这里最容易踩的坑是预处理不一致训练时用了Resize(256) CenterCrop(224)推理时却直接Resize((224, 224))结果尺寸虽一致但裁剪方式不同模型输出会明显抖动。from PIL import Image import torch def predict_image(image_path, model, class_names, transform, device): # 强制转RGB统一处理灰度图与RGBA图 img Image.open(image_path).convert(RGB) # 应用与验证集完全相同的预处理 img_tensor transform(img).unsqueeze(0).to(device) model.eval() with torch.no_grad(): logits model(img_tensor) probs torch.softmax(logits, dim1) conf, idx torch.max(probs, dim1) return class_names[idx.item()], conf.item()convert(RGB)这一行是必须的。很多随手拍的照片是 RGBA 模式或者灰度模式不转换的话张量通道数可能是4或1模型卷积层直接报错。unsqueeze(0)把单张图片扩展成 batch 维度为1的输入因为模型期望的输入形状是(B, C, H, W)B 这一维即使只有一张也要显式补上。model.eval()的作用是关闭 Dropout 和 BatchNorm 的训练行为这一步不写的话同一张图片两次推理结果可能不一样这在演示现场是致命的。如果想进一步观察模型“犹豫不决”的情况可以把所有类别的概率都打印出来后面第6章会用到这个能力。4.2 最小可演示界面Tkinter 三件套与推理卡顿处理系统界面用 Tkinter 就能做不需要引入重型桌面框架。标准流程只有三步一个按钮触发文件选择框一个标签显示识别结果一个标签显示置信度。代码精简后长这样import tkinter as tk from tkinter import filedialog def pick_and_predict(): path filedialog.askopenfilename(filetypes[(图片,*.jpg *.jpeg *.png)]) if not path: return result_label.config(text识别中……) root.update_idletasks() name, conf predict_image(path, model, class_names, val_transform, device) result_label.config(textf{name} 置信度 {conf:.2%}) root tk.Tk() root.title(垃圾分类识别系统) btn tk.Button(root, text选择图片, commandpick_and_predict, width20, height2) btn.pack(pady20) result_label tk.Label(root, text, font(微软雅黑, 14)) result_label.pack(pady10) root.mainloop()这个三件套界面虽然朴素但足够完成演示。真正需要注意的是点击按钮后卡顿的问题predict_image里的前向计算会阻塞 Tkinter 事件循环如果图片很大或模型较深界面会出现短暂的“无响应”状态。现场演示时我建议在程序启动时先跑一次空白预测做 warm-up让模型和 GPU 都进入就绪状态讲究一点的可以把推理放进子线程但毕设演示场景下warm-up 是性价比最高的方案。关于线程方案的具体问题第5章会专门展开。4.3 评估不只看总准确率混淆矩阵与每类精确率召回率总准确率是答辩时最显眼的数字但它会掩盖类别不平衡的问题。比如有害垃圾样本占80%模型把所有图都判成有害垃圾也有80%准确率。我每次拿到训练结果都会先打印混淆矩阵和分类报告这一步能定位模型到底在哪些类别上犯迷糊。from sklearn.metrics import confusion_matrix, classification_report # all_preds 和 all_labels 是在整个验证集上推理得到的列表 cm confusion_matrix(all_labels, all_preds) print(混淆矩阵) print(cm) target_names [可回收垃圾, 有害垃圾, 厨余垃圾, 其他垃圾] report classification_report(all_labels, all_preds, target_namestarget_names, digits3) print(report)confusion_matrix 的行表示真实类别列表示预测类别对角线越亮越好。classification_report 里重点看两个值每类的精确率precision预测成该类的结果里有多少是对的和召回率recall该类样本有多少被找出来了。对于垃圾分类系统我更关注召回率——可回收垃圾被误判成其他垃圾和厨余垃圾被误判成可回收垃圾对后续分拣的影响完全不同。如果某个类的召回率明显低于其他类说明它样本量少或者类内差异大需要回数据阶段补样而不是继续调learning rate。5. 垃圾分类识别系统避坑5个反复出现的翻车场景5.1 训练集和验证集同源验证准确率虚高现象训练loss稳步下降验证准确率却像过山车某一轮98%下一轮掉到80%最终保存的“最佳模型”拿到真实照片上一测还不如随便猜。原因数据划分时按单张图片随机切分连拍的同一纯背景照片同时进入了训练集和验证集。模型真正学会的是记住背景和拍摄参数而不是垃圾本身的特征。验证时碰到同批次的照片等于开卷考试碰到现场新拍的照片就完全失效。解决按拍摄批次或文件夹单元进行划分确保同一个来源的照片只存在于训练集和验证集之一。宁可验证集少几百张图也要保证它和训练集来自不同拍摄批次。数据量不足时最稳妥的做法是重新采集一批时间、光线、背景都有差异的图片作为独立测试集哪怕只有30张也比虚假的90%验证准确率有说服力。5.2 类别不均衡模型永远猜多数类现象总准确率看起来有85%但翻开混淆矩阵某个少数类几乎全被分到其他类每类的召回率差异极大。原因四个类别样本量悬殊比如可回收垃圾有2000张有害垃圾只有100张。CrossEntropyLoss 默认认为每个样本同等重要模型自然偏向样本量大的类别少数类梯度被淹没。解决先做数据层面的努力尽量补齐少数类样本样本补不齐时在损失函数里给少数类加权。PyTorch 里一行代码就能实现class_counts torch.tensor([1960, 120, 800, 500], dtypetorch.float) class_weights class_counts.sum() / (4 * class_counts) criterion nn.CrossEntropyLoss(weightclass_weights.to(device))class_weights按“每类样本数的倒数归一化”计算样本少的类得到更大的损失权重梯度更新时少数类被更多的“注意力”覆盖。这个改动往往能把少数类召回率从20%拉到70%以上代价是多数类准确率略降总体更均衡。答辩时能主动解释类别不均衡的处理过程是很明显的加分项。5.3 训练正常部署必崩图片尺寸、通道数、归一化不一致现象训练和验证阶段没有任何报错打包成界面程序后点选一张照片立刻抛异常错误信息通常涉及tensor shape不匹配或者expected 3 channels之类。原因推理代码没有复用训练时的val_transform而是自己重新写了一段预处理或者遇到灰度图、RGBA图时没有统一转成RGB。训练时数据加载器内部自动做了转换推理路径却漏掉了这一步导致通道数或归一化行为不一致。解决把val_transform定义成全局复用对象预测函数里强制Image.open(path).convert(RGB)。不要寄希望于每个用户都传标准RGB三通道的JPG。照片可能来自任何地方格式参差不齐入口处统一转换是唯一可靠的防线。记得在演示前用几张不同分辨率、不同格式的图片各测一遍。5.4 类别语义重叠模型对模糊样本永远低置信度现象训练损失正常但某些特定物品——比如透明塑料袋、纸杯、一次性竹筷——预测置信度始终在0.5上下徘徊换一张同款物品的图就换一个分类结果。原因类别定义本身存在模糊地带。透明塑料袋既是“可回收塑料”又能当“其他垃圾”处理标注者在数据准备阶段凭感觉打标同类物品被分进了不同文件夹模型看到语义冲突的样本学出来的决策边界自然摇摆不定。解决动手训练之前先写一页《分类判定说明》把最容易混淆的物品按统一口径归入某一类。常见做法是干净塑料袋归可回收垃圾有食物残留的污损塑料袋归其他垃圾纸杯按照“有无塑料淋膜”决定归纸张还是归其他一次性竹筷一律归其他垃圾。数据清洗阶段把不符合口径的样本挑出来重标。先消除标签噪声再谈网络优化这条顺序不能反。5.5 界面按下识别就无响应演示现场翻车现象训练好的模型单独用脚本测试没问题一接进Tkinter界面点击“识别”按钮后窗口像死了一样持续十几秒甚至更久才恢复观众和评委的第一印象直接崩盘。原因模型前向计算被放在了Tkinter主线程里。按钮的回调函数从头到尾同步执行推理期间事件循环被阻塞窗口自然无法响应点击和刷新。模型越大、机器越旧卡顿越明显。解决最省事的方案是程序启动后先做一次warm-up推理让权重加载、显存分配、卷积计算全部预先跑通之后的单张推理会快很多再进一步就把推理丢进子线程完成后通过队列把结果传回界面。毕设演示场景下我建议至少做到warm-up现场实测能从几十秒的“假死”降到一两秒内的可接受等待。另外识别前先在界面上显示“识别中……”给用户明确的反馈体验会好很多。6. 收尾技巧用“三张盲测图”判断模型到底能不能交付我到后面已经不看验证集准确率做交付判断了而是固定准备三张盲测图第一张是从训练集某个类别里挑的典型样张验证模型“有没有记住”第二张是用手机随手拍的同一个类别带阴影、带桌面杂物、角度歪斜验证模型“有没有泛化”第三张是网上找的抽象图或手绘图验证模型“会不会乱猜”。三张图并行测完结果比十轮验证准确率更能说明系统真实水平。如果第二张分类错误问题基本出在训练数据背景太单一回到数据层面补场景而不是继续调参。每次盲测失败的图片我都习惯性存进一个failed/文件夹并在文件名里注明“真实类别_预测类别”。一周后回看这个文件夹你会清晰看到模型在不同类别上的失败集中在哪里——是塑料袋反光被误认成纸张还是深色瓶子与厨余垃圾难分。这些翻车样本就是下一轮数据增强和数据清洗的最佳素材。以前我只盯验证集准确率后来在一次演示中被一张偏光的透明塑料袋照片当众打脸模型信誓旦旦地把它判断成了“可回收垃圾”实物其实是沾满油渍的外卖袋。从那时起我就坚持训练完必须拿没见过的实物场景图跑一遍再决定要不要上台。这个习惯帮我避开了很多次现场翻车。建议你也先准备自己的三张盲测图把训练、评估、盲测三步走完整再开始写界面代码。这套流程走通之后这个项目的每一层你都能解释清楚也愿意去迭代它。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑