
简介苹果品种分类数据集是一份面向机器学习、计算机视觉等方向的图像数据集适合有监督学习与图像分类基础的研究者、算法工程师及农业科技从业者使用可用于解决苹果品种自动识别与分类问题。压缩包共1766个文件其中305张jpg与275张jpeg高清图片约580幅覆盖不同品种苹果的全貌、切面、横截面等多角度图像另有606个txt文件与580个xml文件分别记录品种标签、产地、风味等描述信息和标注框数据整体包大小64.01MB目录结构清晰便于主流框架直接加载。目前已有205人学习/浏览该数据集在智能农业、食品质量检测、电商商品分类等场景具有直接应用价值。读者可基于这些图片和标签开展卷积神经网络CNN等模型的训练与调优并利用不同背景、视角的样本提升模型泛化能力为后续部署自动化识别系统提供可靠数据支撑。1. 苹果品种分类数据集580 张图能把识别模型训到什么程度做图像分类的人手里最不缺的就是公共数据集但真到自己要落地一个农业识别任务时会发现CIFAR 和 ImageNet 离实际场景差得太远。这份苹果品种分类数据集只有 580 张 JPEG 图片文件按数字编号没有预划分的 train/val 目录标签信息需要自己从文件名或外部映射整理。单看数量不大但它的价值在于覆盖了多个品种的完整外观、切面和横截面并且背景不统一反而更接近真实拍摄环境。适合两类人一是刚入门 CNN 分类、想找一个小而完整的数据集跑通全流程的开发者二是做智能农业或农产品品控、需要一份能快速验证精度的私有风格数据。用它配合迁移学习单卡 CPU 也能在十几分钟内看到可用的收敛结果。2. 先摸清数据集的底细文件结构、标签与解压中的坑2.1 数据集的文件构成与标签推断解压后是散落的 JPEG 文件没有子目录文件名是纯数字编号格式类似203.jpeg、530.jpeg。这种命名方式说明数据集作者做了匿名化处理但文件名本身不携带品种信息所以使用前必须自己建立标签映射。建立映射有三种常见做法从图片内容人工标注把每张图打开看一遍记录下来品种名适合品种数量少比如 3-5 类的场景。从外部元数据匹配如果数据集的原始来源有描述文件或表格按文件名编号去关联。按图片特征聚类后人工复核先用无监督聚类粗分再人工确认适合类别多但特征差异明显的情况。我一般会先写一个脚本把所有图片的尺寸、通道数、文件大小统计出来排除损坏文件后再决定标签策略。# 统计每张图片的基本信息输出为 CSV 便于后续处理 python3 -c from PIL import Image import os, csv folder apple_dataset rows [] for f in sorted(os.listdir(folder)): if not f.lower().endswith(.jpg): continue path os.path.join(folder, f) img Image.open(path) rows.append([f, img.size[0], img.size[1], img.mode, os.path.getsize(path)]) with open(image_manifest.csv, w, newline) as fp: writer csv.writer(fp) writer.writerow([filename, width, height, mode, bytes]) writer.writerows(rows) print(f总图片数: {len(rows)}) 这段脚本用 PIL 逐个读取图片元数据不加载完整像素数据所以速度很快。输出清单后第一件事是检查是否有 RGBA 模式的图有的话后面转 tensor 时会报通道数不匹配另外如果发现尺寸差异悬殊比如从 200x200 到 2000x2000就需要在 DataLoader 里统一做 resize不能直接进模型。2.2 zip 解压与伪加密问题资源以 zip 形式分发这本身不复杂但实际下载解压时会出现两个典型问题。第一个是文件名编码如果 zip 在 Windows 下用非 UTF-8 编码压缩Linux 下解压后中文文件名会乱码这个数据集的数字命名不存在这个问题可以跳过。第二个是「伪加密」有些 zip 虽然没有真实密码但文件头里加密标志位被置位解压工具会无脑要求输密码。# 先用 unzip -l 查看压缩包状态 unzip -l apple_dataset.zip | head -20 # 如果报错提示需要密码尝试用 7z 强制忽略加密标志解压 7z x apple_dataset.zip -y # 如果 7z 仍然提示密码检查是否是伪加密 zipinfo -v apple_dataset.zip | grep -i encryptunzip -l只列出文件清单不解压用来确认压缩包内是否有目录层级。7z x在遇到伪加密时的表现比 unzip 宽容多数情况下能直接解出内容。zipinfo是排查工具看加密位是否真的设置。这类问题多出现在从网盘或非官方渠道转存的文件上先排查再动手解压能省不少时间。2.3 划分训练集与验证集时的顺序陷阱580 张图如果按文件名顺序前 80% 做训练、后 20% 做验证是一个常见的翻车操作。因为文件编号和拍摄顺序强相关同一个品种可能连续出现在一段编号区间内这样划分会导致验证集里出现训练集没见过的品种模型在训练集上 acc 很高、验证集上表现不稳定。正确做法是分层随机划分并且按类别保证比例一致。import os import random import shutil from collections import defaultdict SOURCE apple_dataset TRAIN_DIR dataset_split/train VAL_DIR dataset_split/val # 假设 label_map 是 {文件名: 品种名} 的映射字典 label_map {...} # 需要人工标注或从外部来源获取 # 按类别分组 groups defaultdict(list) for fname, label in label_map.items(): if os.path.exists(os.path.join(SOURCE, fname)): groups[label].append(fname) random.seed(42) for label, files in groups.items(): # 每个类别内部按 8:2 随机划分 random.shuffle(files) val_count max(1, int(len(files) * 0.2)) val_files files[:val_count] train_files files[val_count:] for f in train_files: os.makedirs(os.path.join(TRAIN_DIR, label), exist_okTrue) shutil.copy(os.path.join(SOURCE, f), os.path.join(TRAIN_DIR, label, f)) for f in val_files: os.makedirs(os.path.join(VAL_DIR, label), exist_okTrue) shutil.copy(os.path.join(SOURCE, f), os.path.join(VAL_DIR, label, f)) print(划分完成请检查 train/val 的类别分布是否与原始数据一致)代码里的 seed 必须固定否则每次运行划分结果不同实验结果无法复现。max(1, ...)防止某个类别样本数太少导致验证集为空如果某类只有 3-5 张图更好的做法是直接用 K 折交叉验证而不是固定划分。3. 用 PyTorch 跑通苹果品种分类迁移学习与完整训练流程3.1 为什么 580 张图必须用迁移学习580 张图片从头训练一个 ResNet 或 VGG 级别的网络几乎是必挂的。原因在于 CNN 的低层特征边缘、纹理、颜色渐变虽然通用但高层特征需要大量数据才能学出有判别性的组合模式数据量不够模型会直接记住训练集验证集 acc 在 60-70% 徘徊然后开始下降。迁移学习的思路是在 ImageNet 上预训练好的模型已经具备极强的通用视觉特征提取能力我们只替换最后的全连接分类头在苹果数据集上做微调。这样即使只有几百张图也能在验证集上拿到 90% 以上的准确率因为模型只需要学「苹果品种之间差异」这一层映射而不是从头学「什么是苹果」。### 3.2 数据加载与预处理配置 python import torch from torchvision import datasets, transforms, models from torch.utils.data import DataLoader # 训练集增强策略轻量级避免过拟合 train_transforms transforms.Compose([ # 统一缩放到 224x224 适配 ResNet 输入 transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale(0.8, 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_transforms transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset datasets.ImageFolder(dataset_split/train, transformtrain_transforms) val_dataset datasets.ImageFolder(dataset_split/val, transformval_transforms) 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(f类别: {train_dataset.classes}) print(f训练集: {len(train_dataset)} 张, 验证集: {len(val_dataset)} 张)RandomResizedCrop做了随机裁剪模拟不同拍摄距离和构图对大块头苹果这种主体居中的图片效果很好。ColorJitter的三个参数控制在 0.2因为苹果颜色本身就是分类的重要特征调太大反而会抹掉红富士和嘎啦的色差。验证集不用增强保证评估指标可对比。参数选择上batch_size32在 8GB 显存下配合 ResNet18 没问题显存不够就降到 16 或 8。num_workers4对 Windows 用户可能出现 DataLoader 报错改为num_workers0就能解决。3.3 模型构建与训练主循环import torch.nn as nn import torch.optim as optim from torchvision import models # 加载预训练 ResNet18使用最稳定的权重版本 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 替换最后一层全连接 num_features model.fc.in_features num_classes len(train_dataset.classes) model.fc nn.Linear(num_features, num_classes) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() # 只对 fc 层使用较大学习率特征提取层用较小学习率 optimizer optim.SGD([ {params: [p for name, p in model.named_parameters() if fc not in name], lr: 1e-4}, {params: model.fc.parameters(), lr: 1e-3} ], momentum0.9, weight_decay1e-4) # 学习率调度每 5 个 epoch 衰减一半 scheduler optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.5)训练主循环epochs 20 best_acc 0.0 for epoch in range(epochs): # 训练阶段 model.train() running_loss 0.0 correct 0 total 0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * images.size(0) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() train_acc 100.0 * correct / total train_loss running_loss / total # 验证阶段 model.eval() val_correct 0 val_total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) val_total labels.size(0) val_correct (predicted labels).sum().item() val_acc 100.0 * val_correct / val_total print(fEpoch {epoch1}/{epochs} | fTrain Loss: {train_loss:.4f} | Train Acc: {train_acc:.2f}% | fVal Acc: {val_acc:.2f}%) # 保存最佳模型 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_apple_model.pth) scheduler.step() print(f训练完成最佳验证准确率: {best_acc:.2f}%)这里用了分组学习率预训练层的学习率是 1e-4新加的 fc 层是 1e-3这个差异很关键。因为 fc 层是从零开始需要快一点更新而预训练层已有合理权重用大学习率会把学好的特征破坏掉。torch.no_grad()是验证阶段必写的否则梯度图会累积显存爆掉是小事验证时间会慢好几倍。3.4 显存不足与 CPU 训练的替代方案如果机器只有 CPU没有 NVIDIA 显卡上面的代码也能跑只是时间会拉长。ResNet18 在 CPU 上跑一个 epoch 的 464 张训练图batch32大约需要 3-5 分钟20 个 epoch 大概一个多小时。能接受的话直接用torch.device(cpu)即可。显存不够 4GB 时除了降低 batch_size还可以把 ResNet18 换成 ResNet18 的轻量变体——torchvision.models.mobilenet_v3_small参数量约为 ResNet18 的 1/10精度损失在 2-4% 之间但这个数据集的品种差异比较明显实际差距可能更小。4. 538 张图怎么喂出高精度模型数据增强与类别均衡4.1 580 张图的增强策略选择训练集和验证集相加是 580 张其中训练集约 464 张、验证集约 116 张。这个量级做数据增强是必需的但增强不是越猛越好。曾经在一个苹果数据集上把RandomResizedCrop的scale下限设到 0.3结果模型把「局部表皮颜色」当成了主要判据对整果图片的泛化反而变差。我个人推荐的增强组合按优先级排序增强手段参数建议理由随机水平翻转p0.5苹果没有左右语义差异零成本扩倍随机裁剪缩放scale(0.7, 1.0)模拟不同拍摄距离亮度/对比度扰动strength0.2覆盖不同光照环境小角度旋转±15°模拟摆放角度偏差色彩饱和度扰动0.8~1.2覆盖不同成熟度颜色差异不建议用高斯噪声或模糊苹果表皮本身有纹理加噪声会让模型去学「纹理受损」的错误特征。4.2 类别不均衡的处理如果 580 张图被分成 8-10 个品种大概率每个类别只有 50-70 张。类别之间不均衡会带来一个问题模型倾向于把验证样本预测为样本数多的类别因为整体准确率能蒙对更多。处理方式有三种按优先级排加权采样器WeightedRandomSampler每个 batch 里按类别反比采样让小类别有更多出场机会。类别加权损失函数在 CrossEntropyLoss 里传入weight参数。Oversampling对小类别直接复制图片效果等同于加权采样但更占内存。from torch.utils.data import WeightedRandomSampler import numpy as np # 统计每个类别的样本数 labels [label for _, label in train_dataset.samples] class_counts np.bincount(labels) total_count len(labels) # 每个样本的权重 样本总数 / (类别数 * 该类别样本数) weights [] for label in labels: weights.append(total_count / (len(class_counts) * class_counts[label])) # 采样器会自动补足小类别的出现频率 sampler WeightedRandomSampler( weightsweights, num_sampleslen(labels), replacementTrue ) train_loader DataLoader( train_dataset, batch_size32, samplersampler, # 使用 sampler 时不能设置 shuffleTrue num_workers4, pin_memoryTrue )注意sampler和shuffleTrue互斥用采样器时shuffle参数会报错。replacementTrue意味着同一个样本可以在一个 epoch 中被多次采样这是有放回采样的标准做法。判断是否真的需要类平衡先看每个类别的最小样本数。如果最小类别也有 30 张以上且类别差异明显比如红富士 vs 青苹果直接用原始分布训练通常不会有问题如果某个类别只有不到 10 张才需要上采样器。4.3 增强后数据集的可视化验证增强设置的玄学成分很大同样的参数换一个数据集结果完全不同。所以增强配置完成后我强烈建议先做一次可视化检查确认增强后的图片没有出现离谱变形。import matplotlib.pyplot as plt import numpy as np import torchvision.utils as vutils # 取一个 batch 并应用增强 sample_batch, sample_labels next(iter(train_loader)) # 反归一化以便可视化 mean torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1) std torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1) denorm sample_batch * std mean # 用 make_grid 拼成瓷砖图 grid vutils.make_grid(denorm, nrow8, padding2) grid_np grid.permute(1, 2, 0).numpy() grid_np np.clip(grid_np, 0, 1) plt.figure(figsize(16, 8)) plt.imshow(grid_np) plt.axis(off) plt.title(Augmented Training Samples) plt.savefig(augment_check.png, dpi100)如果在保存的augment_check.png里看到主体被切掉一半、颜色偏到紫色、或者图片模糊到看不出是苹果就需要回调增强参数。这一步值得做后续训练出的模型如果精度不如预期排除增强问题能省半天排查时间。5. 训练实战中的六个典型坑从标签错位到精度虚高5.1 标签映射错位导致准确率虚高现象训练时 loss 降得很快验证集准确率在 95% 以上但把模型拿到实拍图片上一测结果完全不对。原因标签文件与图片文件的对应关系错位了。比如标注文件里第 10 行是红富士但脚本读入后从第 9 行开始配对导致所有标签整体偏移一个位置。模型学到的是「第 n 张图对应第 n1 个标签」的错误映射但在验证集上同样错位所以指标是假的。解决训练前随机抽 10 张图做人工对比——打开图片、看模型预测的标签、和真实标签核对。别省这一步后来我用脚本自动检查了标签分布发现有个类别标签重复出现次数异常才定位到映射表有重复项。# 快速检查标签映射是否合理统计每个类别的图片数量范围 from collections import Counter label_counter Counter([label for _, label in train_dataset.samples]) for cls, count in sorted(label_counter.items()): print(f{cls}: {count} 张) # 如果发现某个类别只有 1-2 张而其他类别有 70-80 张大概率映射有问题5.2 预训练权重下载失败现象执行models.resnet18(weights...)时卡住不动或者报URLError、SSLError。原因国内网络访问 PyTorch 官方权重下载地址不稳定。解决手动下载预训练权重到本地然后通过torch.load加载。下载 ResNet18 的权重文件约 44.7MB放到项目目录下model models.resnet18(weightsNone) state_dict torch.load(resnet18-f37072fd.pth, map_locationcpu) # 注意这里不能直接 load_state_dict因为有 fc 层的形状不匹配 # 正确的做法是先移除 fc 层相关键值 state_dict.pop(fc.weight, None) state_dict.pop(fc.bias, None) model.load_state_dict(state_dict, strictFalse) # 然后替换 fc 层 model.fc nn.Linear(model.fc.in_features, num_classes)strictFalse允许部分键不匹配配合手动弹出 fc 层的键确保预训练权重正确加载。5.3 验证集准确率高于训练集现象每个 epoch 打印出的 Val Acc 比 Train Acc 高出 5-8 个百分点看起来不合常理。原因训练集做了数据增强模型被迫看各种裁剪、翻转、颜色扰动后的图片学习难度更大验证集只做 resize图片干净所以验证集准确率高是正常现象。尤其在训练初期增强越强这个差距越明显。解决这不是 bug不用修。但如果训练结束前 Val Acc 始终高于 Train Acc 超过 15 个百分点说明增强强度过大模型没有见过足够多「干净」的训练样本需要调低增强参数。5.4 模型在验证集上 acc 很高但对新图预测失败现象验证集准确率 92%但在百度随便搜一张苹果图片喂进模型预测的品种是错的。原因数据集的拍摄环境、背景、设备比较统一模型学到的特征和真实世界分布有偏差。验证集的图片来自同一批数据源风格一致所以指标好看测试图片来自不同设备、光照、角度模型没见过这种分布准确率自然下降。解决这是小数据集的通病缓解手段是收集外部图片做二次验证。找 20-30 张跟数据集完全无关的苹果图片不同来源、不同分辨率手动标注后跑一个测试脚本用这个外部准确率作为真实的泛化指标。5.5 训练过程中 loss 变成 NaN现象训练到第 3-5 个 epochloss 突然变成nan准确率也跟着变成 0%。原因最常见的是学习率过大导致梯度爆炸或者数据里有异常的像素值比如损坏的 JPEG 解码出来是全黑或全白图经过归一化后产生极端值。解决先把学习率从 1e-3 降到 1e-4 重跑。如果仍然 NaN就要检查数据增强后的图片是不是出现了全黑或全白的情况——ColorJitter在极端参数下可能把图片整体变黑进模型后梯度爆炸。# 在训练循环里加一层梯度裁剪作为保险 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)加上clip_grad_norm_后即使梯度异常也会被限制在合理范围内至少能保住训练不中断。5.6 微调时特征提取层被冻结导致准确率上限低现象只训练 fc 层特征提取层用预训练权重保持不变验证集准确率最高只能到 75-80%再怎么调都上不去。原因苹果数据集跟 ImageNet 的自然图像域差异不小。工业场景的背景、角度、光照和 ImageNet 里的那些自然照片差距较大完全冻结特征提取层模型学不到苹果特有的视觉特征比如果蒂的形状、表面的光泽分布。解决不要冻结特征提取层用低学习率1e-4 或更低让所有层都参与微调。这是上面 3.3 节代码里分组学习率的意义所在。或者采用渐进式解冻先固定所有层训练 fc 层 5 个 epoch再解冻最后两个残差块继续训练。6. 把模型接到真实场景混淆矩阵分析与 Grad-CAM 可视化6.1 用混淆矩阵定位易混品种对模型训练完成后光看准确率是不够的。验证集上 90% 的准确率可能把所有错误都集中在两个品种之间比如黄元帅和青香蕉都是黄绿色表皮模型容易混淆。用混淆矩阵把错误模式可视化出来才能知道后续怎么优化。import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labelsval_dataset.classes) fig, ax plt.subplots(figsize(10, 8)) disp.plot(axax, cmapBlues, colorbarFalse) plt.xticks(rotation45, haright) plt.title(Confusion Matrix - Apple Variety Classification) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150)观察混淆矩阵里非对角线上的高亮格子如果某两个品种互相误判说明它们在视觉特征上确实接近。对于这类易混品种对有两个优化方向一是给它们各多采集一些图片微调模型二是在业务使用时不要求精确到品种而是输出 Top-2 预测结果让用户二选一确认。6.2 Grad-CAM看模型到底在看苹果的哪个部位准确率只能说明模型「做对了」但不能说明「看对了」。用 Grad-CAM 生成热力图能直观看到模型分类时重点关注图像中的哪些像素区域。这一步对排查模型「作弊」很有用比如模型靠背景中的标签纸判断品种而不是苹果本身的特征热力图会集中在图片角落而不是苹果主体。import cv2 import torch import numpy as np from torchvision import transforms def grad_cam(model, image_tensor, target_layer, class_idxNone): 简易 Grad-CAM通过目标层梯度加权激活图生成热力图 gradients [] activations [] def backward_hook(module, grad_input, grad_output): gradients.append(grad_output[0]) def forward_hook(module, input, output): activations.append(output) handle_forward target_layer.register_forward_hook(forward_hook) handle_backward target_layer.register_backward_hook(backward_hook) model.eval() image_tensor image_tensor.unsqueeze(0).to(device) output model(image_tensor) if class_idx is None: class_idx torch.argmax(output).item() model.zero_grad() one_hot torch.zeros_like(output) one_hot[0, class_idx] 1 output.backward(gradientone_hot) # 梯度全局平均池化作为权重 weights torch.mean(gradients[0], dim(2, 3), keepdimTrue) cam torch.relu(torch.sum(weights * activations[0], dim1, keepdimTrue)) # 上采样到原图尺寸 cam torch.nn.functional.interpolate( cam, size(224, 224), modebilinear, align_cornersFalse ) cam cam.squeeze().cpu().numpy() cam (cam - cam.min()) / (cam.max() - cam.min() 1e-8) handle_forward.remove() handle_backward.remove() return cam用register_forward_hook和register_backward_hook分别捕获目标层的输出特征图和梯度。backward_hook拿到的grad_output[0]就是该层输出的梯度forward_hook拿到的output是激活值。权重是梯度的空间平均代表每个通道对预测结果的重要程度。最后用 ReLU 把负贡献滤掉只保留正向激活区域。target_layer通常选模型最后一个卷积层对 ResNet18 来说是model.layer4[-1]。最后一层卷积的特征图分辨率是 7x7上采样到 224x224 后叠加到原图上就能看到模型关注的核心区域。如果热力图集中在苹果的果脐或者某个品种独特的斑点分布上说明模型学到了有意义的特征如果热力图分散在整个背景上就要考虑模型偷懒了。6.3 小数据集的最后一步保留一份固定的验证切分做小数据集实验最容易犯的错误是反复更换随机种子、调整 train/val 划分最后模型始终在「自己的」验证集上表现好但不知道真实泛化水平。我的习惯是把最终的划分结果固化成一个split_index.json保存下来scripts 里固定读取这份 JSON不再每次运行时重新洗牌。import json split_info { seed: 42, train_files: [f for f, _ in train_dataset.samples], val_files: [f for f, _ in val_dataset.samples], class_to_idx: train_dataset.class_to_idx } with open(split_index.json, w) as fp: json.dump(split_info, fp, indent2, ensure_asciiFalse)这样做的实际价值是后续改模型结构、换增强策略、调学习率时验证集始终是同一份数据不同版本的结果才可比。几个月后回过头来对比实验结果也不用猜测当时用的哪次划分。从那以后我每次训练小数据集都强制走这一步——先固化划分再谈调参否则实验结果就是一锅粥对自己的判断都失去了信心。这份数据集虽然不是大而全的商业级资源但拿来验证完整的图像分类流程或者当作物检测模型的分类分支预训练数据都是够用的。希望帮到你。本文还有配套的精品资源点击获取