ARTICLE DETAIL

资讯详情

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

西红柿病害图像分类数据集:3.2万张标注图与11类识别实战

西红柿病害图像分类数据集:3.2万张标注图与11类识别实战 简介这份西红柿病害图像分类数据集面向从事农业视觉识别、深度学习课程实践与CNN分类网络改进的开发者与研究者覆盖11类常见番茄病害包括Bacterial_spot、powdery_mildew、Early_blight等类别明细可查阅包内json文件。资源已按训练集与验证集划分同类图片集中存放便于直接接入主流分类框架训练与评估。压缩包共约2000个文件以1998张jpg图像为主另含1个py脚本与1个json标注文件整体约739.33MB其中show脚本可用于快速可视化样本分布与图像质量。目前已有38人学习下载。读者可借此完成数据加载、类别统计、模型训练与效果对比并配合作者主页的CNN分类网络改进系列内容将数据集用于骨干网络替换、注意力模块消融等实验快速搭建可复现的番茄病害识别流程。1. 三万张西红柿病害图这个数据集到底能帮你解决什么你手头有一批西红柿叶片照片想训练一个能分辨早疫病、晚疫病、叶霉病的分类模型但卡在第一步——没有标注好的数据。自己拍、自己标三千张就能耗掉两周更别说三万张。这个标题指向的正是一个已经标注完成、约 32,000 张的西红柿病害图像分类数据集覆盖 11 种病害类别。它解决的不是模型结构问题而是「数据从哪来、标签怎么对齐、类别怎么分」这三件最耗人力的事。适合谁用做农业 AI 落地的算法工程师、想跑通图像分类全流程的学生、以及需要快速验证病害识别方案的团队。你拿到它之后真正要花心思的地方在于类别映射对不对、划分比例合不合理、增强策略会不会把病斑特征抹掉。下面按「先看清数据长什么样再动手跑通最后避开翻车点」的顺序拆开讲。2. 先搞懂 11 类西红柿病害的标注逻辑与目录结构2.1 类别体系与标注粒度决定了模型上限拿到一个图像分类数据集第一件事不是急着写 DataLoader而是把类别清单和每类样本量拉出来看。11 种西红柿病害通常涵盖早疫病Early Blight、晚疫病Late Blight、叶霉病Leaf Mold、细菌性斑点病Bacterial Spot、靶斑病Target Spot、 mosaic virus花叶病毒、黄化曲叶病毒Yellow Leaf Curl Virus、蜘蛛螨Spider Mites、健康叶片等。不同数据集的类别命名可能是英文、拼音或编号你必须先做一次映射否则训练出来的 label 和实际病害对不上模型再准也没法用。标注粒度上图像分类数据集一般是一图一标签即整张叶片图对应一个类别。但西红柿病害有个坑一片叶子上可能同时出现两种病斑。如果数据集是单标签体系这类样本要么被归到主导病害要么被剔除。你需要抽样几十张图用肉眼确认标注是否与图像内容一致。常见做法是随机抽 5% 的样本按类别分层抽样逐张核对。如果发现某类错标率超过 5%就要考虑清洗或重新标注。样本量分布同样关键。32,000 张分到 11 类平均每类约 2,900 张但实际往往不均衡。健康叶片可能占 4,000 张而某些罕见病害只有 800 张。这种长尾分布会直接导致模型偏向多数类。你需要在训练前统计每类数量对少于 1,500 张的类别做过采样或强增强。下面这段脚本用来统计类别分布并生成映射表import os from collections import Counter from pathlib import Path # 假设数据集按类别文件夹组织dataset/train/类别名/图片 data_root Path(dataset/train) class_counts Counter() class_names sorted([d.name for d in data_root.iterdir() if d.is_dir()]) for cls in class_names: imgs list((data_root / cls).glob(*.*)) class_counts[cls] len(imgs) # 打印类别与数量并生成 类别名-编号 映射 label_map {name: idx for idx, name in enumerate(class_names)} print(类别映射, label_map) for name, cnt in class_counts.items(): print(f{name}: {cnt} 张)这段代码的逻辑很直接遍历训练集下每个类别文件夹统计图片数量同时按字母序生成从 0 开始的整数标签。参数上data_root要改成你实际存放训练集的路径如果数据集已经划分好 train/val/test就对三个子集分别跑一遍确认划分后各类比例是否一致。注意glob(*.*)会匹配所有带扩展名的文件如果文件夹里有非图片文件如.DS_Store需要加后缀过滤比如*.jpg、*.png。2.2 目录组织方式与划分比例怎么定常见的数据集目录有两种一种是train/val/test已经分好每个子集下再按类别分文件夹另一种是全部图片放在一个目录标签在 CSV 里。标题说「已标注」大概率是第一种。你需要确认划分比例。农业图像数据集常见做法是 7:2:1 或 8:1:1。如果原始数据只给了训练集你得自己切分。切分时不能随机打乱因为同一片叶子的多张照片可能被分到训练和验证集造成数据泄漏。正确做法是按「叶片 ID」或「拍摄批次」分组切分。如果你拿到的数据集已经分好 train/val/test先检查三个子集的类别分布是否一致。比如训练集里晚疫病占 15%验证集里只占 5%那验证结果就会失真。下面这段代码用来检查各子集类别比例import pandas as pd from pathlib import Path splits [train, val, test] rows [] for split in splits: root Path(fdataset/{split}) for cls_dir in root.iterdir(): if cls_dir.is_dir(): cnt len(list(cls_dir.glob(*.jpg))) rows.append({split: split, class: cls_dir.name, count: cnt}) df pd.DataFrame(rows) pivot df.pivot(indexclass, columnssplit, valuescount).fillna(0) pivot[train_ratio] pivot[train] / pivot.sum(axis1) print(pivot)逻辑说明把三个子集的类别数量拉成一张透视表并计算每类在训练集中的占比。参数上*.jpg要按实际图片格式调整。如果发现某类在验证集或测试集中数量为 0说明切分时没有按类别分层需要重新用sklearn.model_selection.train_test_split的stratify参数切分。提示切分前先把所有图片路径和标签读进 DataFrame用groupby按类别分层抽样避免某一类在验证集里消失。3. 用 PyTorch 跑通 11 类病害分类的最小训练流程3.1 数据增强与预处理别把病斑特征增强没了西红柿病害识别的关键特征是小面积病斑的颜色、纹理和边缘。如果你直接套用 ImageNet 的增强策略比如随机裁剪到 224×224、颜色抖动幅度过大病斑可能被裁掉或颜色失真。我一般会这样配训练集用RandomResizedCrop(224, scale(0.7, 1.0))保留至少 70% 的叶片区域颜色抖动只调亮度和对比度幅度控制在 0.2 以内再加水平翻转和轻微旋转±15 度。验证集和测试集只做Resize(256)CenterCrop(224)不做随机增强。下面是一个可复用的 Dataset 和 Transform 配置import torch from torch.utils.data import Dataset, DataLoader from torchvision import transforms from PIL import Image from pathlib import Path class TomatoDataset(Dataset): def __init__(self, root, transformNone): self.root Path(root) self.transform transform self.samples [] self.classes sorted([d.name for d in self.root.iterdir() if d.is_dir()]) self.class_to_idx {c: i for i, c in enumerate(self.classes)} for cls in self.classes: for img_path in (self.root / cls).glob(*.jpg): self.samples.append((img_path, self.class_to_idx[cls])) 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_tf transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_tf 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]) ]) train_ds TomatoDataset(dataset/train, transformtrain_tf) val_ds TomatoDataset(dataset/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4)逻辑说明TomatoDataset按类别文件夹读取图片自动生成class_to_idx。训练变换里scale(0.7, 1.0)保证裁剪后仍保留大部分叶片ColorJitter只调亮度和对比度不调饱和度和色调避免病斑颜色偏移。参数上batch_size32适合 8GB 显存如果显存不够降到 16 并配合梯度累积。num_workers按 CPU 核数设置Windows 下建议设为 0 避免多进程报错。3.2 模型选择与训练循环从 ResNet 到 EfficientNet 的取舍11 类分类任务数据量 3 万张不算小。ResNet-50 是稳妥的基线但如果你想要更高精度且显存有限EfficientNet-B0 或 B1 更划算。我一般先用 ResNet-18 跑一个快速基线确认数据管道没问题再换 EfficientNet-B3 做正式训练。优化器用 AdamW学习率 3e-4权重衰减 1e-4余弦退火调度。损失函数用带标签平滑的交叉熵平滑系数 0.1能缓解标注噪声。下面是一个最小训练循环import torch.nn as nn import torch.optim as optim from torchvision.models import efficientnet_b0, EfficientNet_B0_Weights device torch.device(cuda if torch.cuda.is_available() else cpu) model efficientnet_b0(weightsEfficientNet_B0_Weights.IMAGENET1K_V1) model.classifier[1] nn.Linear(model.classifier[1].in_features, 11) model model.to(device) criterion nn.CrossEntropyLoss(label_smoothing0.1) optimizer optim.AdamW(model.parameters(), lr3e-4, weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() scheduler.step() # 每个 epoch 后在验证集上算准确率 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) preds outputs.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) print(fEpoch {epoch1}, Val Acc: {correct/total:.4f})逻辑说明加载 ImageNet 预训练的 EfficientNet-B0把最后一层全连接改成 11 类输出。label_smoothing0.1防止模型对标注噪声过拟合。学习率 3e-4 配合余弦退火30 个 epoch 足够收敛。参数上如果你用 ResNet-50学习率可以降到 1e-4如果 batch size 调到 64学习率可以线性放大到 6e-4。验证时只做argmax算准确率但类别不均衡时准确率会骗人建议同时输出每类的 precision 和 recall。注意如果验证集准确率在 5 个 epoch 内不升反降先检查学习率是不是太大或者数据增强是不是太狠。把ColorJitter关掉再跑一次如果准确率立刻上升说明增强把病斑特征破坏了。4. 避坑三万张西红柿病害数据训练时最容易翻车的 5 个点4.1 现象训练准确率 99%验证准确率 60% 不到原因同一片叶子的多张照片被分到了训练集和验证集模型记住了叶片背景而不是病斑特征。西红柿病害数据集里同一株植物可能拍了几十张如果按图片随机切分必然泄漏。解决按「叶片 ID」或「拍摄时间位置」分组切分。如果数据集没有提供分组信息用图片文件名里的前缀做分组比如leaf001_1.jpg、leaf001_2.jpg归到同一组整组进训练集或验证集。用GroupShuffleSplit代替train_test_split。4.2 现象模型把健康叶片全预测成某一种病害原因健康叶片样本太少或者健康叶片的特征和某种病害早期症状太像。32,000 张里健康叶片可能只有 1,000 张模型为了降低整体损失干脆把不确定的样本都归到多数类。解决对健康叶片做过采样或者用WeightedRandomSampler给少数类更高采样权重。损失函数改用 Focal Loss让模型关注难分类样本。同时检查健康叶片的标注是否混入了早期病叶。4.3 现象训练 loss 震荡剧烈几个 epoch 后突然变成 NaN原因学习率太大或者数据里有损坏图片导致梯度爆炸。西红柿病害数据集可能包含手机拍摄的 HEIC 格式转 JPG 的图片色彩空间异常。解决先把学习率降到 1e-4 跑 3 个 epoch如果 loss 稳定再逐步调高。在 Dataset 的__getitem__里加 try-except跳过无法打开的图片并打印路径。用torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)裁剪梯度。4.4 现象验证集准确率很高但测试集一塌糊涂原因验证集和测试集的拍摄条件不同。比如验证集是实验室均匀光照测试集是田间自然光。数据集的 train/val/test 划分可能按时间切分导致分布偏移。解决先确认三个子集的来源是否一致。如果测试集是独立采集的需要在训练时加入更强的光照和背景增强比如RandomApply([transforms.GaussianBlur(3)])和随机调整 gamma。同时用测试集做一次零样本评估看模型在无微调情况下的表现。4.5 现象某类病害的 recall 始终为 0原因该类样本在训练集中少于 200 张模型根本没学到特征。或者类别名映射时该类被合并到了其他类。解决统计每类数量少于 500 张的类别要么补充数据要么在评估时单独看。检查class_to_idx映射确认没有两个类别指向同一个编号。如果数据集本身不均衡考虑用分层采样或类别权重。5. 进阶用混淆矩阵和 Grad-CAM 验证模型到底看的是不是病斑训练完模型准确率只是一个数字。你真正需要知道的是模型有没有在看病斑。我一般会做两件事画混淆矩阵找出最容易混的类别对用 Grad-CAM 热力图看模型关注区域。如果热力图高亮的是叶片边缘或背景说明模型走了捷径。混淆矩阵用sklearn.metrics.confusion_matrix和seaborn.heatmap就能画。重点看哪两类互相错分最多。比如早疫病和靶斑病在早期症状上都是褐色斑点模型容易混。这时候你需要回到数据层面看这两类的标注边界是否清晰或者考虑加一个二阶段分类器专门区分这两类。Grad-CAM 的实现用pytorch-grad-cam库最省事from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image import numpy as np target_layers [model.features[-1]] cam GradCAM(modelmodel, target_layerstarget_layers) # 取一张验证集图片 img_tensor, label val_ds[0] input_tensor img_tensor.unsqueeze(0).to(device) grayscale_cam cam(input_tensorinput_tensor, targetsNone) grayscale_cam grayscale_cam[0, :] # 叠加到原图 rgb_img img_tensor.permute(1, 2, 0).cpu().numpy() rgb_img (rgb_img - rgb_img.min()) / (rgb_img.max() - rgb_img.min()) visualization show_cam_on_image(rgb_img, grayscale_cam, use_rgbTrue)逻辑说明target_layers选 EfficientNet 最后一个卷积块targetsNone表示用预测类别作为目标。生成的grayscale_cam是 0 到 1 的热力图叠加到原图上就能看到模型关注区域。参数上如果热力图太分散把target_layers换成更浅的层如果太局部换成更深的层。我一般会抽 20 张验证集图片批量生成热力图人工看一遍。如果超过三成图片的高亮区域不在病斑上这个模型就不能上线。还有一个实用技巧把验证集中模型预测错但置信度很高的样本单独拉出来看。这些样本往往是标注错误或者类别定义模糊。我习惯用pandas把image_path、true_label、pred_label、confidence存成 CSV按置信度降序排人工复核前 50 张。十有八九能找出十几张标错的。把这些样本修正后重新训练验证准确率通常能涨 2 到 3 个百分点。最后说个血泪教训别在训练中途频繁改数据增强。我曾经在一个西红柿病害项目里看到验证准确率波动就调增强参数结果模型始终不稳定。后来固定增强策略只调学习率和 batch size反而顺利收敛。数据管道一旦确定就把它当成黑匣子别老去动它。希望帮到你。本文还有配套的精品资源点击获取
返回列表