
简介这份垃圾分类图片数据集面向从事深度学习图像分类的开发者与学习者尤其适合需要真实生活场景样本训练模型、完成课程设计或算法验证的人群。数据集共四十个类别标签采用“一级类别/二级类别”格式一级涵盖可回收物、厨余垃圾、有害垃圾和其他垃圾二级为一次性快餐盒、果皮果肉、旧衣服等具体物体类别与标签对应关系保存在训练集的dict文件中。资源包共2002个文件以1997张jpg图片为主体另含txt说明与json配置压缩包约538.59MB训练集图片按0至39号文件夹存放文件名即类别标签测试集含400张无标注图片testpath.txt记录全部测试文件名。目前已有781人学习读者可据此直接开展分类模型训练、验证与调参省去自行采集与标注成本快速搭建可复现的垃圾分类实验流程。1. 垃圾分类图片数据集.zip从拿到压缩包到跑通第一个分类器你从群里或者某个开源仓库拖下来一个叫「垃圾分类图片数据集.zip」的压缩包解压完一看四个文件夹可回收物、厨余垃圾、有害垃圾、其他垃圾。看着挺规整但真要拿它训一个能用的分类模型问题马上来了——类别不均衡、图片尺寸乱七八糟、有些图根本就是网图带水印甚至还有标注错类的。垃圾分类图片数据集.zip 这类资源本质上是给你一个「能起步」的底子不是给你一个「能上线」的成品。它解决的是从零到一的问题让你在本地把数据加载、清洗、增强、训练这条链路跑通验证你的模型结构和训练流程有没有问题。适合谁适合刚接触图像分类、想拿一个真实场景数据集练手的人也适合需要快速搭一个垃圾分类 demo 给业务方看的工程师。但如果你指望直接拿它训出 95% 以上准确率还不用清洗那大概率要翻车。2. 先搞清楚压缩包里到底是什么目录结构与类别定义2.1 解压后先别急着写代码用三条命令摸清家底很多人拿到 zip 直接torchvision.datasets.ImageFolder一把梭结果训练完发现某个类只有几十张图模型直接摆烂。我一般会先做三件事看目录层级、统计每类数量、抽查图片尺寸和格式。这三步能帮你避开后面 80% 的玄学问题。# 1. 看目录结构确认是不是按类别分文件夹 find ./垃圾分类图片数据集 -maxdepth 2 -type d | head -30 # 2. 统计每个类别下的图片数量假设是 jpg/png for d in ./垃圾分类图片数据集/*/; do echo -n $d: find $d -type f \( -iname *.jpg -o -iname *.jpeg -o -iname *.png \) | wc -l done # 3. 抽查图片尺寸分布看看有没有极端长宽比或超小图 find ./垃圾分类图片数据集 -type f -iname *.jpg | head -200 | xargs -I{} identify -format %wx%h\n {} 2/dev/null | sort | uniq -c | sort -rn | head -20第一条命令确认目录层级如果类别文件夹下面还有子文件夹ImageFolder会把子文件夹名当成类别那就错了。第二条命令统计数量如果最大类和最小类差 10 倍以上后面必须做重采样或加权损失。第三条命令用 ImageMagick 的identify看尺寸如果出现大量 100x100 以下的图训练时 resize 到 224 会糊成马赛克建议直接过滤掉。2.2 类别定义和标签映射别让「其他垃圾」变成垃圾桶垃圾分类图片数据集.zip 里最常见的四类可回收物Recyclable、厨余垃圾Kitchen Waste、有害垃圾Hazardous、其他垃圾Other。但不同来源的数据集标签名可能不一样有的用英文有的用拼音有的用数字。你需要在代码里显式定义映射关系不要依赖文件夹名的默认排序。import os from pathlib import Path # 显式定义类别顺序避免 ImageFolder 按字母排序导致标签错位 CLASS_NAMES [recyclable, kitchen_waste, hazardous, other] CLASS_TO_IDX {name: idx for idx, name in enumerate(CLASS_NAMES)} def build_file_list(root_dir): samples [] for cls_name in CLASS_NAMES: cls_dir Path(root_dir) / cls_name if not cls_dir.exists(): print(f[警告] 类别目录不存在: {cls_dir}) continue for img_path in cls_dir.glob(*): if img_path.suffix.lower() in [.jpg, .jpeg, .png, .bmp]: samples.append((str(img_path), CLASS_TO_IDX[cls_name])) return samples samples build_file_list(./垃圾分类图片数据集) print(f总样本数: {len(samples)}) for i, name in enumerate(CLASS_NAMES): count sum(1 for _, label in samples if label i) print(f{name}: {count} 张)这段代码的关键是CLASS_NAMES手动指定顺序而不是用os.listdir的返回顺序。os.listdir在不同文件系统上的顺序可能不一致今天训练标签 0 是可回收物明天换台机器就变成厨余垃圾了这种坑我踩过不止一次。另外build_file_list里做了后缀过滤把非图片文件比如.DS_Store、Thumbs.db排除掉否则ImageFolder会直接报错。2.3 数据清洗三类必须删掉的图片不是所有图都能用。我一般会写一个清洗脚本把下面三类图直接移走第一类尺寸小于 64x64 的resize 后信息损失太大第二类长宽比超过 3:1 的极端图比如全景截图第三类纯色图或近乎纯色的图这种图没有纹理信息只会干扰训练。from PIL import Image import shutil def is_valid_image(img_path, min_size64, max_aspect_ratio3.0): try: with Image.open(img_path) as img: w, h img.size if w min_size or h min_size: return False, too_small aspect max(w, h) / min(w, h) if aspect max_aspect_ratio: return False, extreme_aspect # 检查是否近乎纯色转灰度后看像素标准差 gray img.convert(L) import numpy as np arr np.array(gray) if arr.std() 10: return False, near_solid return True, ok except Exception as e: return False, fcorrupt: {e} def clean_dataset(root_dir, quarantine_dir./quarantine): os.makedirs(quarantine_dir, exist_okTrue) removed {too_small: 0, extreme_aspect: 0, near_solid: 0, corrupt: 0} for cls_name in CLASS_NAMES: cls_dir Path(root_dir) / cls_name for img_path in cls_dir.glob(*): if img_path.suffix.lower() not in [.jpg, .jpeg, .png, .bmp]: continue ok, reason is_valid_image(str(img_path)) if not ok: key reason.split(:)[0] removed[key] removed.get(key, 0) 1 shutil.move(str(img_path), os.path.join(quarantine_dir, img_path.name)) print(清洗结果:, removed) clean_dataset(./垃圾分类图片数据集)min_size64是经验值再小的话 resize 到 224 会严重模糊。max_aspect_ratio3.0能过滤掉截图和拼接图。arr.std() 10判断近乎纯色这个阈值可以按你的数据集调一般 8 到 15 之间都合理。清洗完把被移走的图放到quarantine目录别直接删万一误判还能捞回来。3. 用 PyTorch 搭一个能跑通的垃圾分类分类器3.1 数据增强和 DataLoader 的参数怎么设垃圾分类图片数据集.zip 的图片大多来自真实场景光照、角度、背景差异很大。数据增强是必须的但别乱加。我一般用RandomResizedCropRandomHorizontalFlipColorJitter三件套验证集只做ResizeCenterCrop。import torch from torch.utils.data import Dataset, DataLoader from torchvision import transforms from PIL import Image class GarbageDataset(Dataset): def __init__(self, samples, transformNone): self.samples samples self.transform transform def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label self.samples[idx] img Image.open(img_path).convert(RGB) if self.transform: img self.transform(img) return img, label train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.05), 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]), ]) # 按 8:2 划分训练集和验证集 import random random.seed(42) random.shuffle(samples) split int(len(samples) * 0.8) train_samples samples[:split] val_samples samples[split:] train_dataset GarbageDataset(train_samples, transformtrain_transform) val_dataset GarbageDataset(val_samples, transformval_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue)RandomResizedCrop(224, scale(0.7, 1.0))的scale下限别设太低0.5 以下会把垃圾袋裁得只剩一角模型学不到完整语义。ColorJitter的hue控制在 0.05 以内再大颜色就失真了有害垃圾的红色标志可能变成橙色。batch_size32是 8GB 显存下的安全值如果你用 16GB 卡可以上 64。num_workers设成 CPU 核数的一半左右太多反而拖慢。3.2 模型选型ResNet18 够用但别冻太多层垃圾分类图片数据集.zip 的规模一般在几千到几万张之间ResNet18 或 ResNet34 足够。我一般用预训练权重但只冻前两层后面的层都参与微调。冻太多层会导致模型欠拟合尤其当你的数据集和 ImageNet 差异较大时。import torch.nn as nn from torchvision import models def build_model(num_classes4, freeze_layers2): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 冻结前 freeze_layers 个 layer不算 conv1 和 bn1 layers_to_freeze [layer1, layer2, layer3, layer4] for i, layer_name in enumerate(layers_to_freeze): if i freeze_layers: for param in getattr(model, layer_name).parameters(): param.requires_grad False # 替换最后的全连接层 in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model model build_model(num_classes4, freeze_layers2) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)freeze_layers2表示冻结layer1和layer2layer3、layer4和fc参与训练。如果你数据集只有一两千张可以冻 3 层如果超过一万张冻 1 层甚至不冻。Dropout(0.3)放在fc前面防止过拟合。注意model.fc被替换后新加的层默认requires_gradTrue不用手动设。3.3 训练循环和类别不均衡的处理类别不均衡是垃圾分类数据集的通病。可回收物往往最多有害垃圾最少。我一般用加权交叉熵权重按类别频率的倒数算。from collections import Counter import torch.optim as optim # 计算类别权重 label_counts Counter([label for _, label in train_samples]) total sum(label_counts.values()) class_weights [] for i in range(len(CLASS_NAMES)): count label_counts.get(i, 1) weight total / (len(CLASS_NAMES) * count) class_weights.append(weight) class_weights torch.tensor(class_weights, dtypetorch.float).to(device) print(类别权重:, class_weights) criterion nn.CrossEntropyLoss(weightclass_weights) optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max20) def train_one_epoch(model, loader, criterion, optimizer, device): model.train() running_loss 0.0 correct 0 total 0 for imgs, labels in 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() * imgs.size(0) _, preds outputs.max(1) correct (preds labels).sum().item() total labels.size(0) return running_loss / total, correct / total def validate(model, loader, criterion, device): model.eval() running_loss 0.0 correct 0 total 0 with torch.no_grad(): for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) loss criterion(outputs, labels) running_loss loss.item() * imgs.size(0) _, preds outputs.max(1) correct (preds labels).sum().item() total labels.size(0) return running_loss / total, correct / total for epoch in range(20): train_loss, train_acc train_one_epoch(model, train_loader, criterion, optimizer, device) val_loss, val_acc validate(model, val_loader, criterion, device) scheduler.step() print(fEpoch {epoch1:02d} | train_loss{train_loss:.4f} train_acc{train_acc:.4f} | val_loss{val_loss:.4f} val_acc{val_acc:.4f})class_weights的公式是total / (num_classes * count)这样少数类的权重会更大。AdamW的lr1e-3配合CosineAnnealingLR是比较稳的组合T_max20对应总 epoch 数。训练时盯val_acc如果train_acc一直涨但val_acc不动甚至下降就是过拟合了该加 dropout 或减层。4. 避坑与排查垃圾分类数据集训练中最容易翻车的 5 个点4.1 现象训练 loss 正常下降但验证准确率始终在 25% 左右原因标签映射错位。ImageFolder默认按文件夹名字母排序如果你的CLASS_NAMES顺序和它不一致标签就全乱了。比如hazardous按字母排可能在kitchen_waste前面但你的映射表里kitchen_waste是 1hazardous是 2模型学到的和验证时对不上。解决不要用ImageFolder的class_to_idx自己写build_file_list显式指定顺序。训练前打印一批样本的图片路径和标签肉眼确认几张比如看到hazardous/xxx.jpg对应 label 2那就对了。4.2 现象某个类别准确率极低其他类别正常原因类别样本数差距过大模型倾向于预测多数类。比如可回收物 5000 张有害垃圾 200 张模型只要全猜可回收物就有 70% 准确率。解决除了加权损失还可以对少数类做过采样。用WeightedRandomSampler让每个 batch 里各类别比例接近。from torch.utils.data import WeightedRandomSampler sample_weights [class_weights[label].item() for _, label in train_samples] sampler WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue) train_loader DataLoader(train_dataset, batch_size32, samplersampler, num_workers4, pin_memoryTrue)注意用了sampler就不能再设shuffleTrue否则会报错。4.3 现象训练时 loss 变成 NaN原因学习率太大或者ColorJitter的hue参数导致某些像素值异常。也有可能是数据里有损坏图片Image.open没报错但转 tensor 后出现 NaN。解决先把lr降到 1e-4 试一轮。如果还 NaN在GarbageDataset.__getitem__里加 try-except遇到异常图片返回全零 tensor 并打印路径。另外检查Normalize的 mean/std 有没有写错写反了也会 NaN。4.4 现象验证集准确率比训练集还高原因验证集太小或者验证集和训练集有重叠。如果数据集总共只有几百张8:2 分完验证集就几十张波动极大。解决用 K 折交叉验证或者至少保证验证集每类不少于 50 张。如果数据实在少考虑用预训练模型做特征提取 线性分类器别微调整个网络。4.5 现象推理时单张图片预测结果和训练时不一致原因推理时的预处理和验证集不一致。比如验证集用了Resize(256) CenterCrop(224)推理时直接Resize(224)图像内容就不一样了。解决把验证集的 transform 单独存成一个变量推理时复用同一个。别凭记忆手写预处理。def predict(image_path, model, transform, device): model.eval() img Image.open(image_path).convert(RGB) tensor transform(img).unsqueeze(0).to(device) with torch.no_grad(): output model(tensor) prob torch.softmax(output, dim1) pred prob.argmax(dim1).item() return CLASS_NAMES[pred], prob[0][pred].item()5. 把准确率从 85% 推到 92% 的两个具体技巧第一个技巧是测试时增强TTA。对同一张验证图做多次不同变换原图、水平翻转、不同裁剪把预测概率平均。这个技巧在垃圾分类这种类间差异细碎的任务上特别管用因为有些图模型可能对翻转敏感。实现很简单在验证循环里对每张图跑三次前向取平均。def validate_with_tta(model, loader, device): model.eval() correct 0 total 0 with torch.no_grad(): for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) # 原始 out1 model(imgs) # 水平翻转 out2 model(torch.flip(imgs, dims[3])) # 轻微缩放后再裁剪用 interpolate 模拟 imgs_scaled torch.nn.functional.interpolate(imgs, scale_factor1.1, modebilinear, align_cornersFalse) imgs_cropped imgs_scaled[:, :, 8:232, 8:232] out3 model(imgs_cropped) outputs (out1 out2 out3) / 3.0 _, preds outputs.max(1) correct (preds labels).sum().item() total labels.size(0) return correct / total注意torch.flip的dims[3]是对宽度维度翻转别写成[2]。interpolate放大 1.1 倍再中心裁剪 224模拟了轻微的尺度变化。TTA 一般能涨 1 到 2 个点但推理时间变成三倍看你能不能接受。第二个技巧是分层学习率。fc层用 1e-3layer4用 1e-4layer3用 1e-5。这样浅层几乎不动深层微调fc 快速适应新类别。用 PyTorch 的param_groups实现。params [ {params: model.fc.parameters(), lr: 1e-3}, {params: model.layer4.parameters(), lr: 1e-4}, {params: model.layer3.parameters(), lr: 1e-5}, ] optimizer optim.AdamW(params, weight_decay1e-4)这个设置比统一学习率稳得多尤其当你的数据集和 ImageNet 分布差异大时。我一般先跑 5 个 epoch 看val_acc曲线如果前 5 个 epoch 就冲到 80% 以上说明分层学习率生效了如果还在 60% 晃检查数据清洗和标签映射。最后说一个我自己的习惯每次训完模型我会把验证集里预测错的图单独存一个文件夹按「真实类别_预测类别」命名。比如hazardous_pred_other文件夹里全是把有害垃圾认成其他垃圾的图。翻一遍这些图你就能发现模型到底在犯什么错——是颜色相近、背景干扰还是标注本身就有问题。这个习惯帮我省了无数次盲目调参的时间。希望帮到你。本文还有配套的精品资源点击获取