
简介包含苹果、香蕉、葡萄、橙子、梨五类常见水果图片的图像分类数据集每张图片附有类别标签适用于机器学习入门、图像识别训练及数据挖掘实践。压缩包内共1310个文件其中1306张JPG图片构成主要样本另有2个标签列表、1个JSON配置及1个Python脚本便于数据加载、标签映射和子目录划分整体大小仅14.07MB轻量易用。目前已有3625人学习下载使用热度较高。借助该数据集读者可从图像预处理、形状颜色纹理等特征分析入手逐步完成特征提取与分类模型构建并运用卷积神经网络进行端到端训练附带脚本还能快速生成训练集、验证集和测试集省去手动整理标签的麻烦。对于课程设计、毕业项目或计算机视觉新手这是一份能完整走通分类流程的基础数据资源。1. 水果分类数据集为什么一份 rar 就劝退了大半新手做图像分类练习、本科毕设或者入门迁移学习的人大概率都下载过类似“水果分类数据集 fruits分类数据集.rar”这样的压缩包。发布者习惯用 rar 把几千张图片打包理由是压缩率高、适合网盘分享但接收者这边解压工具不兼容、中文文件名乱码、解压后的目录结构与训练脚本预期不一致一连串问题全堆在还没开始写模型之前。这份数据集本身并不复杂子文件夹按水果类别划分图片是常见的 JPG/PNG用 PyTorch 的 ImageFolder 几行代码就能加载。真正的门槛往往在 rar 这个分发格式上以及你有没有对数据集目录先做一次“体检”。这篇文章从解压开始把文件核对、样本统计、数据加载到避坑排查完整走一遍适合刚拿到这个数据集、正准备跑通第一个训练脚本的人。2. 解开 fruits分类数据集.rar工具的选型、命令与密码真相2.1 为什么水果数据集偏爱 rar 分发三个现实理由rar 是 WinRAR 的专有压缩格式虽然带专利和版权限制但在国内网盘和高校内网里流传极广。数据集打包者选 rar通常不是图它压缩率比 zip 高多少——对 JPG 这种已经压缩过的图片格式差距其实很小——而是因为三个现实原因第一WinRAR 的右键“添加到压缩文件”是 Windows 用户最熟悉的操作菜单里还能设置恢复记录和分卷第二rar 支持恢复记录网盘传输过程中即使出现扇区损坏有时也能靠恢复记录把整个压缩包救回来第三很多老数据集打包时还没有 7-Zip 的生态分享者用 WinRAR 打包后来的传播就沿用了这个格式。对使用者来说第一关就是“能不能解开”。7-Zip 可以解压 rar 文件吗可以Windows 下装 7-Zip 后右键选择“解压到当前文件夹 / 解压到指定文件夹”就行Linux 下需要安装 p7zip-full 或 unrarmacOS 下推荐 The Unarchiver。但有一个容易被忽略的前提7-Zip 对 rar 的解压是基于 unrar 的兼容实现对老版本 rar 3.x 支持得很好对新版 rar 5.x 的某些固实压缩和加密组合偶尔会解到一半报 CRC 错误。这时候不要急着判定压缩包损坏换 unrar 或者 WinRAR 本身再试一次。工具平台rar 解压支持备注WinRARWindows完整官方实现商业试用版弹窗广告需手动关闭7-ZipWindows / Linux解压支持基于 unrar 兼容实现开源免费日常解压够用p7zip-full7zLinux解压支持apt 安装后命令为 7zunrarLinux / macOS官方兼容性最佳免费但有许可限制Debian 放在 contrib 源提示如果压缩包是分卷fruits分类数据集.part1.rar、part2.rar 这样必须把全部分卷下载到同一目录从 part1 开始解压。2.2 解压实操命令从完整性测试到展开文件拿到压缩包先不要直接解压先做完整性测试。我在处理别人发的 rar 数据集时吃过亏看着几十个文件解压完训练到一半才发现某张图 CRC 校验失败整个 batch 崩掉。所以顺序应该是先7z t测试再7z l浏览内容最后7z x解压。# 1. 测试压缩包完整性不实际解压 7z t fruits分类数据集.rar # 2. 列出压缩包内容和解压后总大小 7z l fruits分类数据集.rar # 3. 解压到指定目录-o 后不能有空格 7z x fruits分类数据集.rar -o./fruits_data -y # 4. 如果 7z 解压报 CRC 错误换 unrar 再试 unrar x fruits分类数据集.rar ./fruits_data/7z t是 test 模式会对压缩包内每个文件做 CRC 校验输出哪里有损坏如果显示 “Sub items Errors” 就需要重新下载完整文件。7z l是列出文件清单重点看有没有中文文件名、是不是分卷、解压后总大小是多少。7z x与7z e的区别要记清x 保留压缩包内目录结构e 把所有文件平铺到单一目录。这个数据集内部是按类别分文件夹的必须用 x 才能保留结构用 e 会把类别信息全部拍平训练脚本直接废掉。-o指定输出目录注意-o与路径之间不能有空格否则会被当成另一个参数。-y表示所有确认提示自动选 Yes适合脚本化执行。如果压缩包带了密码命令行解压会提示输入批量场景下可以在命令里追加-p密码。注意7z l在不知道密码时也能列出文件名只有“加密文件名”模式例外很多分享者会把密码写在原帖评论区或 README 里比自己去猜省事得多。2.3 密码与破解工具的真相rar password cracker 为什么是死路网上搜“rar 密码移除”“rar password cracker”会看到一堆声称能秒破 rar 密码的软件。这里把话说死rar 5.x 默认使用 AES-256 加密密码长度超过 8 位时普通 PC 上暴力破解的时间以年为单位所谓“移除密码”工具几乎都是捆绑木马或挖矿脚本的流氓软件。数据集分享者设置密码通常是防爬虫、防倒卖密码一般就在原帖或随包 README 里找作者要都比重建字典靠谱。如果你确认密码正确却解压失败先检查是不是记错了大小写和空格再试试常见组合数据集名缩写、发布年份、作者名字小写。我曾在某份数据集上折腾了半小时最后发现密码里有个大写字母 I被自己看成了小写 l。另外提醒一句网上流传的“去广告版”“烈火版” WinRAR 多为破解修改版装完轻则弹窗广告重则后台驻留进程。真想绕开广告用 7-Zip 这类开源工具就行不要碰不明来源的 exe。2.4 解压后的第一件事核对文件清单与磁盘余量解压完成后不要急着打开训练脚本先做三件事。第一用du -sh ./fruits_data看实际占用图像数据解压后通常比压缩包大 5 到 10 倍训练时还要生成日志和缓存磁盘余量至少留出数据集的 1.5 倍。第二对照压缩包内文件数find ./fruits_data -type f | wc -l统计实际文件数跟7z l输出的数量对得上再继续。第三随机打开几张 JPG 确认不是损坏图片Linux 下用file xxx.jpg看输出是否为JPEG image data。这一步做完数据集的“解压黑匣子”才算真正打开。很多人训练脚本报FileNotFoundError或Found no valid file根因其实在解压阶段就埋下了中文目录名乱码、文件权限不对、大小写不匹配。下一章用一段 Python 把整个目录扫一遍这些问题会立刻暴露。3. 读懂数据集目录Train/Test 划分、类别统计与不平衡检查3.1 fruits 数据集的目录约定子文件夹就是类别标签把 rar 解开后典型的水果分类数据集长这样根目录下有 train、test有时是 train、valid、test两个或三个子目录每个子目录里再按水果类别分一层比如train/apple/、train/banana/。公开的水果分类数据集大多采用这种结构直接兼容 torchvision 的ImageFolder子目录名就是类别名图片文件本身叫什么无所谓。但也有数据集把所有图片放在一个目录另配一份labels.csv或train.txt记录每张图的标签这就要写自定义 Dataset 了常见做法是读取 CSV 后按文件名映射标签。第一步永远是先看目录树不要猜。Linux 下tree -L 2 ./fruits_data一行就能看清两层结构Windows 下在资源管理器里把“文件夹”列展开。这里有容易踩的坑数据集发布者可能在根目录混入了Thumbs.db、.DS_Store或 README 文档ImageFolder在缺省is_valid_file参数时会过滤掉未知扩展名但如果隐藏文件本身是一个文件夹比如__MACOSX它会被当成一个类别。解压后先清理这些系统残留能让后面的扫描脚本少很多莫名其妙的结果。3.2 用 Python 扫描数据集一段代码看清类别、样本量与不平衡度我不建议用“人眼数文件夹”的方式掌握数据集几百到几千张图片靠看是看不准的。写一段十几行的脚本把类别名、每类样本数、总样本数都打出来顺便检查文件扩展名是否统一。这是动手写训练代码前最值回票价的五分钟。import os from collections import Counter root ./fruits_data/train # 换成你实际解压的路径 category_counts {} ext_counter Counter() for category in sorted(os.listdir(root)): cat_path os.path.join(root, category) if not os.path.isdir(cat_path): # 跳过 __MACOSX、Thumbs.db 这类系统残留 continue files [f for f in os.listdir(cat_path) if os.path.isfile(os.path.join(cat_path, f))] # 只统计图片扩展名其余一并记入扩展名分布 imgs [f for f in files if f.lower().endswith((.jpg, .jpeg, .png, .bmp))] category_counts[category] len(imgs) for f in files: ext os.path.splitext(f)[1].lower() ext_counter[ext] 1 print(类别数量:, len(category_counts)) print(总图片数:, sum(category_counts.values())) print(每类样本数:, category_counts) print(扩展名分布:, dict(ext_counter)) # 顺手算一个不平衡度最大类 / 最小类 max_count max(category_counts.values()) min_count min(category_counts.values()) print(f最大/最小类样本比: {max_count / min_count:.1f}x)逻辑说明os.listdir按名字拿到所有条目os.path.isdir过滤出子目录再对子目录内做二次过滤。Counter统计扩展名分布能暴露“混入了 GIF/WebP”这类 PIL 能读但 torchvision 部分 transform 处理不好的格式。最后算最大最小类样本比比值超过 10 就建议走加权采样否则模型会明显偏向样本多的类别。参数怎么解读类别数量决定模型输出维度比如输出 12 类就用num_classes12总图片数决定 epoch 轮数和训练时长扩展名分布决定要不要统一转码。如果发现数据集中既有.jpg又有.jpeg还有大写.JPG读取时统一用Image.open(...).convert(RGB)让 PIL 去兜底避免训练中途因为格式问题中断。3.3 从标签到训练类别名编码、排序与中英文文件名ImageFolder的标签按子目录名的字母序自动编号class_to_idx是一个字典比如{apple: 0, banana: 1, cherry: 2}。这个行为有两个隐患一是如果子目录名是中文排序规则与编码相关换一台机器结果可能不同二是模型输出层按照这个顺序初始化保存的 checkpoint 里如果记录了类别索引换目录结构后必须重新映射。我的习惯是不管原始目录是中文还是英文训练前先统一改成英文类别名或者额外生成一份class_to_idx.json固化下来让训练脚本和评估脚本读同一个映射文件。否则复现别人的实验时出现“标签错位”是最难排查的问题——训练 loss 在降模型推理结果却全乱了因为权重对应的是另一个顺序的类别表。改名的脚本很简单一行os.rename(old, new)循环即可但注意先备份原始名映射避免想换回来时找不到。另外文件名里的空格和中文在 torchvision 的DatasetFolder里一般没大问题但在 OpenCV 的cv2.imread里会直接返回None它历来不支持非 ASCII 路径。如果后续打算用 OpenCV 做预处理最好在扫描阶段就把文件复制或重命名为纯英文路径这是从根源上绕开这个 bug 的常规做法。4. 用 PyTorch 把 fruit 图片喂进模型从 ImageFolder 到自定义 Dataset4.1 最小可运行方案ImageFolder DataLoader 直接开训解压、扫盘都做完了现在可以写训练侧代码。水果分类属于图像分类里难度中等偏下的任务类间差异苹果和梨比类内差异不同品种的苹果更容易区分常见的做法是先用 ImageNet 预训练模型做迁移学习。数据加载端最简方案就是ImageFolderimport torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集做数据增强验证集只做缩放和归一化 train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.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]), ]) train_dataset datasets.ImageFolder(root./fruits_data/train, transformtrain_transform) val_dataset datasets.ImageFolder(root./fruits_data/val, transformval_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue) print(类别映射:, train_dataset.class_to_idx) print(训练样本数:, len(train_dataset), 验证样本数:, len(val_dataset))说明RandomResizedCrop(224, scale(0.8, 1.0))随机裁一块再缩放到 224比固定 Resize 的泛化性好模拟了果实大小和拍摄距离的变化ColorJitter对水果这种颜色是重要特征的任务要克制使用亮度抖动 0.2 可以色相抖动参数别开太大否则会把青苹果抖成红苹果。Normalize用 ImageNet 的均值标准差迁移学习场景保持和预训练权重一致即可。shuffle在训练集必须为True验证集保持False这样每个 epoch 的评估结果可对比。这里有一个 Windows 用户几乎必踩的坑如果代码没放在if __name__ __main__:保护下直接运行num_workers4会触发多进程报错RuntimeError: An attempt has been made to start a new process...。解决方式是把训练代码放进main()函数或者按平台设置num_workers0。4.2 自定义 Dataset 的场景非标准目录、样本过滤与清单缓存ImageFolder要求“每个类一个子目录”但不是每份 fruits 数据集都这么规范。常见的不规范情况有三种所有图片平铺在一个目录、标签由 CSV 记录子目录内混入大量非图片文件还存在临时缓存目录。这时候就需要自定义 Dataset。import os from PIL import Image from torch.utils.data import Dataset class FruitsDataset(Dataset): def __init__(self, img_dir, label_map, transformNone, valid_ext(.jpg, .jpeg, .png)): # label_map 是 {文件夹名: 类别整数} 或 {文件名: 类别整数} self.samples [] self.transform transform for name in sorted(os.listdir(img_dir)): full_path os.path.join(img_dir, name) if os.path.isdir(full_path): # 子目录模式子目录名就是类别名 if name not in label_map: continue for fname in os.listdir(full_path): if fname.lower().endswith(valid_ext): self.samples.append((os.path.join(full_path, fname), label_map[name])) else: # 平铺模式文件名映射标签 if name.lower().endswith(valid_ext) and name in label_map: self.samples.append((full_path, label_map[name])) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] try: img Image.open(path).convert(RGB) except (IOError, OSError) as e: raise RuntimeError(f损坏图片: {path} - {e}) if self.transform: img self.transform(img) return img, label逻辑说明构造阶段把全部路径, 标签对展开成列表__len__和__getitem__都基于这个列表避免每次迭代都重扫磁盘。label_map把可读的类别名映射成整数这个映射应在训练前固定并保存保证训练和推理用的是同一份。如果发现打不开的图片在__getitem__里抛异常会让整个训练中断我一般写成在初始化阶段用PIL.Image.open(...).verify()快速检查头部把坏文件从self.samples里剔除后再开始训练。这个自定义类的额外好处是加缓存把self.samples序列化成 JSON 或 CSV第二次启动直接加载省去遍历成千上万个文件的等待时间。样本量上万时这个优化体感很明显第一次扫描十几秒之后每次启动只要零点几秒。4.3 训练参数推荐起点图像尺寸、batch size、学习率与 epoch水果分类数据集通常几百到几千张属于小数据集参数设置直接照搬大模型训练很容易过拟合。我一般在 ResNet18/50 或 EfficientNet-B0 上做微调参数起点如下参数推荐起点调整依据图像尺寸224×224与 ImageNet 预训练输入一致更大尺寸收益有限且显存翻倍batch size328GB 显存可跑 ResNet50太大容易 OOM太小 BN 统计不稳定优化器AdamW (lr3e-4)微调用 1e-4 更稳从头训练用 3e-4weight decay1e-4抑制小数据集过拟合epoch30~50水果分类类间差异大通常 20 轮后验证集开始饱和学习率调度CosineAnnealingLR比 StepLR 平滑省去手动找下降节点关键点是预训练模型的 backbone 参数要不要冻结。数据量只有几百张时我习惯先冻结 backbone只训练最后的分类头几个 epoch再解冻 backbone 用 1e-5 左右的低学习率微调。这么做的好处是前期训练稳定不会因为梯度太大把预训练权重冲坏。数据量超过两千张可以直接全参数微调用 1e-4 起步配一个 warmup。关于 batch size 还有一条容易忽略的规则调大 batch size 时学习率要跟着调大线性缩放比例是“新 batch / 旧 batch”。比如从 32 调到 64学习率从 3e-4 调到 6e-4否则收敛速度会明显变慢。BN 层的统计也会随 batch 变动训练中途突然改 batch 会造成一波性能震荡要么一开始就定好要么用多卡sync_bn来缓解。5. 避坑排查解压失败、乱码、样本损毁与验证集污染的 5 条记录5.1 解压中途报错先分清下载不完整还是工具不兼容现象7z x解压到一半输出Data Error in encrypted file. Wrong password?或CRC Failed然后解压中断。原因一是网盘下载时文件不完整rar 尾部数据缺失二是这个 rar 用了较新的压缩特性当前 7-Zip 版本兼容不到位。解决先跑7z t fruits分类数据集.rar做完整性测试测试本身报错就重下测试通过但解压报错换 unrar 或 WinRAR 再试。判断分卷时注意.part1.rar必须从第一个分卷开始单独解压后续分卷必然失败。这条放在最前是因为它最容易被误判——很多人以为是密码问题跑去搜“rar password cracker”绕了一大圈才发现只是文件没下全。5.2 中文文件名乱码GBK 与 UTF-8 的编码冲突现象解压后目录名显示为“鏋滃疄鍒嗙被”之类的乱码或者 Pythonos.listdir输出带一堆\ufffd替换符。原因Windows 上 WinRAR 创建压缩包时中文文件名以本地代码页GBK/GB18030写入Linux 和 macOS 默认 UTF-8解压工具没做代码页转换直接把 GBK 字节按 UTF-8 解码。解决最简单的方法是先在 Windows 上用 Bandizip 或 WinRAR 解压把目录改成英文名后再传到 Linux已经解压乱的写几行 Python 用name.encode(latin-1).decode(gbk)尝试还原前提是乱码只经过一层转义。血泪经验不要在训练中途手工重命名文件夹训练脚本里的路径全部会失效改完还要同步 checkpoint 里的标签映射。一次性改好再训练省得来回折腾。5.3 样本不平衡少数类被模型直接无视现象训练几个 epoch 后验证集上香蕉、苹果的准确率很高杨桃、山竹的准确率接近 0整体准确率看着还行但每个类别的召回率差距悬殊。原因数据集中少数类样本数只有几十张多数类有几千张交叉熵损失被多数类主导模型学到的是“全猜成苹果”这类捷径。解决先按第 3 章的扫描脚本算出最大/最小类样本比超过 10 就介入。首选是WeightedRandomSampler按样本数倒数给采样概率让每个 epoch 里少数类被抽到的次数与多数类接近from torch.utils.data import WeightedRandomSampler # 每个样本的权重 该类样本总数的倒数 labels [sample[1] for sample in train_dataset.samples] class_counts torch.bincount(torch.tensor(labels)) class_weights 1.0 / class_counts.float() sample_weights class_weights[torch.tensor(labels)] sampler WeightedRandomSampler(sample_weights, num_sampleslen(labels), replacementTrue) train_loader DataLoader(train_dataset, batch_size32, samplersampler)说明replacementTrue允许重复采样少数类每个 epoch 也会被多次抽到代价是多数类相对欠采样epoch 数要适当增加。也可以把class_weights传给nn.CrossEntropyLoss(weightclass_weights)直接放大少数类的 loss 梯度。两条路可以同时用但采样器和 loss 权重都加时梯度会被过度放大建议先只用一种看验证集效果再决定要不要叠加。5.4 验证集污染同一颗果实的多张照片被同时分进训练和验证现象验证集准确率 99%模型一换到真实场景就翻车。细看发现训练集和验证集里出现文件名后缀相近的图片比如apple_001_1.jpg和apple_001_2.jpg这是同一颗苹果的不同角度照片信息泄漏了。原因数据集发布者切分时按文件名随机分没有考虑“同一实物”的分组模型在验证时其实“见过”这颗苹果的颜色和纹理。解决按文件名前缀分组重新切分。用sklearn.model_selection.GroupShuffleSplit把apple_001作为 group保证同一组的图片不会同时出现在 train 和 val。没有 sklearn 就手动按 group 的哈希值分桶。数据集原始划分如果已经存在这种泄漏重新切分后准确率降几个点很正常那才是真实水平。5.5 磁盘与内存检查解压前必须看的三项数字现象解压到 80% 弹No space left on device或者训练时 DataLoader 疯狂读盘CPU 跑满 GPU 闲着。解决解压前执行7z l查看解压后总大小再df -h看目标盘剩余空间预留 1.5 倍余量。训练前用nvidia-smi看显存free -g看内存num_workers不要超过 CPU 核心数的一半否则进程调度开销会抵消并行读取收益。数据量大时建议把图片转成 LMDB 或缓存进内存——几千张 224×224 的 RGB 图占不到 2GB 内存配合pin_memoryTrue还能省一次 CPU 到 GPU 的拷贝。很多“训练特别慢”的问题不是 GPU 不行而是磁盘和小文件 IO 卡在瓶颈上。6. 进阶验证技巧给数据集做一次“体检”让实验结果不再“玄学”6.1 用哈希建立数据指纹换环境后还能对齐解压后的数据集被反复拷贝、迁移、重新压缩容易在某个环节混入一两张坏图或重复图导致训练结果不像预期。我的习惯是解压完成后立刻生成一份校验文件把每张图片的 SHA-256 和相对路径写进 manifest 留档保存。import hashlib, os def sha256(path): h hashlib.sha256() with open(path, rb) as f: for chunk in iter(lambda: f.read(65536), b): h.update(chunk) return h.hexdigest() with open(fruits_manifest.txt, w, encodingutf-8) as out: for root, _, files in os.walk(./fruits_data): for fname in files: full os.path.join(root, fname) rel os.path.relpath(full, ./fruits_data) out.write(f{sha256(full)} {rel}\n)以后换机器、重新解压或者怀疑某张图被损坏用同一份 manifest 一比对就能定位差异文件。对几千张小图片来说生成校验和只要几十秒省下的是很多次“换了环境结果复现不了”的玄学排查。6.2 可视化错误样本一眼看出是标注错误还是模型缺陷训练结束后把验证集里预测错的图片挑出来拼成网格比只看 loss 曲线更能定位问题。如果错误样本集中在某两类之间梨被认成苹果说明类间特征接近需要针对这两类增加样本或做定向增强如果错误样本本身就是标签错——人眼能看出图里是苹果文件却标了梨——说明数据集有标注噪声先清洗数据再调模型。代码思路很直接用训练好的模型遍历验证集记录pred ! label的图片、真实标签、预测标签、置信度按置信度降序排序优先看“高置信但预测错误”的样本那些往往是标注错误模型没理由学错。import matplotlib.pyplot as plt import torchvision.utils as vutils misclassified.sort(keylambda x: x[3], reverseTrue) # 按置信度降序 grid_img vutils.make_grid( torch.stack([x[0] for x in misclassified[:16]]), nrow4 ) plt.imshow(grid_img.permute(1, 2, 0).cpu().numpy()) plt.axis(off) plt.show()我个人的习惯是任何一份从网上下载的数据集都要先把“解压校验、类别统计、坏图清理、分组划分”这四件事做完再开始写模型。数据侧做扎实了训练出来的指标才具备可解释性否则模型准确率高是信息泄漏还是真的学到了特征黑匣子永远说不清。这套流程也适用于其它以 rar 分发的图像数据集并不只限于水果分类。希望帮到你。本文还有配套的精品资源点击获取