简介:这份资源是面向深度学习入门者、人工智能课程设计与毕业设计学生的农作物病害识别完整项目包,围绕卷积神经网络对小麦、水稻、玉米、番茄、棉花、大豆、油菜等作物病害图像进行分类识别,帮助读者理解从数据采集、模型训练到验证测试的完整流程。压缩包共455个文件,约119.76MB,包含93个java源码、118个xml配置、95个png与6个jpg图像样本、76个csv数据集、38个so库及12个jar依赖,另有gradle构建脚本、properties配置与README说明,目录结构规范,便于按模块查阅。已有76人学习下载。读者可据此掌握图像处理、模式识别与模型调参思路,并参考现成的工程组织方式完成自己的课程或毕设项目。
1. 农作物病害识别项目:从数据集到推理,一份能跑通的深度学习课程设计资源
农作物病害识别这个方向,每年到了课程设计和毕业设计选题季就会被翻出来。原因不复杂:数据集公开、任务定义清晰、模型选型成熟,做出来还能拍几张识别效果图放进论文里。但真正动手的人都知道,从下载数据集到跑出一个能看的准确率,中间隔着一堆琐碎的坑——图片尺寸不统一、类别样本不均衡、训练集和验证集划分方式不对导致指标虚高、推理时预处理和训练时对不上。这份「基于深度学习的农作物病害识别.zip」就是冲着这些环节来的,它把数据组织、模型搭建、训练脚本和推理入口打包在一起,适合正在做课程设计或毕业设计、需要一份能直接跑通并在此基础上改代码的从业者和学生。下面我按实际拆包和复现的顺序,把这份资源讲清楚。
2. 拆开压缩包先看什么:目录结构与技术栈判断
拿到一个深度学习项目压缩包,我一般不会急着装环境跑代码,而是先花十分钟把目录结构和依赖文件翻一遍。这一步能判断出项目是「真能跑」还是「只放了几个脚本」,也能提前发现版本冲突的隐患。
2.1 典型目录布局与各目录职责
这类农作物病害识别项目,解压后常见的结构大致如下(不同版本可能略有出入,以实际为准):
crop_disease_recognition/ ├── data/ │ ├── train/ │ │ ├── 类别A/ │ │ └── 类别B/ │ └── val/ │ ├── 类别A/ │ └── 类别B/ ├── models/ │ └── model.py ├── utils/ │ ├── dataset.py │ └── transforms.py ├── train.py ├── predict.py ├── config.py └── requirements.txtdata/下按类别分文件夹,这是ImageFolder能直接读取的格式,省去了自己写标签映射的麻烦。models/model.py放网络定义,utils/dataset.py和utils/transforms.py分别管数据加载和预处理,train.py和predict.py是两个入口,config.py集中管理超参数。这种拆分方式在课程设计里算比较规范的,改起来不用满文件找参数。
提示:如果解压后发现
data/是空的,说明数据集需要单独下载,先看README或config.py里有没有数据路径说明,别对着空目录硬跑。
2.2 依赖版本与框架判断
打开requirements.txt,重点看三样东西:深度学习框架及版本、图像处理库、以及有没有额外的训练工具库。常见组合是 PyTorch + torchvision + Pillow + numpy,有些项目会带tqdm做进度条、matplotlib画曲线、scikit-learn算混淆矩阵。
# 查看依赖清单 cat requirements.txt # 常见输出示例 torch>=1.10.0 torchvision>=0.11.0 Pillow>=8.0.0 numpy>=1.21.0 tqdm matplotlib scikit-learn这里有个血泪经验:torch和torchvision的版本必须匹配,比如 torch 1.10 对应 torchvision 0.11,装错版本会在导入时直接报RuntimeError。如果requirements.txt里只写了torch没写版本,建议手动指定一个稳定组合,别让 pip 自己解析。
# 推荐用虚拟环境隔离,避免污染全局 python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 指定版本安装,避免解析出奇怪组合 pip install torch==1.10.0 torchvision==0.11.0 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install -r requirements.txt参数说明:-i后面跟的是国内镜像源,能明显加快下载速度;虚拟环境的作用是把这份项目的依赖和系统里其他项目隔开,后面如果装崩了直接删掉venv重来,不用重装系统 Python。
2.3 配置文件里的关键参数
config.py是判断项目可改性的窗口。我一般会重点看这几个字段:num_classes(类别数)、img_size(输入尺寸)、batch_size、lr(学习率)、epochs、data_root(数据根目录)。这些参数决定了你换数据集或换模型时要动哪里。
# config.py 常见内容示意 class Config: data_root = './data' num_classes = 10 # 类别数,换数据集必须改 img_size = 224 # 输入尺寸,要和模型匹配 batch_size = 32 lr = 0.001 epochs = 30 device = 'cuda' if torch.cuda.is_available() else 'cpu'逻辑说明:num_classes如果和实际数据类别数对不上,训练时会在计算损失那一步报维度错误;img_size要和网络最后几层的下采样倍数配合,比如 224 是 ResNet 系列的常用输入;device这一行是自动判断有没有 GPU,没有就退回 CPU,课程设计阶段用 CPU 跑小数据集也能出结果,只是慢。
3. 数据准备与增强:让模型真正学到病害特征
数据这一环是农作物病害识别项目里最容易被低估的部分。很多人把数据集往data/一丢就开始训练,结果准确率卡在某个值上不去,回头查半天才发现是划分方式或者增强策略出了问题。
3.1 数据集组织与划分策略
ImageFolder要求每个类别一个文件夹,文件夹名就是类别标签。假设你手头的数据集是原始图片加一个标签文件,需要先转成这种结构。
import os import shutil import pandas as pd from sklearn.model_selection import train_test_split # 假设原始数据:images/ 下所有图片,labels.csv 两列 filename, label df = pd.read_csv('labels.csv') train_df, val_df = train_test_split( df, test_size=0.2, stratify=df['label'], random_state=42 ) def copy_to_folder(sub_df, split): for _, row in sub_df.iterrows(): dst_dir = os.path.join('data', split, str(row['label'])) os.makedirs(dst_dir, exist_ok=True) shutil.copy( os.path.join('images', row['filename']), os.path.join(dst_dir, row['filename']) ) copy_to_folder(train_df, 'train') copy_to_folder(val_df, 'val')逻辑说明:stratify=df['label']是关键参数,它保证训练集和验证集里每个类别的比例一致。农作物病害数据集往往存在类别不均衡,比如健康样本远多于病害样本,如果不分层,验证集里可能某个类别只有一两张图,算出来的准确率波动极大。random_state=42固定随机种子,保证每次划分结果一致,方便复现。
注意:不要用训练集里的图片做验证。有些项目为了省事,直接从训练集里随机抽几张当验证集,这样得到的准确率会虚高,答辩时被问到泛化能力就露馅了。
3.2 训练集与验证集的增强差异
预处理这块,训练集和验证集必须用不同的 transform。训练集做随机增强,验证集只做 resize 和归一化,这是标准做法,但新手经常两边用同一套。
from torchvision import transforms # 训练集:随机裁剪、翻转、颜色抖动 train_transform = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.8, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomVerticalFlip(p=0.3), 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]) ]) # 验证集:只做确定性的 resize 和归一化 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]) ])参数说明:RandomResizedCrop的scale=(0.8, 1.0)表示随机裁剪面积占原图的 80% 到 100%,这个范围别设太宽,否则可能把病斑裁掉;RandomVerticalFlip概率设 0.3 而不是 0.5,因为农作物叶片图像上下翻转后语义可能不自然;Normalize用的均值和方差是 ImageNet 的统计值,如果你从零训练而不是用预训练权重,可以改成自己数据集的统计值,但用预训练权重时保持默认。
3.3 类别不均衡的处理
农作物病害数据集里,某些病害样本可能只有几十张,而健康样本有上千张。直接训练会导致模型偏向多数类。常见做法有两种:在损失函数里加类别权重,或者用重采样。
import torch from torch.utils.data import DataLoader, WeightedRandomSampler from collections import Counter # 统计各类别样本数 targets = [s[1] for s in dataset.samples] class_counts = Counter(targets) print('类别分布:', class_counts) # 方案一:损失函数加权 weights = [1.0 / class_counts[i] for i in range(num_classes)] class_weights = torch.FloatTensor(weights).to(device) criterion = torch.nn.CrossEntropyLoss(weight=class_weights) # 方案二:WeightedRandomSampler 重采样 sample_weights = [1.0 / class_counts[t] for t in targets] sampler = WeightedRandomSampler(sample_weights, num_samples=len(sample_weights), replacement=True) train_loader = DataLoader(dataset, batch_size=32, sampler=sampler)逻辑说明:方案一改的是损失函数,让少数类样本的损失被放大;方案二改的是采样策略,让每个 batch 里各类别比例更均衡。两者选一个就行,同时用可能矫枉过正。我一般先用方案一,改动小,不影响 DataLoader 的其他参数。
4. 模型搭建与训练:从预训练权重到学习率调度
模型这块,课程设计里最常见的选择是 ResNet 或 EfficientNet 加预训练权重。从零训练一个小卷积网络也能跑,但准确率通常差一截,而且收敛慢,调参时间成本高。
4.1 用预训练模型替换分类头
以 ResNet50 为例,加载 ImageNet 预训练权重,把最后的全连接层换成自己的类别数。
import torch.nn as nn from torchvision import models def build_model(num_classes, pretrained=True): model = models.resnet50(pretrained=pretrained) # 冻结前面的层(可选) for param in model.parameters(): param.requires_grad = False # 替换最后的全连接层 in_features = model.fc.in_features model.fc = nn.Sequential( nn.Linear(in_features, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, num_classes) ) return model model = build_model(num_classes=10).to(device)参数说明:pretrained=True会下载 ImageNet 权重,第一次运行需要联网;冻结前面层是为了在小数据集上防止过拟合,等训练几轮后再解冻做微调;Dropout(0.5)是常用的正则化手段,如果发现训练集准确率远高于验证集,可以把这个值调大。
4.2 训练循环与学习率调度
训练循环本身不复杂,但有几个细节决定成败:优化器选 Adam 还是 SGD、学习率怎么衰减、什么时候保存模型。
import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3) scheduler = CosineAnnealingLR(optimizer, T_max=config.epochs) best_acc = 0.0 for epoch in range(config.epochs): model.train() for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct, total = 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels = imgs.to(device), labels.to(device) outputs = model(imgs) preds = outputs.argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) acc = correct / total print(f'Epoch {epoch+1}, Val Acc: {acc:.4f}') if acc > best_acc: best_acc = acc torch.save(model.state_dict(), 'best_model.pth')逻辑说明:filter(lambda p: p.requires_grad, ...)只把需要更新的参数传给优化器,冻结的层不参与更新;CosineAnnealingLR让学习率按余弦曲线下降,比固定学习率更容易收敛到好的局部最优;保存best_model.pth而不是最后一个 epoch 的模型,是因为验证准确率可能在后期波动,最好的模型不一定在最后一轮。
提示:如果训练 loss 一直不降,先检查学习率是不是太大(比如设成了 0.1),再检查数据标签有没有对错。我遇到过标签文件和图片文件名差一个后缀导致全部标签错位的情况,loss 卡在 log(num_classes) 附近不动。
4.3 训练过程监控与早停
课程设计里不需要搞太复杂的监控,但至少要能看出模型有没有过拟合。把每个 epoch 的训练 loss 和验证准确率记下来,画个曲线就够。
import matplotlib.pyplot as plt train_losses, val_accs = [], [] # ... 在训练循环里 append ... plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.plot(train_losses, label='Train Loss') plt.xlabel('Epoch'); plt.ylabel('Loss'); plt.legend() plt.subplot(1, 2, 2) plt.plot(val_accs, label='Val Acc') plt.xlabel('Epoch'); plt.ylabel('Accuracy'); plt.legend() plt.savefig('training_curve.png')如果验证准确率连续多个 epoch 不升反降,而训练 loss 还在降,说明过拟合了。这时候可以提前停掉,或者加大 Dropout、加权重衰减。早停的逻辑很简单:记录最佳准确率,如果连续 5 个 epoch 没超过最佳值就 break。
5. 避坑与排查:那些让准确率卡住的常见问题
这一章是我自己复现这类项目时踩过的坑,按「现象 → 原因 → 解决」整理,你对着排查能省不少时间。
5.1 验证准确率异常高但推理时全错
现象:训练日志里验证准确率 95% 以上,但用predict.py跑单张图片,结果全是同一类。
原因:推理时的预处理和验证集不一致。常见情况是predict.py里只做了ToTensor没做Normalize,或者 resize 的尺寸和训练时不同。
解决:把predict.py里的 transform 直接复用val_transform,不要重新写一套。检查Resize和CenterCrop的参数是否和训练时一致。
5.2 训练 loss 不下降,卡在 2.3 左右
现象:loss 从第一个 epoch 就稳定在 2.3 附近,准确率接近随机猜。
原因:标签错位或类别数配置错误。比如num_classes设成了 10 但实际只有 5 类,或者ImageFolder读到的类别顺序和标签文件对不上。
解决:打印dataset.classes看类别列表,打印几个 batch 的标签值确认范围。用len(dataset.classes)替换硬编码的num_classes。
5.3 GPU 显存不足报 CUDA out of memory
现象:训练开始几秒后报RuntimeError: CUDA out of memory。
原因:batch_size太大,或者图片尺寸设得过高,或者没有及时释放中间变量。
解决:先把batch_size减半试,比如从 32 降到 16;如果还不行,把img_size从 224 降到 192 或 160;在验证循环里用with torch.no_grad()包住,减少显存占用。
5.4 验证集准确率波动大,每次跑结果差很多
现象:同样的代码和数据,两次训练得到的验证准确率差 10 个点以上。
原因:随机种子没固定,或者验证集太小。数据划分时如果没设random_state,每次划分结果不同;验证集如果只有几十张图,准确率本身就不稳定。
解决:在训练脚本开头固定所有随机种子,包括torch.manual_seed、np.random.seed、random.seed;验证集至少保证每个类别有 20 张以上图片。
import torch, numpy as np, random def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True set_seed(42)5.5 推理速度慢,单张图片要好几秒
现象:predict.py跑一张图要等好几秒,批量推理更慢。
原因:没有把模型切到 eval 模式,或者没有用torch.no_grad(),导致每次推理都在建计算图。
解决:推理前加model.eval(),并用with torch.no_grad():包住前向传播。如果还慢,检查是不是在用 CPU 推理而模型在 GPU 上训练,把模型.to(device)统一一下。
6. 进阶技巧:用混淆矩阵和单张推理验证模型真实水平
训练完拿到一个准确率数字,其实说明不了太多问题。我习惯做两件事来验证模型是不是真的学到了东西:一是画混淆矩阵看哪些类别容易混,二是拿几张训练集里没有的图片做单张推理,看输出概率分布。
6.1 混淆矩阵定位易混类别
from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs = imgs.to(device) outputs = model(imgs) preds = outputs.argmax(dim=1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm = confusion_matrix(all_labels, all_preds) plt.figure(figsize=(10, 8)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=dataset.classes, yticklabels=dataset.classes) plt.xlabel('Predicted'); plt.ylabel('True') plt.savefig('confusion_matrix.png') print(classification_report(all_labels, all_preds, target_names=dataset.classes))逻辑说明:混淆矩阵的对角线是正确分类的数量,非对角线是错分。如果发现某两类之间互相错分很多,说明这两个病害在图像上确实相似,可以考虑增加这两类的样本量,或者用更强的数据增强。classification_report会输出每个类别的精确率、召回率和 F1,比总体准确率更有参考价值。
6.2 单张图片推理与概率输出
from PIL import Image import torch.nn.functional as F def predict_single(img_path, model, transform, classes, device): model.eval() img = Image.open(img_path).convert('RGB') tensor = transform(img).unsqueeze(0).to(device) with torch.no_grad(): outputs = model(tensor) probs = F.softmax(outputs, dim=1)[0] top_prob, top_idx = probs.max(0) print(f'预测类别: {classes[top_idx.item()]}, 置信度: {top_prob.item():.4f}') # 打印前3个概率 top3 = probs.topk(3) for p, i in zip(top3.values, top3.indices): print(f' {classes[i.item()]}: {p.item():.4f}') predict_single('test_leaf.jpg', model, val_transform, dataset.classes, device)参数说明:unsqueeze(0)是给图片加一个 batch 维度,因为模型期望输入是[N, C, H, W];F.softmax把输出转成概率,方便看置信度。如果置信度普遍偏低(比如最高才 0.4),说明模型没训好或者这张图确实不属于任何已知类别。
6.3 我自己的验证习惯
从那以后我每次训完一个分类模型,都强制走一遍「混淆矩阵 + 单张推理」这两步。准确率数字可以骗人,但混淆矩阵里哪两类在互相错分、单张推理的置信度分布,骗不了人。有一次课程设计答辩,评委老师直接拿了一张手机拍的叶片照片让我现场推理,因为提前做过单张推理测试,预处理流程是通的,当场跑出了正确结果。如果只盯着验证集准确率,遇到这种现场测试很容易翻车。
这份资源的价值在于它把数据组织、训练脚本和推理入口都串好了,你不需要从零搭框架,把精力放在改类别数、换模型和调参上就行。希望帮到你。
本文还有配套的精品资源,点击获取