简介:面向深度学习图像分类任务,这份资源提供了已做好划分的4类别垃圾图片数据集,覆盖厨余垃圾、有害垃圾等常见大类别,可直接用ImageFolder加载训练,省去数据清洗与划分环节。包内共2000个文件,主体为1998张jpg图像,另含一个可视化Python脚本及配套json配置,压缩包整体约591MB,适合图像分类入门与实践项目。数据规模方面,训练集39116张、测试集9777张,总计近5万张,图像分辨率在400至1000像素之间,类别分布与尺寸差异能较好模拟真实场景。数据已按类别文件夹整理,分别存放于train与test目录,脚本无需修改即可运行,随机传入一张图片即可绘制边界框并保存结果,便于快速预览与验证。已有465人学习使用,对于需要标准垃圾分类数据集或快速搭建分类流程的开发者具有直接参考价值。
1. 一份到手就能跑的垃圾图片分类数据集,省掉最麻烦的数据环节
做图像分类时,真正卡住进度的往往不是网络结构选型,而是数据本身。这份垃圾图片分类数据集一共包含 4 个大类别、接近 4.9 万张图片,数据总量 653 MB,分辨率集中在 400 到 1000 之间,全部是 RGB 彩色图。最难能可贵的是它已经按 train/test 完成了数据集划分,train 有 39,116 张,test 有 9,777 张,比例大约是 4:1,拿过来直接用 torchvision 的ImageFolder就能加载,连random_split都省了。对于做深度学习入门、垃圾分类识别 demo,或者只想在图片数据集上快速验证一个分类模型的从业者来说,这套数据的价值在于把数据清洗和划分这两个最琐碎的环节直接抹掉了。下面从目录结构、类别分布、可视化脚本到训练链路逐一拆开看。
2. 数据集目录结构与 ImageFolder 加载原理
2.1 目录组织方式:为什么按文件夹归档是分类数据的默认形态
先看数据集解压之后的目录骨架,readme 和可视化 py 脚本外层放,数据全部集中在data目录下,内部严格区分训练集与测试集。这种组织方式不是随手排的,而是分类任务里最常规的归档惯例:类别名作为目录名,某一个类别的所有图片放进同名目录。这样一来,有没有标签、标签是什么、标签对应的图片在哪,全部通过目录路径就能回答,不需要额外维护一份 CSV 映射表。
data/ ├── train/ │ ├── kitchen_waste/ # 厨余垃圾 │ ├── harmful_waste/ # 有害垃圾 │ ├── recyclable/ # 可回收物 │ └── other_waste/ # 其他垃圾 └── test/ ├── kitchen_waste/ ├── harmful_waste/ ├── recyclable/ └── other_waste/这里类别名的具体拼写以压缩包内实际名字为准,原理是固定的。torchvision.datasets.ImageFolder的加载逻辑就是扫描指定 root 下的一级子目录,把子目录名解析为类别标签,子目录下的每一张图片自动归类到对应标签。使用时要特别注意根路径不能选错:ImageFolder(root='data/train')与ImageFolder(root='data/test')是两个独立的数据集对象,二者共享相同的类别目录结构但数据互不重叠。
2.2 ImageFolder 加载:三行代码实例化训练集与测试集
下面这段代码就是这份数据集的标准打开方式。训练集和测试集分别做一次实例化,后续直接交给 DataLoader。
import torch from torchvision import datasets, transforms # 预处理流水线:统一尺寸 -> 转张量 -> 按 ImageNet 均值方差归一化 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]) ]) # 直接用 ImageFolder 加载,不需要手动解析文件名或构造标签向量 train_ds = datasets.ImageFolder(root='data/train', transform=transform) test_ds = datasets.ImageFolder(root='data/test', transform=transform) print(f"训练集类别数: {len(train_ds.classes)}") print(f"训练集样本数: {len(train_ds)}") print(f"测试集样本数: {len(test_ds)}")逻辑说明:ImageFolder先扫描子目录生成类别列表,再把图片路径与类别索引一一配对。transform在每次取样本时同步执行,Resize((224, 224))将原始分辨率 400 到 1000 的图片统一压缩为 224 见方;ToTensor会把 HWC 的 0-255 像素值转为 CHW 的 0.0-1.0 浮点张量;Normalize则按通道做标准化,让输入分布落在激活函数敏感区间。这两步顺序不能颠倒,先转张量再归一化是固定约定。
参数说明中值得关注的是Resize((224, 224))虽然会改变原始宽高比,但原始图片分辨率普遍在 400 以上,压缩后目标对象依然占足够像素,不会像小图拉伸那样产生严重畸变。若你的实验对比例敏感,可以改用transforms.Resize(256)配合CenterCrop(224),不过这份数据集本身的图像内容相对居中,直接 Resize 的精度损失可接受。
2.3 类别映射机制:class_to_idx 与样本配对关系
ImageFolder实例化后会自动生成类别索引字典和样本列表,这是模型训练时标签编码的依据所在。
print("类别 -> 索引映射:", train_ds.class_to_idx) # 输出示例: {'kitchen_waste': 0, 'harmful_waste': 1, 'recyclable': 2, 'other_waste': 3} print("单个样本结构:", train_ds.samples[0]) # 输出示例: ('data/train/kitchen_waste/22723.jpg', 0) print("样本总数:", len(train_ds.samples))逻辑说明:train_ds.samples是一个列表,每个元素为(图片绝对路径, 类别索引)的元组。ImageFolder内部依靠__getitem__按下标读取图片并应用 transform,因此无需手写一个Dataset子类。class_to_idx的字典序取决于os.listdir的返回顺序,不同操作系统下可能不同,但这不影响训练,因为索引与类别名的对应关系已经固定下来。
提示:如果解压后在 Windows 下运行发现样本数少了几个,优先检查每个类别目录里是否有
Thumbs.db一类的隐藏系统文件,以 jpg 为过滤条件是最稳妥的做法。
3. 数据集划分比例分析与类别均衡性验证
3.1 4:1 划分意味着什么
训练集 39,116 张、测试集 9,777 张,两者之比约 4:1。这个比例在分类任务中属于相当标准的划分策略:训练数据占 80%,测试数据留出 20% 做最终评估。相比常见的 7:3 或 8:1:1,这个划分在总量上保证了每个类别在训练阶段都有充足的样本,同时留出足够的测试空间来评估泛化能力。
| 数据集 | 图片数量 | 占比 | 作用 |
|---|---|---|---|
| train | 39,116 | 80.0% | 模型参数学习 |
| test | 9,777 | 20.0% | 最终泛化评估 |
| 合计 | 48,893 | 100% | 全量数据 |
对垃圾图像分类这类类间相似度偏高的任务,4:1 的划分在工程上是合理的。四个大类之间(厨余、有害、可回收、其他)存在部分外观交叉,比如塑料瓶既是可回收物也可能被误分类为其他垃圾,训练数据多一点能缓解边界样本带来的混淆。真正需要警觉的反而是类别间的均衡性,如果某个类别的图片数量明显偏低,模型会偏向样本量大的类。
3.2 用脚本验证每个类别的图片数量
拿到数据集后先别急着训练,第一步要确认四个类别的图片数量是否均衡。下面这段脚本用标准库遍历目录,统计 train 和 test 下每个类别的图片文件数,不需要安装额外依赖。
import os from collections import Counter def count_images_per_class(root_dir): """遍历一级子目录,统计每个目录下的图片数量""" counter = Counter() for class_name in os.listdir(root_dir): class_path = os.path.join(root_dir, class_name) if os.path.isdir(class_path): # 仅统计 jpg/jpeg 文件,防止混入系统隐藏文件 num = len([f for f in os.listdir(class_path) if f.lower().endswith(('.jpg', '.jpeg'))]) counter[class_name] = num return counter train_counts = count_images_per_class('data/train') test_counts = count_images_per_class('data/test') print("训练集各类别数量:", dict(train_counts)) print("测试集各类别数量:", dict(test_counts)) print("训练集合计:", sum(train_counts.values())) print("测试集合计:", sum(test_counts.values()))逻辑说明:os.listdir只做目录内容遍历,没法判断路径类型,所以先用os.path.isdir过滤目录,再用后缀名过滤图片。这段代码同时可以验证摘要中的总样本数是否吻合。Counter在此处的场景非常合适,设定 key 为类别名,value 直接累计图片数量。
如果统计结果出现某个类别占比超过 40% 而其他类别不足 15%,就要考虑类别均衡策略。常见做法是WeightedRandomSampler,先根据样本数计算权重,让少量类别获得更高的采样概率。但就这份数据集而言,四个大类的分布相对平均,直接在 DataLoader 中开启 shuffle 即可,没有额外加权的必要。
3.3 训练集内部还要不要再切一份验证集
data/test只能用于最终评估,不能作为训练过程中的调参依据。反复使用 test 集判断模型好坏会造成信息泄漏,最终精度会失真。所以在data/train内部再划分出一部分作为验证集是常规操作。
from torch.utils.data import random_split train_len = int(len(train_ds) * 0.9) val_len = len(train_ds) - train_len train_sub, val_sub = random_split(train_ds, [train_len, val_len]) print(f"子训练集: {len(train_sub)}, 验证集: {len(val_sub)}")逻辑说明:random_split按照给定长度比例切分数据集,不需要动磁盘上的文件。子训练集用于梯度回传,验证集用于每轮结束后的指标监测,比如判断是否过拟合或决定何时降低学习率。9:1 的内部分割保留下来后,最终测试仍然使用test_ds,这样整个数据使用链路就是:原始数据 -> 90% 训练 / 10% 验证 -> 独立测试集。
提示:
random_split默认使用全局随机种子,如果希望每次运行结果可复现,在主程序开头加上torch.manual_seed(0)。
4. 可视化脚本细节:边界框绘制与数据抽查
4.1 为什么分类数据集也要画边界框
分类任务本身不需要边界框标注做监督信号,但可视化脚本在这里解决的是「人的确认成本」。计算机把目录名当作标签,人却需要眼睛核对图片内容是否真的属于那个类别。当图片分辨率为 400 到 1000 且目标物体占画面比例不一致时,直接肉眼看原图很容易漏掉角落里的异常样本;如果在图上用一个矩形框把目标区域圈出来,抽查效率会高很多。该脚本的定位就是随机抽图、绘制边界框、保存到当前目录,不依赖 GPU,也不需要读取任何标注文件。
4.2 随机抽图与矩形框绘制实现
脚本的核心逻辑分三步:遍历得到全部图片路径、随机取一张、用 PIL 绘制矩形框并保存。边界框的坐标在这里是人为给定的示意区域,因为数据集本质是分类任务,没有标签文件可读取。
import glob import random from PIL import Image, ImageDraw # 递归收集 train 目录下所有 jpg 图片 image_paths = glob.glob('data/train/*/*.jpg', recursive=True) if len(image_paths) == 0: raise FileNotFoundError("没有找到任何图片,请检查 data/train 路径") # 随机选一张,模拟"传入一张图片"的入口 img_path = random.choice(image_paths) img = Image.open(img_path).convert('RGB') draw = ImageDraw.Draw(img) w, h = img.size # 以图片中心为锚点,绘制占画面 80% 区域的矩形框 box = (int(w * 0.1), int(h * 0.1), int(w * 0.9), int(h * 0.9)) draw.rectangle(box, outline='red', width=4) # 保存到当前目录,不覆盖原图 output_name = 'visual_check.jpg' img.save(output_name) print(f"已保存: {output_name}, 原图来自: {img_path}")逻辑说明:glob.glob('data/train/*/*.jpg', recursive=True)匹配一级子目录下的全部 jpg,这里递归标志实际不改变匹配行为,因为通配符已经指定了两层目录。random.choice负责随机性,每次运行都会挑出不同样本。ImageDraw.Draw是基于 PIL 的绘图句柄,rectangle接收左上角和右下角坐标构成的对角线元组,outline指边框颜色,width控制线宽。
边界框的参数调整思路:如果图片中物体经常偏左或偏右,可以改为水平居中、垂直拉伸的框,比如(int(w*0.2), int(h*0.05), int(w*0.8), int(h*0.95))。不过该资源附带脚本的定位只是通用可视化工具,不需要精确匹配目标位置。如果你的任务是识别目标类别后再框选,那就不是这套脚本的工作范围了。
4.3 脚本运行与输出效果验证
命令行直接执行python visualize.py,脚本会随机抽取一张训练图片,在当前目录下生成visual_check.jpg,同时控制台打印原始图片路径。打开输出图片确认三件事:图片内容与目录名是否一致、边界框是否完整落在画面内、颜色是否正常。若图片偏暗或偏色,确认原始采集设备与格式后再决定是否加入颜色校正,但这不影响分类训练,因为归一化阶段会削弱光照影响。
如果脚本运行时报ModuleNotFoundError: No module named 'PIL',执行pip install pillow即可。该脚本只依赖 Pillow 和标准库,不需要安装 torch,所以在没有 CUDA 的机器上也能跑。
5. 预划分数据直接接入 PyTorch 训练链路
5.1 DataLoader 参数配置与样本批处理
ImageFolder实例化完成的是单样本读取,实际训练还需要DataLoader将样本组装成 batch,并在训练时做打乱、并行读取等调度。
from torch.utils.data import DataLoader train_loader = DataLoader(train_sub, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_sub, batch_size=32, shuffle=False, num_workers=4) test_loader = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=4)逻辑说明:train_sub来自前面random_split得到的子训练集,shuffle=True让每个 epoch 的梯度更新顺序不同,避免模型记住样本排列;验证集和测试集不需要打乱,只做前向计算。num_workers控制子进程数量,Windows 下建议设为 0 或 2,因为多进程数据加载在 Windows 的 spawn 模式下会频繁拉起进程,反而更慢;Linux 下设为 CPU 核心数的一半即可。
5.2 基于 ResNet18 的快速 baseline
拿到预划分数据后快速搭建一个分类 baseline,最直接的选择是 ImageNet 预训练的 ResNet18,它能在参数量和精度之间取得平衡,适合 4 分类任务在较小计算资源下跑通。
import torch.nn as nn from torchvision import models model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) in_features = model.fc.in_features num_classes = len(train_ds.classes) model.fc = nn.Linear(in_features, num_classes) print(f"特征维度: {in_features}, 分类数: {num_classes}")逻辑说明:model.fc原本输出 1000 类,在迁移学习场景下替换为 4 输出的全连接层,前面的卷积特征提取层全部保留预训练权重。in_features从原 fc 层中取出,保证替换后维度匹配。冻结骨干网络的requires_grad=False策略只在样本量极小的情况下使用,本数据集接近 4 万张训练图,更适合全量微调。
5.3 用独立测试集完成最终验证
训练完成后,在test_loader上做一次完整前向推理,打印准确率,这是判断整份数据可用性的直接方式。
model.eval() correct, total = 0, 0 with torch.no_grad(): for images, labels in test_loader: outputs = model(images) _, predicted = torch.max(outputs, dim=1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f"测试集准确率: {correct / total:.4f}")逻辑说明:model.eval()关闭 Dropout 和 BatchNorm 的统计更新,确保推理结果稳定;torch.no_grad()禁止梯度跟踪,减少显存占用和计算开销。torch.max(outputs, dim=1)返回每个样本在 4 个类别上的最大得分索引,与真实标签做逐元素比较后累加计数,最终准确率反映这份预划分数据在跨类泛化上的真实水平。
本文还有配套的精品资源,点击获取