医学影像大模型评测:Recall@K 为什么不能横向排名
一句话结论:把两个模型的 Recall@10 并排放在一起比大小,比的往往不是模型,而是候选池、数据集划分和样本量。
项 | 内容 |
摘要 | 10 月 26 日 Frontiers in Medicine 上线一篇覆盖 50 余项研究的综述,明确拒绝给医学视觉—语言模型排名。本文拆清它给的四条理由、13 篇研究的指标报告率、四类规范的职责边界,并用一个零依赖脚本量化候选池与样本量如何扭曲 Recall@K,最后给出六条可直接写进评测方案的校准线。 |
前置知识 | 检索指标 Recall@K 的含义;知道「验证集划分」与「置信区间」两个词 |
阅读时间 | 约 15 分钟 |
代码环境 | Python 3.9+,零第三方依赖,不联网,输出确定(无随机源) |
读完你能做什么 | 能在评审会上指出一份医学影像模型对比表里哪几列不可比;能给自己团队的评测方案补上候选池、划分、样本量三项声明;能说清 CLAIM 与 TRIPOD+AI 各自管什么 |
一手来源是 2026 年 10 月 26 日上线的综述《Vision–language foundation models for multimodal medical image understanding and report generation: a narrative review with quantitative synthesis》,载于Frontiers in Medicine(Volume 13 - 2026,Section Pathology),DOI10.3389/fmed.2026.1958283,作者 Shahid Mehmood 等 7 人,来自巴基斯坦 Bahria University、阿联酋 University of Sharjah、沙特 IMSIU、韩国 Gachon University 与马来西亚 UiTM。本文引用的数字与原文表述均出自该综述页面,涉及第三方转述处已单独标明。
一、这份综述到底说了什么
它做的不是荟萃分析,而是「带定量综合的叙述性综述」。原文在摘要里把立场写得很直接:
Drawing on more than 50 original studies published between 2020 and 2025, we provide a structured descriptive comparison rather than a statistical meta-analysis.
Because datasets, splits, candidate pools, and metrics differ, cross-task results are presented as exploratory benchmark indicators and not as a clinical-readiness scale.
两句话把边界划死了:这是结构化描述,不是合并统计;跨任务结果只是探索性基准指示,不是临床就绪量表。
它给出的模型数字,本文原样摘录(表中 NR = 原综述标注 not reported,破折号 = 原表未报告,均不等于零分):
模型 | 任务 | 指标 | 数值 | 数据集/规模 |
BiomedCLIP (2023) | 图文检索 | Recall@1 | 0.58 | PMC-15M,1500 万图文对 |
BiomedCLIP (2023) | 图文检索 | Recall@5 / Recall@10 | 0.82 / 0.89 | 同上 |
GLoRIA (2021) | 图文检索 | Recall@1 / Recall@5 / Recall@10 | 0.51 / 0.78 / 0.87 | MIMIC-CXR,37.7 万影像 |
ConVIRT (2022) | 图文检索 | Recall@10 | 0.81(Recall@1 未报告) | MIMIC-CXR |
MMBERT (2021) | 医学 VQA | VQA-RAD 准确率 | 62.5% | VQA-RAD |
MedViLL (2022) | 医学 VQA | VQA-RAD 准确率 | 67.8% | MIMIC-CXR、VQA-RAD |
看这张表最容易犯的错,就是把 0.89、0.87、0.81 三个数排个序,然后说 BiomedCLIP 比 GLoRIA 好。综述在 Table 7 的说明里专门堵了这个口子:
Recall@K is protocol dependent. Dataset, split, retrieval direction, candidate-pool size, and preprocessing must match before systems are compared.
Retrieval values are not directly rankable across different datasets, candidate-pool sizes, directions, splits, and preprocessing pipelines.
二、为什么 Recall@K 会随候选池漂移
候选池就是检索时的候选集合大小。同一个模型,从 200 条里找和从 5000 条里找,Recall@10 的绝对值完全不是一回事——因为随机基线就是K / N。
这事的机制不复杂,但后果被严重低估:候选池扩大 N 倍,随机的命中概率就缩小 N 倍,所以同样的模型能力,在大池子里看起来「变差」,其实是任务变难。要跨池比较,得看相对随机基线的提升倍数,而不是绝对值。
三、动手验证:把候选池与样本量的影响算出来
下面这段脚本不装任何依赖,直接python recall_pool_drift.py就能跑。它把模型能力固定成一个参数(正例与负例得分分布的距离 d'),只让候选池变化,看 Recall@10 怎么动;再算同一个观测值在不同测试集规模下的 Wilson 置信区间。
import math K = 10 # 取前 10 个结果 DPRIME = 2.5 # 模型能力(正例与负例得分分布的距离),固定不动 def log_binom_pmf(n: int, i: int, p: float) -> float: """二项分布 pmf 的对数,用 lgamma 避免大组合数溢出。""" if p <= 0.0: return 0.0 if i == 0 else -math.inf if p >= 1.0: return 0.0 if i == n else -math.inf return (math.lgamma(n + 1) - math.lgamma(i + 1) - math.lgamma(n - i + 1) + i * math.log(p) + (n - i) * math.log1p(-p)) def binom_cdf(n: int, k: int, p: float) -> float: """P(X <= k),X ~ Binom(n, p)。这里 k 很小,直接累加前 k+1 项。""" return math.fsum(math.exp(log_binom_pmf(n, i, p)) for i in range(k + 1)) def recall_at_k(pool: int, k: int = K, dprime: float = DPRIME) -> float: """ 正例落在前 k 名的概率。 对正例得分做数值积分:给定得分 x,负例超过 x 的个数 ~ Binom(pool-1, Φ(-x)), 要求其 <= k-1。 """ total, wsum = 0.0, 0.0 x = dprime - 6.0 while x <= dprime + 6.0: w = math.exp(-0.5 * (x - dprime) ** 2) # 未归一化的正态密度 q = 0.5 * math.erfc(x / math.sqrt(2.0)) # 单个负例超过 x 的概率 total += w * binom_cdf(pool - 1, k - 1, q) wsum += w x += 0.01 return total / wsum def random_baseline(pool: int, k: int = K) -> float: """随机排序下正例进入前 k 的概率,就是 k / N。""" return min(1.0, k / pool) def wilson_interval(hits: int, total: int, z: float = 1.96): """Wilson 分数区间:样本量小的时候比正态近似稳得多。""" if total == 0: return (0.0, 0.0) p = hits / total denom = 1 + z * z / total center = (p + z * z / (2 * total)) / denom margin = z * math.sqrt(p * (1 - p) / total + z * z / (4 * total * total)) / denom return (max(0.0, center - margin), min(1.0, center + margin)) def main(): print(f"模型能力固定 d' = {DPRIME},只改候选池大小,k = {K}") print() print(f"{'候选池 N':>8} | {'Recall@10':>9} | {'随机基线':>9} | {'提升倍数':>9}") print("-" * 44) for n in (200, 500, 1000, 2000, 5000): r = recall_at_k(n) base = random_baseline(n) print(f"{n:>8} | {r:>9.3f} | {base:>9.4f} | {r / base:>8.0f}x") print() print("=== 测试集样本量对 95% 置信区间宽度的影响(观测 Recall@10 = 0.58)===") print(f"{'样本量 n':>9} | {'命中数':>6} | {'95% Wilson 区间':>22} | {'半宽':>7}") print("-" * 56) for n in (30, 100, 300, 1000): hits = round(0.58 * n) lo, hi = wilson_interval(hits, n) print(f"{n:>9} | {hits:>6} | [{lo:.3f}, {hi:.3f}]{'':>5} | {(hi - lo) / 2:>7.3f}") if __name__ == "__main__": main()本机实跑(Windows,Python 3.13.12)输出:
模型能力固定 d' = 2.5,只改候选池大小,k = 10 候选池 N | Recall@10 | 随机基线 | 提升倍数 -------------------------------------------- 200 | 0.796 | 0.0500 | 16x 500 | 0.665 | 0.0200 | 33x 1000 | 0.562 | 0.0100 | 56x 2000 | 0.464 | 0.0050 | 93x 5000 | 0.348 | 0.0020 | 174x === 测试集样本量对 95% 置信区间宽度的影响(观测 Recall@10 = 0.58)=== 样本量 n | 命中数 | 95% Wilson 区间 | 半宽 -------------------------------------------------------- 30 | 17 | [0.392, 0.726] | 0.167 100 | 58 | [0.482, 0.672] | 0.095 300 | 174 | [0.523, 0.634] | 0.056 1000 | 580 | [0.549, 0.610] | 0.031两个结论可以直接拿去用:
- 候选池从 200 扩到 5000,同一个模型的 Recall@10 从 0.796 掉到 0.348,而相对随机基线的提升从 16 倍涨到 174 倍。掉下去的那部分不是能力损失,是任务难度。看到跨论文的 Recall@K 对比表,先问一句候选池多大。
- 测试集 30 例时,95% 区间的半宽是 0.167。两个模型点估计差 0.05,在这个样本量下和没差别是一回事。综述里那些两三个点的差距,多数属于这一类。
四、13 篇研究的指标报告率:缺的不是指标,是配套信息
综述对 13 篇研究做了一次来源审计,分母统一为 13:
报告项 | 篇数 | 占比 |
BLEU/ROUGE | 4 | 31% |
RadGraph F1 | 0 | 0% |
CheXpert/CheXbert 标签评估 | 6 | 46% |
BERTScore | 1 | 8% |
人类专家评估 | 4 | 31% |
推理延迟或吞吐 | 0 | 0% |
综述自己加了一句限定:这些比例只描述被展示的审计样本,不构成领域总体估计。但两项 0% 很有信息量——没有一篇报告推理延迟或吞吐。对要落在读片流程里的系统,延迟和能不能横向扩展,是和准确率同量级的约束条件,却在公开证据里整体缺席。
关于「不排名」,综述在 5.5.1 节给了操作口径:
The source audit in Table 4 shows that direct longitudinal ranking is not defensible when models address different tasks or use different evaluation protocols.
Comparisons should therefore be restricted to studies using the same dataset, patient-level split, preprocessing, and evaluation implementation.
VQA 那边同理。Table 6 的注脚写明:VQA-RAD、SLAKE、PathVQA、ImageCLEF VQA-Med 在答案格式、划分构造、归一化与临床语境上各不相同,模型或数据集之间的表面差异应被理解为基准特异的,而不是共同的成熟度标尺。
五、常见误区
误区一:把破折号当 0 分。综述在 Table 4 注脚专门写了A dash or 'not reported' should not be interpreted as zero performance。ConVIRT 的 Recall@1 是未报告,不是测出来为零。
误区二:把「遵循报告规范」当成「拿到监管许可」。原文措辞是:遵循报告指南本身不构成监管授权(adhering to reporting guidelines does not itself constitute regulatory authorization)。CLAIM 是报告清单,不是通行证。
误区三:跨任务合成一个总分。Table 8 的标题就写着Exploratory cross-task evidence summary (not a clinical-readiness scale),并注明各行指标测的是不同构念。
误区四:把单中心回顾性结果当普遍结论。综述在 roadmap 里把外部验证与亚组验证写进最低标准,正是针对这一点。
误区五:只看准确率不看延迟。13 篇里 0 篇报告延迟或吞吐,这不代表延迟不重要,只代表公开证据存在缺口。自己评测时不要继承这个缺口。
六、工程落地:六条可以写进评测方案的校准线
综述第 9 节提出了「最低临床验证与报告标准」,对齐 FDA/Health Canada/MHRA 与 IMDRF 的良好机器学习实践、EU AI Act、TRIPOD+AI 与 CLAIM。落到一份能执行的内部评测方案,可以拆成六条:
校准线 | 要声明/要做的事 | 对应规范职责 |
数据溯源 | 数据来源、许可、脱敏方式、入排标准 | 报告类 |
患者级划分 | 按患者而非按影像切分,写明划分比例与随机种子 | 报告类(TRIPOD+AI) |
指标与区间 | 主指标 + 95% 置信区间,样本量写进表里 | 报告类 |
候选池与协议 | 检索任务的候选池大小、检索方向、预处理 | 报告类(CLAIM) |
外部与亚组验证 | 至少一处外部数据 + 按人群/设备亚组拆分 | 生命周期实践(GMLP) |
部署后监控 | 上线后的性能监测与回退路径 | 生命周期实践(GMLP) |
四类规范不是一回事,混用会出事:TRIPOD+AI 与 CLAIM 属报告指南,GMLP 属生命周期实践,EU AI Act 属法律义务,适用与否取决于司法辖区与预期用途。综述明确提出这份清单要区分 reporting guidance、lifecycle practice 与 legal obligations 三类。
七、适用边界与产业坐标
这份综述的适用范围是医学影像的视觉—语言基础模型,不包括纯文本临床大模型,也不覆盖手术机器人一类的控制类系统。它的结论强度也有限定:作者给出的十一项优先方向里,RAG grounding、知识图谱推理、时序建模、联邦学习都还停在「待研究」,不是已验证结论。
把视角从论文拉回工程:医学影像类模型真正进到医院里,卡点往往不在模型本身,而在它前面那圈东西——影像与报告的调阅链路、与既有系统的接口、数据不出域的部署形态。综述在数据侧提到的隐私顾虑(去标识不能完全排除模型记忆到具体患者影像或文本)在工程上会转化成一条硬约束:推理与数据都得放在院内,出域通路要能被审计。
这类形态在信创环境里通常由两层承接:平台层负责部署编排与资源隔离,集成层负责把模型能力包成业务系统可调用的接口,并把调用留痕。金蝶天燕 Apusic 这条线上,AAS 应用服务器承担院内应用的承载,ADMQ 分布式消息队列承担影像调阅与报告回传这类异步链路,两者要满足的不是峰值数字,而是数据不出域的前提下接口仍然可管、可审计。至于模型与产品之间的适配口径,以各自的官方文档为准。
八、结论
- 综述覆盖 50 余项 2020–2025 年的研究,给出的定位是结构化描述性比较,不是荟萃分析,也不是临床就绪量表。
- Recall@K 是协议依赖的指标:数据集、划分、检索方向、候选池大小、预处理必须对齐才可比较。固定能力下,候选池从 200 到 5000 会让 Recall@10 从 0.796 变到 0.348,而提升倍数从 16 倍涨到 174 倍。
- 13 篇研究中推理延迟与吞吐的报告率为 0%,这是公开证据的缺口,不是可以忽略的理由。
- CLAIM、TRIPOD+AI、GMLP、EU AI Act 分属报告指南、生命周期实践与法律义务三类,混用会把报告合规误当成准入。
问一句:你们团队的模型对比表里,有没有写明候选池大小和测试集样本量?如果这两列是空的,表里的排名大概率是噪声。