ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

24种商品图像数据集:快速跑通图片分类Baseline的实战指南

2026/10/6 2:55:28 拓冰建站 浏览量
24种商品图像数据集:快速跑通图片分类Baseline的实战指南 简介面向图像分类任务的24种商品图像数据集已经按训练集与测试集完成划分适合图像分类入门、商品识别、科研实验或毕业设计等场景。压缩包共2000个文件包含1998张JPG图片、1个Python可视化脚本和1个JSON配置文件压缩包大小约623.76MB其中JPG为标注图像数据py脚本用于随机展示单张图片json文件可用于类别映射整体结构清晰便于快速套用。data目录下分为train与test两个子目录训练集共27,566张图片、测试集共6,881张图片覆盖手机、化妆品、酒等共24个类别所有图片均按类别存放经测试可直接使用ImageFolder打开无需额外预处理。目前已有237人学习/下载可视化脚本无需修改即可运行随机传入一张图片即可展示并保存在当前目录能帮助核对图像质量、了解类别构成从而加快模型训练前的数据准备流程。1. 24种商品图像数据集图片分类最缺的“已做数据集划分”为什么值得直接用打开任何一个图片分类项目最耗时的不一定是网络结构而是数据准备。自己凑 24 种商品图像你可能要先跑一遍超市或者处理光照、包装反光、背景杂乱最后还得手动做数据集划分按类别分层抽 train/val/test、查重复、看每个类够不够数。这一套下来大半个工作日就没了。而这个“24种商品图像数据集”把最脏的第一步做完了24 类商品、图片分类可直接用的目录结构且关键点是“已做数据集划分”。它解决的不是“有没有数据”的问题而是“拿到就能跑基线”的问题。适合三类人做课程设计的学生、想快速验证 Backbone 换 ImageNet 预训练收益的工程师、需要商品识别 demo 的算法实习生。下面按“结构→训练→调参→踩坑→部署”的顺序讲清楚在这个数据集上一天内做完 baseline 的完整路径。2. 商品图像数据集的结构与划分逻辑先搞清楚你拿到的 24 类是什么2.1 这份数据集解决的是哪个商品图像场景商品图像数据集的常见出处是零售货架识别、无人结算台和电商详情页分类。24 类通常是一些货架高频 SKU 的大类集合比如洗发水、沐浴露、牙膏、洗衣液、可乐、酸奶、饼干、薯片、纸巾、速溶咖啡这类。因为同一类商品在外观上既有共性又有差异不同品牌、不同口味、不同包装批次它天然适合做图片分类的训练集。为什么是 24 类而不是几百类对算法工程师来说类别少意味着可以先不纠结数据 pipeline而把时间花在训练参数和验证方法上。对课程设计来说24 类足够撑起一个像样的分类演示。这个规模是“能跑通”和“有真实感”之间的平衡点。2.2 目录布局与 ImageFolder 的标签映射这种数据集的通行组织方式是train、val、test 三个大目录每个大目录下按类建文件夹文件夹名就是类别名。解压后大致是这样commodity24/ ├── train/ │ ├── 001_shampoo/ │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ │ └── ... │ └── 002_toothpaste/ │ └── ... ├── val/ │ └── 001_shampoo/ └── test/ └── 001_shampoo/逻辑说明文件夹名本身就是标签所以不需要额外解析 CSV 或 JSON。PyTorch 的ImageFolder会读取目录名按字母或数字排序后生成整数标签。这段结构的意义是你也可以在 TensorFlow 里用image_dataset_from_directory直接消费两个框架都不用写自定义 Dataset。参数说明这里的001_shampoo只是示例命名。如果真实数据集里的文件夹带数字前缀要留意排序规则001、002、010这种数字前缀会按字典序排而不是按数值排。我的习惯是训练时不依赖数字前缀只看class_to_idx这个映射的实际结果。2.3 拿到数据集先做三个检查“已做数据集划分”不等于划分得合理。收到压缩包后我一般先跑一个 shell 统计确认每个划分下每类各有多少张图for split in train val test; do echo $split for d in commodity24/$split/*/; do echo $(basename $d): $(ls $d | wc -l) done done逻辑说明这个命令嵌套两层循环外层遍历 train/val/test内层遍历当前划分下的每一个类别目录输出类别名和图片数量。它能让你在 1 分钟内看出三件事train/val/test 总体比例是否合理、是否有某个类在 test 里只出现一两个样本、是否某类整体样本太少。检查之后再看类别分布用 Python 直接读训练集的标签直方图from torchvision.datasets import ImageFolder from collections import Counter train_ds ImageFolder(commodity24/train) counts Counter(train_ds.targets) for cls_idx, cnt in sorted(counts.items(), keylambda x: x[1]): print(f标签索引 {cls_idx}{train_ds.classes[cls_idx]}: {cnt} 张)逻辑说明targets是每个样本对应的标签索引class_to_idx保存了文件夹名到数字的映射。逐类打印样本数是判断是否要接加权采样或清洗欠拟合类的最快方式。对图片分类任务如果某个类只有 5 张训练图普通 CrossEntropy 在这类上几乎学不出稳定特征后面验证时它的 recall 会很低且抖动大。这里有个经验值test/val 每类少于 20 张时准确率单点指标的置信度很差还是要打印 per-class 召回。注意如果统计后发现某个类别样本数相差 10 倍以上不要先急着删图。先看这类是不是真实世界本身就稀疏的类别如果是数据增强上向它倾斜比强行删别的类更合理。2.4 标签体系与中文路径问题商品目录通常有两套标签系统分类号名称比如001_shampoo。训练时我倾向于用完整类名做语义输出。而 Windows 用户容易踩中文路径坑——商品类目命名如果有中文在外面解压到桌面也就算了一旦本地路径里出现C:\Users\张三这种中文用户名部分库会解码失败。解决方式是把整个数据集解压到纯英文路径比如D:\datasets\commodity24数据集内目录名是否中文不影响只要总路径是全英文。也要确认压缩包是否有隐藏的.DS_Store或Thumbs.db。如果不清理ImageFolder会把这个文件当作一个类。用find commodity24 -name .* -delete清掉再加载。3. 在本地跑通 24 类商品图分类PyTorch 从 0 到 1 的脚本3.1 数据加载与 transform 组合这个数据集内部文件夹结构齐全因此请用优化最小实现。从 PyTorch 和 torchvision 启动import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(commodity24/train, transformtrain_tf) val_ds datasets.ImageFolder(commodity24/val, transformval_tf) test_ds datasets.ImageFolder(commodity24/test, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) test_loader DataLoader(test_ds, batch_size32, shuffleFalse, num_workers4) print(len(train_ds), len(val_ds), len(test_ds))逻辑说明ImageFolder自动从目录名生成标签这里三个数据集分别加载 train/val/test各自带 transform。Resize((224,224))在最前面把商品图统一成卷积网络需要的尺寸。Normalize用 ImageNet 统计量是为了匹配预训练权重的输入分布。验证和测试都不做随机增强保证同一个样本每次评估结果一致。参数说明batch_size32是最常见起步值8GB 显存能跑显存不够就降到 16并同步把学习率从 3e-4 降到 1.5e-4。num_workers4在 SSD 上够用机械硬盘或 Windows 上建议改成 0否则数据加载会成为瓶颈。pin_memoryTrue只在用 GPU 训练时开CPU 训练反而拖慢。ColorJitter 这一项不是必须的商品包装对颜色敏感如果接下来要做颜色识别饱和度抖动需要控制幅度不要盲目加强。3.2 为什么用 ResNet18 做迁移学习对这个 24 类任务的起步模型我一般选ResNet18而不是更大网络。原因有三一是商品图像的不同类外观差异足够大ResNet18 的容量足够二是小训练集上大网络很容易过拟合收敛效果反而差三是后续做 CPU 推理也保留速度。初始化如下import torch.nn as nn from torchvision import models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(512, 24) model model.to(cuda)逻辑说明weights...IMAGENET1K会加载 ImageNet 预训练权重到本地它学到的边缘、纹理、形状特征对商品外观仍然有效因此能极大加速收敛。把最后一层fc的输出改成 24便接上我们的标签数。参数说明若磁盘空间或网络状况不适合先下载预训练权重可以用weightsNone随机初始化但从头训练需要把 epoch 增加约一倍准确率通常也不如预训练版本。图片分类小数据集优先迁移学习。3.3 训练循环最少需要的五个步骤导入优化器和损失函数并写一个带验证循环的训练过程。参考实现import torch.optim as optim from tqdm import tqdm opt optim.AdamW(model.parameters(), lr3e-4, weight_decay1e-4) ce nn.CrossEntropyLoss() best_acc 0.0 for epoch in range(20): model.train() run_loss, run_correct, run_total 0.0, 0, 0 for images, labels in tqdm(train_loader): images, labels images.cuda(), labels.cuda() opt.zero_grad() out model(images) loss ce(out, labels) loss.backward() opt.step() run_loss loss.item() * images.size(0) run_correct (out.argmax(1) labels).sum().item() run_total images.size(0) model.eval() v_correct, v_total 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.cuda(), labels.cuda() v_correct (model(images).argmax(1) labels).sum().item() v_total images.size(0) val_acc v_correct / v_total print(fepoch {epoch1}: loss{run_loss/run_total:.4f}, facc{run_correct/run_total:.4f}, val_acc{val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), commodity24_resnet18_best.pt)逻辑说明训练循环的固定套路——zero_grad清梯度、前向计算、反向传播、更新权重验证循环用torch.no_grad()包裹只计算指标不更新权重。out.argmax(1)用来比较预测的类别索引和标签索引。每轮结束后保存验证集表现最好的权重避免最后几轮过拟合后把模型覆盖掉。参数说明AdamW比Adam的 weight decay 行为更规范适合训练小模型。weight_decay1e-4是图像分类常用默认。20 轮在每类几十张样本下通常足够如果 val_acc 仍在上升增加 10 轮如果连续 5 轮不升提前停止不要硬跑。提示训练结束后不要只用最后一个 epoch 的模型。用best_acc对应的权重去评估 test。否则会因为过拟合或学习率末期的参数漂移丢掉最后几个点的准确率。3.4 加载 checkpoint 做单张图推理训练完成后再拿到 demo 或部署用load_state_dict加载保存的权重import torch from PIL import Image from torchvision import models, transforms model models.resnet18(weightsNone) model.fc torch.nn.Linear(512, 24) model.load_state_dict(torch.load(commodity24_resnet18_best.pt, map_locationcpu)) model.eval() tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) img tf(Image.open(val/002_toothpaste/0010.jpg).convert(RGB)).unsqueeze(0) print(model(img).argmax(1).item())逻辑说明加载权重时要注意先重新定义 model 结构再加载 state_dict否则严格层级匹配会报错。model.eval()必须调用否则 BN 层行为和 BatchNorm 统计不一致。这组代码只输出类索引若要输出中文类别名用之前保存的train_ds.classes反向映射classes train_ds.classes print(classes[model(img).argmax(1).item()])参数说明map_locationcpu决定加载到 CPU。如果模型是 GPU 上保存的这里指定 CPU 就不用担心 CUDA 环境。这组推理代码可原样接入 Flask/FastAPI 的简单接口。4. 参数怎么调学习率、输入尺寸与增强的边界值4.1 学习率与 batch_size 的缩放起点默认 batch 32 时学习率 3e-4 对 ResNet18 很稳。如果显存不够把 batch 降到了 16学习率也应减半。线性缩放是最简单也最有效的方式新学习率 旧学习率 × 新 batch / 旧 batch。不要只降 batch 不改 lr否则收敛变慢、后期还会震荡。如果显存充足把 batch 升到 64可以从 3e-4 提高到 6e-4但要注意商品图像样本量不大过大的学习率会在前几个 epoch 直接把 ImageNet 预训练特征打乱。我的经验是 1e-3 以上就必须观察 val_acc 是否出现断崖式下降。可以加余弦退火from torch.optim.lr_scheduler import CosineAnnealingLR sched CosineAnnealingLR(opt, T_max20) ... loss.backward() opt.step() sched.step()逻辑说明CosineAnnealingLR会在 20 轮把学习率从初值平滑降到接近 0适合训练后期在小范围波动。它对模型最终精度的提升比固定学习率更明显。参数说明T_max要和训练总轮数一致。如果实际使用早停轮数 20设置 T_max20 比较合适更换总轮数要同步更换 T_max。另一种常见做法是 SGD 加冲量SGD(model.parameters(), lr1e-2, momentum0.9)配聚类但小数据集上直接 SGD 起步容易震荡。我会先用 AdamW 3e-4 出一版基线再用 SGD 在它基础上微调这样更稳。4.2 输入尺寸和长宽比Resize((224,224))最简单但强迫所有图像等比缩放对细长瓶类商品会导致横向拉伸变形。先看数据统计若货架商品大多数是横竖比例接近 1:1正方形缩放问题不大若有大量洗发水瓶这种长宽比接近 1:2 的物体我会改成train_tf transforms.Compose([ transforms.Resize((256, 224)), transforms.CenterCrop((224, 224)), transforms.ToTensor(), ])逻辑说明Resize((256, 224))先按目标宽高比缩CenterCrop再裁掉边缘背景减少整体图的形变。比较通用的替代是Resize((256, 256)) CenterCrop(224)把多余边裁掉对不同长宽比更公平。参数说明输入尺寸直接决定显存占用和计算量。320×320 在 ResNet18 上会大幅增加训练时间但商品细节如小字商标识别确实需要高分辨率。建议先用 224 做出基线再用 320 重训只看是否显著提升 val_acc提升不超过 1 个百分点就不值得上高分辨率。下表是我会参考的输入策略商品形态推荐预处理原因洗发水/沐浴露这类高瘦瓶Resize(256,224)CenterCrop(224,224)减少纵向拉伸失真包装盒、零食袋Resize((224,224))长宽比接近方形直接缩放心智负担最小透明瓶、玻璃罐ColorJitter 亮度对比增强透明材质依赖背景对比需要更亮变量4.3 数据增强别一把梭图片分类最常翻车的就是增强过度。对只有几十到几百张每类的数据集RandomRotation 给瓶装商品加 15° 以上会带来大量边界空白网络有时候反而学会辨认“这张图是不是被旋转的”而不是商品类别。优化建议是“轻量增强”而不是“全自动增强”示例配置transforms.RandomHorizontalFlip(p0.5) transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2) transforms.RandomAffine(degrees5, translate(0.05, 0.05))逻辑说明RandomAffine 只允许 5° 内旋转和 5% 平移模拟现实中轻微歪斜的货架拍摄。这样既保留了空间结构又能增加样本多样性。ColorJitter 模拟不同门店灯光色温。参数说明这些增强里不要放RandomResizedCrop(scale(0.08,1.0))。RandomResizedCrop 是 ImageNet 分类里常用正则化手段但它会把商品局部截掉破坏商品细节导致任务更难。同样RandomErasing在商品包装细节识别上慎用会抹掉品牌名。离线做增强还要注意一个原则只对 train 做不碰 val/test。要在 val 里也做增强说明你根本不信任划分的结果数据泄漏和指标失真就都来了。5. 图片分类常见问题划分、失衡与解码的 5 次翻车记录5.1 现象某类只在训练集出现val 里少了一类遇到过一个自称为已划分的数据集但 val 的 24 类里少了一类。原因是它按文件级全局随机划分类别少的一类几乎全跑了 train。原因全局随机划分时少数类样本被分配到 traintest 里数量不足或直接为零。解决初始化时先跑第 2.3 节的统计如果发现类缺失将缺失部分的 val 图像重新读入或对缺失类重新做分层划分。可以做快速 Python 验证from torchvision.datasets import ImageFolder tr_labels set(ImageFolder(commodity24/train).targets) te_labels set(ImageFolder(commodity24/test).targets) print(tr_labels - te_labels, te_labels - tr_labels)逻辑说明打印结果能立刻发现两个划分的标签集合是否有差异。如果差异非空说明这个数据集划分不满足“同一个标签在两个划分中都有”这在分类评估中必须修复。经验不是所有“已做数据集划分”的包都做了分层。凡是压缩包只用 random.shuffle 生成 train/val/test 的一定要检查集合差。5.2 现象train_acc 和 val_acc 都有 99%test 却只有 50%先排除加载错误比如 val 用了 train transform再怀疑跨划分重复样本。商品数据集最常见的泄漏是同一个商品的不同拍摄角度同时进了 train 和 test模型记住的其实是背景或拍摄台。原因数据采集时多个角度拍同一个商品划分时按文件名随机没有按商品实例唯一 ID 去重。解决先做文件级 md5 查完全重复再做感知哈希查近似重复。md5 部分find commodity24/train commodity24/test -type f -name *.jpg -exec md5sum {} \; \ | sort | awk last$1{print $2} {last$1} | head逻辑说明先计算所有图片的 md5按哈希排序再输出与上一行哈希相同的文件路径即重复文件。它的局限性是无法识别缩放、裁剪后的近似重复。近似重复常见做法是用imagededup库的感知哈希。如果发现跨划分重复宁可把重复图从 test 移除也不要从 train 移除因为 test 应该是陌生样本的测试。5.3 现象loss 下降正常val_acc 卡在 60% 附近不涨当统计第 2 章发现类别样本差 10 倍时普通 CrossEntropy 会把头部类学得很好长尾类完全学不动。原因类别不平衡分布下等权重交叉熵对大类的 loss 贡献大少数类梯度被淹没。解决用 WeightedRandomSampler 每次按比例多抽少数类from torch.utils.data.sampler import WeightedRandomSampler targets train_ds.targets counts torch.bincount(torch.tensor(targets)) weights 1.0 / counts.float()[targets] sampler WeightedRandomSampler(weights, num_sampleslen(train_ds), replacementTrue) train_loader DataLoader(train_ds, batch_size32, samplersampler)逻辑说明weights 对所有样本按标签出现次数倒数赋值少数类样本被抽取概率更高。replacementTrue允许同一张图在一个 epoch 被抽多次在样本匮乏时这是有必要的。参数说明WeightedRandomSampler 会比较慢因为每个 step 都用索引操作取图。如果大部分类别均衡只有两三个少数类也可以只在 loss 上按类加权重即CrossEntropyLoss(weightclass_weight)对显存和 IO 压力更小。5.4 现象跑数据时 torchvision 报错或 batch 拼不起来用户反馈一个商品图的 logo 区域是透明 PNG直接加载 raw 的时候是 RGBA 四通道。然后 batch 拼接时报通道数不一致。原因数据集里有 PNG 透明通道或某些 JPEG 损坏。解决统一用 PIL 转 RGB。自定义一个 loader 并传入ImageFolderfrom PIL import Image def pil_loader(path: str): with open(path, rb) as f: img Image.open(f).convert(RGB) return img逻辑说明.convert(RGB)会把 RGBA、灰度图、P 模式统一转成三通道 RGB。使用方式是在构造ImageFolder时传入loaderpil_loader参数。经验图片分类数据集里出现 EXIF 旋转标记一般没大问题torchvision 能处理。真正容易翻车的是损坏文件和 4 通道 PNG。5.5 现象加载一切顺利但 predict 时 label 和实际商品对不上几十张图视觉看起来明明是“沐浴露”模型输出索引却对应别的类。常见原因是001、002、0010这种带数字前缀的文件夹名按字符串字典序排序后与直觉顺序不同。原因ImageFolder 按目录名字典序标号带数字前缀的类名排序和自然语言排序不一致。解决训练前固定打印映射表把class_to_idx存到文件with open(class_to_idx.txt, w) as f: for cls, idx in train_ds.class_to_idx.items(): f.write(f{cls}\t{idx}\n)逻辑说明生成映射文件用于把模型输出转化为中文类别名。预测时不要把数字索引当作可信标签以映射表转换结果为准。参数说明如果发现映射顺序和预期不一致可以把一张确定类别的测试图用加载后的模型输出它的 argmax人工比对classes[arg]这是一种闭环验证办法。6. 把 24 类模型推下去之前test 混淆矩阵与 ONNX 部署6.1 用 test 做最终验证并打印分类报告训练过程中的 val_acc 受调参影响真正最后要交付的是 test 上的分类报告。from sklearn.metrics import classification_report y_true, y_pred [], [] model.eval() with torch.no_grad(): for imgs, labels in test_loader: imgs imgs.cuda() y_pred model(imgs).argmax(1).cpu().tolist() y_true labels.tolist() print(classification_report(y_true, y_pred, target_namestest_ds.classes))逻辑说明classification_report 会按类打印 precision、recall、f1这对小数据集尤其重要。例如某类只有 10 张测试图准确率涨一个点就是统计噪声。当你看到包装相似类互相混淆说明分辨率不够可以考虑用 320 输入或细粒度模型而不是盲目加数据增强。我习惯把这份报告存成文件和权重一起作为交付物。6.2 导出 ONNX 用于 CPU 和边缘部署ResNet18 转 ONNX 很简单适合落地到 CPU 或边缘盒子dummy torch.randn(1, 3, 224, 224).cuda() torch.onnx.export(model, dummy, commodity24_resnet18.onnx, input_names[input], output_names[prob], opset_version13)逻辑说明ONNX 导出后模型结构、权重和预处理约定会封在一个文件里用 ONNX Runtime 加载即可不需要 PyTorch 环境。后续如果要换 YOLOv8 做更细粒度的商品检测这个 24 类分类器可以保留作为检测结果的二次分类。导出时确认model.eval()已调用BN 层才会被固化。参数说明opset_version13兼容性较好边缘设备的 NPU 平台对 opset 版本有要求时一般要在 11 到 13 之间选。输入、输出的名字可以后续在运行时按需绑定。落地部署还有个常用安全检查用一张 val 图推理一次记录 softmax 输出。当遇到置信度低于 0.5 或最高分和次高分接近的类别时上报为“不确定”不要直接给用户展示一个可能错误的商品名。这也是商品识别 demo 被别人觉得“像样”和“胡说”的分水岭。我一般不会把 val_acc 当交付数据只把 test 分类报告和 ONNX 放在一起作为可复盘依据。毕竟一个“已做划分”的数据集最终极的意义不是让你复现一个数字而是让你把时间花到正确的环节上。希望帮到你。本文还有配套的精品资源点击获取