简介这份资源是面向深度学习入门与图像分类实践者的水果图像分类数据集覆盖苹果、香蕉、樱桃、火龙果、芒果、橘子、菠萝、木瓜共8个类别可直接用于模型训练与验证省去自行采集与清洗图像的环节。压缩包内共约2000个文件以jpeg图片为主另含少量webp与png样本并附带1个json类别字典和1个可视化py脚本整体约636.77MB解压后分为训练集2220张与测试集550张均按类别子文件夹组织目录结构清晰便于直接接入常见训练框架。资源还提供classes类别映射文件与可视化代码方便快速核对标签与预览样本。目前已有314人学习下载适合课程作业、分类实验及算法对比等场景使用。1. 水果图像分类数据集8 分类任务从数据到落地的第一道坎拿到一个 8 分类的水果图像数据集很多人第一反应是直接套 ResNet 跑一遍结果训练集准确率冲到 99%测试集却连 70% 都不到。问题往往不在模型而在数据本身——类别边界模糊、背景干扰严重、样本分布不均这些坑在水果图像上尤其明显。水果图像分类数据集8 分类通常包含苹果、香蕉、橙子、葡萄、芒果、草莓、菠萝、西瓜这八类常见水果每类几百到上千张不等图像来源多为电商白底图、田间实拍和货架抓拍混合。这个数据集适合两类人一是刚入门深度学习图像识别、想找一个比 MNIST 和 CIFAR 更贴近真实场景的练手项目二是需要快速验证图像分类模型在细粒度视觉任务上表现的工程师。它解决的核心问题是在类别间颜色和形状高度相似的情况下如何让模型学到真正有判别力的特征而不是靠背景颜色走捷径。2. 水果图像分类数据集8 分类的构成、划分与预处理2.1 数据集目录结构与类别定义常见的水果 8 分类数据集目录组织方式有两种按类别分文件夹或者用 CSV 标注文件记录路径和标签。前者更直观后者更灵活。我一般推荐按类别分文件夹因为 torchvision 的 ImageFolder 可以直接读取省去写 Dataset 类的麻烦。典型结构如下fruit8/ ├── train/ │ ├── apple/ │ ├── banana/ │ ├── orange/ │ ├── grape/ │ ├── mango/ │ ├── strawberry/ │ ├── pineapple/ │ └── watermelon/ ├── val/ │ ├── apple/ │ └── ... └── test/ ├── apple/ └── ...每个类别文件夹下放对应水果的图片格式以 JPG 和 PNG 为主。这里有个血泪经验如果数据集中混入了 WebP 或 BMP 格式PIL 读取时不会报错但后续 transform 可能出问题建议统一转成 JPG。另外类别名称建议用英文小写避免中文路径在部分 Linux 环境下编码出错。2.2 训练集、验证集、测试集的划分比例与分层采样8 分类水果数据集的划分不能随便 8:1:1 切一刀。如果某类水果只有 300 张按 8:1:1 分验证集只有 30 张评估结果波动会非常大。我一般按 7:1.5:1.5 划分并且做分层采样保证每个子集的类别分布一致。用 scikit-learn 的 train_test_split 可以一行搞定from sklearn.model_selection import train_test_split import os # 假设所有图片路径和标签已经读入 paths [...] # 图片路径列表 labels [...] # 对应标签列表 # 先分训练集和临时集 train_paths, temp_paths, train_labels, temp_labels train_test_split( paths, labels, test_size0.3, stratifylabels, random_state42 ) # 再从临时集中分验证集和测试集 val_paths, test_paths, val_labels, test_labels train_test_split( temp_paths, temp_labels, test_size0.5, stratifytemp_labels, random_state42 )stratifylabels是关键参数它保证划分后每个类别的比例与原始数据一致。random_state42固定随机种子方便复现。如果某类样本少于 50 张建议直接合并到其他相似类别或者用数据增强扩充后再划分。2.3 图像预处理尺寸、归一化与数据增强参数水果图像分类的预处理分两步基础变换和增强变换。基础变换包括 Resize 到统一尺寸、ToTensor、Normalize。增强变换只在训练时用包括随机水平翻转、随机旋转、颜色抖动。下面是我常用的配置from torchvision import transforms # 训练集变换 train_transform transforms.Compose([ transforms.Resize((256, 256)), # 先放大再裁剪保留更多细节 transforms.RandomResizedCrop(224), # 随机裁剪到 224 transforms.RandomHorizontalFlip(p0.5), # 水平翻转 transforms.RandomRotation(15), # 随机旋转 ±15 度 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, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])RandomResizedCrop(224)的 scale 参数默认是 (0.08, 1.0)对于水果图像建议改成 (0.5, 1.0)避免裁到只剩一小块果皮导致标签歧义。ColorJitter的强度不要太大水果的颜色是重要判别特征抖动过猛会让香蕉和芒果混淆。Normalize 的均值和标准差用的是 ImageNet 的统计值如果数据集风格差异大可以自己算一遍。2.4 用 DataLoader 构建高效输入管道预处理定义好后用 DataLoader 包装成批次。这里有几个参数直接影响训练速度和内存占用from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder train_dataset ImageFolder(rootfruit8/train, transformtrain_transform) val_dataset ImageFolder(rootfruit8/val, transformval_transform) train_loader DataLoader( train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue ) val_loader DataLoader( val_dataset, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue )num_workers4在大多数机器上够用如果 CPU 核心多可以调到 8。pin_memoryTrue在 GPU 训练时能加速数据传输。drop_lastTrue只在训练集用避免最后一个不完整批次影响 BatchNorm 统计。如果显存不够把 batch_size 降到 16 或 8同时把学习率按比例调小。3. 从零训练一个 8 分类水果识别模型选型、训练与调参3.1 模型选型ResNet、EfficientNet 还是轻量级 CNN水果 8 分类不算难任务但类别间相似度高模型容量太小会欠拟合太大又容易过拟合。我一般从三个档位选ResNet-18 作为基线EfficientNet-B0 作为精度优先MobileNetV3-Small 作为速度优先。ResNet-18 参数量约 1100 万在 8 分类任务上训练 30 个 epoch 就能收敛到 90% 以上。EfficientNet-B0 参数量约 530 万精度通常比 ResNet-18 高 2 到 3 个百分点但训练时间更长。MobileNetV3-Small 参数量只有 250 万适合部署到边缘设备但精度会低 5 个百分点左右。选型时还要看数据集规模。如果每类只有 200 张ResNet-18 就够再大容易过拟合。如果每类有 2000 张以上可以上 EfficientNet-B0 甚至 B2。我一般先用 ResNet-18 跑通流程再根据验证集精度决定是否换更大模型。3.2 迁移学习加载预训练权重与冻结策略水果图像和 ImageNet 的分布差异不算大迁移学习能显著加快收敛。加载预训练权重的代码如下import torchvision.models as models import torch.nn as nn # 加载预训练 ResNet-18 model models.resnet18(pretrainedTrue) # 替换最后的全连接层 num_features model.fc.in_features model.fc nn.Linear(num_features, 8) # 8 分类 # 冻结前面的层只训练全连接层 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True冻结策略分两阶段第一阶段只训练全连接层学习率设 1e-3跑 5 个 epoch第二阶段解冻所有层学习率降到 1e-4再跑 20 个 epoch。这样能避免随机初始化的全连接层在初期产生大梯度破坏预训练特征。如果数据集和 ImageNet 差异很大比如全是田间实拍可以一开始就解冻所有层但学习率要更小。3.3 训练循环损失函数、优化器与学习率调度损失函数用 CrossEntropyLoss优化器用 AdamW 或 SGD。AdamW 对学习率不敏感适合快速实验SGD 配合动量在精细调参时上限更高。我一般先用 AdamW 跑基线import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30, eta_min1e-6) for epoch in range(30): model.train() for images, labels in train_loader: images, labels images.cuda(), labels.cuda() optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.cuda(), labels.cuda() outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() print(fEpoch {epoch1}, Val Acc: {correct/total:.4f})weight_decay1e-4是正则化项防止过拟合。CosineAnnealingLR让学习率按余弦曲线下降比 StepLR 更平滑。如果验证集精度波动大可以把 T_max 设大一点比如 50。3.4 关键超参数batch size、学习率与 epoch 数的配合这三个参数要一起调。batch size 增大学习率要按比例增大但不要超过 1e-2。我常用的组合是batch_size32 配 lr1e-3batch_size64 配 lr2e-3。epoch 数看验证集精度曲线如果 20 个 epoch 后还在涨就加到 30 或 50。如果 10 个 epoch 就平了检查学习率是不是太小。还有一个容易忽略的参数是 weight_decay。水果图像分类任务上1e-4 到 1e-3 之间比较合适。太大导致欠拟合太小导致过拟合。可以用验证集精度做网格搜索但一般 1e-4 就够。4. 水果图像分类的避坑与排查8 分类任务里那些翻车现场4.1 现象训练集准确率 99%测试集只有 65%原因数据泄露或分布不一致。常见情况是训练集和测试集来自同一批图片的不同裁剪或者测试集里混入了训练集的增强版本。另一个原因是背景泄露比如所有苹果都在白色背景上所有香蕉都在黄色背景上模型学到了背景而不是水果本身。解决检查数据划分是否按图片原始来源分层而不是随机切分。用 Grad-CAM 可视化模型关注区域如果热力图集中在背景说明模型走了捷径。把背景多样化或者在预处理中加入随机背景替换。4.2 现象香蕉和芒果总是互相误判原因这两个水果在颜色和形状上高度相似尤其是成熟度不同的样本。如果数据集中香蕉偏黄、芒果偏橙模型可能靠颜色区分但遇到青香蕉或红芒果就翻车。解决增加这两个类别的样本多样性覆盖不同成熟度和拍摄角度。在损失函数中加入类别权重让模型更关注难分类样本。或者用标签平滑把硬标签换成软标签缓解过拟合。4.3 现象验证集 loss 震荡剧烈精度忽高忽低原因batch size 太小或者学习率太大。水果图像分类任务中如果每类样本少batch 内类别分布不均匀梯度方向波动大。解决增大 batch size 到 64 或 128同时按比例增大学习率。如果显存不够用梯度累积模拟大 batch。另外把 shuffle 设为 True并在每个 epoch 前手动打乱数据顺序。4.4 现象训练到一半 loss 突然变成 NaN原因学习率过大导致梯度爆炸或者数据中有损坏图片。水果数据集中常见的是图片文件损坏但扩展名正常PIL 读取时返回 NoneToTensor 时报错。解决先用脚本遍历所有图片检查是否能正常读取和转换。把学习率降到 1e-4 再试。如果还有问题加入梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)4.5 现象推理时单张图片预测结果和批量预测不一致原因预处理不一致。训练时用了 RandomResizedCrop 和 ColorJitter推理时只用了 Resize 和 CenterCrop。如果推理代码里误用了训练变换结果会随机波动。解决严格区分 train_transform 和 val_transform推理时只用 val_transform。另外检查 Normalize 的均值和标准差是否和训练时一致。BatchNorm 层在推理时要设为 eval 模式否则会使用当前 batch 的统计量。5. 把 8 分类水果模型推到 95% 以上进阶技巧与验证方法5.1 用 Mixup 和 CutMix 提升泛化能力Mixup 和 CutMix 是两种简单有效的数据增强策略。Mixup 把两张图片按比例混合标签也按比例混合CutMix 把一张图片的部分区域裁剪后粘贴到另一张上标签按区域面积加权。这两种方法能显著缓解过拟合尤其是在样本量不大的水果数据集上。实现代码如下import numpy as np def mixup_data(x, y, alpha0.2): lam np.random.beta(alpha, alpha) batch_size x.size(0) index torch.randperm(batch_size).cuda() mixed_x lam * x (1 - lam) * x[index] y_a, y_b y, y[index] return mixed_x, y_a, y_b, lam # 在训练循环中 images, labels images.cuda(), labels.cuda() images, labels_a, labels_b, lam mixup_data(images, labels) outputs model(images) loss lam * criterion(outputs, labels_a) (1 - lam) * criterion(outputs, labels_b)alpha0.2是常用值越大混合越强。CutMix 的实现类似只是把混合方式换成区域粘贴。我一般先试 Mixup如果验证集精度提升不明显再试 CutMix。两者不要同时用会过度正则化。5.2 用 TTA 和模型集成做最终验证测试时增强TTA是对同一张测试图片做多次变换取预测结果的平均。常见变换包括水平翻转、不同裁剪比例、多尺度缩放。TTA 通常能提升 1 到 2 个百分点代价是推理时间成倍增加。实现方式def tta_predict(model, image, transforms_list): model.eval() preds [] with torch.no_grad(): for t in transforms_list: img t(image).unsqueeze(0).cuda() output model(img) preds.append(torch.softmax(output, dim1)) return torch.mean(torch.stack(preds), dim0)模型集成则是训练多个不同初始化的模型或者不同架构的模型取平均预测。水果 8 分类任务上ResNet-18 和 EfficientNet-B0 的集成通常能到 96% 以上。但集成会增加部署复杂度如果只是验证方法有效性TTA 就够了。5.3 用混淆矩阵和分类报告定位薄弱类别精度只是一个数字要知道模型在哪些类别上弱得看混淆矩阵。用 scikit-learn 可以快速生成from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt # 收集所有预测结果 all_preds [] all_labels [] model.eval() with torch.no_grad(): for images, labels in test_loader: images images.cuda() outputs model(images) _, predicted torch.max(outputs, 1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.numpy()) # 混淆矩阵 cm confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annotTrue, fmtd, xticklabelstrain_dataset.classes, yticklabelstrain_dataset.classes) plt.show() # 分类报告 print(classification_report(all_labels, all_preds, target_namestrain_dataset.classes))看混淆矩阵时重点关注对角线外的数字。如果葡萄和草莓互相误判多说明模型对细小颗粒状水果的纹理特征学得不够。这时候可以针对性增加这两类的样本或者在模型里加入注意力模块。5.4 一个具体技巧用类别权重处理样本不均衡水果数据集中苹果和香蕉的图片往往比菠萝和芒果多好几倍。样本不均衡会导致模型偏向多数类。除了重采样更简单的方法是在损失函数里给少数类更高权重# 计算类别权重 class_counts [len(os.listdir(ffruit8/train/{c})) for c in train_dataset.classes] total sum(class_counts) class_weights [total / (len(class_counts) * c) for c in class_counts] class_weights torch.FloatTensor(class_weights).cuda() criterion nn.CrossEntropyLoss(weightclass_weights)这样少数类的损失会被放大模型更关注它们。权重不要设得太大否则多数类欠拟合。我一般把最大权重控制在最小权重的 5 倍以内。我自己的习惯是每次拿到一个新的水果图像分类数据集先花半小时看数据把每个类别随机抽 20 张图拼成网格图肉眼过一遍。这一步能发现很多代码检查不出来的问题比如标签错标、图片重复、背景单一。模型训练只是最后一步前面的数据工作才是决定上限的关键。希望帮到你。本文还有配套的精品资源点击获取
