医学图像分割这个方向模型结构的花样年年翻新但真正在论文和项目里反复被拿出来说事的评价指标来来回回就那么几个。Dice 和 HD95 是其中出现频率最高的两个几乎到了“不报这两个数就不算完整评估”的程度。可实际做项目的时候我发现很多人对这两个指标的理解停留在“Dice 越高越好、HD95 越低越好”的层面真到了自己写评估代码、对比模型、跟临床医生解释结果的时候问题就全冒出来了为什么两个模型 Dice 差不多HD95 差了一大截为什么小目标上 Dice 看着还行实际分割结果却惨不忍睹为什么自己算的 HD95 和别人论文里的对不上这篇就把 Dice 和 HD95 这两个指标从头到尾拆一遍。不光是给公式更要讲清楚每个指标背后的几何直觉、计算时的坑、以及在不同任务场景下该怎么解读。适合正在做医学图像分割的研究生、算法工程师也适合需要看懂评估结果的临床合作方。读完你应该能自己手写这两个指标的计算逻辑并且知道什么时候该信它、什么时候该警惕它。1. 从混淆矩阵到Dice这个指标到底在衡量什么1.1 Dice的本质是重叠度不是准确率先把最基础的概念摆正。Dice 系数Dice Similarity CoefficientDSC衡量的是两个集合的重叠程度。在分割任务里这两个集合就是“预测为前景的像素集合”和“真实前景的像素集合”。公式长这样Dice 2 * |A ∩ B| / (|A| |B|)其中 A 是预测前景像素集合B 是真实标注前景像素集合|·| 表示集合的元素个数也就是像素数量。分子乘 2 是为了让完全重合时 Dice 等于 1完全不重合时等于 0把值域规范到 [0, 1]。这里有个很多人第一次接触时会绕进去的点Dice 和 IoU交并比长得像但不是一回事。IoU 是交集除以并集IoU |A ∩ B| / |A ∪ B|两者的关系可以推导出来Dice 2 * IoU / (1 IoU) IoU Dice / (2 - Dice)也就是说 Dice 和 IoU 是单调对应的一个高另一个必然高。但 Dice 的数值通常比 IoU 大同样一个分割结果IoU 可能是 0.75Dice 能到 0.857。这就是为什么有些论文报 Dice 不报 IoU——数字好看。你在对比不同论文的结果时一定要看清楚人家报的是哪个别拿 Dice 去跟 IoU 比。那为什么医学图像分割偏爱 Dice 而不是简单的像素准确率Accuracy原因很直接医学图像里前景往往占比极小。一个肝脏 CT 里肝脏可能只占整张图的 5% 甚至更少。如果模型把所有像素都预测成背景Accuracy 能到 95% 以上看着很漂亮但实际一个前景像素都没分出来。Dice 在这种情况下会直接掉到 0 附近因为它只看前景集合的重叠背景预测得再多也不加分。这就是 Dice 对类别不平衡天然鲁棒的原因。1.2 从TP/FP/FN的角度重新理解Dice把 Dice 用混淆矩阵的术语写出来会更容易和检测、分类的指标打通Dice 2 * TP / (2 * TP FP FN)TP 是预测为前景且确实是前景的像素FP 是预测为前景但实际是背景的像素误报FN 是预测为背景但实际是前景的像素漏报。这个写法揭示了一个关键事实Dice 同时惩罚 FP 和 FN而且惩罚权重相同。你多分出去一块FP和少分了一块FN对 Dice 的伤害是一样的。这跟有些指标只关注某一侧不一样。比如召回率Recall只关心 FN你把整张图都预测成前景Recall 就是 1但 Dice 会很低因为 FP 爆炸了。我在实际项目里经常用这个性质来判断模型的行为模式。如果两个模型 Dice 一样我会去看它们的 FP 和 FN 分布一个可能是“保守型”漏报多误报少另一个是“激进型”误报多漏报少。在临床场景下这两种错误的代价完全不同——肿瘤分割里漏报一个病灶可能比多标一块组织严重得多这时候你就不能只看 Dice 的总分得把 Recall 和 Precision 也拉出来看。1.3 Dice在多类别分割里怎么算才不出错单类别前景的 Dice 很直观但医学分割经常是多类别的比如脑肿瘤分割要分增强肿瘤、瘤周水肿、坏死核心三类。这时候 Dice 的计算方式就有讲究了常见的有两种宏平均Macro-average对每个类别分别算 Dice然后取算术平均。每个类别权重相同小类别和大类别一样重要。微平均Micro-average把所有类别的 TP、FP、FN 分别加起来再用总体的 TP/FP/FN 算一个 Dice。大类别因为像素多会主导最终结果。这两种算法出来的数值可能差很多。假设一个三分类任务类别 A 有 10000 个像素 Dice 0.9类别 B 有 1000 个像素 Dice 0.7类别 C 有 100 个像素 Dice 0.3。宏平均是 (0.90.70.3)/3 0.633微平均会被类别 A 拉高到 0.85 以上。如果你关心的是小类别比如临床上的小病灶的分割质量必须用宏平均否则小类别的糟糕表现会被大类别的像素数量淹没。提示论文里报多类别 Dice 时一定要写清楚是宏平均还是微平均以及是否包含背景类。我见过不少复现失败的情况最后发现是背景类算没算进去导致的差异。还有一个容易踩的坑空类别的处理。如果某个类别在某张图里根本不存在真实标注里没有而模型也没预测出来这时候 TPFPFN0Dice 的公式变成 0/0。代码里如果不做特殊处理要么报错要么返回 NaN。常见的做法是这种情况约定 Dice1因为模型确实正确地没有预测出这个不存在的类别或者直接跳过这个类别不计入平均。两种做法都有道理但必须在评估协议里统一否则不同实现之间没法比。2. HD95的几何直觉为什么它比Dice更能暴露边界问题2.1 从表面距离说起Dice 有个天生的盲区它只看像素集合的重叠不关心重叠区域的空间分布。举个极端的例子一个圆形病灶模型预测的结果是一个同样大小但位置偏了半个病灶的圆。这两个圆的像素重叠可能还有不少Dice 也许能到 0.6 以上但实际分割结果在临床上是完全不可用的——边界全错了。HD95 就是为了解决这个问题而存在的。它的全称是 95th Percentile Hausdorff Distance中文叫“95% 豪斯多夫距离”。要理解 HD95得先理解豪斯多夫距离HD。豪斯多夫距离衡量的是两个点集之间的“最大不匹配程度”。具体定义是对于集合 A 中的每个点找到它到集合 B 中最近点的距离这些距离里取最大值记为 h(A, B)反过来对 B 也做一遍得到 h(B, A)两者取较大值就是 HD。HD(A, B) max( h(A, B), h(B, A) ) h(A, B) max_{a∈A} min_{b∈B} ||a - b||用人话说HD 是“从 A 出发走到 B 的最远距离”和“从 B 出发走到 A 的最远距离”中的较大者。它衡量的是两个集合之间最糟糕的那个点的匹配情况。在分割任务里A 和 B 不是前景区域的所有像素而是前景区域的边界轮廓surface。因为内部像素的距离没有意义真正决定分割质量的是边界贴合程度。所以计算 HD 之前要先提取两个 mask 的表面轮廓点集。2.2 HD为什么需要改成HD95原始 HD 有个致命问题它对离群点极其敏感。只要有一个预测像素离真实边界特别远比如模型在图像角落误分了一个孤立的小区域HD 就会直接爆炸到那个最远距离。这个值可能完全不能反映整体分割质量因为 99.9% 的边界都贴合得很好就那一个点毁了整个指标。HD95 的做法是把所有表面点对之间的距离排序取第 95 百分位数而不是取最大值。这样就把最极端的 5% 离群点排除掉了指标更稳健更能反映“绝大多数边界点”的贴合情况。这个改动看起来简单但意义重大。它把 HD 从一个“最坏情况”指标变成了一个“典型最坏情况”指标。在医学图像里标注本身就有不确定性边界附近几个像素的差异是正常的用原始 HD 会把这些正常波动放大成巨大的数值。HD95 则容忍了这部分噪声。2.3 HD95的数值该怎么解读HD95 的单位是像素或毫米如果做了物理间距换算。它的含义是在 95% 的表面点中预测边界和真实边界之间的距离都不超过这个值。举个例子HD95 5.2 mm意思是预测边界和真实边界之间95% 的点距离在 5.2 mm 以内。剩下 5% 的点可能更远但我们认为那是离群点不纳入主要评估。那多大的 HD95 算好这没有绝对标准取决于任务和图像分辨率。一般来说任务类型典型可接受 HD95 范围说明大器官分割肝、肺2-5 mm边界相对清晰器官体积大中等结构心脏、前列腺3-8 mm边界有一定模糊性小病灶肺结节、肿瘤5-15 mm体积小几个像素的偏差就很大细长结构血管、神经1-3 mm对边界精度要求极高这张表只是经验参考实际项目里更重要的是看同一批数据上不同模型的相对表现而不是纠结绝对值。另外要注意HD95 对图像的空间分辨率很敏感。同一个分割结果在原始分辨率下算 HD95 和在降采样后的图像上算数值可能差好几倍。所以对比不同论文的 HD95 时一定要确认它们的图像预处理和体素间距是否一致。3. 手写Dice和HD95那些论文里不会告诉你的实现细节3.1 Dice的实现看似简单坑在边界情况Dice 的实现逻辑很直接但真正写健壮的代码要考虑不少边界情况。下面是一个比较完整的 PyTorch 实现import torch def dice_coefficient(pred, target, smooth1e-6): pred: 预测mask, shape (N, H, W) 或 (N, D, H, W), 值为0或1 target: 真实mask, 同shape smooth: 平滑项, 防止0/0 pred_flat pred.reshape(pred.shape[0], -1) target_flat target.reshape(target.shape[0], -1) intersection (pred_flat * target_flat).sum(dim1) union pred_flat.sum(dim1) target_flat.sum(dim1) dice (2. * intersection smooth) / (union smooth) return dice.mean()这里有几个细节值得说smooth 项加在哪里。有人只在分母加 smooth有人分子分母都加。标准做法是分子分母都加同一个 smooth这样当 pred 和 target 都为空时Dice 返回 1分子 smooth / 分母 smooth符合“正确地预测了空”的语义。如果只在分母加空集情况下返回 0语义就错了。smooth 取多大。1e-6 是常见值但如果你的图像像素值范围很大或者用了浮点概率图可能需要调整。smooth 太大会影响正常情况下的精度太小又起不到防止除零的作用。一般用 1e-5 到 1e-7 之间比较稳妥。pred 要不要二值化。如果你的 pred 是模型输出的概率图经过 sigmoid 后直接代入公式算的是“软 Dice”这在训练时作为损失函数是合理的。但评估时必须先二值化通常阈值 0.5否则算出来的 Dice 没有明确的几何意义也没法和别人的结果对比。多类别怎么处理。如果是 one-hot 形式的多类别 mask可以对每个通道分别算 Dice 再平均def multiclass_dice(pred, target, num_classes, smooth1e-6): pred: (N, C, H, W) one-hot target: (N, C, H, W) one-hot dice_per_class [] for c in range(num_classes): d dice_coefficient(pred[:, c], target[:, c], smooth) dice_per_class.append(d) return torch.stack(dice_per_class).mean()注意这里没有排除背景类。如果背景类也参与平均而背景通常分割得很好Dice 接近 0.99会把整体数值拉高。很多论文报的 Dice 是包含背景的有些是不包含的差个几个百分点很正常。3.2 HD95的实现表面提取是关键HD95 的实现比 Dice 复杂得多核心难点在于表面点的提取和距离计算。我见过太多人直接用 scipy 的directed_hausdorff函数算结果和论文对不上问题基本都出在表面提取这一步。先讲表面提取。对于二值 mask提取表面的方法有几种方法一形态学梯度。对 mask 做膨胀再减去原 mask得到的就是表面像素。import numpy as np from scipy import ndimage def extract_surface(mask): 提取二值mask的表面像素 eroded ndimage.binary_erosion(mask) surface mask ~eroded return surface方法二轮廓提取。用skimage.measure.find_contours提取亚像素精度的轮廓点。方法三距离变换法。计算 mask 内部每个像素到背景的距离距离小于阈值的像素算作表面。这三种方法提取出来的表面点集不完全一样算出来的 HD95 也会有差异。方法一简单快速但表面是像素级的精度受限于分辨率。方法二能得到亚像素精度但轮廓点的数量和分布不均匀。方法三可以控制表面厚度但阈值不好定。我的经验是如果只是做模型间的相对比较方法一足够了因为所有模型用同样的表面提取方式偏差是系统性的不影响排序。但如果要和论文数值对齐必须搞清楚论文用的是什么方法。nnU-Net 框架用的是基于形态学的方法很多论文直接沿用它的实现。距离计算部分核心是对于表面点集 A 中的每个点找到它在表面点集 B 中的最近点距离。暴力计算是 O(|A| * |B|)对于大图像会非常慢。实际实现一般用 KD-Tree 加速from scipy.spatial import cKDTree def hd95(pred_surface, target_surface): pred_surface: 预测表面点坐标, shape (M, ndim) target_surface: 真实表面点坐标, shape (K, ndim) tree_target cKDTree(target_surface) tree_pred cKDTree(pred_surface) # pred中每个点到target的最近距离 dist_pred_to_target, _ tree_target.query(pred_surface) # target中每个点到pred的最近距离 dist_target_to_pred, _ tree_pred.query(target_surface) # 合并所有距离 all_distances np.concatenate([dist_pred_to_target, dist_target_to_pred]) # 取95百分位 hd95_value np.percentile(all_distances, 95) return hd95_value这里有个关键选择百分位数是在合并后的距离上取还是分别对两个方向取再取最大。这两种做法结果不同。做法一合并取百分位把所有 A→B 和 B→A 的距离混在一起取第 95 百分位。这是目前最主流的做法MedPy 库就是这么实现的。做法二分别取百分位再取最大先算 A→B 距离的第 95 百分位再算 B→A 距离的第 95 百分位取两者较大值。这种做法更接近原始 HD 的定义逻辑但用得少一些。两种做法在大多数情况下数值接近但在边界情况比如预测和真实大小差异很大下可能差不少。跟论文对比时要注意。3.3 物理间距换算一个被严重低估的细节医学图像CT、MRI的体素通常不是各向同性的。比如 CT 的层厚可能是 1mm但层内像素间距是 0.7mm。如果你直接在像素坐标系里算 HD95得到的是“像素距离”这个数值在不同分辨率的图像之间没有可比性。正确的做法是把像素距离乘以物理间距换算成毫米。对于 3D 图像每个方向的间距可能不同def compute_hd95_mm(pred_surface, target_surface, spacing): spacing: (dx, dy, dz) 物理间距, 单位mm # 将像素坐标转换为物理坐标 pred_phys pred_surface * np.array(spacing) target_phys target_surface * np.array(spacing) tree_target cKDTree(target_phys) tree_pred cKDTree(pred_phys) dist_pred_to_target, _ tree_target.query(pred_phys) dist_target_to_pred, _ tree_pred.query(target_phys) all_distances np.concatenate([dist_pred_to_target, dist_target_to_pred]) return np.percentile(all_distances, 95)这个细节在论文里经常被一笔带过但实际影响很大。我做过一个实验同一个分割结果用像素距离算 HD95 是 3.2用物理距离算是 4.8mm差了 50%。如果你在复现别人的工作时发现 HD95 对不上先检查 spacing 有没有处理对。注意有些数据集如 MSD提供的图像已经重采样到各向同性这时候 spacing 是均匀的但也不能省略换算因为像素间距通常不是 1mm。4. 两个指标打架的时候该信谁4.1 Dice高但HD95差的典型场景实际项目里最常见也最让人头疼的情况就是 Dice 和 HD95 给出矛盾的信号。我整理了几种典型场景场景一大器官分割边界模糊。肝脏分割中肝脏和周围组织的边界在 CT 上对比度不高模型可能把边界往外扩了几个像素。因为肝脏体积大这几个像素对 Dice 影响很小可能只掉 0.01但 HD95 会明显变大因为边界上每个点都偏了。这种情况下 Dice 会给你“模型很好”的错觉HD95 才是真相。场景二小目标分割一个离群点。肺结节分割中模型主体分得很好但在远处误分了一个小区域。Dice 可能只掉一点点但 HD95 会因为这个离群点直接翻倍。这时候 HD95 又过于悲观了需要结合可视化判断。场景三多连通区域。真实标注是一个连通的肿瘤模型预测成了两个分离的区域。Dice 可能还有 0.7但 HD95 会很大因为两个区域之间的间隙在表面距离上体现得很明显。场景四空洞。模型预测的区域内部有个洞Dice 受影响不大洞的面积小但 HD95 会捕捉到这个洞的边界到真实边界的距离。这些场景说明一个道理Dice 衡量的是“整体重叠质量”HD95 衡量的是“边界最差情况”。两者互补不能互相替代。我在项目里评估模型时从来不会只看一个指标。通常的做法是Dice 作为主要排序指标因为它稳定、直观、和临床的“分割准确度”感知最接近HD95 作为辅助指标用来发现 Dice 掩盖的边界问题再加上 Recall 和 Precision判断模型的错误偏向关键案例一定要可视化指标只是筛选工具最终判断还得靠眼睛4.2 不同任务下两个指标的权重分配不同临床任务对分割质量的要求不一样两个指标的权重也应该不同放射治疗计划中的靶区勾画边界精度直接关系到照射范围HD95 的权重要高。靶区多勾出去 2mm 可能意味着正常组织多受照射。这种情况下即使 Dice 很高HD95 不达标也不能接受。器官体积测量比如肝脏体积评估关心的是总体积准确度内部像素的归属比边界几个像素的偏差更重要。Dice 权重更高HD95 可以适当放宽。病灶检测和随访关心的是病灶有没有被找到、大小变化趋势。Recall 最重要Dice 次之HD95 参考即可。手术导航对实时性和边界精度都有要求HD95 和 Dice 都要看而且对推理速度有硬约束。我一般会在项目开始时就根据临床需求确定一个“指标优先级”写进评估协议里避免后面模型选型时来回扯皮。4.3 指标之外的陷阱标注质量本身说了这么多指标有个根本问题不能回避如果真实标注本身就不准再精确的指标也没有意义。医学图像分割的标注依赖医生手工勾画不同医生之间、同一医生不同时间勾画的结果都有差异。这个差异叫做“观察者间差异”inter-observer variability。有研究显示即使是经验丰富的放射科医生在肝脏分割上的 Dice 一致性也只有 0.95 左右在更复杂的结构上可能只有 0.85。这意味着什么意味着如果你的模型 Dice 达到了 0.92而医生之间的一致性上限是 0.95那你的模型已经接近“人类水平”了再往上提升的空间很小。反过来如果模型 Dice 是 0.88看起来不高但考虑到标注本身的噪声可能实际表现比数字显示的要好。所以在解读 Dice 和 HD95 时一定要有一个参照系人类专家之间的一致性水平。这个数据通常需要你自己在数据集上算——让多个医生标注同一批数据计算他们之间的 Dice 和 HD95。这个基线比任何绝对阈值都有意义。我在一个项目里就吃过这个亏。模型 Dice 0.91团队觉得不够好花了两周调模型调到 0.93。后来算了医生间一致性是 0.92才发现我们早就过了有意义的提升区间那两周基本白干。从那以后我拿到任何新数据集第一件事就是评估标注一致性。5. 评估流程的工程化别让指标计算成为瓶颈5.1 评估代码的常见性能问题HD95 的计算在 3D 图像上可能非常慢。一个 512×512×200 的 CT 体积表面点可能有几十万个KD-Tree 构建和查询都要花时间。如果测试集有几百个病例评估跑几个小时是常事。几个优化方向降采样。如果只是做模型间的相对比较可以对表面点做随机降采样比如只取 10000 个点。HD95 是统计量采样足够多时数值会收敛。我试过用 5000 个点采样和全量计算的 HD95 差异在 0.1mm 以内但速度快了十几倍。GPU 加速。距离计算可以搬到 GPU 上用矩阵运算做对于中等规模的点集比 KD-Tree 快。但显存是个限制点太多会爆。并行化。不同病例之间是完全独立的用多进程并行评估是最直接的加速方式。Python 的multiprocessing或者 joblib 都能做。缓存表面点。如果同一个真实标注要和多个模型的预测结果比较真实标注的表面点只需要提取一次缓存起来复用。5.2 评估协议要固定哪些东西为了让评估结果可复现、可对比评估协议里必须明确以下内容项目必须明确的内容常见坑二值化阈值pred概率图转mask的阈值不同阈值Dice差几个点表面提取方法形态学/轮廓/距离变换不同方法HD95差10-20%百分位数计算合并取还是分别取边界情况差异明显物理间距是否换算、spacing来源不换算则无法跨数据集比较多类别平均宏平均/微平均、含不含背景差几个百分点空类别处理跳过还是记1影响小类别表现评估后处理有没有去除小连通域对HD95影响巨大这张表里的每一项我都见过因为没统一而导致对比失败的案例。最隐蔽的是后处理有些模型推理后会做连通域分析去掉小于一定体积的孤立区域。这个操作对 Dice 影响很小但能把 HD95 从 20mm 降到 5mm。如果对比的两个模型一个做了后处理一个没做HD95 的对比就完全没意义。5.3 用MedPy和MONAI算指标的注意事项实际项目中很少有人从零写指标计算大多用现成的库。最常用的是 MedPy 和 MONAI。MedPy 的hd95函数from medpy.metric.binary import hd95, dc # 注意MedPy要求输入是布尔数组且前景为True dice dc(pred.astype(bool), target.astype(bool)) hd hd95(pred.astype(bool), target.astype(bool), voxelspacing(1.0, 0.7, 0.7))MedPy 的坑输入必须是布尔数组传入 0/1 整数数组可能报错或结果不对voxelspacing参数如果不传默认是 1算出来的是像素距离当 pred 或 target 全空时hd95会返回 NaN 或报错需要自己处理它的表面提取用的是形态学方法和 nnU-Net 的实现有细微差异MONAI 的compute_hausdorff_distancefrom monai.metrics import compute_hausdorff_distance, compute_dice_metric hd compute_hausdorff_distance( y_pred, y, include_backgroundFalse, percentile95, directedFalse, spacing(1.0, 0.7, 0.7) )MONAI 的坑include_background默认是 True多类别时记得改directedFalse才是双向 HDdirectedTrue只算单向它内部对表面提取有自己的实现和 MedPy 不完全一致返回的是每个 batch 每个类别的值需要自己做聚合我的建议是选定一个库之后整个项目统一用这个库不要混用。如果必须和论文对比先确认论文用的是什么库什么版本然后用同样的设置复现一遍确认能对上再开始自己的实验。6. 从指标反推模型问题一些实战经验6.1 用Dice和HD95的组合定位问题类型把 Dice 和 HD95 放在一起看能大致判断模型的问题出在哪里DiceHD95可能的问题排查方向低高整体分割失败检查数据预处理、标签对齐低低小目标漏分或大目标部分缺失检查类别不平衡、损失函数高高边界模糊或存在离群预测检查边界损失、后处理高低分割质量好正常可进一步看细分指标这个表不是绝对的但能给你一个排查的起点。比如 Dice 0.85、HD95 15mm 这种情况大概率是边界问题可以考虑加边界损失Boundary Loss或者在推理后做形态学平滑。6.2 边界损失能不能救HD95说到 HD95 差很多人第一反应是加边界损失。Boundary Loss、Hausdorff Loss 这些确实是为改善边界而设计的。但实际用下来效果因任务而异。Boundary Loss 的核心思想是在边界像素上加权让模型更关注边界区域。它在边界清晰的任务上比如骨骼分割效果明显但在边界本身就模糊的任务上比如肿瘤水肿区提升有限因为模型学不到明确的边界。Hausdorff Loss 直接优化 HD 的近似理论上最对口。但它对离群点敏感的问题会传递到训练中导致训练不稳定。实践中常用的是它的平滑版本或者和 Dice Loss 组合使用。我的经验是先确认 HD95 差是不是真的边界问题。如果可视化发现预测结果整体偏移或者有大的离群区域那加边界损失没用得先解决定位问题。只有确认边界贴合是主要矛盾时边界损失才值得尝试。6.3 后处理对HD95的“作弊式”改善有些后处理操作能显著改善 HD95但对实际分割质量提升有限我称之为“指标作弊”去除小连通域把小于 N 个像素的孤立预测区域去掉。这能消除离群点HD95 大幅下降但如果那个小区域是真实的小病灶你就把它误删了。最大连通域保留只保留最大的连通区域。对单目标分割有效但多病灶任务直接废掉。形态学闭运算填补小空洞平滑边界。对 HD95 有改善但可能把两个相邻的结构连在一起。边界平滑用样条曲线拟合边界再重新填充。视觉上好看HD95 也好看但可能偏离真实边界。这些操作不是不能用但必须清楚它们的代价。我的原则是后处理只能用于消除明确的噪声不能用于“美化”指标。如果后处理带来的 Dice/HD95 提升超过 5%我会非常警惕一定要可视化确认没有引入新的错误。7. 写在最后的一点个人体会Dice 和 HD95 这两个指标我用了快五年踩过的坑比想象中多。最开始觉得公式简单实现也就几行代码能有什么问题。后来才发现真正的难点不在公式本身而在评估协议的设计和结果的解读。同一个模型换一种表面提取方法HD95 能差 20%换一个二值化阈值Dice 能差 3 个点换一个多类别平均方式整体 Dice 能差 5 个点。这些差异在论文里往往被一笔带过但在实际项目里它们决定了你的模型选型是否正确。我现在做任何分割项目评估部分会花和模型开发差不多的时间。先把评估协议定死写成一个独立的、有单元测试的模块所有模型都用同一套代码评估。指标计算的结果会做交叉验证——用两个不同的库算同一批数据确认数值一致。关键病例一定会可视化指标只是筛选眼睛才是最终裁判。还有一点不要迷信指标的绝对值。Dice 0.90 不一定比 0.88 好如果 0.90 那个模型在临床关心的关键区域表现更差的话。指标是工具不是目的。理解指标背后的几何意义和临床意义比记住公式重要得多。