医学影像公平性研究里,FRAME 要解决的是一个很具体的问题:当模型在某个群体上的表现明显差于另一个群体时,这个差距到底来自数据采样方式带来的“假象”,还是来自模型表征里真实存在的偏置。它不是一套装完就能跑出所有结果的软件包,而是一种思路框架。如果你正在做医学影像 AI 的公平性评估、模型上线前审计,或者要给性别、年龄、人种等亚组出一份性能差异报告,这篇内容值得看完。
FRAME 这个缩写第一次看到,容易联想到视频抽帧工具,或者 Ubuntu 桌面环境里跟 frame 相关的显示参数,但在这个语境下,它跟图像抽帧没有关系。它指的是把医学影像模型在亚组之间的性能差异拆成两个来源:sampling variation,也就是采样变异;representational cause,也就是表征层面的原因。区分这两类来源,直接决定了你能不能把“模型存在偏见”这句话说准。
1. 先搞清楚一个问题:分组准确率差异不等于模型歧视
1.1 为什么直接对比准确率会误判
做医学影像 AI 公平性,最常见也最容易误导人的做法,是直接拿模型在男性和女性、不同年龄段、不同体表特征的人群上分别算准确率、AUC、敏感度、特异度,然后比较差异。差异一大,就得出“模型有偏见”的结论。这个结论在逻辑上并不可靠。
模型在一个群体上表现差,来源可能完全不同。
第一,模型本身有问题。比如训练集里某些群体样本质量差、标注噪声大,模型在这个群体上学到了错误模式。
第二,数据采样有问题。两个群体在数据集里的患病率、病变严重程度、成像设备、扫描协议不一样,你看到的性能差异是数据构成导致的,不是模型表征导致的。
第三,评估本身有问题。某群体样本量太小,性能指标的置信区间很宽,看起来差 3 个百分点,实际上随机噪声就能解释。
FRAME 的核心动作,是把第二个来源从总差异里剥离出来。只有做完这一步,你才有资格判断模型的表征是否真的偏袒了某一个群体。
1.2 采样结构到底在怎么干扰公平性评估
医学影像数据集的采集结构,天然存在大量不平衡。
门诊数据里,某个群体的疾病谱可能和另一个群体不同;体检数据里,年龄组之间的患病率差异往往很明显;影像设备层面,不同医院、不同厂商的设备采集参数不同,会给图像纹理带来系统性差异。
这些差异里,一部分是真实世界的客观属性,比如疾病的流行病学差异;另一部分是采集过程的偏倚,比如某个科室更倾向于给特定人群开特定检查。无论哪一种,都会影响模型在亚组上的表现。
如果你不控制这些采样因素,直接比较模型在不同群体上的性能,结果必然混入大量“数据构成噪声”。FRAME 的思路恰好是:先把数据采样过程描述清楚,把患病率、病变分布、成像条件、样本量等因素显式纳入考虑,再观察在消除这些因素之后,模型表征层面的差异是否仍然存在。
2. FRAME 的核心是把“总差异”拆成两个来源
2.1 采样变异和表征原因分别指什么
用更工程化的话解释。
采样变异,指的是因为数据采集和抽样方式不同,导致不同组的样本在特征分布上本来就不一样。A 组里大多是早期病变,B 组里大多是中晚期病变;A 组用的是高分辨率设备,B 组用的是旧设备;A 组样本量是 B 组的十倍。这些因素会让模型在 B 组表现更差,但这个“更差”跟模型自己的表征偏好没有强关系。
表征原因,指的是模型内部学到的特征表示本身就存在群体偏置。模型学到的纹理模式更偏向某类采集条件,或者对某个年龄段特有解剖结构的敏感度不足。这类原因即使换一套采样均衡的数据集,也可能继续存在,因为它藏在模型的特征空间里。
FRAME 要做的,就是把一个观测到的亚组性能差异,分解成这两个来源各自贡献了多少。
2.2 分解逻辑:先对齐分布,再比较残差
具体实现方式在不同研究里会有差异,但通用逻辑是一致的。
假设你观测到模型在组 G1 和组 G2 之间的性能差异为 D_total。可以把它看成两部分之和:
D_total = D_sampling + D_representation
D_sampling 是采样结构差异的贡献,D_representation 是模型表征层面的贡献。
FRAME 类方法的典型做法,是构造一个“采样无关”的对照分布。具体来说,通过重采样、倾向得分加权或合成数据,把 G1 和 G2 在关键协变量上的分布对齐。然后在对齐后的数据上重新评估模型:
- 如果性能差距大幅缩小,说明采样变异是主要来源;
- 如果差距仍然存在,说明表征层面的原因不可忽略。
下面是一段示意性伪代码,描述这类分解的基本流程,实际实现时以你自己选择的算法库为准:
# 示例:FRAME 类分解的伪代码流程 # 1. 读取影像数据的预测结果、真实标签、分组信息和协变量 # df 包含 pred, label, group, covariate # 2. 训练倾向得分模型:用协变量预测样本属于哪个组 # propensity_model = LogisticRegression() # propensity_model.fit(df[covariates], df['group']) # df['propensity'] = propensity_model.predict_proba(df[covariates])[:, 1] # 3. 计算原始组间性能差异 # diff_raw = performance(df[df.group == 'G1']) - performance(df[df.group == 'G2']) # 4. 用倾向得分加权后重新计算组间性能差异 # df['weight'] = 对每个样本按组别和倾向得分计算权重 # diff_weighted = weighted_performance(df[df.group == 'G1']) - weighted_performance(df[df.group == 'G2']) # 5. 对比 diff_raw 与 diff_weighted,差值近似采样变异贡献这里要注意,分布对齐不是简单把两组样本数量凑成一样多。关键的协变量包括患病率、病变严重程度、病灶大小、成像设备、采集协议、患者年龄分布等。只做样本量均衡,往往掩盖了更重要的混叠因素。
2.3 这件事难在哪里
既然逻辑这么清楚,为什么很多团队没有这样做,或者做起来不简单?难点主要有三个。
第一,协变量采集不全。医学影像数据库里,影像本身常常是完整的,但患者的临床信息、检查设备、扫描参数往往缺失。你不知道两组之间的患病率到底差多少,就没办法精确剥离采样成分。
第二,协变量之间存在纠缠。患病率和年龄有关,成像设备和医院级别有关,病变严重程度和转诊路径有关。你想控制其中一个,另外几个又跟着动,统计校正难度很大。
第三,表征层面的差异本身很难直接测量。你没法把模型学到的特征向量单独拿出来,判断哪一个维度和群体身份相关。FRAME 这类方法通常只能给出间接估计。承认这个边界,比假装精确更稳妥。
3. 在自己数据集上完整跑一遍 FRAME 的步骤
3.1 明确组别、任务和指标
动手之前,先回答三个问题。
第一,要比较哪几个组?性别、年龄、体表特征、民族、医保类型、地区都算常见分组维度。但分组不是越多越好,每多分一组,统计功效就会被摊薄。建议第一轮只选一个维度、两组或三组。
第二,要评估什么任务?分类、分割、病灶检测、疾病进展预测,公平性的表现形式不一样。分类任务看 AUC 和校准误差,分割任务看 Dice 和表面距离误差,检测任务看真正例率和假正例率。
第三,用什么指标定义差距?单看准确率不够。医学影像里经常要同时看敏感度、特异度、阳性预测值、阴性预测值,最好加上校准曲线。不同指标对采样变异的敏感程度不同,只看单一指标很容易被误导。
3.2 盘点协变量并做分布诊断
接下来,把手头数据的协变量清单列出来。至少包括这四类:
- 样本统计量:各组的样本量、阳性病例数、阳性率。
- 临床分布:病变类型、严重程度分级、病灶大小、位置。
- 成像参数:设备厂商、磁场强度或扫描协议、层厚、分辨率、对比度设置。
- 患者属性:年龄分布、合并症、检查指征。
对每一组,先做描述性统计和分布可视化。如果发现某个协变量在组间差异很大,比如 A 组 80% 是早期病变,B 组 50% 是中晚期病变,那么这个协变量就应该进入校正模型。
这一步做完,你会得到一份“采样结构诊断报告”。这份报告本身就有价值,写技术报告或论文补充材料时,也应该把它放进去。
3.3 选择对齐方式:加权、匹配还是重采样
常见的对齐方式有三种,各有适用场景。
| 方式 | 适用场景 | 优势 | 短板 |
|---|---|---|---|
| 倾向得分加权 | 样本量中等偏小、协变量较多 | 不丢弃样本,统计功效较高 | 权重方差可能很大,极端权重影响结果 |
| 最近邻匹配 | 样本量较大、协变量较少 | 直观,结果容易解释 | 丢失样本较多,匹配质量依赖距离定义 |
| 重采样/合成数据 | 两组样本量差异悬殊 | 可以直接控制分布 | 实现复杂,合成数据可能引入新偏倚 |
选择哪种方式,取决于样本量和协变量维度。样本量大、协变量少,匹配和加权都可以;样本量小,倾向得分加权通常比重采样更稳妥。
无论用哪种方式,都要记录对齐前后各组的协变量分布变化。对齐之后,再做一次分布诊断,确认两组已经足够接近。
3.4 计算分解结果并判断主要来源
在原始数据和对齐后的数据上分别评估,记录每一组指标值和置信区间。然后计算:
- 原始数据上的组间差异;
- 对齐后数据上的组间差异;
- 两者之差,就是采样变异的贡献;
- 对齐后仍然存在的差异,就是表征原因的贡献。
如果对齐后差异趋近于零,说明观测到的不公平大概率是采样结构造成的。如果对齐后差异依旧明显,说明模型表征本身需要进一步审查。
我有个习惯:报告里同时给出原始差异和对齐后差异,不单独报任何一个。单独报原始差异会误导读者,单独报对齐后差异又让读者无法理解原始问题有多大。两个数字放在一起,谁贡献了什么,一目了然。
4. 解释结果时最容易踩的坑
4.1 对齐后差异缩小,不代表模型没有问题
对齐后差异缩小,只能说明采样变异解释了大部分观测差距,不能说明模型表征没有任何问题。可能有两个原因。
一是对齐协变量清单不完整。某个真正影响表征的混叠变量没有被纳入校正,比如缺乏关键的临床检验指标、缺乏患者用药信息,校正自然不彻底。
二是模型可能存在更隐蔽的表征偏好,它需要更精细的探针任务才能暴露,而不是简单看整体性能指标就能看出来。
所以,FRAME 的结果应该被理解为“在给定协变量集合下、在给定评估指标下的归因”,不是一个全称命题。
4.2 小样本下别急着比较差异
医学影像公平性研究经常遇到亚组样本量很小的情况。某个少数群体的影像只有几百张,算出来的 AUC 置信区间非常宽。这时候你把原始差异和对齐后差异拿来比较,差异本身可能就在噪声范围内。
处理方式没有捷径。要么收集更多数据,要么用重采样方法估计置信区间,比如 bootstrap。我一般会做至少 1000 次 bootstrap 来估计差异的置信区间,如果区间跨过零,就不要急着下结论。
4.3 不要把统计分解当成因果证据
FRAME 能帮你区分采样变异和表征原因对“观测性能差异”的贡献,但本质是统计分解,不是干预实验。
看到“对齐后差异仍然存在”,只能说模型表征和群体身份之间存在相关性,不能说模型在因果意义上“歧视”了某个群体。要得到因果结论,需要更严格的设计,比如在保持临床变量不变的情况下,改变样本的群体标注,观察模型输出变化。这类实验在公开数据集上很难做,所以大多数情况下,表达成“存在与群体相关的表征差异”更准确。
5. 我在实际项目里建议的最小工作流
5.1 先跑通一个模型、两个群体
不要一开始就想着同时评估三个模型、五个任务、十个群体。先选一个任务、一个模型、两个主要群体,把完整流程跑通。
跑通不等于算出了一组数字,而是你能回答这四个问题:
- 原始差异是多少,置信区间是多少?
- 哪些协变量在组间分布差异最大?
- 对齐后差异缩小了多少?
- 这个缩小是否显著?
回答完这四个问题,你才算真正理解这套方法在自己数据上的行为。之后再扩展模型数量和群体数量,会顺利很多。
5.2 报告里必须写清楚哪些信息
如果你要在技术报告或论文里使用 FRAME 或类似思路,至少包含这些内容:
- 分组定义和样本量统计,包括各组阳性例数;
- 模型性能的原始组间差异,附置信区间;
- 协变量清单及其在组间的分布差异;
- 对齐方法的具体参数和实现细节;
- 对齐后组间差异,附置信区间;
- 对结果边界的说明,包括未纳入分析的协变量和已知局限。
这些要素能让读者判断你的归因结论是否可靠,也方便别人复现。漏掉任何一个,结果都可能被误读。
5.3 往后可以扩展的几个方向
如果 FRAME 在自己数据集上验证稳定,可以考虑几个扩展。
第一,把单一指标扩展到误分类错误分析。很多公平性问题不体现在平均性能上,而体现在特定错误模式上。比如某一群体的假阳性率特别高,这需要单独分析。
第二,引入表征层面的探针分析。在模型的特征空间里训练一个简单分类器,看能否通过特征预测群体身份。如果探针分类器准确率很高,说明表征中保留了群体相关信息,这比只看端到端指标更接近“表征原因”。
第三,把 FRAME 和模型修复结合起来。一旦识别出表征原因占比较大,可以尝试域泛化、对抗去偏或带约束的训练方法调整模型,然后再用同一套评估流程验证是否改善。
6. 关于名称和通用运行环境的一些说明
最后说几句容易混淆的地方。
FRAME 是一种方法论意义上的框架,不是某个固定的软件包,也不是一条命令能跑完的工具。不同团队对“采样变异”和“表征原因”的定义会有细微差异,对齐时选择的协变量也不同,不同论文的结果不能直接横向比较。读论文或复现时,先看它如何定义采样模型,再看它如何展示结果,最后才是看结论。
如果你是在 Ubuntu 之类的基础环境里跑这类分析,它本质上是普通的 Python 数据处理流程,会涉及 pandas、scikit-learn、statsmodels、PyTorch 等依赖。不要在“frame”这个名称上纠结,它既不指视频编码里的帧,也不指图形界面里的 Frame 控件,而是“框架、体系”的本意。
注意:如果数据里协变量缺失严重,第一步不是急着做对齐,而是先补全或合理估计关键协变量。强行对缺失严重的字段做校正,只会让结果置信区间更大,甚至引入新的偏倚。
这套流程真正落地时,最需要盯住的不是某个推导公式,而是你手里的数据到底能不能支撑“分离”这个动作。协变量齐不齐、样本量够不够、指标选得合不合理,决定了 FRAME 分析是真正帮助判断,还是只给已有的成见添了一个好看的附件。