简介在语义分割任务中总体像素精度容易被高频类别主导逐类别的mIoU才是定位薄弱环节的关键指标。这套轻量脚本工具面向PyTorch实践者聚焦上述评估需求。压缩包内仅含2个Python脚本整体约4KB结构精简无需额外依赖即可直接运行。两段脚本分工明确一段将模型输出转化为8位预测图另一段读取对应标注mask逐类别计算IoU并汇总整个测试集的平均mIoU输出结果可用于分析类别不平衡等典型问题。脚本逻辑直观便于按需修改既能用于快速验证分割模型也能嵌入现有训练评估流程。目前已有7160人学习下载小巧实用适合语义分割初学者掌握评估细节也方便研究者快速获取各类别精度反馈。1. 语义分割里那个绕不开的mIoU它是精度表也是黑匣子做过语义分割的人十有八九都经历过这样一幕跑完几十个epoch的模型日志里总损失掉到了0.1以下看起来完美收敛可一算mIoU只有六十几个点。换一个解码头、调一下辅助loss的权重mIoU可能突然又涨了三个点。你隐约觉得哪里不对劲但指标摆在那只能继续调参。这个mIoU就是全类别的平均交并比是所有语义分割论文里必须出现的一个数字也是工程上判断模型好不好用的硬指标。问题是它并不是一个单一数字那么简单——mIoU背后藏着各类别的IoU、类别不均衡、混淆矩阵、忽略像素等一系列容易把人绕进去的细节。这篇文章只聊一件事怎么把各类别mIoU计算这件事彻底搞清楚从公式原理到代码实现再到边界情况和踩坑经验。不管你是刚开始跑FCN、DeepLab还是U-Net还是已经在训遥感影像分割模型但被验证集指标搞到怀疑人生这篇文章想做的是让那个黑匣子打开给你看。mIoU全称是Mean Intersection over Union语义分割任务中最常见的评价指标衡量模型预测的每个像素类别和真实标注的重合程度。这个指标直接反映了模型对每个类别的分类精度尤其是小目标、边缘区域和类别不平衡场景下的表现。这篇笔记适合所有做语义分割算法落地、模型评估和调优的从业者从原理到代码实现从参数设置到常见坑点一次讲透。2. 从交并比到平均交并比mIoU公式里藏着的高频细节2.1 先搞懂单个类别的IoU预测和标签的交集/并集但分母不是你想的那样单个类别的IoU定义很简单对于类别$c$来说假设模型把所有像素预测成了两类——是类别$c$或者不是类别$c$同时真实标注也把所有像素分成了是类别$c$或者不是类别$c$。于是就有了四个数字真正例TP预测是类别$c$且标注也是类别$c$的像素数、假正例FP预测是类别$c$但标注不是、假负例FN预测不是类别$c$但标注是、真负例TN预测不是且标注也不是。那IoU的数学形式直观来看是交集像素数除以并集像素数即$$\text{IoU}_c \frac{TP_c}{TP_c FP_c FN_c}$$注意这里的分母并集是三类像素之和而不是TPFPTNFN。TN不在分母里也不在分子里。这个区分非常重要因为很多初写代码的人会很自然地拿整个图像的像素总数当分母或者把TN算进去那样算出来的数字非常虚高——背景类别占比大时IoU可以被冲到0.99看起来模型无敌了其实只是在碾压背景。$$IoU_c \frac{TP_c}{TP_c FP_c FN_c}$$再往深想一步FP和FN的性质完全不同。FP是模型把别的类别错认成了类别$c$FN是模型漏检了本属于类别$c$的像素。这两个数字一不平衡IoU就下来了。以遥感图像语义分割为例建筑物边缘通常只有1-2个像素宽一旦解码器输出边缘偏移半个像素FP和FN同时爆发IoU直接掉到0.5以下而其他指标比如准确率还好端端地停在0.95以上。所以IoU对边缘像素的敏感度远比像素准确率高这也是它成为语义分割核心指标的原因。2.2 经典的11点法和像素级累加两种差之毫厘的实现路径mIoU之所以叫平均顾名思义是把所有类别的IoU求平均。但具体怎么算业界存在两套常见做法数学上等价但工程实现不同数值上可能有微小差异。第一种是PASCAL VOC最早推广的办法先按类别逐张图像算IoU然后对同一类多张图的结果做平均最后对所有类别再做一次平均。这种做法学名叫per-image平均。第二种是像素级累加把整个验证集的所有像素对应的TP、FP、FN先全局累加然后一次性计算每个类别的IoU最后求平均。第二种是现在的主流做法因为整个验证集上类别分布更稳定单张图如果某个类别只出现几个像素per-image平均会把这张图的IoU拉得特别低产生剧烈波动。如果你跟论文对比mIoU数值尽量确认对方用的是哪种累加方式不然你复现出来的数字可能和论文差一两个点。实现上有一个非常重要的细节各类别的IoU逐类计算完成后求的是算术平均不是加权平均。也就是说不管某个类别在数据集中只占0.1%的像素还是占50%的像素它对最终mIoU的贡献是相同的。这就是为什么mIoU天然是类别不均衡场景下更严苛的指标——小类别的表现一旦差直接把整体mIoU拖下水。反过来这也意味着如果一个小类别的IoU为0整体mIoU可能会被拉低好几个点即便模型在绝大多数像素上都预测正确。2.3 为什么要各类别单独算混淆矩阵、weighted IoU和频率平衡有些语义分割框架默认给你一个总的mIoU数字不看各类别明细。但工程上真正的排障信息全藏在各类别IoU列表里。举例来说道路分割任务里路面的IoU可能已经到0.92但人行道只有0.61这说明问题大概率出在路缘边界附近或者是标注本身不一致。此时如果你只盯着mIoU0.76这个数字你根本不知道从何下手调模型。在实践中我一般会同时打印三个东西各类别的IoU、各类别的TP/FN/FP像素数、以及类别频率占比。这三个放在一起才能判断IoU低到底是标注太差、类别太稀有还是模型根本没有学会这个类别的特征。顺便说一句有个常见的操作是把惩罚因子加进loss或评估里也就是weighted IoU——给罕见类别更高的权重让模型优先学它。这种做法的前提是你能拿到可靠的各类别IoU来反向设计权重系数而各类别IoU的计算能力正是这一步的地基。类别不均衡对mIoU的影响非常微妙一个做无人驾驶的朋友跟我说过他的血泪经验马路上行人这一类别只占全部像素的不到2%模型每帧图像都能错过分岔路口的那几个行人像素整个人行类别IoU就只剩0.3上下而总mIoU还能维持在0.72。这个0.72其实掩盖了安全隐患。所以如果做自动驾驶或遥感影像语义分割建议把每类IoU最低阈值写进验收标准而不是只看最终mIoU。3. 动手实现各类别mIoU计算PyTorch代码、参数与边界坑3.1 最小可用的mIoU实现预测输出如何对齐标注形状写代码之前先明确输入输出。训练好的模型输出是一张特征图形状通常是[B, C, H, W]其中C是类别数。要算mIoU首先要把这张特征图转成每个像素的预测类别也就是在通道维度上做argmax得到[B, H, W]的索引图。同时真实标注gt的形状一般是[B, H, W]每个位置存放类别编号边界往往是255或某个特殊值表示忽略区域。下面是我在PyTorch里常用的一个基础实现思路非常直白先算混淆矩阵再逐类取IoUimport torch def compute_miou_per_class(pred, label, num_classes21, ignore_index255): pred: [B, H, W] 已经做过argmax的预测类别索引 label: [B, H, W] 真实标注 num_classes: 类别总数包含背景 ignore_index: 需要忽略的标注像素值通常为255或-1 返回: 每个类别的IoU列表和整体的mIoU # 先忽略ignore_index的像素置为一个不影响后续统计的临时类别 pred pred.clone() label label.clone() ignore_mask (label ignore_index) label_copy label.clone() label_copy[ignore_mask] num_classes # 移到额外的bin里 # 定义有效像素mask并展平为一维 valid_mask (label ! ignore_index) p pred[valid_mask] l label_copy[valid_mask] # 用interruptible的bincount构造混淆矩阵 # 混淆矩阵形状: (num_classes1, num_classes1)最后一个bin是忽略像素 count torch.bincount(l * (num_classes 1) p, minlength(num_classes 1) ** 2) confusion count.view(num_classes 1, num_classes 1) # 只取前num_classes行和前num_classes列忽略bin是第num_classes行/列 confusion confusion[:num_classes, :num_classes] # 逐类计算IoU iou_list [] for c in range(num_classes): tp confusion[c, c].item() fp confusion[:, c].sum().item() - tp fn confusion[c, :].sum().item() - tp denom tp fp fn iou tp / denom if denom 0 else float(nan) iou_list.append(iou) # 只对有效类别求平均nan直接忽略 valid_iou [iou for iou in iou_list if iou iou] # 过滤nan miou sum(valid_iou) / len(valid_iou) if valid_iou else 0.0 return iou_list, miou这段逻辑很简单bincount把一个像素的(ground_truth类别, 预测类别)二元组线性化为一个整数下标然后统计频次最后reshape回混淆矩阵。有效像素这里只排除了ignore_index但实际场景中你很可能还需要排除预测为num_classes之外值的越界情况尤其是模型输出类别数和你统计类别数不一致时。另一个常见操作是把预测和标注都直接拉到0到num_classes-1范围内再统计但那样忽略了预测越界这一信息不利于排障。如果数据量大到用PyTorch的bincount都变慢可以用更简单的逐图累加方式替代但务必保持全局累加而不是逐图平均前文已经说过了全局累加的mIoU变化更平稳。3.2 训练中如何实时计算并记录各类别IoU记录频率与评估批次模型训练的时候每一步都算全量验证集的mIoU当然不现实常规做法是每隔N个epoch对验证集跑一次前向推理所有样本拼接后统一算mIoU。这里有个非常容易翻车的点验证集一次能塞进显存吗如果显存有限只能一块一块算每一块的预测结果不能急着算IoU而是要把所有batch的TP、FP、FN先按类别累加起来。正确姿势是写一个混淆矩阵累积器每跑完一个batch就把该batch的混淆矩阵加到全局混淆矩阵上最后统一计算IoU。这个和3.1中直接用全量像素计算本质上是一模一样的但如果你在每个batch上单独算IoU再平均结果会被样本数量不均衡严重影响。代码上把混淆矩阵累加器定义成类每个batch调用update接口传入pred和label训练结束时调用compute_miou。class ConfusionMatrixAccumulator: def __init__(self, num_classes, ignore_index255): self.num_classes num_classes self.ignore_index ignore_index self.confusion torch.zeros((num_classes, num_classes), dtypetorch.long) def update(self, pred, label): # pred和label形状均为[B, H, W] valid_mask (label ! self.ignore_index) p pred[valid_mask] l label[valid_mask] # 等价于3.1中的bincount逻辑这里直接用矩阵索引累加 count torch.bincount(l * self.num_classes p, minlengthself.num_classes ** 2) self.confusion count.view(self.num_classes, self.num_classes) def compute(self): ious [] for c in range(self.num_classes): tp self.confusion[c, c].item() fp self.confusion[:, c].sum().item() - tp fn self.confusion[c, :].sum().item() - tp denom tp fp fn ious.append(tp / denom if denom 0 else float(nan)) valid [iou for iou in ious if iou iou] return ious, sum(valid) / len(valid) if valid else 0.0注意update里pred必须已经是argmax之后的形状如果你直接把概率图传进来这个类的运算就会错得离谱。另外在训练时我建议记录频率不是每个epoch都打印全部类别IoU那样日志太长而是用trainer的eval回调每5个epoch打印一次全量类别IoU表格其余只打印mIoU和loss。真正发生突变的是某个特定类别IoU的波动而不是总mIoU这个细节能帮你早几个epoch发现问题。3.3 三张图像分割神器对比MMSegmentation与TorchMetrics如果你不想自己手写mIoU计算也可以用现成框架。MMSegmentation里有一个IoU的Metric类它内置了ignore_index、accumulate等参数底层就是混淆矩阵累加你只需要在配置里指定typeIoU它会在每个验证周期结束时输出各类别IoU和mIoU。这类框架的好处是省事坏处是如果配置不当比如忘记设ignore_indexNone导致标注边界被统计容易得到虚高或者虚低的指标。另外有一个轻量的选择是TorchMetrics的JaccardIndex它可以直接在训练循环里用支持num_classes、averagemacro或者nonenone正好返回每个类别的IoU列表。它的工程实现是纯Tensor化的延迟很低适合在训练循环里每个batch都算但要注意TorchMetrics里的averagemacro默认是加权平均还是算术平均不同版本行为有差异务必读源码确认。我个人的习惯是自己的实验代码里用3.1的手写版本因为能控住所有细节跑大批次对比实验时用MMSegmentation因为它自带验证流程和日志模块。两者算出来的数值应该极其接近如果出现偏差优先检查类别排序是否一致——很多框架的类别顺序是按ASCII码或者数据集字典序排的不一定是你心里想的背景在0号位。3.4 训练过程中mIoU不涨但loss下降可能哪里出了问题这个现象非常常见——训练loss持续下降但验证mIoU停滞甚至抖动。最经典的原因有两个。第一个是阈值偏移。如果你的模型输出没有做argmax而是直接拿概率图里的最大值当预测类别那么只要相对的类别概率大小关系对了loss已经很低了mIoU自然接近饱和。但如果各类别概率分布越来越尖锐但选错类别mIoU就会纹丝不动。这时你需要检查的是模型是否过度自信——看各类别平均置信度如果一个类别的平均置信度是0.97但IoU只有0.4那大概率是模型在瞎蒙一个高频类别。第二个是标注噪声。语义分割标注的边界其实非常主观城市景观数据集中某些类的边缘像素标注在不同标注员之间可能差异十几像素。如果你用MSE或CE loss硬学这些噪声标注模型输出的概率图会变得平滑但IoU可能上不去。这里有个玄学经验如果训练鲁棒性差可以把边缘几像素的标签抹掉相当于扩大ignore区域mIoU反而可能上涨1-2个点。这种做法本质上是在告诉模型别学我不确定的东西。4. 各类别mIoU的避坑指南五个最容易翻车的地方4.1 第一个坑ignore_index没处理好把背景或边界带进统计现象mIoU异常偏高或异常偏低一查发现某个类别——通常是背景——的IoU高达0.99其他类则全部低于0.3最终mIoU看似正常但完全没有参考价值。原因很多数据集在标注时边界像素、未标注区域或特定类别都被标成255或者-1如果代码里没有屏蔽这些像素它们会被当成背景类别统计进去背景的TP数量扩大好几倍IoU被拉飞了。解决检查评估代码里的valid_mask是否排除了ignore_index同时还要注意255这类值在PyTorch里会变成无符号大数直接参与bincount会把混淆矩阵撑爆。最安全的做法是先把label里所有ignore_index替换成一个在num_classes之外的孤立值再做掩码统计。4.2 第二个坑类别tensor的精度和device不一致导致静默出错现象训练时一切正常一到算mIoU就报错或者mIoU值跳变到不合理范围代码不报异常但结果看着诡异。原因pred经过了GPU上的argmax算出来的索引值label可能是从Dataset里读出来的PIL Image转的numpy数组两者一个是torch.float32一个是torch.int64且device不同。当你做pred[valid_mask]时类型被隐式转换数值根本不匹配导致混淆矩阵完全错位。解决统一 tensor 的类型和device最好统一为torch.long并放到CPU上算mIoU。还有一个容易被忽视的点有些数据集的标注类别从1开始而不是0此时要在训练前做一个整体减1的操作否则所有类别都错位mIoU直接变成0.1以下。4.3 第三个坑类别数没对齐预测的通道数和评估的num_classes不一致现象模型最后一层有19个通道但你评估时传的num_classes21或者反过来。结果就是某些类别几乎没有positive预测IoU异常低甚至有的类完全没出现在混淆矩阵里。原因定义模型分类头时用了num_classes变量定义评估器时可能硬编码或者从不同配置文件读取两处一旦没同步就会静默错位。解决写一个单例配置类或直接使用同一份config文件里的num_classes并且启动评估时打印模型类别数 vs 评估类别数的日志确保两者一致。这个检查应该写进CI或训练脚本里而不是手动确认。4.4 第四个坑小数位取舍导致论文复现时mIoU对不上现象自己实现在验证集上跑出的mIoU是0.674但论文报告的是0.680怎么调都复现不出。多方排查后发现问题出在统计方式差异——论文可能只在subset上评估或者每张图单独算IoU再平均或者四舍五入到小数点后一位。原因严格来说这不是实现的错而是评估协议不一致。不同数据集的官方评测工具有不同的边界处理方式比如Cityscapes在eval期间会把边界像素的预测类别忽略掉而PASCAL VOC则全部计入。解决看论文的Evaluation段落确认对方有没有提到ignore pixels、背景类是否算入mIoU、镜像翻转或其他数据增强是否应用在推理阶段。复现的时候务必使用官方评估脚本而不是自己写一套近似实现。如果实在没有官方脚本就在论文里说清楚自己的评估条件和忽略像素规则避免别人复现时产生疑虑。4.5 第五个坑类别不均衡导致小类IoU不升反降误判模型在变差现象某次加了数据增强或者改了loss权重总mIoU从0.75涨到0.76但小类别的IoU从0.31掉到0.28。有人会很紧张觉得模型退化了于是rollback版本。原因mIoU是算术平均你看到的0.01涨幅可能完全来自大类别的提升而小类别本来就只有几百个像素个别图像标注错误就足以让它的IoU波动几个百分点。这不是模型退化是统计噪声。解决画每个类别的TP/FP/FN随时间变化的曲线而不是只看IoU数字。如果小类别的TP在增加、FP也在略增一般说明模型在往正确方向走如果FP快速增长但TP不动说明模型在把小类别误分类成大类别。另外评估集如果太小建议做多次采样评估并报告均值±方差。5. 从mIoU再往前一步边界IoU与频率加权是什么值不值得投入5.1 边界IoU对边缘更敏感适合评估分割质量标准mIoU对所有像素的贡献是一视同仁的因此一张图上占大面积的类别主导了统计。边缘像素往往只占不到5%的比例但它们才是决定分割质量观感的关键。为了更精确地评估边缘质量业界提出了Boundary IoU它的思路很直接只统计预测和标注的边界区域附近的像素。实现上Boundary IoU通常先把标注和预测各做一次形态学腐蚀保留边界带然后在边界带内计算TP、FP、FN。如果模型边缘抖动严重标准mIoU可能只掉0.5-1个点但Boundary IoU能掉5-10个点放大问题。这个指标适合用在医学图像、卫星图像等对边界精度要求极高的任务中如果只是做传统的街景分割它的增益未必明显但可以做参考指标来观察模型瓶颈。5.2 类别频率加权mIoU什么时候该用它来替代原版mIoU对所有类别一视同仁但如果你的应用核心是背景和大面积物体小类别几乎不影响安全性那么不加权确实说不过去。Frequency Weighted IoUFWIoU就是把每个类别的IoU按像素频率加权后求平均这样占比高的类别对指标的贡献更大数值上更接近人眼对整体视觉质量的判断。FWIoU的值通常比mIoU高因为高频类别的IoU一般也更高。在做产品验收时我会同时报告mIoU和FWIoU前者告诉算法团队模型在小类上的极限后者告诉产品团队模型在真实场景中的体感效果。如果你的目标是发论文或比赛冲榜建议锁定mIoU因为它是通行语言如果目标是业务交付两者都看但验收线画在FWIoU上更贴合实际。5.3 训练时直接优化mIoU可微近似和置信度校准的两个选择既然mIoU是最终评价指标自然有很多人想在训练时直接优化它。mIoU本身不可微因为它内部有argmax操作所以常规做法是用Lovasz-Softmax或者Dice loss做代理它们分别从排序损失和集合相似度的角度近似mIoU。Lovasz-Softmax的做法是把每个像素的误差按大小排序然后对排序后的误差向量做凸损失的逐步优化它的梯度能让模型在训练过程中优先修正那些IoU贡献大的误分类像素。Dice loss则直接对每个类别的soft输出和标签的重叠程度求梯度对小类别天然有放大作用。我自己在遥感影像分割里试下来的经验是Lovasz-Softmax作为辅助loss与CrossEntropy混合mIoU能比纯CE高0.5-1.5个点但要注意让辅助loss的权重不要过大初始设0.1-0.3之间再根据loss数值动态调整。如果你追求的是工程上的确定性还有一种间接但有效的思路先用标准CE训练到mIoU平台期再冻结backbone只训head并用Dice loss微调20个epoch往往能再挤出一点mIoU。这种两段式训练不需要改模型结构只是换了一下loss调度策略性价比很高。5.4 大模型时代的新问题类别加权与logit缩放对mIoU的影响这两年用的分割模型越来越大有的直接在CLIP、SAM预训练权重上微调。在这种大模型上各类别的logit尺度差异可能非常大直接做argmax取值会有系统性偏差。你会看到某个高频类别几乎霸屏小类别全部消失mIoU低得离谱。解决方法是给logits做类别级别的温度缩放也就是在argmax前对每个类别的logit乘以一个可学习的系数这个系数用验证集的混淆矩阵来调优。本质上等于在评估时做一个类别偏置的补偿。这个技巧如果配合类别频率加权使用可以显著提升小类别的IoU代价是多了一个超参数需要小心过拟合验证集。一个血泪经验是这个缩放系数必须在另一个验证集上确认不能在训练集上反推否则会把训练集的偏差放大到评估结果上。6. 验证和调参技巧如何用各类别mIoU反向定位模型问题6.1 可视化混淆矩阵一眼看出哪些类别互相打架一个模型mIoU只有0.63但你不知道是所有类别都差一点还是两三个类别互相严重混淆。这时候把整个验证集的混淆矩阵画出来类别之间互相错认的规律一清二楚。在Python里可以直接用matplotlib的imshow配合colorbar画混淆矩阵图行是真实类别列是预测类别。你通常会发现一个规律形状相似或语义相近的类别之间互相混淆严重比如卡车vs公交车、植被vs树。看到这种模式就该考虑给模型加更多上下文信息或者在解码器阶段对不同类别分支做区分。另一个常见规律是一切类别向背景靠拢如果背景列的数值整体偏高说明模型把前景当背景的概率很大这是类别不均衡下的典型表现此时可以考虑给前景类别的loss加权重。6.2 per-image监测哪张图的mIoU最低定位数据集本身的坑除了全局混淆矩阵逐张计算图像IoU并排序找到最低的几张图去肉眼检查是定位数据问题的利器。我经常发现mIoU最低的图像几乎全是标注有问题的图——比如路牌被遗漏、小型车辆在远距离完全没标注。这类情况光看指标根本无法发现但一旦发现把它们从验证集里剔除或修正mIoU可能立刻提升1-2个点。做法不复杂在eval循环里为每张图像维护一个独立的mIoU列表跑完后按值排序把后5%的图像路径导出到一个txt文件再配合可视化脚本截图保存。需要注意的是这种挑刺只能在调试阶段做一旦决定用某个测试集做最终验收就不要再反复挑刺删图否则指标失去了公正性。这里有一个折中的做法把评估数据分成两个集合一个用于日常迭代找bug一个最终定版只跑一次。6.3 把mIoU拆成precision和recall绕开指标掩盖问题IoU是precision和recall的一种复合形态但它把两者压缩成单个数字有时会掩盖问题。比如某个类别IoU是0.5但你的precision可能是0.9recall只有0.32也就是说模型很少漏检但误检极多导致大量FP在分母里膨胀。这种情况在很多目标比较稀疏的任务里很常见。排查时我给每类打印precision和recall再看看边界。如果recall低但是precision高考虑降低置信度阈值、增加正样本的采样如果precision低但recall高则考虑收紧边界或者增加负样本。本质上这可以让你判断mIoU低是定位不准还是检测太少。6.4 多尺度与TTA推理是否能提升mIoU我自己的经验和判断常见做法是在推理时做多尺度缩放并融合结果最典型的TTA配置是[0.5, 0.75, 1.0, 1.25, 1.5]五种尺度再把预测的概率图缩放回原分辨率做平均。这个操作通常能让mIoU涨0.3-0.8个点尤其是对边缘细节和尺度变化明显的遥感场景增益明显。另一个效果很好的技巧是水平翻转做TTA成本只有一次额外前向推理。但老实说TTA带来的提升在大型backbone上并不总是值得的因为推理时间翻倍甚至五倍。最终是否需要TTA取决于业务场景的延迟要求。如果你服务端推理有50ms预算加一次水平翻转可能刚好能承受如果是实时视频流就别做TTA了。这里有一个实际参数建议验证阶段用TTA做best model的最终确认训练过程中的epoch评估不要开TTA因为会拖慢实验循环。我自己的习惯是先不开TTA跑完一个训练周期选出一个候选模型然后只对这个候选模型开TTA做最终报告这样既保证了验证的严谨性又不拖慢实验迭代速度。6.5 给论文或项目交付的一个mIoU报告模板避免被质疑不管你是发论文还是交付项目最终团队之间对mIoU的理解不一致会带来巨大扯皮。建议报告至少包含以下内容数据集名称和图像数量类别列表和每类的像素占比confusion matrix图每类precision、recall、IoU和整体mIoU表格是否忽略边界像素、ignore_index值推理时是否使用TTA及多尺度参数单GPU还是多GPU平均、world size是否影响BatchNorm统计量以及评估脚本的commit版本。把这些写进报告后别人复现你的数字就非常容易了。如果你的评估脚本和训练脚本不在同一个仓库递交给对方时顺便附上一个版本号让验收方知道自己的代码对没对上。被质疑mIoU造假是一件极痛苦的事而这种事大多源于评测约定不清楚而不是真正的伪造。最后说一个我自己的教训在遥感影像语义分割项目上有一次我从开源的MMSegmentation换到自己的轻量推理管线结果mIoU掉了0.03。我花了整整两天排查最后发现是Resize时插值方式从bilinear变成了nearest导致边界像素发生了微妙偏移。从那天起我给自己立了一个规矩任何评估代码的改动都用同一组测试图像跑一遍新旧管线对比mIoU差异差异超过0.1个点就必须找到原因再继续。这个习惯救了我很多次。希望帮到你。本文还有配套的精品资源点击获取