ARTICLE DETAIL

资讯详情

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

花生叶片缺陷图像分类:770张已标注数据训练与调优实战

花生叶片缺陷图像分类:770张已标注数据训练与调优实战 简介本资源为花生叶片缺陷图像分类数据集面向从事图像分类、农业病害识别及深度学习模型改进的开发者与研究者可用于训练和评估分类网络。数据已完成预处理可直接作为分类模型输入共划分3个类别疾病叶片、死掉的叶片与健康叶片具体类别信息可查看包内json文件。压缩包共780个文件以777张jpg图像为主体另含1个py脚本、1个png与1个json标注文件整体约23.18MB并已划分训练集与测试集各类图片分目录存放便于直接读取。资源中附带show脚本可快速可视化数据集分布与样本效果。目前已有110人学习下载。读者可获得一套开箱即用的叶片缺陷分类数据用于分类网络训练、对比实验与改进验证同时结合配套的图像分类与分割改进、计算机视觉完整项目内容快速搭建实验流程并复现结果。1. 花生叶片缺陷图像分类约 770 张已标注数据能跑出什么结果拿到「花生叶片缺陷图像分类数据集【已标注约770张数据】」这个标题多数人第一反应是770 张够不够训一个能用的分类模型我的判断是——够但前提是你别把它当成 ImageNet 那种量级的玩具来对待。花生叶片缺陷识别属于典型的农业细粒度视觉任务田间场景下病斑、虫害、缺素症状在颜色和纹理上高度相似770 张的规模意味着你必须把每一张图的价值榨干而不是简单丢进ImageFolder跑一遍resnet50就完事。这个数据集适合三类人一是做智慧农业、植保巡检方向需要快速验证一个叶片病害分类 baseline 的工程师二是手里有类似小样本农业数据集、想找一套可复现训练流程的算法同学三是想拿真实场景数据练手图像分类算法从数据清洗到部署的从业者。它解决的核心问题是在标注数据有限、类别间视觉差异细微的条件下如何用迁移学习加数据增强把分类精度推到可用水平。下面我按自己实际跑这类数据集的顺序把选型、训练、调参和踩坑讲清楚。2. 先搞清楚数据长什么样类别分布、图像质量与划分策略2.1 约 770 张已标注数据的典型结构与检查方法农业图像分类数据集通常按类别分文件夹存放目录结构大概率是peanut_leaf_defect/类别名/图片文件。在写任何训练代码之前我一般先跑一段统计脚本把类别数、每类样本量、图像尺寸和通道模式摸清楚。这一步不做后面划分训练集时很容易出现某类验证集只有两三张的尴尬局面。import os from PIL import Image from collections import Counter root peanut_leaf_defect # 数据集根目录按实际路径替换 class_counts {} size_stats [] for cls in sorted(os.listdir(root)): cls_dir os.path.join(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, .bmp))] class_counts[cls] len(imgs) for f in imgs[:20]: # 每类抽样20张看尺寸避免全量IO过慢 with Image.open(os.path.join(cls_dir, f)) as im: size_stats.append((im.size, im.mode)) print(类别分布:, class_counts) print(总样本数:, sum(class_counts.values())) print(尺寸/模式抽样:, Counter(size_stats).most_common(5))这段脚本做三件事统计每类图片数量、汇总总样本量、抽样查看图像尺寸和色彩模式。参数上root指向解压后的数据集目录抽样数量20可以按需调整数据量小的时候直接全量统计也行。输出里如果发现某类只有三四十张、另一类有两百多张那类别不平衡就是你必须先处理的问题而不是等到训练完看混淆矩阵才后悔。尺寸抽样则决定了你后面Resize到 224 还是 256——如果原图普遍只有 100 多像素强行放大到 448 只会引入插值噪声。2.2 小样本下训练/验证/测试怎么切才不翻车770 张数据如果按 8:1:1 切测试集只有 77 张单类可能不到 10 张评估结果的方差会大到让你怀疑人生。我一般对这类小数据集采用分层抽样stratified split并且把验证集比例提到 15%20%测试集保留 10% 但明确它只用于最终一次评估不参与任何调参决策。import shutil from sklearn.model_selection import train_test_split def split_dataset(root, out_root, val_ratio0.15, test_ratio0.10, seed42): for cls in sorted(os.listdir(root)): cls_dir os.path.join(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, .bmp))] # 先切出测试集再从剩余里切验证集保证比例稳定 train_val, test train_test_split(imgs, test_sizetest_ratio, random_stateseed) train, val train_test_split(train_val, test_sizeval_ratio / (1 - test_ratio), random_stateseed) for subset, files in [(train, train), (val, val), (test, test)]: dst os.path.join(out_root, subset, cls) os.makedirs(dst, exist_okTrue) for f in files: shutil.copy(os.path.join(cls_dir, f), os.path.join(dst, f)) split_dataset(peanut_leaf_defect, peanut_split)关键点在test_size的换算先按test_ratio切出测试集剩下的再按val_ratio/(1-test_ratio)切验证集这样最终验证集占总量的比例才等于你设定的val_ratio。seed固定住保证每次划分一致否则你调参时验证集变了指标波动根本分不清是模型改了还是数据换了。分层抽样由train_test_split在每类内部独立执行来近似实现类别极不平衡时可以考虑stratify参数但这里按类循环已经天然分层。注意划分完一定要再跑一次统计确认每个子集里每类都至少有 5 张以上否则验证指标没有参考意义。3. 迁移学习选型与训练从 ResNet 到轻量模型的取舍3.1 为什么小样本农业图像优先用预训练 backbone770 张图从零训练一个 CNN几乎必然过拟合。花生叶片缺陷的判别依据主要是病斑的颜色、形状和纹理分布这些低层和中层特征在 ImageNet 预训练模型里已经学得很扎实你只需要微调高层语义部分。常见做法是冻结 backbone 的前若干层只训练最后的分类头和部分高层 block。选型上resnet18或resnet50是最稳的 baselineefficientnet_b0在参数量和精度之间平衡得不错如果最终要部署到边缘设备mobilenet_v3_small值得一试。我一般先用resnet18跑通流程拿到基准精度再决定要不要换更大的模型。3.2 用 PyTorch 跑通第一个训练循环下面这段代码是一个最小可用的训练脚本包含数据增强、迁移学习、学习率调度和验证。数据增强对 770 张的数据集来说是刚需RandomResizedCrop、RandomHorizontalFlip、ColorJitter这三个基本够用农业图像里垂直翻转通常不合理叶片朝向有语义所以不加RandomVerticalFlip。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models train_tf transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) data_dir peanut_split train_ds datasets.ImageFolder(f{data_dir}/train, train_tf) val_ds datasets.ImageFolder(f{data_dir}/val, val_tf) train_loader DataLoader(train_ds, batch_size16, shuffleTrue, num_workers2) val_loader DataLoader(val_ds, batch_size16, shuffleFalse, num_workers2) device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) for p in model.parameters(): # 先冻结全部再解冻高层 p.requires_grad False model.fc nn.Linear(model.fc.in_features, len(train_ds.classes)) for p in model.layer4.parameters(): # 解冻最后一个stage p.requires_grad True model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) for epoch in range(30): model.train() for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct total 0 with torch.no_grad(): for x, y in val_loader: x, y x.to(device), y.to(device) pred model(x).argmax(1) correct (pred y).sum().item() total y.size(0) print(fepoch {epoch1}, val_acc{correct/total:.4f})逻辑上分四块数据增强与加载、模型改造、优化器与调度、训练验证循环。参数说明几个关键点batch_size16是 770 张数据下的稳妥选择显存够可以提到 32lr1e-3配合AdamW适合只训练分类头和解冻层如果你解冻更多层学习率要降到1e-4量级T_max30对应总 epoch 数余弦退火让学习率平滑衰减。layer4是 ResNet 的最后一个残差 stage解冻它能在不引入过多参数的情况下提升对病斑语义的适应能力。跑完 30 个 epoch如果验证集准确率还在震荡先别急着加 epoch去看数据增强是不是过强了。3.3 类别不平衡与增强策略的参数怎么定如果统计阶段发现类别不平衡CrossEntropyLoss可以传weight参数按类别样本数的倒数来设。另一个有效手段是WeightedRandomSampler让每个 batch 里各类别出现概率接近。数据增强的强度需要根据验证集表现来调RandomResizedCrop的scale下限从 0.7 降到 0.5 会增强尺度不变性但病斑被裁掉的风险也变大我一般先保持 0.7过拟合明显时再降。ColorJitter的幅度不宜过大否则会把病斑颜色特征搅乱brightness 和 contrast 各 0.2 是经验值。提示训练前把model.fc换成新层后确认filter(lambda p: p.requires_grad, ...)里确实包含了新层参数否则分类头根本不会被更新。4. 评估与排查小数据集上指标为什么会骗你4.1 混淆矩阵比准确率更能暴露问题770 张数据训出来的模型准确率 85% 听起来还行但如果某一类召回率只有 40%实际部署就是灾难。评估阶段必须看混淆矩阵和每类的 precision/recall。用sklearn的classification_report配合confusion_matrix是最快的方式。from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for x, y in val_loader: x x.to(device) preds model(x).argmax(1).cpu().numpy() all_preds.extend(preds) all_labels.extend(y.numpy()) print(confusion_matrix(all_labels, all_preds)) print(classification_report(all_labels, all_preds, target_namesval_ds.classes, digits3))classification_report会给出每类的 precision、recall、f1-score 和支持样本数。重点看支持样本数少但 recall 也低的类那通常是模型没学到该类的判别特征需要针对性补充数据或调整增强。混淆矩阵里如果两类互相误判严重说明它们在视觉上确实接近可以考虑引入更细粒度的注意力机制或者干脆合并这两类再评估业务上是否可接受。4.2 验证集准确率虚高的三个来源第一个来源是数据泄漏如果同一片叶子的多张照片被分到了训练集和验证集验证指标会虚高。农业数据集采集时往往对同一病叶拍多张划分时必须按「叶片个体」而不是按「图片」来切否则模型记住的是这片叶子的背景而不是病斑特征。第二个来源是验证集太小77 张里随机波动几个样本就能让准确率跳 5 个百分点解决办法是交叉验证取平均。第三个来源是测试集被反复用于调参调着调着测试集就变成了验证集最终报告的数字没有意义。我的习惯是测试集只在最后跑一次跑完就封存。5. 避坑与常见问题770 张数据训练时的真实翻车记录现象训练 loss 正常下降验证准确率始终在 25% 左右不动。原因通常是类别数对不上——model.fc的输出维度用了默认的 1000而数据集只有几类或者ImageFolder读到的类别顺序和标签映射错位。解决方法是打印train_ds.classes和len(train_ds.classes)确认nn.Linear的输出维度与之一致同时检查数据集目录下有没有混入非类别文件夹比如__MACOSX或隐藏目录它们会被ImageFolder当成一个额外类别。现象验证集准确率比训练集还高。在小数据集上这不一定是好事常见原因是训练时用了强增强而验证时没有导致训练难度被人为抬高另一个原因是验证集样本太少且恰好容易分。解决办法是先把增强调弱一档再看如果仍然验证高于训练检查验证集是否混入了训练集图片用文件哈希去重。现象模型对某一类几乎全部预测错误。原因可能是该类样本量过少也可能是该类图像在颜色或背景上与其他类差异过大模型学到了背景捷径。解决办法是先做类别加权再用WeightedRandomSampler平衡采样同时检查该类图片是否有明显的采集偏差比如全部来自同一地块、同一光照条件。现象换了随机种子后准确率波动超过 10 个百分点。这是小数据集的典型特征说明模型对数据划分敏感。解决办法是改用 5 折交叉验证报告平均指标和标准差而不是只报一次划分的结果。如果标准差很大说明数据量确实不够支撑稳定结论需要考虑补充数据或使用更强的预训练模型。现象推理时单张图片预测结果和验证时不一致。原因通常是推理时的预处理和验证时不一致比如忘了Normalize、Resize的插值方式不同、或者 PIL 读进来是灰度图而训练时是 RGB。解决办法是把验证集的 transform 单独抽成一个函数推理时复用同一个函数不要手写一套新的预处理。6. 把 770 张用到极致交叉验证、模型集成与推理加速小数据集的进阶玩法不是换更大的模型而是把现有数据的信息量榨到极限。我一般会做两件事5 折交叉验证和 checkpoint 集成。5 折交叉验证把数据分成 5 份每次用 4 份训练、1 份验证最终报告 5 次的平均准确率和标准差。这样做的好处是每个样本都有机会出现在验证集中指标更可靠同时你得到了 5 个在不同数据子集上训练的模型。把这 5 个模型的预测概率平均就是最简单的集成通常能比单模型提升 24 个百分点而且几乎不增加推理成本如果串行推理或只增加少量成本如果并行。# 假设已有5折的模型 checkpoint 列表 models_list [] for fold in range(5): m models.resnet18(weightsNone) m.fc nn.Linear(m.fc.in_features, num_classes) m.load_state_dict(torch.load(ffold{fold}_best.pth, map_locationdevice)) m.eval().to(device) models_list.append(m) def ensemble_predict(x): probs torch.zeros(x.size(0), num_classes, devicedevice) with torch.no_grad(): for m in models_list: probs torch.softmax(m(x), dim1) return probs.argmax(1)集成推理时把所有模型的 softmax 概率相加再取 argmax比投票法更平滑。如果部署环境对延迟敏感可以把 5 个模型蒸馏成一个学生模型但蒸馏需要额外的训练轮次770 张数据下蒸馏效果不一定稳定我一般优先用集成。另一个实用技巧是测试时增强TTA对同一张测试图做水平翻转和中心裁剪分别推理后平均概率。TTA 在小数据集上通常能再涨 12 个百分点代价是推理时间翻倍。如果业务允许值得加上。最后说一个我自己的习惯每次跑完实验把配置文件、数据划分文件、随机种子和最终指标一起存档。小数据集实验的可复现性特别脆弱隔两周回来你根本记不清当时用的是哪个增强强度、哪个学习率。这个习惯帮我省了无数次重跑的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表