尧图精选

蝴蝶分类数据集20类实战:从解压到Baseline训练与避坑指南

🕒 发布时间:2026/10/1 3:04:44 📁 来源:尧图网络
简介蝴蝶分类数据集20类.zip 面向机器学习与图像识别方向的开发者、深度学习入门者及生物多样性研究者用于训练和测试蝴蝶物种自动识别模型。压缩包共1870个文件以1866张jpg图像为主体另含2个txt标签文件、1个json字典文件和1个系统隐藏文件整体约60.96MB。其中json字典记录图片路径与物种、属名等元信息txt文件分别列出20个蝴蝶物种名称及其所属属名图像按类别组织便于直接构建分类标签。数据集覆盖20个蝴蝶种类可用于卷积神经网络等模型的训练与验证也适合作为图像分类练手项目或生物学比较分析的样本来源。目录结构清晰、标注信息完整能帮助读者省去自行采集与整理数据的时间快速进入建模与实验环节。目前已有118人学习下载适合需要现成多类别图像数据的中初级实践者参考使用。1. 蝴蝶分类数据集20类.zip从拿到压缩包到跑通第一个baseline你从群里或者某个公开资源站下载了一个叫「蝴蝶分类数据集20类.zip」的压缩包解压之后大概率是一堆按文件夹分好的蝴蝶图片每个文件夹名对应一个物种。问题来了这个数据集到底能不能直接喂给模型类别是否均衡图片尺寸是否统一要不要先做清洗这些才是决定你后面模型能不能收敛的关键。我见过太多人拿到数据集直接ImageFolder一把梭结果训练准确率卡在60%上不去回头一看有三个类别的图片混进了同一个文件夹还有一批图是纯色背景的无效样本。这篇文章就是帮你把「蝴蝶分类数据集20类」从解压到跑通第一个baseline的完整路径走一遍包括目录结构怎么整理、数据怎么划分、增强怎么配、模型怎么选、训练中常见的翻车点怎么排查。适合刚拿到这个数据集想做课程设计、打比赛或者练手迁移学习的人也适合想快速验证一个分类pipeline的老手。2. 先搞清楚蝴蝶分类数据集20类的目录结构与类别分布2.1 解压后先别急着写代码用三条命令摸清家底拿到压缩包的第一件事不是打开Python而是用命令行把基本情况摸清楚。很多人跳过这一步后面划分训练集时才发现某个类别只有十几张图或者图片格式五花八门。常见做法是先看目录层级、文件数量和格式分布。# 查看解压后的目录结构只看两层 find ./butterfly20 -maxdepth 2 -type d | head -30 # 统计每个类别文件夹下的图片数量 for d in ./butterfly20/*/; do echo -n $d: find $d -type f \( -iname *.jpg -o -iname *.jpeg -o -iname *.png \) | wc -l done # 查看图片格式分布确认是否有非图片文件混入 find ./butterfly20 -type f | sed s/.*\.// | sort | uniq -c | sort -rn第一条命令让你看清目录是butterfly20/类别名/图片还是多了一层嵌套。第二条命令输出每个类别的图片数量如果发现最多和最少相差超过5倍后面就要考虑重采样或者用带权重的损失函数。第三条命令检查是否有.txt、.DS_Store、.db这类垃圾文件混在里面这些文件如果不清理ImageFolder在某些版本下会直接报错。参数说明-maxdepth 2控制目录递归深度避免输出太多-iname忽略大小写匹配扩展名sed s/.*\.//提取扩展名。如果发现图片总数在2000到4000之间每个类别平均100到200张这属于小样本分类的典型场景后面选模型和增强策略都要围绕这个量级来设计。2.2 类别不均衡时先做一次可视化统计再决定要不要重采样统计完数量之后建议用一段短脚本把分布画出来同时检查图片尺寸是否统一。蝴蝶数据集常见的问题是不同来源的图片被合并进来尺寸从224×224到1024×1024都有直接resize到统一尺寸会导致部分图片严重失真。import os from PIL import Image import matplotlib.pyplot as plt from collections import Counter root ./butterfly20 class_counts {} size_counter Counter() 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))] class_counts[cls] len(imgs) # 抽样统计尺寸每个类别最多看20张 for f in imgs[:20]: with Image.open(os.path.join(cls_dir, f)) as im: size_counter[im.size] 1 # 打印类别分布 for k, v in sorted(class_counts.items(), keylambda x: x[1]): print(f{k}: {v}) # 打印最常见的5种尺寸 print(\nTop 5 sizes:, size_counter.most_common(5)) # 画柱状图 plt.figure(figsize(12, 4)) plt.bar(range(len(class_counts)), list(class_counts.values())) plt.xticks(range(len(class_counts)), list(class_counts.keys()), rotation90) plt.tight_layout() plt.savefig(class_dist.png, dpi120)这段代码做了三件事统计每个类别的图片数、抽样统计图片尺寸分布、画出类别分布柱状图。如果发现某个类别少于50张而最多的超过300张建议在训练时用WeightedRandomSampler做重采样而不是简单复制图片。尺寸方面如果主流尺寸是224×224或256×256直接统一resize问题不大如果尺寸差异极大建议先做中心裁剪再resize避免长宽比失真。注意不要用os.listdir直接遍历所有文件后假设都是图片一定要加扩展名过滤。我见过有人把标注文件labels.csv也当成图片喂进去训练时直接报UnidentifiedImageError。3. 把蝴蝶分类数据集20类切成训练/验证/测试集三种划分方案与代码3.1 分层抽样划分保证每个类别在三个集合中的比例一致小样本分类最怕的是某个类别在验证集里一张图都没有。常见做法是用sklearn的train_test_split做分层抽样先按8:1:1切再对训练集做一次切分得到验证集。下面这段脚本会生成三个.txt文件每行是图片路径 类别索引后续用自定义Dataset读取。import os from sklearn.model_selection import train_test_split root ./butterfly20 classes sorted([d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))]) class_to_idx {c: i for i, c in enumerate(classes)} all_paths, all_labels [], [] for cls in classes: cls_dir os.path.join(root, cls) for f in os.listdir(cls_dir): if f.lower().endswith((.jpg, .jpeg, .png)): all_paths.append(os.path.join(cls_dir, f)) all_labels.append(class_to_idx[cls]) # 先切出测试集占10% X_train_val, X_test, y_train_val, y_test train_test_split( all_paths, all_labels, test_size0.1, stratifyall_labels, random_state42 ) # 再从剩余中切出验证集占10%相对于总数 X_train, X_val, y_train, y_val train_test_split( X_train_val, y_train_val, test_size0.111, stratifyy_train_val, random_state42 ) def dump(paths, labels, filename): with open(filename, w) as f: for p, l in zip(paths, labels): f.write(f{p}\t{l}\n) dump(X_train, y_train, train.txt) dump(X_val, y_val, val.txt) dump(X_test, y_test, test.txt) print(ftrain: {len(X_train)}, val: {len(X_val)}, test: {len(X_test)})关键参数是stratify它保证切分后每个类别的比例和原始一致。random_state42固定随机种子方便复现。第二次切分的test_size0.111是因为要从90%的数据里再切出10%实际比例约等于总数的10%。如果某个类别只有20张图按8:1:1切完训练集只有16张这时候建议把验证集和测试集合并只做训练/验证划分或者用交叉验证。3.2 用ImageFolder加自定义划分适合快速验证的写法如果你不想生成.txt文件也可以直接用torchvision.datasets.ImageFolder配合Subset做划分。这种写法代码更短但灵活性差一些适合快速跑通baseline。import torch from torchvision import datasets, transforms from torch.utils.data import Subset import numpy as np transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), ]) full_dataset datasets.ImageFolder(./butterfly20, transformtransform) targets np.array(full_dataset.targets) # 按类别分层采样索引 train_idx, val_idx, test_idx [], [], [] for c in range(len(full_dataset.classes)): c_idx np.where(targets c)[0] np.random.seed(42) np.random.shuffle(c_idx) n len(c_idx) n_test max(1, int(n * 0.1)) n_val max(1, int(n * 0.1)) test_idx.extend(c_idx[:n_test]) val_idx.extend(c_idx[n_test:n_test n_val]) train_idx.extend(c_idx[n_test n_val:]) train_set Subset(full_dataset, train_idx) val_set Subset(full_dataset, val_idx) test_set Subset(full_dataset, test_idx) print(len(train_set), len(val_set), len(test_set))这种写法的好处是不用维护额外的文件列表坏处是每次换随机种子都要重新跑。max(1, ...)保证即使某个类别只有几张图验证集和测试集也至少有一张。实际项目中我一般用第一种.txt方案因为方便和团队共享划分结果也方便后续做交叉验证。4. 训练第一个baseline从ResNet18到EfficientNet的选型与参数配置4.1 为什么小样本蝴蝶分类优先用预训练ResNet18而不是从头训练蝴蝶分类数据集20类通常只有几千张图从头训练一个CNN几乎必然过拟合。常见做法是加载ImageNet预训练权重替换最后一层全连接只微调分类头或者整个网络。ResNet18参数量约1100万在224×224输入下推理速度快适合先跑通pipeline。如果你追求更高精度可以换EfficientNet-B0或ConvNeXt-Tiny但训练时间会增加。import torch import torch.nn as nn from torchvision import models def build_model(num_classes20, archresnet18, pretrainedTrue): if arch resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1 if pretrained else None) model.fc nn.Linear(model.fc.in_features, num_classes) elif arch efficientnet_b0: model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1 if pretrained else None) model.classifier[1] nn.Linear(model.classifier[1].in_features, num_classes) else: raise ValueError(fUnsupported arch: {arch}) return model model build_model(num_classes20, archresnet18) print(sum(p.numel() for p in model.parameters() if p.requires_grad))替换最后一层时要注意ResNet18的最后一层叫fcEfficientNet-B0的叫classifier[1]不同版本的torchvision命名可能略有差异。如果加载预训练权重时报size mismatch说明分类头没替换干净检查一下是否有多余的fc层。参数说明pretrainedTrue会下载ImageNet权重第一次运行需要网络如果离线环境可以提前下载好权重文件再用weights参数指定路径。4.2 训练循环里的五个必调参数学习率、batch size、优化器、调度器、早停小样本微调的参数配置和从头训练差别很大。我一般用AdamW学习率设1e-4到3e-4batch size根据显存尽量大通常32或64。调度器用CosineAnnealingLR训练20到30个epoch。早停patience设5到7监控验证集准确率。import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR from torch.utils.data import DataLoader from torchvision import transforms train_tf transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(0.2, 0.2, 0.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]), ]) # 假设用前面生成的txt构建Dataset这里省略Dataset类定义 train_loader DataLoader(train_set, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_set, batch_size32, shuffleFalse, num_workers4) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr3e-4, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30) best_acc, patience, counter 0.0, 7, 0 for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), 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) pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) acc correct / total print(fEpoch {epoch1}, val_acc{acc:.4f}) if acc best_acc: best_acc, counter acc, 0 torch.save(model.state_dict(), best.pth) else: counter 1 if counter patience: print(Early stop) break学习率3e-4是微调预训练模型的常用起点如果loss震荡明显就降到1e-4。weight_decay1e-4相当于L2正则小样本场景下能抑制过拟合。数据增强里RandomResizedCrop的scale(0.7, 1.0)控制裁剪面积比例蝴蝶图片主体通常居中裁剪太狠会丢掉关键特征。ColorJitter的强度不要太大蝴蝶翅膀颜色是重要分类依据色相抖动过强反而有害。提示如果验证集准确率在前5个epoch就冲到90%以上然后开始下降大概率是学习率太大或者增强太弱导致过拟合。先把学习率降到1e-4再考虑加Dropout或Label Smoothing。5. 蝴蝶分类数据集20类训练中的避坑与排查记录5.1 现象训练loss正常下降但验证准确率始终在随机水平附近原因最常见的是标签和图片路径没对齐。用.txt方案时如果生成文件时类别索引和class_to_idx不一致模型学到的映射就是错的。另一个可能是Normalize的均值和方差用错了比如用了ImageNet的统计量但输入是灰度图。解决先打印前10个batch的标签分布确认每个类别都出现过。再用一张训练集图片过一遍模型看预测结果是否合理。如果标签没问题检查transforms是否在验证集上误用了随机增强。5.2 现象训练到第3个epoch突然报CUDA out of memory原因num_workers设太大导致内存泄漏或者验证时没有用torch.no_grad()导致计算图累积。蝴蝶数据集图片尺寸如果没统一某个batch里混入超大图也会爆显存。解决把num_workers降到2或0先排查验证循环加上with torch.no_grad():。如果还不行在Dataset的__getitem__里强制resize到固定尺寸不要依赖DataLoader的collate_fn自动处理。5.3 现象某个类别的准确率始终为0原因这个类别的图片可能全部是无效样本比如纯背景、模糊、或者标注错误。也可能是这个类别在训练集中样本太少模型直接忽略了。解决把这个类别的图片单独抽出来可视化人工检查一遍。如果确实有大量无效图考虑剔除或重新标注。如果只是样本少用WeightedRandomSampler给这个类别更高采样权重或者在损失函数里加类别权重。5.4 现象模型在测试集上表现远好于验证集原因划分数据时没有固定随机种子导致每次运行验证集和测试集内容不同。或者测试集里混入了训练集的图片造成数据泄漏。解决固定random_state并且用文件哈希或路径去重确保三个集合没有交集。我一般会在划分后加一段检查代码用集合运算确认train_paths test_paths为空。5.5 现象推理时单张图片预测结果和批量预测不一致原因推理时的预处理和训练时的验证预处理不一致。比如训练用了CenterCrop(224)推理时直接Resize(224)导致输入分布偏移。解决把验证集的transform单独封装成一个函数推理时直接调用同一个函数。不要凭记忆手写预处理容易漏掉Normalize或者用错插值方式。6. 用混淆矩阵和Grad-CAM定位蝴蝶分类的难分物种跑通baseline之后准确率卡在85%左右上不去这时候需要看模型到底在哪些类别上犯错。我一般先画混淆矩阵找出混淆最严重的类别对再用Grad-CAM看模型关注区域是否落在蝴蝶翅膀上。下面这段代码用sklearn和pytorch-grad-cam做分析。import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image # 假设已经跑完测试集收集了所有预测和真实标签 all_preds, all_labels [], [] model.eval() with torch.no_grad(): for imgs, labels in test_loader: imgs imgs.to(device) preds model(imgs).argmax(1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) disp ConfusionMatrixDisplay(cm, display_labelsfull_dataset.classes) disp.plot(xticks_rotation90, cmapBlues) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi120) # Grad-CAM取最后一层卷积 target_layers [model.layer4[-1]] cam GradCAM(modelmodel, target_layerstarget_layers) # 取一张验证集图片 input_tensor val_set[0][0].unsqueeze(0).to(device) grayscale_cam cam(input_tensorinput_tensor)[0] # 叠加到原图需要反归一化 rgb_img input_tensor[0].cpu().permute(1, 2, 0).numpy() rgb_img (rgb_img - rgb_img.min()) / (rgb_img.max() - rgb_img.min()) visualization show_cam_on_image(rgb_img, grayscale_cam, use_rgbTrue) plt.imsave(gradcam.png, visualization)混淆矩阵能直接告诉你哪两个物种被频繁混淆比如某两种蛱蝶在翅膀纹理上极其相似模型靠颜色区分但测试集里颜色变异大。Grad-CAM则能验证模型是否真的在看蝴蝶主体如果热力图集中在背景的树叶或花朵上说明模型学到了虚假相关这时候需要加背景随机裁剪或者用更严格的中心裁剪。参数说明target_layers选最后一层卷积是因为它保留了空间信息且感受野足够大。show_cam_on_image要求输入RGB图像在0到1之间所以要先做min-max归一化。如果显存不够Grad-CAM可以只跑几张图不用全量跑。我自己的习惯是每次训完一个模型先看混淆矩阵再决定要不要继续调参。如果混淆矩阵对角线很干净只是个别类别差那就针对性补充该类别的数据或者做类别特定的增强。如果混淆矩阵一片糊那说明模型根本没学到有区分度的特征得回头检查数据质量和预处理。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →