简介:面向Python毕业设计的农作物病虫害识别分类项目,完整包含模型训练与推理源码、带标注图像数据集及配套使用说明,可作为课程设计或毕业设计的直接蓝本。资源包共31个文件,涵盖后端Python脚本、图片样本、模型参数与推理文件、前端页面代码(vue、html、js)以及txt、yml等配置说明,整体19.64MB,压缩包目录结构清晰,便于按模块检索。已有180人学习下载,适合计算机视觉方向的学生参考。项目完整展示了数据预处理、卷积神经网络模型构建、训练评估与模型优化等环节,并提供可运行的推理服务配置和前端界面,能直观演示病虫害分类效果。数据集覆盖苹果叶病害等常见作物病虫害,代码层次分明,扩展性与可读性较好,方便二次开发或答辩展示。
1. 基于 Python 的农作物病虫害识别分类:毕设选题里最容易被低估的交付路径
每到毕设季,“农作物病虫害识别分类”都是农学和计算机交叉方向里出镜率最高的选题。它比聊天机器人、电商网站更有技术含量,公开数据集又比医学影像好找,于是很多人从网上翻一套基于 Python 的源码,配上数据集,想两三天跑通。真实情况是:模型训练本身没什么悬念,卡住的往往是 Python 环境、数据集目录、中文标签和最后的识别接口。有人甚至因为一张图片的通道顺序不对,把整锅结果推翻重来。这篇笔记写给两类人:一类是拿它做毕业设计、需要独立讲清楚原理并现场演示的学生;另一类是想在本地快速搭一套图像分类原型、验证“数据集 + 训练 + 接口”全链路的开发者。目标只有一个:照着做,能复现,敢答辩。
2. 跑通项目的第一步:Python 版本、依赖库与数据集目录约定
毕设源码包拿到手,先别急着双击 train.py。绝大多数项目翻车,都发生在环境不一致和数据集路径对不上。这章的思路是先把运行环境固定住,再按约定摆好数据目录,最后写一段小脚本探查真实数据分布。
2.1 环境安装:torch / opencv / scikit-learn 的版本搭配陷阱
常见做法是用 Anaconda 或 Miniconda 建一个独立环境,避免把系统 Python 搞乱。Python 版本建议锁在 3.8 到 3.10 之间,PyTorch 对 3.10 以上支持得晚,某些老源码用到torchvision.transforms的旧接口时,在 3.11 下容易出现莫名其妙的类型报错。安装环节最省心的顺序是:先建环境,再装 PyTorch,最后装图像和数值计算库。
conda create -n plant python=3.9 -y conda activate plant # CPU 版 torch,毕设演示完全够用 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install opencv-python pillow scikit-learn matplotlib flask参数说明:torch和torchvision必须同版本配套,否则加载预训练权重时会报operator不匹配;opencv-python负责读图和基础图像处理,pillow是 torchvision 底层依赖,scikit-learn用来算准确率、召回率和混淆矩阵,flask留到最后一章做识别接口。安装 torch 时我用的是 CPU 版,因为毕设数据量一般不大,CPU 训练一轮也就几分钟;如果你手头有 NVIDIA 显卡,把 index-url 换成官方 CUDA 源即可,性能提升立竿见影。
Windows 用户容易踩的坑是 pip 装 torch 速度极慢,看到进度条半天不动就想放弃。解决办法是用国内镜像源安装普通包,torch 则建议直接走官方 CPU whl 源,这个源在国内访问速度尚可。装完验证一下环境是否通:
python -c "import torch, torchvision, cv2, sklearn; print(torch.__version__, torchvision.__version__, cv2.__version__)"如果这行能打印出版本号,说明环境稳了。随后把conda activate plant写进项目 README 的开头,免得自己隔几天回来忘记怎么复现。
2.2 数据集的组织方式:train / val / test 目录结构与类别标签映射
公开的农作物病害数据集,比如 PlantVillage、天池柑橘病叶数据集、Kaggle 上的玉米小麦病害集,下载下来后目录结构五花八门。有的按类别文件夹分好,有的是一张 CSV 标注文件配一堆图片。单位项目里最常见、也最容易被源码接受的格式是这样:
dataset/ ├── train/ │ ├── Tomato___Late_blight/ │ │ ├── 001.jpg │ │ └── 002.jpg │ ├── Tomato___healthy/ │ └── Corn___Common_rust/ ├── val/ └── test/torchvision.datasets.ImageFolder就是按这种“根目录/类别/图片文件”的层级读数据的,类名直接取文件夹名。如果源码里用的是ImageFolder,而你手头是 CSV 标注格式,最常见的做法是写一段脚本,按标注把图片复制到对应类别的文件夹里,而不是去改源码的数据读取逻辑。
这里有一个容易被忽略的约定:类别文件夹名最好用英文或拼音,比如Tomato___Late_blight,而不是“番茄晚疫病”。原因很简单,Windows 控制台默认编码不是 UTF-8,中文路径在ImageFolder里偶尔能读,但到了torch.utils.data.DataLoader多进程加载时会随机报UnicodeDecodeError,属于典型的玄学问题,查起来极费时间。显示层面的中文名放到最后预测阶段做映射即可。
我一般还会在数据整理完后生成一份classes.txt,一行一个类别,顺序与文件夹名的字典序一致。这个文件后面训练、评估、答辩展示都要反复用,建议手动核对一遍:
import os data_root = "dataset/train" classes = sorted(os.listdir(data_root)) with open("classes.txt", "w", encoding="utf-8") as f: for c in classes: f.write(c + "\n") print(f"共 {len(classes)} 个类别") for c in classes: n = len(os.listdir(os.path.join(data_root, c))) print(f"{c}: {n} 张")逻辑说明:os.listdir拿到的是所有子目录名,排序后写入classes.txt,保证训练时ImageFolder的类别索引与文件里的顺序一一对应。统计每个类别的图片数量,是为了在动手训练前就知道数据是否均衡——病虫害数据集里“健康”类通常远多于“染病”类,这个问题放到 2.3 处理。
2.3 数据探查脚本:训练前先看类别分布与图片尺寸
很多人拿到数据集就开训,结果跑完十个 epoch 才发现某类只有二十张图,验证集里一张都没有。病虫害识别分类里,数据不均衡是常态,所以正式训练前的探查这一步值得做扎实。我一般写一个独立的探査脚本:
from PIL import Image import os data_root = "dataset/train" sizes = {} counts = {} for cls in sorted(os.listdir(data_root)): cls_path = os.path.join(data_root, cls) counts[cls] = 0 for fname in os.listdir(cls_path): if not fname.lower().endswith((".jpg", ".jpeg", ".png")): continue img = Image.open(os.path.join(cls_path, fname)) sizes[img.size] = sizes.get(img.size, 0) + 1 counts[cls] += 1 print("类别数量:", counts) print("图片尺寸分布:", sizes)运行完,重点看两个信息。一是各类别样本量是否差了一个数量级以上,如果“健康”类有 5000 张而某病害只有 150 张,训练时就该给少数类加权采样,或者用数据增强“造假”。二是图片尺寸分布,病虫害数据集里常见 224×224、256×256 和 512×512 混在一起,如果直接 resize 到统一尺寸,需要注意小图放大会变糊,影响识别。
把这两步做完,环境、数据、标签就算对齐了。下一步才轮到预处理和模型选型。
3. 从预处理到模型选型:识别分类管线怎么搭才容易出效果
这一章解决“模型怎么选、图片怎么喂”的问题。病虫害识别本质是图像分类,不是目标检测,所以不需要画框,只要给定一张叶片图片,输出它属于哪个病害类别。管线顺序是:预处理 → 数据增强 → 模型 → 损失函数 → 优化器。
3.1 预处理与数据增强:归一化参数抄哪组、增强做到什么程度
预处理里最容易被抄错的是归一化参数。torchvision自带的预训练模型,比如 ResNet,是在 ImageNet 上训练的,它期望输入图片的 RGB 三通道均值是[0.485, 0.456, 0.406],标准差是[0.229, 0.224, 0.225]。有人觉得病害叶片颜色深,自作主张改成“自己的归一化参数”,结果就是加载预训练权重后特征分布被破坏,训练半天 loss 降不下去。我的建议是:除非你是从头训练整个模型,否则归一化参数老老实实用 ImageNet 那组。
数据增强这条线,病虫害场景和通用分类不太一样。叶片在图片里的姿态千奇百怪,有正着拍的、斜着拍的、反着卷起来的,所以随机旋转和随机翻转比随机裁剪更实用。常用的组合如下:
from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize(256), transforms.RandomCrop(224), transforms.RandomRotation(degrees=15), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomVerticalFlip(p=0.3), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transform = 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]) ])参数说明:先 resize 到 256,再随机裁剪出 224×224,这比直接 resize 成 224 多了一点空间扰动,能变相增加训练样本的多样性。Rotation 我只设了 15 度,病虫害识别里旋转过大没有实际语义,比如一片叶子转了 90 度并不代表新的病害特征。RandomVerticalFlip我设的 0.3 而不是 0.5,因为实际拍摄时叶片倒置的情况没有水平翻转常见。
一个血泪经验:数据增强不是越多越好。小数据集上加太多增强,模型会长时间在增强后的“合成样本”上打转,真实病害特征反而学不稳。业务感强的做法是先跑一个不加增强的基线,再逐步加增强看验证集准确率变化。
3.2 模型选型:ResNet18、MobileNetV3 与迁移学习的取舍
病虫害识别分类选主干网络,我有三条标准:预训练权重好找、显存和内存压力小、答辩时能讲清楚原理。ResNet18 几乎是最稳的选择。它参数量只有 1100 万左右,CPU 训练一轮十分钟上下,残差结构几句话就能向老师解释明白,而且torchvision里直接提供在 ImageNet 上预训练好的权重,迁移学习效果远好于从零训练。
MobileNetV3 轻量,适合最后一章做的 Web 接口部署,缺点是同等数据下准确率通常比 ResNet18 低一到两个点。如果你想让演示更流畅,可以用 MobileNetV3;如果更看重准确率和稳定性,选 ResNet18。某些源码里会默认用 ResNet50,但对几千张级别的病虫害数据集属于杀鸡用牛刀,训练时间长,还容易过拟合。
用迁移学习的标准姿势是加载预训练模型,把最后一层全连接换成自己的类别数,然后整体微调:
import torchvision.models as models import torch.nn as nn num_classes = 10 # 替换成你 classes.txt 里的实际类别数 model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes)逻辑说明:weights=models.ResNet18_Weights.IMAGENET1K_V1是当前 torchvision 推荐写法,旧写法pretrained=True在新版本里会警告甚至报错。替换model.fc是因为原来输出 1000 类,现在只需输出自己的类别数。in_features要提前取出来,否则把fc直接替换后取不到原输入维度。
迁移学习的一个常见疑问是“要不要冻结主干网络”。我的做法是:先冻结 backbone,只训练fc几个 epoch,让新分类头先收敛;然后解冻全部参数,用一个更小的学习率微调整个网络。这样比直接全量微调稳定,能避免新初始化的分类头在前期把主干梯度带偏。
3.3 训练配置:学习率、batch size 与损失函数的选择
分类任务的损失函数没有太多悬念,用nn.CrossEntropyLoss,内部已经包含 softmax,不需要在模型输出层再手动加激活。优化器上,SGD 加 momentum 的泛化能力普遍比 Adam 好,这在中小数据集上表现尤其明显;Adam 的优点只是前期收敛快,但后期容易跑进尖锐极小值。推荐配置是:
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5) criterion = nn.CrossEntropyLoss()参数说明:初始学习率 0.001 是迁移学习的常用起点,比从头训练的 0.01 小,因为预训练权重已经接近局部最优,步子太大会破坏已有特征。weight_decay=1e-4相当于 L2 正则,对抗过拟合很有用。StepLR每 10 个 epoch 把学习率减半,这个策略简单可靠;如果嫌手动调周期麻烦,ReduceLROnPlateau也能用,它会在验证 loss 连续不降时自动降学习率。
batch size 的设定逻辑是“够用就好”。CPU 训练设 16,GPU 设 32 或 64。这里注意,batch size 调大后学习率也可以适当放大,线性缩放规则是 batch 翻倍、学习率翻倍,但病虫害数据集规模不大,保持 0.001 不调也能收敛。数据不均衡明显时,给CrossEntropyLoss传入weight参数,让少数类的损失加权放大,比简单复制少数类样本更干净。
训练配置这部分往往被当成“玄学”,其实核心就一句话:迁移学习用小学习率、带动量的 SGD、加 weight decay,别一上来就堆 epoch,先把 30 轮的基线跑通,再根据曲线调。
4. 训练与评估:损失曲线、混淆矩阵与模型文件的落盘
训练环节的目标不只是让 loss 掉下来,而是得到一份能解释、能展示的结果。这章把训练循环、评估指标和模型保存串起来讲清楚。
4.1 训练循环:日志输出、checkpoint 与早停策略
一个可复现的训练循环要同时做好三件事:每个 epoch 结束打印训练 loss 和验证准确率、把表现最好的模型单独存一份、在验证集不再提升时提前停住。核心代码如下:
best_acc = 0.0 for epoch in range(epochs): model.train() running_loss = 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() # 验证 model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in val_loader: outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() val_acc = correct / total print(f"epoch {epoch+1}: loss={running_loss:.4f}, val_acc={val_acc:.4f}") if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), "checkpoints/best_model.pth") scheduler.step()逻辑说明:model.train()和model.eval()切换的是 dropout 和 batch norm 的行为,如果不切,验证时结果会随机波动。optimizer.zero_grad()必须在每个 batch 前清空梯度,否则梯度会累加。torch.no_grad()在验证时关掉梯度计算,省显存的同时避免意外修改参数。
这里的核心参数是验证频率和打印信息粒度。病虫害数据集小,一个 epoch 通常就是几百步,每个 epoch 打印一次完全够。checkpoints目录要提前建好,否则第一次保存时直接报FileNotFoundError。早停策略我一般这样判断:连续 8 个 epoch 验证准确率没有刷新最好记录,就停掉,防止最后一个模型把几百轮训练时间耗在过拟合上。
4.2 评估指标:准确率之外还要关注哪些数字
准确率只能说明整体水平,答辩时老师一定会问“哪个类别分得差”。所以要提前跑出每个类别的精确率、召回率和 F1,再配一张混淆矩阵图,这比口头解释“模型效果不错”有说服力得多。
from sklearn.metrics import classification_report, confusion_matrix import numpy as np all_preds = [] all_labels = [] model.eval() with torch.no_grad(): for images, labels in val_loader: outputs = model(images) _, predicted = torch.max(outputs, 1) all_preds.extend(predicted.tolist()) all_labels.extend(labels.tolist()) print(classification_report(all_labels, all_preds, target_names=classes)) cm = confusion_matrix(all_labels, all_preds) print(cm)参数说明:classification_report会按类别输出 precision、recall、f1-score 和样本数,target_names传classes.txt里读进来的列表,这样结果直接可读。confusion_matrix的维度是“真实类别数 × 预测类别数”,对角线越亮越好。这套结果跑完,建议再用 matplotlib 画一张混淆矩阵热力图存成 png,答辩 PPT 里直接放图,比任何描述都有用。
病虫害场景里特别要关注“健康类”和“早期病害类”是否互相混淆。因为早期病斑面积小,模型很容易把极早期的病叶当健康叶片。如果混淆矩阵里这两个类别交叉明显,说明预处理阶段可以增加对比度增强,或者拍摄标准化没做到位,只调模型结构是没用的。
4.3 模型输出:torch.save 与 ONNX 导出的适用场景
训练结束后,源码包里常见的交付物有三种:.pth权重文件、完整.pt模型、.onnx模型。.pth只存参数,加载时需要重新构建模型结构,适合训练流程里继续微调;.onnx是跨平台格式,可以用 ONNX Runtime 加速,后续做 Web 接口更稳。
torch.save(model.state_dict(), "checkpoints/best_model.pth") dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, "checkpoints/best_model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}} )参数说明:torch.onnx.export的dummy_input尺寸必须与模型输入一致,直接用 224×224。dynamic_axes把 batch 维设成动态,这样接口既能一次识别一张图,也能批量识别多张。导出前记得把模型切成 eval 模式,否则导出的 onnx 里会残留 dropout 随机性。
这一章做完,手里就有了一份训练记录、一组评估数字、一份混淆矩阵和两个格式的模型文件。到这一步,离一个可演示的毕设系统只差最后一层壳:把模型包成识别接口,并处理真实场景里的边界情况。
5. 农作物病虫害识别分类的 5 个踩坑与排查记录
这一章写的是我把这套流程从零跑到能演示的过程中,真正卡住过我的问题,每一条都是“现象 → 原因 → 解决”的结构。如果你是一个新手,建议先把这章放在手边,遇到问题直接对号入座。
5.1 数据加载卡死、训练慢:num_workers 与图片解码是首凶
现象:训练循环刚跑起来,进度条卡住不动或者每个 epoch 要等很久;Windows 上偶尔还会弹出python 已停止工作的窗口。原因是默认DataLoader的num_workers在 Windows 上表现不稳定,图片尺寸过大导致每次读取都在做大量解码运算。解决办法是显式指定num_workers,并控制图片加载尺寸:
train_loader = torch.utils.data.DataLoader( train_dataset, batch_size=16, shuffle=True, num_workers=0, pin_memory=False )参数说明:Windows 下num_workers设为 0,让数据加载在主进程内同步完成,是最稳的做法;Linux 服务器上可以设 4 或 8,能显著提速。pin_memory只有 GPU 训练时才有意义,CPU 训练反而多一次内存拷贝。如果你的数据集里混着一批 4000×3000 的高清原图,务必在预处理里先 resize 到 256,否则每读一张图上 GB 级内存,训练直接被打爆。
5.2 显存溢出:batch size 与输入尺寸的取舍
现象:GPU 训练时torch.cuda.OutOfMemoryError突然出现,CPU 训练则表现为内存占用飙升到接近物理内存上限。原因很直接,模型输入尺寸和 batch size 太大,或者验证时忘了关梯度。解决思路是优先压 batch size,其次压输入尺寸:
优先调整顺序:batch size 减半 → 输入尺寸从 224 降到 160 → 检查是否开了太多 DataLoader 子进程常见做法是把 batch size 从 64 降到 32,如果还溢出,就把Resize从 256 改成 200,输入降到 176。分辨率从 224 降到 160,对病虫害识别准确率的影响通常不到 1 个点,但显存占用能降一半。另外,验证过程务必用torch.no_grad()包住,否则显存里会堆积一整条反向传播计算图。
5.3 训练准确率高、验证准确率低:增强与正则化没跟上
现象:训练集准确率冲到 98%,验证集停在 82% 上不去,典型过拟合。原因通常来自三个方向:训练样本太少、增强强度不足、模型容量过剩。解决办法不是换一个更大的模型,而是把正则化做扎实:
train_transform = transforms.Compose([ transforms.Resize(256), transforms.RandomCrop(224), transforms.RandomRotation(degrees=20), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])参数说明:把 rotation 从 15 度增加到 20 度,ColorJitter的强度也略微提升,让模型看到更多“不完美”的叶片图片。如果这样还过拟合,就把 ResNet18 换到 MobileNetV3,用容量更小的模型强迫网络学更本质的特征;或者把weight_decay从 1e-4 调到 5e-4。过拟合没有一次性解法,通常是增强、模型容量、正则化三处各收紧一点才压得住。
5.4 中文路径与中文标签乱码:编码问题导致数据集读不全
现象:在 Anaconda Prompt 里能看到中文文件名,训练一跑就报FileNotFoundError或者张量维度对不上;有时一半图片神秘消失。原因八成是 Python 在 Windows 下默认编码是 GBK,而源码和数据集标签是 UTF-8,两边一碰就出乱码。解决分两步,第一步在代码入口强制指定 UTF-8,第二步在数据集整理阶段避开中文文件名:
import sys sys.stdout.reconfigure(encoding="utf-8")如果数据已经用中文命名,批量改成拼音或英文更省心。这里不要抱有侥幸心理,中文文件名即使这次能跑,换个电脑换个环境变量就翻车。标签显示层的中文,放到最后的 Flask 接口里做映射,比如"Tomato___Late_blight": "番茄晚疫病",两层解耦后,数据层永远保持安全字符。
5.5 预测结果与训练结果对不上:归一化与通道顺序不一致
现象:验证集准确率 90%,单独拿一张图片调用预测脚本,结果稳定输出错误类别,而且每次都是同一个错。原因几乎都出在推理时的预处理和训练不一致:要么没做归一化,要么图片通道顺序不对。opencv-python读出来的图片是 BGR 顺序,而 torchvision 的模型期望 RGB,直接喂会得到错乱的颜色特征;cv2.imread读图后必须转一次顺序:
import cv2 img = cv2.imread("test.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 之后再做 resize、转 tensor、归一化逻辑说明:cv2.cvtColor把 BGR 转成 RGB,这一步必须在 resize 之前做,避免颜色通道已经被插值算法混合。另外,推理脚本里的transforms要和训练时用的val_transform完全一致,尤其是归一化的 mean 和 std。很多毕设源码给的 predict.py 里忘了归一化,这个 bug 藏得很深,因为单张图跑起来不报错,就是结果不对。
6. 最后一步:用 Flask 包一个识别接口,并把置信度阈值调明白
训练进度的终点不是.pth文件,而是一个能给别人演示的入口。
6.1 Flask 最小接口与图片上传识别
常见做法是用 Flask 起一个本地服务,接收上传图片,返回识别类别、置信度和时间。代码量不大,却是答辩现场最出效果的部分:
from flask import Flask, request, jsonify from PIL import Image import torch, torchvision.transforms as transforms app = Flask(__name__) model = torch.load("checkpoints/best_model.onnx", map_location="cpu") model.eval() transform = 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]) ]) @app.route("/predict", methods=["POST"]) def predict(): file = request.files["image"] img = Image.open(file.stream).convert("RGB") tensor = transform(img).unsqueeze(0) with torch.no_grad(): outputs = model(tensor) probs = torch.softmax(outputs, dim=1) conf, idx = torch.max(probs, 1) return jsonify({"class": classes[idx.item()], "confidence": round(conf.item(), 4)}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)启动后浏览器访问http://127.0.0.1:5000/predict或用 POST 方式传一张图片,就能拿到 JSON 结果。注意.convert("RGB")这一步,PIL 打开图片时可能是 RGB、RGBA 甚至灰度图,通道和归一化约定必须在这里再次统一。
6.2 用测试集切片验证接口,并调整置信度阈值
接口能跑通只是起点,真正值得做的是用测试集切片打一遍接口,看它会不会把低置信度的图片硬分类。如果模型对某张图输出 0.55 的置信度,说明它其实在“猜”,而接口不该给出斩钉截铁的答案。常见做法是加一个阈值判断:置信度低于 60% 时返回“待人工复核”,而不是强行指定一个类。
我现在的习惯是每次交付前,从测试集里随机抽 200 张图,跑一遍接口,统计置信度分布和错误样本的置信度区间。这个习惯源于一次翻车:某次演示时,一张没见过的环境噪声图被模型以 0.97 的置信度判成健康叶片,当场被追问“模型是不是只会答健康”。之后我就把阈值逻辑写进接口,低于阈值就拒绝回答,效果反而更好。置信度阈值不用拍脑袋定,用验证集画一张正确样本和错误样本的置信度分布图,选两者分离最明显的点即可。
这套流程走到这里,你手里是一个可复现、可解释、可演示的农作物病虫害识别分类系统,每一步都能讲出原因,希望帮到你。
本文还有配套的精品资源,点击获取