ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

农作物病虫害识别实战:基于Python与CNLNet的深度学习图像分类项目

农作物病虫害识别实战:基于Python与CNLNet的深度学习图像分类项目 简介面向计算机相关专业毕业生及项目实战学习者这份基于Python的农作物病虫害识别分类项目包含完整源码、配套数据集与使用说明可直接用于毕业设计、课程设计或期末大作业。项目整合了EfficientNet、ResNet、Vision Transformer、Swin Transformer等主流视觉模型实现并提供训练脚本、演示Demo压缩包及训练日志便于对比不同网络在病虫害识别任务上的效果也方便二次开发与扩展。压缩包内共96个文件其中76个Python脚本为主要代码15个pyc为预编译模块另有2个zip演示/数据包、2个txt说明与日志、1个Markdown使用文档整体大小约25.09MB结构清晰、开箱即用。资源上线后已有128人学习下载适合希望快速获得可运行基线并深入理解模型训练流程的读者。1. 农作物病虫害识别分类项目先看这份 Python 源码包能帮你解决什么如果你正在为毕业设计选题发愁或者想找一个能完整跑通的图像分类实战项目这份基于 Python 的农作物病虫害识别项目值得你先花十分钟拆开看看。它不是那种只有几个 py 文件的教学 demo而是一套带有训练日志、预训练结构、数据集和演示程序的完整工程压缩包解开之后能直接从训练走到识别。项目核心是一个名为 CNLNet 的分类网络搭配从 EfficientNet、ResNet 到 Swin Transformer、Vision Transformer 这类主流骨干模型库适合做横向对比实验也适合直接拿来当毕设主体。下面我会把包里的文件结构、训练流程和个人踩过的坑一次讲清楚看完你就能判断它适不适合你的场景。2. 源码包拆解CNLNet 主体、timm 风格模型库与数据配套2.1 压缩包里的核心文件哪些是你要的哪些只是伴生库解压以后你会发现文件数量不少而且里面混着大量models、layers目录和*.pyc文件。第一次接触的人容易懵觉得这是不是打包混乱。其实这恰恰是这类项目常见的组织方式训练入口和模型定义分开模型库按骨干网络分文件pyc是编译后的 Python 字节码运行速度更快也起到一定源码保护作用。先说你要重点关心的几个文件文件/目录作用我的使用建议train_val.py训练与验证入口主改文件所有参数都在这里调cnlnet_5CNL.pycCNLNet 网络核心定义直接调用不要反编译dataloader.pyc数据加载器一般不用改但要注意数据路径CNLtrainlog.txt训练日志复现时对照用能看损失和准确率走势f1.txt每轮类别 F1 分数记录判断哪些类别识别差aggregation_zeropad*.pyc上下文聚合模块CNLNet 的关键组件cupy_layers基于 cuPy 的自定义层需要对应 CUDA 版本环境models/大量骨干网络定义做对比实验时从这里换模型aidemo.zip/ipdemo.zip演示程序毕设答辩演示用README.md使用说明建议第一个读它这里有个很容易误判的点models目录里那些efficientnet.py、resnet.py、swin_transformer.py、vision_transformer.py、xcit.py、rexnet.py、cotnet.py等文件其实是标准的人工智能模型库结构类似 timm不是这个项目自己发明的算法。它们的作用是给 CNLNet 提供可替换的骨干特征提取网络。也就是说你可以在不改动整体训练框架的前提下把骨干网络从 ResNet 换成 Swin Transformer观察同一个数据集上不同骨干的表现这个工作在毕设论文里是非常好的一章实验。2.2 CNLNet 结构猜测5 个上下文聚合层在做什么cnlnet_5CNL.pyc中间的5CNL我理解是 “5 个上下文聚合层” 的意思。结合旁边那一堆aggregation_zeropad.pyc、aggregation_zeropad_dilate.pyc、aggregation_zeropad_mix_merge.pyc文件来看这个网络的特色在于对特征图做多尺度的上下文信息聚合简单说就是把每个像素周边更大范围的信息融合进来帮助模型识别那些只靠局部纹理难以判断的病害区域。这种做法在病虫害识别里很合理。比如水稻稻瘟病的病斑早期可能只是叶片上一个针尖大的褐色点局部特征和正常叶片差异极小但如果把视野放宽到周围叶脉的分布和颜色过渡模型就能找到更稳定的判别依据。zeropad表示聚合时采用零填充来保持特征图尺寸dilate则用空洞卷积扩大感受野mix_merge大概率是把不同空洞率的聚合结果做融合。这个思路和图像分割里的 ASPP、PSPNet 有相似之处但在分类网络里通过旁支形式嵌入属于轻量级上下文增强方案。从实操角度看你不需要重写这些层只需要在train_val.py里确认 CNLNet 的输入尺寸、类别数和骨干通道数是否和你的数据集匹配。如果默认是 224×224 输入、1000 类 ImageNet 预训练初始化你要做的就是把最后分类头改成自己的类别数这个通常写在训练脚本的num_classes参数里不同作者写法不太一样可能叫num_classes、n_classes或者classes若找不到就直接打开cnlnet_5CNL.pyc同目录的调用代码看传入参数。2.3 配套的网络库为什么毕设项目要塞这么多模型文件刚开始我觉得这属于打包不干净后来才意识到这是故意的这份资源想让你做“多模型对比实验”这在课程设计和期末大作业评阅时是加分项。你可以写“在相同数据集与训练参数下对比 ResNet50、EfficientNet-B0 与 CNLNet 的 Top-1 准确率和 F1”再配上三张训练曲线截图论文第三章的实验设计就立住了。这些模型文件大多来自开源库的副本位于models/和layers/下。它们之间不是孤立的factory.py、registry.py负责注册模型名称features.py提取特征图输出helpers.py处理权重加载。如果你要换骨干网络常见做法是在train_val.py里找到类似from models.factory import create_model的导入然后通过--model命令行参数传模型名比如python train_val.py --model resnet50 --epochs 50 --batch-size 32 --lr 0.001部分模型定义文件里会内置default_cfgs字典里面写了 ImageNet 预训练权重的下载地址。国内网络环境下这些地址可能下载失败建议在运行训练前主动检查~/.cache/torch/hub/checkpoints/目录看到文件体积为零或者下载卡住不动就去手动下载后放进该目录并改成同名文件。3. 跑通训练流程从解压数据集到 train_val.py 出第一个 F13.1 环境准备Python 版本、CUDA 与 cuPy 的对应关系这是整个项目复现中最容易翻车的环节所以放在步骤第一步说。cupy_layers目录的存在意味着源码依赖 cuPycuPy 是 NVIDIA CUDA 的 Python 封装版本必须和本机 CUDA 严格对齐。先看显卡驱动支持的 CUDA 版本再装对应 cuPy顺序不能倒。我在一台 CUDA 11.4 的机器上试过直接pip install cupy结果装出来的是 cuPy 12.x底层要求 CUDA 12import 阶段直接报错。后来改成按 CUDA 版本安装才解决对应关系大致如下# CUDA 11.2 ~ 11.4 用 cuPy 11.x pip install cupy-cuda11x # CUDA 12.0 及以上用 cuPy 12.x pip install cupy-cuda12x装完之后验证一下能不能正常导入再进下一步。很多人在这一步卡住是因为压根不知道 cuPy 是这东西的隐含依赖README 里如果作者没写你只能从cupy_layers目录名和报错信息里反推。PyTorch 版本建议用 1.10 到 2.0 之间的版本太新的版本本身没问题但部分旧模型文件里用了老式 API比如torch.nn.functional.affine_grid的旧参数形式升级后行为会变。如果你是新装机建议直接建一个独立环境用 conda 管理依赖conda create -n pest python3.8 -y conda activate pest pip install torch1.13.1 torchvision0.14.1 --extra-index-url https://download.pytorch.org/whl/cu117 pip install cupy-cuda11x pip install numpy opencv-python pillow pandasPython 版本方面我实测 3.8 最稳。不要用 3.10 以上跑带老版本pyc的项目pyc文件是编译产物不同 Python 小版本的字节码协议不完全兼容轻则警告重则ImportError还不好定位。3.2 数据目录组织ImageNet 风格的文件摆放方式解压后先看数据集的目录结构这类分类项目大概率先按类名建子文件夹建议整理成下面的样式和dataloader.pyc的默认读取逻辑基本吻合data/ ├── train/ │ ├── rice_blast/ │ │ ├── 001.jpg │ │ ├── 002.jpg │ │ └── ... │ ├── rice_bacterial_blight/ │ └── ... └── val/ ├── rice_blast/ └── ...每个子文件夹的名字就是类别名训练时dataloader会根据文件夹名自动生成标签。这里有一个需要确认的点训练集和验证集的类别文件夹命名必须完全一致大小写都不能差。我第一次跑的时候训练集叫Rice_Blast验证集叫rice_blast脚本没报错但验证准确率始终在 5% 以下后来才发现是ImageFolder按字母序给同一个类别分配了两个不同的标签索引等于模型在猜。如果你的数据集划分不是这种train/val结构而是只有一个总文件夹外加一个labels.csv那就要在train_val.py里找DatasetFolder或者自定义Dataset类的代码段去适配。常见处理方式是把 CSV 读进来构建类别映射表import pandas as pd from PIL import Image from torch.utils.data import Dataset class PestDataset(Dataset): def __init__(self, df, img_dir, transformNone): self.df df.reset_index(dropTrue) self.img_dir img_dir self.transform transform # 用类别名生成从字符串到整数索引的映射 self.class_names sorted(self.df[label].unique()) self.class_to_idx {name: i for i, name in enumerate(self.class_names)} def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] img Image.open(f{self.img_dir}/{row[filename]}).convert(RGB) label self.class_to_idx[row[label]] if self.transform: img self.transform(img) return img, label这段代码的核心是class_to_idx字典它把字符串类别转换成0到N-1的整数索引PyTorch 的交叉熵损失只接受整数标签。如果你的 CSV 里已经直接给了数字标签就把class_to_idx那行改成直接读取数字列否则会出现expected dtype long的类型报错。__getitem__返回的是(图像张量, 标签)的元组这是 PyTorch Dataset 的标准约定dataloader 在迭代时会自动把多个样本堆叠成 batch这个结构不要改动只改内部的文件读取和标签解析即可。3.3 train_val.py 关键参数与一次完整的训练启动打开train_val.py后先找argparse段落这是所有可调参数的入口。以下是一份常见参数对照表具体名称以你压缩包里的实际代码为准参数名参考取值说明--epochs50~100数据集不大时 50 轮足够看验证集是否继续下降--batch-size16~32显存 8G 以内建议 16CNLNet 加注意力模块吃显存--lr0.001~0.01用预训练模型做微调时取 0.001从零训练取 0.01--num-classes你的类别数默认可能写了 1000这是最常见的报错来源--input-size224 或 256和预处理逻辑配套不要单独改一边--checkpoint./checkpoint/断点续训和最终模型保存目录--devicecuda或cpu没 GPU 也能跑但速度慢到无法接受一个典型的训练启动命令如下python train_val.py --dataset_path ./data --arch cnlnet_5CNL --epochs 60 --batch-size 16 --lr 0.001 --num-classes 6 --input-size 224命令里的--arch cnlnet_5CNL指定使用 CNLNet框架通过create_model去models/目录下找对应实现--dataset_path ./data告诉脚本去哪里读数据集这个路径写错会在启动后一两秒内抛出文件不存在错误并且往往不是第一个报错--num-classes 6表示你的病虫害类别数为 6如果写错和实际数据不符训练过程不报错但最后输出的混淆矩阵行列数对不上。如果你的训练脚本用的是 YAML 配置而不是命令行参数就去找config.pyc同级的*.yaml文件修改model_arch和data_path字段效果一样。训练跑起来以后如果看到一轮时间超过十分钟不要再干等去检查 GPU 利用率nvidia-smi -l 1正常来说利用率应该在 90% 上下波动如果只有 20% 多说明数据加载线程卡住了常见原因有两个一是num_workers设置过高导致磁盘 I/O 争抢建议从 4 开始往下调二是读取了尚未解压完全的 zip 数据集文件把数据完整解压到 SSD 后再跑一次。3.4 训练日志与 f1.txt怎么判断模型真的训好了CNLtrainlog.txt记录了每一轮的训练损失、验证损失和 Top-1 准确率f1.txt则按类别记录 F1 分数。这两个说明书级文件是复现实验的“标尺”先看损失曲线形状是不是正常下降再看最终 F1 是否达到你心里的及格线。判断模型训练正常我一般会看三个指标第一训练损失前 10 轮是否明显下降。如果前 10 轮损失纹丝不动且数值一直在 2.0 以上徘徊很可能学习率设置过大导致梯度震荡或者预处理归一化参数和网络预期不匹配。第二验证准确率是不是跟着训练损失一起涨。要是训练损失降了验证准确率停在原地甚至下降说明模型过拟合了常见对策是调大--weight-decay或者把数据增强里的随机裁剪比例调小。第三f1.txt里类别间 F1 差距不能过大。比如某一类 F1 有 0.92另一类只有 0.45那这一类大概率出现了训练样本太少、背景太复杂、或者标注不一致的问题后面要针对性地补样本。# 每训练完一轮查看 f1.txt 末尾几行确认各类别表现 tail -n 20 f1.txt4. 避坑与排查这份资源最容易翻车的五个点位4.1 pyc 文件与 Python 版本水土不服现象import cnlnet_5CNL的时候直接抛出ValueError: source code string cannot contain null bytes或者ImportError: bad magic number程序根本跑不起来。原因pyc是编译产物Python 3.8 编译的pyc用 3.10 解释器去加载字节码版本对不上就会报这个错。解决装一个 Python 3.8 环境重跑。如果一定要用高版本 Python唯一的路线是找到同名的.py文件替换没有.py的话就别折腾反编译了直接换环境最省时间。注意cupy_layers目录下的__init__.pyc和constants.pyc也一样整个项目尽量统一在一个解释器版本下跑。4.2 cuPy 与 CUDA 版本不匹配导致 import 崩溃现象训练脚本走到from cupy_layers import ...时崩溃报错信息类似cupy_backends.cuda.api.runtime.CUDARuntimeError: cudaErrorInsufficientDriver。原因cuPy 是编译绑定到特定 CUDA 版本的本机驱动支持的 CUDA 版本低于 cuPy 要求或者根本装错了 cupy 包。解决先跑nvidia-smi看右上角 CUDA Version再按第一节的方式装对应版本。装完不做任何训练先python -c import cupy能过再继续。这一步属于典型的“环境依赖黑匣子”一旦忽视后面所有的报错排查都会绕远路。4.3 数据集路径写对目录但加载出来是 0 张图现象启动训练日志显示Found 0 images in classes: [...]直接结束。原因dataloader.pyc内部用 torchvision 的ImageFolder读取数据集它只认jpg/jpeg/png等常规图片格式如果你的图片是.tif、.bmp或者文件名带中文默认加载函数会把它们过滤掉导致数量为零。解决把图片统一转成.jpg文件名改成纯英文数字。批量转换可以写一个几行的 Python 脚本from PIL import Image from pathlib import Path root Path(./data/train) for img_path in root.rglob(*.tif): # 目标文件直接替换后缀命名保持同一风格避免后续解析混乱 dst img_path.with_suffix(.jpg) im Image.open(img_path).convert(RGB) im.save(dst, quality95) # 转换完成后删除原图防止 ImageFolder 同时读取两种格式造成重复 img_path.unlink()这段代码先把tif转成 RGB 模式的jpg再用with_suffix(.jpg)得到新路径注意原路径里剩余文件名部分写入dst时保持和原图同名转换完成后删除原始文件避免同一个样本被读两次。如果你不想删源文件就把src和dst放在不同目录。数据增强别在这一步做直接转格式和颜色模式就行。4.4 显存不够但不敢调 batch-size现象训练刚开始几轮正常某一步突然报CUDA out of memory程序退出。原因默认batch-size设置偏高加上 CNLNet 的注意力模块本身占用大量显存前几轮能跑纯属侥幸后面特征图缓存累积到一定程度就爆了。解决把--batch-size从 32 改成 16还不稳就改成 8同时看脚本里有没有--accumulation-steps梯度累积参数。另外把输入尺寸从 256 改到 224对显存的影响是平方级别的有时候这个改动比调 batch 更立竿见影。如果用的是 Windows 系统还建议在train_val.py头部加上if __name__ __main__: # Windows 下多进程数据加载需要这段保护否则 num_workers 0 会无限递归 import multiprocessing multiprocessing.freeze_support()4.5 训练正常但保存的模型加载后准确率崩塌现象训练时验证集 Top-1 到 0.85重新加载.pth权重再跑一次准确率掉到 0.3 以下。原因保存权重时同时存了model.state_dict()和预处理参数但重新加载时走了另一套预处理归一化的均值和标准差对不上或者输入尺寸不一致。解决确认保存模型时是否把预处理参数一起打包加载模型时一定要走和训练时完全一致的预处理流程。我自己习惯把预处理信息写进 checkpoint 的附加字段避免过两个月就忘记当时的设置。5. 进阶用法换上自己的数据集做迁移学习把 demo 变成毕设演示5.1 微调 CNLNet用训练好的权重初始化你的新任务当你手里有一套自己的植物病害图片数量不一定多比如每类两三百张这时不要从零训练 CNLNet直接加载项目自带的训练权重做微调效果更好。常见做法是先把最后一层分类头替换成你的类别数然后冻结骨干网络只训练分类头跑 20 轮左右后再解冻全部层做低学习率微调。import torch import torch.nn as nn from models.factory import create_model # 先用 num_classes1000 创建原结构再替换分类头 model create_model(cnlnet_5CNL, pretrainedTrue, num_classes1000) num_ftrs model.classifier.in_features # 或者其他名称以你的实际模型为准 model.classifier nn.Linear(num_ftrs, 6) # 第一阶段只训练分类头 for name, param in model.named_parameters(): # 属于 classifier 层的参数保持可训练其余全部冻结 param.requires_grad classifier in name optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr0.001)这段代码是迁移学习的固定套路pretrainedTrue会加载 ImageNet 或原项目数据集的预训练权重然后model.classifier nn.Linear(num_ftrs, 6)保证新任务的输出维度和你的类别数一致。filter里的requires_grad判断确保优化器只更新分类头参数这个阶段学习率可以给大一点不容易破坏底层特征。第二阶段解冻全部层时把学习率调到 0.0001 量级并且建议用torch.optim.lr_scheduler.ReduceLROnPlateau验证集 F1 连续三轮不涨就自动降学习率省得手动盯曲线。这里的in_features名称需要根据实际模型调整如果你替换后报维度不匹配的错就打开网络定义文件找最后一层全连接的名字可能是fc、head或output。另外换了新数据集后类别名不要包含中文和空格f1.txt里按类别名排列时解析会更方便。5.2 数据增强策略小数据集下提高 F1 最直接的手段病虫害图片和通用物体识别有个明显区别病斑在叶片上的位置随意性很强同一张叶子旋转 90 度仍然属于同一个病。因此数据增强策略里旋转和翻转非常有效色彩抖动要适度因为某些病害区分恰好依赖颜色信息比如黄叶和绿叶。from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.6, 1.0)), transforms.RandomRotation(30), transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.1, hue0.05), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])参数说明scale(0.6, 1.0)控制随机裁剪面积占比保留 0.6 到 1.0 的比例能模拟近距离和远距离拍摄RandomRotation(30)允许角度在正负 30 度内旋转超过这个范围会裁掉大量叶片边缘信息ColorJitter的hue只给 0.05因为色调稍微偏移就会让病斑颜色失真这个参数宁可小不可大。验证集和测试集绝不使用上述随机变换只保留 Resize、CenterCrop、ToTensor 和 Normalize否则验证指标会虚高。5.3 把模型跑成演示程序给老师看比讲论文更快压缩包里的aidemo.zip和ipdemo.zip提供了现成的演示程序入口通常基于 Flask 或者 Tkinter 写一个上传图片→输出类别和置信度的界面。如果你觉得它自带的界面风格太旧可以自己写一个更简洁的版本核心推理代码只要十几行import torch import torch.nn.functional as F from torchvision import transforms from PIL import Image def predict(model, image_path, class_names): # 与训练保持完全一致的预处理流程这一步省略会导致结果全错 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]), ]) img transform(Image.open(image_path).convert(RGB)).unsqueeze(0) with torch.no_grad(): logits model(img) probs F.softmax(logits, dim1) top_idx probs.argmax().item() return class_names[top_idx], probs[0][top_idx].item()unsqueeze(0)的作用是把单张图片变成 batch 维度为 1 的张量model(img)才能正常前向F.softmax把输出变成概率分布argmax()取概率最大的那个类别索引。演示程序里建议把 Top-3 结果全部显示出来并标上概率百分比因为有些病害在视觉上本来就极其相似只显示第一名容易让懂行的评委质疑模型的可信度。5.4 我现在的固定习惯拿到任何复现包先做这三件事第一解压后立刻跑python -c import cupy; print(cupy.__version__)和python -c import torch; print(torch.__version__)版本不对直接换环境绝不在原环境里硬调。第二用.pyc文件里的时间戳或 README 的开头几行判断作者用的 Python 版本然后把pyc文件和解释器版本对齐这件事写进备忘录。第三第一次训练永远只用 100 张图跑 3 轮专门验证数据加载、模型前向和损失计算三条链路是否通畅全部通过再加大数据量和轮数。从那以后我每次拿到这类打包资源都会强制走一遍这套流程它在数不清的源码包里帮我省下了至少一整天的排错时间希望也能帮到你。本文还有配套的精品资源点击获取
返回列表