简介面向农作物病虫害检测与图像分类任务这份资源提供了一套覆盖腰果、木薯、玉米、番茄等作物的真实场景图片分类数据集共1000张高质量图像包含22个类别涉及炭疽病、胶病、叶斑病、叶枯病、条纹病毒病等常见病虫害以及对应健康状态。所有图片按类别建立文件夹标注质量高可直接作为YOLOCLS等图像分类算法的训练数据省去手动整理标签的过程。随附的YOLO11cls一键训练脚本和博主训练结果日志能够帮助读者快速跑通训练流程、对比分类效果适合实际项目快速验证。资源包为1个PDF文件大小约5.63MB内附数据集详细介绍和百度网盘获取方式便于下载原图数据。目前已有63人浏览学习整体内容紧凑实用适合农业AI项目开发者、目标检测与分类算法学习者作为数据补充与练手素材能显著减少数据集准备环节的时间成本聚焦模型迭代和应用验证。1. 目标分类起步1000张农作物病虫害图值得用YOLO11cls跑一遍看到“YOLO11cls 农作物病虫害分类数据集”这个组合第一反应是这不就是图像分类里最快落地的那条路吗1000张图不算多把分类文件夹整理好用 YOLO11cls 的预训练权重做迁移学习半小时左右就能拿到一个能看 top1 的模型。它解决的核心问题是在不想做目标检测框标注、只想快速验证“这批病斑到底能不能用分类模型分清”的时候给你一条低门槛路径。适合刚入坑分类任务的工程新手也适合需要做技术预研的团队。整个流程就三件事整理文件夹、跑训练脚本、看验证结果。2. 分类文件夹整理把散图变成 YOLO11cls 能直接吃下的 ImageFolder 结构2.1 YOLO11cls 不吃标注文件它吃的是目录结构做目标检测时你得给每张图配一个 txt 标注框坐标做分割时还要加多边形点集。但 YOLO11cls 的分类分支不认这些它只认 ImageFolder 约定数据集根目录下分 train、val、test 三个大目录每个大目录下再按类别建子目录子目录里放对应类别的图片。Ultralytics 内部会自动扫描子目录把文件夹名按字典序排序后映射成 0、1、2… 的类别索引。这意味着磁盘上的文件夹名字就是最终标签来源你在配置里写的 names 只影响日志展示不影响真实标签映射。这个设计让数据集整理变得极轻量不需要写标注文件重命名目录就等于改标签对 1000 张这种规模的数据集非常友好。同时它也不绑定 YOLO 专属格式今天用 YOLO11cls 跑明天想换 ResNet、ViT 一样能直接喂进去。2.2 目录结构、命名规范与批量检查脚本我一般建议先在 datasets 下建一个独立的项目目录避免和原始下载图混在一起。目录骨架是这样的datasets/ └── pest_cls/ ├── train/ │ ├── tomato_late_blight/ │ ├── tomato_early_blight/ │ ├── tomato_leaf_mold/ │ └── ... ├── val/ └── test/类别目录名统一用英文小写加下划线不要用中文、空格、括号。我在实际项目里踩过中文目录名的坑部分 Linux 环境下 YAML 解析直接出乱码TensorBoard 和 wandb 里类别显示也全乱排错成本远比重命名一次高。图片文件也建议重命名成“类别英文名_序号.jpg”的格式因为从搜索引擎或微信收集来的原文件名经常带特殊字符训练时日志打印容易刷屏。重命名和目录整理可以用一段 Python 脚本批量完成import os import shutil from pathlib import Path src_root Path(raw_images) # 原始图片里面按中文类别分了文件夹 dst_root Path(datasets/pest_cls) for src_cls_dir in src_root.iterdir(): if not src_cls_dir.is_dir(): continue # 中文类名转英文代号建立映射字典下面只是示例 cls_map {番茄晚疫病: tomato_late_blight, 番茄早疫病: tomato_early_blight} cls_name cls_map.get(src_cls_dir.name) if cls_name is None: print(f跳过未映射类别: {src_cls_dir.name}) continue for i, img_path in enumerate(src_cls_dir.glob(*.jpg)): new_name f{cls_name}_{i:04d}.jpg dst_dir dst_root / train / cls_name dst_dir.mkdir(parentsTrue, exist_okTrue) shutil.copy2(img_path, dst_dir / new_name) # copy2 保留拍摄时间等元数据这段代码的作用是把原始目录中的中文类名映射成英文并把图片按新命名规则复制到 train 目录。copy2 比 copy 多保留文件的元数据对后续追溯图片来源有用。映射表需要你自己维护这是整理环节唯一不能全自动的部分。脚本跑完后用下面的检查脚本过一遍所有图片from PIL import Image from pathlib import Path import json root Path(datasets/pest_cls) min_per_class 10 # 每个类别最少样本数低于这个值直接告警 report {} for split in [train, val, test]: for cls_dir in (root / split).iterdir(): if not cls_dir.is_dir(): continue ok 0 bad [] for img_path in cls_dir.glob(*.*): try: im Image.open(img_path) im.verify() # 只校验文件完整性不做像素解码 ok 1 except Exception as e: bad.append(img_path.name) report[f{split}/{cls_dir.name}] {ok: ok, bad: bad} print(json.dumps(report, indent2, ensure_asciiFalse))verify() 只校验文件格式头和解码结构速度很快1000 张图几秒跑完。它能发现 0 字节文件、截断的 JPEG、伪装成 jpg 的其他格式文件。对检查出的坏图我建议移到 quarantine 目录而不是直接删除万一后面发现是批量修复能解决的编码问题还有后悔药能吃。2.3 划分脚本固定种子、保留类别比例、只复制不移动整理好 train 目录后要把它按比例拆成 train、val、test 三份。很多教程只做 train/val 二划分但我强烈建议多留一个 test原因后面章节会细说。划分脚本代码如下import random import shutil from pathlib import Path src Path(datasets/pest_cls/train) dst Path(datasets/pest_cls) val_ratio, test_ratio 0.1, 0.1 random.seed(42) # 固定种子保证每次划分结果一致 for cls_dir in src.iterdir(): if not cls_dir.is_dir(): continue images list(cls_dir.glob(*.jpg)) random.shuffle(images) n_val int(len(images) * val_ratio) n_test int(len(images) * test_ratio) for img in images[:n_val]: target dst / val / cls_dir.name / img.name target.parent.mkdir(parentsTrue, exist_okTrue) shutil.copy2(img, target) for img in images[n_val:n_val n_test]: target dst / test / cls_dir.name / img.name target.parent.mkdir(parentsTrue, exist_okTrue) shutil.copy2(img, target) # 剩下的留在 train 目录不用动固定 seed42 保证了重跑脚本时划分结果完全一致这是做实验对比的前提。我特意用 copy2 而不是 move 或 os.rename原因很现实1000 张图复制也就几十秒但 move 会把原目录清空一旦你后面要调整类别映射或重新划分原始数据已经没了只能重新收集。test 比例设 0.1 意味着每类大约留十几张做最终验证对小数据集来说足够评估趋势但不足以做精细的阈值调优。划分完成后打印一下每类 train/val/test 数量确认没有哪个类别在 val 里只剩下两三张。遇到过 val 某类只有 1 张图的情况训练时 loss 正常验证时 top1 忽高忽低很难判断是模型问题还是验证集噪声。3. 用 YOLO11cls 在本地跑通最小训练命令、YAML 与参数边界3.1 环境安装与版本确认YOLO11cls 是 Ultralytics 框架里的分类分支命令行入口和检测、分割共用同一个 yolo 命令。安装只需要 pip 装一个包pip install ultralytics装完后确认一下 torch 相关环境是否就绪python -c import torch, ultralytics; print(torch.__version__, ultralytics.__version__)如果 torch 还没装建议先装官方推荐的 CPU 版或 CUDA 版不要在装完 ultralytics 后发现 torch 缺失再绕一圈。Ultralytics 版本迭代很快旧版本可能没有 yolo11cls 权重入口如果后面命令报错说模型不存在优先把 ultralytics 升级到最新版再排查。3.2 data.yaml 怎么写YOLO11cls 训练时需要一个数据配置文件指向我们刚才整理的目录。写法非常简单path: ../datasets/pest_cls train: train val: val test: test names: 0: tomato_late_blight 1: tomato_early_blight 2: tomato_leaf_mold这里 path 是相对于 YAML 文件所在目录的路径。如果你把 yaml 放在项目根目录而数据集在 datasets 下那 path 写 ../datasets/pest_cls 是常见做法。train、val、test 三个字段填的是相对于 path 的子目录名。有个容易搞混的点在 classify 任务里names 不是必需的。你删掉 names 字段Ultralytics 也会自动按文件夹字典序生成类别名。如果手动写了 names必须保证顺序和文件夹名排序一致否则日志里显示的是你的 names实际标签还是按文件夹排序两边错位会让你误判模型性能。我自己用下来小项目干脆不写 names让系统按文件夹名走避免出错。3.3 最小训练命令与关键参数边界数据准备到位后训练一条命令就能跑起来yolo classify train modelyolo11cls.pt datapest_cls.yaml epochs50 imgsz224 batch16modelyolo11cls.pt 表示加载 YOLO11 分类预训练权重做迁移学习。注意这里有个大坑如果你把 model 参数写成 yolo11cls.yaml那是从随机初始化开始搭建网络而不是加载预训练权重。1000 张图的小数据集从零训练几乎不可能收敛到可用精度迁移学习是必须的。epochs50 是小数据集的起步值不要贪多跑完先看趋势再决定要不要续训。imgsz224 是分类任务的默认输入分辨率YOLO11cls 内部会做缩放和归一化不需要你提前改图。batch16 取决于显存大小8GB 显卡跑 224 分辨率基本是上限。如果显存不足优先把 batch 降到 8不要再低因为小数据集本身梯度噪声大batch 太小会让 loss 曲线抖动严重。训练完成后终端会打印 best.pt 和 last.pt 的保存路径并在验证集上输出 top1_acc 指标。训练完想快速验证单张图用 predict 命令yolo classify predict modelruns/classify/train/weights/best.pt sourcedatasets/pest_cls/test/tomato_late_blight/0001.jpg输出会打印预测类别和置信度。我第一次跑通时习惯先把测试集里每个类抽 5 张图分别预测一遍用肉眼确认模型不是在瞎猜再做后续指标分析。4. 一键训练脚本把划分、校验、训练、验证打包成可重复流水线4.1 脚本主流程设计手工敲命令适合第一次跑通流程但后面换参数、重新跑实验时,很容易出现“上一步 s脚本要做的是把第 2 章的整理校验和第 3 章的启动训练串成一个 pipeline以后每次只改顶部参数配置一条命令出结果。我的脚本骨架如下#!/bin/bash set -e # 可调参数区 EPOCHS50 IMGSZ224 BATCH16 DATA_YAMLpest_cls.yaml MODEL_BASEyolo11cls.pt SEED42 # 1. 重新划分数据集固定 seed幂等执行 python split_dataset.py --seed $SEED # 2. 检查所有图片完整性有坏图直接终止 python check_images.py --min-per-class 10 # 3. 备份训练产物防止覆盖上一轮结果 BACKUP_DIRbackup/$(date %Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR # 4. 启动训练 yolo classify train \ model$MODEL_BASE \ data$DATA_YAML \ epochs$EPOCHS \ imgsz$IMGSZ \ batch$BATCH \ seed$SEED \ cos_lrTrue \ label_smooth0.05 # 5. 训练完成后把 best 权重复制到带时间戳的备份目录 BEST_WEIGHTruns/classify/train/weights/best.pt if [ -f $BEST_WEIGHT ]; then cp $BEST_WEIGHT $BACKUP_DIR/best_$EPOCHS.pt echo best weight saved to $BACKUP_DIR fiset -e 的作用是只要中间任何一步命令返回非零退出码整个脚本立刻终止。这个约束在自动化训练场景里非常重要数据校验没通过就继续训练只会浪费几小时算力最后还得回头查数据。第 3 步的备份目录是血泪教训换来的Ultralytics 每次训练都会往 runs/classify/train 写入结果新的一轮会把旧的覆盖掉如果不提前备份上一轮表现最好的权重可能再也找不回来。4.2 需要调的参数与显存边界一键脚本里的参数不是设完就不动了。真正影响小数据集效果的参数有这几项整理成表方便对照参数建议范围说明epochs30~10050 起步看 val loss 是否还有下降趋势imgsz224分类任务默认值小数据集不建议放大batch8~328GB 显存上限约 16太小梯度噪声大lr00.001~0.005迁移学习建议低于默认 0.01dropout0.2~0.5数据量小适当 dropout 缓解过拟合label_smooth0.05~0.1防止模型对 1000 张图过于自信cos_lrTrue让学习率余弦衰减后期 loss 更稳定这里单说 lr0。很多人直接沿用 Ultralytics 默认值 0.01但这个值对迁移学习场景偏高。加载预训练权重后backbone 特征已经比较稳定过大的学习率会把学好的特征破坏掉。我一般用 0.005 起步如果发现 val loss 前 10 轮就冲高不回落直接砍到 0.001 重跑。dropout 和 label_smooth 是防止过拟合的两个互补手段1000 张图训练 50 轮模型容量完全足够把所有训练样本背下来不加正则项的话 val acc 和 train acc 会拉开明显差距。batch 的边界判断不只看显存还看类别数量。如果类别数超过 10 个batch8 时每个 batch 里可能覆盖不到所有类别梯度方向偏差很大。宁可把 imgsz 降到 160也要保证 batch 不小于 8。用 yolo11n-cls.pt 这种轻量级权重也能缓解显存压力效果不一定比大模型差。4.3 中断续训与回滚训练到一半断电、OOM、机房重启都是常见事故。Ultralytics 支持断点续训一条命令搞定yolo classify train modelruns/classify/train/weights/last.pt resumeTrueresumeTrue 会继承上次训练的 epochs、imgsz 等全部参数从 last.pt 继续跑。但这个机制只对同一台机器、同一个 runs 目录有效。如果你想在另一台机器上续训只复制 last.pt 不够还得带上 args.yaml 文件。这又是一个隐藏坑runs/classify/train 目录下有个 args.yaml 记录了本次训练的全部超参数续训时它会优先于命令行参数生效。所以在脚本里我一直保留“备份整个 train 目录”的习惯而不是只备份权重文件。最稳妥的备份命令是cp -r runs/classify/train $BACKUP_DIR/train_dir这个备份能在模型翻车时完整回滚到任意历史版本包括当时用的超参数、数据划分、类别映射。数据整理到这一步整套流程已经可以重复使用了。5. 农作物病虫害分类的 5 个高频翻车现场避坑排查记录5.1 文件夹名带中文导致类别错位现象训练能启动loss 正常下降但验证集 top1 异常低或者日志里显示的类名和实际图片对不上。原因Ultralytics 按文件夹名字典序生成类别索引中文目录名在不同操作系统下的排序结果不一致且部分环境对非 ASCII 路径解析会出问题。即使能跑TensorBoard 里也会出现乱码干扰人工判断。解决统一转成英文小写加下划线。批量重命名脚本不复杂关键是把映射表维护好一次到位不要反复改from pathlib import Path root Path(datasets/pest_cls/train) cls_map {番茄晚疫病: tomato_late_blight} for old_name, new_name in cls_map.items(): old_dir root / old_name if old_dir.exists(): old_dir.rename(root / new_name)5.2 一张坏图让训练中断在第 40 轮现象训练跑到一半抛 RuntimeError提示某个图片解码失败重新运行后崩的位置可能又不同断点续训也只能续一段再崩。原因数据集中混入了 0 字节文件、非标准 JPEG 编码或截断下载的图片。PyTorch DataLoader 流式读取时才会触发解码训练前的整体扫描不一定能发现。解决用第 2 章的 check_images.py 在训练前全量验证一遍把坏图移到 quarantine 目录。注意 verify() 只校验文件结构不校验颜色空间和像素内容如果你的图里有灰度图、RGBA 图混用训练时虽然不报错但模型特征分布会受影响。可以在校验脚本里统一转换为 RGB 并覆盖保存。5.3 1000 张图从零训练导致 top1 只有三成现象50 轮训练完验证集 top1 在 30%~45% 之间波动怎么看都像个随机猜测模型。原因model 参数写成了 yolo11cls.yaml 而不是 yolo11cls.pt。前者是随机初始化网络结构后者是加载预训练权重。1000 张图根本不够深层网络从零学会通用特征所有病斑细节都得从头学数据量差两三个数量级。解决切换回 modelyolo11cls.pt并可选加 freeze10 冻结 backbone 前 10 层只训练后层。冻结参数减少训练显存占用也能防止小数据把预训练特征破坏掉。如果 freeze 后训练 loss 降不下去再逐层解冻重训。5.4 验证集 top1 达到 92%一到田间新图就翻车现象val 和 test 都是自己随机划分的准确率很高拿手机在田间新拍几张图放进去预测结果完全不对。原因随机划分导致 train、val、test 同源同分布三者的背景、光照、拍摄设备都一致。模型真正学到的是“这张图来自训练集那种环境”而不是病斑本身的纹理特征。解决按采集批次划分而不是逐张随机划分。把同一时间、同一地块、同一设备拍摄的照片整体放进同一个 split。具体做法是先把文件名按采集批次加前缀再按前缀分组切分。5.5 类别不均衡让少数类被模型吞掉现象总共 1000 张图某个类只有 20 张训练完这个类的召回率接近 0混淆矩阵里对应的一行几乎全黑。原因分类损失被大头类别主导模型把所有样本都预测成高频类也能拿到很低的 loss。解决最直接的做法是给少数类补样本。如果补不了把少数类图片在 train 目录里复制 2~3 份注意只能复制 train不能复制到 val 和 test否则验证分数虚高。复制增强虽然简单粗暴但在 1000 张这种量级上非常有效。更好的方向是把视觉上相似的少数类合并成一个大类以降低分类难度。6. 从「能跑」到「可信」混淆矩阵、续训与类别均衡6.1 用混淆矩阵替代单点 top1top1 只是一个数字它不会告诉你哪些类别容易被搞混。我会在训练完用 best.pt 对整个 test 目录跑一批预测生成混淆矩阵from ultralytics import YOLO from pathlib import Path from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt model YOLO(runs/classify/train/weights/best.pt) test_root Path(datasets/pest_cls/test) class_names sorted([p.name for p in test_root.iterdir() if p.is_dir()]) y_true, y_pred [], [] for cls_name in class_names: for img_path in (test_root / cls_name).glob(*.jpg): res model.predict(str(img_path), verboseFalse)[0] y_true.append(cls_name) y_pred.append(class_names[res.probs.top1]) cm confusion_matrix(y_true, y_pred, labelsclass_names) ConfusionMatrixDisplay(cm, display_labelsclass_names).plot() plt.xticks(rotation45) plt.tight_layout() plt.savefig(confusion_matrix.png)拿到混淆矩阵后重点看对角线之外的高值格子。两个类长期互相误判通常不是 epochs 不够而是这两个病的病斑视觉特征本身接近。这时候加训练轮次收益很低不如合并类别或者补拍样本。6.2 续训而不是重跑前面脚本里已经跑了 50 轮如果混淆矩阵显示整体趋势还行只是欠拟合接着跑 20 轮比重新开始更有价值。yolo classify train \ modelruns/classify/train/weights/best.pt \ datapest_cls.yaml \ epochs20 \ imgsz224 \ batch16 \ lr00.001把 model 指向上一轮的 best.pt同时把学习率降到 0.001这是续训的标准操作。Ultralytics 会把它当作一次新的训练写入 runs/classify/train2不会覆盖上一轮结果方便做 A/B 对比。我自己做这套流程时翻过一个大车第一次跑完 100 轮看到 top1 不错就收工了后来被田间现场图打脸退回来看混淆矩阵才发现两个病害在边缘光照下几乎一样。现在我的习惯是先跑 50 轮出矩阵再做 20 轮针对性续训样本不够就去补而不是闷头加 epochs。希望帮到你。本文还有配套的精品资源点击获取