ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

用CNN识别交通标志:GTSRB数据集实战指南

用CNN识别交通标志:GTSRB数据集实战指南 简介这套项目聚焦智慧交通中的交通标志识别任务以德国GTSRB公开数据集为实验数据通过CNN卷积神经网络完成43类交通标志的自动分类旨在为道路标志自动识别提供一套可运行的深度学习解决方案。项目覆盖从图像预处理、模型输入构造、网络结构设计到训练调参与评估的完整流程适合具备一定Python基础并希望接触深度学习图像分类实战的开发者。压缩包共8个文件包含5个针对不同环节的Python脚本数据清洗、输入数据加载、CNN结构、训练与测试、2个CSV标签文件训练集与测试集以及1个XML工程配置文件整体大小仅为310KB目录简洁。目前已有623人学习浏览。通过这套代码读者可以快速复现一个可用的交通标志识别模型并基于脚本灵活调整卷积层、全连接层等结构从而加深对CNN特征提取、超参数调节和模型评估等关键环节的理解同时项目代码按模块拆分也便于在此基础上扩展新的识别应用是入门智慧交通与深度学习项目的实用参考。1. 用CNN识别交通标志为什么GTSRB是入门智慧交通最值的一仗如果你正在找一个人工智能大作业或毕设选题又想避开烂大街的MNIST和CIFAR-10用CNN对GTSRB.zip做交通标志识别是个性价比极高的方向。这个项目用深度学习CNN对着GTSRB里的四万多张真实路拍图片做43类分类既有真实场景的光照变化、运动模糊和遮挡噪声又不需要自己动手标数据一套PyTorch代码跑下来验证集精度超过95%是很轻松的事。相比手写数字识别它更接近智慧交通里辅助驾驶真正要做的事相比目标检测又不用碰锚框和NMS这些复杂概念。这篇文章从拆解数据集讲起到模型搭建、参数调整、常见坑排查最后落到错例分析给你一条能直接照着走的完整路线。2. 拆开GTSRB.zip:数据集结构、ROI裁剪与数据增强怎么做2.1 先看清GTSRB的目录和标注:43个类不只是43个文件夹把GTSRB.zip解压后第一眼看上去结构很规整但细看有几个和普通分类数据集不一样的地方。最典型的目录布局是Training下面挂43个子文件夹名字从00000到00042每个子文件夹里放着属于这一类的图片和一个GT-开头、以类编号结尾的CSV标注文件。Final_Test_Images有些版本叫Test则把全部测试图片平铺在同一个目录下配一个单独的GT-final_test.csv。如果你下载的是Kaggle版本目录可能略有出入但CSV标注的格式是统一的。每个CSV文件的字段是固定的Filename代表图片文件名Width和Height是图片原始宽高Roi.X1、Roi.Y1、Roi.X2、Roi.Y2是交通标志在图片里的边界框坐标ClassId是类别编号。这里有个关键信息——GTSRB的图片不是预处理好的正方形小图而是从街景中截取出来的大图标志只占画面的一部分。比如一张100x100的图片标志可能只在中间40x50的区域。如果你不管标注直接把整张图喂给CNN模型会把天空、树木、路面这些背景纹理一并学进去测试时换一条街的背景精度立刻掉下来。还有一个容易被忽略的点是图片格式和尺寸。训练集原始图片以PPM格式为主部分下载源转成了PNG无论哪种读进来之后尺寸都是不一致的——小的只有十几像素大的能到两百多像素。这一点和MNIST那种统一28x28完全不同所以预处理的第一步不是直接进模型而是先把数据读进内存看一眼再设计流水线。我习惯先把每个类随机抽9张图拼成一张画布存下来肉眼确认标志位置和背景情况再开始写DataLoader。这一步花不了两分钟却能在后面省下大量排查时间。GTSRB/ ├── Final_Training_Images/ │ ├── 00000/ │ │ ├── 00000_00000.ppm │ │ ├── 00000_00001.ppm │ │ └── GT-00000.csv │ ├── 00001/ │ │ └── ... │ └── 00042/ └── Final_Test_Images/ ├── 00000.ppm ├── 00001.ppm └── GT-final_test.csv拿到目录结构后先把每个类别的CSV读进来确认行数和图片数对得上。GTSRB官方提供的CSV用分号分隔不是逗号直接调用pandas的read_csv会出现所有字段挤在一列里的情况。解决办法是显式指定分隔符为分号这一步很多人踩过坑。类别总数是43编号从0到42没有缺号这一点比很多工业数据集要干净。2.2 读取CSV标注并裁剪ROI:别把背景噪声喂给CNN明确了标注格式之后写数据集读取类时最核心的决定就是按ROI裁剪。CSV里的Roi.X1、Roi.Y1是左上角坐标Roi.X2、Roi.Y2是右下角坐标PIL的crop方法接收的正是这个格式的四元组。裁剪之后模型学到的才是标志本身的纹理而不是标志周围的环境。用PyTorch写一个可复用的Dataset类是这套流程里最值得认真写的部分。它既要负责读图、按ROI裁剪、统一尺寸也要负责把类别编号转成Tensor。我一般会把是否使用ROI做成一个开关方便后面做对比实验——验证一下裁ROI到底能提多少点。下面是这个Dataset类的完整实现import os import pandas as pd from PIL import Image from torch.utils.data import Dataset from torchvision import transforms class GTSRBDataset(Dataset): def __init__(self, root_dir, annot_file, use_roiTrue, target_size(48, 48), transformNone): root_dir: 图片所在目录训练集某个类或测试集总目录 annot_file: 对应的CSV标注文件路径 use_roi: 是否按Roi坐标裁剪False则使用整张图 target_size: resize后的统一尺寸 transform: 额外的数据增强流水线 self.df pd.read_csv(annot_file, sep;) self.root_dir root_dir self.use_roi use_roi self.target_size target_size self.transform transform def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] img_path os.path.join(self.root_dir, row[Filename]) image Image.open(img_path).convert(RGB) if self.use_roi: # 边界框坐标来自CSV直接传给PIL的crop x1, y1 int(row[Roi.X1]), int(row[Roi.Y1]) x2, y2 int(row[Roi.X2]), int(row[Roi.Y2]) image image.crop((x1, y1, x2, y2)) # 统一尺寸先resize到目标大小 image image.resize(self.target_size, Image.BILINEAR) if self.transform: image self.transform(image) label int(row[ClassId]) return image, label这个类的核心逻辑很简单读图、转RGB、按坐标裁剪、resize最后交给transform做增强。转RGB这步千万别省PPM格式读进来默认是RGB没问题但如果是灰度图或者带透明通道的PNG转RGB可以统一通道数避免后面模型第一层卷积报通道不匹配的错误。裁剪时直接套用PIL的crop接口参数顺序是左、上、右、下和CSV里的X1、Y1、X2、Y2一一对应不需要换算。使用这个Dataset时需要注意两点。第一点是训练集43个文件夹需要分别读取再拼接因为每个类有自己的CSV。可以用一个循环遍历所有子目录把每个类的GTSRBDataset实例用torch.utils.data.ConcatDataset拼接起来。第二点是在做实验对比时use_roi参数设为False会退化成整图训练这就是你验证裁剪价值的最好对照组很多同学做完对比才发现精度差了5个点以上。2.3 统一尺寸与数据增强:从原始尺寸到训练集的完整流水线ROI裁剪做完之后图片尺寸依然五花八门有的标志裁出来是30x40有的是120x80。CNN要求输入尺寸固定所以必须统一到一个目标大小。这里的选择通常有三个32x32、48x48、64x64。32x32训练速度快显存占用小但GTSRB里有些标志类别之间的差异非常细微比如限速30和限速50的区别主要体现在数字上低分辨率下数字笔画糊成一团精度天花板会比较低。64x64保留了更多细节但训练时间差不多是48x48的两倍。我一般取48x48作为默认值这是速度和精度的折中点。尺寸变换的方式也有讲究。最直接的做法是resize到正方形但GTSRB的ROI不是严格正方形直接拉伸会让圆形标志变椭圆、三角形标志变形。CNN对轻微形变有容忍度所以很多工程上的做法就是直接拉伸不影响最终效果。如果你追求稳妥可以先等比缩放让长边符合目标尺寸再在短边两侧补灰边。两种做法各有适用场景我给出的建议是先跑一版直接resize的作为baseline如果测试集上圆形标志的错误率明显高于方形标志再换成补边方案对比一次。数据增强是让模型在真实路拍场景下不翻车的核心手段。GTSRB图片天然包含光照过曝、欠曝、模糊、遮挡等情况如果只用原始图片训练模型会对这些变化非常敏感。PyTorch的torchvision.transforms提供了现成的增强组件组合起来的典型流水线如下import torch from torchvision import transforms # 训练集增强旋转平移缩放亮度对比度扰动 train_transform transforms.Compose([ transforms.RandomRotation(degrees15, fill0), transforms.RandomAffine(degrees0, translate(0.1, 0.1), scale(0.9, 1.1)), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ]) # 验证集和测试集不做随机构变换只做张量化和归一化 eval_transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ])增强组件里最容易犯的错是加水平翻转。MNIST和CIFAR-10上翻转能提升泛化能力但GTSRB里有限速标志、禁止超车标志这些标志里有数字和文字水平翻转之后数字30变成03文字方向也会颠倒模型学到的是错误语义。所以交通标志分类的数据增强只做旋转、平移、缩放和颜色扰动不做翻转。旋转角度我控制在15度以内超过这个范围虽然标志还是能认出来但会引入过多无效视角训练收敛变慢。归一化参数mean和std这里用了0.5的固定值把像素从[0,1]映射到[-1,1]简单够用。更严谨的做法是先遍历训练集算出每个通道的均值和标准差再填入但对这个任务来说0.5和真实统计值之间的差别基本体现不到最终精度上。至此数据端的完整流水线就齐了读CSV、按ROI裁剪、统一到48x48、做随机增强、归一化。接下来可以进入模型设计和训练环节。3. 从零搭CNN训练交通标志识别模型:架构选择与关键参数3.1 选型理由:为什么用LeNet-5变体而不是一上来就ResNet做GTSRB分类第一个要回答的问题是用多大的网络。很多新手一上来就搬ResNet-50甚至EfficientNet结果在小尺寸输入上训练慢、显存爆精度反而不如一个精心调过的浅层网络。GTSRB训练集约四万张图输入尺寸48x48这是个典型的小图分类任务LeNet-5级别的网络容量已经完全够用关键是加上卷积层的宽度和深度让模型有足够的表达能力区分类别间的细微差别。我常用的基准结构是三层卷积加两层全连接。第一层卷积从3通道变32通道第二层64通道第三层128通道每层后面跟BatchNorm和ReLU中间穿插最大池化。全连接层接256维隐层加Dropout防止过拟合最后输出43维分类结果。这个结构和经典LeNet-5的骨架一致但卷积核数量和深度做了扩展可以理解为LeNet-5的现代变体。它在GTSRB上验证集精度一般能到96%到98%训一个epoch只需要几十秒非常适合做课程设计和毕设的基础模型。为什么不用Transformer或RNNTransformer在视觉上需要足够多的数据和大规模预训练才能发挥优势四万张图的数据量对它来说偏小从零训练ViT很容易欠拟合而CNN的归纳偏置局部连接、权值共享、平移等变天然适合小数据图像任务。RNN本身是为序列建模设计的硬套到静态图像分类上既丢失空间结构信息又增加训练复杂度没有任何收益。故选型结论很明确这个任务里CNN是性价比最高的选择先把CNN的精度做到极限再考虑其他结构。下面的表格给出了基准网络各层的输出尺寸和参数量输入按48x48x3计算。这个表格在写实验报告或答辩PPT时可以直接用能让评委一眼看到你的网络设计思路是清楚的。层名称 输出尺寸 参数量 Conv2d(3, 32, 3) 48x48x32 896 BatchNorm2d(32) 48x48x32 64 MaxPool2d(2) 24x24x32 0 Conv2d(32, 64, 3) 24x24x64 18496 BatchNorm2d(64) 24x24x64 128 MaxPool2d(2) 12x12x64 0 Conv2d(64, 128, 3) 12x12x128 73856 BatchNorm2d(128) 12x12x128 256 MaxPool2d(2) 6x6x128 0 Linear(4608, 256) 256 1180672 Dropout(0.5) 256 0 Linear(256, 43) 43 11051总参数量约128万其中全连接层占了绝大部分。如果你用的是老显卡或想在CPU上跑可以把第三层卷积的128改成64参数量能降一大截精度损失通常在1个百分点以内。另一个可选优化是把最后的全连接层换成全局平均池化参数量会再降但对小数据集来说收益不明显反而稍难收敛所以我保留传统全连接结构。3.2 训练脚本:数据加载、模型定义、训练循环一次跑通模型定义用PyTorch的nn.Module写几层下来很直观。注意每个卷积层后接BatchNorm全连接层后接Dropout。BatchNorm的作用是让每层输入分布稳定可以大幅加快收敛速度尤其适合这种小数据量任务。Dropout只加在全连接层不加在卷积层这是经验卷积层本身有参数共享的约束过拟合风险不高。训练循环里值得关注的细节有三个损失函数用CrossEntropyLoss它内部已经包含Softmax不要在模型最后一层再加Softmax优化器用Adam初始学习率1e-3训练过程中保存验证集精度最高的权重而不是保存最后一个epoch的权重避免后期过拟合导致的最佳模型丢失。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, ConcatDataset class TrafficSignCNN(nn.Module): def __init__(self, num_classes43): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Linear(128 * 6 * 6, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) x self.classifier(x) return x这段网络定义有两个设计点值得展开。卷积核全部用3x3加padding1这么做的好处是特征图尺寸经过卷积后不缩小只由池化层负责降维信息损失可控。池化用2x2步长248x48输入经过三次池化变成6x6全连接层输入维度是128x6x64608。如果你把输入改成32x32这个维度会变成128x4x4需要同步修改全连接层第一层的输入维度这是新手最常见的报错来源之一。训练主循环用vanilla写法不用PyTorch Lightning之类的高级封装方便你看到每一步在做什么。每个epoch结束计算验证集精度精度超过历史最优就保存权重文件。def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total 0.0, 0, 0 for images, labels in loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) return total_loss / total, correct / total def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0.0, 0, 0 with torch.no_grad(): for images, labels in loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) total_loss loss.item() * images.size(0) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) return total_loss / total, correct / total这段训练函数里model.train()和model.eval()的切换不是形式主义。Dropout层在训练时随机丢弃神经元在评估时必须关闭BatchNorm在训练时用批内统计量在评估时用累计的全局统计量PyTorch通过这两个模式切换来管理漏掉的话验证精度会忽高忽低。optimizer.zero_grad()清空上一步梯度这一步漏掉会导致梯度累加loss直接发散。这两个细节是训练循环里最容易被忽略又最致命的。主脚本里把训练集43个文件夹分别封装成Dataset再拼接按8:2切出验证集测试集只保留到最终评估时才用。训练30个epoch每个epoch后打印一次训练损失、训练精度和验证精度。跑完看验证精度走势正常情况应该是第5个epoch左右就超过90%后面缓慢爬升到96%以上。3.3 参数怎么调:学习率、batch size、Dropout和数据增强的配合模型能跑通只是第一步参数怎么配直接决定了精度天花板。GTSRB这个任务上我调参的血泪经验可以浓缩成几条可复用的规则。学习率是最先要确认的参数Adam优化器不敏感但也不是万能初始学习率设为1e-3验证集精度如果前几个epoch纹丝不动降到3e-4再试。1e-2的学习率在大多数情况下会让loss直接冲到NaN。我在第15个epoch左右把学习率降到1e-4精度通常能再往上跳一个点这是手动学习率衰减的常规操作比余弦退火在小数据集上更可控。Batch size方面64是一个稳当地起点。Batch size太小比如8或16BatchNorm统计量不稳定训练震荡明显太大比如256或512收敛更快但精度上限略低因为每个batch的梯度方向更平滑但容易陷入尖锐极小值。显存够用的前提下我建议先在64上跑通有余力再试128。Dropout固定0.5就好这个值是经典经验调大调小都容易出问题——0.7以上会欠拟合0.3以下过拟合风险上升。数据增强和训练参数的配合容易被忽视。数据增强不是加的越多越好GTSRB的训练集有四万张增强太强反而会丢失原始分布信息。我推荐的组合是旋转15度、平移10%、缩放10%、亮度对比度各0.3这套组合既能模拟真实路拍中常见的车辆震动和光照变化又不至于让标志变形到失真。另一个技巧是把增强强度作为后续调参的变量如果验证集精度在97%上不去试着把旋转角度提高到20度、亮度扰动提高到0.4有时能换来最后1个点的提升。早停法也是必做的验证集精度连续5个epoch不提升就停止训练并回滚到历史最佳权重。四万张数据训练30个epoch在普通GPU上几分钟就完成早停的意义不在于省时间而在于避免过拟合——第20个epoch之后训练精度可能已经接近100%验证精度却在下降这就是过拟合信号保存最佳权重才能在测试集上拿到最好结果。4. 交通标志识别常见问题排查:GTSRB的五个坑与解法4.1 坑一精度卡在90%上不去——多半是没裁ROI这个坑我见过太多次也是我自己最初做这个项目时卡得最久的地方。现象很典型训练精度已经到99%验证精度却始终在90%附近晃悠换模型、调学习率都没有明显改善。原因在于训练时把整张图片直接resize喂进了网络图片里除了交通标志还有大片背景区域。CNN的卷积核在提取特征时会平等扫描整个画面背景中的树干纹理、天空颜色、路面裂缝都会被编码进特征图训练集和验证集来自同一批路拍场景背景特征碰巧能辅助分类但一到测试集换个场景就失效。解决办法就是回到第2章的GTSRBDataset把use_roi参数设为True用CSV里给的Roi.X1、Roi.Y1、Roi.X2、Roi.Y2把标志区域裁出来再训练。做过这个切换之后验证集精度一般直接跳到95%以上效果立竿见影。如果不想舍弃背景里的上下文信息也可以退一步做半裁剪——把ROI向外扩20%再做裁剪既保留少量上下文又不让背景主导特征提取。这个方法在部分遮挡场景下比严格裁剪更鲁棒但需要写额外的边界框膨胀逻辑。4.2 坑二验证集和测试集混用结果虚高我先说明一个GTSRB的经典陷阱官方给的Final_Test_Images目录是完整的测试集带标注很多人拿着它反复调参、反复改模型最后报出来的精度自然好看。但这不是真正的测试精度因为你已经提前看了答案调参过程隐式地把测试集的信息泄露进了模型。更麻烦的是GTSRB测试集里混有一部分困难样本包括严重遮挡、模糊到人眼都难辨认的标志如果你拿整个测试集做验证精度会偏低于是又去调整验证集构成来回折腾整个评估流程就乱了。正确做法是这样的训练集按8:2或者9:1切出一部分当验证集验证集只用来做模型选择和早停判断。Final_Test_Images里的官方测试集全程只在你觉得模型已经调好之后跑一次跑完的记录就是最终汇报精度。常见做法是在训练脚本里加一个随机种子保证每次切分方式一致这样不同模型对比时验证集是同一个分布。我见过太多大作业汇报现场翻车就是因为学生提前用测试集调参答辩时换个新测试数据精度立刻崩塌。4.3 坑三类别不均衡导致少数类全错GTSRB虽然整体数据量够用但43个类之间样本数并不均衡。有的类别像限速50、限速30这种常见标志单类样本量超过两千有的类别像限速20、禁止通行的某些变体样本量只有几百。如果不做处理模型会把学习重心放在样本量大的类别上样本少的类别在训练中获得的梯度占比太小最终这类标志的recall接近零。现象就是混淆矩阵里少数类所在的行几乎全是0验证精度看着还行但每类平均精度惨不忍睹。解决方案有三种按优先级排列。第一种是给CrossEntropyLoss传入class_weight参数权重按类别样本数的倒数归一化实现最简洁只需要一行代码。第二种是用WeightedRandomSampler在采样时让少数类有更高的出现概率效果和class_weight类似但实现稍复杂一点。第三种是对少数类做额外数据增强比如把该类别的旋转角度和亮度扰动调大成本最高但最接近无损。我做课程设计时用第一种就够答辩时讲清楚原理和为什么这样解决比盲目堆数据更有说服力。4.4 坑四尺寸变换姿势不对标志形状被压扁如果训练精度不低、但测试时圆形标志的错误率明显高于三角形和方形标志多半是尺寸变换时直接拉伸出了问题。GTSRB的ROI宽高比不固定用PIL的resize直接把所有图变成48x48正方形圆形标志被压成椭圆内部文字和符号同步变形CNN虽然对轻微形变有容忍度但变形超过一定范围就会导致特征错位。解决方式是改成letterbox补边先等比缩放让长边等于目标尺寸短边方向用灰色像素填充到目标尺寸保持标志原始宽高比。实现时在Dataset的__getitem__里加一个分支先算出缩放比例再创建目标尺寸的画布把缩放后的图贴上去。代价是边缘多了一圈灰边对卷积特征提取影响很小。要不要做这一步可以用对照组来验证训练两个模型一个直接拉伸一个letterbox对比测试集上圆形标志类别的准确率差异。如果差异在1个百分点以内直接拉伸的简单方案完全够用不用为了理论正确牺牲实现简洁度。4.5 坑五训练震荡不收敛训练过程中loss曲线反复震荡、精度停在50%附近上不去是新手最容易遇到又最难定位的问题。这类问题通常不是单一原因造成的按排查顺序看先检查数据归一化像素值有没有缩放到[0,1]区间或者归一化到[-1,1]如果直接把0到255的原始像素喂进网络梯度幅度会偏大Adam也很难稳住再检查优化器参数学习率是否过大初始学习率超过1e-2时CrossEntropyLoss很容易发散最后检查DataLoader有没有设置shuffleTrue如果不打乱数据模型会按类别顺序学习相邻批次都是同一个类别BatchNorm统计量急剧波动loss自然震荡。还有一个容易被忽略的细节模型最后一层不要手动加Softmax。CrossEntropyLoss内部做了LogSoftmax和NLLLoss的组合如果你在forward里提前Softmax损失计算时等于对概率又做一次对数变换数值范围完全错乱训练曲线直接起飞。我在检查别人的训练脚本时这种错误遇到的频率远高于想象。排除掉上述四个因素后训练过程基本会稳定收敛剩下要处理的就是精度上不去的调参问题而不是不收敛的崩溃问题。5. 把模型用到智慧交通场景前:用混淆矩阵和错例定位最后的短板模型训练完验证集精度到了97%很多人就认为项目结束了。但如果你想把这个模型讲清楚或者在答辩时不被问倒混淆矩阵和错例分析是必须补上的最后一环。计算混淆矩阵用sklearn的confusion_matrix传入所有验证集的真实标签和预测标签生成一个43x43的矩阵再用seaborn画成热力图。观察矩阵里对角线之外的高亮区域你会发现误判不是随机的——限速30和限速50互相混淆禁止左转和禁止右转互相混淆这些语义上相近的标志在特征空间里的距离本来就近模型犯错有明确的规律可循。错例分析的做法是把预测错的图片单独保存到一个文件夹同时记录真实类别、预测类别和置信度。按置信度从高到低排序优先看那些模型自信地错的样本。这类样本里通常有两种情况一种是标志被遮挡或严重模糊人眼都难识别这种错可以接受另一种是数据增强没覆盖到的光照极端情况比如强烈的逆光让标志整体发暗。如果后者占比高说明数据增强里ColorJitter的亮度扰动范围要加大而不是换网络结构。如果错例集中在少数几个类别回去看第4章的类别不均衡处理是否生效。我自己做这个项目时有个习惯最后把模型在测试集上跑一遍挑出一张最有代表性的错误预测打印出图片、真实标签、预测标签和置信度放进论文或者答辩PPT的显著位置。这张图传递的信息量比10张精度曲线都要大它证明了你不只是把代码跑通而是真正理解了问题出在哪一步。最后说一个我自己踩过的坑最初我做GTSRB时也迷信复杂模型花大量时间调ResNet的结构结果精度一直卡在91%后来换上干净的数据预处理加三层CNN精度直接到97%。这件事之后我养成了一个习惯——遇到新数据集永远先用最简单、最快收敛的方案跑通再逐步加复杂度只有这一步走稳了后面所有优化才有参照物。希望帮到你。本文还有配套的精品资源点击获取
返回列表