简介面向深度学习与图像识别的初学者以及需要快速搭建目标检测实验的开发者这份昆虫识别数据集提供了轻量且完整的数据基础能够省去自行采集图片和手工标注的环节尤其适合刚接触YOLO、SSD等目标检测框架的学习者。数据集涵盖 acuminatus、armandi、boerner、coleoptera、leconte、linnaeus 共6类昆虫包内共438个文件包括217张JPG图片、217个XML标注文件及4个TXT说明文件压缩包整体约16.61MB体积小巧便于下载、存储和快速部署。每张图片都有对应的XML标注记录目标类别和边界框坐标可直接用于目标检测、昆虫分类等模型训练TXT文件可供查看类别清单或数据划分方式。数据已按7:2:1划分为训练集、验证集和测试集免去手动切分的步骤从而可以快速迭代模型参数、检验算法效果。目前已有1703人浏览学习适合作为小规模入门数据集用来跑通检测流程、开展课程实验或进行迁移学习热身也可以用于练习数据格式转换和标注可视化检查。1. 217 张昆虫图片能做什么这不是一个“玩具数据集”拿到「昆虫识别数据集.zip」这类小包多数人的第一反应是才 217 张图能训出什么我的判断正好相反——小数据集恰恰是跑通深度学习图像识别全流程的最快路径。这个包包含 6 种昆虫分别是 acuminatus、armandi、boerner、coleoptera、leconte、linnaeus有图片有标注还给出了 7:2:1 的训练/验证/测试划分建议。适合刚学 PyTorch、想做一个完整分类项目、或者需要反复调参验证效果的从业者。别拿它和几百万张的大数据集比拿来练迁移学习、数据增强、模型评估这一套它比大包效率高得多也能把踩坑提前暴露出来。2. 拆开 zip 先做体检目录结构、标注格式和 7:2:1 切分2.1 先看目录别急着训练很多人下载数据集后第一件事就是写训练脚本结果训练到一半发现图片损坏、标签缺失、目录混乱白跑几小时。我现在的习惯是任何数据集 zip 解压后先做一次体检搞清楚三件事——图片在哪、标注在哪、类名怎么映射。unzip 昆虫识别数据集.zip -d insect_data cd insect_data find . -maxdepth 2 -type f | head -20解压时如果终端不识别中文文件名先把 zip 重命名为insect_data.zip再解压省得后面所有路径都带中文。find的-maxdepth 2只扫两层目录避免把大目录树全部打出来head -20是只看前 20 个文件确认结构后再说。接着用 Python 做一个更完整的盘点from pathlib import Path data_dir Path(insect_data) imgs sorted(data_dir.rglob(*.jpg)) anns sorted(data_dir.rglob(*.xml)) sorted(data_dir.rglob(*.txt)) sorted(data_dir.rglob(*.json)) print(jpg 数量:, len(imgs)) print(候选标注数量:, len(anns)) print(图片样例:, [p.name for p in imgs[:5]])rglob是递归查找不管图片嵌在第几层目录都能找出来。.jpg只筛选图片文件XML、TXT、JSON 都先列出来因为标注格式在摘要里没写死拿到手才有真相。这步能快速看出图片和标注是否一一对应尤其要警惕图片 217 张、标注却只有 150 张的情况。2.2 标注格式怎么读VOC、YOLO、CSV 的三种情况这个数据集的摘要只写了“包含图片和标注”没写标注格式。我把常见情况分成三类分别给对应的解析方式。如果看到.xml大概率是 VOC 格式用 ElementTree 解析import xml.etree.ElementTree as ET def parse_voc(xml_path): tree ET.parse(xml_path) root tree.getroot() names [] for obj in root.iter(object): name obj.find(name).text bbox obj.find(bndbox) names.append({ name: name, xmin: int(bbox.find(xmin).text), ymin: int(bbox.find(ymin).text), xmax: int(bbox.find(xmax).text), ymax: int(bbox.find(ymax).text), }) return namesVOC 里每个 object 节点对应一个目标name是类别bndbox是坐标。这种格式适合转到目标检测直接把 XML 转成 YOLO 格式后丢给 ultralytics 训练。如果看到.txt大概率是 YOLO 的归一化坐标格式每行是class x_center y_center width height我一般会写一个解析函数确认坐标范围。def parse_yolo_txt(txt_path): lines txt_path.read_text(encodingutf-8).strip().splitlines() return [line.split() for line in lines if line.strip()]如果看到.json可能是 COCO 格式用json.load()读重点关注categories和annotations两个字段。无论哪种格式我建议先随机抽三张图把标注可视化一下确认不是“文件名对上了但框和昆虫完全无关”。2.3 按 7:2:1 切分先切分再做增强摘要明确说了训练集验证集测试集 721这个比例在小数据集上是合理的。217 张图训练集约 152 张验证集约 43 张测试集约 22 张。切分最忌讳的是直接按目录顺序切因为图片可能是按采集时间排列的顺序切会把同类昆虫全切到训练集里。import random import shutil from pathlib import Path random.seed(42) imgs sorted(Path(insect_data).rglob(*.jpg)) random.shuffle(imgs) n len(imgs) train_imgs imgs[:int(n * 0.7)] val_imgs imgs[int(n * 0.7):int(n * 0.9)] test_imgs imgs[int(n * 0.9):] print(ftrain{len(train_imgs)}, val{len(val_imgs)}, test{len(test_imgs)})random.seed(42)固定随机种子保证每次切分结果一致shuffle打乱顺序避免同类图片扎堆。这个脚本是最基础的随机切分只解决了“顺序”问题没解决“类别均衡”问题。如果某个类只有 10 张图随机切有可能把这类全部切进训练集。更稳的方式是先按类别分组再从每个类里按 7:2:1 抽。from collections import defaultdict class_to_imgs defaultdict(list) for img in imgs: label img.parent.name class_to_imgs[label].append(img) train_imgs, val_imgs, test_imgs [], [], [] for label, label_imgs in class_to_imgs.items(): random.shuffle(label_imgs) k len(label_imgs) train_imgs label_imgs[:int(k * 0.7)] val_imgs label_imgs[int(k * 0.7):int(k * 0.9)] test_imgs label_imgs[int(k * 0.9):]这里默认图片的父目录名就是类别名。如果解压后图片是平铺的就要先从标注文件里拿到类别名再按类别归组。分层切分的好处是每个类在三个集合中都存在模型评估某个冷门类时不会因为测试集里压根没有它而直接报零。3. 用 PyTorch 跑通昆虫分类迁移学习、数据增强和训练参数3.1 为什么选迁移学习217 张图不够从零训一个 CNN六类昆虫152 张训练图平均每类才 25 张。从零训练一个 ResNet 这样的网络参数百万级喂这么点数据基本只能记住训练集验证集一测就露馅。所以我在这种场景下从不自己设计卷积网络直接用 ImageNet 预训练权重把最后一层全连接替换成 6 分类输出前面卷积层全部冻结只训练分类头。import torch import torch.nn as nn from torchvision import models model models.resnet18(pretrainedTrue) num_classes 6 model.fc nn.Linear(model.fc.in_features, num_classes) for name, param in model.named_parameters(): if name.startswith(fc): param.requires_grad True else: param.requires_grad Falseresnet18在小型数据集上比 resnet50 更合适因为 resnet50 参数量大冻结后只训练最后一层问题不大但一旦解冻更多层过拟合速度会非常快。name.startswith(fc)只保留最后的全连接层可训练其余参数不更新这样真正参与训练的参数量很小几分钟就能跑完一个 epoch。提示如果图片是灰度图而预训练模型期望输入是三通道 RGB加载时要先img.convert(RGB)否则后面Normalize的时候维度对不上最常见的报错就是size mismatch。3.2 数据加载与增强让 152 张图“长”出三倍样本数据增强是小数据集最后的救命稻草。我的经验是翻转加旋转加颜色扰动组合起来效果比单用一种好得多。PyTorch 里用torchvision.transforms直接组合。from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomAffine(degrees10, translate(0.05, 0.05)), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])Resize统一到 224×224 是为了匹配 ImageNet 预训练模型的输入RandomHorizontalFlip对昆虫图片有效因为昆虫姿态不依赖左右方向RandomAffine的degrees10控制最大旋转角度旋转太大会把昆虫形状破坏ColorJitter的brightness和contrast模拟光照变化。验证集和测试集不能做随机增强只用Resize和Normalize否则每次评估结果都不一样没法对比实验。Normalize的三个均值和三个标准差是 ImageNet 的标准值使用预训练模型时不能乱改。改成别的数值轻则训练不收敛重则验证集 acc 永远在 20% 左右徘徊这时候先回头检查 Normalize 参数是否和预训练权重匹配。3.3 训练脚本和参数建议batch size、学习率和早停数据切好、增强配好就可以写训练循环了。我一般会把训练和验证拆成两步训练时每个 epoch 结束后立刻在验证集上跑一次。from torch.utils.data import DataLoader from torchvision import datasets train_ds datasets.ImageFolder(insect_split/train, transformtrain_tf) val_ds datasets.ImageFolder(insect_split/val, transformval_tf) train_loader DataLoader(train_ds, batch_size16, shuffleTrue, num_workers2) val_loader DataLoader(val_ds, batch_size16, shuffleFalse, num_workers2) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.1) for epoch in range(15): model.train() for imgs, labels in train_loader: out model(imgs) loss criterion(out, labels) optimizer.zero_grad() loss.backward() optimizer.step() model.eval() total, correct 0, 0 with torch.no_grad(): for imgs, labels in val_loader: out model(imgs) pred out.argmax(dim1) correct (pred labels).sum().item() total labels.size(0) print(fepoch {epoch 1}, val acc: {correct / total:.4f}) scheduler.step()batch_size16在 224×224 的图上每 batch 占内存不算大CPU 也能跑如果显存吃紧就换成 8。lr1e-3是 Adam 配合迁移学习的常用起点太高会震荡太低收敛太慢。StepLR每 5 个 epoch 把学习率乘 0.1让后期训练更稳定。num_workers2是 Linux/Mac 的常用设置Windows 上如果报多线程错误直接改成num_workers0。这个循环只训练最后一层所以 15 个 epoch 完全够。千万不要上来就batch_size64加num_workers8对小数据集反而容易把错误信号放大loss 曲线跟心电图一样。4. 小数据集避坑指南5 个让我重跑实验的常见问题4.1 训练集 acc 99%验证集 acc 只有 60%现象是最经典的自欺欺人训练 loss 一路降到 0.1 以下训练集 acc 到了 99%但验证集 acc 卡在 60%。原因很简单152 张训练图对 6 分类来说样本量太少模型直接把图像背景和噪声一起记住了迁移学习也没救回来。解决方法是分三路下手一是冻结更多层只训练最后一个全连接头二是加强数据增强把RandomAffine的degrees加到 15或者加RandomErasing让模型不能只看局部纹理三是在全连接层里加nn.Dropout(p0.3)强制模型不依赖单个神经元的输出。从那以后我养成了习惯训练时每隔几个 epoch 就瞟一眼训练 acc 和验证 acc 的差值差值超过 25 个点立刻停不等着把 15 个 epoch 跑完。4.2 PIL 报 “cannot identify image file”现象是训练到一半DataLoader 突然抛PIL.UnidentifiedImageError: cannot identify image file断点续训都麻烦。原因是数据集中有些图片扩展名是.jpg但文件本身是坏的、空的或者后缀被改过Pillow 打开后识别不出真实格式。解决方法是训练前先做一轮图片体检把不能正常打开的图片挑出来from PIL import Image from pathlib import Path def filter_bad_images(paths): good_paths [] for p in paths: try: with Image.open(p) as im: im.verify() good_paths.append(p) except Exception as e: print(bad image:, p, e) return good_paths imgs filter_bad_images(sorted(Path(insect_data).rglob(*.jpg))) print(保留图片数:, len(imgs))Image.open默认是惰性加载不真正读全部数据必须跟一个verify()才能把文件完整性校验跑完。挑出来的坏图片不要直接删先移到broken_images/目录确认是损坏还是偶尔个例。如果坏图只有三张移除后重新切分即可如果坏图超过 10%要考虑 zip 包是不是没下载完整重下一次再验。4.3 标签和图片对不上模型学了个寂寞现象是训练正常验证 acc 也不低但看分类报告发现某个类的 precision 是 0。原因是切分图片列表和标注列表没有按同一个文件名规则配对比如图片叫0037.jpg标注叫0037.xml但切分时一个用了带路径的完整名另一个用了文件名排序一乱全错位。我现在的做法是不管图片和标注放在哪个目录统一用Path.stem作为关联键再写一个检查脚本确认每个图片都有对应标注、每个标注都有对应图片。from pathlib import Path img_stems {p.stem: p for p in Path(insect_data).rglob(*.jpg)} ann_stems {p.stem: p for p in Path(insect_data).rglob(*.xml)} img_only set(img_stems) - set(ann_stems) ann_only set(ann_stems) - set(img_stems) print(只有图片没标注:, img_only) print(只有标注没图片:, ann_only)Path.stem返回去掉后缀的文件名0037.jpg和0037.xml会得到同一个0037是最安全的配对方式。不要手动去字符串切片拼接文件名里有没有下划线、减号不同系统排序规则还不一样。检查通过后再进切分流程能省掉后面一整轮踩坑。4.4 总 acc 还不错但某个类全错现象是验证集整体 acc 75%但armandi这个类的 recall 是 0因为它总共只有 15 张图被随机切分切走了 12 张训练、3 张验证测试集里只剩 1 张。原因就是类别不平衡加随机切分。解决方法是先用分层切分替换随机切分再看具体类别分布。如果某个类实在太少我给训练集加WeightedRandomSampler让少样本类被抽到的概率更高。from torch.utils.data import WeightedRandomSampler labels [label for _, label in train_ds.samples] weights [1.0 / labels.count(l) for l in labels] sampler WeightedRandomSampler(weights, num_sampleslen(labels), replacementTrue) train_loader DataLoader(train_ds, batch_size16, samplersampler)weights越大采样到该样本的概率越高1.0 / labels.count(l)是反比权重样本越多的类别权重越小样本越少的类别权重越大。num_samples设置成训练集总长度保证每个 epoch 采样数量不变。注意用sampler后不能再传shuffleTrue这是常见的翻车点Dataloader 会直接报冲突错误。4.5 Windows 下中文路径乱码模型加载失败现象是换到 Windows 后解压出的中文目录名显示成乱码ImageFolder找不到类目录直接抛FileNotFoundError。原因是 zip 包里的中文文件名编码在不同系统间不一致Windows 默认用 GBKLinux 用 UTF-8解压工具处理不好就会乱。解决方法是解压后第一步就把所有目录和文件名改成 ASCII 命名不要抱侥幸心理。批量重命名可以用pathlib在 Python 里完成把中文目录甲虫/改成beetle/图片保持原名不变。另外读取标注文件时所有open()操作都显式加encodingutf-8不要依赖系统默认编码否则同样的代码在 Windows 和 Linux 上跑出完全不同的结果。5. 验证模型不是看 val_acc混淆矩阵和置信度阈值的实用操作217 张图按 7:2:1 切测试集只有 21 张左右错一张图 acc 就掉将近 5 个点。只看验证集 acc根本分不清模型是“全部类都学得均衡”还是“运气好把常见的几个类猜对了”。我每次训完的最后一步永远是输出混淆矩阵和分类报告。from sklearn.metrics import confusion_matrix, classification_report model.eval() y_true, y_pred [], [] with torch.no_grad(): for imgs, labels in val_loader: out model(imgs) conf, pred torch.softmax(out, dim1).max(dim1) y_true labels.tolist() y_pred pred.tolist() print(confusion_matrix(y_true, y_pred)) print(classification_report(y_true, y_pred, target_namestrain_ds.classes))torch.softmax把 logits 转成概率分布max(dim1)同时拿到最大概率和对应类别。混淆矩阵对角线是对的部分非对角线是具体哪个类被认成了哪个类比单个 acc 数字有用得多。如果coleoptera经常被认成leconte说明这两个类的训练样本在视觉上太像需要回去看原始图片和标注是标注错了还是本来就需要更细的特征。分类报告里的 macro 平均和 weighted 平均也值得看。六类样本不均衡的情况下weighted precision 会被样本多的类拉高macro 平均则只看每一类的平均表现。我一般以 macro 为主因为这个小数据集的目标是“每个类都要认对”不是“把总量刷高”。数据量小还带来另一个问题模型对低置信度样本的判断不可靠。我给预测加一道置信度门槛低于阈值的样本不采用宁可标成“不确定”。with torch.no_grad(): for imgs, labels in val_loader: probs torch.softmax(model(imgs), dim1) conf, pred probs.max(dim1) mask conf 0.75 print(f高置信度样本: {mask.sum().item()} / {labels.size(0)})阈值设多少没有标准答案我会在验证集上扫一遍 0.5、0.6、0.75、0.9看高置信度样本被接受后的 acc 能到多少。小数据集上这个手段特别有用因为有些类本身外观模糊让模型硬猜最终只会带歪整个评估结果。从那以后我每次跑完一组实验都强制走一遍“confusion matrix classification report 置信度阈值扫描”这个流程再去看 val_acc。这包昆虫识别数据集虽小但正好能练出这套评估习惯。如果你也想把这个 zip 包拿来复现一遍解压后按第二章的体检流程走再用第三章的迁移学习脚本训练最后按第五章做模型评估基本不会卡在奇怪的地方。希望帮到你。本文还有配套的精品资源点击获取