桑叶品种图像分类数据集:从数据清洗到ResNet迁移学习实战指南

发布时间:2026/10/18 7:09:47
桑叶品种图像分类数据集:从数据清洗到ResNet迁移学习实战指南 简介桑叶品种图像分类数据集约5000张已标注图片面向计算机视觉与农业智能识别任务覆盖红王、白王、Chiang Mai 60等10个品种适合用于模型训练、迁移学习与效果对比。资源包共2000个文件主体为1998张jpg样本图另含1个json类别配置和1个Python可视化脚本压缩包约730MB。数据已划分训练集与测试集同类图片集中存放便于加载评测json文件可维护10类品种标签映射show.py脚本能一键预览各类别样本分布帮助判断数据均衡情况。目前已有76人学习使用适合机器学习初学者、农业科研人员及竞赛选手作为桑叶品种识别基准数据。围绕该数据还可延伸图像分类网络改进实验从数据准备逐步走向模型优化与农业场景落地。1. 桑叶品种图像分类数据集5,000张已标注图片到底解决什么问题做桑叶品种识别最堵的往往不是模型选型而是数据。某开发者自己下田拍了几千张按品种分文件夹兴冲冲开训结果验证集准确率七成不到。细看发现同一种桑叶在不同光照下长相差很多夹了不少杂草和病叶标注全凭个人手感谁来了都得返工。这时候“桑叶品种数据集图像分类数据集【已标注约5,000张数据】”这类资源的价值就体现出来了它把采集和标注这两件最耗时的事替你做了输入一张桑叶图像输出品种类别适合拿去训卷积神经网络、验证迁移学习效果也适合做细粒度分类的入门实验。不过拿到手先别急着训练标注质量、类别分布、图像来源这三件事如果不核验后面全是在替别人的错误买单。2. 拆解数据集结构目录树、标注清单和标签映射的落地写法2.1 先认数据组织方式按文件夹标注与按清单标注拿到这类图像分类数据集常见做法是两种组织方式。第一种是“按文件夹标注”训练集、验证集下面每个品种一个子文件夹图片直接放进去torchvision 的ImageFolder能直接读第二种是“按清单标注”一个 CSV 或 JSON 文件记录“图片路径, 品种名”的对应关系图片集中存放。两种我都处理过按文件夹标注最省事按清单标注灵活但路径容易写错。先看顶层目录长什么样。解压之后不要急着打开看图先跑一条命令确认树形结构tree -L 2 ./sangye_dataset # 或者没有 tree 命令时 find ./sangye_dataset -maxdepth 2 -type d | head -30输出里如果直接看到train/品种A/xxx.jpg这种层级说明是按文件夹标注如果看到images/xxx.jpg加一个labels.csv说明是按清单标注。前30行足够判断了不用全部打印。目录结构认清楚之后下一步做类别统计。import os from collections import Counter data_root ./sangye_dataset/train # 按你的实际路径改 counts Counter() for class_name in os.listdir(data_root): class_dir os.path.join(data_root, class_name) if not os.path.isdir(class_dir): continue n len([f for f in os.listdir(class_dir) if f.lower().endswith((.jpg, .jpeg, .png))]) counts[class_name] n for name, n in counts.most_common(): print(f{name}: {n}) print(total:, sum(counts.values()), classes:, len(counts))这段脚本用Counter统计每个文件夹下的图片数量endswith过滤掉非图片文件比如 Thumbs.db 或隐藏文件。训练前一定要跑一次它能告诉你三件事类别数是否和标注文档一致、有没有类别图片特别少、有没有空目录。如果有的类别只有几十张后面训练时的类别不平衡问题就必须单独处理。按文件夹标注和按清单标注的差异整理成一张表拿到数据先对照一下组织方式目录形态读取工具主要坑按文件夹标注train/品种A/01.jpgImageFolder直接读类别ID按字母序生成容易漂移按清单标注images/01.jpglabels.csv自定义Dataset路径相对/绝对写错匹配不到图片按文件夹标注时很多人会忽略一个问题ImageFolder默认用文件夹名的字母序作为类别ID如果哪天你手动改了某个文件夹名ID 顺序就变了训练好的模型权重全废。按清单标注则要小心路径里的反斜杠和相对路径尤其从 Windows 传到 Linux 服务器时分隔符不统一会直接匹配失败。2.2 图像规格与文件完整性先查再训别拖到训练中报错约5,000张数据的量级不算大但图片尺寸、通道数、损坏文件这三项必须在训练前扫一遍。桑叶这类自然图像数据来源可能是田间拍摄也可能是扫描仪或手机翻拍分辨率差异会非常大。不检查直接喂给网络轻则 resize 变形重则训练中断。用 PIL 全量扫描一遍记录尺寸分布和打不开的文件from PIL import Image import os bad_files [] sizes set() for root, _, files in os.walk(./sangye_dataset): for f in files: if not f.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(root, f) try: with Image.open(path) as im: sizes.add(im.size) except Exception: bad_files.append(path) print(distinct sizes:, sizes) print(bad files:, len(bad_files)) for p in bad_files[:10]: print(p)sizes是一个集合如果打印出来有十几种尺寸说明数据来源不统一。此时不能简单粗暴地Resize((224,224))长宽比差异大的图会被拉伸畸变尤其桑叶这种依赖叶形判别的任务畸变等于人为制造噪声。常见做法是先等比例 resize 短边到 256再中心裁剪到 224把畸变影响降到最低。损坏文件如果只有几个直接剔掉或重新采样。损坏文件多的数据集要怀疑是不是压缩传输过程中丢包最好重新获取。这个检查脚本跑一遍约一两分钟换来的是训练时的稳定值。2.3 标签映射类别名到 ID 的映射必须固化下来训练时类别名要转成整数 ID最常见的问题不在第一次转换而在推理阶段。训练时用的是文件夹名 A、B、C 的字母序推理时换了机器文件列表顺序不一样ID 对不上准确率直接崩。我一般会把映射写死成一个 JSON 文件训练和推理共用它不每次重新生成import json import os class_names sorted(os.listdir(data_root)) mapping {name: idx for idx, name in enumerate(class_names)} with open(class_mapping.json, w, encodingutf-8) as f: json.dump(mapping, f, ensure_asciiFalse, indent2) print(mapping)注意两点sorted保证映射确定性JSON 用ensure_asciiFalse防止中文品类名被转成\uXXXX否则你排查模型输出时看到一堆转义符会崩溃。这个 JSON 文件很小提交代码时一起入库队友拿到就能对齐。3. 类别划分与数据加载随机切分、按批次切分和增强参数怎么定3.1 划分策略随机切分还是按采集批次切分图像分类数据集的常规划分是训练集、验证集、测试集按 7:1.5:1.5 或 8:1:1 切。但如果这批桑叶数据存在“同一株桑树拍了多张照片”的情况随机切分会让同一株的图片同时出现在训练和验证里模型记住的是这株树的纹理特征而不是品种特征验证集的准确率会虚高。拿到数据先看目录里是否有批次、日期或植株编号信息。如果文件名里有tree_01、batch_3这类字段按这个维度划分更稳妥。只有图片没有额外元数据时才退而求其次做随机切分并且把验证集准确率当上限参考。用 sklearn 做分层随机切分的脚本import os from sklearn.model_selection import train_test_split images [] for root, _, files in os.walk(./sangye_dataset): for f in files: if f.lower().endswith((.jpg, .jpeg, .png)): images.append(os.path.join(root, f)) labels [os.path.basename(os.path.dirname(p)) for p in images] train_imgs, val_imgs train_test_split( images, test_size0.15, stratifylabels, random_state42) print(len(train_imgs), len(val_imgs))stratifylabels按照类别比例分层抽样保证每个品种在训练和验证里的占比一致避免某个少样本品种全被切到验证集。random_state42固定随机种子让切分结果可复现否则每次跑出来的数据分布都不一样对比实验没法做。3.2 数据增强桑叶任务的参数不能照搬 ImageNet 预设数据增强是防止过拟合的主要手段约5,000张图训一个分类模型不加增强很容易在十几个 epoch 后验证集停止上涨。但桑叶分类的增强参数有讲究不能照搬 ImageNet 的通用配置。关键点有三个。第一旋转范围可以放开到 ±30°因为田间拍摄角度不固定第二水平翻转要谨慎部分桑树品种的叶形左右不对称翻转等于改变了形态学特征第三随机裁剪的 scale 下限别设太低桑叶的判别特征分布在叶缘和叶脉裁掉太多就没了。我常用的增强配置增强操作参数建议说明随机旋转±30°模拟拍摄角度差异水平翻转视品种而定叶形对称才开不对称就关随机裁剪scale 0.7~1.0下限别低于0.5亮度/对比度±0.2适应不同光照随机擦除p0.25模拟病斑和遮挡数据增强要在训练集上用验证集和测试集只做 resize 和归一化这个顺序不能搞反。有人把随机裁剪加在验证集上导致验证损失一直抖动还以为是模型问题实际上是数据分布不一致。3.3 用 PyTorch 自定义 Dataset 加载路径、标签和变换一次写清如果数据集是文件夹结构ImageFolder一行能搞定。但如果要做按批次划分或者后面要加多标签任务自定义Dataset更灵活。写一个最简版本import os from PIL import Image from torch.utils.data import Dataset class SangyeDataset(Dataset): def __init__(self, img_paths, labels, transformNone): self.img_paths img_paths self.labels labels self.transform transform def __len__(self): return len(self.img_paths) def __getitem__(self, idx): path self.img_paths[idx] image Image.open(path).convert(RGB) if self.transform: image self.transform(image) return image, self.labels[idx]Image.open是惰性加载真正读文件发生在__getitem__里所以convert(RGB)必须在打开后立刻做统一通道数排除灰度图和带透明通道的图。transform由外部传入训练和验证可以共用这个类只是传入的 transform 不同。DataLoader 的参数也不能忽视。num_workers设为 4 或 8 能显著加快数据读取pin_memoryTrue在 GPU 训练时减少显存拷贝时间。5,000张图规模不大这两个参数开了之后训练速度体感差异明显。from torch.utils.data import DataLoader train_loader DataLoader( SangyeDataset(train_imgs, train_labels, transformtrain_transform), batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader( SangyeDataset(val_imgs, val_labels, transformval_transform), batch_size32, shuffleFalse, num_workers4, pin_memoryTrue)shuffle只在训练集开验证集保持False保证评估时每个样本只看一次。如果你的服务器 CPU 核数很少num_workers设太高反而会因为进程切换开销拖慢速度4 是稳妥起点。4. 从0到1跑通分类ResNet18迁移学习的配置清单与评估方法4.1 基线模型选什么约5,000张数据不适合从头训大模型桑叶品种分类约5,000张图、十个到二十个类别这个量级属于中小规模图像分类。从零开始训练 ResNet50 或更深的网络参数动辄几千万这点数据量远远不够结果就是训练集 loss 降到很低验证集准确率上不去典型的过拟合。带 ImageNet 预训练权重的 ResNet18 是稳妥起点。ResNet18 参数少在这类数据量上不容易过拟合训练速度快CPU 都能勉强跑推理如果想追高精度把基线换成 ResNet50 或 EfficientNet-B3但要同时加强数据增强和正则化。顺序上先跑通小模型再往大了换而不是一上来就堆大网络。4.2 训练参数学习率、批大小、epochs 怎么设迁移学习的核心原则是预训练权重已经学会了通用特征微调时学习率要小别把已有特征冲掉。我的基准配置参数推荐值说明输入尺寸224×224通用配置EfficientNet 可换成 240batch size32显存不够就 165,000张图两个epoch约312步优化器AdamWlr1e-4weight_decay1e-3学习率1e-4 起步验证集不降就减半低于1e-6基本没收益epochs30~40 加早停patience5损失函数CrossEntropyLoss多分类标准选择AdamW 的weight_decay是 L2 正则的一种实现能抑制权重过大。learning rate 的调整策略用ReduceLROnPlateau盯着验证集 loss连续两三个 epoch 不降就把学习率乘 0.5比固定 epoch 数下降更省心。4.3 最小可跑通的训练脚本加载预训练模型替换分类头import torch import torch.nn as nn from torchvision import models, transforms model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_classes 12 # 按你的实际类别数改 model.fc nn.Linear(model.fc.in_features, num_classes)weightsmodels.ResNet18_Weights.IMAGENET1K_V1是当前 torchvision 推荐的预训练权重加载写法旧的pretrainedTrue写法在新版本里会告警。num_classes改成你数据集的类别数model.fc是 ResNet18 最后的全连接层把它的输出维度换成自己的类别数。这行代码是迁移学习的关键操作前面的卷积层参数全部保留只有最后这层是新训练的。训练循环精简版device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-3) criterion nn.CrossEntropyLoss() scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, patience2, factor0.5) for epoch in range(30): model.train() for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(images), labels) loss.backward() optimizer.step() # epoch 结束后在验证集上算 loss喂给 scheduler model.eval() val_loss 0.0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) val_loss criterion(outputs, labels).item() val_loss / len(val_loader) scheduler.step(val_loss) print(fepoch {epoch}: val_loss {val_loss:.4f})optimizer.zero_grad()必须在每一步前清空梯度否则梯度会累积。model.eval()切换 BN 层到推理模式torch.no_grad()关闭梯度计算省显存。scheduler.step(val_loss)把验证集 loss 喂给调度器触发学习率下降的条件是 loss 不降只看训练 loss 会因为过拟合而误判。4.4 评估指标准确率不够必须看混淆矩阵单一准确率在类别不平衡的数据集上会骗人。某个品种只占总数 3%模型全预测成另一个大类准确率照样有 97%但那个少样本品种一个没认对。桑叶品种分类的实际诉求恰恰是别把少见品种漏掉。评估时至少看三项宏平均 F1macro F1、每个类别的精确率和召回率、混淆矩阵。from sklearn.metrics import classification_report, confusion_matrix import numpy as np all_preds [] all_labels [] model.eval() with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) preds model(images).argmax(dim1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print(classification_report(all_labels, all_preds)) print(confusion_matrix(all_labels, all_preds))classification_report输出每个类别的精确率、召回率和 F1confusion_matrix直观显示哪些品种互相混淆。如果混淆矩阵里某两个品种频繁互相认错比如长相近似的两个品种说明它们的形态差异确实小这时要么加更多该类别的样本要么考虑用细粒度分类的方法把注意力集中在叶脉纹理等局部特征上。5. 桑叶品种分类的避坑记录标注噪声、背景依赖与数据泄漏5.1 标注噪声验证集准确率上不去先怀疑标签别先怀疑模型现象训练 loss 正常下降但验证集准确率始终在 70% 上下波动怎么调参都涨不上去。某课题组遇到过类似情况排查了一天最后随机抽了 50 张错误样本人工看发现其中 8 张的标签本身就是错的。原因约5,000张已标注数据不代表标注一定正确。标注人员对品种的区分标准理解不一致幼叶和成熟叶可能被标成不同品种部分图像里夹带了杂草或病叶。解决训练结束后把验证集错分样本按 batch 输出成图片网格逐个看。如果某个类别的错误样本里混着明显标注错误的图把这些图挑出来重新标注或直接从训练集删除。标注清理的操作重复两轮准确率常有 3~5 个百分点的提升这比换模型划算得多。5.2 类别不平衡少样本品种被淹没在多数类里现象分类报告里大部分类别 F1 在 0.9 以上个别类别只有 0.3 甚至更低。出错的位置集中在某几个样本量很小的品种上。原因数据集的类别按品种划分自然采集时样本量不可能平均。有的桑树品种种植面积小照片数量天然少模型在多数类的梯度主导下对少样本类的特征拟合不充分。解决先统计类别分布确认问题范围然后给少样本类提高采样权重。PyTorch 里用WeightedRandomSampler或在损失函数里传类别权重from torch.utils.data import WeightedRandomSampler class_counts torch.bincount(torch.tensor(train_labels)) class_weights 1.0 / class_counts.float() sample_weights class_weights[train_labels] sampler WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue) train_loader DataLoader(train_dataset, batch_size32, samplersampler)sample_weights越大该样本被抽到的概率越高。replacementTrue允许同一张图在一个 epoch 内重复出现保证少样本类每次迭代都能见到。评估指标同步换成宏平均 F1别只看整体准确率。5.3 背景依赖模型记住了土壤和叶片边缘而不是品种特征现象验证集准确率很高但把自己拍的照片拿过来测准确率暴跌。用 Grad-CAM 可视化发现模型激活的热点区域在叶片周围的土壤和背景上而不是叶片本身。原因桑叶数据的背景不统一有的拍在黄色土壤上有的拍在蓝色薄膜上背景颜色和品种采集批次强相关模型学会了用背景做判断。这在农业图像数据集里是高频问题尤其是不同时间、不同地块采集的数据。解决一是数据增强里加随机背景替换或亮度扰动降低模型对背景的依赖二是如果训练集和实际使用场景的背景差异大换一个带背景多样性的数据集或自采数据补充。可视化检查用 Grad-CAM 是标准操作训练完必须看一遍激活图再下结论。5.4 数据泄漏同一株桑树的多张照片同时出现在训练和验证集现象验证集准确率 95% 以上但测试集或实际表现明显低于这个数字。模型在验证集上“记忆”了植株个体特征而不是泛化到了品种特征。原因随机切分时没有考虑图像来源同一株桑树的多张照片被随机分到了训练和验证两侧。模型的分类依据变成了“这是不是我见过的某一株”而不是“这是哪个品种”。解决如果文件名或目录结构里带植株编号、地块编号、采集日期等信息按这些字段分组后整组划分。没有任何元数据时至少先观察图片命名规律有规律就按规律切没有规律只能承认验证集偏乐观。5.5 损坏图片与错误后缀训练中途 OSError 中断现象训练跑到一半抛OSError: image file is truncated进程中断前面的 epoch 白跑。原因图片文件在传输或压缩过程中损坏部分文件后缀是.jpg但实际内容不是有效 JPEG。解决训练前的完整性扫描不能省。PIL 打开失败的就剔除对于截断的 JPEG可以设置ImageFile.LOAD_TRUNCATED_IMAGES True让它能读出来但这类图片质量已受损宁可剔除。把这些坏文件的名单单独存一份文本方便向数据提供方反馈或后续补采。6. 进阶技巧分层微调与用 Grad-CAM 验证模型的注意力6.1 分层微调先冻住 backbone再解冻最后几层约5,000张图做迁移学习一个常见误区是上来就对全网络用同一个学习率微调。更稳的做法是分两阶段走第一阶段冻住 backbone 的浅层卷积只训练最后的全连接层跑几个 epoch 让分类头先收敛第二阶段解冻深层卷积用更小的学习率微调。for name, param in model.named_parameters(): param.requires_grad False # 先冻结所有层 for name, param in model.fc.named_parameters(): param.requires_grad True # 只训练分类头 # 训练几个 epoch 后解冻 layer4 再微调 for name, param in model.layer4.named_parameters(): param.requires_grad True optimizer torch.optim.AdamW( [p for p in model.parameters() if p.requires_grad], lr5e-5, weight_decay1e-3)冻结的意义在于浅层卷积学的是纹理、边缘这些通用特征桑叶的分类差异主要在中层和深层特征里。先让分类头适应新的类别分布再小步微调深层特征能有效防止特征被破坏。lr5e-5是解冻后的建议值比第一阶段小一半。6.2 Grad-CAM 验证模型到底看了叶子的哪个部位模型准确率达标不代表判断依据合理。桑叶品种判定的本质是细粒度视觉差异模型应该关注叶脉走向、叶缘锯齿、叶片形状这些部位。用 Grad-CAM 把激活热力图叠到原图上能直观看到模型的注意力集中在哪里。常见做法是借助pytorch-grad-cam这个库用GradCAMPlusPlus作为可视化的核心模块。流程固定把模型的最后一层卷积当作目标层传入验证集若干图像导出热力图。如果热力图高亮区域集中在叶片边缘和叶脉说明模型学到了合理的形态特征如果高亮区在背景或叶柄就要警惕前面的背景依赖问题。我曾经在桑叶分类上直接全量微调模型在自家验证集上跑了 0.94 的准确率换了个拍摄环境立刻掉到 0.7。后来养成习惯所有数据集实验训练完先做 Grad-CAM 可视化再下结论这一步花不了十分钟却能提前暴露背景依赖、过拟合这些测试指标看不出来的问题。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询