news 2026/10/14 9:41:02

Python农作物病虫害识别分类实战:CNN与迁移学习

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python农作物病虫害识别分类实战:CNN与迁移学习

简介:面向计算机专业毕业设计、课程设计与项目实战学习者的完整可运行资源包,围绕Python农作物病虫害识别与分类任务,提供模型训练、验证及推理所需全部代码。资源压缩包共96个文件,以76个py源码文件为主,涵盖CNN(含CNLNet变体)及EfficientNet、ResNet、Vision Transformer、Swin Transformer等多种视觉模型实现,配套15个pyc编译模块、训练日志与说明文档,整体约25.1MB。项目已调试可直接运行,数据集与演示程序一并提供,便于快速复现识别分类流程并开展实验对比。资源已吸引两千余人学习,适合需要完整项目源码支撑毕业设计、课程作业或深度学习图像分类练习的读者使用。

1. 基于python的农作物病虫害识别分类项目源码:先搞清楚它到底帮你解决什么问题

手机拍一张水稻叶片照片,几秒后屏幕上跳出“稻瘟病,置信度0.93”。这不是农业专家系统里的演示视频,而是现在一个用Python写的图像分类工程能做到的基础效果。基于python的农作物病虫害识别分类项目源码,核心就是拿卷积神经网络(CNN)对作物叶片图像做分类,把“这是什么病”从肉眼经验变成可重复、可量化的判断。压缩包里通常包含三块东西:模型源码、整理好的图像数据集、一份讲怎么跑起来的使用说明。很多人下载后急着训练,结果卡在文件路径、依赖版本和数据划分上,折腾两天连第一次epoch都没跑完。

这个项目的真正难点不是“把模型跑起来”,而是让模型在真实农田环境里可靠。田间照片的光照、泥土、拍摄角度都比公开数据集复杂得多,识别系统必须能区分“病斑”和“虫咬”、“缺素”和“药害”。如果你是有Python基础的学生、做农业信息化的工程师,或者想验证边缘设备推理方案的硬件玩家,这个方向值得投入。它把图像分类、迁移学习、数据集工程三个常见需求串在了一个完整的项目里,既能当毕业设计,也能当企业原型的起点。

2. 拿到压缩包先别急着训练:看懂源码、数据与说明的分工

2.1 解压后我一般先找这几个入口:train.py、predict.py、requirements.txt

一个规范的源码包,目录往往会分成源码、数据、模型产物和文档四块。我先给一个最常见的布局,不同项目名字可能不同,但套路基本一致:

crop_pest_project/ ├── src/ │ ├── train.py # 训练脚本,负责加载数据、训练模型、保存权重 │ ├── predict.py # 推理脚本,读入单张图片并输出类别和置信度 │ ├── model.py # 模型定义,常见做法是封装ResNet/MobileNet │ ├── data_utils.py # 数据加载、数据集划分、预处理函数 │ └── config.py # 超参数和路径配置,集中修改的地方 ├── data/ │ ├── images/ # 按类别分目录存放的图片 │ └── labels.csv # 可选,如果源码不按目录读取则用标签文件 ├── models/ │ └── best_model.pth # 训练产物,通常不在下载包里,需自己生成 ├── docs/ │ └── 使用说明.md └── requirements.txt # 用pip安装的依赖清单

想快速判断这个项目能不能用,不要先看readme,先看三个文件:train.py里数据是怎么读的、requirements.txt里有哪些依赖、config.py里默认的数据路径和类别数。这三个点能决定你按使用说明跑一次能不能成功。很多源码包里的路径写的是作者本机的绝对路径,比如/home/user/data,到你电脑上一跑就报文件不存在。这不是bug,是路径没适配,后面我会专门讲。

如果压缩包里没有requirements.txt,就去看使用说明里列出的依赖。常见组合有两种:PyTorch系的torch/torchvision/numpy/opencv-python/scikit-learn/matplotlib,或者TensorFlow系的tensorflow/keras。我偏好开头先确认框架,再做环境安装,避免硬编码的模型权重文件在两套框架之间换不过来。

2.2 环境搭建:用虚拟环境把依赖锁死,避免“我机器上能跑”

农业识别项目经常会在不同机器之间拷贝,最怕某台机器已经装了老版本的numpy,把新代码的API调用带崩。我拿到这类源码的第一件事永远是新建一个虚拟环境,而不是直接往全局环境里装:

python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install --upgrade pip pip install -r requirements.txt

venv会把Python解释器和依赖包都隔离在当前目录下,版本冲突不会扩散到系统环境。如果项目代码是基于PyTorch的,我会优先装CPU版还是GPU版?开发阶段先装GPU版,因为病虫害图像动辄上万张,用CPU跑一个完整的epoch会让人失去耐心。但要注意,torch和torchvision的版本需要配套更新,如果先用pip装了最新版torchvision,再装旧版torch,很可能导入时报torchvision找不到匹配的算子。建议先看使用说明里写了哪类依赖组合,比如它明确要求torchvision,就用同一套索引安装。

安装完后验证一下:

python -c "import torch, torchvision; print(torch.__version__, torchvision.__version__)"

这一步能过滤掉一大半“明明装了却导入失败”的问题。另外注意,requirements.txt里如果出现了opencv-contrib-python和opencv-python同时安装,会导致cv2的接口混乱,只保留其中一个即可。图像分类项目用opencv-python就够了,除非源码里调用了sift之类的contrib函数。

2.3 准备一份最简配置:数据路径、类别数、输出目录

大部分训练脚本都会引用一个配置文件。我习惯把路径、类别、学习率等集中在一个地方,这样调参和换数据都很方便。常见的config.py长这样:

import os # 数据根目录:按类别分目录的图片都放在这个文件夹下 DATA_ROOT = "data/images" # 训练完成后保存权重文件的目录 SAVE_DIR = "models" # 类别数量:有几类病虫害就填几类 NUM_CLASSES = 15 # 图像统一缩放尺寸,迁移学习常用224x224 IMG_SIZE = 224 # 训练超参数 BATCH_SIZE = 32 EPOCHS = 30 LEARNING_RATE = 1e-3 WEIGHT_DECAY = 5e-4 # 如果使用GPU,设置设备;没有GPU则回退到CPU DEVICE = "cuda" if os.environ.get("USE_GPU", "1") == "1" else "cpu"

这段配置里最值得注意的是DATA_ROOT和NUM_CLASSES。很多源码包默认的类别数是固定写死在你下载的数据集上的,如果你自己新增识别对象,比如把“健康叶片”也放进训练集,就必须同步修改NUM_CLASSES,否则模型最后一层全连接输出维度对不上,训练时直接报维度错误。IMG_SIZE也不是越大越好,迁移学习用的预训练模型大多在224×224或256×256分辨率下表现稳定,盲目改成512会把显存和训练时间拉上去,但准确率收益很小。

3. 整理数据集:病虫害识别最花时间的一步,也是模型上限的决定因素

3.1 标签目录结构:用ImageFolder直接读,省掉手写标签

图像分类项目里最省事的组织方式,就是根目录下面按类别建子文件夹,每一类图片直接放进去。PyTorch的torchvision.datasets.ImageFolder可以直接扫这种目录,自动把文件夹名映射成数字标签。不用手写CSV,也不用自己写解析函数,这对农业场景的快速迭代特别友好。

from torchvision import datasets, transforms # 先定义基础的预处理,Resize和Normalize稍后会细说 data_transforms = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 假设数据目录结构为:data/images/稻瘟病/xxx.jpg, data/images/健康/xxx.jpg dataset = datasets.ImageFolder(root="data/images", transform=data_transforms) print(dataset.classes) print(dataset.class_to_idx)

ImageFolder会把data/images下的每一个子目录当作一个类别,类别的排序依据是目录名的字母顺序。这里有一个隐蔽的坑:如果你自己的标注目录是中文名,比如“稻瘟病”、“玉米叶斑病”,ImageFolder仍然能读,但打印出来的class_to_idx顺序是按中文编码排列的,训练脚本里如果硬编码了“0号类别是健康”,很容易和实际顺序对不上。我一般会把class_to_idx输出保存下来,推理阶段加载模型时按同一个映射做预测,让训练和部署的类别编号保持一致。

另外,dataset.classes的类别顺序会在训练和推理两条链路里被反复使用。如果某天你删掉了一个类别的目录,重新训练后类别顺序全变了,之前保存的权重基本作废。所以数据目录一旦定下来,不要轻易改动结构。

3.2 数据集划分:避免同一株叶片同时出现在训练集和验证集

训练集、验证集、测试集怎么切,是病虫害识别项目里决定“假准确率”的关键。常见做法是先把数据集按类别做分层划分,保证每个类别在三份数据里的比例接近,而不是把整个目录随机打散后切三段。

import os import random import shutil from collections import defaultdict def split_dataset(source_root, train_ratio=0.8, val_ratio=0.1): """ 按类别分层划分,保留10%作为测试集。 划分后会创建train/val/test三个新目录。 """ class_dict = defaultdict(list) for class_name in os.listdir(source_root): class_dir = os.path.join(source_root, class_name) if not os.path.isdir(class_dir): continue for img_name in os.listdir(class_dir): class_dict[class_name].append( os.path.join(class_dir, img_name) ) for class_name, img_list in class_dict.items(): random.shuffle(img_list) total = len(img_list) train_end = int(total * train_ratio) val_end = int(total * (train_ratio + val_ratio)) for split_name, split_list in [ ("train", img_list[:train_end]), ("val", img_list[train_end:val_end]), ("test", img_list[val_end:]) ]: out_dir = os.path.join(source_root, split_name, class_name) os.makedirs(out_dir, exist_ok=True) for img_path in split_list: shutil.copy2(img_path, out_dir)

这段代码背后的逻辑是:先按类名分组,再把每一组的图片随机打乱,按比例切成三段。这样做的好处是,样本数量少的类别不会因为随机切分导致某些类别在验证集里没有数据。比如“稻曲病”只有200张,如果你对全部图像做全局随机切分,分到验证集的可能只有十几张,甚至一张都没有。分层划分能保证每个类别在训练和验证里都保持大致相同的占比。

另一个需要警惕的问题在代码里不直接体现:同一株作物在不同角度拍下的多张照片,可能分布在了训练集和验证集里。因为脚本只按“路径是否相同”去重,并不会识别图像内容是否来自同一个样本。如果模型在训练集里见过某一个病斑的近距离特写,又在验证集里看到同一病斑的远景图,验证准确率会虚高。这类同源污染的解决办法是划分之前先按“拍摄时间+地块”去重,或者把来自同一目录连续编号的图片放进同一个分片里。项目里如果时间来不及,至少要记住:任何划分都不能保证绝对干净,最后的测试集尽量用另一个来源的图片。

3.3 预处理和数据增强:病斑纹理比普通分类更怕过度增强

图像分类的预处理一般分三步:缩放到统一尺寸、转Tensor、做标准化。标准化用的均值和标准差来自ImageNet预训练模型的统计值,迁移学习时沿用这套归一化参数,能让预训练权重直接生效。病虫害识别里更值得关注的是数据增强。

from torchvision import transforms train_transforms = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transforms = 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]) ])

注意验证集的增强只做缩放和中心裁剪,不做随机翻转、旋转和颜色抖动,因为验证集要尽量接近真实推理时的输入分布。如果验证集也做了RandomRotation,同一张图每次验证的结果都不一样,你就很难判断模型是变好了还是运气变好了。

数据增强参数怎么调,我付出血泪经验的结论是:不要一味追求和通用分类比赛一样的增强强度。病斑纹理是识别的主要线索,旋转15度以内可以模拟叶片姿态差异,但如果把RandomResizedCrop的scale下限调成0.4,模型很容易只看到叶片边缘的绿色区域,病斑特征反而被裁掉了。ColorJitter的亮度对比度也不能太过,田间光照虽然变化大,但过度改变颜色会让模型把“黄化”和“正常老叶”搞混。常见的做法是先用轻度增强,等看训练集损失已经很低而验证集损失偏高时,再加一点旋转或裁剪来对抗过拟合。参数建议先按下面这个范围起步:

增强操作推荐范围使用注意
RandomResizedCropscale=(0.7, 1.0)低于0.5容易丢病斑
RandomRotation10~20度旋转过大破坏叶脉走向
ColorJitter亮度/对比度0.2左右过高容易混淆黄化症状
RandomHorizontalFlip概率0.5叶形对称类病害可用

4. 训练识别模型:迁移学习、训练循环与参数选择

4.1 模型选型:ResNet、MobileNet还是从零搭一个CNN

病虫害识别属于典型的细粒度图像分类,类内差异大,类间差异小。同样都是叶斑病,有的病斑是圆形的,有的是不规则形的;不同病害之间的边界又很模糊。从零搭一个三层CNN很容易在训练集上过拟合,因为它没有预训练知识,只能靠少量农业图像硬学纹理特征。我一般不会这么做。

更可靠的做法是直接加载在ImageNet上预训练过的分类模型,把最后一层全连接换成本项目的类别数,然后做迁移学习。常用选择是两个方向:ResNet18/ResNet50偏重识别精度,MobileNetV3偏重模型体积和推理速度。

模型参数量级推理速度典型场景
ResNet1811M级快服务器或PC端效果优先
ResNet5025M级中等精度要求高且有GPU
MobileNetV35M级快边缘设备和手机端
EfficientNetB05M级快精度和速度相对均衡

如果压缩包里的model.py已经写了某个模型,我会先保留它的默认结构,避免改了网络后加载不了预训练权重。只有在默认模型跑通后,再替换成MobileNetV3验证边缘部署的可行性。

import torch import torch.nn as nn from torchvision import models def create_model(num_classes, model_name="resnet18"): if model_name == "resnet18": weights = models.ResNet18_Weights.IMAGENET1K_V1 model = models.resnet18(weights=weights) # 替换最后一层全连接为当前项目的类别数 in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes) elif model_name == "mobilenet_v3": weights = models.MobileNet_V3_Large_Weights.IMAGENET1K_V1 model = models.mobilenet_v3_large(weights=weights) in_features = model.classifier[-1].in_features model.classifier[-1] = nn.Linear(in_features, num_classes) else: raise ValueError(f"Unsupported model: {model_name}") return model

核心逻辑是:保留预训练模型从输入到倒数第二层学到的通用特征提取能力,只替换最后的分类头。这样即使你的病虫害数据只有几千张,模型也不会崩溃式过拟合。替换fc或classifier[-1]前,必须先读出原来层的in_features,否则写死的输入维度会和预训练权重对不上。

4.2 训练脚本骨架:从数据加载到模型保存

有了模型,接下来要把完整训练流程串起来。这个流程几乎在所有图像分类项目里通用,我按照一个可复现的最小训练脚本来写:

import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms from model import create_model from config import DATA_ROOT, SAVE_DIR, NUM_CLASSES, BATCH_SIZE, EPOCHS, LEARNING_RATE, DEVICE def main(): # 1. 数据加载 train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_ds = datasets.ImageFolder(root=DATA_ROOT + "/train", transform=train_transform) val_ds = datasets.ImageFolder(root=DATA_ROOT + "/val", transform=val_transform) train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=4) # 2. 模型与损失函数 model = create_model(NUM_CLASSES, model_name="resnet18").to(DEVICE) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) # 3. 训练循环 best_acc = 0.0 for epoch in range(EPOCHS): 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) scheduler.step() epoch_loss = running_loss / len(train_ds) # 4. 验证 val_acc = evaluate(model, val_loader, DEVICE) print(f"Epoch {epoch+1}/{EPOCHS}, loss={epoch_loss:.4f}, val_acc={val_acc:.4f}") # 5. 保存验证集上最好的模型 if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), f"{SAVE_DIR}/best_model.pth") def evaluate(model, loader, device): model.eval() correct, total = 0, 0 with torch.no_grad(): for images, labels in loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, preds = torch.max(outputs, dim=1) correct += (preds == labels).sum().item() total += labels.size(0) return correct / total if __name__ == "__main__": main()

这段脚本里比较重要的是scheduler.step()的位置。我用的是StepLR,每10个epoch把学习率乘0.1,这种策略能在训练后期把损失压得更低。如果你发现验证集准确率在某个区间震荡,先检查是不是学习率没有下降。

torch.save(model.state_dict())只保存模型参数,不保存类别映射。所以训练完成后,我会把dataset.class_to_idx单独存成一份JSON,否则预测脚本很可能把“稻瘟病”输出成“健康”。

4.3 训练时看什么:loss曲线、验证准确率和混淆矩阵

很多初学者训练时只盯着终端打印的loss,看到数字在降就安心了。但loss降不代表模型在病虫害识别里好用。类别不均衡时,模型只要输出频率最高的那个类,loss就能很低。所以我会同时关注三个指标:

  • 训练loss:它下降缓慢说明学习率过低或模型容量不够;下降后又反弹说明过拟合已经开始。
  • 验证准确率:简单直观,但类别不均衡时会失真,所以要配合混淆矩阵看。
  • 每一类的召回率:比如“玉米大斑病”识别得再好,如果“玉米小斑病”老是分错,整体准确率再高也没用。

验证时算混淆矩阵,用scikit-learn一行能完成:

from sklearn.metrics import confusion_matrix, classification_report import numpy as np all_labels = [] all_preds = [] model.eval() with torch.no_grad(): for images, labels in val_loader: images = images.to(DEVICE) outputs = model(images) _, preds = torch.max(outputs, dim=1) all_labels.extend(labels.cpu().numpy()) all_preds.extend(preds.cpu().numpy()) print(classification_report(all_labels, all_preds, target_names=dataset.classes)) print(confusion_matrix(all_labels, all_preds))

classification_report会输出每个类别的precision、recall、f1-score。病虫害识别里我优先看recall,因为漏诊比误诊更危险:一个健康叶片被判成病害,最多是农户多打一次药;一个正在扩散的病害被漏掉,可能整片田都受影响。如果某个类别的recall明显低于其他类别,就要去检查它的样本数量和图像多样性,而不是急着调模型结构。

5. 病虫害识别项目常见问题排查:从读图失败到“假准确率”

5.1 报错FileNotFoundError:中文路径、相对路径和目录结构不一致

现象:照着使用说明运行训练脚本,加载数据集时报FileNotFoundError: [Errno 2] No such file or directory,但文件夹明明存在。

原因:最常见的是三处不一致。第一,源码里的数据路径是作者机器上的绝对路径,换到你电脑后没改config.py。第二,Windows系统下路径分隔符是反斜杠,部分脚本在拼接路径时用/写死了,虽然Python通常能兼容,但一旦代码里混用了os.path.join和字符串拼接,就会拼出无效路径。第三,中文目录名在部分Linux环境下乱码,尤其是压缩包在Windows解压后传到Linux服务器,内码转换导致目录名对不上。

解决:先别改代码,直接让Python告诉你它真实读取的路径是什么。在运行前执行:

python -c "import os; print(os.path.abspath('data/images/train'))"

如果这个路径不对,就回到config.py,把DATA_ROOT改成绝对路径或基于当前文件位置的相对路径。更稳妥的做法是不要依赖命令行启动时的工作目录,在config.py里用os.path.dirname(__file__)定位项目根目录。

5.2 训练loss很快降到0.1,验证准确率却只有60%

现象:训练到第5个epoch,训练集loss已经降到0.2以下,但验证准确率一直在60%徘徊,甚至不再增长。

原因:这是过拟合的典型信号。模型把训练集里的背景、拍摄位置、光照等偶然特征记住了,却没有学到真正的病斑纹理。病虫害数据集的样本数量通常不多,如果从头训练一个深层网络,这种过拟合几乎无解。

解决:先换成预训练模型并冻结除最后一层外的参数,做一次“只训练分类头”的快速实验;如果验证准确率有明显提升,再解冻部分层微调。另外,把验证集和训练集的增强策略再检查一遍,验证集一定不能有RandomResizedCrop或RandomRotation。最后可以调大WEIGHT_DECAY到1e-3,或者提前结束训练。

5.3 CUDA out of memory:不是batch调小就万事大吉

现象:程序跑了一个epoch后,在第二个epoch突然报CUDA out of memory,代码本身没有改过。

原因:显存溢出不只是batch_size的关系。图像分辨率、DataLoader的num_workers、模型大小都会影响显存占用。训练开始时不溢出,后来溢出,往往是因为验证或测试阶段把整批数据一次性加载了,而验证集图片和训练集不是同一批,某个batch里包含了更大尺寸的图片。

解决:先把batch_size减半,同时在DataLoader里加上pin_memory=True。如果还溢,检查是否在不该开梯度的阶段开了梯度,验证时加with torch.no_grad()。另外,transforms.Resize((224,224))会在预处理阶段把图片都统一尺寸,如果报错发生在数据加载阶段,优先看是不是某个损坏图片解码后尺寸反常。

5.4 类别不均衡导致模型只会猜“健康”

现象:训练结束后,整体准确率有90%,但检查分类报告发现“健康”这一类recall是98%,某几种病害的recall不到30%。

原因:田间收集的数据天然不均衡,健康叶片数量可能占了一半以上,而某些病害样本只有一两百张。交叉熵损失会尽可能拟合多数类,少数类对loss的贡献被稀释。

解决:最简单的办法是把训练集中的少数类做过采样,或者用WeightedRandomSampler让每个batch里每个类别出现概率更均衡。我一般先用类别权重的损失函数:

from torch.utils.data import WeightedRandomSampler labels = train_ds.targets class_counts = torch.bincount(torch.tensor(labels)) weights = 1.0 / class_counts.float() sample_weights = weights[labels] sampler = WeightedRandomSampler(sample_weights, num_samples=len(labels), replacement=True) train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, sampler=sampler)

WeightedRandomSampler会按照样本权重的比例抽batch,少数类每个epoch被抽到的次数更多。代价是模型可能对少数类过拟合,需要在验证集上密切观察这一类别的recall,而不是整体准确率。另一种做法是直接换损失函数,如FocalLoss,让模型更关注难分类样本,但实现稍复杂,我建议先过采样。

5.5 验证集泄露:同源照片让模型分数虚高

现象:训练时验证准确率达到95%,信心满满地部署到真实农田,结果准确率掉到70%。

原因:很大概率是数据划分时,同一个病害样本的多张连续拍摄照片被同时分进了训练集和验证集。模型见过某个病斑的“近距离版”,验证集里出现它的“远距离版”,模型等于直接背下了答案。这在公开数据集的规范划分下不太常见,但自己整理田间数据时很容易发生。

解决:划分数据集之前,按照图片文件名里的拍摄编号做分组,同一个编号的照片必须划进同一个集合。如果文件名没有规律,可以按拍摄时间给图片分组。判断是否泄露的简单方法是看验证集准确率是否高到异常,比如比训练集准确率还高,或者比实际场景表现高出一大截。这时不要相信准确率,要把几个典型样本打印出来人工检查。

6. 实战技巧:把训练好的模型变成一个能落地的识别工具

模型训练好只是第一步,真正要拿去田间试用,还需要解决“预测时该信它多少”的问题。病虫害识别里最危险的输出不是分类错误,而是模型在遇到没见过的症状时,仍然强行给出一个高置信度结果。我习惯在预测脚本里加一个置信度阈值,低于阈值就输出“未知,建议人工复核”。

import torch from torchvision import transforms from PIL import Image def predict_image(image_path, model, class_names, device="cpu", threshold=0.5): 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]) ]) image = Image.open(image_path).convert("RGB") image_tensor = transform(image).unsqueeze(0).to(device) model.eval() with torch.no_grad(): outputs = torch.softmax(model(image_tensor), dim=1) confidence, pred_idx = torch.max(outputs, dim=1) confidence = confidence.item() if confidence >= threshold: return class_names[pred_idx.item()], confidence else: return "未知病害", confidence

这段代码看着简单,但很重要的一点是class_names必须和训练时的class_to_idx完全一致。我每次训练完都会把类别索引存成JSON,预测脚本从JSON读,而不是靠手敲。阈值threshold建议先用0.5,再根据实际误报情况调。如果模型频繁把健康叶片识别成病害,就把阈值提高到0.7;反过来,如果真实病害经常被拒识,就降到0.3。

还有一个进阶技巧:把模型保存成torchscript或ONNX格式,推理时不再依赖原始Python模型定义。特别是拿到边缘设备上用的时候,ONNX的跨框架兼容性和推理速度都更稳。导出ONNX一次就够:

python -c "import torch; model = create_model(...); model.load_state_dict(torch.load('models/best_model.pth')); model.eval(); dummy = torch.randn(1,3,224,224); torch.onnx.export(model, dummy, 'model.onnx')"

导出后最好用onnxruntime验证一次输出一致,别等部署到现场才发现算子不兼容。我个人的习惯是:把所有类别打印出来,提前拍几张“模型应该不认识”的图片测阈值,确认低置信度样本会不会被拒识。这比单纯调高准确率更接近田间使用的真实场景。栽过跟头才知道,识别模型真正值钱的不是准确率小数点后两位,而是它在没见过的环境里敢不敢说“我不知道”。希望这篇笔记能把你在下载压缩包后可能踩的坑提前填上,让这个python项目真正成为能跑进田里的工具,而不是停留在训练日志里的数字游戏。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 9:41:02

智能体技能设计实战:从提示词到Agent能力封装

做了快两年的AI应用开发,我越来越觉得,真正决定一个智能体项目上限的,往往不是接入了多强的模型,而是“agent-skills”的设计质量。什么意思呢?同一个大模型,有人接出来的智能体能力极强,可以稳…

作者头像 李华
网站建设 2026/10/14 9:40:43

【AI大模型进阶】微调中的过拟合表现:训练集 loss 降了,测试集降了吗?

【AI大模型进阶】微调中的过拟合表现:训练集 loss 降了,测试集降了吗? 集 loss 降了,测试集降了吗? 写在前面 大模型微调不管是全参数微调、LoRA 微调、QLoRA 低秩微调,大家最直观的监控指标就是损失 loss。很多人训练的时候看到训练 loss 一路持续下降,就默认模型效…

作者头像 李华
网站建设 2026/10/14 9:40:01

裸机STM32移植LwIP实现PING与TCP Client

简介:本资源是一份面向STM32嵌入式开发者的LwIP协议栈实战工程,聚焦无操作系统环境下以太网外设的底层移植与通信验证,适用于具备C语言基础和STM32标准库开发经验的中级进阶学习者。项目基于STM32F429IGT6主控与LAN8720A PHY芯片,…

作者头像 李华
网站建设 2026/10/14 9:39:55

ASA原理与电子维修实战:01 什么是ASA:从电气响应到诊断证据

第1章 什么是ASA:从电气响应到诊断证据 本章导言:维修不只是测量,更是理解电路的响应 在电子设备维修中,我们经常需要回答一个问题:怎样判断一个断电电路是否存在异常? 传统维修通常从测量开始。使用万用表测量电阻、二极管压降和电容,检查电源与地之间是否短路,或者…

作者头像 李华
网站建设 2026/10/14 9:39:54

搜索结果里的舆情负面,多久能淡化

搜索结果里的舆情负面淡化时间取决于处置动作、内容形态、平台规则等多因素,不存在统一时限,需结合具体场景判断。舆情负面淡化的核心影响因素 舆情负面的淡化周期并非固定,核心取决于三个维度:一是内容本身的侵权性与传播广度&am…

作者头像 李华
网站建设 2026/10/14 9:39:45

微信聊天记录提取与解密实战:SQLCipher库、dat图片还原到AI语料构建

简介:微信聊天记录提取与分析工具是一款面向程序员、数据分析学习者和课程作业开发者的含完整源代码的实用软件,可提取微信聊天记录并导出为 HTML、Word、CSV 等格式,同时生成年度聊天报告和沟通行为统计,用于个人记录回顾、沟通习…

作者头像 李华