
简介这份玉米病害图像分类数据集面向从事农业图像识别、深度学习入门与CNN分类实践的学生和开发者提供约2,800张已标注样本覆盖尾孢叶斑灰、普通锈蚀等4个类别具体类别以包内json文件为准。资源已按训练集、验证集、测试集划分各类图片分目录存放便于直接构建分类任务并评估模型泛化能力包内附带show脚本可快速可视化样本分布与图像内容。压缩包共约2000个文件以1998张jpg图像为主体另有1个py脚本和1个json标注文件整体约49.46MB结构清晰、开箱即用。目前已有284人学习下载。借助该数据集读者可完成数据加载、类别统计、模型训练与对比实验并配合作者主页的CNN分类网络改进系列内容进一步尝试骨干网络替换、注意力机制等优化思路适合作为课程设计、竞赛练手或论文实验的基础数据。1. 玉米病害图像分类数据集从拿到手到跑通第一个baseline你手里有一份约2,800张的玉米病害图像分类数据集已经标注好了。这件事听起来简单但真正动手的人会立刻撞上几个问题类别怎么分布的图像尺寸统一吗标注格式是文件夹分类还是CSV能不能直接喂给YOLOv8或Transformer我见过太多人拿到数据集第一件事就是torchvision.datasets.ImageFolder一把梭结果训练到一半发现某个类别只有几十张图模型直接摆烂。这份数据集的核心价值在于“已标注”三个字。图像分类的数据标注和检测不一样它不需要画框只需要把每张图归到对应病害类别。常见做法是按文件夹组织比如train/rust/、train/blight/每个文件夹名就是类别标签。2,800张的规模不算大但足够跑通一个从数据清洗到模型部署的完整链路。适合谁适合想入门农业视觉检测的算法工程师、做智慧农业课题的研究生以及需要快速验证图像分类方案的技术团队。下面我从数据检查、格式转换、模型选型到训练调参把这条链路拆开讲。2. 先搞清楚数据长什么样2,800张玉米病害图的清洗与分布检查2.1 用三行Python摸清类别分布和图像尺寸拿到数据集别急着写DataLoader先做一次全量扫描。我一般会跑下面这段脚本把每个类别的文件数、图像宽高、通道数、文件大小全部拉出来。这一步能直接暴露类别不平衡、损坏文件、灰度图混入等问题。import os from PIL import Image from collections import defaultdict data_root ./corn_disease_dataset # 替换为你的数据集根目录 stats defaultdict(lambda: {count: 0, sizes: [], modes: set()}) for cls_name in os.listdir(data_root): cls_dir os.path.join(data_root, cls_name) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): fpath os.path.join(cls_dir, fname) try: with Image.open(fpath) as img: stats[cls_name][count] 1 stats[cls_name][sizes].append(img.size) stats[cls_name][modes].add(img.mode) except Exception as e: print(f[损坏] {fpath} - {e}) for cls, info in stats.items(): sizes info[sizes] w_avg sum(s[0] for s in sizes) / len(sizes) h_avg sum(s[1] for s in sizes) / len(sizes) print(f{cls}: {info[count]}张, 平均尺寸({w_avg:.0f},{h_avg:.0f}), 模式{info[modes]})这段代码的逻辑很直白遍历每个类别文件夹用PIL打开每张图记录尺寸和色彩模式。跑完之后重点看三个东西。第一类别计数是否悬殊如果某个类别少于100张后面必须做重采样或强增强。第二尺寸方差大不大如果从200px到2000px都有统一缩放到224或384之前要考虑是否保留长宽比。第三modes里如果出现L或RGBA说明有灰度图或带透明通道的图训练前要统一转成RGB。参数说明data_root指向你的数据集根目录目录结构假设是根目录/类别名/图片文件。如果你的标注是CSV格式需要先按标签列拆分成文件夹或者改写上面的遍历逻辑直接读CSV。2.2 类别不平衡的处理过采样、欠采样还是损失加权2,800张图分到几个病害类别里大概率是不均匀的。玉米常见病害包括锈病、大斑病、小斑病、灰斑病等有些病害在田间拍摄时更容易采集有些则偏少。我一般先算一个比例最大类别数除以最小类别数。如果超过5就必须处理如果在2到5之间可以用轻量增强扛过去。处理方式有三种。过采样是把少样本类别的图重复采样配合强增强避免过拟合。欠采样是砍掉多样本类别的图但2,800张本来就不多砍完可能不够训练。损失加权是在CrossEntropyLoss里给每个类别一个权重权重和样本数成反比。我通常优先用损失加权因为它不改变数据分布实现也简单import torch from torch import nn class_counts [320, 180, 95, 210, 150] # 按类别顺序填入实际数量 total sum(class_counts) weights [total / (len(class_counts) * c) for c in class_counts] class_weights torch.tensor(weights, dtypetorch.float32) criterion nn.CrossEntropyLoss(weightclass_weights)这里的class_counts要换成你实际统计出来的数字。weights的计算逻辑是总样本数除以类别数再除以该类样本数这样少样本类别的loss权重会更大。注意如果某个类别只有几十张权重会飙得很高训练容易震荡这时候可以设一个上限比如最大权重不超过10。提示损失加权不是万能药。如果少样本类别的图像本身质量差、标注噪声大加权反而会放大错误梯度。先检查少样本类别的图是否清晰、标签是否准确再决定要不要加权。3. 从文件夹到Tensor构建可复现的玉米病害分类数据管道3.1 用torchvision做分层拆分与增强数据检查完下一步是拆训练集、验证集、测试集。图像分类最常见的翻车点是用random_split直接随机拆导致某个类别在验证集里一张都没有。正确做法是分层拆分按类别比例分配。下面这个脚本用sklearn的train_test_split做分层然后构建Dataset和DataLoader。import os from sklearn.model_selection import train_test_split from torchvision import datasets, transforms from torch.utils.data import DataLoader, Subset data_root ./corn_disease_dataset full_dataset datasets.ImageFolder(rootdata_root) targets [label for _, label in full_dataset.imgs] # 先拆出测试集再拆验证集 train_idx, test_idx train_test_split( range(len(targets)), test_size0.15, stratifytargets, random_state42 ) train_targets [targets[i] for i in train_idx] train_idx, val_idx train_test_split( train_idx, test_size0.15, stratifytrain_targets, random_state42 ) train_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_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_set Subset(datasets.ImageFolder(data_root, transformtrain_transform), train_idx) val_set Subset(datasets.ImageFolder(data_root, transformval_transform), val_idx) test_set Subset(datasets.ImageFolder(data_root, transformval_transform), test_idx) train_loader DataLoader(train_set, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_set, batch_size32, shuffleFalse, num_workers4) test_loader DataLoader(test_set, batch_size32, shuffleFalse, num_workers4)逻辑说明ImageFolder默认按文件夹名排序生成类别索引所以训练集和验证集的类别顺序必须一致这里用同一个full_dataset的imgs列表做索引拆分保证标签映射不变。stratifytargets确保每个类别在训练、验证、测试集中的比例一致。增强部分训练集用了随机裁剪、水平翻转、旋转和颜色抖动验证和测试集只做缩放和归一化。参数说明Resize((256,256))再RandomCrop(224)是经典搭配先放大再裁剪增加尺度多样性。RandomRotation(15)对田间拍摄的角度变化有帮助但不要超过30度否则叶片方向失真。ColorJitter的四个参数控制亮度、对比度、饱和度、色调的扰动幅度农业图像受光照影响大适当加一点有好处。Normalize用的是ImageNet的均值和标准差如果你从零训练可以用数据集自身的统计值但用预训练模型就必须对齐。3.2 选模型ResNet、EfficientNet还是ViT2,800张图属于小样本场景模型选型直接决定能不能收敛。我的经验是优先用预训练模型优先用参数量小的。ResNet50有2,500万参数EfficientNet-B0只有530万ViT-Base有8,600万。小样本下ViT从零训练基本没戏但用预训练权重微调可以试。下面给一个EfficientNet-B0的微调示例这是我在2,800张规模下最常用的baseline。import torch import torchvision.models as models from torch import nn, optim device torch.device(cuda if torch.cuda.is_available() else cpu) model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1) num_classes len(full_dataset.classes) model.classifier[1] nn.Linear(model.classifier[1].in_features, num_classes) model model.to(device) # 只微调分类头和最后一个block冻结前面的层 for name, param in model.named_parameters(): if classifier not in name and features.8 not in name: param.requires_grad False optimizer optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30)逻辑说明加载ImageNet预训练的EfficientNet-B0替换分类头为你的病害类别数。冻结前面所有层只训练分类头和最后一个特征块。这样做的好处是训练快、不容易过拟合2,800张图在单卡上几分钟就能跑完一个epoch。AdamW的weight_decay设1e-4lr设1e-3配合余弦退火调度器。参数说明features.8是EfficientNet-B0的最后一个特征块解冻它可以让模型适应玉米叶片的纹理特征。如果你的类别数少于5可以只训练分类头如果类别数超过10建议多解冻一个block。T_max30表示30个epoch完成一个余弦周期实际训练可以设50到100个epoch观察验证集loss什么时候不再下降。注意不要一上来就解冻全部层。小样本全量微调很容易在第一个epoch就把预训练权重带偏验证集准确率会先降后升很多人看到第一轮掉点就放弃了。先冻结训练几轮再逐步解冻是更稳的做法。4. 训练玉米病害分类模型参数怎么设、指标怎么看、什么时候停4.1 训练循环与验证集监控训练循环本身不复杂但有几个细节决定成败。下面是一个完整的训练和验证循环包含loss记录、准确率计算和最佳模型保存。import time def train_one_epoch(model, loader, criterion, optimizer, device): model.train() running_loss, correct, total 0.0, 0, 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, correct, total 0.0, 0, 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 best_acc 0.0 patience, wait 10, 0 for epoch in range(50): 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}: train_loss{train_loss:.4f} train_acc{train_acc:.4f} val_loss{val_loss:.4f} val_acc{val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_corn_model.pth) wait 0 else: wait 1 if wait patience: print(fEarly stopping at epoch {epoch1}) break逻辑说明每个epoch先训练再验证记录训练和验证的loss与准确率。best_acc跟踪验证集最高准确率每次刷新就保存模型权重。patience10表示连续10个epoch验证集准确率没提升就早停避免过拟合。参数说明batch_size32在2,800张图下大约每个epoch有60到70个batch。如果显存不够降到16或8但要注意学习率也要相应调小。epoch50配合早停实际训练通常在20到35个epoch之间结束。验证集准确率是主要监控指标但如果类别不平衡还要看每类的召回率后面会讲。4.2 混淆矩阵与分类报告别只看总体准确率总体准确率在类别不平衡时会骗人。假设锈病占60%其他病害各占10%模型全猜锈病也有60%准确率。所以训练完必须看混淆矩阵和每类指标。from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs imgs.to(device) outputs model(imgs) _, preds outputs.max(1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, target_namesfull_dataset.classes)) print(confusion_matrix(all_labels, all_preds))classification_report会输出每个类别的精确率、召回率、F1分数。重点看少样本类别的召回率如果低于0.6说明模型没学好这一类需要回头检查数据增强是否够、损失权重是否合理。混淆矩阵能看出哪些类别容易混比如大斑病和小斑病在早期症状上很像模型分错是正常的可以考虑合并类别或增加这两类的区分性特征。提示如果测试集准确率和验证集差很多先检查测试集的预处理是否和验证集一致。我踩过这个坑验证集用了Resize(224)测试集忘了加Normalize结果准确率掉了20个点排查了一下午。5. 玉米病害分类的避坑与排查5个血泪教训5.1 现象训练loss震荡不下降验证准确率卡在随机水平原因学习率太大或者预训练权重和你的数据预处理不匹配。常见的是Normalize的均值和标准差用错了比如用了自己算的但实际加载的是ImageNet预训练权重。解决先把学习率降到1e-4试一轮如果loss开始下降说明是学习率问题。然后检查预处理用预训练模型时必须用对应的Normalize参数。EfficientNet和ResNet都用ImageNet的mean[0.485,0.456,0.406]std[0.229,0.224,0.225]。5.2 现象某个类别验证集准确率始终为0原因这个类别的样本在训练集中太少或者标签文件夹名有拼写错误导致ImageFolder把它当成了另一个类别。还有一种可能是这个类别的图像本身有问题比如全是灰度图或分辨率极低。解决先打印full_dataset.class_to_idx确认类别映射再统计每个类别的训练样本数。如果少于50张考虑过采样或合并到相似类别。如果图像有问题单独拿出来看看必要时剔除。5.3 现象训练集准确率99%验证集准确率60%原因过拟合。2,800张图对深度模型来说太少模型记住了训练集的噪声。数据增强不够强、模型参数量太大、训练轮数太多都会导致这个问题。解决加强增强加RandomErasing、Mixup或CutMix。换更小的模型比如从ResNet50换到EfficientNet-B0。加Dropout在分类头前面加nn.Dropout(0.5)。早停的patience从10降到5。5.4 现象DataLoader报错“image file is truncated”原因数据集里有损坏的JPEG文件可能是下载或传输过程中断导致的。PIL默认会抛异常但有时候只是警告。解决在Dataset里加一个容错处理遇到损坏文件就跳过或重新保存。更彻底的做法是训练前跑一遍全量检查把损坏文件列出来删掉或修复。from PIL import ImageFile ImageFile.LOAD_TRUNCATED_IMAGES True这行代码让PIL加载截断的图片但只是权宜之计最好还是找到损坏文件替换掉。5.5 现象GPU利用率低训练速度慢原因num_workers设得太小或者数据增强在CPU上成了瓶颈。还有一种可能是图像尺寸太大224已经够用但有人用512甚至1024。解决num_workers设成CPU核心数的一半到全部比如8核设4到8。用pin_memoryTrue加速CPU到GPU的传输。图像尺寸控制在224到384之间2,800张图不需要太大分辨率。如果还慢考虑把增强后的图预处理好存成numpy数组训练时直接读。6. 把2,800张用到极致小样本下的进阶技巧与验证习惯2,800张图在图像分类里属于小样本但用对方法完全可以做到85%以上的验证准确率。我自己的习惯是先用EfficientNet-B0加损失加权跑一个baseline记录混淆矩阵然后针对混淆严重的类别做定向增强比如大斑病和小斑病容易混就专门对这两类加更多的旋转和颜色抖动最后用测试集做一次终评不再回头调参。进阶技巧方面Mixup和CutMix在小样本下很有效。Mixup把两张图按比例混合标签也按比例混合相当于让模型看到更多样的样本组合。CutMix是把一张图的一部分剪切粘贴到另一张图上强迫模型关注局部特征。这两个方法在2,800张规模下通常能提升2到5个点。实现上PyTorch里可以用torchvision.transforms.v2.MixUp和CutMix也可以手写。还有一个容易被忽略的点验证集的构建方式。如果你的数据集里同一片叶子的不同角度被分到了训练集和验证集验证准确率会虚高。正确做法是按拍摄批次或叶片ID拆分确保验证集的叶片在训练集里没出现过。这个细节在农业图像里特别重要因为田间拍摄往往同一株玉米拍很多张。最后说一个我自己的习惯每次训练完把验证集里预测错的图单独存到一个文件夹按“真实类别_预测类别”命名。过一段时间回头看这些错图能发现很多数据问题比如标签标错了、图像模糊、类别定义有歧义。这个习惯帮我省了很多次重新标注的功夫。希望帮到你。本文还有配套的精品资源点击获取