ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

1000张图也能训出能用的病虫害分类模型?YOLO11cls小样本实战指南

1000张图也能训出能用的病虫害分类模型?YOLO11cls小样本实战指南 简介面向使用YOLO11cls开展农作物病虫害图像分类的开发者与学习者这份PDF资源提供了真实场景图片数据集的结构说明与获取渠道。包内虽仅1个PDF文件5.63MB但它对应1000张已按分类文件夹整理的作物图片涵盖腰果、木薯、玉米、番茄四大类作物下共22种状态包含炭疽病、细菌性枯萎病、草地贪夜蛾、叶斑病、花叶病等典型病虫害样本所有图像按类别文件夹存放标注质量较高可直接用于YOLO11cls等分类任务。文档同步介绍了配套的YOLO11cls一键训练脚本及博主训练结果日志便于快速搭建训练流程、对比实际效果。由于图片数据集体量较大整体托管在百度网盘PDF内附有数据集基本介绍与获取凭证下载后先读说明即可按指引取得完整图片及脚本。目前已有63人浏览学习适合正在做农作物病虫害检测项目或希望补充通用分类数据集场景的读者。1. 只有1000张图的病虫害分类项目凭什么能训出能用的模型如果你做过农作物病虫害识别一定体会过那种「找图两小时标注一整天」的滋味。这份项目标题给的是个非常务实的组合目标分类——农作物病虫害分类数据集1000张图按分类文件夹整理好外加一个YOLO11cls一键训练脚本。它解决的并不是「从零研发识别算法」这种大问题而是很多入门者和农业从业者真正卡住的地方手上只有少量图片、不知道该按什么结构放、不知道怎么把数据喂给模型。1000张图放在深度学习里确实不算多但只要你用对目录结构、用对预训练权重、把训练参数调到符合小样本的节奏这套流程足够让你在半天内拿到一个能区分几种常见病虫害的可用模型而不是模型代码和数据集各躺各的、根本跑不起来。这套方案适合谁适合刚走通YOLO系列目标检测、现在想转图像分类的开发者也适合农学背景、有几千张田间照片但不会写训练流程的研究生。它的核心思路很朴素数据集按分类文件夹整理YOLO11cls负责从文件夹直接学类别特征一键训练脚本把环境确认、数据校验、参数配置、训练启动和结果落盘串成一条命令。后文我会把目录整理、脚本参数、小样本调参和常见翻车点逐项拆开每一段你都能直接照着做。2. 把1000张图变成YOLO11cls认识的目录文件夹整理与数据校验2.1 ImageNet风格目录是YOLO11cls的默认食物目录树与data.yamlYOLO11cls虽然是YOLO系列的新成员但它的分类模式沿用的是最经典的ImageNet目录组织方式数据根目录下分train和val两个文件夹每个类别一个子文件夹图片直接放在类别文件夹里。模型训练时不是读一个标注文件而是根据图片落在哪个文件夹来认定它的类别。这意味着「分类文件夹整理」不是可有可无的步骤而是YOLO11cls能否工作的前提条件。我建议最终目录结构固定成下面这样类名用英文小写加下划线避免中文路径和空格带来的编码问题。crop_pest_dataset/ ├── data.yaml ├── train/ │ ├── healthy/ │ │ ├── 001.jpg │ │ └── ... │ ├── leaf_spot/ │ │ ├── 001.jpg │ │ └── ... │ ├── powdery_mildew/ │ │ ├── 001.jpg │ │ └── ... │ └── leaf_curl/ │ ├── 001.jpg │ └── ... └── val/ ├── healthy/ ├── leaf_spot/ ├── powdery_mildew/ └── leaf_curl/data.yaml是给YOLO11cls喂数据路径的配置文件内容很简单但path字段要特别注意最好写相对路径也就是相对你执行训练命令时所在目录的路径。很多人在自己电脑上跑通了把数据集拷到服务器上就报错Dataset not found八成就是path写死了绝对路径。path: crop_pest_dataset train: train val: val names: 0: healthy 1: leaf_spot 2: powdery_mildew 3: leaf_curl这里names的序号其实不需要严格对应文件夹名模型训练时ultralytics会扫描train目录下的子文件夹按名称排序自动生成类别索引。但建议手动写names并保持顺序一致一方面是为了后面推理时能直观对应类别名另一方面是防止某些环境下系统排序规则不同导致类别顺序漂移。2.2 从散图到分类文件夹两个脚本快速落地大多数人手上的图片不是规规矩矩按类别分好的而是散在一堆文件夹里文件名也千奇百怪。那么第一步就是把散图按类别归拢。常见做法是维护一个CSV清单每一行写图片路径,类别然后用脚本批量拷贝。import csv import shutil from pathlib import Path csv_file label_list.csv src_root Path(./raw_images) dst_root Path(./crop_pest_dataset) with open(csv_file, encodingutf-8) as f: for row in csv.reader(f): img_rel, label row[0].strip(), row[1].strip() src src_root / img_rel dst dst_root / train / label / src.name dst.parent.mkdir(parentsTrue, exist_okTrue) shutil.copy2(src, dst)这段脚本做的事情很直接从CSV里读图片相对路径和类别名按类别创建目录再把图片复制进去。我故意用copy2而不是move因为原始图可能还有别的用途复制一份训练集更安全。如果你确定原始图不再需要改成shutil.move能省一半磁盘空间。类别怎么定、定几个是一个需要你提前想清楚的问题。1000张图如果分10个类每个类只有100张很多类会很难训如果你先只做二分类「有病/无病」或者聚焦在你们当地最常见的3~4种病虫害效果会稳定得多。整理数据时顺手跑一下数量统计让心里有数find crop_pest_dataset/train -type f | cut -d/ -f3 | sort | uniq -c这就是个标准的管道命令先列出train下所有文件取第三个字段也就是类别文件夹名再排序去重统计。看到某个类别只有二三十张图你就该考虑是不是要把这个类合并到「其他」里或者去补数据。2.3 切块扩样暴增训练集让1000张图变成3000张图1000张图做分类并不算充裕尤其是当原始图片是1920×1080甚至更高分辨率的田间照片时。病虫害的区域往往只占画面的一小部分如果整张图直接缩到imgsz224病斑特征会被压缩得几乎看不见。这时候有两个选择一是先做目标检测把叶片框出来再对叶片做分类这是两步方案的思路二是在数据集整理阶段把大图按网格切块只保留包含叶片或病斑的小块变相扩充样本量。from PIL import Image from pathlib import Path img_path Path(./raw_images/leaf_spot_001.jpg) img Image.open(img_path) w, h img.size crop_size 512 overlap 64 out_dir Path(./cropped) step crop_size - overlap for i, top in enumerate(range(0, h - crop_size 1, step)): for j, left in enumerate(range(0, w - crop_size 1, step)): box (left, top, left crop_size, top crop_size) crop img.crop(box) crop.save(out_dir / fleaf_spot_001_{i}_{j}.jpg)切块时我用了一个overlap64的滑窗步长这样相邻图块之间有重叠一张大图能产出几十个训练块。切完之后记得把明显没有叶片内容的纯土块、纯天空块删掉这个筛选动作如果人工做太累可以写个简单脚本按颜色方差过滤——方差太低的块基本是纯色背景直接丢弃。1000张原图按这种方式切到3000~4000个训练块很常见训练数据的有效信息量会明显提升。2.4 数据分布检查在你跑训练之前先让脚本自己体检图片整理完成不代表可以直接开训。我见过太多人第一次训练失败是因为数据集里混入了损坏图片、或者某些图其实是PNG带透明通道、有些是灰度图。YOLO11cls底层用的图像库大多能处理这些情况但一旦某张图解码失败训练进程会在跑了几个epoch之后突然崩溃报错信息还很抽象。所以在一键训练脚本里第一步应该先做数据体检。from PIL import Image from pathlib import Path def check_images(root_dir): bad_files [] count 0 for p in Path(root_dir).rglob(*.jpg): count 1 try: with Image.open(p) as im: im.load() except Exception: bad_files.append(str(p)) print(f共扫描 {count} 张图片损坏 {len(bad_files)} 张) for b in bad_files: print(损坏:, b) return bad_files这个检查脚本逐个打开图片并真正加载像素数据能过滤掉「后缀是.jpg但内容已经损坏」的文件。训练之前跑一遍发现损坏图直接删掉或者重新导出比训练中途崩溃要省心得多。如果你用的是PNG把rglob(*.jpg)改成rglob(*.[jp][pn]g)这类通配就行。3. 用YOLO11cls一键训练脚本跑通第一个模型环境准备与命令参数3.1 数据集下载、放置与Path参数换机器不翻车标题里的数据集是随项目附带的放到你自己的工作目录后第一件事不是写训练脚本而是确认ultralytics环境装好了。常见的安装方式如下pip install ultralytics --index-url https://pypi.org/simple装完以后可以用一行命令验证YOLO11cls能不能正常加载python -c from ultralytics import YOLO; model YOLO(yolo11n-cls.pt); print(model.model.__class__.__name__)能打印出模型类名说明环境没问题。注意yolo11n-cls.pt是YOLO11分类模式的轻量预训练权重后缀-cls表示classification和做目标检测的yolo11n.pt不是同一个权重。第一次运行会自动把权重文件下载到用户目录下的~/.cache/ultralytics里后续训练就离线了。之前提到的data.yaml中path字段强烈建议写成相对路径。因为一键训练脚本很可能在多个机器间移动——今天在笔记本上调参明天丢到服务器上跑整夜。如果path写死成C:/Users/xxx/crop_pest_dataset或者/home/ubuntu/crop_pest_dataset换机器必报错。相对路径的值是相对于你执行yolo命令时所在的目录所以训练脚本和执行命令要放在数据集根目录的上一级保持固定相对关系。3.2 一键训练脚本本体先跑通再谈优化很多所谓「一键训练脚本」其实就是把官网命令行抄了一遍。那不是脚本那叫命令记录。真正的一键脚本至少要做三件事检查数据集是否完整、自动推导关键路径、启动训练并保存产物。下面这个脚本是我常用的一种结构#!/usr/bin/env python # train_cls.py import argparse from pathlib import Path from ultralytics import YOLO def main(): parser argparse.ArgumentParser() parser.add_argument(--data, typestr, defaultcrop_pest_dataset/data.yaml) parser.add_argument(--model, typestr, defaultyolo11s-cls.pt) parser.add_argument(--epochs, typeint, default50) parser.add_argument(--imgsz, typeint, default224) parser.add_argument(--batch, typeint, default32) parser.add_argument(--lr0, typefloat, default0.001) parser.add_argument(--device, typestr, default0) parser.add_argument(--patience, typeint, default15) args parser.parse_args() data_yaml Path(args.data) if not data_yaml.exists(): raise FileNotFoundError(f数据集配置文件不存在: {data_yaml.resolve()}) model YOLO(args.model) model.train( datastr(data_yaml), epochsargs.epochs, imgszargs.imgsz, batchargs.batch, lr0args.lr0, deviceargs.device, patienceargs.patience, projectruns/classify, namefcrop_pest_{args.imgsz}_{args.epochs}ep, ) if __name__ __main__: main()然后在命令行里一行启动python train_cls.py --data crop_pest_dataset/data.yaml --epochs 50 --batch 32这段代码的逻辑并不复杂但有几个设计考量。第一model.train的project和name参数把训练产物统一放到了runs/classify/下不会散落在当前目录里后面查看best.pt、混淆矩阵图都方便。第二把patience暴露成参数而不是写死在代码里因为早停轮数在小样本和大样本上的最佳值差异很大1000张图的数据集建议patience15loss容易波动给足耐心。第三YOLO(args.model)加载的是预训练权重如果你传yolo11s-cls.yaml这种结构文件就变成了从零训练在小数据集上效果会打折。3.3 脚本逻辑说明与5个必调参数上面的脚本核心只有一处调用model.train(...)但ultralytics的train方法内部帮你处理了学习率调度、数据加载、验证集评估、早停、权重保存这一整套流程。你不需要自己写训练循环但需要知道每个参数控制什么。下面这5个参数是1000张图这个规模下最值得调的参数默认值小样本建议调参理由lr00.010.001数据量少时梯度噪声大偏大的学习率容易反复震荡减半再试是常规操作batch1632左右显存允许的情况下适当加大batch能压低BN层的统计噪声imgsz224224或256分类任务不需要为了检测任务把图放大到640256在小目标细节和显存之间更平衡freeze08~10冻结前若干层主干参数让模型只微调高层特征强约束适合小数据patience10015小数据集过拟合来得快早停太晚val/top1_acc掉到谷底才停就太迟了其中freeze参数的坑比较隐蔽它只在加载预训练权重时生效如果你给model传的是yaml结构文件而不是.pt权重文件freeze会被静默忽略。另外freeze10的意思是冻结网络前10层对YOLO11s-cls来说大约冻结了主干网络前三分之二的卷积层剩下的层继续更新。这个小技巧能让1000张图的训练稳定性明显提升但也别冻得太多否则新类别的特征完全学不出来。4. 小样本调参不玄学损失、学习率、冻结与早停的配合4.1 从预训练权重迁移学习不要从零开始1000张图从零训练一个ResNet或者YOLO11分类头结果基本可以预料训练集准确率跳得飞快验证集一路躺平这是典型的欠拟合加过拟合并存。根本原因是数据量不足以让网络从头学到「叶片纹理」这种通用视觉特征。常见做法是加载在ImageNet上预训练好的权重把前几层当作固定的特征提取器只微调后面几层去适配病虫害分类任务。具体到YOLO11cls加载预训练权重的方法就是YOLO(yolo11s-cls.pt)。这个权重内部包含ImageNet分类任务学到的通用特征比如边缘、纹理、颜色分布。你要做的不是重新发明这些特征而是让模型知道「叶斑」和「健康叶片」在特征空间里的分界在哪里。用freeze参数冻结前几个stage相当于告诉模型通用特征你学得很好了别再动了帮我专心把最后几层分类层调好。实际操作中freeze8和freeze10差异不大但freeze0和freeze10在小样本上的收敛速度差异肉眼可见。4.2 学习率、batch与epochs三者怎么联动YOLO11cls的train方法内部自带学习率warmup和余弦退火调度理论上不需要你手动调学习率策略但初始学习率lr0的选择仍然直接影响小样本训练成败。batch越小每个step的梯度越不稳定学习率就要相应调低反过来如果显存充足把batch从32提到64学习率可以稍微放大一点点。一个我常用的参考比值是lr0 * batch / 64。也就是说如果你在batch64时用lr00.001能稳定收敛那么改成batch32时lr00.0005是一个合理的起跳点。1000张图片按batch32算每个epoch才30个step左右整个训练过程非常短。50个epoch看起来很多但实际训练时间可能只有几分钟到十几分钟这取决于你用CPU还是GPU。epochs的选择在小样本场景下比较纠结。训练集小意味着模型很容易在10个epoch内就把训练集背下来但你真正关心的是验证集准确率。我的建议是epochs设一个看起来偏大的值比如80然后靠patience15让训练自动早停最后拿best.pt而不是last.pt。这样你不需要手工反复试「到底多少epoch合适」训练到验证集指标不再提升时自己就停了。4.3 用早停和时间戳续训保护你的训练过程早停机制是ultralytics内置的每训练完一个epoch就在验证集上评估一次如果patience个epoch内最优指标没有刷新训练提前终止。对小数据集这几乎是最重要的护身符因为过拟合通常在训练中期突然出现没有早停的话最后保存的last.pt很可能已经烂掉。训练过程中途中断是常态。可能是服务器被重启也可能你手动停掉去调参。中断后不需要重新开始一行命令续训python train_cls.py --data crop_pest_dataset/data.yaml --model runs/classify/crop_pest_224_80ep/weights/last.pt续训时把model参数从预训练权重换成上次训练的last.pt脚本其余逻辑不变。ultralytics会检测到这个权重文件的训练状态自动读取之前保存的epoch、优化器状态和学习率调度位置。要注意的是续训之后脚本里project和name最好改成新的目录名避免把旧的结果文件覆盖掉。我习惯在name里加时间戳import time run_name fcrop_pest_{time.strftime(%m%d_%H%M)}这样每次续训或者调参后的结果都不会互相覆盖横向对比best.pt时也有依据。4.4 类别不平衡时权重和指标都要改1000张图如果平均分配到4个类每个类250张那是理想情况。现实往往是「健康叶片」占了600张「稻瘟病」只有100张。类别不平衡在分类任务里最直接的影响是模型倾向于把不确定的样本全部判成大类因为这种偷懒策略的总体准确率并不低。但你要的显然不是这种模型。ultralytics分类任务没有像目标检测那么完善的类别权重参数常见做法是给类别设定sample weight或者更实用一点在数据层面给少样本类别做复制扩增。比如稻瘟病只有100张可以通过水平翻转、轻度旋转、颜色抖动把它扩到300张。PIL和OpenCV都能做但要注意扩增只作用于训练集验证集保持原图。from PIL import Image, ImageEnhance src Image.open(train/rice_blast/012.jpg) dst src.transpose(Image.FLIP_LEFT_RIGHT) dst.save(train/rice_blast/012_flip.jpg) enhancer ImageEnhance.Color(src) dst enhancer.enhance(1.3) dst.save(train/rice_blast/012_color.jpg)类别不平衡还要影响你的评估方式。只看top1_acc是不够的必须看每类的precision、recall和F1。ultralytics在训练结束后会输出混淆矩阵图如果看到少样本类别的召回率特别低就说明模型在这个类上学到的特征不足扩增数据或者增加该类样本是最直接的改进方向。5. 1000张图训练翻车实录5条避坑笔记5.1 训练acc 99%但换张照片直接错模型只认了背景这是小样本分类项目里最经典的现象。某个类别的训练图片全部在水泥地上拍摄另一类全部在泥土地上拍摄模型根本不需要学叶片特征直接看背景颜色就能分类。训练准确率接近100%验证集也是高得离谱但你把模型拿到田间对新拍的叶子一测立刻现出原形。原因在于模型「抄了近道」背景特征比病斑特征更明显计算loss时更容易降低误差。解决这个问题的思路是提前掐断近道。第一种办法是在数据整理时把叶片区域尽量裁剪出来减少背景面积。第二种是加入随机背景替换或随机擦除的数据增强人为破坏背景与类别的强相关性。ultralytics里可以设置hsv_h、hsv_s、hsv_v等颜色增强参数也可以打开mixup让模型看到混合背景的样本。5.2 train和val「串图」指标虚高而不自知有些人是从视频里抽帧做数据集的相邻帧几乎一模一样。如果随机划分train和val时没有考虑图片来源很可能同一段视频的相邻帧分别跑到了训练集和验证集。验证集里全是「见过的画面」指标自然好看但模型没有任何泛化能力。实践中的解决办法是按来源分组划分而不是按单张图片随机划分。比如你有10段不同地块的视频每个视频的帧放同一个集合里去划分。如果是不同人拍摄的独立照片也要先按拍摄批次分组再划分。理想划分比例是训练集占80%~85%验证集占15%~20%并且确认验证集的每个类别都有足够数量避免某类只有两三张验证图导致评估结果剧烈波动。5.3 训练到一半显存炸了从头再来1000张图做分类按道理对显存要求不高但YOLO11cls默认的深度训练模式下如果你把imgsz调到640以上、batch也拉得很高6GB显存的卡照样能秒炸。最常见的情况是用户看网上的目标检测教程把imgsz640抄过来分类任务根本用不到这么大的输入。分类模型的输入一般224就够了你是在训练模型识别「叶子上有什么病」不是在找「叶子的边界框坐标」。解决手段有梯度累积但ultralytics的分类模式并不直接暴露accumulate参数更实用的做法是把batch减小、imgsz恢复到224。如果显存还是不够确认一下是不是开了大量线程加载图片导致CPU内存不足workers0有时候比workers8在小数据集上跑得更稳定因为省掉了进程间通信的开销。5.4 精度指标全绿单个类别却烂到底只看平均精度会掩盖很多问题。你的模型可能有四个类别其中三个类别识别得很好但最需要关注的那个病害类别识别率只有不到五成。平均准确率看起来还有八十多分实际用起来却不敢依赖。检查办法是训练结束后直接看混淆矩阵哪个类被误判成了哪个类一目了然。出现这种情况先看该类别的训练样本数是不是远少于其他类再看该类别的图片是否拍摄角度、光线条件和其他类差异过大导致模型把「光照条件」当成了类别特征。解决路径通常是扩充该类样本、增加针对性数据增强、或者干脆降低你对该类别的上线预期改为在推理时加入额外的置信度阈值过滤。5.5 resume之后loss暴涨反而白训了续训last.pt时出现loss比上一次训练结束时高出很多通常不是优化器状态丢了而是你的训练配置变了。比如上次用imgsz224续训时不小心改成imgsz256输入尺寸变化会导致特征分布漂移或者你换了batch大小学习率没跟着调优化器重新适应新batch时产生剧烈波动。遇到这种情况我的习惯做法是如果上次训练的最后的best.pt已经足够好直接加载best.pt在新的配置下从零开始继续微调而不是用last.pt续训。best.pt保存的是早停前验证集指标最优的权重它的泛化能力比最后一个epoch的状态可靠得多。续训场景里last.pt并不总是最佳选择。6. 训完不是终点用混淆矩阵、单图推理和ONNX导出做模型体检训练结束后的产物在runs/classify/目录下重点关注weights/best.pt、confusion_matrix.png和results.png。其中confusion_matrix.png是你判断模型是否可用的第一份证据对角线上的颜色越深越好如果某个列对应的色块明显发亮说明有大量其他类别的样本被误判到了这一类。from ultralytics import YOLO from PIL import Image import numpy as np model YOLO(runs/classify/crop_pest_224_50ep/weights/best.pt) img np.array(Image.open(test_images/wheat_leaf_spot.jpg)) results model.predict(img, imgsz224)[0] probs results.probs.data.cpu().numpy() top5 probs.argsort()[::-1][:5] print(Top5 索引:, top5) print(Top5 置信度:, probs[top5]) names model.names print(预测类别:, names[int(top5[0])], f置信度: {probs[top5[0]]:.4f})这段推理代码把best.pt加载进来对一张新图输出Top5预测。实际部署时如果你的分类项目已训练完成建议把权重导出成ONNX格式推理速度和部署便利性都更好yolo export modelruns/classify/crop_pest_224_50ep/weights/best.pt formatonnx imgsz224导出成ONNX之后可以脱离ultralytics环境用ONNX Runtime在任意平台加载推理。最后再说一个体检技巧把验证集里预测错误的所有图片单独列出来检查一遍看它们是「模型判断错了」还是「样本本身标注错了」。有时候1000张图手工整理难免有几张类别放错。把这些错误图片抽出来手工看一轮纠正标注效果往往比盲目调一次参数提升更多。这是我跑小样本分类时最实用的习惯。图像分类是个反馈极快的领域1000张图从整理到训练出结果半天完全够用。你不需要一上来就追求SOTA精度先跑通流程、拿到一套能用的脚本和数据组织规范后续补数据、换模型结构都是顺水推舟的事。希望你从这套流程里建立自己的数据整理习惯和调参手感再遇到更大规模的数据集时也能心中有数。本文还有配套的精品资源点击获取
返回列表