开始前,会这些就够了
- 知道预测与真实标签的区别
- 能读列表、求和与简单导数
- 先读 同一批分类结果,为什么能算出三个总分?:按该文准备相关概念。
一张图的前景完全正确,另一张图的前景完全错开。两张分数是 1 和 0,平均显然是 0.5。把两张图的像素合起来再算,却得到 0.9。这里没有算术错误:你改变了每张图在总分里的份量。
本例只讨论一个前景通道,标签是 0 或 1,预测可用概率。先明确聚合规则,再处理一张图里完全没有前景时的特殊情况。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;制作时实测 Windows / Python 3.14.0。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- dice_aggregation.py:完整可运行示例
- README.md:运行说明与独立练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和产物验证记录
- SHA256SUMS.txt:内容摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B dice_aggregation.py解压到独立练习文件夹,在含 dice_aggregation.py 的目录打开终端运行。已运行人工掩码聚合、空标签梯度与有限差分;未运行 MONAI、训练网络、处理真实图像或进行临床评测。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
先把分子和分母单独保存
本文采用 D=(2Σpᵢyᵢ+a)/(Σpᵢ+Σyᵢ+b)。pᵢ 是第 i 个像素属于前景的预测概率,yᵢ 是 0 或 1 的真实标签;a 加在分子,b 加在分母。损失为 L=1−D,分数越高,损失越低。
p 和 y 都是二值时,Σpᵢyᵢ 数的是交集,分母是两份前景像素数相加。概率预测时,它是软版本;把概率先按阈值改成 0/1,再算评测指标,是另一条计算路线。
V-Net 的公式采用平方分母 Σpᵢ²+Σyᵢ²。二值掩码上二者相同,概率预测上并不相同。本文固定线性分母,不能把它的梯度直接套到平方分母版本。
大目标可以把小目标的失败盖过去
第一张图有 9 个前景像素,全部预测正确,分子 18、分母 18,D₁=1。第二张预测为 [1,0],标签为 [0,1],交集为零,分母为 2,D₂=0。这里暂设 a=b=0。
逐图平均给每张图同样份量:(1+0)/2=0.5。先合并再相除得到 (18+0)/(18+2)=0.9。合并结果恰好等于按各图分母加权的分数平均:18/20×1+2/20×0。
所以对同一数据,把两张图分别做两个 batch 再平均,与放入同一 batch 合并计算,结果可能不同。报告应写清楚是逐图宏平均还是合并像素;不要只写“mean Dice”。该例是跨图聚合,类别之间的平均还需要另一份约定。
空掩码不是一个小数点问题
预测和标签都为空时,原式是 0/0。若分子分母都加同一个正数 ε,结果是 1;若只在分母加 ε,结果是 0。也可以在评测中把某类空样本记为缺失并排除,但必须同时报告有效样本数。
空标签、非零预测时,如果分子完全不加平滑数,分子恒为零,Dice 损失恒为 1,对预测概率的导数也为零。这意味着该形式单独使用时,在这类样本上没有压低假前景的局部信号。加在分母的 ε 只处理除零,不能凭空产生分子的梯度。
采用 N=2Σpᵢyᵢ+a、B=Σpᵢ+Σyᵢ+b,损失对 pⱼ 的导数为 (N−2yⱼB)/B²。空标签令 yⱼ=0,得到 a/B²。对 p=[0.2,0.1]、a=b=0.00001,导数约为 0.000111104;程序用中心差分核对。这里求的是对概率的导数,若前面有 sigmoid,还要继续乘链式导数。
把评价和训练的约定写进报告
至少记录前景通道、是否含背景、概率还是阈值掩码、分母是否平方、先求和还是先相除、空标签如何处理,以及平滑数加在哪里。MONAI 的 batch 与 smooth_nr/smooth_dr 正好把其中几项暴露为参数。
本例不判断哪种汇总一定更好。若关心每位样本的效果,逐图平均更直接;若合并统计,要额外查看小目标失败。本文不提供医疗判断,也没有使用医学图像训练网络。
运行完整实验并检查报告
保存为 dice_aggregation.py,运行 python -B dice_aggregation.py。预期输出来自本次实际运行;完整结果写入 report.json。
已运行人工掩码聚合、空标签梯度与有限差分;未运行 MONAI、训练网络、处理真实图像或进行临床评测。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Dice aggregation and empty-target gradients; Python standard library."""
import json
import math
from pathlib import Path
def parts(pred, target):
if not pred or len(pred) != len(target):
raise ValueError('nonempty equal lengths required')
if not all(math.isfinite(p) and 0 <= p <= 1 for p in pred):
raise ValueError('predictions must be finite probabilities')
if not all(y in (0, 1) for y in target):
raise ValueError('binary labels required')
return 2 * sum(p*y for p,y in zip(pred,target)), sum(pred)+sum(target)
def dice(pred, target, smooth_nr=0.0, smooth_dr=0.0):
n,d=parts(pred,target)
if not all(math.isfinite(s) and s >= 0 for s in (smooth_nr,smooth_dr)):
raise ValueError('nonnegative finite smoothing required')
if d+smooth_dr == 0:
raise ValueError('undefined empty/empty Dice; choose a policy')
return (n+smooth_nr)/(d+smooth_dr)
def loss_gradient(pred,target,a,b):
n,d=parts(pred,target)
n += a; d += b
return [(n-2*y*d)/(d*d) for y in target]
def main():
batch=[([1.0]*9,[1]*9),([1.0,0.0],[0,1])]
individual=[dice(p,y) for p,y in batch]
macro=sum(individual)/len(individual)
sums=[parts(p,y) for p,y in batch]
pooled=sum(n for n,d in sums)/sum(d for n,d in sums)
weighted=sum(d*s for (n,d),s in zip(sums,individual))/sum(d for n,d in sums)
assert macro == 0.5 and pooled == weighted == 0.9
# A different batching policy changes the mean of batch Dice scores.
microbatch_mean=macro
whole_batch=pooled
empty=[0.0,0.0]; labels=[0,0]; epsilon=1e-5
empty_both=dice(empty,labels,epsilon,epsilon)
empty_denominator=dice(empty,labels,0,epsilon)
assert empty_both == 1 and empty_denominator == 0
pred=[0.2,0.1]
grad=loss_gradient(pred,labels,epsilon,epsilon)
no_numerator=loss_gradient(pred,labels,0,epsilon)
h=1e-6
fd=[]
for i in range(len(pred)):
up=pred[:]; down=pred[:]; up[i]+=h; down[i]-=h
fd.append(((1-dice(up,labels,epsilon,epsilon))-(1-dice(down,labels,epsilon,epsilon)))/(2*h))
assert max(abs(a-b) for a,b in zip(grad,fd)) < 1e-9
assert no_numerator == [0.0,0.0]
rejected=[]
for p,y in [([],[]),([1,0],[1]),([float('nan')],[0]),([0],[2])]:
try: parts(p,y)
except ValueError: rejected.append(True)
try: dice(empty,labels)
except ValueError: rejected.append(True)
assert len(rejected)==5
report=dict(individual=individual,macro=macro,pooled=pooled,denominator_weighted=weighted,
microbatch_mean=microbatch_mean,whole_batch=whole_batch,empty_both_smoothed=empty_both,
empty_denominator_only=empty_denominator,empty_target_loss_gradient=grad,
finite_difference=fd,denominator_only_gradient=no_numerator,invalid_rejections=len(rejected),checks_passed=True)
Path('report.json').write_text(json.dumps(report,indent=2)+'\n',encoding='utf-8')
print('per-image Dice =',individual)
print(f'mean of image scores={macro:.6f}; pooled score={pooled:.6f}')
print(f'empty/empty: numerator+denominator={empty_both:.1f}; denominator-only={empty_denominator:.1f}')
print('empty-target gradient with numerator smoothing =',[round(x,9) for x in grad])
print('empty-target gradient with denominator-only smoothing =',no_numerator)
print('checks=PASS; report.json written')
if __name__ == '__main__': main()
怎样运行:在独立练习文件夹中执行 python -B dice_aggregation.py。2026-10-18 已在 Windows、Python 3.14.0 运行此份源码并通过自带检查。已运行人工掩码聚合、空标签梯度与有限差分;未运行 MONAI、训练网络、处理真实图像或进行临床评测。
运行后对照结果
per-image Dice = [1.0, 0.0]
mean of image scores=0.500000; pooled score=0.900000
empty/empty: numerator+denominator=1.0; denominator-only=0.0
empty-target gradient with numerator smoothing = [0.000111104, 0.000111104]
empty-target gradient with denominator-only smoothing = [0.0, 0.0]
checks=PASS; report.json written记住这些要点
- 逐图平均与合并求比值回答不同问题。
- 合并 Dice 会按每张图的分母改变份量。
- 空掩码得分与梯度都依赖明确的平滑约定。
- 概率版、阈值版与平方分母版不能混用导数。
别把例子用过头
- 只写平均 Dice,却不写先求和还是先相除。
- 把分母加 ε 误认为空标签一定有训练信号。
- 把本例的线性分母导数套到平方分母实现。
自己试一小步
加入 20 张预测和标签都为空的图,总分会怎样改变?
- 分别用空图记 1、记 0、忽略三种规则计算逐图平均。
- 保留分子分母,比较合并结果并注明平滑是在每图加入还是合并后加入。
- 为含一个假前景的空标签图计算损失对概率的导数。
做到这些,就算完成
- 每种规则都报告有效样本数。
- 能解释至少两种规则为何不一致。
- 保留独立手算与程序输出,并解释它们是否一致。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- MONAI DiceLoss 源码与参数 (新标签页)↗
batch 参数、分子分母平滑与求和轴
- V-Net 第 3 节 Dice loss layer (新标签页)↗
Dice 损失背景及平方分母变体
- Generalised Dice overlap (新标签页)↗
空集合稳定项及类别权重是不同设计