传统机器学习是「把数据搬到计算」——所有数据汇总到中心机房再训练。联邦学习反过来,是「把计算搬到数据」——和你熟悉的 MapReduce 数据本地性(data locality)同一个思想:不动数据,下推代码。区别是这里 reduce 阶段聚合的不是数据,而是各节点本地算出的模型梯度,原始数据永远留在用户设备上。
痛点:医院病历、手机输入法记录、银行交易流水——这些数据既敏感又分散,法律(GDPR)和商业利益都不允许集中上传,但不集中又训练不出好模型。联邦学习的答案是:模型去找数据,而不是数据去找模型。
机制就是 McMahan 等人 2016 年提出的 FedAvg(联邦平均),一个反复执行的循环:服务器把当前全局模型下发给一批客户端 → 每个客户端用本地数据训练几步 → 只把权重更新(不是数据)传回 → 服务器按数据量加权平均,得到新全局模型。
聚合公式很朴素:w ← Σ (n_k / n) · w_k。w_k 是第 k 个客户端训练后的本地权重,n_k 是它的样本数,n 是总样本数。直觉:谁的数据多,谁的本地模型在平均里就更有发言权——本质就是一次加权 reduce。
import numpy as np # FedAvg 的核心其实就是这个加权平均函数(框架可用 Flower/FedML) def fedavg(client_weights, client_sizes): # client_weights: 每个客户端本地训练后的权重向量列表 # client_sizes: 每个客户端的本地样本数 n_k total = sum(client_sizes) # 按样本量加权:数据多的客户端权重占比更大 global_w = sum( (n_k / total) * w_k for w_k, n_k in zip(client_weights, client_sizes) ) return global_w # 这就是下一轮要下发的全局模型 # 模拟:3 个客户端,原始数据从未离开它们 w = fedavg([np.array([1.,2]), np.array([3.,4]), np.array([2.,2])], [100, 50, 200]) # 银行(200)发言权最大 print(w) # [1.857 2.286] —— 偏向样本多的客户端
差分隐私像数据库查询的「可控脱敏」:你能问「平均工资是多少」,但系统会在答案里掺一点精心校准的噪声,让你无法通过「有张三 vs 没张三」两次查询的差值反推出张三的工资。关键是隐私不再靠「我觉得安全」,而是一个可以用数字 ε 量化、可以累加预算的数学保证——类似你熟悉的限流配额(token bucket):每次查询消耗预算,用完就不能再查。
痛点:传统「匿名化」(删名字、删身份证)反复被攻破——靠几个旁路字段交叉比对就能重新识别个人。差分隐私给出一个更硬的定义:一个随机算法 M 满足 ε-差分隐私,当且仅当对任意只差一条记录的两个数据集 D、D′,以及任意输出 S:
Pr[M(D) ∈ S] ≤ eε · Pr[M(D′) ∈ S]
直觉拆解:加不加你这条记录,输出的概率分布几乎不变(被 eε 这个倍数夹住)。于是攻击者看到结果,也分不清你到底在不在数据集里——你的存在被「淹没」了。ε(隐私预算)越小,两个分布越像,隐私越强,但噪声越大、精度越低。这是隐私与可用性之间一个没法绕开的旋钮。
怎么实现?最经典的 Laplace 机制:在真实答案上加 Laplace 噪声,大小 = 敏感度 / ε。敏感度 = 改动一条记录最多让答案变多少(「计数」查询为 1)。用到深度学习就是 Abadi 等人 2016 的 DP-SGD:训练时先裁剪每个样本的梯度(控制敏感度),再加高斯噪声,并用「moments accountant」精确累计全程花掉的总 ε。
import numpy as np # Laplace 机制:对一个「计数」查询做差分隐私发布 def private_count(true_count, epsilon, sensitivity=1.0): # 计数查询:增删一条记录最多让结果变 1,故 sensitivity=1 scale = sensitivity / epsilon # 噪声幅度 ∝ 1/ε noise = np.random.laplace(loc=0, scale=scale) return true_count + noise # 发布加噪后的结果 true = 1000 # 真实有 1000 个患者得某病 print(private_count(true, epsilon=0.1)) # 强隐私 → 噪声大,可能 987 或 1013 print(private_count(true, epsilon=2.0)) # 弱隐私 → 噪声小,约 1000.4 # 训练神经网络时用官方库 Opacus(PyTorch)一行接管: # from opacus import PrivacyEngine # model, optim, loader = PrivacyEngine().make_private(...) # 自动裁剪+加噪
普通加密像把数据锁进保险箱——要用就得先解锁,解锁瞬间明文就暴露给了服务器。同态加密(HE)是一个「带手套的保险箱」:你能隔着手套在箱子里直接操作,全程不打开。对应到你的世界:服务器能对加密的数据库字段直接做 SUM / 点积,算完返回的还是密文,只有持私钥的你能解开——而服务器从头到尾没见过一个明文。
痛点:你想用云端的算力或第三方模型处理敏感数据(病历、财务),但不信任对方。明文上传 = 泄密;本地算 = 没算力。HE 让你「把计算外包出去,又不交出数据」。
数学核心是「保持运算结构」:加密函数 E 满足 E(a) ⊕ E(b) = E(a + b)。也就是说,密文上的某种运算 ⊕,解密后正好对应明文的加法。能同时支持密文加法和乘法、且支持任意次的,叫全同态加密(FHE)——这是 Craig Gentry 在 2009 年用理想格(ideal lattices)首次构造出来的,是密码学几十年的里程碑。
为什么以前做不到?每次密文运算都累积噪声,乘法尤其涨得快,噪声超阈值就再也解不开。Gentry 的突破是 bootstrapping(自举):用同态运算「给密文自己解密再重新加密」把噪声重置,从而支持无限深度计算。代价是极慢——这也是 HE 至今没普及的根因。机器学习中更常用 CKKS 方案(Cheon 等人 2017),支持近似实数运算,适合容忍少量误差的神经网络推理。
import tenseal as ts # OpenMined 的 HE 库,封装了 CKKS # 1) 建上下文 = 生成密钥 + 设定 CKKS 参数 ctx = ts.context(ts.SCHEME_TYPE.CKKS, poly_modulus_degree=8192, coeff_mod_bit_sizes=[60, 40, 40, 60]) ctx.global_scale = 2**40 ctx.generate_galois_keys() # 2) 加密两个向量(想象成你的私密特征) a = ts.ckks_vector(ctx, [1.0, 2.0, 3.0]) b = ts.ckks_vector(ctx, [4.0, 5.0, 6.0]) # 3) 直接在密文上算点积——服务器侧可执行,看不到明文 enc_dot = a.dot(b) # 全程是密文运算 print(enc_dot.decrypt()) # ≈ [32.0] = 1*4+2*5+3*6,近似但够准
安全多方计算(MPC)要解决一个看似悖论的问题:几方一起算出一个共同结果,但谁都不暴露自己的输入。类比你熟悉的分布式 quorum:数据被切成若干「秘密份额(secret share)」散在各方手里,单份毫无意义,只有按协议合起来才有结果——但 MPC 更狠,它合出的是「计算结果」而非原始数据本身。经典启蒙题是姚期智的「百万富翁问题」:两个富翁想知道谁更有钱,又都不肯说出自己有多少。
痛点:多家医院想联合统计某药疗效,但谁都不能看别家的病历;多家银行想联合反欺诈,又受竞争与合规所限。需要「合作计算,零信任共享」。
最易懂的机制是加性秘密分享(additive secret sharing):把私密数 x 拆成几个随机数之和 x = x₁ + x₂ + x₃ (mod p),每方只拿一个 xᵢ。单看任何一份都是均匀随机数、泄露零信息;但加法可各方在自己份额上独立做,最后把结果份额相加,就能还原 x + y 的真值——全程没人见过原始数 x、y。乘法更复杂(需 Beaver 三元组等协议),但思想一致。
联邦学习里最重要的落地是 Bonawitz 等人 2017 的 安全聚合(Secure Aggregation):客户端用配对掩码把梯度「加性分享」给服务器,服务器只能解出所有梯度之和、看不到任何单个客户端的梯度,还能容忍部分客户端掉线——正好补上概念①「只传梯度也泄密」的窟窿。
import random P = 2**31 - 1 # 一个大素数,所有运算在 mod P 下进行 def share(secret, n=3): # 拆成 n 份随机份额,前 n-1 份随机,最后一份兜底使总和=secret parts = [random.randrange(P) for _ in range(n - 1)] parts.append((secret - sum(parts)) % P) return parts # 任何单份都是均匀随机数,泄露零信息 # 两方各自把私密数拆成份额,分发到 3 个计算节点 sx, sy = share(5), share(3) # Alice=5, Bob=3,互不告知 # 每个节点只在自己手里的份额上相加(本地、无需通信) node_sums = [(a + b) % P for a, b in zip(sx, sy)] # 最后公开合并各节点结果 → 还原 x+y,但 5 和 3 从未暴露 print(sum(node_sums) % P) # 8 ✓
它们不是互斥的替代品,而是保护不同层面的积木,生产系统通常叠加:
| 技术 | 保护什么 | 主要代价 | 信任假设 |
|---|---|---|---|
| 联邦学习 | 数据不集中(位置) | 通信成本、Non-IID | 聚合服务器半可信 |
| 差分隐私 | 个体不可识别(结果) | 精度下降 | 加噪方可信 |
| 同态加密 | 计算中不可见(过程) | 慢几个数量级 | 无需信任算力方 |
| 安全多方计算 | 各方输入机密(过程) | 通信轮次多 | 多数方不合谋 |
典型组合:联邦学习定框架(数据不出门)→ 安全聚合 / MPC 让服务器看不到单个梯度 → 差分隐私给模型加噪防个体反推 → 必要时对最敏感片段加 HE。一句话:FL 管位置,MPC/HE 管过程,DP 管结果。