ARTICLE DETAIL

资讯详情

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

五作物叶片病害数据集与ResNet50迁移学习实战指南

五作物叶片病害数据集与ResNet50迁移学习实战指南 简介面向计算机视觉与农业智能化应用场景的叶片病害图像分类数据集覆盖玉米、马铃薯、水稻、甘蔗、小麦5种作物共17个病害类别全部图像已完成标注并划分好训练集与测试集适合训练CNN分类模型、验证迁移学习效果或用于课程设计与论文实验。资源包共2000个文件压缩后约280.36MB其中包含1998张jpg图片、1个Python可视化脚本和1个json类别配置文件。运行show脚本即可快速浏览数据分布json文件可查看各类别名称与划分规则省去手动整理和标注成本已有208人学习下载。使用这份数据可直接开展图像分类任务配合作者分享的CNN网络改进专栏可进一步对比不同网络结构在复杂叶片病害识别上的精度与泛化性能对做智慧农业方向研究或深度学习实战进阶都有实用价值。1. 五作物叶片病害数据集约 13,000 张已标注图像能解决什么问题做农业视觉项目的人应该都有同感找齐一套像样的叶片病害图像分类数据集比调模型本身更磨人。这个数据集覆盖玉米、马铃薯、水稻、甘蔗、小麦五种大田作物合计约 13,000 张已标注图像每张图都带明确的健康或病害类别标签解压后就能直接喂给图像分类模型训练不用自己爬图、清洗、打标。对经常要快速验证分类思路的人来说它最大的价值在于省掉两周以上的数据准备时间。它适合正在做作物病害识别、农业图像分类、或者需要一份现成 benchmark 数据集来验证网络结构选型的同学。2. 数据集目录与标注体系先看清文件结构再动手拿到压缩包后第一件事不是急着训练而是把目录结构和标注方式看清楚。图像分类数据集的标注方式常见的有两种一种是 ImageNet 风格的文件夹组织类别名直接当文件夹名另一种是 CSV 或 JSON 映射表。这个数据集按常见的做法整理成了 train、val、test 三个子集子集内部按类别分文件夹同时附一份 label_mapping.csv 记录类别名到数字索引的映射。这样无论你想用 ImageFolder 直接加载还是自己写 Dataset 类做精细控制都能快速切换不用二次加工。2.1 目录结构与文件组织解压后的顶层目录结构大概是这样的leaf_disease_dataset/ ├── train/ │ ├── Maize_Healthy/ │ ├── Maize_Leaf_Spot/ │ ├── Maize_Rust/ │ ├── Potato_Healthy/ │ ├── Potato_Early_Blight/ │ ├── Potato_Late_Blight/ │ ├── Rice_Healthy/ │ ├── Rice_Blast/ │ ├── Rice_Bacterial_Leaf_Blight/ │ ├── Sugarcane_Healthy/ │ ├── Sugarcane_Red_Rot/ │ ├── Wheat_Healthy/ │ ├── Wheat_Strip_Rust/ │ └── Wheat_Leaf_Rust/ ├── val/ │ └── 与 train 相同的类别文件夹 ├── test/ │ └── 与 train 相同的类别文件夹 ├── label_mapping.csv └── README.txttrain、val、test 三个目录里的子文件夹是完全一致的类别数量相同、命名相同后两个目录分别用于验证和最终测试。这里有个细节值得注意val 和 test 的用途不同val 是用来在训练中做早停、调学习率、选 epoch 的而 test 必须留到最后才碰一次这样最终评估指标才可信。很多人图省事只用 train 加 val把 test 合并进 val等提交结果或写报告的时候手里就没有干净的数据了。label_mapping.csv 里存的是文件夹名到数字标签的映射内容类似下面这样ClassName,LabelIndex Maize_Healthy,0 Maize_Leaf_Spot,1 Maize_Rust,2 Potato_Healthy,3 Potato_Early_Blight,4 Potato_Late_Blight,5 Rice_Healthy,6 Rice_Blast,7 Rice_Bacterial_Leaf_Blight,8 Sugarcane_Healthy,9 Sugarcane_Red_Rot,10 Wheat_Healthy,11 Wheat_Strip_Rust,12 Wheat_Leaf_Rust,13如果要用 PyTorch 的 ImageFolder 加载这个文件可以不用管因为 ImageFolder 会自动按字母序生成类别索引但如果你要对齐自己的标签体系或者想用 TensorFlow 的 flow_from_directory那份 CSV 就是唯一的映射标准。我的习惯是先读一遍 CSV确认类别顺序和 ImageFolder 的 class_to_idx 一致然后再进入训练环节。2.2 类别标签体系与划分比例五种作物各自覆盖的病害类别不同整理下来大致是这些玉米三类马铃薯三类水稻三类甘蔗两类小麦三类加上每个作物的健康叶片类别总计 14 个类别。每类图像数量并不完全均衡主粮作物比如玉米和小麦的样本会偏多一些甘蔗这种经济作物的健康样本会在两个左右类别里被拉掉一部分这点在做评估时要尤其留意准确率这个单一指标很容易被样本多的类别拉高。划分比例上train 约占 70%val 占 15%test 占 15%。这个比例对迁移学习来说是够用的每个类别在训练集里大约能分到 130 到 200 张图像配上预训练权重做微调已经能训出可用的模型。如果你打算把五个作物合并成一个 14 类的大分类器上述划分保持不变如果你想按作物单独训五个模型需要自己按子目录重新聚合两种路线我在第 3 章都会给出对应做法。3. 用 ResNet50 做迁移学习训练数据增强、超参数与全流程代码数据集准备好只完成了第一步重头戏在训练环节。对 13,000 张这个规模从零训练一个深度卷积网络并不明智参数量跟不上样本量泛化必然出问题。常规做法是加载 ImageNet 预训练权重做迁移学习把最后的全连接层换成自己的类别数然后以较小的学习率微调。ResNet50 在这个场景下是性价比很高的选择残差结构在农业图像这种背景复杂、目标尺度多变的场景里表现稳定训练显存占用也适中微调时单卡 11GB 左右就能跑起来。3.1 数据加载与增强策略数据加载直接用 torchvision 的 ImageFolder增强策略是决定模型能不能泛化的关键。农业图像最典型的干扰是拍摄角度不一、光照变化、叶片姿态不同所以增强里旋转、翻转和颜色抖动必须加from torchvision import datasets, transforms from torch.utils.data import DataLoader transform_train transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) transform_eval 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(leaf_disease_dataset/train, transformtransform_train) val_dataset datasets.ImageFolder(leaf_disease_dataset/val, transformtransform_eval) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue)先 Resize 到 256再 RandomCrop 到 224等价于给模型提供了小幅平移和缩放扰动比直接 Resize 到 224 效果更好。ColorJitter 的三个参数值得解释一下brightness0.3 表示亮度在正负 30% 范围内随机变化contrast 和 saturation 同理。农业图像里早晚光照色温差异很大颜色抖动太弱模型容易把背景色调错当成病害特征太强又会把病斑原本的颜色特征洗掉0.2 到 0.3 之间是比较稳的范围。验证集上不要用任何随机增强只做 Resize 和 Normalize否则验证指标会带噪声早停判断就不准了。Normalize 用的 mean 和 std 是 ImageNet 的统计值因为迁移学习加载的是 ImageNet 预训练权重输入分布必须和预训练时保持一致。3.2 迁移学习训练参数配置模型部分把 ResNet50 的最后的 fc 层替换成 14 类输出。这里有两种微调策略可选一种是把 backbone 完全冻结只训练 fc 层速度快但效果有限另一种是全参数微调backbone 用较小的学习率新加的 fc 层用较大学习率。第二种更常用我直接给出完整实现import torch import torch.nn as nn from torchvision import models model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V2) num_classes len(train_dataset.classes) model.fc nn.Linear(model.fc.in_features, num_classes) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr1e-3, momentum0.9, weight_decay1e-4) fc_params model.fc.parameters() backbone_params [p for name, p in model.named_parameters() if not name.startswith(fc.)] optimizer torch.optim.SGD([ {params: backbone_params, lr: 1e-4}, {params: fc_params, lr: 1e-3} ], momentum0.9, weight_decay1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.1) best_val_acc 0.0 for epoch in range(30): 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) 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) val_acc correct / total print(fEpoch {epoch1}: loss{running_loss/len(train_dataset):.4f}, fval_acc{val_acc:.4f}) if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pth) scheduler.step()超参数选择基于 13,000 张图像和 14 类划分的场景整理成表格更好对照修改参数取值说明输入尺寸224×224ResNet50 默认输入配合预训练权重batch_size32单卡 11GB 显存可跑显存小就降到 16backbone 学习率1e-4微调旧特征太高会破坏预训练知识fc 层学习率1e-3新分类头从零学稍高收敛更快weight_decay1e-4缓解过拟合五作物分类任务够用schedulerStepLR, step10, gamma0.1每 10 个 epoch 学习率降为原来的 1/10总 epoch30配合早停实际 15-20 轮左右收敛SGD 加 momentum 在这种中等规模数据集上比 Adam 更容易收敛到更平坦的极小值验证准确率通常高 1-2 个百分点。weight_decay 用 1e-4 而不是 Adam 常用的 1e-5因为叶片病害图像纹理细节多模型很容易记住病斑周围的纹理噪声适当的 L2 约束能压住这一倾向。代码里保存模型只认 val_acc 最高的那一轮而不是最后一轮的权重。训练后期损失下降变慢验证准确率可能已经开始回落直接用最后一轮权重往往比最佳权重低 3% 甚至更多。这个习惯我在很多项目里都吃过亏所以现在不管任务大小都是边训边存。4. 模型评估混淆矩阵、分类报告与逐类别阈值设定训练结束只是拿到了模型权重真正能说明问题的是评估环节。农业图像分类里最容易被忽视的恰恰是对结果的分析总准确率上去了不代表每个类别都可靠。五作物 14 类中有些类别视觉特征高度相似比如马铃薯早疫病和晚疫病早期症状都是叶片上出现褐色斑点不做混淆矩阵分析根本发现不了模型在哪些地方翻车。4.1 用混淆矩阵定位易混类别在测试集上跑一遍完整推理收集所有预测结果和真实标签然后用 sklearn 直接算混淆矩阵from sklearn.metrics import confusion_matrix, classification_report import numpy as np model.load_state_dict(torch.load(best_model.pth, map_locationdevice)) model.eval() all_labels [] all_preds [] with torch.no_grad(): for images, labels in test_loader: images images.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_labels.extend(labels.cpu().numpy()) all_preds.extend(preds.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵行真实类别列预测类别) print(np.array2string(cm, max_line_width120))混淆矩阵的行代表真实类别列代表预测类别对角线越集中说明分类越可靠。拿到矩阵后不要只扫一眼对角线要重点看非对角线上的大数字如果马铃薯早疫病被大量预测成晚疫病说明这两个类在特征空间里距离太近单靠视觉信息很难区分。这时候的补救措施不是继续加训练轮次而是回到数据层面检查这两个类别的样本质量或者考虑把两个难以区分的类别合并成一个“马铃薯疫病类”语义上虽然粗一些但对田间用药决策来说可能已经够用。另外一个值得看的维度是每类的样本量在混淆矩阵里的占比。用 np.sum(cm, axis1) 算出每行总数再用对角线除以行总数得到每类召回率这样才能发现样本量小的类别是不是被样本量大的类别压住了。4.2 分类报告与逐类别指标解读classification_report 输出的指标更直观这里给一个测试集上的示例格式类别precisionrecallf1-score测试样本数Maize_Healthy0.960.970.96128Maize_Leaf_Spot0.910.880.89121Maize_Rust0.940.950.94117Potato_Early_Blight0.820.790.8096Potato_Late_Blight0.840.810.82102Rice_Blast0.890.920.90110Wheat_Leaf_Rust0.900.870.88105注意 precision、recall、f1-score 这三列的差异。precision 低说明预测为该类别的图像里混了很多其他类recall 低说明该类别的图像被漏判了很多。如果某个类别两者都低检查该类别的训练样本是否存在大量背景干扰。此时还应该做一件事调整分类阈值。默认情况下模型预测取 softmax 输出的最大值但对样本不平衡的类别可以给每一类设定独立的置信度阈值。比如马铃薯早疫病的可靠样本只有 96 张模型对它的平均置信度天然偏低把阈值从默认的 top-1 改成“置信度超过 0.5 才输出该类别否则标记为不确定”虽然会降低硬准确率但能显著减少误判给另一个病害类别的风险。这个操作在需要下发到田间做辅助诊断的场景里非常有价值宁可让模型说“我看不准”也不要让它给出一个高置信度的错误答案。5. 常见问题与排查五条能救场的踩坑记录这一部分我把实际做这类数据集时遇到的五个高频问题整理出来每个都按现象、原因、解决的顺序写。这五条每一句都是真实翻车换来的建议先对照自己的流程检查一遍再开始训练。5.1 数据侧来自同一植株的叶片同时进了训练集和验证集现象训练时验证准确率一路飙到 0.98你满怀信心地把模型部署到田间发现实际识别准确率连 0.7 都不到。原因数据划分时没有考虑图像来源。同一个植株的不同叶片或者同一块地里同一天拍的照片光照、土壤背景、叶片姿态几乎一致如果它们随机分布到训练集和验证集模型等于直接背答案验证指标是假的。这个现象在农业数据集里太常见了因为采集时通常是对着同一植株拍很多张。解决划分时必须按植株 ID 或者拍摄批次作为分组单位来分层划分保证同一个源的图像要么全部进训练集要么全部进验证集。5.2 数据侧类别不平衡让总准确率变成障眼法现象整体准确率 0.93看起来不错但看混淆矩阵发现甘蔗赤腐病的召回率只有 0.55三分之一样本被分到了健康类。原因甘蔗类别总数少训练时模型对它的学习不充分而甘蔗健康样本多只要把所有样本都预测成健康总准确率也能维持在一个好看的数值上。解决第一个手段是统计每个类别的样本数对样本少的类别在 DataLoader 里做加权采样让模型每个 epoch 看到各类的次数相对均衡第二个手段是配合第 6 章的 Focal Loss 使用它能让模型把注意力放在难分类的少数类别上。5.3 数据侧标注噪声集中在早期病害和健康叶片的边界上现象验证集上健康类别的 precision 只有 0.85随机抽 20 张预测为健康的图像发现里面有几张其实是病害早期症状。原因叶片病害早期症状本身就淡标注员容易出现前后标准不一致同样一张有少量斑点叶片一个人标健康另一个人标病害初期。这是人工标注数据集的通病不是代码问题。解决训练前用置信学习工具自动筛查可疑样本比如先用模型预测所有训练集样本找出模型置信度高但标注不同的图像再让有经验的人复核这些边界样本。实际操作中这类边界修复通常能带来 2-4 个百分点的 f1 提升。5.4 训练侧验证损失先降后升模型记住的是背景而不是病斑现象训练 loss 持续下降验证 loss 在某个 epoch 后反弹验证准确率停滞在 0.85 左右上不去。原因训练轮次过多导致过拟合模型开始记忆叶片背景里的土壤纹理、拍摄环境等与病害无关的特征。这是 13,000 张规模数据集最容易踩的坑标标准准的过拟合信号。解决先确认第 3 章代码里的 best_val_acc 保存逻辑在工作确保拿到的是最佳轮次权重而非最后一轮其次增加数据增强强度比如给 ColorJitter 的 brightness 调高到 0.4让背景颜色变化更剧烈最后把 weight_decay 从 1e-4 调到 5e-4 再试一轮。如果验证准确率还在 0.85 附近不动大概率不是过拟合问题而是某些类别本身难以区分回到 4.1 看混淆矩阵找问题类别。5.5 训练侧学习率设置不当导致损失震荡现象训练 loss 在 1.2 附近反复横跳怎么都降不下去验证准确率在 0.6 到 0.7 之间剧烈波动。原因backbone 的迁移学习初始学习率开太高了。预训练权重已经学得很充分对它们用 1e-3 的学习率相当于在已经收敛的平面上乱跳SGD 的动量还会放大这种震荡。解决把 backbone 参数的学习率降到 5e-5 或 1e-5fc 层保持 1e-3 不变同时给优化器加一个 warm-up前 2 个 epoch 学习率从 0 线性升到目标值。在 torchvision 里实现 warm-up 需要自己做调度器包装但效果是立竿见影的特别是对甘蔗这类样本少的作物训练曲线会立刻稳下来。6. 进阶技巧Focal Loss、测试时增强与注意力可视化训练收敛之后如果你希望模型质量再上一个台阶常规的加数据、调参已经接近天花板这时候该考虑的是三个针对性技巧。第一个是 Focal Loss。4.2 里提过类别不平衡和难例并存时CrossEntropyLoss 对已分类正确的简单样本贡献了太多梯度Focal Loss 通过调制因子压低简单样本的权重把梯度让给难分类样本class FocalLoss(nn.Module): def __init__(self, alpha0.25, gamma2.0): super().__init__() self.alpha alpha self.gamma gamma def forward(self, inputs, targets): ce_loss nn.functional.cross_entropy(inputs, targets, reductionnone) pt torch.exp(-ce_loss) loss self.alpha * (1 - pt) ** self.gamma * ce_loss return loss.mean()alpha 控制正负样本权重比例gamma 控制难易样本的调制强度gamma 越大对难样本的注意力越强一般取 2.0 起步。这个损失在甘蔗赤腐病这种少数类上通常能拉高 5 个百分点以上的召回率但要注意 gamma 过大会让模型对噪声标签更敏感配合 5.3 的置信学习清洗后使用更稳。第二个技巧是测试时增强即推理时把同一张图做多次变换后取平均预测。常见做法是把水平翻转后的预测结果和原图预测结果做平均代码实现很简单def predict_tta(model, image): output model(image.unsqueeze(0).to(device)) flipped torch.flip(image, dims[2]) output model(flipped.unsqueeze(0).to(device)) return torch.softmax(output, dim1).mean(dim0)TTA 对验证集准确率的提升通常只有 0.5-1%但换来的是单张图像预测的稳定性提升这对农业现场采集到的模糊图像很有实际意义。第三个是注意力可视化。用 Grad-CAM 输出模型聚焦区域贴到原始叶片图上能直观确认模型看的是病斑而不是叶片边缘或背景土壤。我在做这个五作物数据集时检查过一次发现部分小麦条锈的预测是靠着叶片边缘纹理判出来的而不是锈病孢子堆区域。定位到这个问题后我在增强里增加了 RandomErasing 和 Cutout强制模型不能只依赖某一小块局部特征。从那以后每次做完一轮训练我都会强制走一遍混淆矩阵、类别阈值、Grad-CAM 三个步骤再定稿权重这个习惯替我省掉了至少三次实测返工。需要这份数据集跑通整个流程的话按标题搜完整名称就能找到下载地址解压后先按第 2 章把目录结构对一遍再对照第 3 章的参数开训有问题随时回来对第 5 章的清单排查。希望帮到你。本文还有配套的精品资源点击获取
返回列表