资讯动态

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

发布时间:2026/10/14 0:15:39 来源:尧图企业网站定制
简介这是一套面向计算机相关专业学生与项目实战学习者的农作物病虫害识别分类完整项目包可直接用于毕业设计、课程设计或期末大作业。项目以Python实现涵盖数据加载、模型构建、训练验证等环节并附带数据集与使用说明下载后即可运行省去从零搭建的繁琐。压缩包共96个文件以76个py源码文件为主辅以15个pyc编译文件、2个zip子包、2个txt日志与1个md说明文档整体约25.1MB目录中可见dataloader、models、config等模块模型部分包含resnet、efficientnet、swin_transformer、vision_transformer等多种骨干网络实现便于读者理解分类任务的完整流程与网络结构。目前已有2167人学习下载说明该方案在同类毕设选题中具有较高参考价值。读者可据此掌握数据预处理、模型训练与验证的代码组织方式并借助说明文档快速复现结果适合作为入门深度学习项目实战的练手素材。1. 农作物病虫害识别项目从一份源码包到能跑通的分类模型拿到「基于python的农作物病虫害识别分类项目源码数据集使用说明.zip」这类资源多数人的第一反应是解压、找train.py、直接python train.py然后被一堆ModuleNotFoundError和路径报错劝退。这个标题背后其实是一条完整的图像分类落地链路农作物叶片图像 → 数据清洗与增强 → 迁移学习建模 → 训练调参 → 推理部署。它解决的是「不想从零标注、从零搭网络但又要一个能识别稻瘟病、玉米锈病、早疫病等常见病虫害的分类器」这个具体诉求。适合两类人一是刚学完 python 基础、想找一个真实数据集练手的入门者二是需要快速验证农业视觉方案可行性的工程师。下面我按自己复现这类项目的顺序把每一步拆开讲清楚。2. 先看清数据集的底子类别分布、图像质量与划分陷阱2.1 农作物病虫害数据集通常长什么样这类项目配套的数据集绝大多数是 PlantVillage 风格的叶片特写单一叶片居中、背景干净、按「作物_病害」命名文件夹比如Tomato___Late_blight、Corn___Common_rust。类别数一般在 10 到 38 之间单类几百到几千张不等总量常见在 2 万到 5 万张。图像分辨率参差从 256×256 到 1024×1024 都有格式以 JPG 为主。先别急着训练第一步是把数据集的真实结构摸清楚。我一般会写个脚本统计每个类别的数量和尺寸分布因为类别不平衡是这类数据集最常见的暗坑——健康叶片样本往往远多于某几种罕见病害直接训练会让模型偏向多数类。import os from collections import Counter from PIL import Image data_root data/plant_disease # 按实际解压路径改 counter Counter() size_stat [] for cls in sorted(os.listdir(data_root)): cls_dir os.path.join(data_root, cls) if not os.path.isdir(cls_dir): continue imgs [f for f in os.listdir(cls_dir) if f.lower().endswith((.jpg, .jpeg, .png))] counter[cls] len(imgs) # 抽样看尺寸避免全量读图太慢 for f in imgs[:5]: with Image.open(os.path.join(cls_dir, f)) as im: size_stat.append(im.size) print(类别数:, len(counter)) for k, v in counter.most_common(): print(f{k:40s} {v}) print(尺寸样本:, Counter(size_stat).most_common(5))这段代码做两件事统计每类图片数量抽样统计分辨率分布。data_root指向解压后的数据集根目录imgs[:5]是每类只抽 5 张看尺寸避免几万张图全读一遍。跑完你会得到一张类别分布表如果最大类和最小类差距超过 5 倍后面就得在采样或损失函数上做处理。2.2 训练集/验证集/测试集怎么划分才不翻车很多人直接random_split按 8:1:1 切这在 PlantVillage 上问题不大但如果你的数据集里同一片叶子被拍了多张连拍、不同角度随机切会导致训练集和验证集出现近乎重复的样本验证准确率虚高到 99%一上真实场景就崩。这是血泪经验。正确做法是按「来源」分组再切如果文件名或目录里能看出拍摄批次、地块编号就按组切看不出来至少保证同一类里做分层抽样stratified split让每个集合的类别比例一致。import numpy as np from sklearn.model_selection import train_test_split paths, labels [], [] for idx, cls in enumerate(sorted(os.listdir(data_root))): cls_dir os.path.join(data_root, cls) if not os.path.isdir(cls_dir): continue for f in os.listdir(cls_dir): if f.lower().endswith((.jpg, .jpeg, .png)): paths.append(os.path.join(cls_dir, f)) labels.append(idx) # 先切出测试集再从剩余切验证集保证分层 X_train, X_test, y_train, y_test train_test_split( paths, labels, test_size0.15, stratifylabels, random_state42) X_train, X_val, y_train, y_val train_test_split( X_train, y_train, test_size0.15, stratifyy_train, random_state42) print(len(X_train), len(X_val), len(X_test))stratifylabels是关键参数它保证切分后每个集合的类别比例和原始一致。random_state42固定随机种子方便复现。切完把三个列表存成 txt 或 csv后续训练直接读别每次重新切——否则你调参时验证集一直在变指标没法对比。提示如果数据集自带train/val/test目录优先用它自带的划分别自己重切作者的划分往往已经规避了同源泄漏。3. 用迁移学习搭分类模型为什么选 ResNet/EfficientNet 而不是自己堆 CNN3.1 迁移学习的选型理由农作物病虫害数据集规模通常不足以从零训练一个深网络。自己堆 5 层 CNN准确率可能卡在 85% 上不去换成 ImageNet 预训练的 ResNet50 或 EfficientNet-B0冻结主干只训分类头往往两三个 epoch 就能到 95% 以上。原因是底层卷积学到的边缘、纹理、颜色斑块特征对叶片病斑识别同样有效你只需要让网络学会「把这些特征映射到你的类别」。选型上ResNet50 稳、显存占用中等、社区资料多适合第一次跑通EfficientNet-B0 参数少、精度接近适合显存紧张或要部署到边缘设备。我一般先用 ResNet50 跑基线确认流程通了再换轻量骨干做对比。import torch import torch.nn as nn from torchvision import models def build_model(num_classes, backboneresnet50, freezeTrue): if backbone resnet50: model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V2) if freeze: for p in model.parameters(): p.requires_grad False in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) elif backbone efficientnet_b0: model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1) if freeze: for p in model.parameters(): p.requires_grad False in_features model.classifier[1].in_features model.classifier[1] nn.Linear(in_features, num_classes) return model model build_model(num_classes15, backboneresnet50, freezeTrue) print(sum(p.numel() for p in model.parameters() if p.requires_grad))weights...IMAGENET1K_V2加载预训练权重freezeTrue冻结主干只让最后的全连接层参与训练。model.fc是 ResNet 的分类头替换成你的类别数。最后一行打印可训练参数量冻结后应该只有几万到几十万说明主干确实冻住了。如果显存够、数据量上万可以在训练后期解冻部分层做微调通常能再涨一两个点。3.2 数据增强与 DataLoader 配置叶片图像增强要贴合真实拍摄变化旋转、水平翻转、轻微颜色抖动是安全的垂直翻转要谨慎因为叶片正反面纹理不同翻了可能变成不存在的样本。裁剪缩放统一到 224×224ResNet 标准输入。from torchvision import transforms from torch.utils.data import DataLoader, Dataset from PIL import Image train_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) class LeafDataset(Dataset): def __init__(self, paths, labels, tf): self.paths, self.labels, self.tf paths, labels, tf def __len__(self): return len(self.paths) def __getitem__(self, i): img Image.open(self.paths[i]).convert(RGB) return self.tf(img), self.labels[i] train_loader DataLoader(LeafDataset(X_train, y_train, train_tf), batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(LeafDataset(X_val, y_val, val_tf), batch_size32, shuffleFalse, num_workers4)Normalize的均值和方差是 ImageNet 的统计值用预训练权重就必须用这套别自己算。RandomResizedCrop(224, scale(0.7,1.0))让模型见到不同尺度的病斑scale下限别低于 0.5否则裁得太狠病斑可能被裁没。num_workers4在 Linux 上提速明显Windows 上如果报错就改成 0。3.3 训练循环与关键参数import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max10) for epoch in range(10): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() out model(imgs) loss criterion(out, labels) loss.backward() optimizer.step() running_loss loss.item() * imgs.size(0) scheduler.step() print(fepoch {epoch1} loss {running_loss/len(X_train):.4f})AdamW的weight_decay1e-4是正则化防止过拟合lr1e-3适合只训分类头的场景如果解冻主干要降到 1e-4 量级。CosineAnnealingLR让学习率按余弦曲线衰减T_max设成总 epoch 数。训练时盯 loss 曲线如果训练 loss 降但验证 loss 升就是过拟合加增强或加 dropout如果两个都不降检查学习率是不是太大或标签有没有对错。4. 评估与推理别只看准确率混淆矩阵才是照妖镜4.1 分类评估指标怎么读准确率在类别不平衡时会骗人。假设健康叶片占 70%模型全预测成健康也有 70% 准确率。所以必须看每类的 precision、recall、F1以及混淆矩阵——它能告诉你模型把哪种病认成了哪种病这在农业场景里至关重要把「晚疫病」认成「早疫病」可能导致用错药。from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) preds model(imgs).argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, digits4)) cm confusion_matrix(all_labels, all_preds) print(cm)model.eval()关掉 dropout 和 batchnorm 的训练行为torch.no_grad()省显存。classification_report直接给出每类指标重点看 recall 低的类——那意味着漏检多。混淆矩阵里如果某两类互相错得厉害说明它们视觉特征接近要么加数据要么在损失函数里给这两类加权。4.2 单张图片推理与部署前检查def predict(img_path, model, tf, idx_to_cls): model.eval() img Image.open(img_path).convert(RGB) x tf(img).unsqueeze(0).to(device) with torch.no_grad(): prob torch.softmax(model(x), dim1)[0] conf, idx prob.max(0) return idx_to_cls[idx.item()], conf.item() idx_to_cls {i: c for i, c in enumerate(sorted(os.listdir(data_root)))} label, conf predict(test_leaf.jpg, model, val_tf, idx_to_cls) print(label, round(conf, 4))推理用的 transform 必须和验证集一致别用训练时的增强版否则结果不稳定。softmax把 logits 转成概率conf是置信度。实际部署时建议设一个置信度阈值比如低于 0.6 就输出「不确定请人工复核」比硬给一个错答案强。注意idx_to_cls的类别顺序必须和训练时labels的编码顺序完全一致否则预测结果会整体错位。最稳妥的做法是训练时就把这个映射存成 json推理时直接读。5. 避坑与排查复现这类项目最容易栽的 5 个地方现象一一运行就报ModuleNotFoundError: No module named torch。原因环境里没装 PyTorch或者装的是 CPU 版但代码写了.cuda()。 解决先pip install torch torchvision去 PyTorch 官网按你的 CUDA 版本选对应命令。代码里统一用device torch.device(cuda if torch.cuda.is_available() else cpu)别硬写.cuda()。现象二训练准确率 99%换一批真实照片全错。原因数据集背景太干净模型学的是背景而不是病斑或者训练验证同源泄漏。 解决加背景随机裁剪、颜色抖动检查划分是否按来源分组拿手机拍几张真实叶片测一下别只信验证集。现象三loss 变成 nan。原因学习率太大或者输入没归一化或者某类标签越界。 解决先把 lr 降到 1e-4 试确认Normalize用对了打印labels.max()看有没有超过类别数。现象四DataLoader卡住不动。原因num_workers在 Windows 或某些容器环境下的多进程问题。 解决把num_workers改成 0先跑通再说Linux 上如果卡检查共享内存--shm-size。现象五显存不够CUDA out of memory。原因batch_size 太大或输入分辨率太高。 解决batch_size 从 32 降到 16 或 8输入从 224 降到 192换 EfficientNet-B0 这种轻量骨干开启混合精度训练。6. 把模型压到能落地ONNX 导出与置信度阈值调优跑通训练只是第一步真要落地到田间地头的手机或边缘盒子得把 PyTorch 模型转成 ONNX再用 ONNX Runtime 推理速度和兼容性都好很多。import torch.onnx model.eval() dummy torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy, leaf_disease.onnx, input_names[input], output_names[logits], dynamic_axes{input: {0: batch}, logits: {0: batch}}, opset_version12, ) print(exported)dynamic_axes让 batch 维度可变部署时能一次处理多张。opset_version12兼容性较好。导出后用onnxruntime加载验证一遍输出是否和 PyTorch 一致别导完就不管。import onnxruntime as ort import numpy as np sess ort.InferenceSession(leaf_disease.onnx, providers[CPUExecutionProvider]) x np.random.randn(1, 3, 224, 224).astype(np.float32) out sess.run(None, {input: x})[0] print(out.shape, out.argmax())置信度阈值这块我一般会在验证集上扫一遍把阈值从 0.5 到 0.9 逐档试看「拒识率」和「错分率」的权衡。农业场景宁可拒识让人工复核也别把病认错导致用药错误。下面这张表是我在某次 15 类任务上的实测参考置信度阈值拒识率已识别样本准确率0.503%96.2%0.7011%98.1%0.8524%99.3%0.9038%99.6%阈值越高模型越保守错分越少但拒识越多。实际部署我通常选 0.7 左右兼顾覆盖率和可靠性。这套流程我踩过最大的坑是导出 ONNX 时忘了model.eval()导致 dropout 还在生效推理结果每次都不一样排查了半天。后来养成习惯导出前先eval()导出后拿同一张图对比 PyTorch 和 ONNX 的输出差超过 1e-3 就回去查。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑