ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

34类植物叶片图像分类数据集:从划分校验到ResNet18训练全流程

34类植物叶片图像分类数据集:从划分校验到ResNet18训练全流程 简介大型34植物叶片图像分类数据集已完成训练集与测试集划分可直接作为图像分类任务的标准数据源适用于入门图像分类、植物识别及YOLOv5等分类模型训练。数据保存结构规范train与test目录下按34个类别分文件夹存放其中训练集含27346张、测试集含6654张合计超过3.4万张真实叶片图像覆盖苹果、葡萄、猕猴桃等常见植物无需额外清洗即可用ImageFolder直接加载。资源共2000个文件主体为1998张JPEG图像另含1个JSON类别字典文件和1个可视化Python脚本压缩包总大小约508.58MB。JSON文件提供34种植物类别映射Py脚本可随机抽取4张图片并保存展示效果便于快速预览数据与核查标注情况。目前已有179人浏览学习适合需要现成分类数据集进行模型训练、算法验证或教学演示的开发者使用。1. 34类植物叶片数据集为什么“已做数据集划分”比模型选型更值得先看34类植物叶片图像分类数据集是一份把图像分类最常见的落地场景压到一整个硬盘里的数据资产34个植物类别、几万张叶片图片并且已经按训练、验证、测试三个集合划分完毕。对刚接触图像分类的人来说第一个翻车点通常不在模型而在自己动手切数据时把同一株叶片的不同照片同时送进训练集和验证集让准确率虚高得可疑。这份数据集相当于把这一步的后悔药提前吃掉了。它适合两类人做作物或叶片识别、想跑通图像分类完整流程的初学者以及需要一份干净基线数据的算法工程师。下面先把这份数据集的构造和划分逻辑讲透再给出能直接复现的训练脚本和踩坑清单。2. 数据集的构造与划分逻辑为什么划分比例不是拍脑袋定的2.1 34个类别怎么选从叶片形态到应用场景植物叶片识别是图像分类里一个很有代表性的任务类别多、类间差异细、同类别内形态差异大。34类这个规模比MNIST那种10类数字要难又比ImageNet那种1000类更贴近实际项目的体量。常见的类别构成一般会覆盖粮食作物、蔬菜、杂草和观赏植物这几大类比如水稻、小麦、玉米这类禾本科茄科、十字花科的蔬菜再加上一些常见的园艺植物。这样设计的好处是每个大类下面都有几个形态相近的类别能真正检验模型区分细粒度特征的能力而不是靠颜色就能糊弄过去。这里要区分一个很容易混的概念34类植物叶片分类和作物病害图像数据集划分是两个方向。病害识别关注的是叶片上的病斑、褪绿、焦枯这些局部异常类内差异往往比类间差异还大而植物物种识别关注的是叶形、叶缘、叶脉、叶基这些整体结构。做模型时两者对图像分辨率和数据增强的要求不同病害识别需要保留局部细节裁剪要小步长物种识别则更在意整片叶子的轮廓。如果你后面要扩展到叶片病害识别可以复用这套数据集划分的思路但图片采集标准得重新定。2.2 图像规格与采集环境背景、光照与分辨率一个植物叶片数据集的“难度”很大程度写在图像的采集环境里。公开数据集里常见的做法有两种一种是实验室统一背景叶片摆放在纯色底上光照均匀另一种是田间或户外自然背景叶片带着泥土、露水、遮挡甚至残缺。34类这种偏应用的数据集通常两种都有混入这会让模型的泛化能力更真实但也意味着你不能指望模型只靠背景色做判断——训练时如果不过正则化验证集上看着不错的准确率换到另一批拍摄条件下的图片上会掉得很难看。分辨率也是要先摸清的底。多数这类数据集的图片是几百到上千像素的JPGRGB三通道但难免混进灰度图、带透明通道的PNG甚至Exif方向信息错误的照片。这些细节在训练时会变成DataLoader的暗雷有的库默认三通道输入灰度图一进来就炸有的图片能读但尺寸极小resize到224后糊成一团。所以在动手训练前先对图像做一次完整性和格式体检这一步省不得后面第3章会给可直接跑的脚本。2.3 划分逻辑为什么比例不是拍脑袋定的标题里特意标了“已做数据集划分”说明划分这件事本身是有讲究的。常见做法有三种按目录划分、按文件列表划分、按叶片个体分组划分。三者的差异和适用场景我一般用下面这张表来对比。划分方式做法优点风险典型场景按目录划分训练/验证/测试三个目录各自包含34个类别子目录加载最简单PyTorch的ImageFolder直接可用同一叶片的多张照片可能被切分到不同集合数据本身按个体分文件夹存放按文件列表划分三个CSV/TXT记录图片路径和标签灵活可做分层抽样和精确控制需要自己写Dataset读取逻辑原始数据混放在一个大目录按叶片个体分组划分先用叶片个体ID分组再按组切分避免同一株/同一片叶子的照片同时出现在训练和验证集需要额外元数据实现成本高数据采集时记录了样本ID比例上公开数据集最常见的配置是训练集占70%-80%验证集和测试集各占10%-15%。划分的时候要做到两件事一是分层抽样保证每个类别在三个集合里的占比和全集一致防止某个类别在验证集里只有两三张二是固定随机种子让每次划分结果可复现。这两点也是衡量一份划分“专业不专业”的试金石。2.4 划分带来的信任问题怎么确认它真的划分好了“已做数据集划分”这句话意味着出版方替你完成了分层抽样和种子固定拿到手就能直接开训。但从工程角度我不建议直接信任这个结论。数据在打包、传输、解压过程中可能发生文件丢失、目录错位甚至同一个文件被复制进两个集合。所以拿到数据集的第一步不是写模型而是先核对统计各类别数量、检查集合之间有没有交集、验证图像能否正常解码。这三件事做完了再谈训练。3. 拿到数据集后的第一步核对目录、统计类别与差集校验3.1 先看目录结构train/val/test三件套长什么样根据“已做数据集划分”这个描述最常见的落盘方式就是三个顶层目录加类别子目录。你可以用tree命令快速确认结构是否符合预期。tree -L 2 dataset/这段命令会打印两层目录树。第一层应该是train、val、test三个文件夹第二层是各自的34个类别子文件夹。如果第二层出现的是数字编号而不是可读的类名说明可能用了类别ID映射需要再找一份标签映射表如果只有两个顶层目录说明验证集和测试集被合并了或者划分是通过标注文件实现的。看目录结构是最快的定性判断接下来再做定量统计。3.2 用Python统计各类别图像数量一眼看出不均衡确认目录结构后我会先跑一个统计脚本把34个类别在每个集合里的图像张数列出来。这一步能同时发现两个问题数据集整体是不是不均衡以及划分时有没有做到分层抽样。import os from collections import defaultdict root dataset splits [train, val, test] stats defaultdict(dict) for split in splits: split_path os.path.join(root, split) for class_name in sorted(os.listdir(split_path)): class_path os.path.join(split_path, class_name) if os.path.isdir(class_path): count len([f for f in os.listdir(class_path) if f.lower().endswith((.jpg, .jpeg, .png))]) stats[class_name][split] count for class_name, split_counts in sorted(stats.items()): total sum(split_counts.values()) print(f{class_name}: train{split_counts.get(train, 0)}, fval{split_counts.get(val, 0)}, test{split_counts.get(test, 0)}, ftotal{total})这段脚本遍历train、val、test三个目录统计每个类别子目录下JPG和PNG格式的文件数量然后把每类的三集合数量打印成一行。筛选后缀的目的是排除隐藏文件或缩略图缓存。参数说明root改成你本地解压后的数据集路径如果数据集用的是CSV标注而不是目录结构这段脚本要改成读CSV并统计标签列。看到输出后重点检查两类异常某类别在训练集有几百张但在验证集只有一两张说明分层抽样没做干净某个类别总数明显偏少说明数据集本身长尾严重后续训练要加类别权重。3.3 校验集合是否有交集防止数据泄露的最后一道闸划分数据集最大的隐患是训练集和验证集出现重叠文件。判断标准是文件路径的唯一性我会把三个集合的完整文件路径拉出来求交集数量为零才算干净。def collect_files(split_path): files set() for class_name in os.listdir(split_path): class_path os.path.join(split_path, class_name) if not os.path.isdir(class_path): continue for f in os.listdir(class_path): files.add(os.path.abspath(os.path.join(class_path, f))) return files train_files collect_files(os.path.join(root, train)) val_files collect_files(os.path.join(root, val)) test_files collect_files(os.path.join(root, test)) print(train∩val:, len(train_files val_files)) print(train∩test:, len(train_files test_files)) print(val∩test:, len(val_files test_files))这段脚本把每个集合的全部文件转成绝对路径集合再做两两交集计算。集合比较比列表遍历快几个数量级几万张图片对set来说毫秒级完成。说明一下这里的“交集”判断的是同一路径文件被复制到两个集合。还有一种更隐蔽的情况同一叶片的不同照片文件名不同、路径不同但内容属于同一个样本这种用路径去重查不出来需要数据集自带样本ID元数据才能做个体级去重。如果你发现交集不为零优先检查是不是硬链接或重复下载导致的把重复文件从验证集或测试集里删掉保留训练集那份。数据都划分好了还出现交集基本可以判定为打包疏漏别带着这个雷去训练。3.4 图像完整性与格式体检把DataLoader的暗雷提前排掉目录和集合关系确认无误后还要做一次图像解码体检。图片文件存在不代表能正常读入常见的坑是下载中断导致的截断文件、文件名是JPG但内部编码是WEBP、单通道灰度图混入。我用Pillow逐张验证。from PIL import Image invalid_files [] for split in splits: split_path os.path.join(root, split) for class_name in os.listdir(split_path): class_path os.path.join(split_path, class_name) if not os.path.isdir(class_path): continue for f in os.listdir(class_path): path os.path.join(class_path, f) try: with Image.open(path) as img: img.load() if img.mode not in (RGB, RGBA): invalid_files.append((path, funexpected mode: {img.mode})) except Exception as exc: invalid_files.append((path, fdecode error: {exc})) for path, reason in invalid_files[:20]: print(reason, path) print(total invalid:, len(invalid_files))这段脚本会遍历所有图片用Image.open配合img.load()触发完整解码捕获两类问题解码报错和色彩模式异常。截断文件通常在load阶段抛出OSError早期版本的Pillow还可能给出模糊的“image file is truncated”提示。对非法文件我的处理原则是能删则删不能删就在Dataset的读取逻辑里加一次try-except返回同批次的另一张图避免整个DataLoader在工作到一半时崩掉。参数说明img.mode只放行了RGB和RGBA如果数据集里混有L模式灰度图可以统一用img.convert(RGB)转成三通道而不是直接拉黑。4. 用这个数据集跑通图像分类最小训练流程与三个核心参数4.1 模型选型先ResNet18别上来就ViT34类、几万张图片这个规模对模型的选择有一个很务实的结论预训练CNN是最稳的起点。最新的图像分类模型里ViT和各类Transformer变体在ImageNet上表现亮眼但它们在中小规模数据集上需要更长的训练日程和更强的数据增强否则很容易欠拟合。EfficientNet系列则是精度和算力的好平衡点但调参门槛比ResNet高。我一般会先用ResNet18跑通流程因为它结构简单、预训练权重好找、对学习率不敏感哪怕参数不是最优也能在几个epoch内看到准确率爬升。等流程验证通了再换成ResNet50或EfficientNet-V2做精度提升这是投入产出比最高的路径。有人会问那YOLO不是更快吗这里要分清任务边界YOLO这类检测模型解决的是“目标在哪”而这份数据集是图像分类任务输出的是一个34维的类别分布没有边框坐标。你可以把叶片检测和叶片分类串成流水线但第一个环节不需要也不应该用检测模型硬扛。检测数据集的划分比如yolov8训练自己的数据集时要求train/images和val/images配套和分类数据集的划分逻辑也不同分类数据集只要类别目录对齐就行更简单。4.2 数据增强叶片场景怎么开叶片图像分类的数据增强核心矛盾是既要增加形态多样性又不能破坏类别判断依据。水平翻转和随机旋转是安全操作因为植物叶片不区分左右。随机裁剪要控制范围crop比例低于0.6时容易把叶尖或叶缘裁掉反而让模型学到不完整的特征。色彩抖动要谨慎——叶片的绿色深浅本身就是分类线索过度改变色相会让训练集和真实照片分布偏离。下面这段增强配置是我在类似数据集上常用的起点用PyTorch的torchvision.transforms实现。from torchvision import transforms train_transform transforms.Compose([ transforms.Resize(256), transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) eval_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]) ])这段代码里Resize到256再RandomResizedCrop到224是为了让网络输入尺寸固定且保留一定裁剪自由度scale范围定在0.7到1.0保证裁剪后的叶片主体仍然完整。RandomRotation给15度应对拍摄角度不端正的情况但不要超过30度否则倒置的叶片会引入无意义的样本。Normalize用的是ImageNet预训练权重对应的均值和标准差这是从torchvision加载预训练ResNet时的标准搭配。推理阶段不随机裁剪用CenterCrop保证每张测试图都以同样方式进入网络否则验证集结果会带有随机性很难复现。4.3 训练脚本PyTorch核心流程数据处理干净后训练脚本本身可以很精简。我给出一个能直接跑的PyTorch版本核心包含数据集加载、预训练模型替换分类头、训练循环、验证循环和最优模型保存。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, models device torch.device(cuda if torch.cuda.is_available() else cpu) num_classes 34 batch_size 32 epochs 20 lr 1e-3 train_data datasets.ImageFolder(rootdataset/train, transformtrain_transform) val_data datasets.ImageFolder(rootdataset/val, transformeval_transform) train_loader DataLoader(train_data, batch_sizebatch_size, shuffleTrue, num_workers4, drop_lastTrue, pin_memoryTrue) val_loader DataLoader(val_data, batch_sizebatch_size, shuffleFalse, num_workers4, pin_memoryTrue) model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, num_classes) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lrlr) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxepochs) best_acc 0.0 for epoch in range(epochs): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * images.size(0) scheduler.step() model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) acc correct / total print(fepoch {epoch1}/{epochs}: loss{running_loss/len(train_data):.4f}, val_acc{acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_model.pth)这个脚本的关键参数概念batch_size32是显存和稳定性的折中如果显存只有4G可以降到16但注意drop_lastTrue会把最后不足一批的数据丢掉避免BatchNorm在批大小不一致时行为异常。学习率1e-3配Adam是ResNet18的稳妥起点换成SGD时通常要配momentum0.9和初始lr0.01到0.1。CosineAnnealingLR把学习率从初始值余弦下降到接近零比固定学习率在20个epoch里更容易收敛到好的局部最优。ImageFolder会自动按子目录名生成类别索引顺序以os.listdir为准所以前面第3章检查目录结构这步又反哺了这里——如果类名乱了模型学到的映射也跟着乱。4.4 三个必调参数batch_size、学习率调度和验证频率训练脚本能跑通之后真正影响结果的是三个参数的取舍。batch_size决定梯度噪声和显存占用分类任务里32或64是甜点区超过128容易过平滑小于16则训练不稳定。学习率是第一生产力Adam用1e-3起步观察前两个epoch的loss变化如果loss震荡不降把学习率降到3e-4如果loss下降非常慢提到3e-3。验证频率在实验阶段用每个epoch一次最合适既能看清趋势又不至于浪费太多时间如果数据集很大可以改成每2到3个epoch验证一次并在验证集上做早停把验证准确率不再提升时的权重作为最终模型。5. 训练与复现中的5个典型翻车点现象、原因、解法5.1 图像损坏导致DataLoader在训练中途崩溃现象训练跑到第7个epochDataLoader突然抛OSError提示某张图片无法读取整个进程中断。这是所有图像分类训练里最常见的黑匣子问题因为文件能拷贝不代表能解码。原因分两类一是数据集打包传输时文件截断二是极少数图片用了JPG扩展名但内部是其他编码。解决不要临时改Dataset代码去跳过而是在训练前跑第3章的体检脚本把损坏文件统一删除或移出目录。如果数据集是只读挂载不方便删文件就在Dataset的读取逻辑里做防御性处理读取失败时返回同batch里另一张正常图片代价是这批样本数少1但至少不中断训练。5.2 类别不均衡导致整体准确率高、个别类别全错现象验证集整体准确率94%但查看逐类准确率时有三四个类别准确率不到50%其中一个类别几乎全部被预测成它的近亲类别。原因这份数据集虽然是“大型”但34类内部数量未必均匀长尾类别的样本太少模型倾向于把不确定样本分到先验概率高的类别。解决最简单的做法是在CrossEntropyLoss里传入类别权重权重按每类样本数的倒数归一化更彻底的做法是使用WeightedRandomSampler让每个batch里长尾类别也有机会出现。评估时不要只看整体准确率用macro-F1或逐类准确率否则模型有没有真正学会区分那34类你根本不知道。5.3 验证集准确率异常高测试集结果却崩掉现象训练时验证集准确率一路涨到97%换到测试集一测只有81%中间差了十几个点。原因最常见的是训练集和验证集存在数据泄露比如同一个叶片的不同照片被分到了两个集合或者第3章说的文件交集没查干净。另一个原因是验证集参与调参次数过多模型隐式地过拟合了验证集。解决先跑第3章的差集校验确认文件没有重叠再确认划分是按叶片的个体ID而不是按照片随机切分。测试集的结果才是最终交付指标验证集只是训练过程中的温度计如果温度计放错了位置测出来的体温自然不可信。5.4 随机种子没固定两次训练结果差两个点现象同样的代码、同样的数据上午训练验证集93.5%下午重新跑变成91.8%代码一行没改。原因PyTorch默认行为里有很多随机源包括数据加载的shuffle顺序、CUDA计算的非确定性、模型初始化的随机种子。只要是随机两次结果就不可能完全一致。解决训练脚本开头固定好所有能固定的种子再把cudnn的基准模式关掉。注意固定种子只能保证结果可复现不能消除不同随机种子之间的结果波动所以正式实验尽量跑2到3次取中位数这也是做基线实验时容易被忽略的细节。5.5 标签与目录错位混淆矩阵上的近邻错乱现象训练一切正常但看混淆矩阵发现比如水稻的图片被大量分到小麦类而这两类在类名顺序上恰好挨着。原因ImageFolder按子目录名的os.listdir顺序分配索引如果数据集在打包时目录顺序和标签文件顺序不一致或者某个类别目录被错误嵌套索引就会错位。解决在训练前打印ImageFolder的class_to_idx映射和数据集说明文件逐项核对再随机抽取每个类别的三五张训练图确认图片内容确实属于目录名对应的植物。这一步看起来土但对34类这种中等规模数据集一次映射错误能让整个模型白训。6. 验证划分不只是看准确率混淆矩阵与困难样本排查技巧模型训完验证集准确率达标这只是第一步。图像分类项目的验收我习惯再往下走两步输出混淆矩阵并手动抽查误判样本。一个合理的34类分类结果混淆矩阵应该是“对角线强亮、非对角线稀疏”如果某个非对角单元特别亮说明两个类别特征相近需要回去看是标注噪声还是视觉差异确实微小。用sklearn可以快速出矩阵和逐类报告。import torch import numpy as np from sklearn.metrics import confusion_matrix, classification_report all_preds, all_labels [], [] model.eval() with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) all_preds.extend(torch.argmax(outputs, dim1).cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) report classification_report(all_labels, all_preds, target_namestrain_data.classes) print(report) np.save(confusion_matrix.npy, cm)这段代码跑完之后重点看classification_report里每个类别的recall值找出低于整体水平的类别。然后去数据集里挑几张这类别的错误预测图片人工确认是叶片本身形态相近、照片光照异常还是原始标注就标错了。如果是标注错把样本从训练集里剔除或修正比调模型结构有效得多。这一步做完再谈换EfficientNet或者加数据增强才有依据。我的个人习惯是先跑通、再调参、最后才谈换模型。每次拿到新的图像分类数据集哪怕对方说已经划分好了我也会先花十分钟跑第3章的三个校验脚本。这在数据量小的时候看不出差别但做到34类、几万张这个量级一次文件交集没查、一个类别映射错位浪费的时间就是按天算的。数据划分是整个图像分类流程里最不性感、也最能决定成败的一环把这一步守住后面所有工作都稳。希望这份流程笔记帮你在自己的数据集上少走几个弯路。本文还有配套的精品资源点击获取
返回列表