
简介这份深度学习图像分类数据集面向从事计算机视觉入门与垃圾分类识别实践的开发者、学生及算法爱好者围绕塑料瓶、玻璃瓶、金属瓶等可回收物类别构建可用于训练与评估轻量级分类模型帮助解决垃圾分类场景下样本获取难、类别标注不统一的问题。资源包共约2000个文件以1998张jpg图像为主体另含1个py脚本与1个json类别文件压缩包整体约39.65MB图像按类别目录存放相同类别归入同一文件夹便于直接接入常见训练框架读取。数据已预先划分为训练集约2200张、验证集约200张、测试集约100张比例合理可支撑模型训练、调参与最终性能验证的完整流程。目前已有268人学习下载适合希望快速搭建垃圾分类识别基线、验证网络结构或开展课程实验的读者参考使用。1. 从一堆瓶子照片说起这份垃圾分类数据集到底能跑出什么结果你手头如果正好有一批塑料瓶、玻璃瓶、金属瓶的照片想训一个三分类模型却卡在“数据从哪来、怎么组织、怎么喂给网络”这一步那这份资源就是冲这个场景来的。它把 2200 张左右的训练图、200 张左右的验证图、100 张左右的测试图按目录分好同一类别的样本放在同一个文件夹下类别数标称 3 类具体类别名称以随包的 json 类别文件为准。也就是说拿到手不用自己再写划分脚本直接接ImageFolder或flow_from_directory就能起训。适合刚入门图像分类、想跑通一个完整深度学习 pipeline 的人也适合需要快速验证某个 backbone 在垃圾细分类上表现的熟手。下面我按“先看清结构、再动手训练、最后避坑”的顺序拆一遍。2. 先看清目录与类别映射别让标签错位毁掉整轮训练2.1 目录结构决定了你用什么加载器这份数据集的核心约定只有一条相同类别的图片放在同一目录下。这是最经典的图像分类组织方式好处是几乎所有框架都有现成加载器直接吃这种结构。常见做法是根目录下并列train、val、test三个子目录每个子目录里再按类别分子目录。你拿到手第一件事不是写模型而是把目录树打印出来确认层级因为一旦层级多一层或少一层标签就会整体错位训练 loss 会以一种“玄学”的方式不下降。# 打印目录树确认 train/val/test 与类别子目录的层级关系 find ./dataset -maxdepth 3 -type d | sort # 统计每个类别目录下的图片数量核对是否与简介里的 2200/200/100 量级吻合 for d in ./dataset/train/*/; do echo -n $d : ls $d | wc -l done第一段find用来确认层级-maxdepth 3是因为根目录、划分目录、类别目录刚好三层超过三层说明结构不对。第二段循环按类别统计数量如果某个类别只有几十张而其他类别上千张那就是典型的类别不平衡后面训练要单独处理。注意图片文件名里带rf.和一长串哈希这是标注工具导出时的重命名规则不影响加载但别手动去改改了反而可能和 json 里的记录对不上。2.2 类别名以 json 为准不要靠猜简介里写的是“塑料瓶、玻璃瓶、金属瓶等等”这个“等等”就是坑。目录名可能是英文、可能是拼音、可能是数字编号真正的类别语义在 json 类别文件里。我一般会先把这个 json 读出来建立“目录名 → 类别中文名”的映射再决定后续可视化时怎么标注。import json import os # 读取类别映射文件具体文件名以资源包内实际为准 with open(./dataset/classes.json, r, encodingutf-8) as f: class_map json.load(f) print(类别映射:, class_map) # 列出训练集实际目录名和 json 做交叉核对 train_dir ./dataset/train dir_names sorted(os.listdir(train_dir)) print(训练集目录:, dir_names) # 找出目录里有、但 json 里没登记的类别这类必须处理 missing [d for d in dir_names if d not in class_map] print(未登记类别:, missing)这段代码的关键在最后一步交叉核对。class_map的键应该是目录名值是对应语义。如果missing非空说明要么 json 键名和目录名不一致要么目录里混进了无关文件夹比如.ipynb_checkpoints、__MACOSX这种。这类脏目录如果不剔除加载器会把它当成一个独立类别你的三分类瞬间变成四分类而且模型永远学不会这个“类别”准确率卡在一个诡异的值上。2.3 划分比例与数据量是否够用2200 训练、200 验证、100 测试比例大致是 22:2:1。这个划分对三分类来说验证集每类平均 60 多张测试集每类 30 多张做趋势判断够用但做精细的超参搜索就偏少验证集上的指标抖动会比较大。我的习惯是训练时固定随机种子并且把验证集指标和训练集指标一起看单看验证集容易过拟合到那 200 张上。如果要做交叉验证可以把训练集和验证集合并后重新做 5 折测试集始终留到最后只跑一次。提示测试集在调参阶段不要碰。很多人调着调着就把测试集当验证集用最后报出来的指标是虚高的上线就翻车。3. 把数据喂进模型ImageFolder 与 DataLoader 的实操配置3.1 用 torchvision 的 ImageFolder 快速起训目录结构确认无误后最省事的加载方式就是ImageFolder。它自动按子目录名生成类别索引顺序是目录名的字典序所以训练集和验证集必须用同一套目录名否则索引对不上。下面这段是能直接抄的骨架。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集增强随机裁剪翻转缓解小数据集过拟合 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) # 验证/测试集只做确定性变换不做随机增强 eval_tf 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(./dataset/train, transformtrain_tf) val_ds datasets.ImageFolder(./dataset/val, transformeval_tf) test_ds datasets.ImageFolder(./dataset/test, transformeval_tf) # 核对类别索引是否一致这一步不能省 assert train_ds.class_to_idx val_ds.class_to_idx test_ds.class_to_idx, \ 三个划分的类别索引不一致检查目录名 train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) test_loader DataLoader(test_ds, batch_size32, shuffleFalse, num_workers4) print(类别索引:, train_ds.class_to_idx) print(训练批次数:, len(train_loader))参数上Resize((224,224))是给 ImageNet 预训练 backbone 用的如果你换更小的自定义 CNN可以降到 128 省显存。Normalize的均值方差是 ImageNet 统计值用预训练权重时保持一致从头训也可以沿用影响不大。batch_size32在 2200 张上大约 69 个 batch显存不够就降到 16。num_workers在 Windows 上如果报错就设成 0这是老问题了。那个assert是血泪经验三个划分的class_to_idx必须完全相等否则你算出来的验证准确率是错的而且错得很隐蔽。3.2 换 TensorFlow 时的 flow_from_directory 写法如果你用 Keras逻辑一样只是 API 不同。关键点还是class_mode和类别顺序。from tensorflow.keras.preprocessing.image import ImageDataGenerator train_gen ImageDataGenerator( rescale1./255, rotation_range15, horizontal_flipTrue, brightness_range[0.8, 1.2], ).flow_from_directory( ./dataset/train, target_size(224, 224), batch_size32, class_modecategorical, ) val_gen ImageDataGenerator(rescale1./255).flow_from_directory( ./dataset/val, target_size(224, 224), batch_size32, class_modecategorical, shuffleFalse, ) # 打印类别索引确认和训练集一致 print(训练类别:, train_gen.class_indices) print(验证类别:, val_gen.class_indices)rescale1./255是把像素压到 0-1和 PyTorch 的 Normalize 是两种归一化路线选一种即可别混用。shuffleFalse在验证集上必须设否则你没法把预测结果和原图对应起来做错误分析。class_indices打印出来两边要一致不一致就是目录名有差异常见于大小写不同或多了空格。3.3 数据量偏小时该不该上预训练2200 张训练图三分类从头训一个 ResNet 也能收敛但容易过拟合验证准确率会在 80% 上下反复横跳。我的建议是直接用 ImageNet 预训练权重只替换最后的全连接层学习率调小到 1e-4 量级通常几个 epoch 就能到 90% 以上。如果非要从头训那就把增强开足加 dropout 和 weight decay并且早停。判断依据很简单看训练准确率和验证准确率的差距差距超过 10 个点就是过拟合该上预训练或加正则了。4. 训练与评估三分类任务的关键参数和验证方法4.1 损失函数与优化器的选择三分类是互斥的用交叉熵。优化器用 AdamW 比 SGD 省心尤其是小数据集。下面这段是训练循环的核心重点看学习率和权重衰减的设置。import torch.nn as nn import torch.optim as optim from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) # 用预训练 ResNet18替换最后一层为 3 分类 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, 3) model model.to(device) criterion nn.CrossEntropyLoss() # 预训练部分用小学习率新加的分类头用大学习率 optimizer optim.AdamW([ {params: model.fc.parameters(), lr: 1e-3}, {params: [p for n, p in model.named_parameters() if fc not in n], lr: 1e-4}, ], weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max20) for epoch in range(20): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() print(fepoch {epoch1}, loss {running_loss/len(train_loader):.4f})分层学习率是这里的关键。fc层是随机初始化的需要大学习率快速学预训练的主干已经学到了通用特征小学习率微调即可给大了会把预训练权重冲垮反而比从头训还差。weight_decay1e-4是轻量正则数据量小可以适当加大到 1e-3。CosineAnnealingLR让学习率平滑下降比阶梯下降更稳。如果你发现 loss 在前几个 epoch 就变成 nan多半是学习率给大了先把fc的 lr 降到 1e-4 试试。4.2 评估指标不能只看准确率三分类如果类别不平衡准确率会骗人。比如金属瓶占 70%模型全预测金属瓶也有 70% 准确率但塑料瓶和玻璃瓶全错。所以必须看混淆矩阵和每类的 precision/recall。from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) outputs model(imgs) preds outputs.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(confusion_matrix(all_labels, all_preds)) print(classification_report(all_labels, all_preds, target_nameslist(train_ds.class_to_idx.keys())))confusion_matrix的对角线是分对的非对角线看哪两类容易混。瓶类分类里透明塑料瓶和透明玻璃瓶在低分辨率下确实容易混如果混淆矩阵显示这两类互相错得多说明模型没学到材质差异可以考虑提高输入分辨率或者加一个专门区分透明材质的预处理。classification_report里的f1-score是综合指标比准确率更能反映真实水平。4.3 用测试集做最终验证的正确姿势测试集只跑一次跑之前把模型和超参全部冻结。跑的时候用和验证集完全一致的预处理不要临时改Resize尺寸或归一化参数否则指标不可比。如果测试集指标比验证集低很多比如低 10 个点以上说明验证集太小导致过拟合或者两个划分的数据分布不一致这时候要回头检查划分是不是随机的。注意测试集上的错误样本要单独存下来看。分类任务里看错样本比看指标有用得多往往能发现标注错误或者类别定义模糊的问题。5. 避坑与排查这份数据集最容易翻车的五个地方5.1 现象训练 loss 不降准确率卡在 33% 左右原因三分类随机猜就是 33%卡在这个值说明模型没学到任何东西。最常见的原因是标签错位也就是class_to_idx在训练集和验证集之间不一致或者 json 类别文件和目录名对不上导致标签全是乱的。另一个可能是归一化参数用错比如用了 ImageNet 的均值方差但输入是 0-255 没除 255。解决先跑一遍 3.1 里的assert确认三个划分的类别索引一致。再打印一个 batch 的图片和标签肉眼确认图片内容和标签对应。最后检查ToTensor()后面有没有接Normalize以及Normalize的参数是否和预处理匹配。5.2 现象验证准确率比训练准确率高很多原因这听起来是好事但通常是验证集太简单或者训练增强太狠。200 张验证图如果恰好都是清晰、角度正的样本而训练集里混了大量模糊、遮挡的图就会出现这种倒挂。另一个原因是训练时用了 dropout 或强增强验证时关掉了导致训练指标被压低。解决把训练集和验证集的图片各抽样 20 张拼图对比看分布是否一致。如果验证集明显更“干净”说明划分不是随机的需要重新划分。如果确认是增强导致的以验证指标为准即可不用管训练指标偏低。5.3 现象某个类别 recall 极低几乎全错原因类别不平衡或者这个类别的样本本身标注质量差。瓶类数据里金属瓶如果反光严重模型可能学不到稳定特征。也可能是这个类别的图片数量远少于其他类模型倾向于预测多数类。解决先统计每类数量如果差距超过 3 倍用WeightedRandomSampler做重采样或者在 loss 里加weight参数。然后把这个类别的错样本调出来看如果是标注错了就修正如果是图片质量太差就考虑剔除或补充。5.4 现象DataLoader 报错提示找不到文件或解码失败原因图片文件名里带rf.和长哈希某些系统对长文件名或特殊字符处理有问题。另一个常见原因是图片实际是 CMYK 或 RGBA 模式ImageFolder默认按 RGB 读遇到四通道图会报错。解决先确认文件确实存在用ls看文件名有没有被截断。如果是模式问题在 transform 里加transforms.Lambda(lambda x: x.convert(RGB))强制转成三通道。这个坑在混合来源的数据集里特别常见。5.5 现象训练到一半显存爆了原因batch_size给大了或者num_workers开太多导致内存里缓存了过多图片。2200 张 224x224 的图不算大但如果原图是几千万像素Resize之前会先解码成巨幅位图显存和内存都会飙。解决把batch_size降到 16 或 8num_workers降到 2。如果还爆在ImageFolder之前先用脚本把图片统一缩放到 256 短边存一份训练时直接读小图这是最彻底的办法。6. 进阶技巧用混淆矩阵反推数据问题再决定要不要补数据跑完第一轮别急着调模型结构先看混淆矩阵。我一般会把混淆矩阵和错样本拼图放在一起看这一步比调参有用得多。具体做法是从验证集里挑出所有预测错误的样本按“真实类别 → 预测类别”分组每组抽 9 张拼成 3x3 的图肉眼找规律。import matplotlib.pyplot as plt from PIL import Image import random # 收集错样本的路径和真实/预测标签 wrong [] model.eval() with torch.no_grad(): for imgs, labels in val_loader: imgs_dev imgs.to(device) preds model(imgs_dev).argmax(dim1).cpu() for i in range(len(labels)): if preds[i] ! labels[i]: wrong.append((labels[i].item(), preds[i].item())) # 按真实类别分组统计错误流向 from collections import Counter flow Counter(wrong) print(错误流向 (真实-预测):, flow) # 如果某一类错误特别集中说明这两类在特征上重叠flow这个计数器会告诉你错误集中在哪几对类别之间。比如(0, 1)有 30 个而(0, 2)只有 2 个说明类别 0 和类别 1 在模型眼里几乎一样。这时候你有三个选择提高输入分辨率让细节可辨、加针对性的数据增强比如对这两类做更强的颜色抖动、或者回头检查这两类的定义是不是本身就模糊。我遇到过塑料瓶和玻璃瓶混淆严重的情况最后发现是拍摄时都是透明瓶身模型只能靠瓶盖颜色区分把分辨率从 224 提到 320 后明显改善。另一个技巧是看模型对错样本的置信度。如果错样本的 softmax 概率接近 0.5说明模型在两类之间犹豫属于难样本如果概率接近 1.0 还错那基本是标注错了。把高置信度错样本单独拉出来人工复核往往能揪出标注问题。这套流程走下来你对这份数据的理解会比只看准确率深得多。从那以后我每次拿到新数据集都强制先跑一遍目录核对和混淆矩阵再动模型。希望帮到你。本文还有配套的精品资源点击获取