拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从数据集清洗到模型微调:动物图像分类训练避坑指南

从数据集清洗到模型微调:动物图像分类训练避坑指南

简介:这套动物图像数据集汇集了10个常见类别共28000余张图片,涵盖狗、猫、马、蜘蛛、蝴蝶、鸡、羊、牛、松鼠和大象,全部经过人工筛选,画质中等且接近真实拍摄状态,适合用于图像分类、模型训练、算法对比以及迁移学习实践。数据按类别分文件夹组织,每类图片数量从2000到5000张不等,目录层级简单,研究人员可直接按文件夹加载,也便于生物学家模拟智能画廊等贴近业务的应用。压缩包内共2000个文件,以jpeg和jpg图片为主(分别有1488个和503个),另有8个png及1个Python脚本可辅助数据预处理或批量处理,整个包为7z格式,大小569.39MB。作者曾用该数据集测试自制卷积神经网络和谷歌Inception等模型,准确率从约80%提升至98%,说明数据规模和类别分布足以支撑不同复杂度的实验。目前已有142人学习下载,对计算机视觉入门者或需要快速验证分类算法的研究人员来说,是一份结构清晰、可直接上手的数据资源。

1. 十个类别的动物 JPG 数据集:28000 张图到底够不够训练一个能用的图像分类模型

做图像分类项目,最烦的不是调模型,而是没有干净、能直接喂进网络的训练数据。这个资源把 10 个不同类别的动物图片整理成了 28000+ 张 JPG,覆盖猫、狗、马、大象这类真实拍摄照片,不是 CIFAR-10 那种 32x32 的小缩略图。如果你正在做动物识别、图像分类课设,或者想验证一套训练流程能不能跑通,这份数据集能帮你省掉爬虫、清洗、去重这一大段重复劳动。它适合两类人:一是刚接触深度学习分类任务、需要标准数据练手的学生;二是已经在做检测或分类项目、需要一个快速 baseline 数据集的工程师。先给个结论:28000 张图、每类两千多张的量级,配合预训练模型微调,跑到 90% 以上准确率是大概率事件,但前提是先把目录结构、图片完整性和划分方式搞清楚——这三个环节翻车,后面全白搭。

2. 数据集构成与目录结构:先摸清 28000 张 JPG 的家底再动手

2.1 目录结构与命名规则

解压之后第一件事,不是急着写训练脚本,而是先看目录。这个资源的常规打包方式是每个类别一个文件夹,类名即标签。典型结构如下:

animals-10/ ├── butterfly/ # 类别目录名 = 标签 │ ├── 00000001.jpg │ ├── 00000002.jpg │ └── ... ├── cat/ ├── chicken/ ├── cow/ ├── dog/ ├── elephant/ ├── horse/ ├── sheep/ ├── spider/ └── squirrel/

文件名通常是纯数字或者"类别名_编号"的组合,比如dog_001.jpg。这种结构对 PyTorch 的ImageFolder和 YOLOv8 的 classify 模式都非常友好,标签完全由目录名决定,不需要额外维护 CSV 映射表。我一般拿到手会先执行一次tree -L 2确认层级,再逐个目录数一下图片数量,防止某个文件夹下面还套着子文件夹——这种"目录套目录"的情况在网盘打包的资源里出现过不止一次,ImageFolder会把子目录当成新类别,类别数直接翻倍。

2.2 类别构成与样本分布:为什么这 10 类适合做分类入门

这个数据集的 10 个类别,常见构成是 dog、cat、horse、elephant、butterfly、chicken、cow、sheep、spider、squirrel(以实际解压后的目录名为准)。每组大约 2400~3000 张,整体在 28000+ 的量级。这个类别设计是有讲究的:大类目之间形态差异足够大,比如大象和蝴蝶几乎不可能互相认错,模型容易快速收敛,给新手建立信心;但同时又安排了 sheep 和 cow、dog 和 cat 这种"高危混淆对",让实验后期有调优空间,不至于一次跑完就无事可做。

类别参考数量识别难度主要混淆对象
dog约3000低cat
cat约2800低dog
horse约2800中cow(草原背景)
elephant约2600低无(体型特征明显)
butterfly约2500中spider(深色小物体)
chicken约2800中无(禽类特征突出)
cow约2500中sheep、horse
sheep约2700中cow
spider约2400高butterfly、背景纹理
squirrel约2600中cat(树丛场景)

关于图片本身的规格,这类爬取整理的数据集通常分辨率不高,常见在 200~500 像素区间,也有少数超过 1024 的大图,JPEG 压缩质量参差不齐。JPG 格式的优势是省空间,28000 张全量也就 1~2GB,PNG 可能要翻三倍。对 CNN 来说,JPG 的压缩损失在可接受范围内——模型反正会做 resize 到 224 或 256,过高的原始分辨率反而浪费显存。需要注意的恰恰反过来:很多图本身就模糊,不是压缩的锅,是拍摄时对焦就飘了,这类图在增强阶段会被进一步放大问题。

3. 把原始 JPG 变成可训练数据集:校验、划分与增强的完整流程

3.1 完整性校验:先把坏图揪出来

不要直接拿原始目录开训。网盘下载的资源,传输中断、百度网盘秒传失败、源站点本身就缺文件,都会导致 JPG 头尾不全。最典型的坑是:训练到第 3 个 epoch 突然报OSError: image file is truncated,然后整个进程崩掉。PIL 默认对截断图片只是警告不报错,但 PyTorch 的 DataLoader 多进程读取时会把警告升级成异常。所以我拿到任何数据集的第一步永远是跑一遍校验脚本:

# check_images.py import os from collections import Counter from PIL import Image root = "animals-10" bad = [] stats = Counter() for cls in os.listdir(root): cls_path = os.path.join(root, cls) if not os.path.isdir(cls_path): continue for name in os.listdir(cls_path): path = os.path.join(cls_path, name) stats[cls] += 1 try: with Image.open(path) as im: im.load() # 强制解码整张图,截断文件在这里抛异常 if im.mode not in ("RGB", "L"): bad.append((path, "mode:%s" % im.mode)) except Exception as e: bad.append((path, str(e))) print("各目录数量:", dict(stats)) print("问题文件数:", len(bad)) for item in bad[:30]: print(item)

这段代码两件事:im.load()强制完整解码,截断、损坏的文件会在这里抛出异常并被记录;同时检查颜色模式,只保留 RGB 和灰度 L。在ImageFolder默认流程里,灰度图会被当作单通道输入,和预训练模型期望的 3 通道对不上,轻则训练报错,重则通道对齐出问题、loss 直接 NaN。发现灰度图后我一般统一转成 RGB 再覆盖保存,不删除——删除会破坏类别比例。

3.2 分层划分 train / val / test:按类别比例切,别全局 shuffle

很多教程直接random.shuffle所有文件再按 8:1:1 切,这在类别均衡的数据集上问题不大,但这个资源每类数量略有差异,全局切会导致某个类别在验证集里占比偏高或偏低,测试结果一次一个样。正确做法是按类别分层划分,每类内部独立切分:

# split.py import os, random, shutil random.seed(42) # 固定种子,保证可复现 root = "animals-10" out = "split" ratio = (0.8, 0.1, 0.1) # train / val / test for cls in os.listdir(root): cls_path = os.path.join(root, cls) if not os.path.isdir(cls_path): continue files = sorted(os.listdir(cls_path)) random.shuffle(files) n = len(files) n_train = int(n * ratio[0]) n_val = int(n * ratio[1]) parts = { "train": files[:n_train], "val": files[n_train:n_train + n_val], "test": files[n_train + n_val:], } for part, names in parts.items(): dst = os.path.join(out, part, cls) os.makedirs(dst, exist_ok=True) for f in names: shutil.copy(os.path.join(cls_path, f), os.path.join(dst, f)) print("划分完成:train : val : test = 8 : 1 : 1")

参数说明:random.seed(42)固定随机序列,同一份数据任何时候重新划分都能得到相同结果,这对复现实验结果至关重要;8:1:1 是 28000 张量级下的常用比例,每类大约 2240 张训练、280 张验证和测试。如果后续要做数据增强或集成实验,可以把测试集单独锁起来,只在 train 和 val 之间反复腾挪,避免验证集被增强样本污染。另外注意一个细节:如果文件名里同一个前缀代表同一只动物(比如horse_0001_01.jpg和horse_0001_02.jpg是同一匹马的连续帧),必须按前缀分组再划分,否则训练集和验证集之间会泄漏场景信息,这部分在第 5 章避坑里细说。

3.3 数据增强参数:旋转别超过 15 度

划分完成后,训练集需要做增强。动物分类和 CIFAR-10 有本质区别:动物图像有明确的"上下"语义,旋转 90 度或 180 度会产生头朝下、侧躺这种反自然样本,模型会强行去学这些错误先验。我用的是下面这套参数,普适性很好:

# transforms.py from torchvision import transforms transform_train = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=15), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 验证集/测试集只做 resize 和归一化,不做任何随机增强 transform_eval = 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.7, 1.0)表示裁剪区域至少占原图 70%,这个下限比默认的 0.08 温和得多,因为动物主体通常占画面很大比例,裁太狠会把尾巴或耳朵当成主体;RandomRotation(degrees=15)是我反复调过的边界值,超过 30 度猫狗的姿态就开始不自然;ColorJitter三个通道的抖动幅度控制在 0.3,模拟不同光照和摄像头白平衡差异,再大就会出现色彩失真。同一组变换里,RandomHorizontalFlip对大部分动物是安全的,但如果你后面做的是文字识别或车牌识别这类方向对称性敏感的任务,这一项必须关掉——这是另一个话题了。

4. 训练方案:PyTorch 微调与 YOLOv8 两套落地方案

4.1 PyTorch 数据加载与预训练模型微调

数据准备好之后,最快出效果的路线不是从零训练,而是加载 ImageNet 预训练权重做迁移学习。ResNet18 在这个 10 类任务上足够用,显存占用小,单卡 8GB 就能跑。核心思路是冻结骨干网络,只训练最后的全连接分类头:

# train.py(关键部分) import torch import torch.nn as nn from torchvision import datasets, models from torch.utils.data import DataLoader train_ds = datasets.ImageFolder("split/train", transform=transform_train) val_ds = datasets.ImageFolder("split/val", transform=transform_eval) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4, pin_memory=True) val_loader = DataLoader(val_ds, batch_size=64, shuffle=False, num_workers=4, pin_memory=True) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) for p in model.parameters(): p.requires_grad = False # 冻结骨干,只训分类头 model.fc = nn.Linear(model.fc.in_features, 10) model = model.to(device) optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss()

参数说明:model.fc.in_features读取原分类头的输入维度,ResNet18 是 512,这里不需要手写;lr=1e-3是 Adam 在分类头场景下的稳妥起点,处理大约两万两千张训练图时不需要学习率预热;num_workers=4取决于机器核心数,Windows 上建议改成 0,否则多进程会频繁报错——这是平台差异,不是代码问题。冻结骨干之后训练非常快,20 个 epoch 在单张 3090 上大约 10 分钟,在 CPU 上也能跑,只是慢三到五倍。

4.2 训练循环与收敛判断

训练循环常规但有三个细节值得提:验证集评估不计算梯度、按验证准确率保存最优模型、验证指标连续不再上升就提前停。第三个尤其关键,动物数据集各类别难度不均,spider 类可能还在慢慢涨,而 elephant 类早就饱和了,全局准确率容易进入平台期:

# train_loop.py(伪代码,省略批循环细节) best_acc = 0.0 patience = 5 bad_epochs = 0 for epoch in range(30): train_one_epoch(model, train_loader, optimizer, criterion, device) acc = evaluate(model, val_loader, device) # 验证准确率 print(f"epoch {epoch:02d} val_acc={acc:.4f}") if acc > best_acc: best_acc = acc torch.save(model.state_dict(), "best_resnet18.pth") bad_epochs = 0 else: bad_epochs += 1 if bad_epochs >= patience: print("验证集连续 5 个 epoch 未提升,提前停止") break

patience=5的意思是允许验证集最多连续 5 个 epoch 不创新高,之后强制停止并回退到保存的最优权重。这个机制比"固定训练 30 轮"更省时间,而且能避免过拟合尾段——训练后期损失还在降,但验证集开始抖动,说明模型在记忆训练集的背景纹理而不是动物本身。

4.3 YOLOv8 直接跑分类:零转换成本

如果你想用 YOLOv8 的训练管线做这个数据集,有一个很多教程没点破的捷径:YOLOv8 内置 classify 模式,数据组织格式和ImageFolder完全一致,第 3 章划分出来的split/train、split/val目录可以直接用,不需要转成 TXT 标签,也不需要标注框:

yolo classify train data=split model=yolov8n-cls.pt epochs=30 imgsz=224 batch=32

data=split指向的目录里必须有 train / val 两个子目录,且每个子目录下面按类别归档图片,这正是第 3 章脚本的输出结构。yolov8n-cls.pt是 YOLOv8 官方在 ImageNet 上预训练的分类权重,检测版的yolov8n.pt不能直接用于 classify 模式。需要提醒一句:如果你最终目标是目标检测(画框框住动物),仅仅有这个分类数据集不够,必须额外标注边界框,常见做法是用 labelImg 手工标注一部分,或者先用检测模型自动生成伪标签再人工修正。分类数据集和检测数据集是两套东西,别指望一份 JPG 直接喂出检测模型。

5. 避坑记录:动物数据集训练里踩过的五个常见问题

5.1 数据准备期的三个坑

坑一:验证集准确率忽高忽低,每次跑结果差异巨大。现象是同一个random.seed下,改一个不相关的超参数,验证集结果波动超过 5 个百分点。原因是划分时用了全局random.shuffle再切分,没有按类别分层,某些类别在验证集里的样本量只有几十张,随机波动被放大。解决就是回到第 3.2 节的按类别分层划分脚本,保证每个类别在三个集合里的比例一致。从那以后我所有分类项目都强制走分层划分,这已经是条件反射了。

坑二:训练到第 3 个 epoch 直接崩,报OSError: image file is truncated。现象是前两个 epoch 一切正常,第三个 epoch 某个 worker 进程突然崩溃,重启后崩在相同位置。原因是数据集中存在下载或传输过程中被截断的 JPG 文件,PIL 在单线程下只会警告,多进程 DataLoader 随机采样到这张图时异常被放大。解决是训练前跑一次第 3.1 节的校验脚本,把所有问题文件统一处理。处理方式我建议先转存成标准 RGB JPG 覆盖原文件,而不是直接删——删除会改变类别分布,对类别间样本量差异本来就存在的场景影响更大。

坑三:ImageFolder报类别数比预期多,或者class_to_idx顺序乱。现象是明明只有 10 个目录,打印train_ds.classes却有 11 个甚至 12 个。原因是目录里混入了隐藏文件目录(macOS 的.DS_Store或 Windows 的Thumbs.db)、中文名目录、以及带了空格的目录名,ImageFolder会把这些也当成类别。解决是训练前重命名所有目录为小写英文字母,清理隐藏文件。这个坑在新手拿网盘资源时出现频率极高,因为 macOS 打过的压缩包几乎都带.DS_Store。

5.2 训练阶段的翻车现场

坑四:训练损失正常下降,但验证准确率卡在 82% 上不去,瓶颈出现在 sheep 和 cow 这对。现象是分类报告的sheep这个类别的 recall 只有 60% 出头,大量 sheep 被预测成 cow。原因有两层:这对类别本身形态接近,且背景高度相似(都是草原牧场),模型学到的是背景纹理而不是动物特征。解决思路按顺序尝试:先看混淆矩阵确认混淆对,然后针对性增加类别权重,比如把sheep的损失权重从 1.0 提到 1.5;如果还不行,就检查训练集里是不是 sheep 的图片质量整体比 cow 差——这类数据集爬取来源不一,某些类别的图整体更模糊。权重调整要克制,别超过 2.0,否则模型会为补偿权重而过度拟合 sheep 的噪声。

坑五:验证集准确率 94%,模型部署到真实照片上只有 60%。现象是训练期间各项指标都很漂亮,一换环境就露馅。原因最可能是数据泄漏:同一个动物个体的多张连续帧被切分到了 train 和 val 两个集合,验证集里出现的"新猫"实际上和训练集里的是同一只猫,模型记住了猫的个体特征和拍摄场景,而不是学会"猫"这个类别概念。解决方法是切分前按文件名前缀分组,比如cat_12_01.jpg和cat_12_02.jpg同属个体cat_12,先按个体分组,再把整组放进同一个集合。这个坑在摄像头连续采集的数据里尤其普遍,手动爬取的数据也可能出现同源图片。

6. 阶段验证技巧:用混淆矩阵和错误样本判断模型是真学会了还是背下来了

训练结束只看一个总准确率,是分类项目最容易自欺欺人的地方。总准确率 92% 听起来不错,但如果失效都集中在 spider 和 butterfly 这两个类别上,问题的性质就完全不同了。我的习惯是每次训练完强制生成一张混淆矩阵和一份 top-5 错误样本清单:

# evaluate_report.py import torch from sklearn.metrics import confusion_matrix, classification_report import numpy as np # preds / labels 为完整验证集上收集的预测结果和真实标签 cm = confusion_matrix(labels, preds) names = val_ds.classes print(classification_report(labels, preds, target_names=names)) # 过滤对角线,找混淆最严重的类别对 off = cm - np.diag(np.diag(cm)) pairs = [] for i in range(len(names)): for j in range(len(names)): if i != j and off[i][j] > 0: pairs.append((names[i], names[j], int(off[i][j]))) pairs.sort(key=lambda x: -x[2]) for i, j, n in pairs[:5]: print(f"{i} 被错判为 {j}: {n} 张")

输出里classification_report的recall列尤其值得看:某个类别的 recall 偏低,说明模型对这个类别的特征表达不足;precison 偏低则说明它和其他类别容易互串。动物数据集里最常上排行榜的混淆对是 sheep↔cow 和 dog↔cat,前者是形态+背景双重相似,后者是毛色、姿态的多变性。如果 top-1 混淆对占错误总量的 60% 以上,加数据不如先做类别加权;如果错误分布很散,说明模型容量或训练时长不足,优先加深网络而不是继续堆数据。

再进一步的做法是导出错误样本图:把预测错的那几张图拼成一张网格图看一眼。90% 的情况下你会发现问题不在模型——有的图里蜘蛛只占画面角落的 0.5%,有的"蝴蝶"图拍的根本是标本,还有几张标签本身就是错的,这类资源是爬虫自动归档的,错标率按千分之几估算属于正常。发现标签错误直接修正或删除,别让模型去拟合错误标注。

在那之后我每次跑完分类实验,都会强制自己先走一遍混淆矩阵和错误样本检查,再决定要不要调结构——这个习惯帮我避免过至少三次在错误方向上的浪费调参。一个 28000 张的 JPG 数据集做到这份上,该榨的价值基本都榨出来了,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表