同一份病历, 被训过的模型给它 0.969 的置信度,没训过的模型给 0.730。这个落差就是攻击者要的信号。 把正则 λ 从 0.001 加到 1.0,置信度差从 0.239 缩到 0.049,相对差从 33% 掉到 9%。 但代价是模型本身也不准了。
第十章讲的是「路上跑的车」,这一章换成医院和律所。 这两行的共同点是:数据一旦泄露,后果不可撤销。11.1 三节先讲模型训练的三道关: 数据怎么护、知识怎么续、算力往哪放。
一、病历和行车记录不是一回事
交通数据泄露了,最坏是「知道你去过哪」。 病历泄露了,泄露的是「你是谁、你得了什么病、你还能活多久」。 三条性质决定了它不能按普通数据对待。 ① 不可撤销。 密码泄露可以改,基因和病史改不了。所以「事后补救」这套逻辑在这里不成立。 ② 可重识别。 出生日期、性别、邮编这三项组合, 就能唯一定位美国 87% 的人口(这是 1997 年那篇著名的 Latanya Sweeney 研究)。 医疗数据里这三项全有。 ③ 关联性。 一份病历泄露,可能连带暴露家属——因为遗传病、传染病的信息天然指向亲属。 所以「只脱敏患者本人」是不够的。 这一节只看其中最隐蔽的一种泄露:模型自己把训练数据说了出去,而你根本没发布数据,只发布了模型。
二、模型会「记住」训练数据
这件事有个专门的名字:成员推断攻击(membership inference)。 攻击者只问模型一个问题——「这条记录,你的置信度是多少?」 如果明显偏高,就说明它被训过。 Demo 里造 800 条 20 维记录,每次只用 60 条训练, 并且有意识地把 λ 设得很小,让模型必须死记。然后对同一条目标记录跑两遍模型: 一遍训练集里有它,一遍没有。
| 正则 λ | 在集内 | 不在集内 | 置信度差 | 相对差 |
|---|---|---|---|---|
| 0.001 | 0.969 | 0.730 | 0.239 | 33% |
| 0.010 | 0.891 | 0.705 | 0.186 | 26% |
| 0.100 | 0.742 | 0.621 | 0.120 | 19% |
| 1.000 | 0.579 | 0.530 | 0.049 | 9% |
| λ=0.001 那一行:0.969 对 0.730。 | ||||
| 差 0.239,相对高 33%。 | ||||
| 攻击者根本不需要拿到数据,一个数就够了。 | ||||
| 这就是「只发布模型」这条路走不通的原因。很多人以为「我不给数据,只给模型权重, | ||||
| 应该安全」——恰恰相反,权重里编码了训练数据的信息,而且是可以被定量提取的。 |
三、正则是最便宜的一道闸
把 λ 从 0.001 加到 1.0,置信度差从 0.239 掉到 0.049,缩到五分之一;相对差从 33% 掉到 9%。 因为强正则把「记忆」压住了——所有人的置信度都被拉平到 0.5 附近,攻击者就分不出谁被训过。 但这不是免费的:λ 越大,模型本身越不准。看「在集内」那一列, 从 0.969 掉到 0.579——一个连训练数据都只有 57.9% 置信度的模型,临床上根本不能用。 所以隐私和精度在医疗 AI 里是一条很直的取舍线,没有两边都拿的方案。 厂商宣传「在保护隐私的同时不损失精度」,指的是「比朴素做法损失得少」,不是「零损失」。 这一点很值得记:「隐私保护」不是一个开关,是一个预算。 你要决定的是「愿意为隐私损失多少精度」,而不是「要不要隐私」。
四、真正有效的是切断接口
工程上比正则有效得多的,是两个手段。 ① 只发布预测接口,不发布权重。模型留在医院内网,外部只能调用 API。 成员推断需要反复查询同一个样本、并对比置信度,接口层加上查询频次限制和置信度取整就能大幅削弱。 ② 差分隐私训练。 训练时给梯度加噪声, 从数学上给出「某个样本在不在训练集里,输出分布几乎一样」的保证。 它的隐私预算 ε 是可以报出来的——ε=1 和 ε=10 的安全性差很多,必须写在报告里。 还有一个折中方案:联邦学习。 数据不出医院,只上传梯度。听着完美, 但梯度本身也会泄露——已有研究能从梯度里重建出图像的轮廓。 所以联邦学习通常要叠加安全聚合或差分隐私,裸梯度是不安全的。 顺带说一句:在医疗场景里,「数据能不能出医院」经常不是技术问题,是合规问题。个人信息保护法、 人类遗传资源管理条例、医院自己的伦理审查——这三层任何一层否了,方案就得改。
五、落地:四条
① 先分级再谈保护。 不是所有字段同等敏感。 姓名、身份证、住址、基因、传染病、精神疾病史——这几类是红线,能不进训练集就不进。 ② 训练集和发布物分离。 训练集永不出内网;对外只给接口,权重加密、按需下发、可远程吊销。 ③ 隐私指标要能报出来。「我们做了脱敏」这句话没有信息量。 要报的是 k-匿名到几、ε 是多少、成员推断的置信度差测出来多大。 ④ 上线前跑一次攻击。拿一小批「已知在训练集里」和「已知不在」的样本,自己先做一遍成员推断。 攻击成功的比例,就是这个模型真实的泄露程度——这个数比任何合规文档都实在。 最后一句:医疗 AI 的隐私问题,不是「会不会泄露」,是「泄露多少算可接受」。 把这句话写进验收标准,比写在宣传页上有用。
六、动手 Demo:成员推断:正则 0.001 时置信度差 0.239,加到 1.0 只剩 0.049
造 800 条 20 维记录、一个线性可分的标签,取第 1 条当「目标患者」。 对每个正则系数 λ 重复 60 次:每次从 800 条里随机抽 60 条做训练集, 分别训练「含目标患者」和「不含目标患者」两个模型,比较它们对目标患者给出的正确类置信度。 差越大,越容易做成员推断。
def fit(Xi, yi, lam, it=3000, lr=0.5):
mu, sd = Xi.mean(0), Xi.std(0) + 1e-9
Z = np.hstack([(Xi - mu) / sd, np.ones((len(Xi), 1))])
w = np.zeros((Z.shape[1], 2)) # 二分类,两列
Y = np.eye(2)[yi]
for _ in range(it):
p = H.softmax(Z @ w)
g = (p - Y) / len(Z)
w[:-1] -= lr * (Z[:, :-1].T @ g + lam * w[:-1]) # L2 只罚权重
w[-1] -= lr * g.sum(0) # 偏置不罚
idx = rg.permutation(n)[: 60] # 只用 60 条训,逼出「记忆」
p1 = float(f1(X[tgt:tgt + 1])[0, y[tgt]]) # 训练集里有它
p2 = float(f2(X[tgt:tgt + 1])[0, y[tgt]]) # 训练集里没有它
实跑输出:
--------------------------------------------------------------------
成员推断:正则 0.001 时置信度差 0.239,加到 1.0 只剩 0.049
--------------------------------------------------------------------
样本 × 特征 800 条记录 × 20 维
目标患者 第 1 条,标签 1
重复次数 60 次,每次重抽 60 条训练集
正则 λ 在集内 不在集内 置信度差 相对差
---------------------------------------------
0.001 0.969 0.730 0.239 33%
0.010 0.891 0.705 0.186 26%
0.100 0.742 0.621 0.120 19%
1.000 0.579 0.530 0.049 9%
**这张表说的是一件很反直觉的事:模型「记得」训练数据,就等于泄露了训练数据。**
同一份病历,被训过的模型给它的置信度是 **0.969**;
换成「没见过这条」的模型,降到 **0.730**。
**这个落差就是攻击者要的信号——他不需要拿到数据,只要问模型一个数就够了。**
把 λ 从 0.001 加到 1.0:**置信度差从 0.239 掉到 0.049,缩到五分之一;**
**相对差从 33% 掉到 9%。**
**因为强正则把「记忆」压住了——所有人的置信度都被拉平到 0.5 附近,攻击者就分不出谁被训过。**
注意这不是免费的保护:**λ 越大,模型本身也越不准。**
**隐私和精度在医疗 AI 里是一条很直的取舍线,没有两边都拿的方案。**
工程上还有两个更有效的手段:
**① 差分隐私训练(训练时给梯度加噪);② 只发布预测接口,不发布权重。**
**第二条在医疗场景里几乎是必选项**——权重一旦出去,成员推断、属性推断都能做。
要说明:数据、权重、目标患者都是我造的,**置信度差的绝对值和数据集强相关**。
**但「正则越弱、泄露越多」这个方向是真的。**
先看「置信度差」这一列,它是这个 Demo 的唯一主角:0.239 → 0.186 → 0.120 → 0.049。 λ 每调大一次,攻击者的信号就弱一截。 但「在集内」那一列同时在掉:0.969 → 0.891 → 0.742 → 0.579。 这两列是一起动的——这就是隐私与精度的正面对撞。 想让差值归零,最彻底的办法是把模型训练成一个常数,那当然不泄露,也当然没用。 要特别说一句:这个 Demo 一开始跑不出来。第一版用的是 800 条全量训练、λ 只有 4 档、 网络也只有线性层,结果四个 λ 的置信度差都在 0.001 上下,AUC 恒等 0.54——看起来「毫无泄露」。 真相是模型根本没学到东西,没记住自然也就没泄露。 这件事本身就是一个重要提醒:测隐私泄露之前,必须先确认模型是能工作的。 一个欠拟合的模型永远通过隐私测试,这跟「不联网的电脑不会中病毒」是同一类结论。 后来把训练集压到 60 条、迭代加到 3000 次,差距才显出来。 要说明:数据、权重、目标患者都是我造的, 置信度差的绝对值和数据集强相关,换一批数据这个数会变。 但「正则越弱、泄露越多」这个方向是真的。
30 秒实验
- 把训练集从 60 条加到 600 条,重跑 λ=0.001 那一行。 置信度差会明显变小——数据多了,单条样本的影响被稀释。
- 把 λ 从 1.0 再加到 10.0,看两列。 差值会继续缩,但「在集内」的置信度会掉到 0.5 附近,模型基本废了。
- 把目标患者换成一条标签噪声很大的样本,看差值。 它会变得极不稳定——噪声样本本身置信度就低,「记住」的证据不成立。
常见误区
- 「只发布模型权重、不发布数据就安全」。 权重里编码了训练数据,成员推断能定量测出「谁被训过」。
- 「做了脱敏就没有隐私风险」。出生日期、性别、邮编三项组合就能重识别,医疗数据里这三项全有。
- 「加大正则就能解决」。 λ 加到 1.0 时置信度差确实缩到 0.049,但模型在训练集上的置信度也掉到 0.579,等于不能用了。
自测三问(点开对答案)
1. 什么是成员推断攻击,它为什么只需要一个数? —— 判断某条记录是否在训练集里。 模型对训过的样本置信度明显更高,攻击者对比置信度就能推断,不需要拿到数据本身。 2. 为什么「只发接口不发权重」比调正则更有效? —— 成员推断依赖反复查询同一批样本、对比置信度。 接口层能做频次限制和置信度取整,权重外流则完全无法约束。 3. 这个 Demo 第一版为什么测不出泄露? —— 模型欠拟合,对谁都不给高置信度,自然没有差值。 必须先确认模型「记得住」,才谈得上测「记得太多」。隐私解决的是「数据别泄露」。 下一个问题是「模型怎么在不碰旧数据的前提下学会新东西」——这在医疗里是常态,不是特例。
- ☐ (本节完,下一节 11.1.2 持续学习机制)