简介:一套面向垃圾分类识别场景的深度学习方法资源,内含约八万张图片,覆盖二百四十五个常见类别,并配套深度学习框架实现代码,可直接用于模型训练与推理。资源主要面向深度学习初学者、算法工程师和环保智能化项目开发者,能够省去大规模数据收集、清洗与预处理的繁琐环节,让人把精力集中在模型调试与效果提升上。压缩包共计两千个文件,其中JPG与JPEG格式图片约占一千九百八十五个,其余为若干Python训练脚本、一份JSON配置参数文件和一份Markdown使用说明,整体大小约五百六十一兆字节,文件按类别组织,目录层次清晰,便于按需加载与二次开发。当前已有131人学习/下载。下载后即可复现图像分类全流程,图片样例涵盖电池、塑料瓶等废弃物类别,代码内置数据读取、模型构建与评估逻辑,可支撑现有模型微调、新类别扩展,适合作为垃圾分类项目的基线方案。
1. 8万张图245个类,这份垃圾分类数据集和tf代码到底能做什么
先说结论:这份“下载即用”的资源,本质上是一套完整的图像分类训练套餐——8万张图片被按245个垃圾类别组织好,配好了一套TensorFlow训练代码。你拿到手不需要满网找爬虫脚本、不用一张张整理素材,解压、配环境、跑通训练,就能得到一个能对常见垃圾做分类的模型。这个体量放在垃圾分类场景里属于中等偏上:常见的公开分类数据集大多在几十到一百多类徘徊,245类覆盖了可回收物、厨余垃圾、有害垃圾和其他垃圾的绝大部分常见实物形态。
这个资源最适合两类人:一类是做智慧环卫、智能分拣箱、巡检机器人项目的工程师,需要用真实照片快速验证算法可行性;另一类是想拿“工业级图像分类”做毕设或练手的学生,8万张的规模比CIFAR这类玩具数据集有说服力。但它并不是解压就能跑出90%准确率的魔法包,图片质量参差、类别不均衡、标注噪声这些坑都在后面等着你,这份笔记会把跑通它的路和坑一起拆开。
2. 拆开这份数据集和tf代码包:图片、标注与训练入口
2.1 245个类别是怎么组织的:从类别编号到目录命名
这类打包好的数据集最常见组织方式是按目录分号命名:解压后是一个大类文件夹,下面挂着245个子文件夹,每个子文件夹名字就是类别标签,比如plastic_bottle、banana_peel、battery这类英文短横线命名,也有的会用编号加名称的形式如001_carton。这意味着标签不在一份XML或JSON里,而是隐含在目录结构里,训练代码读取标签时直接取父目录名即可。
这个设计对后面做数据加载很友好,tf.keras.utils.image_dataset_from_directory或者tf.data的list_files都能直接按目录解析出标签和类别索引。但从工程角度有三个隐患:第一,目录名如果夹杂中文或空格,在跨平台运行时容易出编码问题,特别是用Windows解压后放到Linux服务器上训练的场景;第二,类别目录的字母排序会成为模型的类别索引顺序,这决定了预测输出数字与类名的映射关系,一旦改动目录名,模型的类别解释就全偏了;第三,245个目录里很可能存在近义类,比如plastic_bag和plastic_wrap,视觉上极度接近,这会让模型在训练时学得异常艰难。
拿到数据后第一件事永远不是开训练,而是先绘制一张类别分布直方图。因为每类326张这个平均值在25%分位和75%分位之间大概率差异悬殊,有的类可能有一两千张,有的只有几十张。后面第5章会专门讲这个坑,这里先记住:任何分类数据集的第一个动作是统计,不是训练。
2.2 图片质量与标注形状:哪些文件会被训练流程拒之门外
8万张图片听着多,但实际文件形态大概率并不统一。从收集渠道来看,这类数据集往往混合了爬取的网络图片、监控截图和手机拍摄照片,因此尺寸从几十KB到几MB都有,分辨率从320×240到4000×3000都可能出现。编码格式多数是JPEG,但也可能混入PNG带透明通道的图,甚至少数损坏的截断文件。
对TensorFlow分类任务来说,PNG和JPEG混跑问题不大,因为tf.image.decode_image会自动按头部信息解码,但有两个硬伤必须处理:一是损坏图片在解码时会直接抛InvalidArgumentError,让整个训练中断;二是大尺寸图片如果不做统一缩放,显存占用会像脱缰野马。常见的做法是在数据管线里加一层解码容错,对损坏文件做跳过或移除处理,这比训练到一半崩了再回去排查要省心太多。
标注形状也是一个值得提前确认的点。这个数据集定位是“分类”,绝大多数情况下没有目标检测框,标注就是类别标签本身。但如果你拿的是别人二次加工过的版本,里面可能混着VOC格式的XML框标注或COCO的JSON标注,那就意味着任务已经不是分类而是检测了。所以先把任务形态对齐:这份标题写的是“分类数据集”,后续所有代码按单标签多分类设计,输出层是245路的Softmax,不是带回归头的检测器。
2.3 tf代码包的常见骨架与训练入口定位
标题里写“tf代码”,说明这套资源除了数据还附带一套TensorFlow训练脚本。常见打包形式通常是几个.py文件加一个requirements.txt,核心文件大概包括:数据加载与预处理模块、模型构建模块、训练主循环、预测与评估脚本。如果你拿到的版本只给了模型结构文件而没有完整训练脚本,那需要自己补数据管线,工作量也不会太大。
确定入口的快速方法是看主训练文件中tf.keras.applications或models.load_model的调用位置。如果代码用了预训练模型做迁移学习,入口大概率在这里;如果代码是纯手工搭建卷积网络,入口就在Sequential或自定义Model处。这一判断决定了后面调参时改哪里,也决定了训练时间预期——纯手搭的浅层网络在8万张图上训练速度未必慢,但精度上限通常低于预训练模型。
拿到代码后第一轮建议做的事是静态走查,别急着装环境。在代码编辑器里全局搜索三个关键词:batch_size、learning_rate、num_classes。把num_classes是否等于245确认下来,这是最容易被忽略却影响成败的一行。有些打包代码原先是在别的数据集上跑通的,类别数写的是10或者1000,不改成245,最后一层全连接层的输出维度就和标签对不上。
3. 让这份“下载即用”的代码在本地真正跑起来
3.1 准备环境:TensorFlow版本、GPU驱动与计算图检查
先说版本匹配,这是所有TensorFlow代码跑不通的第一嫌疑犯。有的打包代码是TF 1.x写的,有的已经迁到TF 2.x,两者的API差异非常大:TF 1.x里的tf.placeholder、tf.Session()在TF 2.x的Eager模式下完全不可用,而TF 2.x的tf.keras在TF 1.x下也缺不少功能。建议直接用TF 2.x运行,如果代码是旧版API,常见的几处tf.Session().run()改成model.fit()其实工作量不大。
GPU环境建议用nvidia-smi先看驱动支持的CUDA版本,再按TensorFlow的版本要求装对应CUDA和cuDNN。一个已经翻过无数车的事实是:TensorFlow对CUDA版本非常挑剔,2.10以上版本默认要求CUDA 11.2,如果你装了CUDA 12.x还强行跑,tensorflow.python.framework.errors_impl.NotFoundError会直接告诉你找不到libcudart.so。如果你不想折腾驱动的匹配,CPU版也能跑小批量冒烟测试,只是8万张图的全量训练别指望一天能跑完。
环境配好后可以用一段极简代码验证计算图和GPU是否真的参与计算:
import tensorflow as tf print("TensorFlow 版本:", tf.__version__) print("GPU 可用:", tf.config.list_physical_devices('GPU')) print("GPU 设备名称:", tf.test.gpu_device_name() if tf.test.is_gpu_available() else "未检测到GPU") # 简单跑一个矩阵乘法,确认GPU上的计算路径真的通 with tf.device('/GPU:0'): a = tf.random.normal([100, 100]) b = tf.random.normal([100, 100]) c = tf.matmul(a, b) print("矩阵乘法结果形状:", c.shape)这段代码的逻辑很直白:先打印版本和GPU发现情况,再在GPU上执行一次随机矩阵乘法。打印出的设备名称如果是/device:GPU:0说明驱动和TensorFlow版本已经握手成功,后面训练时才会真正用到显存。
3.2 数据加载的两种写法:image_dataset_from_directory与tf.data手写管线
因为数据集按目录组织,最简单的写法是用image_dataset_from_directory,五行就能把数据流水线建好。另一种做法是用tf.data.Dataset.list_files手写管线,好处是对图片解码、尺寸统一、数据增强的每个环节都完全可控。这个数据集图片尺寸不一、质量有差异,我一般推荐后者,虽然代码多一点,但翻车时你能精确判断是哪一步出了问题。
下面这段代码是一个可直接复用的手写分类管线,兼容损坏图片跳过、统一缩放、随机增强和性能预取:
import tensorflow as tf IMG_SIZE = (224, 224) BATCH_SIZE = 32 AUTOTUNE = tf.data.AUTOTUNE def decode_and_preprocess(path, label): # 读取二进制内容,decode_img内部处理jpg/png混合情况 img = tf.io.read_file(path) img = tf.image.decode_image(img, channels=3, expand_animations=False) # 统一缩放尺寸,224x224是EfficientNet/MobileNet系列标准输入 img = tf.image.resize(img, IMG_SIZE) img = tf.cast(img, tf.float32) / 127.5 - 1.0 # 归一化到[-1,1] return img, label def create_dataset(data_dir): # list_files会递归扫描所有子目录,路径里包含类别目录名 files = tf.data.Dataset.list_files(data_dir + "/*/*.jpg", shuffle=True) # 从路径里解析标签:父目录名是类别,用tf.strings.split按'/'切分后取倒数第二个 labels = files.map(lambda p: tf.strings.split(p, '/')[-2]) # 将字符串标签映射为整数索引,这是tf.keras.layers.StringLookup做的事 class_names = sorted(set([p.split('/')[-2] for p in tf.io.gfile.glob(data_dir + "/*")])) label_table = tf.lookup.StaticHashTable( tf.lookup.KeyValueTensorInitializer( keys=tf.constant(class_names), values=tf.constant(range(len(class_names)), dtype=tf.int64) ), default_value=-1) labels = labels.map(lambda l: label_table.lookup(l)) ds = tf.data.Dataset.zip((files, labels)) ds = ds.map(decode_and_preprocess, num_parallel_calls=AUTOTUNE) ds = ds.shuffle(10000).batch(BATCH_SIZE).prefetch(AUTOTUNE) return ds, class_names train_ds, class_names = create_dataset("data/train") val_ds, _ = create_dataset("data/val") print("类别总数:", len(class_names)) print("训练批次数量:", len(train_ds))这段代码的关键逻辑是标签解析不再依赖Keras的目录推断,而是通过StringLookup或StaticHashTable把目录名转成整数索引。注意decode_image补齐了位深和通道统一,避免8万张图里混入灰度图和带Alpha通道的PNG导致训练时形状不匹配。shuffle(10000)的缓冲大小需要和数据集规模匹配,8万张图的训练集这里也够用,但验证集我建议去掉shuffle,保持评估稳定性。
3.3 启动第一个5个epoch的冒烟训练
第一次训练不要直接上全量,先跑一个极小的冒烟测试:用1%的数据、5个epoch验证整个数据管线、模型构建、损失计算、反向传播这条链路是通的。这一步通常能在10分钟内跑完,却能帮你过滤掉90%的低级错误。下面这段脚本把冒烟训练独立成文件,后续要切全量只需把路径指向完整数据目录。
import tensorflow as tf def build_model(num_classes=245): base = tf.keras.applications.MobileNetV3Large( input_shape=(224, 224, 3), include_top=False, weights='imagenet', pooling='avg' ) base.trainable = False # 冒烟阶段冻结预训练权重,加速迭代 model = tf.keras.Sequential([ base, tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(num_classes, activation='softmax') ]) return model model = build_model(num_classes=245) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) # 冒烟训练只取3个batch small_train = train_ds.take(3).repeat(5) small_val = val_ds.take(1) history = model.fit( small_train, epochs=5, validation_data=small_val, verbose=1 )这段代码里MobileNetV3Large是245类分类里性价比很高的选择,比EfficientNetB0更快,比ResNet50更省显存。冒烟阶段冻结backbone只训练最后的全连接层,目的是验证梯度能正常回传。take(3).repeat(5)构造了一个只有3个batch的小数据集凑够5个epoch,避免8万张图还没读完就已经把显存烧掉。冒烟通过了再解冻backbone做正式训练。
4. 245类分类训练的参数调优:让损失真正降下来
4.1 输入尺寸与增强策略:224还是320,要不要做CutMix
垃圾分类图片的真实场景有个明显特点:不同类别在视觉尺度上差异很大——一个饮料瓶和一张纸巾在画面中的占比差距悬殊。这意味着统一缩放尺寸的选择直接影响精度上限。224×224是MobileNet系列的标准输入,训练速度快,但对细粒度区分(比如区分透明塑料瓶和白色塑料瓶)会丢失纹理细节;320×240这类更大输入能保留更多特征,但显存占用和训练时间都会翻倍。我一般建议第一轮用224跑通全局,如果发现某些混淆类别始终分不开,再对模型输入尺寸做升级。
增强策略上,基础的水平翻转和随机裁剪是必须的,色彩抖动(亮度、对比度、饱和度随机偏移)对这个数据集特别有效,因为垃圾桶、垃圾袋里的拍摄光照条件五花八门。CutMix和MixUp这类高阶增强在245类场景下能显著提升模型泛化性,但有个前提是类别数多、每类样本少的情况下,混类增强会引入标签噪声,需要把alpha参数调小。推荐起步配置是RandomFlip+RandomBrightness+RandomContrast,后面看混淆矩阵再加CutMix。
4.2 迁移学习的正确打开方式:冻结、解冻、分段学习率
8万张图配245个类,这个数据量刚好卡在“从零训练勉强能收敛”的边界。但不要赌从零训练,ImageNet预训练权重在这个场景下几乎是白捡的优势。垃圾图片虽然和自然图像有域差异,但边缘、纹理、颜色这些底层特征仍然是通用的。正确的做法分三步:第一步冻结backbone只训练分类头和最后几层,用一个较大的学习率快速把新类别映射学出来;第二步解冻backbone的后半段,用更小的学习率做整体微调;第三步全模型微调,这时学习率要降到1e-5量级,防止预训练特征被破坏。
这个分段调优策略比一次性全模型训练有两大优势:一是收敛更快,因为分类头在一开始就有足够的梯度信号;二是更稳,预训练权重在初期不会被大学习率直接冲击。如果你用的是ResNet50或EfficientNet,实践中我通常把70%的训练时间花在第二阶段,因为分类头和backbone后半段的适配才是精度提升的关键。
4.3 类别不均衡的四个应对手段
平均每类326张只是均值,垃圾分类数据集的类别分布往往高度长尾。比如纸张、塑料瓶这些常见垃圾类别可能有上千张,而温度计、药品这类有害垃圾可能只有几十张。这会直接导致少数类在损失里占比过小,模型倾向把所有样本都预测成高频类。
应对手段按优先级排:第一是class_weight,计算每类样本数的倒数做权重,让少数类在损失中贡献更大,这是最便宜的做法;第二是欠采样高频类,可以把高频类样本数裁剪到多数类的3倍以内,效果往往比加权重更明显;第三是Focal Loss,它通过调制因子让模型聚焦难样本;第四是SMA样本按类别做多副本,但要注意验证集别做同样的操作。表格对比一下四种方式的适用场景:
| 手段 | 适用条件 | 副作用 |
|---|---|---|
| class_weight | 多数类与少数类差距在10倍以内 | 高频类精度下降 |
| 高频类欠采样 | 高频类样本严重冗余 | 丢失高频类多样性 |
| Focal Loss | 存在大量难分样本 | 超参gamma需要调 |
| 多副本采样 | 少数类样本不足50张 | 过拟合风险上升 |
实践中我通常先用class_weight跑一轮看效果,如果少数类召回率还是上不去,再结合高频类欠采样。多副本采样放在最后用,因为它最接近人为制造数据,容易让模型在验证集上翻车。
5. 避坑:垃圾分类数据集的五个高频翻车现场
5.1 训练跑到一半崩溃:tf.data读到损坏图片
现象:训练循环在某个epoch进行到约30%时突然报InvalidArgumentError,错误信息指向decode_image或decode_jpeg,堆栈里能看到图片文件名。 原因:8万张图片里混入了截断的JPEG文件,可能是下载时网络中断,也可能是爬虫脚本保存不完整。decode_image遇到损坏文件会抛异常,而TensorFlow在Dataset.map里的异常默认会直接终止整个训练。 解决:在数据管线里包一层ignore_errors,或者在预处理函数里用try逻辑捕获解码异常。推荐前者,一行改动:
ds = ds.map(decode_and_preprocess, num_parallel_calls=AUTOTUNE) # 遇到损坏图片直接跳过该样本,不中断训练 ds = ds.apply(tf.data.experimental.ignore_errors())5.2 验证集准确率高到虚:训练集和验证集出现重复图
现象:模型训练到第3个epoch,验证集准确率突然跳到98%以上,但部署到真实场景实测只有70%出头。 原因:图片可能是同一个来源的不同尺寸缩略图,也可能在打包时原本就有重叠;更隐蔽的是爬虫数据里同一张原图被存了两次但文件名不同,而划分训练验证集时是按文件名哈希分的,重复图被拆到两边。 解决:在划分数据集前先做一遍感知哈希去重。用imagehash库计算每张图的dHash,相似度高于阈值的两张图合并到同一侧。这个方法在垃圾数据集上特别有效,因为同类垃圾的照片背景差异小,不同文件名的相似图不少。
5.3 少数类一个都认不出:类别分布长尾
现象:训练结束后查看各类别召回率,排名后50个类别的召回率都低于10%,但这些类并不是不存在,而是样本数太少。 原因:前面提到的长尾分布问题,平均每类326张掩盖了尾部类别只有几十张的事实。模型在这个类别上的梯度贡献被高频类淹没。 解决:先打印每类样本数,确认尾部类别的具体数量。如果尾部类别里有部分类别低于80张,优先做法是收集更多该类别的公开图片扩充;如果扩充不了,对尾部类别单独用数据增强做10倍扩充,并配合class_weight抬升损失权重。注意增强不是简单的翻转旋转,要做色彩空间扰动,否则模型只是记住了同样的几张图。
5.4 显存OOM不是显存不够:batch size与图片尺寸的锅
现象:训练到第2个epoch报ResourceExhaustedError: OOM when allocating tensor,但nvidia-smi看显存明明还有好几个G。 原因:TensorFlow的默认显存分配是“按需增长”,但数据管线里prefetch和shuffle会额外占用不少显存,而且验证集评估阶段也会申请显存。更重要的是,8万张图里混着超大尺寸图时,resize操作先解码后缩放,大图解码时临时缓冲区会瞬间吃满显存。 解决:先开tf.config.experimental.set_memory_growth让显存动态增长;再把BATCH_SIZE从32降到16;最后确保resize操作在解码后立即执行,不要让高分辨率张量在内存里长期逗留。如果三个手段都用了还是OOM,就只能换GPU或者切CPU训练。
5.5 245类全连接层改错:权重数量对不上报错
现象:模型构建时报Shape must be rank 2 but is rank 3或Dense layer expects input to have rank 2。 原因:打包代码原先可能用了include_top=True的完整模型,顶层是1000类ImageNet分类头;改成include_top=False后,主干输出的特征是三元组结构,接GlobalAveragePooling2D后才是二维张量。还有一个常见错误是num_classes没改成245,全连接层还是1000维,前向推理时输出维度与标签索引不匹配。 解决:检查三处:include_top=False是否设置,pooling='avg'是否设置在主干进Dense之前,Dense(num_classes)里的num_classes是否等于245。这三行改对,90%的模型构建报错可以消除。
6. 把模型从训练台搬到现场:验证、导出与边缘部署
6.1 用混淆矩阵确认每个垃圾类到底能不能分
训练准确率不代表一切,那张245×245的混淆矩阵才是真实答案所在。我在训练完成后必做一件事:把验证集的预测置信度和真实标签导出,画出混淆矩阵的热力图,找出混淆最严重的类别对。如果塑料瓶和玻璃瓶始终相互误判,你要检查数据本身是否存在标注错架问题,而不是盲目调模型复杂度。这个诊断动作比多跑10个epoch更有价值。对于245类分类,重点关注对角线之外的高亮簇,按像素规模排序处理前三处混淆簇即可,不必追求全部类别都完美区分。还有个值得尝试的技巧是记录下每张验证图的置信度,把置信度低于0.5的低置信样本全部挑出来人工复查,很多情况下是标注错误而不是模型出错。
6.2 导出TFLite:把模型塞进分拣设备的存储卡
训练结束后,模型若要做现场部署,常见两条路:TensorFlow Serving或TFLite。嵌入式分拣设备上TFLite是更轻量的选择,量化后模型体积可以压到原始的四分之一以下。这里有一个和运行环境强相关的冷知识:如果你的部署设备是树莓派这类用TF卡启动的板子,模型文件放在TF卡上时,读取速度会直接影响单张推理延迟,尤其是TFLite这样的单模型文件,随机读取模式下劣质TF卡能把推理时间拖慢30%以上。所以在拷贝模型后,我都会顺手跑一遍df -h确认存储余量,再用hdparm -tT看一下设备的原始读速度,确认不是存储拖后腿。
TFLite转换最小命令如下,转换后模型在树莓派这类低成本设备上也能稳定跑在每张图100毫秒左右的延迟:
tflite_convert \ --saved_model_dir=./saved_model \ --output_file=./model_quantized.tflite \ --input_shapes=1,224,224,3 \ --input_arrays=input_layer \ --output_arrays=predicted_classes \ --post_training_quantize=True参数说明:input_arrays和output_arrays名称需要按你训练时实际定义的张量名填写,在TensorFlow 2.x里也可以用keras_model_export配合TFLiteConverter.from_keras_model直接转换,避免名字对不上。post_training_quantize=True打开后模型权重量化为int8,精度通常只掉1%到2%,但在边缘设备上的提速收益非常明显。我自己的习惯是转换后先在PC上加载TFLite模型跑一遍同样验证集,与训练时的准确率做一次对比,确认量化损失在可接受范围内,再拷进设备里做端到端实测。这个习惯帮我挡过不少部署阶段的翻车事故,希望帮到你。
本文还有配套的精品资源,点击获取