一个灵敏度 50.2%、特异度 93.3% 的检查,单看是个不错的检查。 但放到患病率 1% 的人群里,阳性预测值只有 7.0%。二十万人里, 13246 个健康人被误判成阳性,989 个病人被漏掉。 同一个阈值,患病率换成 50%,阳性预测值就是 88.2%;换成 0.1%,只剩 0.8%。
11.1 讲的是「模型怎么练出来、放在哪」。 这一节开始讲「它进了诊室之后,那几个指标该怎么读」。 这是医疗 AI 落地时最容易出事的一步,而且出事的方式很安静。
一、两类指标,说的不是一件事
评价一个检查有两组常用指标,很多人会混着用,而这正是问题的起点。 灵敏度=病人里查出阳性的比例。特异度=健康人里判为阴性的比例。 这两个都是「模型自己的属性」——换人群,它们基本不变。 阳性预测值=查出阳性的人里真正有病的比例。 这个不是模型属性,是场景属性——同一个模型,换个人群,它是另一个数。 分清这一点非常关键:厂商报的永远是前两个(好看、稳定),而医生真正想知道的是第三个。 这三者之间差一个患病率,而患病率是使用方决定的。 它们的关系用贝叶斯公式就能写出来: 阳性预测值 = 灵敏度 × 患病率 ÷(灵敏度 × 患病率 + (1 − 特异度) × (1 − 患病率))。 分母的第二项是「健康人被误判的比例」,而健康人的基数通常大得多。
二、患病率把同一个模型变成两个模型
Demo 造 20 万人、患病率 1%,患者分数 N(1.5,1)、非患者 N(0,1)。阈值取 1.5,得到第一张表:
| 阈值 | 灵敏度 | 特异度 | 阳性预测值 | 误诊数 | 漏诊数 |
|---|---|---|---|---|---|
| 0.5 | 85.5% | 69.0% | 2.7% | 61317 | 288 |
| 1.0 | 71.5% | 84.1% | 4.3% | 31559 | 566 |
| 1.5 | 50.2% | 93.3% | 7.0% | 13246 | 989 |
| 2.0 | 31.5% | 97.7% | 12.1% | 4523 | 1362 |
| 2.5 | 16.5% | 99.4% | 20.7% | 1252 | 1660 |
| 先看阈值 1.5 那一行:灵敏度 50.2%、特异度 93.3%。 | |||||
| 单看这两个数,这是一个相当不错的检查。 | |||||
| 但阳性预测值只有 7.0%——查出来阳性的人里,九成以上是虚惊一场。 | |||||
| 具体到人数:误诊 13246 人,漏诊 989 人。 | |||||
| 为什么?因为患病率只有 1%。二十万人里 2000 个病人、198000 个健康人。 | |||||
| 特异度 93.3% 意味着健康人里有 13246 个被误判——比真病人多好几倍。 | |||||
| 误诊的数量由「健康人的基数」决定,不是由「模型准不准」决定。 | |||||
| 把阈值往下调会怎样? | |||||
| 灵敏度上去了,误诊暴涨:阈值 0.5 时灵敏度 85.5%,但误诊 61317 人, | |||||
| 阳性预测值掉到 2.7%——等于「查出来阳性几乎都是没事」。 | |||||
| 往上调呢?阈值 2.5 时阳性预测值 20.7%,但漏诊 1660 人——2000 个病人里放走 83%。 | |||||
| 这就是阈值选择在低患病率人群里的残酷性:你只能在「大量虚惊」和「大量漏诊」之间选一个。 |
三、换成高患病率人群,同一个阈值就变好了
Demo 第二张表固定阈值 1.5,只改患病率:
| 患病率 | 阳性人数 | 其中真病 | 阳性预测值 |
|---|---|---|---|
| 50.0% | 112998 | 99685 | 88.2% |
| 10.0% | 44343 | 20196 | 45.5% |
| 1.0% | 28275 | 1981 | 7.0% |
| 0.1% | 26984 | 213 | 0.8% |
| (这一张换了 40 万人的样本来算,所以 1% 那行的阳性人数和表一不同。) | |||
| 同一个阈值、同一个模型、同一个灵敏度和特异度——患病率从 50% 掉到 0.1%, | |||
| 阳性预测值从 88.2% 掉到 0.8%。 | |||
| 检查本身一点没变,变的只是「在谁身上用」。 | |||
| 中间那两行更值得看:50% 到 10%,阳性预测值从 88.2% 掉到 45.5%,直接砍半; | |||
| 10% 到 1%,再从 45.5% 掉到 7.0%,砍到不足六分之一。 | |||
| 下降不是线性的——患病率每降一个数量级,阳性预测值就塌一层。 | |||
| 最后一行的 0.1% 最能说明问题:26984 个阳性里只有 213 个真病,阳性预测值 0.8%。 | |||
| 换句话说,这个检查在 0.1% 的人群里基本等于抛硬币——而且是一枚 99.2% 概率抛错的硬币。 | |||
| 这就是为什么「AI 诊断准确率 95%」这句话必须带上「在什么人群里」。 | |||
| 同样是 95%,用在三甲医院专科门诊和用在社区筛查,临床意义差几十倍。 | |||
| 反过来说,这也是分级诊疗的统计学理由: | |||
| 先在社区做问卷筛出高风险人群(把验前概率抬上去),再让他们去大医院做贵的检查。 | |||
| 这不是流程繁琐,这是在把患病率调到模型能工作的区间。 | |||
| 这个道理不只适用于医疗。风控里的欺诈检测、工业里的缺陷检测、 | |||
| 安全里的入侵检测——凡是「正例极其稀有」的场景,都会撞上同一堵墙。 | |||
| 「模型准确率 99%」在 0.1% 的阳性率下,阳性预测值可能只有个位数。 | |||
| 所以看这类报告,有一个固定的追问顺序: | |||
| ① 你的测试集里正例占多少?② 落地场景里正例占多少?③ 这两者差多少? | |||
| 第三个问题答不上来,前两个数字就没有意义。 |
四、落地:报告和流程各改一处
报告侧:阳性预测值必须报,而且要报「在什么患病率下」。只报灵敏度和特异度的报告, 在低患病率场景里等于把最重要的信息藏起来了。 流程侧:把「谁来做这个检查」当成参数。同一个模型,给专科门诊用和给体检中心用, 阈值应该不一样——因为两边的验前概率不一样。 所以部署时不能只给一个模型,要给一套「按人群分档的阈值」。 再加一道人机分工: 模型负责「不放过」,医生负责「别冤枉」。具体做法是把阈值调低(灵敏度优先), 接受大量假阳性,然后用医生的时间去做二次判读。 筛查场景本来就该这样设计——因为漏掉一个病人的代价,远高于多叫一个人回来复查。 最后一句:在医疗 AI 里,「准确率」不是终点,「阳性预测值 × 患病率」才是。 前者是模型的自评,后者是它在诊室里的实际表现。
五、动手 Demo:诊断阈值:特异度 93% 的检查,在 1% 患病率下阳性只有 7% 是真病
造 20 万人,按 1% 患病率标标签;患者的检验分数取 N(1.5,1)、非患者取 N(0,1),分数高于阈值判为阳性。 先扫五个阈值,算灵敏度、特异度、阳性预测值和误诊/漏诊人数; 再固定阈值 1.5,把患病率换成 50%、10%、1%、0.1%,看阳性预测值怎么变。
y = rg.random(n) < prev # 1% 患病率
score = rg.normal(np.where(y, 1.5, 0.0), 1.0) # 患者 N(1.5,1)
pos = score > th # 阈值判阳
tp = int((pos & y).sum())
fp = int((pos & ~y).sum())
sens = 100.0 * tp / max(tp + fn, 1) # 灵敏度
spec = 100.0 * tn / max(tn + fp, 1) # 特异度
ppv = 100.0 * tp / max(tp + fp, 1) # 阳性预测值
实跑输出:
--------------------------------------------------------------------
诊断阈值:特异度 93% 的检查,在 1% 患病率下阳性只有 7% 是真病
--------------------------------------------------------------------
人数 × 患病率 200000 人 × 1%
分数分布 患者 N(1.5,1),非患者 N(0,1)
阈值 分数高于它判为阳性
阈值 灵敏度 特异度 阳性预测值 误诊数 漏诊数
----------------------------------------------------
0.5 85.5% 69.0% 2.7% 61317 288
1.0 71.5% 84.1% 4.3% 31559 566
1.5 50.2% 93.3% 7.0% 13246 989
2.0 31.5% 97.7% 12.1% 4523 1362
2.5 16.5% 99.4% 20.7% 1252 1660
先看阈值 1.5 那一行:**灵敏度 50.2%、特异度 93.3%。**
**单看这两个数,这是一个相当不错的检查。**
**但阳性预测值只有 7.0%**——**查出来阳性的人里,九成以上是虚惊一场。**
具体到人数:**误诊 13246 人,漏诊 989 人。**
**为什么?因为患病率只有 1%。**
二十万人里 2000 个病人、198000 个健康人。
**特异度 93.3% 意味着健康人里有 13246 个被误判——比真病人多好几倍。**
表二把这件事说得更清楚:固定阈值 1.5,只改患病率。
患病率 阳性人数 其中真病 阳性预测值
----------------------------------------
50.0% 112998 99685 88.2%
10.0% 44343 20196 45.5%
1.0% 28275 1981 7.0%
0.1% 26984 213 0.8%
**同一个阈值、同一个模型,患病率从 50% 掉到 0.1%,阳性预测值从 88.2% 掉到 0.8%。**
**检查本身一点没变,变的只是「在谁身上用」。**
这条在医疗 AI 里极其重要:**模型报的灵敏度/特异度是在某个数据集上测的。**
**换到另一个患病率的人群,阳性预测值会完全变样。**
**所以「AI 诊断准确率 95%」这句话,必须带上「在什么人群里」。**
实务上的对策也很直接:**先筛高风险人群(提高验前概率),再做检查。**
这就是分级诊疗和初筛问卷存在的统计学理由。
要说明:分数分布是我**设的**(两组正态、有重叠),**真实检验指标的分布往往更偏态**。
**但「患病率决定阳性预测值」这件事是纯概率,不依赖数据。**
这张表的读法是从右往左:先看误诊和漏诊的人数,再看阳性预测值,最后才看灵敏度和特异度。 顺序反了,就会被那两个「看起来漂亮」的百分比骗过去。 阈值 1.5 那一行最值得停一下:灵敏度 50.2% 意味着一半的病人查不出来。 而特异度 93.3% 听起来很好,落到 19.8 万健康人身上就是 13246 个误诊。 「比例」和「人数」在基数悬殊时完全是两个故事。 第二张表是这一节的核心证据:阳转率从 88.2% 掉到 0.8%,检查没变。 这就是「阳性预测值是场景属性」的量化证明。 在 0.1% 的患病率下,这个检查基本等于抛硬币——查出来阳性的人里 99.2% 是健康的。 顺带说一句:患病率越低,阳性预测值越接近「特异度的补数之比」,跟灵敏度几乎无关。 所以在筛查场景里,提升特异度比提升灵敏度更值钱。这跟直觉相反——大家都盯着灵敏度做, 但在低患病率下,1 个点的特异度提升,减少的误诊人数远超 1 个点的灵敏度提升。 要说明:分数分布是我设的(两组正态、有重叠), 真实检验指标的分布往往更偏态,还常有「灰区」需要复检。 但「患病率决定阳性预测值」这件事是纯概率,不依赖数据。
30 秒实验
- 把患病率从 1% 改成 5%(比如有家族史的人群),重跑第一张表。 阳性预测值会涨到三成左右——患病率抬五倍,阳性预测值抬四倍多。
- 把特异度从 93.3% 人为提到 99%(比如加一道复检),看阈值 1.5 那一行。 阳性预测值会跳一大截——低患病率下,特异度就是命门。
- 把两组分布的均值差从 1.5 缩到 0.8(检查变差),看整张表。 所有阈值下阳性预测值都会掉,因为灵敏度和特异度同时变差。
常见误区
- 「准确率 95% 的模型可以直接用」。在 1% 患病率下,阳性预测值可能只有 7%,九成以上是虚惊。
- 「灵敏度特异度高就是好检查」。这两个数与人群无关,真正决定临床价值的是它们和患病率的组合。
- 「阈值调低就能多查出病人」。 灵敏度确实上去了,但阈值 0.5 时误诊 61317 人,阳性预测值掉到 2.7%。
自测三问(点开对答案)
1. 为什么灵敏度、特异度换人群不变,阳性预测值却会变? —— 前两个是条件概率,只依赖模型本身;阳性预测值还取决于人群里正例的占比,即患病率。 2. 1% 患病率下,为什么特异度比灵敏度更值钱? —— 因为健康人基数巨大(19.8 万),特异度每提高 1 个点, 减少的误诊人数远多于灵敏度提高 1 个点带来的真阳性。 3. 「AI 诊断准确率 95%」这句话缺了什么? —— 缺「在什么人群里」。同样 95%,专科门诊和社区筛查的阳性预测值差几十倍。上一节解决「查出来阳性怎么办」。 再往上游一步: 还没生病的人,怎么判断谁该干预?这就是健康管理要做的事,而它的评价口径和诊疗完全相反。
- ☐ (本节完,下一节 11.2.2 健康管理创新)