首页 / 资讯中心 / 文章详情

水果分类数据集实战:从解压到迁移学习的图像分类全流程

水果分类数据集实战:从解压到迁移学习的图像分类全流程 ★ FEATURED ARTICLE
简介一份面向机器学习与计算机视觉入门者的水果图像分类数据集涵盖苹果、香蕉、葡萄、橙子、梨五类常见水果图片及对应标签可支撑图像分类、特征提取与模型评估等典型任务。压缩包内共1310个文件其中1306张JPG图片构成主要训练素材辅以2个类表清单、1个JSON配置和1个Python脚本方便按目录读取图片、解析标签并快速生成训练集整体仅14.07MB轻量易用。目前已有3625人学习下载适合课程实验、算法对比或个人项目起步。借助自带的脚本与标签结构使用者可直接开展数据预处理与训练、验证、测试集划分也可对照脚本理解数据加载流程从而复现经典CNN图像分类模型积累从数据准备到模型评估的完整实战经验。所有图片已按类别存放目录结构清晰便于快速开展实验。1. 水果分类数据集解压之后才是真正的开始拿到「水果分类数据集 fruits分类数据集.rar」这个文件的人往往不是缺素材而是卡在素材之后的每一步解压、清洗、组织目录、挑预处理参数再到写第一版训练脚本。水果分类是图像分类里最常被拿来练手的对象因为类别直观、图片可解释但恰恰因为它看起来简单很多人跳过了数据体检直接训练最后在调试上耗掉的时间比训练还多。这份 rar 能解决的是“没有现成图片”的问题而一篇能照着走的笔记解决的是“图片怎么变成可训练数据集”的问题。适合刚接触图像分类的开发者、准备课程设计或入门竞赛的学生。这里先说结论数据集好不好用不看图片总数看三类东西——类别目录结构、文件名规范、类别样本是否均衡它们决定后面八成调试成本。2. 解压与目录组织把 rar 变成 PyTorch 认得的训练集2.1 先看压缩包结构两种常见布局决定你要不要写转换脚本解压前先做一件事把压缩包当成黑匣子先列目录再动手。这样做不是谨慎过度而是 rar 包怎么组织直接决定你接下来写不写转换脚本。不同来源的水果分类数据集差别很大有的按 train/val 分好里面是 apple、banana 这类英文子目录有的则是几百张图平铺在一起类别靠文件名前缀区分还有少数带着标注 csv。最常见的做法是先 rar l 或 bsdtar -tf 列前几十行看到路径是否带类别目录。判断标准就一条从数据根目录往下数第一级目录是 train 或 val第二级目录是类别名第三级才是图片这种结构torchvision.datasets.ImageFolder可以直接吃几乎零转换成本。凡是第一级目录直接是图片、类别写在文件名里的都得先做一次目录重建。# 只列目录不急着解压先看清内部结构 unrar l 水果分类数据集 fruits分类数据集.rar # 某些 Linux 发行版没装 unrar 时用 bsdtar 列目录 bsdtar -tf 水果分类数据集 fruits分类数据集.rar | head -60head -60只取前 60 行避免文件多时刷屏。unrar 和 bsdtar 二选一unrar 在多数系统源里要单独装bsdtar 常随 libarchive 自带。两者都能列目录但解压时对中文文件名的编码处理不一样后面避坑章会细说。如果你看到MACOSX这样的目录名说明这个包在 macOS 上打过包隐蔽的系统文件通常也混在里面解压后必须过滤。提示列目录时中文文件名如果显示成乱码先别慌很多是终端显示编码问题不代表文件真坏了。等解压出来再用脚本确认。2.2 用脚本重建类别目录并完成划分结构确认完毕就开始解压、重建。我的习惯是先解压到fruits_raw再转换成fruits_split的 train/val 结构而不是边解压边改。原因是保留原始包作为后悔药万一转换脚本写错重新解压一次就行不用对着半成品目录脑补。# 解压到原始目录禁止直接解压到训练目录 mkdir -p fruits_raw bsdtar -xf 水果分类数据集 fruits分类数据集.rar -C fruits_raw # 解压完成后先做一次文件计数确认没有遗漏 find fruits_raw -type f | wc -l-C fruits_raw指定解压目标目录避免文件散落在当前目录里。wc -l只统计文件个数数字跟你在压缩包里看到的条目数对得上说明解压完整。接下来写转换脚本把原始结构统一成train/类别名/图片和val/类别名/图片import os import shutil import random from pathlib import Path random.seed(20240601) # 固定种子保证划分结果可复现 src Path(fruits_raw) # 解压后的原始目录 dst Path(fruits_split) # 输出目录train/val val_ratio 0.2 # 验证集比例 image_exts {.jpg, .jpeg, .png, .bmp, .webp} for sub in src.iterdir(): # 遍历每一层子目录 if not sub.is_dir(): continue # 过滤隐藏目录和 macOS 系统目录 if sub.name.startswith(.) or sub.name MACOSX: continue for tag in (train, val): (dst / tag / sub.name).mkdir(parentsTrue, exist_okTrue) images [p for p in sub.iterdir() if p.suffix.lower() in image_exts] random.shuffle(images) val_n int(len(images) * val_ratio) for img in images[:val_n]: shutil.copy2(img, dst / val / sub.name / img.name) for img in images[val_n:]: shutil.copy2(img, dst / train / sub.name / img.name)这个脚本处理两类常见状态原始目录是“类别名/图片”时直接遍历如果原始结构是“某子目录/类别名/图片”这种套了一层目录的情况把循环改一下就行本质逻辑不变。val_ratio取 0.2 是中小数据集的经验值样本总量大可以降到 0.1每类只有几十张时建议提到 0.3。我特意用copy2而不是move因为训练过程中你可能要反复调整划分比例原始文件留着不亏。random.seed放在脚本最顶部保证你换了机器也能分出一模一样的两个集合这是实验可复现的基本功。2.3 训练前的数据体检类别数量、损坏图片与隐藏文件目录建好不代表数据能用接下来做体检。这一步很多人嫌麻烦跳过其实它是整个流程里性价比最高的一步十几秒钟能排查掉训练时可能卡你一小时的问题。体检看三样东西每类图片数量、损坏文件、隐藏文件。from PIL import Image from pathlib import Path base Path(fruits_split/train) for cls_dir in sorted(base.iterdir()): if not cls_dir.is_dir() or cls_dir.name.startswith(.): continue ok, broken 0, 0 for p in cls_dir.iterdir(): if p.suffix.lower() not in {.jpg, .jpeg, .png, .bmp, .webp}: continue try: with Image.open(p) as im: im.load() ok 1 except Exception: broken 1 print(f损坏图片: {p}) print(f{cls_dir.name}: 正常 {ok}, 异常 {broken})PIL 的load()才会真正解码图片数据只open()不会触发读取所以必须两者合用。suffix.lower()统一小写后比对扩展名避免.JPG和.jpg被判成两类。目录名以.开头的一律跳过.DS_Store、Thumbs.db这类系统文件就是这样混进数据集的。跑完打印每类数量你就能立刻看出类别是否均衡。常见的水果分类数据集里苹果、香蕉这类常见水果可能有大几百张某些小众水果只有几十张这个比例差在后续训练里非常致命。体检完的目录结构应该是这样路径含义训练脚本里的行为fruits_split/train/apple/苹果类训练样本ImageFolder 自动标成某个 classfruits_split/train/banana/香蕉类训练样本另一个 classfruits_split/val/apple/苹果类验证样本评估阶段生成预测与真实标签注意 ImageFolder 的类别编号按目录名字母排序顺序可能跟你预期不一样训练前先打印dataset.class_to_idx核对别到算指标时才发现类别顺序对不上。3. 预处理与增强让 fruits 分类数据集的每张图都进得了训练3.1 缩放与归一化怎么定先看你从零训练还是迁移学习预处理的目标是固定的把形状、亮度、大小各异的图片变成模型能稳定消化的张量。水果数据集最常见的问题不是拍摄角度而是尺寸不统一、背景占比失控、同一类别颜色差异大。所以缩放、归一化、增强三件套要一起定而不是一个一个凑。方案输入尺寸归一化适用场景从零训练小模型64128ToTensor 后再除以 255或自行统计 mean/std类别少、显存紧、纯练手迁移学习224ImageNet 的 mean/std几百到几千张样本的常见选择高分辨率大模型256384跟随预训练权重的设置原图分辨率高、背景干扰大迁移学习选 224 不是玄学预训练模型就是在 ImageNet 224×224 上学的输入尺寸和归一化都变等于换了个任务。从零训练时我一般先用 64×64 或者 96×96 试通流程确认 loss 在下降再考虑放大分辨率。直接上 224 对纯新手不友好训练时间长、调参噪音大而且水果分类这个任务没难到必须高分辨率。归一化数值也别乱写。从零训练时可以用ToTensor()自带的 [0,1] 归一化或者统计整个数据集的 mean/std 再标准化。迁移学习必须配[0.485, 0.456, 0.406]和[0.229, 0.224, 0.225]这组 ImageNet 统计值预训练权重是在这套数值下学出来的换掉等于把输入分布改了前面学的特征全白费。3.2 用 torchvision.transforms 组一套默认 pipeline以迁移学习为目标一套能直接跑通的预处理是这样from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(size224, scale(0.6, 1.0)), transforms.RandomHorizontalFlip(p0.5), 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]) ]) val_transform 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.6, 1.0)表示裁剪面积占原图的 60%100%。这个值别设太低太低会把水果切得只剩局部类别信息丢失。比如你裁到一个只有苹果皮纹理的区域模型学到的就不是“苹果”而是“红色纹理”。ColorJitter三项都给了 0.2为了缓解光线和拍摄风格差异。水果在真实场景里颜色差异很大但增强幅度超过 0.3 就会出现绿色葡萄被调成紫红色这种失真模型被迫学颜色以外的特征反而更难收敛。val_transform只做 Resize 到 256 再 CenterCrop 到 224不做任何随机增强。验证集的作用是模拟真实分布加了随机裁剪等于给评估结果注入噪声你看到的指标就不可信了。配套的 DataLoader 也有一处细节验证集shuffleFalse保证评估时样本顺序稳定。from torch.utils.data import DataLoader train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers2)batch_size32在 224 尺寸下显存占用适中训练集 shuffle 是梯度下降需要随机性验证集不需要。3.3 增强参数不是越大越好三招判断增强有没有过猛增强参数是最容易走极端的地方。有人为了“增加样本量”把 ColorJitter、RandomErasing 全部拉满结果训练集损失降不下去验证集也跟着波动。增强本质是正则化正则力度要和模型容量、数据量匹配不是越大越好。判断方法很简单固定同一个划分、同一个 batch_size、同样的 epoch 数分别跑两套增强参数对比 train loss 和 val loss。# 用同一个划分、同一个模型跑一轮完整训练只改增强口径 config_a { jitter: (0.1, 0.1, 0.1), erase_scale: 0.0, } config_b { jitter: (0.5, 0.5, 0.5), erase_scale: 0.2, } # 比较 train loss 与 val loss # 训练损失压不下去 - 增强过猛或预处理毁了图 # 训练损失降很快但验证损失不降 - 过拟合此时才该加大增强erase_scale对应 RandomErasing 的抹除面积比例我一般控制在 00.15。水果主体占图比例大抹掉 20% 以上就把关键区域盖住了这已经不是数据增强是数据破坏。网上很多人一上来先加增强其实顺序反了。正确顺序是先跑一个不加任何增强的基线确认模型能正常收敛再逐步加增强观察指标变化。每次只改一个变量否则你根本说不清是 ColorJitter 还是 RandomResizedCrop 导致的效果变化。4. 避坑清单水果分类数据集最常见的 5 个翻车点以下五条每一条都是我在不同图像分类数据集上踩过的按“现象-原因-解决”排列你能直接对号入座。前三条通常在解压和体检阶段出现后两条进了训练才会暴露。4.1 解压后目录乱码类别错乱现象把 rar 在 Linux 上解压后train 下的中文类别目录全部变成乱码ImageFolder 读出来的class_to_idx印出一堆看不懂的名字。Windows 上解压可能正常换个解压工具又不对。乱码目录还会让后续 Copy 脚本在手动映射类别时对不上号。原因rar 打包时通常记录的是打包者系统的本地编码最常见的是 GBK而 Linux 默认按 UTF-8 解码文件名两边对不上就乱码。这不是文件坏了是编码信息在解压时丢了。解决解压前用bsdtar -tf先看条目确认哪些目录名可能是非 UTF-8解压后如果有乱码用 Python 把乱码名按 GBK 重新解码再转成英文类别名。更省事的办法是不要依赖原始目录名做类别先人工确认类别清单再用脚本按位置或按文件名前缀重建目录。我的习惯是一律转成英文小写类别名apple、banana、grape后面所有脚本都用英文名杜绝二次乱码。4.2 隐藏文件被当成类别类别数莫名多出一堆现象训练脚本里dataset.class_to_idx打印出十几类比你知道的水果种类多出一大截而且多出来的类名叫.DS_Store、Thumbs.db或者MACOSX。原因macOS 和 Windows 在压缩目录时会把系统元数据文件一起打包ImageFolder 只要看到一个目录就当成一个类别它不关心目录名是不是系统文件。解决数据体检脚本里过滤隐藏目录转换脚本只认图片扩展名目录过滤用sub.name.startswith(.)和sub.name MACOSX两条规则。如果你用 ImageFolder 直接读原始目录训练前必须显式过滤掉不是目标类别的目录。最稳的方式还是走“转换脚本重建目录”那一步把系统文件挡在训练之前。4.3 训练到一半读图崩掉损坏图片与灰度图现象训练跑到第二个或第三个 epoch 突然报错错误信息经常是broken data stream或者cannot identify image file。有时候不报错而是 shape 对不上模型要求 3 通道某张图只有 1 个通道tensor 拼接直接炸。原因这类数据集图片来源杂有的 jpg 文件早就损坏了有的其实是灰度图或带 alpha 通道的 PNG。PIL 能打开不代表是干净的 RGB 三通道。解决在体检脚本里用Image.openim.load()逐个验证真正加载样本时统一convert(RGB)把可能的灰度图、RGBA 图都转成三通道损坏文件直接移出数据目录或记录到bad.txt里跳过。这套检查必须在训练前跑完不要等训练崩了再回头排查那个排查成本高得多。4.4 划分没固定随机种子实验结果对不上现象同样一份数据、同样的超参数隔一天再跑验证集准确率差了三到五个点你甚至开始怀疑模型训练是个玄学。原因划分训练集时没固定随机种子DataLoader 的shuffle也在用新的随机序列。两次实验的数据子集都不一样结果当然对不上。这不只是复现问题——你根本没法判断精度变化是模型改动带来的还是数据集划分变了带来的。解决划分脚本和训练脚本开头都固定random.seed和torch.manual_seed把划分结果导出成train.txt、val.txt两个清单文件下次训练直接按清单读文件这是最稳的复现方式。要对比实验时只改模型相关代码数据和顺序一律不动。4.5 类别不平衡准确率虚高现象验证集整体准确率 82%看着还行但打开分类报告发现某个小众类别精确率和召回率都是 0模型根本没学会这一类。原因某种水果的样本量占一半以上模型只要把决策边界偏到大类方向就能拿到高准确率小类被完全忽略。类别不平衡在公开的水果分类数据包里很常见尤其是热带水果和小众品种。解决先不急着调模型看classification_report的单类 recall。如果某类样本太少用WeightedRandomSampler按样本数的反比给少量类别加权或者干脆把小样本类别剔除。先保证每类样本量基本齐整再谈模型结构。只看总准确率判断模型好坏是水果分类数据集最大的坑。5. 从练手到交付用迁移学习快速验证并读懂单类指标5.1 十行代码换一个可信基线几百到几千张的水果图片从零训练一个 CNN 很容易欠拟合。最常见的做法是 ResNet18 加 ImageNet 预训练权重冻结骨干先只训练全连接层几轮后看损失不再降再解冻微调。import torch import torch.nn as nn from torchvision import models torch.manual_seed(0) # 固定种子保证结果可复现 model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) model.fc nn.Linear(model.fc.in_features, num_classes)注意 torchvision 新旧版本写法有差异旧版用pretrainedTrue新版用weights...。装了什么版本就看对应文档两种写法混用会直接报错。这套迁移学习方案在 fruits 分类数据集上通常几十个 epoch 就能收敛出可用的基线后面再谈结构改进才有参照物。5.2 用分类报告和混淆矩阵看清单类真实表现训练完别只盯着总准确率跑一下分类报告和混淆矩阵能看出模型到底在学什么。from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_true, y_pred, digits3)) print(confusion_matrix(y_true, y_pred))按类别看 recall 和 precision。如果模型在“盘子”“背景”这些类别上很强、在水果上一般说明模型在偷懒靠背景线索分类回预处理环节检查RandomResizedCrop的 scale 是不是裁掉了太多主体。我第一次拿水果数据集练手时跳过体检直接迁移学习准确率 92%还挺满意。后来换了一批拍摄风格不同的图片就全崩翻出混淆矩阵才发现模型在靠盘子边缘分类而不是水果本身的纹理。从那以后“先体检、再训练、用单类指标验收”成了我的固定流程。分类报告和混淆矩阵这两样也成了我所有图像分类项目的标配检查项。希望你也能少走这一段弯路。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站