☰
蘑菇图像分类数据集详解:PyTorch与Keras加载训练实战
2026/10/14 13:25:39 网站建设 项目流程

简介:面向图像分类与yolov5分类任务训练的12种蘑菇识别数据集,类别涵盖姬松茸、阿曼妮塔、牛肝菌、Cortinarius等,具体映射见随附的json类别字典,使用前可先查看该字典确认各类别名称与数量。数据已按约8:2划分为训练集和测试集,训练集9600张、测试集2400张,文件夹按类别保存、目录结构清晰,可直接作为yolov5分类数据集或CNN分类网络输入,免去自行采集、清洗与划分的负担。整套资源共2000个文件,包含1998张jpg原图、1个json类别字典和1个py样本可视化脚本,压缩包约97.67MB;show.py脚本可快速预览各类别样本,便于检查图像质量、类别分布与命名规范。目前已有1048人学习使用,适合图像分类入门、蘑菇识别模型训练及分类网络课程实验,也适合作为算法对比或毕业论文的基准数据集。

1. 把蘑菇图像数据集拆开看:划分好的文件夹才是这份数据最值钱的部分

图像分类数据集有很多,但当你拿到一份“12种蘑菇图像识别数据集”的时候,真正值得先看的不是那 12 个类别,而是它的组织方式。这份数据几乎把蘑菇识别项目起步阶段最麻烦的环节全部解决了:图片已经按 train / val / test 三套目录切好,图片按类别存放在对应文件夹里,类别字典文件单独保存,不需要再写分割脚本,不需要手工整理标签。它适合刚把深度学习框架跑通、准备做第一个真实分类任务的新手,也适合需要快速验证某个分类模型在自然图像上效果的开发者。很多做过蘑菇识别、叶片识别或昆虫识别的人都会有一个共同感受:最后让模型翻车的往往不是网络结构,而是数据划分和标签映射。这份数据集把这两件事提前做了。

2. 拿到数据集先做三件事:目录结构、字典映射、样本分布核对

2.1 标准三段式目录结构:train / val / test 各自长什么样

这类数据集的通用排布方式非常稳定,主目录下同时存在 train、val、test 三个文件夹,每个文件夹内部按类别建子文件夹,子文件夹名和类别字典里的 key 一一对应。有的版本会把 val 写成 valid,有的把 test 写成 test,本质没区别。常见划分比例大致在 7:2:1 或 8:1:1——train 负责拟合,val 负责选模型和调超参,test 只在最终评测时碰一次,测试集参与训练是实验上的大忌。

解压压缩包之后,我建议先扫一眼目录结构,确认外层和内层没有多套了一层目录,这在实际下载的数据里非常常见,像“mushroom_dataset / mushroom_dataset / train”这种嵌套经常出现。用命令行检查比用资源管理器直观得多,也能顺便把目录层级关系看清楚:

cd mushroom_dataset find . -maxdepth 2 -type d | sort | head -50

这段命令打印两层目录树。如果需要把每个类别目录下的图片数量也统计出来,再补一步:

for cls in train/*/; do cls=${cls%/} count=$(find "$cls" -type f | wc -l) echo "$cls : $count" done

脚本按字母顺序遍历 train 目录下的所有子目录,统计每个类别文件夹中的文件数量并打印出来。这里有一个值得注意的细节:${cls%/}是去掉路径末尾的斜杠,避免输出中出现train/class_001/和class_001两种不一致的表示。

2.2 类别字典文件的两个关键字段:类名和数值标签怎么对上

类别字典文件常见的两种形态分别是 JSON 和 CSV,也可能是纯文本格式,但核心内容是一致的。JSON 形态通常是“类名 → 显示名”的映射,这是我说的最常见做法:

{ "class_001": "species_name_01", "class_002": "species_name_02", "class_003": "species_name_03", "class_004": "species_name_04", "class_005": "species_name_05", "class_006": "species_name_06", "class_007": "species_name_07", "class_008": "species_name_08", "class_009": "species_name_09", "class_010": "species_name_10", "class_011": "species_name_11", "class_012": "species_name_12" }

这里只写出前三个字段示意,实际文件里 12 个 key 一一对应 12 个类别文件夹。要注意的是:JSON 里的 key 是稳定标识,value 是方便人阅读的名字,在训练阶段真正生效的是 key 到整数索引的映射关系。很多框架会按字母序给类别文件夹自动分配索引,而不是按字典文件里的顺序,所以拿到数据后别急着写死“class_001 等于标签 0”之类的假设。

CSV 形态的字典就多一列,常见格式是三列:文件夹名、物种名、期望类别 ID。做训练前我更推荐先读 JSON 或 CSV 的文件夹名列,然后和 os.listdir 的结果做一次比对,保证两边完全一致。这个核对步骤看起来多余,但能提前暴露“类名字段拼写不一致”“文件夹缺一个”“字典里混进了多余 key”三种问题。

2.3 首轮核对脚本:30 行代码摸清数据集的真实家底

靠人工数文件夹不现实,直接写一个简短的 Python 脚本,把三件事一次做完:校验字典和目录是否对齐、统计每个集合的图片数、找出空文件夹和异常文件。这是我每次拿到新数据集都会先跑的流程:

import os import json from collections import Counter # 路径按实际解压位置修改 BASE = "mushroom_dataset" dict_path = os.path.join(BASE, "class_dict.json") with open(dict_path, "r", encoding="utf-8") as f: class_dict = json.load(f) expected_keys = set(class_dict.keys()) actual_dirs = set(os.listdir(os.path.join(BASE, "train"))) # 1. 比对字典 key 与 train 目录下的子文件夹名 print("字典有但目录没有:", expected_keys - actual_dirs) print("目录有但字典没有:", actual_dirs - expected_keys) # 2. 统计 train / val / test 的图片数,并按类别列出分布 for split in ["train", "val", "test"]: split_dir = os.path.join(BASE, split) counts = {} for cls in os.listdir(split_dir): cls_dir = os.path.join(split_dir, cls) if not os.path.isdir(cls_dir): continue n = len([f for f in os.listdir(cls_dir) if f.lower().endswith((".jpg", ".jpeg", ".png"))]) counts[cls] = n total = sum(counts.values()) print(f"{split}: 总图片数 {total}, 类别数 {len(counts)}") print("最少样本类别:", min(counts, key=counts.get), counts[min(counts, key=counts.get)])

脚本前半段取字典的所有 key 与 train 下的子文件夹名求差集,任何一边多出或缺少都会直接打印出来;后半段分别统计三个集合的图片数。这里用列表推导加endswith过滤文件后缀,是避免把 Thumbs.db、.DS_Store 这类非图片文件计入样本数。拿到输出之后,重点看两个信息:第一,val 和 test 有没有出现字典之外的文件夹名;第二,有没有某个类别在 train 里有几百张,在 val 或 test 里却只剩个位数。后者对后续训练影响非常大,后面章节会说怎么处理。

3. 把“文件夹”变成可训练数据:PyTorch 和 Keras 两种加载路线

3.1 PyTorch 路线:ImageFolder 直接按文件夹名生成标签

PyTorch 的torchvision.datasets.ImageFolder是为“按文件夹分好类的图片数据”量身设计的加载器。它做的事情本质上只有两件:扫描根目录下每个子文件夹,把子文件夹名当作类别名并自动生成类别到整数索引的映射;按文件夹顺序读取图片路径,在__getitem__返回图片张量和对应的整数标签。使用这套数据集的 PyTorch 加载代码非常简单:

import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms # 定义预处理流程:resize -> 转张量 -> 归一化 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]) ]) # 直接读取已经划分好的训练文件夹 train_dataset = datasets.ImageFolder("mushroom_dataset/train", transform=transform) val_dataset = datasets.ImageFolder("mushroom_dataset/val", transform=transform) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4, pin_memory=True) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)

注意ImageFolder自动生成的类别索引是按文件夹名字母序排列的,不是按class_dict.json里的顺序。也就是说train_dataset.classes里第一个名字会被映射成 0。如果你的验证代码或者评估脚本里手动把某个类名固定成了一个数字,这里就会产生错位。

打印验证这一步是值得养成的习惯:

print(train_dataset.classes) print(train_dataset.class_to_idx)

classes是类别名列表,class_to_idx是“类名 → 索引”的字典。两个字幕对照着看,标签映射是否正常一清二楚。另一件要注意的事是:train 用了shuffle=True,val 没有 shuffle。验证集必须保证每次评估看到的样本顺序稳定,才能在两个模型之间做公平对比。

3.2 Keras 路线:flow_from_directory 的数据流方式和参数对照

TensorFlow / Keras 侧的老牌加载方式是ImageDataGenerator配合flow_from_directory,它的工作逻辑和 ImageFolder 基本相同:给一个根目录,按子文件夹自动生成标签。代码会比 PyTorch 那边更短:

from tensorflow.keras.preprocessing.image import ImageDataGenerator # 只做缩放,不做增强 datagen = ImageDataGenerator(rescale=1.0 / 255.0) # train 和 val 各创建一个生成器 train_gen = datagen.flow_from_directory( "mushroom_dataset/train", target_size=(224, 224), batch_size=32, class_mode="categorical", shuffle=True, seed=42 ) val_gen = datagen.flow_from_directory( "mushroom_dataset/val", target_size=(224, 224), batch_size=32, class_mode="categorical", shuffle=False, seed=42 )

target_size会直接把图片缩放成指定尺寸,省去手动 Resize;class_mode="categorical"表示标签输出成 one-hot 编码,配合多分类网络最后一层 softmax 使用;如果设置成"sparse",输出就是整数标签,也完全可以。flow_from_directory的class_indices属性和 ImageFolder 的class_to_idx定位相同,同样按字母序生效。

这里要提醒一句:如果你是第一次拿这套数据集跑 TensorFlow,优先用flow_from_directory。虽然新版 TensorFlow 主推image_dataset_from_directory,风格更接近 PyTorch 的返回 tensor,但很多现成的老教程和迁移学习代码仍然依赖 ImageDataGenerator 的结构。两种方案都能跑通,差别不大,选一种熟悉的吃透就行。

3.3 预处理参数选择:为什么 224×224 和减均值是默认起点

很多初学者会问,为什么蘑菇图片那么大,加载时非要 resize 成 224×224。答案和模型预训练生态绑定在一起:绝大多数开源分类模型在训练时输入尺寸就是这个规格,比如 ResNet 系列、EfficientNet 的 B0 到 B7 里也有很多默认用 224。直接用 224×224 意味着可以套用 ImageNet 上预训练好的权重,迁移学习效果远好于从零训练。如果你用的是 Swin Transformer 这类模型,它可能要求 224 或 256,具体看模型卡上的说明。

归一化参数 mean 和 std 直接用预训练权重的配套值,这是最保险的做法。PyTorch 里最常见的几组数字就是[0.485, 0.456, 0.406]和[0.229, 0.224, 0.225],对应 ImageNet 统计出来的均值方差。如果换用另一种自己从零训练的模型,mean 和 std 应该用训练集的统计值重新计算,而不是套用预设。还有一个实际经验:蘑菇的识别特征集中在菌盖纹理、菌褶和菌柄形态上,如果原始图片分辨率很高而训练资源有限,与其直接暴力 resize 到 224 丢掉细节,不如先用中心裁剪或随机裁剪把主体区域裁出来再缩放。比如先随机裁0.875比例的区域再缩到 224,对细节保真比单纯拉伸好很多。

4. 从数据集到跑通训练:在线增强、不均衡处理和收敛参数

4.1 在线增强配置:旋转、翻转之外的三种常用扰动

图像数据增强做在线处理,也就是训练时实时扰动、验证时不做,这个选择本身有充分理由:一方面避免了离线生成增大存储占用,另一方面每次 epoch 看到的样本都不一样,等效训练数据量被放大。蘑菇图像和一般自然图像差别不大,常用的扰动方式也比较固定:

train_transform = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.8, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=15), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2, hue=0.05), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

RandomResizedCrop会在图片上随机裁剪一块区域再缩放到 224,用来模拟不同拍摄距离和构图差异,scale=(0.8, 1.0)表示裁剪区域占原图面积的 80% 到 100%,留一部分空白让模型学会应对目标不在正中心的情况。RandomRotation只给了 15 度,旋转角度不宜太大,因为蘑菇本身有方向性,菌盖的朝向过度旋转会引入不自然的样本。ColorJitter控制亮度、对比度、饱和度、色相扰动,模拟不同光照和相机白平衡差异。

在 Keras 侧,这些增强基本都能在 ImageDataGenerator 里通过参数打开,例如rotation_range=15、width_shift_range=0.1、brightness_range=[0.8, 1.2],思路相同,参数含义一致。增强不是越强越好,训练初期可以先用较小的扰动,观察 loss 是否正常下降后再逐步加大幅度,否则模型可能花很长时间学不到真实特征。

4.2 12 类样本不均衡时:采样器优先还是 loss 加权优先

先看训练集统计结果,如果每个类的图片数量差距在 1.5 倍以内,可以认为基本均衡,不需要做额外处理。如果出现某个类别两三百张、另一个类别只有三五十张的情况,则需要介入。处理类别不均衡,我一般先改采样器,不用急着动 loss,因为采样器实现简单且不会改变梯度计算方式。PyTorch 的写法:

from torch.utils.data import WeightedRandomSampler # 为每个样本分配一个权重,与类别样本数成反比 labels = train_dataset.targets class_counts = torch.bincount(torch.tensor(labels)) weights = 1.0 / class_counts.float() sample_weights = weights[labels] sampler = WeightedRandomSampler( weights=sample_weights, num_samples=len(sample_weights), replacement=True ) train_loader = DataLoader(train_dataset, batch_size=32, sampler=sampler, num_workers=4)

原理是:样本少的类别里每个样本权重高,sampler 在每次抽样时被抽到的几率更大,训练时一个小类在每个 epoch 里出现的次数变多。replacement=True允许同一个样本在一个 epoch 内被重复采样。

如果用了采样器之后小类准确率仍然偏低,再看 loss 加权。CrossEntropyLoss 本身就接收weight参数,把每个类别的 weight 设成样本数的倒数或倒数开根号:

criterion = torch.nn.CrossEntropyLoss( weight=1.0 / class_counts.float() )

采样器和 loss 加权不要同时上,两个一起用容易把小类样本过拟合,导致验证集上小类准了、大类反而倒退。先用采样器评估一个 epoch,不够再切换。这是实践中比较稳的顺序。

4.3 小数据训练的参数底线:batch、学习率和早停怎么配合

12 类蘑菇数据集,样本总量通常只有几百到几千张,这个规模不适合从零随机初始化训练一个大网络。常见做法是加载预训练权重,冻结前几层或全部骨干层,只训练最后的分类层或最后几个 block,学习率要小。这里给一组我用过多次、起步有效的参数基线:batch size 32,优化器 Adam 或 SGD+momentum,初始学习率1e-4(如果冻结了大部分层可以放宽到1e-3),epoch 数先设 50,配合早停。

早停是必要配置,因为小数据集很快会过拟合,训练 loss 还在降的时候验证 loss 可能已经开始回升:

from torch.optim.lr_scheduler import ReduceLROnPlateau scheduler = ReduceLROnPlateau(optimizer, mode="min", factor=0.5, patience=5) best_val_acc = 0.0 patience = 0 max_patience = 10 for epoch in range(50): # 训练一个 epoch... train_loss = run_train_epoch(model, train_loader, optimizer, criterion) # 验证... val_acc = evaluate(model, val_loader) scheduler.step(val_loss) if val_acc > best_val_acc: best_val_acc = val_acc torch.save(model.state_dict(), "best_model.pt") patience = 0 else: patience += 1 if patience >= max_patience: print("早停触发,保存最佳模型") break

ReduceLROnPlateau的作用是当验证指标连续 5 个 epoch 不再变好时把学习率减半,给训练过程一次“跳出小坑”的机会。早停的 patience 设为 10,如果验证准确率连续 10 个 epoch 没有创新高就停止训练。这套机制保证了在无人值守的情况下,最终保存下来的模型一定是在验证集上表现最优的那一份,而不是最后一个 epoch 的权重。很多翻车现场都发生在“训练完直接保存最后一步权重”上,这一步习惯一定不要省。

5. 避坑:文件夹数据集最容易翻车的五个位置和排查顺序

5.1 现象:类别字典写了 12 类,实际目录只有 11 个文件夹

两边的 key 数量对不上,通常是下载不完整或解压不完整导致的。如果缺失不严重,比如只缺一个类别文件夹,但样本量本来就少,强行用 11 类训练会导致模型永远不认识那一类蘑菇。排查顺序上,先重跑一次 2.3 节的比对脚本,确认缺失的具体是哪个类别,再去原数据源确认压缩包是否完整、是否需要重新下载。千万别直接改字典文件硬凑成 11 类,否则后续做模型发布时类别数写死,容易和外部使用方的字典对不上。

5.2 现象:验证集准确率居高不下,但换到真实场景测试明显下滑

这类问题最典型的表现是:val 准确率 95% 以上,test 也表现不错,但部署后用户拍一张真实照片,识别效果明显变差。优先怀疑两件事:一是划分时有没有把同一张图或相似图同时放进 train 和 val 里,产生了数据泄漏;二是划分脚本本身有 bug,把 train 的顺序打乱后,val 在内存副本上切,结果 val 里混进了几个 train 的样本。解决方法是做图片去重检查。常见做法是对全量图片计算感知哈希,然后查 train 和 val 之间是否有哈希相同的图片。

5.3 现象:加载后标签乱序,混淆矩阵里预览的类别对不上

买了数据集后自己写了评估脚本的人经常会踩这个坑。字典文件里 class_001 到 class_012 排得好好,但 PyTorch 的 ImageFolder 是按文件夹名字母序重新分配索引的,Keras 也一样。如果评估脚本里用了固定的“class_001 对应索引 0”这样的硬编码,混淆矩阵的热力图会彻底乱掉。解决方法是每次加载后打印class_to_idx或class_indices,以打印结果为准建立标签映射,尤其注意字母序下 class_010 会排在 class_002 前面,这个细节最容易出事。

5.4 现象:训练 loss 一直不降,或者降得很慢

如果在冻结骨干、只训练分类层的情况下,loss 长时间不降,先看数据增强是不是太强。随机裁剪加高旋转角度加颜色扰动叠在一起,可能把蘑菇主体的纹理细节都破坏了,模型看到的是“加了各种噪声的模糊色块”。我自己会先关掉增强跑一次短训练,确认模型能在原始图片上过拟合,再把增强逐项加回来。另外一个常见原因是学习率过小,冻结层训练用1e-3没问题,解冻全网络后用1e-4,这和预训练权重的幅度有关。

5.5 现象:训练时验证集可复现,但推理时换一张分辨率完全不同的图效果骤降

训练时RandomResizedCrop会在 224×224 的尺度下随机裁剪,验证时如果直接用Resize((224, 224)),两张图的几何变换规律完全不一致。推理时如果再用另一种方式,比如先压缩到 512 再居中裁剪,效果又会变化。这个事的本质是:所有训练阶段涉及几何变换的操作,推理阶段必须保持同一套逻辑。常见做法是统一成“先 resize 到短边 256,再中心裁剪 224”,或者干脆统一成大图直接 resize。选定一种,训练、验证、推理三处全部一致。

6. 把这个数据集用透:用训练/验证分布差异直接验证划分质量

最后分享一个我非常依赖的验证脚本。很多数据集声称已经划分好了,但划分质量需要自己确认一遍。这里不看准确率,只看两个集合的样本分布是否还保持相似。做法是分别统计每个类别在 train 和 val 中的占比,然后计算互信息或直接看比值差。当某个类别在 train 的占比是 20%,在 val 里却只有 5%,说明划分脚本很可能按文件顺序切分的,而没有按类别标签做分层采样,这种划分会把类别分布打偏。

import os import math def class_ratio(root): total = 0 per_class = {} for cls in os.listdir(root): path = os.path.join(root, cls) if not os.path.isdir(path): continue per_class[cls] = len(os.listdir(path)) total += per_class[cls] return {k: v / total for k, v in per_class.items()} train_ratio = class_ratio("mushroom_dataset/train") val_ratio = class_ratio("mushroom_dataset/val") diff = {} for cls in train_ratio: diff[cls] = abs(train_ratio[cls] - val_ratio.get(cls, 0)) for cls, d in sorted(diff.items(), key=lambda x: -x[1])[:5]: print(f"{cls}: train {train_ratio[cls]:.4f} vs val {val_ratio.get(cls, 0):.4f}")

一个合格的划分,绝大多数类别的 train 与 val 占比差应该维持在几个百分点内。如果某个类差到 10 个百分点以上,就要回原数据源检查预处理方式或者自己重新划分。虽然重新划分耽误时间,总好过带着一个不均衡的 val 做模型选择。

我自己用过太多“标注好、划分好”的数据集,实际踩了坑之后才明白:任何号称划分好的数据都需要先由自己这一关验收。这份 12 种蘑菇数据集的价值在于它把最繁琐的活干完了,同时它也把最后一个责任交到了使用方手上——套用对了,它就是一份能快速帮你跑通全流程的标准数据;用错了,你再回头查配置、查映射、查划分,代价远高于重新整理一份数据。承载这类数据集的关键是流程意识,而不是某一个具体网络有多强。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询