
简介面向图像分类与深度学习入门者的小型ImageNetTiny ImageNet数据集专为需要标准公开数据进行模型训练、算法验证的学生和研究者设计省去自行爬取、清洗与整理数据的繁琐过程。数据涵盖 altar、bell pepper、maypole 等200个类别每个类别包含500张图片训练集共8万张、测试集共2万张均已按类别分文件夹存放文件夹名即类别标签解压后无需额外处理即可直接用于PyTorch、TensorFlow等主流深度学习框架。资源包共2000个文件以jpeg图像为主体1998个另含1个json类别字典和1个python可视化脚本压缩包大小188.78MB。解压后包含data-train、data-test两个独立目录每个子目录以类别名命名检索与加载均很方便json字典提供WordNet类别编号与名称的映射可视化脚本可随机抽样展示各类别图像便于检查数据质量与模型输入效果。已有393人学习下载数据集规模适中既能覆盖较多语义类别又避免完整ImageNet的存储和算力负担适合图像分类入门、迁移学习实践、课程设计以及算法对比实验。1. 小型ImageNet 200分类图像分类数据集为什么它是练手的最佳尺寸做深度学习的图像分类最怕的不是算力不够而是数据集大到让你没机会踩坑。全量ImageNet-1K有上千类、上百万张图光下载、解压、做索引就可能耗掉一整晚CIFAR-10、CIFAR-100又太小每类样本几千张换个强模型上去很快过拟合验证不出真实的泛化能力。小型ImageNet图像分类数据集200分类正好卡在训练舒适区类别数够多每类样本只有几百张在单张消费级显卡上就能跑通数据整理、加载、训练和调参的完整闭环。适合想拿图像分类练手又不想一上来就背全量数据集的深度学习者、研究生和一线算法工程师。我照从业者常用的PyTorch路线把数据集结构、Loader细节、训练参数和常见翻车点一次说清新手能照抄熟手也能对着边界参数查漏。2. 200分类小型ImageNet是什么和CIFAR、全量ImageNet对比后才知道怎么选2.1 数据集生态里的中间站类别数、样本量和分辨率三个维度先说“小型ImageNet”这个叫法。业内常见的对应物是 Tiny ImageNet-200也有开发者以 ImageNet 子集形式发布 200 类版本。它跟 CIFAR 系列最大的差别是类别数。CIFAR-10 每类约 6000 张图分类边界相对清晰CIFAR-100 虽然也有 100 类但图像是 32x32很多类别在低分辨率下只能靠整体轮廓区分模型能力稍强就开始背训练集。ImageNet-1K 每类约 1300 张共 128 万张左右单卡训练一轮几十分钟起步调试成本很高。200 分类子集通常把单类样本压到几百张分辨率按版本不同常见是 64x64。数据集类别数单类训练样本量参考常用分辨率单卡训练一轮成本CIFAR-1010约6000张32x32分钟级CIFAR-100100约600张32x32分钟级200分类小型ImageNet200每类几百张量级常见64x64十几分钟到半小时ImageNet-1K1000约1300张224x224几十分钟以上这个表不追求精确基准只看量级。选型的核心逻辑是数据规模小到个人电脑能跑难度又够到“能暴露模型缺陷”。样本数量少也正是做数据增强、迁移学习、蒸馏这类技巧的最佳演练场。换句话说它不是 ImageNet 的“缩水版”而是刚好能装进你显存和耐心里的完整课题。2.2 目录结构与标签设计train/val/类别文件夹怎么组织拿到这类数据集后最常见的目录结构是这样的data/ ├── train/ │ ├── n01443537/ │ ├── n01629819/ │ ├── n01632877/ │ └── ...200个类别目录每个目录下一批图片 ├── val/ │ ├── n01443537/ │ ├── n01629819/ │ └── ...部分版本直接用图片文件 val_annotations.txt原因见第5章 ├── labels.txt # 每行一个类别ID按官方顺序排 └── words.txt # 类别ID到人类可读描述的映射如果 train 和 val 都是按类分目录那么用 PyTorch 的ImageFolder是最省事的选择。但重点在于“标签顺序”ImageFolder默认按文件夹名的字母序生成索引class_to_idx200 个目录排出来是字母序不是labels.txt里官方给的类别顺序。多数训练场景下这没关系因为训练不需要知道类别本身的语义但当你需要和官方评估脚本、每类精度做对照时就得自己维护映射。这类数据集经常配套labels.txt或classes.txt以及描述文本我按常见约定写一个通用脚本把“官方类别 ID”和“模型输出索引”对齐import torch from torchvision.datasets import ImageFolder root data/train ds ImageFolder(root) idx_to_class_id {v: k for k, v in ds.class_to_idx.items()} # 读取 labels.txt 里的官方类别顺序 with open(data/labels.txt) as f: class_id_list [line.strip() for line in f if line.strip()] # 类别ID - 官方索引 official {cid: i for i, cid in enumerate(class_id_list)} def to_official(y): class_id [idx_to_class_id[int(i)] for i in y] return torch.tensor([official[c] for c in class_id])这里的逻辑要拆开看ds.class_to_idx就是ImageFolder按字母序给出的字典value 是模型索引labels.txt里的顺序则是数据集“官方顺序”。很多评估脚本要求输出按官方顺序排列的 logits训练时不转没关系评估前必须转。labels.txt在有的版本里叫classes.txt有的放在 val 目录下读取前先看文件头几行。还要注意n01443537这种类别 ID 并不是从 0 开始的连续数字直接拿它当 logits 索引会错位一整个类。2.3 每类几百张意味着什么细粒度难度与显存预算200 分类数据有一个隐性优点当它是从全量 ImageNet 抽出来的子集时类别往往来自中粒度到细粒度的混合比如鸟、狗、蘑菇这些容易相互混淆的子类。模型要区分它们不能只看全局颜色还需要关注纹理、边缘和形状组合。这正是从 CIFAR 跨到真实分类任务的重要门槛。显存预算也要按分辨率盘算。常见版本的图是 64x64直接用原生大小喂给模型ResNet-18 在 batch size 128 时16G 显存完全能压住甚至 batch 256 也能跑。要是把图 resize 到 224 再上 ResNet-50显存需求会涨到接近全量 ImageNet 训练的八成那就失去小型数据集的意义了。我的默认推荐是先按原生分辨率训练确定有提升需求时再用随机裁剪加 resize 到 112而不是一上来就 224。分辨率上去单步时间变长调试迭代变慢对小数据集多调几轮数据策略的收益通常比拉高分辨率更明显。另一个关键点是预训练模型进场。使用 ImageNet 预训练权重做迁移学习是 200 分类数据的经典用法而“从零训练一个 ResNet-18”则适合研究模型本身的收敛特性。两种用法参数差异很大第 4 章会分别给参数和代码。3. 用PyTorch把200分类图像数据集接进训练管线ImageFolder和Loader的四个注意点3.1 最小读取代码ImageFolder 一行能省掉自己写 Dataset 的功夫先从最简单的开始用ImageFolder直接构造训练集from torchvision import datasets, transforms from torch.utils.data import DataLoader train_ds datasets.ImageFolder( rootdata/train, transformtransforms.Compose([ transforms.RandomResizedCrop(64, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) ) train_loader DataLoader( train_ds, batch_size128, shuffleTrue, num_workers4, pin_memoryTrue )逻辑说明ImageFolder会递归扫描 root 下所有子文件夹把图片解码后按父目录名打标签。默认类索引是字母序这点前面提过训练时可以不用管。RandomResizedCrop的 scale 设为 0.7-1.0适合 64 的小图如果你拿到的版本原图就是 64且想做从零训练我建议把这一步换成Resize(64)保留原始分布增强只留翻转和颜色扰动。mean 和 std 用 ImageNet 统计量是行业习惯因为这个数据集就是 ImageNet 子集颜色分布接近。这里的num_workers4本地够用具体看机器 CPU 核数和磁盘 IOpin_memoryTrue在 GPU 训练时能减少一次数据拷贝但不会额外提升多少吞吐——这是一个容易让人误会的参数它不是加速器只是减少阻塞。3.2 小分辨率下的数据增强该怎么设计小分辨率数据的增强分两派。一派是“原图 64增强只用水平翻转和轻度颜色抖动”最大限度保留原始信息另一派是“先 resize 到 112 或 128再上随机裁剪”让模型看局部放大的细节。如果目标是迁移学习选第二派如果是从零训练看算法差异选第一派。我个人的折中方案是train_transform transforms.Compose([ transforms.Resize(112), transforms.RandomResizedCrop(112, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])这里把 64 的原图先 resize 到 112再随机裁剪相当于让模型看到局部放大后的内容。scale 取 0.8-1.0不会裁到太小导致类内变化不足。ColorJitter的强度要收敛亮度、对比度、饱和度都压在 0.2 以内。200 类里相当一部分是自然图像颜色本身就是强判别特征增强太狠会把类别信息一起抹掉。每类样本本来就少也别急着上 CutOut 或 RandAugment先跑基础增强不够再叠加一次只加一个操作。3.3 训练前先跑一页检查脚本shape、标签和吞吐很多翻车发生在训练第一天图像通道不对、标签超过 200、DataLoader 卡死。我习惯先写一个几行的小脚本确认三件事图像 shape 和 dtype、每个 batch 的标签分布、每秒能喂多少 batch。import time, torch train_iter iter(train_loader) imgs, labels next(train_iter) print(imgs:, imgs.shape, imgs.dtype) print(labels unique:, labels.unique(), min/max:, labels.min().item(), labels.max().item()) start time.time() for i, (imgs, labels) in enumerate(train_loader): if i 20: break throughput 20 * imgs.size(0) / (time.time() - start) print(f20 batches in {time.time()-start:.2f}s, throughput {throughput:.1f} img/s)labels.unique()应当覆盖 0 到 199 的大多数类如果某个 batch 里类别特别集中说明 shuffle 设置或文件读取顺序有问题。min/max可以用断言强行规范assert labels.max() 200。dtype 应当是float32如果看到uint8一定是 transform 里漏了ToTensor()。吞吐数值不固定但拿到基线后每次改num_workers、改图像尺寸都能做对比num_workers开太大时吞吐反而下降属于正常现象不用过度优化。4. 训练一个200分类的ResNet-18从零到能出结果的参数与代码4.1 初始化选择裸网络还是ImageNet预训练200 类子集规模不算大但也不是“一看就能收敛”的数据量。从零训练时ResNet-18 在约 10 万张图上训练 60 到 120 个 epoch能达到尚可的准确率ResNet-50 从零则更依赖优化器设置动不动就发散。我的默认策略是先跑裸 ResNet-18 建立基线原因是查错快、显存低、方便反复调参基线确认后再换预训练权重走迁移。这两种模式参数差很多别混用。import torch.nn as nn from torchvision.models import resnet18 # 裸模型 model resnet18(num_classes200) nn.init.kaiming_normal_(model.fc.weight, modefan_out) nn.init.constant_(model.fc.bias, 0) # 预训练版本 from torchvision.models import ResNet18_Weights model resnet18(weightsResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, 200)从零训练的收敛慢但能直接观察模型在“小数据 小图”下的行为预训练迁移几乎一开始就能在验证集上看到几十个百分点但防过拟合的压力更大。分水岭在 epoch 数从零至少 60迁移 10 到 20 就够。使用预训练权重时输入尺寸要尽量靠近预训练任务这就是为什么第 3.2 节推荐 resize 到 112 而不是 64。4.2 训练循环AdamW warmup cosine AMP给一段可以照搬的简化训练循环省略 EarlyStopping 和 EMA那部分放到第 6 章讲import torch from torch.cuda.amp import autocast, GradScaler device cuda model.to(device) optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay5e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_maxepochs, eta_min1e-5 ) scaler GradScaler() for epoch in range(epochs): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad(set_to_noneTrue) with autocast(): logits model(imgs) loss torch.nn.functional.cross_entropy(logits, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() scheduler.step()这个循环里有三个容易踩的细节。一是 warmup代码里没体现但实践经验是 ResNet-18 在 batch 128 时lr 直接用 1e-3 起步前几步 loss 可能冲到 10 以上甚至 NaN。常见做法是头 3 到 5 个 epoch 做线性 warmup从 0 升到 1e-3不想写 warmup 就用 1e-4 起步但收敛会慢不少。二是CosineAnnealingLR的T_max直接填总 epoch 数eta_min1e-5是下限。三是autocast只包前向和 loss 计算不包 backwardscaler.step(optimizer)内部会处理梯度缩放GradScaler要跨 epoch 保留整个训练只创建一个。如果不想用混合精度直接loss.backward()和optimizer.step()即可其余不用动。4.3 关键参数表与第一次调整顺序下面这个表按常见消费级显卡和 64x64 输入写可以直接抄走参数裸 ResNet-18预训练 ResNet-18说明batch size128-25664-128预训练模型对单样本噪声更敏感batch 大反而容易靠“记忆”epochs80-12015-20预训练别太久10 个 epoch 后验证集经常过峰lrAdamW1e-33e-4预训练权重整体要用更小 lrweight decay5e-45e-4CNN 基本不动warmup epochs3-52预训练可以短一些数据增强建议裸网络用Resize(64)加轻增强预训练用Resize(112)加RandomResizedCrop。调参时每次只动一个变量先调 lr再看增强最后加深网络。一步改两个参数模型掉点你都不知道是哪一步的锅。如果换到 Vision Transformer 一类模型参数要整体换一套weight decay 推到 0.05lr 降到 5e-4epoch 至少 120小数据集上 Transformer 收敛本来就慢。第一次训练跑到验证集准确率进入平台期再去延 epoch 数因为小数据训练往往越到后面越吃提升提前早停容易连后悔药都没得吃。5. 训练200分类数据的常见坑与排查目录、分辨率、保存和混合精度的血泪经验5.1 验证集没有按类分文件夹acc卡在1/200现象训练集用ImageFolder一切正常验证集换上去之后准确率要么 0.5%要么不断震荡看起来像完全随机。原因很多 Tiny ImageNet 镜像的 val 目录不包含 200 个子目录而是直接放了一万张图片加一个val_annotations.txt里面每行是“文件名 类别ID 其他字段”。直接用ImageFolder读取会把这 1 万张图全部当成一个类标签全部变成 0准确率自然卡在 1/200 附近。解决要么按val_annotations.txt把文件挪进 200 个子目录要么写一个自定义 Dataset用标注文件当标签源。挪文件是一次性工作但会改动原始数据之后换机器还得再挪我推荐写自定义 Dataset读取标注文件构造(img_path, label)列表再交给 DataLoader。这样不管目录长什么样代码都不受影响。5.2 盲目resize到224模型反而学得更差现象把 64 的图用双线性插值放大到 224显存涨到原方案的近十倍训练速度变慢验证精度却比原生 64 还低三四个点。原因上采样只增加了像素没有增加信息。对小分辨率自然图像224 输入让卷积感受野变大局部纹理被插值抹糊网络本身也更重数据和参数规模不匹配。很多人以为“ImageNet 预训练模型都是 224 输入所以我的图也得是 224”但忘了全量 ImageNet 的原图是几百像素起步只有 64 像素的图放大到 224等于让模型看一张模糊海报。解决先确认数据集原生分辨率按原生尺寸设计输入。用 torchvision 的 ResNet-18conv1 是 7x7 步长 2吃 64 输入完全没问题不用改网络。确实想放大选 112 或 128同时用RandomResizedCrop制造局部细节这比单纯拉大分辨率真实得多。5.3 最佳模型没保存最好的epoch再也找不回来现象每轮结束都保存last.pth训练完用验证集最高的那一轮提交发现效果反而变差或者训练中途中断全部进度丢掉。原因训练曲线是有波动的最后几个 epoch 在验证集上的表现不一定最好随机种子、数据加载顺序都会让验证指标出现一个尖峰。只有把历史最好权重存下来才能保证提交时用的是最优那轮。解决每次验证后更新best_acc同时保存模型、优化器和调度器的state_dict。保存格式用state_dict比整个模型更抗 torchvision 版本差异torch.save({ epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), scheduler_state_dict: scheduler.state_dict(), best_acc: best_acc, }, best.pth)额外提醒一句直接覆盖best.pth有个隐忧第 30 轮的模型比第 40 轮好但你不知道两者在哪些类别上强。我一般把 top3 轮次的权重都存下来按best_30.pth、best_40.pth这样命名避免手滑把唯一一枚后悔药覆盖掉。5.4 AMP下loss波动或在固定轮次出现NaN现象开了autocast和GradScaler前几轮正常到某个 epoch loss 突然变成 NaN 或掉到极小值关闭混合精度后一切正常。原因GradScaler的 scale 因子在梯度值整体偏小时会持续下降一旦遇到损坏样本比如解码后全黑的图梯度被截断scale 没过门槛就被误放行导致溢出。另外当 loss 本身小到 1e-5 量级时float16 的精度会被淹没梯度更新方向直接失真。解决先关 AMP 复现一遍确认“关了就好”再逐个排查数据里有没有损坏图片。训练中每个 epoch 末尾打印一次scaler.get_scale()如果 scale 降到很低还出现 NaN就把 batch size 调大一档或者干脆用 float32 训练。混合精度在小数据集上的收益不如大数据集明显为这点收益折腾一周不值得。6. 让200分类数据再用两次迁移学习、蒸馏与每类看坏例的习惯6.1 先迁移到自己的数据是性价比最高的一步预训练模型在 200 分类上微调比从零训练省掉约八成时间。常见做法是在 ImageNet 预训练权重基础上先冻结前几层比如 conv1 和 layer1只更新后面部分和新的分类头跑 10 个 epoch 后放开全部层再用 3e-4 往下走。放开时机看验证集如果某两个 epoch 之间准确率几乎不动就解冻下一组层。这是防止过拟合最稳妥的安全做法。6.2 蒸馏把大模型知识倒给小模型200 分类数据量小训练一个 ResNet-50 当 teacher再用 KL 散度把 logits 蒸馏给 MobileNet 或 ResNet-18通常比直接训小模型多拿几个点。温度 T3 起步loss 用 0.7 倍的 KL 加 0.3 倍的交叉熵。小模型离线推理更快边缘部署经常用这套。这算把一个数据集用两次的最佳示范大模型负责学知识小模型负责跑起来。6.3 验证习惯看单类准确率和易错对训练完不看总准确率我会把 200 类各自的准确率拉出来排序把最低的 10 类打印成表格再画一张混淆矩阵。很多时候模型搞错的不是图片本身而是某个上下文场景——比如鸟类数据里姿态差异大的样本、背景遮挡严重的样本。最容易丢分的类往往就是样本量少且外观近似的类这一点和 CUB-200-2011 这类纯细粒度数据集很接近看一眼错例就知道下一步是加数据还是调损失。我刚开始做 200 分类实验时总以为把验证集准确率刷上去就完事。后来换了评估脚本才发现自己在某个细分类上只有 30% 的准确率只是其他类太简单把平均值拉回来了。那次之后我每次都先把最低的 10 类跑出来看宁可训练少跑几个 epoch也要把模型输在哪看明白。希望帮到你——数据集不大但值得较真。本文还有配套的精品资源点击获取