ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

混凝土骨料粒度图像分类实战:数据集划分、可视化与训练

混凝土骨料粒度图像分类实战:数据集划分、可视化与训练 简介面向混凝土骨料粒度识别的图像分类数据集适合深度学习初学者、目标检测与图像分类项目开发者快速上手。数据已经按训练集、验证集划分完毕其中训练集600张、验证集270张共870张混凝土骨料颗粒图像涵盖A8、A16、A32、B8、B16、B32、C8、C16、C32九种粒度等级可直接作为yolov5等模型分类任务的训练数据。资源包共902个文件主体为900张JPG图片另含1个JSON类别字典文件与1个Python可视化脚本压缩后大小仅43.03MB便于下载与实验。可视化脚本可随机抽取4张图片并保存展示结果帮助快速核对图像与标签的对应关系。目前已有63人学习使用目录结构清晰、train与val分区明确省去手动整理数据集的步骤让使用者将更多精力投入模型调参与精度优化。1. 把混凝土骨料粒度图像识别分类数据集拆开先看懂划分和可视化再谈准确率混凝土骨料粒度识别真正在料场、破碎站和实验室里做过的人都知道瓶颈从来不在网络结构而在数据本身。同一堆石子户外顺光、传送带抓拍、实验台补光拍出来是三种完全不同的图像分布这套图像分类数据集的聪明之处是把“粒度档位”直接映射到划分好的数据目录和类别字典文件上还附带一个 python 数据可视化脚本让训练前就能暴露脏标注和类别不平衡。接下来的路线是——看懂结构、跑通可视化、建立基线、排查问题、用混淆矩阵做交叉验证每一步都给出能直接复现的命令和代码。如果你是打算在工地或搅拌站自己搭一套骨料粒度图像识别分类方案的人照着这份清单走能少走很多弯路。2. 骨料粒度图像分类数据集的构成从筛分标准到类别字典2.1 粒度类别体系怎么定为什么分类任务是四档起步混凝土骨料在建材行业里的分级靠的是标准方孔筛。把碎石从大往小逐层过筛留在哪一层筛网上面就归入哪一档。工程上最常见的连续粒级是 5~10 毫米、10~20 毫米、20~31.5 毫米更细的粉末和机制砂则归到 5 毫米以下。图像识别分类要做的事情本质上是代替人工“目测估档”给一张骨料图片判断它最接近筛分体系里的哪个区间。这里有一个值得较真的问题为什么不做成回归任务直接预测粒径毫米数我在实际项目里的体会是除非相机视场里有已知尺寸的参照物固定宽度的传送带、垫板或者标尺否则单目图像缺少绝对尺度强行回归出来的毫米数误差很大反而是让模型做“落在哪个档位”的分类可以绕开尺度歧义。只要能准确分到相邻档位工程上已经足够指导筛网调换和混凝土配比调整。类别数一般定四到六类。定得太细比如把 5~31.5 毫米切成十几个档位相邻档之间大量存在“介于两者之间”的模糊样本标注人员判断一致性会崩模型学到的基本是标注噪声。定得太粗比如只有“细料”和“粗料”两类对生产指导又没有意义。四分类是在标注成本、模型可学性和现场可用性之间比较折中的一个选择。下面的四分类方案是我在项目里常用的起点类别名用小写英文方便直接用作目录名和代码里的键值。类别字典键类别名粒度区间肉眼判断依据0powder0~5 mm粉体多颗粒细碎容易结团1fine5~10 mm指节大小棱角清晰2medium10~20 mm半个拳头大表面粗糙3coarse20~31.5 mm手掌心大小形状极不规则我坚持用英文类别名还有一个现实原因Linux 服务器、PyTorch 的 ImageFolder、Docker 部署默认用 UTF-8 处理路径英文目录名不会遇到编码问题而 matplotlib 画图时如果类别名是中文没装中文字体就直接显示成方块还得额外配字体纯属给自己找事。2.2 划分好的数据是怎么组织的目录、比例和类别字典文件“划分好的数据”落到磁盘上就是一套规则清晰的目录树训练、验证、测试三部分完全独立每个部分内部按类别名建子目录图片按文件名堆进去。这种结构最大的好处是 torchvision 的 ImageFolder 可以直接读不需要写解析脚本。一个典型的骨料粒度数据集目录结构如下aggregate_granularity_dataset/ ├── classes.json # 类别字典文件id 到类别名的映射 ├── visualize_data.py # python 数据可视化脚本 ├── train/ │ ├── powder/ # 类别键 0 │ ├── fine/ # 类别键 1 │ ├── medium/ # 类别键 2 │ └── coarse/ # 类别键 3 ├── val/ │ ├── powder/ │ ├── fine/ │ ├── medium/ │ └── coarse/ └── test/ ├── powder/ ├── fine/ ├── medium/ └── coarse/train/val/test 各司其职训练集只参与权重更新验证集在每个 epoch 后跑一次用来选择最佳模型和调学习率测试集只在最终评估时碰一次绝对不能拿来指导训练中的任何决定。常见划分比例是 6:2:2 或 8:1:1我一般用 8:1:1因为这类骨料图像数据量本来就不大训练集多一点更稳验证集和测试集每类留 30~50 张就够反映真实水平了。类别字典文件 classes.json 的内容也很简单就是一个整型 id 到类别名的映射{ 0: powder, 1: fine, 2: medium, 3: coarse }为什么需要这个字典而不是直接用文件夹名因为在训练框架里标签通常是整数索引模型预测输出也是整数如果没有这份映射画混淆矩阵、写报告、部署到现场解释结果时都要靠猜。字典文件相当于数据集的“说明书”也是下游代码的唯一解耦点以后想加一个“大于 31.5 毫米特粗料”类别只需要在字典里追加一个键值对。这里有一个很隐蔽的坑PyTorch 的 ImageFolder 按子目录名称的字母序生成类别索引不是按 classes.json 的顺序。上面这套目录里字母序是 coarse、fine、medium、powder于是 ImageFolder 的 class_to_idx 里 0 号是 coarse而 classes.json 里 0 号是 powder两边对不上。所以拿到数据集第一步是对齐两个映射关系import json import torchvision with open(classes.json, r, encodingutf-8) as f: classes json.load(f) train_data torchvision.datasets.ImageFolder(train) print(classes.json:, list(classes.values())) print(ImageFolder:, train_data.class_to_idx)如果输出顺序不一致解决办法有两个一是重新生成目录名按 class_to_idx 的顺序把 train/val/test 里的类别子目录重命名二是写一个小脚本把数据集的 target 统一重映射到 classes.json 的索引。第一种更干净因为它把 val/test 的目录顺序也一起修正了后续每次训练都不用再做 label 转换。最后是 visualize_data.py 的职责。这个脚本应该在跑任何训练之前先执行一遍输出三类东西每个类别的样本数、抽样网格图、类别分布柱状图。它的核心目的不是“画好看”而是用最快的方式暴露目录错位、类别不平衡和文件名乱码这三类问题靠人眼翻文件夹很难发现。下一章我就把脚本的每一步拆开讲。3. 用 Python 数据可视化脚本验证数据质量一张图看出问题3.1 环境准备numpy、matplotlib、pillow 的最小安装命令数据集附带的可视化脚本一般不依赖深度学习框架numpy、matplotlib、pillow 三件套足够。很多人卡在第一步其实“python 安装 numpy 库的方法”就是一行 pip 命令但为了让项目环境干净我的习惯是先建虚拟环境再装依赖python -m venv .venv # Windows 激活命令.venv\Scripts\activateLinux/macOS 用 source .venv/bin/activate source .venv/bin/activate pip install numpy matplotlib pillow装完后用一条命令验证导入是否成功python -c import numpy, matplotlib, PIL; print(ok)如果 pip 在工厂内网环境里下载慢可以临时换镜像源比如pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy matplotlib pillow。至于 python 安装教程里常说的 PATH 问题装 Python 时勾选“Add Python to PATH”就能避开后续一堆麻烦这个习惯我在新机器上每次都先确认一遍。选这三个库的原因很明确pillow 读 jpg/png 轻量化不会像 opencv 那样引入一堆系统依赖numpy 用来做数组统计批量算均值、方差都方便matplotlib 出图可以存成 PNG 再放大看细节也能直接 show 出来交互检查。可视化脚本跑通后训练脚本里的图像读取也要依赖同一套 IO 逻辑先保证这里没毛病。3.2 读取类别字典并统计每个类别样本数可视化脚本的第一步是读入 classes.json按类别汇总图片数量并指出缺失的目录。下面是完整代码import json import os with open(classes.json, r, encodingutf-8) as f: classes json.load(f) print(类别字典:, classes) train_root train counts {} for cls_id, cls_name in classes.items(): cls_dir os.path.join(train_root, cls_name) if os.path.isdir(cls_dir): # 只统计常见图片后缀避免把 Thumbs.db、.DS_Store 这类系统文件数进去 valid_ext (.jpg, .jpeg, .png, .bmp) counts[cls_name] len( [f for f in os.listdir(cls_dir) if f.lower().endswith(valid_ext)] ) else: counts[cls_name] 0 print(各类别样本数:, counts)这段逻辑看起来简单有两个细节值得说明。一是遍历字典的顺序来自 classes.json不是来自磁盘目录所以它能同时检查“字典里的类别在磁盘上是否存在”目录缺失时 count 会输出 0二是用 endswith 过滤后缀而不是直接 len(os.listdir())能避开隐藏文件和系统生成的临时文件这一点在 Windows 导出的数据集里经常踩到。输出结果能立刻暴露三类常见问题某个类别数量为 0说明目录名和字典键值对不上某个类别数量比其他类多出好几倍说明原始数据采集没有做均衡多个类别数量全部偏少说明这个数据集本身样本不够后续要考虑数据增强或迁移学习不能硬训。看到不平衡别着急处理先用视觉网格图确认一下分布到底长什么样。3.3 用 matplotlib 绘制样本总览和类别分布柱状图样本总览图的目的是人工抽检。把每个类别的前几张图拼在一个网格里用眼睛扫一遍能发现机器统计发现不了的问题某张图把两只手拍进去了某张图骨料只占了画面一角某张图干脆是对着地面拍的。import matplotlib.pyplot as plt from PIL import Image # 2x2 网格正好放下四分类类别多时改成 2x3 或 3x3 fig, axes plt.subplots(2, 2, figsize(8, 8)) axes axes.flatten() for idx, (cls_id, cls_name) in enumerate(classes.items()): cls_dir os.path.join(train_root, cls_name) files sorted(os.listdir(cls_dir)) if not files: continue # 只看每类第一张想看得更全可以改成 range(3) 拼接多张 img Image.open(os.path.join(cls_dir, files[0])) axes[idx].imshow(img) axes[idx].set_title(f{cls_id}: {cls_name}) axes[idx].axis(off) plt.tight_layout() plt.savefig(sample_grid.png, dpi150) plt.show()figsize 设为 8x8是为了在屏幕上放大之后仍然能看到骨料表面的棱角和纹理dpi150 是兼顾清晰度和文件体积的选择。如果你发现某些类别的首张图明显“画风突变”比如训练集里是自然光拍摄验证集里却是实验室补光说明采集环境不统一这个信息比任何训练指标都重要。类别分布柱状图用来量化不平衡程度代码也很短fig, ax plt.subplots(figsize(7, 5)) class_names list(counts.keys()) class_values list(counts.values()) ax.bar(class_names, class_values) ax.set_xlabel(granularity class) ax.set_ylabel(image count) ax.set_title(Granularity Class Distribution) # 类别名一长或者类别一多横坐标会挤成黑疙瘩rotation 是必备参数 plt.xticks(rotation45, haright) plt.tight_layout() plt.savefig(class_distribution.png, dpi150) plt.show()matplotlib 画图横坐标太密集的问题几乎每个用 python 画分布图的人都会遇到。如果类别名是中文或者超过六个默认水平排布的标签必然重叠成一片黑色。多写一行plt.xticks(rotation45, haright)就能解决 90% 的情况要是还很挤把 labelsize 调小到 8 或 9再用 tight_layout 重新排版就行。分布图的主要阅读方式是看“有没有类别占比低于 5%”。一旦出现这种情况模型会倾向于把少数类直接忽略掉后面需要用类别权重或重采样来干预。看到柱状图明显一边倒时不要急着删数据先去确认少数类是不是真的这么罕见还是采集环节漏拍了。3.4 顺带检查图像尺寸和文件完整性图像分类模型最后都要缩放到固定尺寸如果数据集里混入了不同比例的图片甚至有几张损坏文件训练时会突然报错或者出现奇怪的形变。可视化脚本里加一段尺寸统计成本很低但很实用import collections size_counter collections.Counter() for cls_name in counts: cls_dir os.path.join(train_root, cls_name) for f in os.listdir(cls_dir): if not f.lower().endswith((.jpg, .jpeg, .png)): continue try: with Image.open(os.path.join(cls_dir, f)) as img: size_counter[img.size] 1 except Exception as e: # 单张图片损坏时不能打断整个脚本先记录下来统一处理 print(读取失败:, cls_name, f, e) print(图片尺寸统计:, size_counter.most_common(5))这段代码用 collections.Counter 统计最常见的五种尺寸。如果结果是清一色的同一分辨率说明数据来源单一后续增强时要小心过拟合如果出现十几种分辨率说明是从手机、相机、视频帧混采来的这时候缩放到 224x224 会带来严重的信息损失最好先用代码统一裁剪到目标长宽比。用 PIL 的 Image.verify() 做损坏检测也可以但 verify 会忽略部分解码错误我在实际项目里更信任直接 open 再读一次的 try 方式。4. 用 PyTorch 跑通基线分类最小训练代码和参数设定4.1 为什么先从图像分类而不是目标检测开始骨料粒度识别有两种落地形态。如果场景是“每次拍一张或一段骨料堆的图判断这批料以哪一档为主”图像识别分类就够用模型输出一个类别标签就行。如果需求升级成“在传送带上同时框出多块骨料逐块判断粒度”那才需要走到目标检测数据集也要改成带边界框标注的格式。我做第一个版本时一定先用分类去验证数据有效性。原因很简单分类模型只需要目录结构就能训练代码量少能快速暴露标注问题目标检测要人工标框成本高一个数量级而且在标注本身不可靠的前提下检测框再准也救不了错误类别。等分类结果的准确率和混淆矩阵说明标注可信了再往检测或细粒度识别走是更稳的路线。如果你后续确实要升级到目标检测yolov8 训练自己的数据集是常见选择但前提同样是先有一份目录规范、类别定义清晰的数据。分类基线就是这份数据的试金石如果分类在干净标注下都学不明白那检测大概率只会更糟。另外分类模型还能直接复用 ImageNet1k 预训练权重迁移学习的收益在骨料这种小众图像上非常明显。4.2 用 ImageFolder 做数据加载顺便解决类别不平衡PyTorch 的 torchvision.datasets.ImageFolder 可以直接读上一章那种目录结构自动按子目录生成类别索引省去手写 Dataset 的功夫。数据预处理按训练集和验证集分开定义训练集加随机翻转和颜色抖动验证集不做随机增强import torch import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader train_transform transforms.Compose([ transforms.Resize((224, 224)), 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((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) train_data torchvision.datasets.ImageFolder(train, transformtrain_transform) val_data torchvision.datasets.ImageFolder(val, transformval_transform) train_loader DataLoader(train_data, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_data, batch_size32, shuffleFalse, num_workers2)几个参数的选择理由Resize 到 224x224 是 ImageNet 预训练模型的标准输入ResNet 系列直接可用RandomHorizontalFlip 对骨料来说没有方向性安全且能增加样本多样性ColorJitter 模拟户外早晚光照变化这是骨料图像最常见的干扰来源Normalize 用 ImageNet 的均值和标准差因为后面要加载预训练权重。num_workers 在 Windows 上不要设太大默认 2 比较稳太大容易报 DataLoader worker 进程错误。4.3 ResNet18 微调训练循环和参数设定加载预训练模型并把最后一层全连接换成自己的类别数这是迁移学习的标准做法import torch.nn as nn model torchvision.models.resnet18(weightstorchvision.models.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, len(train_data.classes)) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)类别不平衡时用带类别权重的交叉熵损失。计算权重的常见做法是让少数类获得更大的权重公式是每类样本数的倒数再归一化我在骨料数据集上验证过比手动调明显好使labels [img[1] for img in train_data.samples] class_counts torch.tensor( [labels.count(i) for i in range(len(train_data.classes))], dtypetorch.float ) class_weights class_counts.sum() / class_counts class_weights class_weights / class_weights.sum() criterion nn.CrossEntropyLoss(weightclass_weights.to(device))训练循环我习惯固定 30 个 epoch 上界配合“验证集准确率刷新最好成绩就保存权重”的策略实际训练多少轮由结果决定。优化器用 Adam学习率取 1e-4这是微调预训练模型比较保守但稳定的组合optimizer torch.optim.Adam(model.parameters(), lr1e-4) best_acc 0.0 for epoch in range(30): model.train() train_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() train_loss loss.item() model.eval() correct 0 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) total labels.size(0) correct (predicted labels).sum().item() val_acc correct / total print(fepoch {epoch1}/{30} loss{train_loss/len(train_loader):.4f} val_acc{val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pt) print(saved best_model.pt)这里有三个参数值得说明。学习率 1e-4 看起来小但微调预训练权重时如果直接用 1e-3前几个 epoch 就可能把 ImageNet 学到的通用特征冲掉骨料图像又不是自然图像一旦冲掉就很难再学回来。batch_size 32 在单卡 8G 显存下跑 ResNet18 很轻松过小的 batch 会让每个 step 的梯度方向抖动变大。epoch 上限 30 是因为骨料数据集规模不大训练到后面验证集准确率往往在第 8 到第 15 轮就停下来了追求更多轮次容易过拟合。参数建议取值选择理由输入尺寸224x224预训练模型的标准输入ResNet 直接可用batch_size32显存占用适中小数据集下梯度稳定优化器Adam自适应学习率免去手动调 momentum学习率1e-4保护预训练权重避免初始震荡epoch 上限30配合 best 权重保存多数场景实际更早收敛训练过程中要盯两件事train_loss 是否平稳下降val_acc 是否明显高于随机猜测四分类是 25%。如果 val_acc 一直贴着 30% 到 40% 徘徊先别调模型回去看可视化脚本的输出大概率是标注或者目录出了问题。5. 骨料粒度图像识别的常见问题脏标注、类别不平衡和数据泄漏的排查5.1 脏标注与类间不干净验证集准确率高测试集崩盘现象训练和验证准确率都能到 90% 以上一跑测试集掉到 75% 左右且掉下去的主要是某几个相邻类别。把测试集预测错误的图片打印出来看会发现不少图片从人眼角度看就是模糊样本比如一颗直径 10 毫米的石子被标成了 medium。原因骨料粒度按筛分标准划分但图像上相邻档位之间是连续过渡的标注员面对“5~10 毫米”和“10~20 毫米”的边界样本时主观判断必然不一致。这个不一致如果存在于训练集内部模型会把两种标签当成同一类去学验证集可能表现还行因为验证集里的错误标注恰好和训练集一致但测试集来自另一批标注分布就错开了。解决不要只依赖模型指标用数据可视化脚本做交叉抽检——把每个类别中部和边缘的样本各抽十张请两个标注员独立重新标注逐张比对不一致的图片人工复核后修正。如果项目周期紧至少把测试集全部人工复核一遍测试集只有几十张成本完全可以接受。5.2 类别不平衡powder 类把 fine 类吞掉现象powder 类有上千张fine 类只有两百多张训练出的模型把大量 fine 样本误判成 powder但整体准确率因为有 powder 撑着仍然显示 85% 以上。原因损失函数在类别样本数不均时整体样本贡献了大部分梯度少数类梯度被淹没。模型参数收敛到“全部输出为多数类”的局部最优这在准确率指标上还不容易被发现。解决除了第四章代码里用 class_weight 给少数类加权还可以在 DataLoader 里用 WeightedRandomSampler让每个 epoch 抽取的样本中各类数量均衡import torch sample_weights torch.tensor([class_weights[label].item() for label in labels]) sampler torch.utils.data.WeightedRandomSampler( weightssample_weights, num_sampleslen(sample_weights), replacementTrue, ) train_loader DataLoader(train_data, batch_size32, samplersampler, num_workers2)class_weight 是从损失函数层面给梯度加权采样器是从数据分布上强制均衡。我一般两个都做但要注意采样器会让每个 epoch 里同一张图被抽到多次训练轮次可以适当减少。另外评估时不要只看准确率多类别的平衡数据集用 macro-F1 更靠谱否则很容易被“多数类准确率高”骗过去。5.3 数据泄漏同一堆骨料同时出现在训练集和测试集现象验证集准确率高达 97%测试集准确率也很高但模型部署到现场拍摄的新骨料上直接翻车准确率崩到 60% 以下。原因数据划分时直接对文件列表做随机打散而相机在传送带或料堆上连续抓拍时同一堆骨料的不同角度照片之间存在极强的相似性。随机打散会把同一拍摄批次的一张图放进训练集另一张放进测试集模型在测试时其实见过近乎相同的图像内容指标自然虚高。解决划分数据集必须按拍摄批次或场景分组而不是按文件名随机打散。比如现场按“某一车进料”“某一次补光拍摄”作为分组单位整个分组的图片整体划入 train、val 或 test 的其中一边。目录结构里预留 batch_id 或者按时间命名的子目录比事后人工核对靠谱得多。检验泄漏的一个土办法是测试集里随机挑两张和训练集最相似的图片如果肉眼都很难区分那就是分边没分干净。5.4 背景捷径模型学会了认传送带而不是骨料现象同一个模型在实验室数据上验证准确率 95%带着去工地跑新数据只有 70%。把模型预测错误的图打印出来发现凡是背景是黑皮带的一律被猜成 coarse背景是白墙的一律被猜成 powder。原因骨料图像里背景区域占比往往很大卷积网络很容易捕捉到背景颜色和纹理这种“捷径特征”而不是骨料本身的粒度。这不是模型笨而是数据采集时没有固定成像条件不同类别恰好用了不同场景。解决一是统一采集背景尽量让所有类别的照片都在同一类传送带或同一块垫板上拍摄二是在数据增强里加入随机裁剪让模型不能只依赖整图边缘的背景信息三是用 Grad-CAM 热力图检查模型决策时关注的区域如果激活集中在画面四角而不是骨料主体基本就是学到背景了需要重新裁剪或采集。5.5 数据可视化脚本的常见小坑matplotlib 横坐标叠成黑疙瘩现象画类别分布图时类别名字一长或者数量一多横坐标标签全部叠在一起图存下来根本没法阅读。原因matplotlib 默认对离散分类坐标不做自动避让字符串标签按原样水平排列类别一多必然重叠。这在用 python 画图时是个非常普遍的体验问题。解决在 bar 图之后加plt.xticks(rotation45, haright)和plt.tight_layout()还挤的话用ax.tick_params(axisx, labelsize8)把字号调小或者每隔一个类别才保留一个标签。如果连中文标签也显示成方块那就是系统缺中文字体需要给 matplotlib 指定字体路径这也是我坚持类别名用英文的另一个理由。6. 进阶把混淆矩阵和预测错误的样本变成下一轮回合的动作6.1 快速打印混淆矩阵的脚本训练完基线模型后验证集上的准确率只是起点真正的信息藏在混淆矩阵和错误样本里。脚本如下import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) _, predicted torch.max(outputs, 1) all_preds.extend(predicted.cpu().tolist()) all_labels.extend(labels.tolist()) cm confusion_matrix(all_labels, all_preds) fig, ax plt.subplots(figsize(6, 5)) im ax.imshow(cm, cmapBlues) ax.set_xticks(range(len(train_data.classes))) ax.set_yticks(range(len(train_data.classes))) ax.set_xticklabels(train_data.classes, rotation45, haright) ax.set_yticklabels(train_data.classes) for i in range(cm.shape[0]): for j in range(cm.shape[1]): ax.text(j, i, cm[i, j], hacenter, vacenter) plt.xlabel(predicted) plt.ylabel(true) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150) plt.show()这里有一件必须做的事画图之前先确认 train_data.classes 的顺序和模型输出的类别索引一致。如果第四章里发现 class_to_idx 与 classes.json 不对齐必须先在训练时完成重映射否则混淆矩阵的横纵坐标和真实类别对应不上后面所有分析都是在猜。6.2 从错误样本推导改进动作拿到混淆矩阵后看两个重点。第一错误是否集中在相邻档位比如 fine 和 medium 互相混淆而 coarse 几乎不出错。如果是这样说明类别边界本身模糊下一步要么重新定义边界标准给标注员提供更明确的参考图要么改成排序任务做粒度档位的序数回归这两条路我都试过改标注标准往往比改模型结构见效更快。第二把预测错误且置信度最高的样本单独打印出来按错误组合成组观察。如果“把 fine 认出 medium”的图片里每一张都有同一个光源反光区域说明模型学到的是光照模式如果图片里都有手指或者铲子边缘说明采集时没有控制视场需要回炉修正数据采集规范。这类观察花一下午时间换来的是下一轮训练样本质量明显提升。我最早拿到第一版骨料数据时满脑子都是换模型、调学习率结果验证集卡在 82% 死活上不去。后来把可视化脚本跑一遍样本网格图直接显示 fine 目录里混了十几张实际是 medium 的图人工清掉并重新划分后同一个模型直接涨到 91%。从那以后我的固定习惯是先画图后训练先消错后调参这套顺序在骨料粒度识别上没让我失望过。希望帮到你。本文还有配套的精品资源点击获取
返回列表