多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

花类识别数据集实战:解压校验、标签处理与PyTorch图像分类训练

花类识别数据集实战:解压校验、标签处理与PyTorch图像分类训练 简介花类识别数据集.zip 是一份面向图像分类入门与植物识别实践的中型数据集适用于计算机视觉初学者、高校相关课程设计以及轻量级识别模型验证。内容涵盖洋甘菊、郁金香、玫瑰、向日葵、蒲公英五个常见花类共4242张花朵照片每个类别约800张类别较为均衡。图片分辨率约320x240像素未强制统一缩放保留原始纵横比为图像预处理、数据增强和特征提取提供了真实多样的训练环境。压缩包文件总数为2000个文件类型明细中jpg图像占比最高达8646个同时附带4个Python脚本、2个pyc及1个txt说明文件可辅助完成数据加载、标签读取和模型训练代码组织。整体包体约449.82MB已有609人学习浏览。借助该数据集可免去自行爬取、筛选和整理花朵图像的繁琐过程直接获得按类别分好的图像库与基础脚本适合快速搭建花卉分类Demo、完成课堂作业或进行基准测试实验。1. 花类识别数据集.zip解压之前先想清楚你要做的是分类还是检测“花类识别数据集.zip”这个名字第一眼看就是个普通压缩包但凡是做过视觉项目的人都知道麻烦从来不在下载那一下而在解压之后。我最早拿到类似的一份花类识别数据集时以为解压就能直接开训结果光整理标签、清洗损坏图片、核对目录结构就耗掉一个晚上。这类数据集解决的核心问题很明确把爬图、去重、清洗、打标签这几周脏活提前做完给你一份按类别归档、带标注的训练素材拿来就能喂给深度学习模型。适合正在做植物识别工具、农业视觉项目或者想快速验证分类网络的从业者和学生。不过它对新手有一点门槛zip 包内部的组织方式直接决定你后续用哪套代码理解这一点比急着解压重要得多。2. 花类识别数据集.zip 里有什么目录结构、标注格式与训练路线选择2.1 解压前先看 zip 列表目录结构决定加载代码的写法拿到压缩包的第一件事不是双击解压而是查看压缩包内部的目录结构。花类识别数据集最常见的组织方式有两类。第一类是“按类别分文件夹”根目录下每个文件夹是一种花的英文名或拼音文件夹里直接放图片这种结构可以被 torchvision.datasets.ImageFolder 直接消费你几乎不用写任何数据解析代码。第二类是“图片平铺 标签映射表”所有图片堆在同一个 images/ 目录下旁边配一个 CSV、JSON 或 txt 文件每一行记录文件名和类别 ID 的对应关系这种结构需要自己写 Dataset 类解析映射表代码量会多出几十行。怎么快速分辨这两类我习惯在解压前先执行 unzip -l 看压缩包的文件列表。如果列表里全是花名目录开头的图片路径比如 daisy/001.jpg、rose/003.jpg 这种前缀那就是文件夹式如果列表里图片路径没有类别前缀、或者混着一个 labels.csv / annotations.json那就是平铺式。这一步只需要十几秒但能帮你提前选择加载方案避免解压后对着几百个文件无从下手。# 列出 zip 内前 30 条记录观察路径前缀是否带类别名 unzip -l 花类识别数据集.zip | head -30 # 如果列表里出现 csv/json/txt 元数据文件重点看它的命名 unzip -l 花类识别数据集.zip | grep -E \.(csv|json|txt)$关于子目录结构还有一个常见的坑有的数据集把所有图片放进 train/ 和 test/ 两个大目录每个大目录下面又按类分目录有的则是 train/ 和 validation/ 分开图片按类别分散。如果数据集本身自带了划分你用验证集调参、测试集做最终评估时就要克制别提前把 test 集消耗在模型选择上如果内部没有划分就得按第 3.3 节的方式自己补一次分层抽样。目录结构不仅是路径问题它在很大程度上决定你后续写数据加载代码的成本。2.2 标签文件是黑匣子先确认标注内容再选训练模型“标签”这个词在花类识别数据集里可能指好几种东西。最常见的是文件夹名即标签类别信息挂在文件名上其次是 CSV 文件里 fileName 和 label 两个字段再复杂一点的是 JSON 里每个样本带 id、category_id 甚至 bbox 字段。我个人强烈建议先把标签文件打开看前几行而不是用代码去猜它的 schema因为 CSV 列的顺序、分隔符、表头是否存在这些细节会让不少现成脚本直接跑飞。# 查看 CSV 标签文件的前 5 行确认列名和分隔符 head -5 花类识别数据集/labels.csv如果发现标签文件里存在 bbox 列这份数据集就具备做目标检测的潜力如果只有纯 class label那只能做图像分类。花类识别这个任务有它的特殊性花小而密、遮挡多、一朵花在画面里可能只占几个百分点所以如果有边界框标注检测路线通常好过分类路线。但和 COCO 2017 那种十几万张的检测数据集相比花类数据集里能有人工标注的 bbox 非常稀缺——因为检测标注的人工成本比分类标注高出一个量级。如果你决定走检测路线后续用 YOLOv8 训练自己的数据集时还需要把标签转成 YOLO 格式的 txt 文件这会多一道工序。因此拿到压缩包先扫一眼标签文件等于在项目开始前就明确了技术路线的天花板。2.3 类别数与每类样本量先在心里给“能不能训出来”打个底花类识别数据集.zip 里通常包含的类别数在 5 到 100 之间每类样本数从几十张到几百张不等。这个规模决定你后续采用什么训练策略。和鸢尾花数据集那种 150 个样本、3 个类别的小型经典数据集不同花类识别数据集的类别数和样本量跨度更大你的策略必须跟着规模走。每类样本量推荐 backbone训练策略预期效果3050 张ResNet18迁移学习冻结全部特征层可用但脆弱单类误差大50200 张ResNet18 / ResNet50冻结 backbone 微调最后阶段较稳定能上线试运行200 张以上ResNet50 / EfficientNet解冻更多层做整体微调特征泛化能力较好我自己判断数据集可用的底线是最小类别样本数不低于 50。低于 50模型在这个类别上的输出基本靠猜还会因为类别数增加而拉低其他类别的置信度。如果某类只有三四十张我建议直接把它从任务中移除不要觉得浪费——留着它只是在制造一个注定学不好的类别。还有一类常见情况是数据集内有大量重复或近似重复的图片。花类摄影数据集经常出现同一朵花在不同距离、不同角度下的连拍这些图片在特征空间里距离极近。如果原样喂进训练集模型会反复看到近似样本容易对特定那朵花过拟合而不是学到这类花的普遍特征。处理重复样本的简单做法是每类内部做感知哈希去重用 imagehash 库算每张图的 ahash 或 dhash汉明距离小于 5 的两张图视为重复删除其中一张。这个清洗动作对训练稳定性帮助明显别嫌烦训练时长异常偏高的元凶之一往往是这些重复样本。3. 把花类识别数据集.zip 跑起来解压校验、标签统计与数据划分3.1 解压与完整性校验下载中断和半截文件是训练事故的第一来源zip 包从网盘、聊天工具或邮件附件下载时文件完整性很难保证。断点续传失败、网盘中转服务器截断、压缩包在拷贝过程中丢失尾部数据这些情况光看文件后缀根本看不出来要解压到一半才发现报错。更糟的情况是解压时只报个别文件损坏你忽略之后训练到一半程序崩掉。我的习惯是先用 md5sum 校验再解压最后用 find 统计文件数量三步走完才确信数据可用。# 第一步校验压缩包完整性输出的 md5 值要和源文件比对 md5sum 花类识别数据集.zip # 第二步解压到目标目录用 -q 减少输出-d 指定目录 unzip -q 花类识别数据集.zip -d flower_dataset/ # 第三步统计图片文件总数确认没有丢失关键目录 find flower_dataset -type f \( -name *.jpg -o -name *.jpeg -o -name *.png \) | wc -l # 第四步按类别目录统计图片数量发现空目录或异常目录 for dir in flower_dataset/*/; do count$(find $dir -type f | wc -l) echo $(basename $dir): $count donemd5sum 虽然已经不适合做安全校验但用来应对压缩包在传输中丢字节这种普通问题完全够用。第四步的循环输出要仔细看如果某个类别目录的计数明显小于其他类可能源数据集本来如此也可能是解压过程中该目录出了问题。这时候先从 zip 包里单独提取这个目录试试比如unzip 花类识别数据集.zip daisy/* -d /tmp/retry单独能解出来说明是之前解压的断点问题单独也解不出来说明源压缩包内部就缺文件只能找数据提供方补。3.2 标签分布统计与脏数据清洗一份数据集能不能用看这一步解压完成后训练前一定要写脚本统计类别分布并检查图片是否可解码。说实话公开分享的花类识别数据集整理时的严谨程度差别很大有的里面混着损坏图片、非图片文件、甚至标签张冠李戴。如果你直接开训训练过程很可能在前几个 epoch 就因一个损坏文件中断或者更隐蔽——模型在某种标签错误上安静地拟合出偏差前期 metrics 看着漂亮上线才暴露问题。import os from collections import Counter from PIL import Image dataset_root flower_dataset class_counter Counter() corrupted_files [] for cls_name in os.listdir(dataset_root): cls_dir os.path.join(dataset_root, cls_name) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): fpath os.path.join(cls_dir, fname) try: with Image.open(fpath) as img: img.verify() # 只校验文件头不加载像素速度快 class_counter[cls_name] 1 except Exception: corrupted_files.append(fpath) print(类别分布按样本数从高到低) for cls_name, count in class_counter.most_common(): print(f {cls_name}: {count}) print(f\n损坏文件数: {len(corrupted_files)}) for f in corrupted_files[:20]: print(f {f})这段脚本里 img.verify() 是效率关键。它只读图片头部信息验证完整性不加载像素数据所以即使数据集有上万张图跑完也就几十秒。verify 通过不代表图片一定完全正常但绝大多数损坏都能在这一步暴露。对于损坏文件删除前先确认原始 zip 里同样位置是否也是坏文件如果原始包没问题说明解压过程出错重新单独解压如果原始包就是坏的只能剔除该样本并在后续统计中视为不存在。统计完类别分布后一个判断标准可以立刻用上最小类别样本数低于 50训练时该类别基本靠运气低于 20建议直接从任务中移除。不要觉得移除类别浪费了数据留下它只会让模型在这个类别上输出随机猜测还会因为类别数增多而稀释其他类别的概率表现。3.3 划分训练集、验证集、测试集分层抽样与固定随机种子数据划分是后续一切实验的基准。我的默认做法是 7:2:1 的比例按类别分层抽样每个类内部的样本先随机打乱再按比例切。为什么要分层因为花卉数据集里类别的先验分布差异很大如果直接全量随机打乱再切很可能出现某些小类在训练集里一个样本都没有的情况那么验证集和测试集的指标就完全失真。以下代码假设你用的是文件夹式结构输出一个划分后的目录树。import os import random import shutil random.seed(42) # 固定种子保证每次运行划分结果一致 dataset_root flower_dataset output_root flower_split train_ratio, val_ratio 0.7, 0.2 for cls_name in os.listdir(dataset_root): cls_dir os.path.join(dataset_root, cls_name) if not os.path.isdir(cls_dir): continue files [f for f in os.listdir(cls_dir) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(files) n_train int(len(files) * train_ratio) n_val int(len(files) * val_ratio) for split_name, part_files in zip( [train, val, test], [files[:n_train], files[n_train:n_train n_val], files[n_train n_val:]] ): out_dir os.path.join(output_root, split_name, cls_name) os.makedirs(out_dir, exist_okTrue) for fname in part_files: shutil.copy2( os.path.join(cls_dir, fname), os.path.join(out_dir, fname) ) print(划分完成输出目录, output_root)这里有个关键细节一定不要用 move要用 copy2。原始数据是你唯一没有风险的资产一旦划分脚本有 bug或者你想换一种切分比例保留原始数据就能立刻重来。以前贪省事把文件直接移动了后来想补几张图片回来发现原始目录已经乱七八糟这就是没留好后悔药的结果。另外random.seed(42) 必须在每次运行脚本前重新设置如果你在脚本里调用了其他随机库、或者换了 Python 版本划分结果都可能不同。严谨一点的做法是把划分后的清单保存为一个 CSV记录 filename、split、label 三列这样即使目录被重建也能按清单精确恢复。4. 用 PyTorch 在花类识别数据集上训练分类模型数据加载与关键参数4.1 数据加载器与归一化参数ImageFolder 够用但增强别乱加数据划分完成后就可以进入训练代码了。文件夹式结构的数据集torchvision.datasets.ImageFolder 是性价比最高的加载方式它自动把子目录名解析为类别索引返回 (image_tensor, label) 对。花类图片大多是自然光下的植物照片与 ImageNet 预训练模型的输入分布接近因此直接用 ImageNet 的均值和标准差做归一化不要自己重新统计数据集的均值方差——换成自算的统计值会让预训练权重失效迁移学习的效果反而变差这是我踩过多次的教训。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transforms transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees15), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transforms 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]) ]) train_dataset datasets.ImageFolder(flower_split/train, transformtrain_transforms) val_dataset datasets.ImageFolder(flower_split/val, transformval_transforms) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue) print(类别映射:, train_dataset.class_to_idx) print(训练集样本数:, len(train_dataset))代码里训练集和验证集用了完全不同的变换策略。训练集需要 RandomHorizontalFlip 和 RandomRotation 来增强多样性、降低过拟合验证集只能用 Resize CenterCrop因为验证集的作用是模拟真实推理时的输入分布任何随机变换都会让验证指标抖动导致你无法判断模型是否真的变好了。Resize((256, 256)) 后 CenterCrop(224) 的组合是迁移学习的常规操作先放大再裁剪能保留中心区域的细节这个 256 到 224 的差距也给空间变换留了一点余地。如果你的 backbone 是 ViT输入尺寸通常是 224 或 384Resize 的尺寸也要对应调整不能直接抄这个参数。注意Windows 上 num_workers 大于 0 时训练代码必须放在if __name__ __main__:块里否则会触发 DataLoader worker 重复初始化的 RuntimeError。Linux 上没有这个限制。4.2 ResNet 微调冻结策略、优化器选择与学习率设置花类数据集的样本量从几百到几千不等从头训练一个深度网络基本不可行所以预训练权重微调是主流。微调时不是所有层都需要梯度常见的策略是先冻结 backbone 只训练最后的全连接分类头跑若干轮让分类头适配新数据后再解冻最后一个残差阶段继续微调。这样做的合理性在于冻结的层保留了 ImageNet 上学到的通用视觉特征解冻的深层特征则负责学习花类特有的纹理和形状。直接从一开始就解冻全部层小数据集很容易把预训练特征“冲毁”。import torch.nn as nn from torchvision import models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 第一阶段冻结全部参数只训练新加的分类头 for param in model.parameters(): param.requires_grad False num_features model.fc.in_features num_classes len(train_dataset.classes) model.fc nn.Linear(num_features, num_classes) optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() # 第一阶段的训练循环省略跑 15-20 轮后进入第二阶段 # 第二阶段解冻 layer4 和新分类头用更小学习率微调 for name, param in model.named_parameters(): if layer4 in name or fc in name: param.requires_grad True optimizer torch.optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lr1e-4 )选择 ResNet18 还是 ResNet50取决于数据量。样本总量不到 3000ResNet18 就够了参数量小、过拟合风险低、训练速度快样本量超过 10000可以换 ResNet50特征表达能力更强。优化器方面我习惯用 Adam 开第一阶段收敛快、不用手动调动量第二阶段换成带动量的 SGD 有时更好但 Adam 也完全可以关键是学习率要降一个数量级。这里最重要的参数是学习率参数第一阶段分类头第二阶段解冻 layer4学习率1e-31e-4优化器AdamAdam 或带动量 SGDbatch size3232训练轮数15201015权重保存验证集最优验证集最优学习率跨度不要超过 10 倍否则底层参数被大步长更新预训练特征会被破坏后续训练会出现训练集 loss 下降但验证集完全不动的情况这种状态很难拉回来。4.3 训练循环与模型保存只看 loss 会骗你验证集才说了算训练循环本身不复杂真正容易出错的是模型保存策略和过拟合判断。我见过的初级选手最爱犯的错是把最后一个 epoch 的模型拿来部署但深度学习训练中最后一个 epoch 往往已经过拟合验证集准确率不是最高点。正确做法是在验证集上记录最优指标保存该指标对应的权重训练结束后加载这份权重做测试和部署。import torch device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) best_val_acc 0.0 best_model_path best_flower_model.pth for epoch in range(20): model.train() running_loss 0.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() model.eval() correct 0 total 0 val_loss 0.0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) val_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc correct / total print(fEpoch {epoch1}: train_loss{running_loss:.4f}, fval_loss{val_loss:.4f}, val_acc{val_acc:.4f}) if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model_path) print(f 保存最佳模型val_acc{val_acc:.4f})训练过程中如果发现 train_loss 一直降但 val_acc 在某个 epoch 之后开始波动向下这就是过拟合信号。第一步不是降学习率而是看数据增强是否太弱第二步才是引入早停或学习率调度。花类数据集的过拟合往往来自同一朵花的重复视角照片增强手段只能缓解不能根治更正确的做法是回到 2.3 节的去重环节把近似重复样本删掉再重新训练。另外torch.save(model.state_dict()) 保存的是权重字典加载时需要先构建同样结构的模型再 load_state_dict如果你的训练脚本和推理脚本的模型定义不一致加载会直接报 key 不匹配这种情况在换机器、换代码仓库时特别常见。5. 花类识别数据集使用避坑指南5 个典型翻车现场与排查方法5.1 解压报错invalid zip archive: could not find eocd现象unzip 解压时提示 could not find end-of-central-directory record或者 Python 的 zipfile 模块抛 BadZipFile 异常。文件名的后缀明明是 .zip但所有解压工具都拒绝工作。原因zip 文件的中央目录尾部记录End of Central Directory在文件写入流程里是最后生成的文件一旦被截断这条记录就会丢失。网盘下载中断、聊天工具传输时文件被压缩中转、U盘拷贝空间不足都可能造成这种结果。解决先执行 md5sum 比对原始文件的校验值如果对不上重新下载如果多次下载仍然报错尝试 7-Zip 的修复压缩包功能但修复成功率不高。最稳妥的方案是请数据提供方打一个分卷压缩包或者换传输渠道拿到手后先比对大小和校验值再解压。整个过程用一句口诀概括解压之前先校验校验不过别硬来。5.2 图片预览正常但训练时报解码失败现象文件资源管理器里能看到图片缩略图双击也能预览但训练脚本跑到某几张图时报 PIL.UnidentifiedImageError 或 cv2.error程序直接中断。原因资源管理器预览图片靠的是系统缩略图缓存或者读了文件扩展名就渲染占位图它不会严格按图像编码标准解码而 Pillow 和 OpenCV 会读取文件头的 magic bytes遇到非图片内容时直接拒绝。数据集中混入伪扩展名文件或者图片下载不全都会触发这类错误。解决把 3.2 节的 verify 脚本先跑一遍把所有报错文件单独列出来。接着用 file 命令看看这些文件真实的格式如果是文本或 HTML 内容改名的直接从数据集中移除并计数减一。需要强调的是不要试图让训练代码跳过这些文件——跳过会让 batch 里出现标签和图片数量不匹配的问题处理成本远高于删掉它们。5.3 标签错位导致验证结果忽高忽低现象训练时 loss 正常下降但验证集准确率停留在 60% 上下细看验证错误的样本发现模型预测的类别和图片内容完全不搭。原因数据集的标签文件有可能是数据集作者在图片整理完成之后重新生成的 CSV行顺序和文件名列表不一致也有可能是某个类别文件夹被合并或重命名之后标签文件没有同步更新。这是典型的“标签是黑匣子”情况不抽样检查根本发现不了。解决不要依赖“第几行对应第几张图”的顺序假设用文件名做显式关联。检查方法是从验证集里随机抽 20 张人工对照标签如果发现 3 张以上错位就要重建标签映射。重建时优先看文件名里的类别前缀或 ID比如文件名为 daisy_0042.jpg 说明它属于 daisy 类按这个规则重新生成标签比猜 CSV 的排列规则可靠得多。5.4 类别不平衡让 90% 准确率成了假象现象测试集整体准确率超过 90%看起来效果很好但按每个类别单独计算时发现样本数量大的几类准确率接近 100%样本量小的几类准确率只有 30% 到 50%。原因模型在训练时统计了类别的先验分布样本多的类别对损失的贡献大模型学会了把所有不确定样本猜成高频类别。整体准确率被高频类别主导掩盖了低频类别的真实表现。花类数据集里的玫瑰、向日葵这类常见花样本特别多而铃兰、鸢尾这类小众花样本很少这种偏斜很容易出现。解决先看类别分布最大类和最小类的样本比超过 10:1 就要处理。最简单的做法是在损失函数里加权重CrossEntropyLoss 的 weight 参数可以按类别样本数的倒数设置更稳妥的做法是大类做下采样、小类做重复采样让每个 batch 里各类样本数量接近。评估时以 macro-F1 或每个类别的召回率为准不再看整体准确率。5.5 数据泄露验证集和训练集里出现了同一朵花现象验证集准确率高达 98%但把模型放到真实拍摄的新图片上测试准确率掉到 80% 左右而且掉幅稳定在两位数。原因数据集里同一朵花的多个角度、多个距离照片被随机划分到不同集合。模型记住的是这些具体照片的纹理和颜色组合而不是花类本身的可泛化特征验证集对模型而言相当于开卷考试指标自然虚高。这种问题在花卉摄影数据集里特别普遍因为拍摄者通常会对同一株花连拍好几张。解决划分前先对文件名做分组——很多数据集的文件名遵循同一个前缀加序列号的规则同一个前缀往往是一个拍摄场景下的连拍。以这些前缀作为分组键使用 sklearn 的 StratifiedGroupKFold 或 GroupShuffleSplit 做划分保证同一个分组的图片只落在同一个集合里。划分完之后可以做一个快速验证随机挑几张验证集图片在训练集里搜感知哈希相似度最高的图如果相似度极高说明分组划分没做好。6. 把混淆矩阵分析作为模型验收的最后一步花类模型训练到 val_acc 达到预期只说明整体正确率过关。真正在部署前需要确认的是哪些类别之间在互相混淆。花类数据有明显的特点菊科下不同品种、颜色接近的月季与蔷薇、花瓣形状相近的几种野花这些类别之间的错误被整体准确率稀释后很难察觉。建议在测试集上导出全部预测结果画一张混淆矩阵再看一份 classification_report重点看每个类别的 precision 和 recall。from sklearn.metrics import confusion_matrix, classification_report import matplotlib.pyplot as plt # 构建测试集 DataLoader变换策略和 4.1 节的 val_transforms 保持一致 test_dataset datasets.ImageFolder(flower_split/test, transformval_transforms) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue) # 收集全部测试集预测结果 all_preds, all_labels [], [] with torch.no_grad(): for images, labels in test_loader: images images.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().tolist()) all_labels.extend(labels.tolist()) class_names list(test_dataset.class_to_idx.keys()) print(classification_report(all_labels, all_preds, target_namesclass_names, digits3)) cm confusion_matrix(all_labels, all_preds) plt.figure(figsize(12, 10)) plt.imshow(cm, interpolationnearest, cmapBlues) plt.colorbar() plt.xticks(range(len(class_names)), class_names, rotation45) plt.yticks(range(len(class_names)), class_names) plt.xlabel(Predicted Class) plt.ylabel(True Class) plt.tight_layout() plt.savefig(flower_confusion_matrix.png, dpi150)看混淆矩阵时重点找对角线外深色的格子。如果两个类别互相错认说明它们的视觉特征在大样本下依然不可分。此时不要盲目加数据先人工看一批错误样本如果发现是光照、拍摄角度导致的系统性偏向比如某个类别的图片大多在暗背景下拍摄可以考虑给所有样本统一做光照归一化如果发现是类别本身边界模糊比如某种花的园丁品种和野生品种混在一起那可能需要联系数据提供方确认标签定义。另外建议把前 20 个置信度最高的错误预测导出成图片墙人工扫一眼往往能发现规律。我在实际项目中养成的习惯是混淆矩阵存成 PNG、错误样本按类别单独归档每次改模型后都覆盖更新。这不是形式主义等模型在线上表现异常时这些记录是你快速定位问题方向的依据。以上是围绕花类识别数据集做分类项目的完整流程从解压校验到标签核对再到训练和验收。最大的教训是这份数据集的 zip 包只是项目的起点标签可靠性、划分策略、过拟合控制才是决定模型上限的部分希望这些内容能帮到你稳住基线、少踩几个坑。本文还有配套的精品资源点击获取
返回列表