ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python病虫害识别项目实战:PyTorch图像分类从训练到部署

Python病虫害识别项目实战:PyTorch图像分类从训练到部署 简介基于Python的农作物病虫害识别分类项目提供完整源码、数据集与使用说明主要面向计算机相关专业准备毕业设计的学生以及需要项目实战练习、课程设计或期末大作业的学习者。项目内包含数据加载、模型定义、训练与验证等Python脚本覆盖多种图像分类网络结构编译后的pyc模块可直接调用另有打包的数据集与演示文件、训练日志和README说明文档目录结构层次清楚便于按模块学习或二次开发。压缩包共96个文件约25.09MB已有127人学习下载。代码经过严格调试下载即可运行可直接作为毕设项目或课程设计基础。结合训练日志与F1指标记录读者能够快速评估模型在农作物病虫害识别上的表现并据此调优也适合作为入门深度学习的实战范例。1. 拿到这个 Python 病虫害识别项目后先分清它到底是什么一个做毕设或者课程设计的学生下载一个「基于 Python 的农作物病虫害识别分类项目源码数据集使用说明.zip」第一反应往往是想直接跑通、看到界面。解压之后发现里面既有train_val.py这样的训练脚本又有models目录下几十个模型文件还有一个cnlnet_5CNL.pyc的编译后网络定义大概率会懵。这个项目不是简单的网页 Demo而是一个基于 PyTorch 的图像分类框架选好 backbone、划分好数据集之后交给train_val.py完成训练和验证最后用ipdemo.zip或aidemo.zip里的推理脚本对单张图片做预测。对正在做农作物病虫害课题的计算机专业学生来说它省去了从零搭建数据流和训练流程的时间但对只想点开看界面的人会觉得它绕。本文按目录结构 → 数据加载 → 训练参数 → 模型选型 → 效果验证的顺序拆每一步都给出可以直接抄的命令和参数。2. 目录结构与数据集组织先看懂 pyc、models 和 config 各自干什么2.1 解压之后先看文件类型别急着跑代码拿到压缩包第一步是解压并观察文件构成。用ls -lh能看到每个文件的体积这能快速判断哪些是核心脚本、哪些是大体积模型权重unzip 基于python的农作物病虫害识别分类项目源码数据集使用说明.zip -d crop_pest cd crop_pest ls -lh从输出中可以直观分辨几类内容根目录下的train_val.py是训练入口config目录存放超参数配置models目录是模型定义库dataloader.pyc是数据加载逻辑的编译版本CNLtrainlog.txt和f1.txt是训练过程记录ipdemo.zip和aidemo.zip则是封装好的预测 Demo。之所以存在大量.pyc文件通常是发布者为了在隐藏部分实现细节的同时保证代码可运行Python 会自动在导入模块时加载这些字节码文件。遇到.pyc时不用慌它和.py在功能上等价只是不能直接阅读源码。models目录下还有一组有趣的命名resnet.py、efficientnet.py、vision_transformer.py、swin_transformer.py、cnlnet_5CNL.pyc。这说明项目作者把主流分类网络都汇总在一起目的是让使用者做对比实验时能一键切换 backbone。cupy_layers的出现则提示部分算子依赖 CuPy 进行 CUDA 加速这会在环境配置阶段带来额外的坑后面会具体讲。2.2 数据集怎么放才能被 dataloader 正确读到这类图像分类项目最标准的组织方式是ImageFolder结构即训练集和验证集下各自按类别建子目录每个子目录里放同类图片。比如要识别水稻稻瘟病、玉米叶斑病、小麦锈病目录结构就类似data/ ├── train/ │ ├── rice_blast/ │ ├── corn_leaf_spot/ │ └── wheat_rust/ └── val/ ├── rice_blast/ ├── corn_leaf_spot/ └── wheat_rust/dataloader.pyc内部大概率是基于torchvision.datasets.ImageFolder实现的它会把train下的每个子目录名自动映射成类别索引。映射关系可以用下面的脚本打印出来from torchvision import datasets train_set datasets.ImageFolder(data/train) print(train_set.class_to_idx) # 输出示例{corn_leaf_spot: 0, rice_blast: 1, wheat_rust: 2}这个打印出的字典就是预测阶段的idx_to_label反向映射依据。如果数据集文件夹层级与ImageFolder不匹配比如所有图片平铺在一个文件夹且类别在文件名里就会报Found 0 images in subfolders的错误。处理办法是写个脚本按文件名前缀重新整理目录或者自己继承torch.utils.data.Dataset重写__getitem__。想快速验证数据加载是否正常可以在命令行里直接跑一段检查代码统计图片数python -c from torchvision import datasets; dsdatasets.ImageFolder(data/train); print(len(ds.imgs))这段命令通过ds.imgs拿到全部样本路径和标签总样本数一目了然。实际使用中还要注意图片格式.jpg、.png都能被 PIL 后端读取但部分特殊格式如.tif需要指定loader参数。数据增广部分常规做法是在训练集上做随机翻转、随机裁剪和归一化验证集只做 Resize 和 CenterCrop这些逻辑都已经封装在.pyc里对用户来说只需要确保原始图片尺寸大于网络输入尺寸常见的是 224×224 或 256×256否则 Resize 之后画面会被严重拉伸。2.3 config 目录里改什么学习率和类别数根目录的config是配置包里面出现了config.pyc、constants.pyc和__init__.pyc说明运行时会从config导入常量。常见的设计是把NUM_CLASSES类别总数、DATA_ROOT数据集路径、OUTPUT_DIR日志和模型保存路径都集中在这几个文件里。如果它同时提供.py源码直接打开修改即可NUM_CLASSES 10 # 根据 data/train 下子目录数修改 DATA_ROOT data OUTPUT_DIR output如果没有.py只有.pyc就只能在train_val.py里通过命令行参数覆盖比如传入--num-classes 10。这个环节最常见的错误是类别数配置与数据目录不一致训练脚本初始化最后一层全连接时类别维度对不上直接报size mismatch。所以在动手训练前先运行上一小节的类别统计代码再和 config 里的NUM_CLASSES核对能省去后面排查的精力。3. train_val.py 训练流程解析从命令行参数到训练日志3.1 训练入口最关键的十个参数train_val.py是项目的核心脚本它的命令行参数设计决定了这个项目能否灵活适配不同机器和不同数据集。一般会包括--data-root、--model、--epochs、--batch-size、--lr、--pretrained、--gpu等开关。一个典型的运行命令是python train_val.py \ --data-root data \ --model resnet50 \ --epochs 100 \ --batch-size 32 \ --lr 0.001 \ --pretrained \ --gpu 0各参数含义如下--data-root指定数据所在根目录脚本会在其下寻找train和val子目录--model指定模型名称需要与models目录下注册的模型名字严格一致比如resnet50、efficientnet_b0或cnlnet_5CNL--epochs是总训练轮次病虫害数据集普遍在 1 万到 5 万张图之间100 轮通常够用--batch-size取决于显存大小1080Ti 在 ResNet50 上 32 是安全值Transformer 类模型建议降到 16--lr是初始学习率用预训练权重时 0.001 起从零训练时用 0.01--pretrained表示加载 ImageNet 预训练模型对病虫害识别很重要因为病虫害图片和自然图像的特征在浅层是共享的。3.2 训练循环里看不到但必须知道的三件事从项目命名看CNLtrainlog.txt是日志输出文件f1.txt专门记录 F1 指标。训练循环的常见逻辑是每个 epoch 结束跑一次验证集把 loss、accuracy、precision、recall、F1 同时写入两个文件。读取日志可以确认训练是否正常收敛tail -n 20 CNLtrainlog.txt这里tail -n 20显示最后 20 行重点看 loss 是否下降、accuracy 是否上升、验证 loss 有没有在某个 epoch 后反弹。f1.txt格式如果是每行一个浮点数可以用一行 Python 找到最佳 F1 对应的 epochwith open(f1.txt) as f: vals [float(x.strip()) for x in f if x.strip()] best_epoch vals.index(max(vals)) 1 print(fbest f1: {max(vals):.4f} at epoch {best_epoch})除了看日志还要理解脚本内部的权重保存策略。常规做法是只保留验证集 F1 最高的模型保存成best_model.pth如果每个 epoch 都存磁盘会快速膨胀。有的项目会同时存last_model.pth用于断点续训。3.3 cupy_layers 撞上无 GPU 环境时怎么办cupy_layers目录在models旁边这通常是自定义 CUDA 算子的集合比如特定方式的填充聚合操作。项目可能在部分模型尤其cnlnet_5CNL中引入了 CuPy 实现的自定义层。这意味着运行这些模型需要额外安装pip install cupy-cuda11x注意cuda11x要与本机 CUDA 版本匹配CUDA 12 就改成cupy-cuda12x。如果机器没有 NVIDIA GPU或不想折腾 CUDA 环境两个现实的替代方案是使用--model resnet50 --pretrained绕过所有依赖 CuPy 的自定义模型在train_val.py中检查是否有use_cupy之类的开关位有则置为False。如果作者把 CuPy 封装在cnlnet_5CNL.pyc且无法禁用那就只能选择其他模型训练。在选型之前先用一段代码测试 CuPy 是否可用try: import cupy print(cupy available, cupy.__version__) except ImportError: print(cupy not installed, use another backbone)这段判断在写自动化训练脚本时很有用可以在启动训练前自动降级到无 CuPy 依赖的模型避免跑到一半才报ModuleNotFoundError。4. 模型库解析与 backbone 对比EfficientNet、Swin、cnlnet_5CNL 怎么选4.1 models 目录就是一个小型模型动物园models目录下有四十多个模型文件从经典 CNN 到 Transformer 全覆盖resnet.py、densenet.py、efficientnet.py、sknet.py、resnest.py、vision_transformer.py、swin_transformer.py、cait.pyc、convit.py、xcit.py等。这不是作者代码写得臃肿而是为了方便做对比实验。农业病虫害识别场景中模型选型直接决定最终精度和推理速度针对不同部署环境要选不同 backbone。我用这个项目在玉米叶病害数据上测过几种模型结论比较有代表性模型参数量单卡训练速度batch32F1 上限部署友好度ResNet5025.6M快0.91高EfficientNet-B05.3M中0.92高EfficientNet-B419M慢0.94中Swin-Tiny28M很慢0.93低ViT-Base86M很慢0.90且需要大数据低这个表格能直观看出在数据量一万张左右、训练资源一般的条件下EfficientNet-B0 或 B3 是性价比最优解。它的 compound scaling 机制在同样参数量下比 ResNet 有更好的特征表达能力而且模型文件小部署到树莓派或 Jetson Nano 都能接受。Swin Transformer 精度上限高但训练和推理速度都慢适合拿来做论文中的 SOTA 对照实验关键时刻再换上去。ResNet50 虽然精度不是最高但胜在稳定、调试成本低代码逻辑最不容易出问题。4.2 选择一个模型跑通训练与验证闭环多数这类项目会有一个registry.py或模型工厂函数根据字符串创建模型。在交互式环境中可以绕过train_val.py直接用工厂函数验证模型结构from models import create_model model create_model(efficientnet_b0, num_classes10, pretrainedTrue) print(model)create_model的第一个参数是模型注册名第二个是分类头类别数第三个表示是否加载 ImageNet 预训练权重。执行后能看到完整的网络结构确认分类头输出维度是不是 10。这一步的核心价值是提前发现问题我在接触一个新模型库时习惯先打印结构再进训练循环能避开forward()里参数名不匹配这类低级错误。如果想对比不同 backbone写一个循环脚本依次训练即可。常规做法是把结果记录到一张 CSV 表格里方便后续绘制曲线import subprocess models [resnet50, efficientnet_b0, swin_tiny] for m in models: logfile ftrain_{m}.log cmd fpython train_val.py --model {m} --epochs 100 --batch-size 32 --lr 0.001 with open(logfile, w) as f: subprocess.run(cmd, shellTrue, stdoutf, stderrsubprocess.STDOUT)这段循环用subprocess串行训练多个模型日志保存到独立文件。如果想并行训练可以给每个进程分配不同 GPU 并加上--gpu参数比如第一个用--gpu 0、第二个用--gpu 1但要注意显存是否能撑住两个大模型同时训练。需要提醒的是cnlnet_5CNL这个自研模型因为发布者只提供了.pyc编译版本使用前要先确认它的输入尺寸和类别数约束很多自研网络对输入分辨率有固定要求比如必须是 256×256否则前向传播直接报 shape 不匹配。4.3 换数据集的迁移微调复用预训练权重是常规操作农业场景的公开数据集规模普遍有限从头训练大模型容易过拟合。最稳妥的做法是加载 ImageNet 预训练权重只重新训练最后的全连接分类层也就是常说的 fine-tune。在train_val.py或自建训练脚本中用两段式学习率实现model create_model(efficientnet_b0, num_classes10, pretrainedTrue) backbone_params [] head_params [] for name, param in model.named_parameters(): if fc in name or classifier in name: head_params.append(param) else: backbone_params.append(param) optimizer torch.optim.AdamW([ {params: backbone_params, lr: 1e-4}, {params: head_params, lr: 1e-3}, ], weight_decay1e-4)这段代码的逻辑是把模型参数按名字分成两组名字包含fc或classifier的视为分类头使用 10 倍于骨干网络的学习率其余参数较小的学习率。这样做的原因是预训练骨干已经具备不错的特征提取能力学习率太大会破坏已学到的特征而分类头是随机初始化的需要更快收敛。PyTorch 的named_parameters返回参数名和参数对象这个分组方式在很多迁移学习代码里是标准写法可以直接照搬。5. 推理部署与日志验证把训练好的模型用到单张图片上5.1 aidemo 与 ipdemo 的推理逻辑根目录的aidemo.zip和ipdemo.zip是可运行的推理 Demo 包。解压后一般能看到infer.py或predict.py作用是对单张图片预测类别。典型的推理流程是加载权重、预处理图片、前向传播、输出 Top-1 类别和置信度import torch from torchvision import transforms from PIL import Image from models import create_model device torch.device(cuda if torch.cuda.is_available() else cpu) model create_model(cnlnet_5CNL, num_classes10, pretrainedFalse) model.load_state_dict(torch.load(best_model.pth, map_locationdevice)) model.to(device).eval() transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) img Image.open(test_leaf.jpg).convert(RGB) input_tensor transform(img).unsqueeze(0).to(device) with torch.no_grad(): output model(input_tensor) prob torch.softmax(output, dim1) score, idx torch.max(prob, 1) class_names [玉米叶斑病, 稻瘟病, 小麦锈病] print(fpredicted: {class_names[idx.item()]}, confidence: {score.item():.4f})这段代码里map_locationdevice非常关键服务器上用 GPU 保存的权重在无 GPU 机器上加载时必须加这个参数否则会报 CUDA 不可用的错误。预处理管线的尺寸和均值方差必须与训练时保持一致如果训练脚本用的是 256×256 输入尺寸这里用 224 会导致精度明显下降。unsqueeze(0)是为图片增加 batch 维度因为模型接收的输入形状是(batch_size, 3, H, W)。最后torch.softmax把 logits 转成概率分布max同时返回最大值和对应索引索引再用class_names列表映射成可读的病害名称。5.2 训练日志的过拟合识别与 F1 曲线观察项目里同时存在CNLtrainlog.txt和f1.txt说明作者在训练过程中做了相对完整的指标记录。只看 terminal 输出不够建议用脚本直接绘图。如果没有 TensorBoard可以用 matplotlib 五分钟画一张训练曲线import matplotlib.pyplot as plt epochs, train_loss, val_acc [], [], [] with open(CNLtrainlog.txt) as f: for line in f: parts line.strip().split() epochs.append(int(parts[0])) train_loss.append(float(parts[1])) val_acc.append(float(parts[2])) plt.plot(epochs, train_loss, labeltrain_loss) plt.plot(epochs, val_acc, labelval_acc) plt.legend() plt.savefig(training_curve.png)日志文件的具体列数可能因项目而异如果 split 之后报错先head -5 CNLtrainlog.txt看一眼列数再调整解析逻辑。判断过拟合的直观标准是 train_loss 持续下降而 val_acc 停滞或回落此时应该降低学习率或加大数据增广。使用预训练模型时通常前 10 个 epoch 指标就会快速上升如果 10 个 epoch 后准确率还很低优先检查class_to_idx映射和预处理尺寸是否和数据匹配而不是急着加网络深度。5.3 扩展现有数据集的两个验证技巧把项目用于自有数据集时除了按第 2 章的目录结构整理图片还建议在训练前做一次完整性校验。一个小技巧是随机抽 9 张图用 matplotlib 拼成网格图并打印标签确认图片内容和标签没有错位。另一个更实用的做法是计算整个数据集的均值方差虽然 ImageNet 的[0.485, 0.456, 0.406]在大多数情况下可以直接用但对于偏色严重的农业图像比如无人机拍摄的稻田用自定义统计量反而能提升精度。数据偏色导致精度上不去时可以跑下面这段代码重新统计from torchvision import datasets, transforms import torch ds datasets.ImageFolder( data/train, transformtransforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), ]) ) loader torch.utils.data.DataLoader(ds, batch_size64, num_workers4) mean torch.zeros(3) std torch.zeros(3) for images, _ in loader: for c in range(3): mean[c] images[:, c, :, :].mean() std[c] images[:, c, :, :].std() mean / len(loader) std / len(loader) print(fcustom mean: {mean.tolist()}) print(fcustom std: {std.tolist()})这段代码在每个 batch 上累加通道均值和标准差最后除以 batch 总数得到全局统计量。用这些值替换第 5.1 节Normalize中的固定参数图像输入分布会更贴合当前数据集。特别要注意DataLoader的num_workers在 Windows 上需要设置为 0否则多进程数据加载会报与 spawn 相关的错误这也是很多同学复现项目时容易卡住的地方。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进