简介:联邦学习通过共享模型梯度来协作训练,但明文梯度存在隐私泄露风险,恶意服务器可利用模型反推等手段还原参与方的私有数据。安全聚合作为关键防御技术,旨在保护各方梯度不被窥探。Shamir门限秘密共享通过多项式拆分与拉格朗日插值,在份额域完成聚合,使服务器仅接触聚合结果而无法还原单个参与方梯度,具备信息论安全性。这类技术特别适用于医疗影像、金融数据等隐私敏感场景。基于该思想实现的FedSTSS安全聚合协议,在保持FedAvg模型精度的同时,将通信开销控制在合理倍数内,为联邦学习的隐私保护提供了无损替代方案。本文从协议原理、Python可复现代码到对比实验设计逐步拆解,并总结了定点数编码、份额序列化等工程实践中的关键陷阱,帮助研究者和工程师快速落地验证。
1. FedSTSS 在解决什么问题:一条反直觉的结论
如果你做过联邦学习实验,多半会觉得“梯度又不是原始数据,服务器拿到了也无所谓”。但真实情况是:恶意服务器可以从梯度反推训练样本,甚至还原出参与方本地的私有图像数据。基于Shamir门限秘密共享的联邦学习安全聚合模型(FedSTSS)做的正是把“梯度明文上传”替换成“份额分发、份额域聚合、门限恢复”这套流程,服务器全程接触不到任何单一参与方的梯度。这篇笔记适合三类人:做联邦学习安全聚合方向研究的、毕业设计需要演示对比实验的、以及想弄明白门限秘密共享在真实联邦系统里怎么落地的工程师。下面从协议原理讲到可复现代码,再把对比实验设计和五个高频踩坑点一起说清楚。
2. 从 FedAvg 到 Shamir 门限:FedSTSS 为什么把秘密拆成 t 份
2.1 威胁模型:诚实但好奇的服务器为什么能反推训练数据
联邦学习最常见的基线算法是 FedAvg:服务器下发全局模型,参与方在本地用私有数据训练若干轮,再把模型更新(或梯度)加密传输回服务器,服务器做加权平均。这里有个容易被忽略的前提——加密传输只解决“传输过程被窃听”的问题,服务器本身拿到的是解密后的明文梯度。这就落入了“诚实但好奇”的威胁模型:服务器会按协议执行,但它会偷偷分析收到的梯度。
为什么梯度泄露很危险?因为梯度是训练数据的函数。对图像分类任务,服务器只要拿到最后一轮某个参与方的梯度,用模型反推(model inversion)或梯度匹配(gradient matching)方法,可以恢复出该参与方训练集中的代表性样本。对人脸、医疗影像这类敏感数据,这等于直接泄露隐私。更实际的情况是:联邦学习系统的运营方和参与方经常不是同一主体,参与方把梯度交出去等于把数据使用痕迹交给了平台方。
常见的缓解手段是差分隐私(DP),在梯度上叠加噪声。DP 的代价是精度损失,而且噪声标度需要针对每一轮训练调参,调大了模型不收敛,调小了隐私预算又不够。另一类手段是同态加密,但它对非加法运算支持有限,性能开销也高。FedSTSS 走的是第三条路:用 Shamir 门限秘密共享把梯度拆成若干份额,让服务器只能看到聚合后的梯度,看不到任何个人的梯度。这条路在数学上是信息论安全的——即使服务器拿到了 t-1 个份额,也得不到任何关于秘密的信息。
2.2 Shamir 门限秘密共享的数学基础:多项式与拉格朗日插值
Shamir 门限秘密共享的核心思想可以用一句话概括:要分享秘密 s,就随机找一个 t-1 次多项式 f(x),让 f(0)=s,然后把 f(1)、f(2)……f(n) 分别发给 n 个参与方。任意 t 个人凑齐,可以用拉格朗日插值恢复出 f(0),也就是秘密 s;少于 t 个人,拿到的只是多项式上的若干个孤立点,无论怎么猜都得不到 s 的任何信息。
这里的多项式定义在有限域上,通常取一个大素数 p 作为模数。假设 p=2^61-1(这是一个梅森素数,Python 的 int 可以高效处理),秘密 s 和所有运算都在 0 到 p-1 之间。构造多项式时,常数项就是秘密,另外 t-1 个系数用密码学安全的随机数生成器产生。每个参与方拿到的份额是 (i, f(i)),i 通常是参与方编号 1 到 n。
恢复秘密时用的是拉格朗日插值。给定 t 个点 (x_i, y_i),恢复 f(0) 的公式是:
result = Σ y_i · Π_{j≠i} (-x_j) / (x_i - x_j)
分母的除法在有限域里是乘以分母的模逆元。Python 里可以用 pow(den, p-2, p) 直接算出模逆,这是费马小定理的应用。代码落在第 3 章。
FedSTSS 选择 Shamir 而不是简单地把梯度切成几段发给不同参与方,关键原因是 Shamir 份额具有可加性:两个多项式相加,次数不会超过 t-1,而且在 x=0 处的值等于两个秘密之和。也就是说,每个参与方把自己收到的同一编号份额相加,再把相加后的份额交给服务器,服务器恢复出来的就是所有参与方梯度之和。整个过程可以完全在份额域完成聚合,没有任何一方看到完整的个人梯度。
2.3 FedSTSS 的核心协议:在份额域完成加权聚合
FedSTSS 的协议流程可以拆成四个步骤。第一步,服务器广播全局模型参数;第二步,每个参与方在本地训练,计算模型更新向量;第三步,参与方把更新向量中每个标量分量分别做 Shamir 门限分享,得到 n 个份额,自己保留一个,其余发给其他参与方;第四步,参与方把手里所有份额向量逐分量相加,得到聚合份额并上传给服务器;服务器收集到至少 t 个参与方的聚合份额后,逐分量执行拉格朗日插值,恢复出全局更新。
这里容易混淆的一点是:服务器恢复的是“聚合后的梯度”,不是某个参与方的梯度。只要参与方数量不小于门限 t,服务器就只拿到一个总和的秘密共享,单方的梯度信息被 n-1 个其他参与方的随机份额掩盖。这样 FedAvg 的加权平均逻辑完全保留,只是把“明文上传梯度”换成了“份额上传、门限恢复”。与 SecAgg 方案相比,FedSTSS 省掉了双掩码的协商过程,代价是通信量更大,后面第 4 章的对比实验里会量化说明。
协议里还有两个必须明确的参数:门限 t 和参与方数量 n。t 决定安全性:服务器或者攻击者要恢复任何个人梯度,至少要拿到 t 个参与方的聚合份额(或者合谋 t 个参与方)。t 越大越安全,但容错能力越差——参与方掉线超过 n-t 个,这一轮就恢复不了。实践中我一般取 t ≥ n/2+1,既保证过半数诚实,又能容忍接近一半的参与方掉线。
3. 最小可复现的 Python 实现:三个核心模块直接跑通
3.1 shamir.py:份额生成、份额相加与门限恢复
先实现 Shamir 部分。以下代码可以用 Python 3.8+ 直接运行,不需要装额外依赖;环境只需要 numpy 和 PyTorch,前者做向量运算,后者做本地训练。
import secrets P = 2**61 - 1 # 梅森素数,所有运算在有限域上进行 def encode_float(x, scale=10**6): """把浮点数编码成有限域里的整数,负数会映射到 p-x 区间""" v = int(round(x * scale)) return v % P def decode_int(v, scale=10**6): """把有限域整数解码回有符号浮点数""" if v > P // 2: v -= P return v / scale def make_poly(secret, degree, p=P): """构造 t-1 次多项式,常数项是秘密,其余系数随机""" coeffs = [secret % p] + [secrets.randbelow(p) for _ in range(degree)] return coeffs def eval_poly(coeffs, x, p=P): """霍纳法求多项式在 x 处的值""" acc = 0 for c in reversed(coeffs): acc = (acc * x + c) % p return acc def share(secret, t, n, p=P): """把秘密拆分给 n 个参与方,至少 t 个参与方可以恢复""" assert t <= n, "门限不能大于参与方数量" coeffs = make_poly(secret, t - 1, p) return [(i, eval_poly(coeffs, i, p)) for i in range(1, n + 1)] def recover(shares, t, p=P): """用拉格朗日插值从 t 个份额恢复秘密,shares 是 (x, y) 列表""" assert len(shares) >= t, "份额数量不足,无法恢复" shares = shares[:t] result = 0 for xi, yi in shares: num, den = 1, 1 for xj, _ in shares: if xi == xj: continue num = (num * (-xj)) % p den = (den * (xi - xj)) % p result = (result + yi * num * pow(den, p - 2, p)) % p return result份额生成用的是 t-1 次多项式,所以系数个数是 t-1 加上常数项。随机数来源用 secrets 模块而不是 random,因为密码学场景不能用伪随机数生成器。P 取 2^61-1 是经过考虑的:有限域越大,编码时溢出风险越小,同时 Python 的 int 运算在这里仍然很快。recover 函数中用 pow(den, p-2, p) 计算模逆,这是费马小定理的标准用法。
这里要特别注意 encode_float 的 scale 参数。梯度数值一般在 0.001 到 0.1 量级,scale 取 10^6 可以把浮点误差压到 10^-6 以下。scale 太小会让恢复结果和原始浮点数偏差过大;scale 太大则可能让整数超出有限域范围,导致模运算后无法正确还原。
3.2 聚合协议模块:把梯度拆份额、收份额、在份额域求和
有了基础份额函数,下一步实现协议层。参与方本地的模型更新通常是一个很长的向量,做法是对每个分量独立做门限分享。这是逐分量共享,代码写成循环即可。
def share_vector(vec, t, n, p=P): """把梯度向量逐分量拆成 n 份,返回 n 个参与方的份额向量""" shares_per_party = [[] for _ in range(n)] for val in vec: # 每个分量拆成 n 个 (i, y) 对 shares = share(int(round(val * 10**6)) % p, t, n, p) for party_idx, (_, y) in enumerate(shares): shares_per_party[party_idx].append(y) return shares_per_party def aggregate_share_vectors(share_vectors, t, p=P): """服务器收到 m 个参与方的聚合份额向量,逐分量求和的份额做门限恢复""" n = len(share_vectors) dim = len(share_vectors[0]) restored = [] for d in range(dim): # 这 d 维在所有参与方上的份额相加,再做门限恢复 shares = [(i + 1, share_vectors[i][d] % p) for i in range(n)] restored.append(recover(shares, t, p)) return restoredshare_vector 返回一个二维结构:shares_per_party[i] 是参与方 i 应该持有的完整份额向量。注意协议里参与方不会把整个份额向量交给服务器,而是先在参与方之间互相交换份额、逐分量相加,最后把相加后的份额向量上传。aggregate_share_vectors 是服务器侧的操作,模拟的是“已经收到 t 个参与方的聚合份额”之后做恢复。
这个设计保证了服务器只接触聚合份额。实际工程里参与方之间的份额交换可以用 gRPC 或 RabbitMQ 做点对点传输,本地实验用内存矩阵模拟流程即可。矩阵的行下标代表“份额编号”,列下标代表“参与方编号”,参与方 i 把自己生成的所有第 j 行份额发给参与方 j,收到后按列相加。
3.3 主训练循环:用 PyTorch 在 MNIST 上验证 FedSTSS
接下来把协议和真实训练串起来。下面的代码在 MNIST 上验证 FedSTSS 的精度,参与方数量 n=5,门限 t=3,每个参与方本地训练 2 个 epoch。
import copy import torch import torch.nn as nn from torchvision import datasets, transforms class SimpleNet(nn.Module): def __init__(self): super().__init__() self.fc = nn.Sequential( nn.Flatten(), nn.Linear(28 * 28, 128), nn.ReLU(), nn.Linear(128, 10) ) def forward(self, x): return self.fc(x) def local_train(model, loader, epochs=2, lr=0.01): model.train() opt = torch.optim.SGD(model.parameters(), lr=lr) for _ in range(epochs): for x, y in loader: opt.zero_grad() loss = nn.functional.cross_entropy(model(x), y) loss.backward() opt.step() return model def flatten_grad(model, ref_model): """计算当前模型相对参考模型的更新向量(参数差)""" grad_vec = [] for p, ref in zip(model.parameters(), ref_model.parameters()): grad_vec.extend((p.detach() - ref.detach()).view(-1).cpu().tolist()) return grad_vec def fedstss_round(global_model, client_loaders, t, n, p=P): # 1. 每个参与方基于全局模型做本地训练 local_models = [local_train(copy.deepcopy(global_model), loader) for loader in client_loaders[:n]] # 2. 每个参与方拆自己的更新向量,得到份额矩阵 share_matrices = [] for model in local_models: grad_vec = flatten_grad(model, global_model) share_matrices.append(share_vector(grad_vec, t, n, p)) # 3. 参与方之间交换份额:第 i 个参与方持有所有份额矩阵的第 i 列 # share_matrices[k][i] 是第 k 个参与方分给第 i 个参与方的份额 aggregated = [] for party in range(n): col_sum = [0] * len(share_matrices[0][party]) for k in range(n): for d in range(len(col_sum)): col_sum[d] = (col_sum[d] + share_matrices[k][party][d]) % p aggregated.append(col_sum) # 4. 服务器收集全部参与方的聚合份额,逐维恢复全局更新 restored_grad = aggregate_share_vectors(aggregated[:t], t, p) scale = 10**6 restored_grad = [decode_int(v, scale) for v in restored_grad] # 把向量还原成模型参数形状并更新全局模型 idx = 0 new_state = copy.deepcopy(global_model.state_dict()) for name, param in global_model.named_parameters(): numel = param.numel() delta = torch.tensor(restored_grad[idx:idx + numel]).view_as(param) new_state[name] = param.detach() + delta idx += numel global_model.load_state_dict(new_state) return global_model实验数据准备部分不做展开,重点看坐论逻辑:第 3 步参与方之间交换份额时,share_matrices[k][party] 的含义是“第 k 个参与方分给第 party 个参与方的份额”,每个参与方拿到所有 k 的对应列后相加。第 4 步服务器只取前 t 个参与方的聚合份额做恢复——如果某个参与方掉线,只要剩余的聚合份额够 t 个,这一轮依然能完成。实际运行前要确认 PyTorch 环境里 torchvision 的 MNIST 数据集路径正确;如果不想下载数据集,也可以用 sklearn 的 make_classification 生成合成数据替换。
这个循环看起来简单,但它是 FedSTSS 和 FedAvg 的唯一区别——FedAvg 里参与方直接把 grad_vec 发给服务器,FedSTSS 里发的是聚合份额。把第 3、4 步换成直接发送明文梯度,就是 FedAvg 基线。
3.4 文档说明部分应该包含什么
作为一个“源码+文档说明”的交付项目,文档说明的价值在于让评审或者接手的人 5 分钟内复现实验。我一般会按四块组织。第一块是协议设计文档,画清参与方、服务器、份额流向的时序图,明确威胁模型假设。第二块是实验环境说明,列出 Python 版本、PyTorch 版本、依赖安装命令,这个信息比实验代码本身更容易被忽略。第三块是复现步骤,从 MNIST 数据下载到运行主脚本、再到复现对比实验的完整命令。第四块是结果记录,包括各基线在固定随机种子下的精度表、通信量统计、以及实验日志的存放位置。
这里尤其要注意随机种子管理。联邦学习实验对随机种子极其敏感,同一个配置换一个 seed,精度可能差 0.5 个百分点以上。文档里必须写明每个实验用哪组 seed,以及是否固定了数据划分的随机性。不然对比实验的结果根本无法复现,这也是“高分项目”和“能跑的项目”之间最明显的分界线。
4. 对比实验这样设计才有说服力:三组基线 + 三个评价维度
4.1 三组基线:FedAvg、FedAvg+DP、FedSTSS
对比实验的目的是回答一个问题:FedSTSS 在隐私保护和模型精度之间的取舍是否值得。为此需要三组基线。第一组是 FedAvg,不做任何隐私保护,作为精度的上界。第二组是 FedAvg+DP,在参与方上传梯度前叠加高斯噪声,噪声的方差由隐私预算 ε 决定,作为“有损保护”的代表。第三组是 FedSTSS,用 Shamir 门限做安全聚合,精度损失理论上只来自定点数编码。
实验配置建议保持一致:相同的数据划分方式(包括 IID 和 Non-IID 两种情况)、相同的参与方本地训练轮数、相同的全局通信轮数。只有这样才能把差异归因到聚合协议本身。Non-IID 划分可以用狄利克雷分布控制数据分布偏移程度,常见做法是设置 α=0.5 做中度 Non-IID。
FedAvg+DP 的噪声标度需要单独调参。一个常见做法是固定裁剪阈值 C=1.0,然后把高斯噪声标准差设为 σ = C · sqrt(2·ln(1/δ)) / ε,其中 δ 取 1e-5。ε 可以从 8 到 1 试几个档位,你会看到 ε 越小精度掉得越快。这一步能直观展示 DP 的精度代价,与 FedSTSS 的“无损保护”形成对比。
4.2 三个评价维度:精度、通信量、隐私保护强度
对比实验至少要从三个维度看结果。精度维度记录全局模型在测试集上的 top-1 accuracy,每一轮都记录,画成曲线。FedSTSS 的精度应该和 FedAvg 几乎重合,差距在 0.2 个百分点以内属于正常浮动;超过 1 个百分点就要检查定点数编码是不是出了问题。
通信量维度统计每一轮全局通信的字节数。FedAvg 是 n 个参与方各上传一个维度为 d 的 float32 向量,总字节数约 n·d·4。FedSTSS 是 n 个参与方各上传一个维度为 d 的份额向量,每个份额是 0 到 p-1 的整数,如果用 uint64 打包,字节数约 n·d·8。也就是说 FedSTSS 的通信量是 FedAvg 的 2 倍左右——这个代价不算离谱,但在大规模参与方场景下需要权衡。
隐私保护强度维度是最难量化、也最重要的维度。常见的做法是做一个“梯度反推攻击”实验:假设服务器拿到了通信内容,尝试用 model inversion 恢复参与方的训练图像。对 FedAvg 可以成功恢复出近似图像;对 FedSTSS 因为服务器拿不到任何个人梯度,攻击直接失败。这个实验能直观展示 FedSTSS 的隐私保护效果,比堆一堆理论公式更有说服力。
4.3 实验表格怎么读:先看趋势,再看方差
| 基线 | 梯度处理方式 | 隐私保障类型 | 预期精度 | 单轮通信量 |
|---|---|---|---|---|
| FedAvg | 明文上传 | 无保护 | 最高(基准) | n·d·4 字节 |
| FedAvg+DP | 加噪后上传 | 差分隐私(有损) | 随 ε 减小明显下降 | n·d·4 字节 |
| FedSTSS | Shamir 门限份额聚合 | 信息论安全(无损) | 与 FedAvg 几乎一致 | n·d·8 字节 |
读这个表格最关键的是看趋势而不是看单个数值。如果 FedSTSS 的精度曲线和 FedAvg 非常接近,说明安全聚合没有破坏模型收敛性;同时通信量曲线稳定在 2 倍左右,说明付出的代价是可预测的。FedAvg+DP 的精度曲线则会呈现一个明显的下降区间,这个区间越陡,说明隐私保护对模型质量的破坏越大。
还要注意方差问题。联邦学习实验里,Non-IID 场景下不同 seed 之间的精度波动经常比方法之间的差异还大。我在设计实验时会固定 3 个 seed,每个配置跑 3 次,报告均值和标准差。如果你只跑一次、只报一个数,评审很难判断结果是真实差异还是随机波动。另外 Non-IID 场景下还要关注参与方本地灾难性遗忘问题——参与方本地多轮训练后模型会严重偏向本地分布,这和联邦学习里的数据分布偏移是同源的。FedSTSS 不解决灾难性遗忘,它只保证梯度隐私,所以对比实验要把这两个因素分开看。
5. FedSTSS 避坑指南:份额丢失、浮点误差、阈值设置三座山
5.1 恢复出的梯度对不上原始梯度:定点数编码和素数域
现象:恢复出来的全局模型在测试集上精度急剧下降,或者直接不收敛。检查恢复前后梯度的差值,发现误差在 0.1 量级。
原因:最常见的翻车点是把浮点梯度直接塞进 Shamir 共享。浮点数在有限域里没法直接精确表示,必须转成整数。整数和浮点数之间的转换精度不够,每一轮的误差累积起来就会毁掉模型。另一个原因是梯度数值乘上 scale 之后超过了素数 p 的一半,导致 decode_int 时正负号判断出错。
解决:scale 至少取 10^6,并且在编码后用 max(abs(grad_vec)) * scale 和 p/2 做个对比检查。工程上我习惯写一行 assert 防止溢出:
assert max(abs(g * 10**6) for g in grad_vec) < P // 2, "梯度数值超出有限域范围,请调大素数或调小 scale"5.2 服务器拿到完整份额,隐私目标形同虚设
现象:对照第 3.3 节的协议实现,把“聚合份额相加”这一步简化成了参与方直接把个人梯度份额全量发给服务器,服务器自己执行 recover。
原因:这是从 FedAvg 迁移到 FedSTSS 时最容易犯的协议级错误——只做了“份额化传输”,没有做“份额域聚合”。如果服务器收到的是“参与方 A 的个人梯度份额”和“参与方 B 的个人梯度份额”,它只要收集到 t 份,就能恢复出单个参与方的梯度,隐私保护完全不成立。
解决:必须保证参与方之间先交换份额、在参与方侧完成相加,服务器只接收“聚合后的份额”。实现层面最简单的方法是矩阵列求和:把参与方编号作为列,份额编号作为行,每个参与方把所有收到的第 i 行份额相加后再上传。这个顺序不能颠倒,协议设计文档里建议单独画一张时序图说明。
5.3 阈值 t 设太小,合谋参与方轻松拆穿个体隐私
现象:算法在 n=10、t=2 时跑通了,精度也没问题,但安全性经不起推敲。两个参与方合谋就能把第三个参与方的梯度恢复出来。
原因:Shamir 门限的语义就是“任意 t 个份额可以恢复秘密”。t 设得小,固然容错能力强,但也意味着安全门槛低。攻击者只要拿下 t 个参与方的份额(或者服务器与 t-1 个参与方合谋),就能恢复任意参与方的梯度更新。
解决:t 的设置要同时考虑诚实参与方比例和掉线容错。我一般取 t ≥ n/2 + 1,这样即便服务器与 n/2 个参与方合谋也恢复不了个人梯度;同时允许最多 n-t 个参与方掉线而不中断训练。如果你的场景掉线率很高,可以把 t 调到 2n/3,但明确接受安全性的下降。这个权衡要写进文档说明的威胁模型部分。
5.4 恢复结果看起来“差不多对”:批次顺序漂移和随机种子
现象:验证恢复出的梯度时,发现和直接计算的梯度相比,误差在 10^-4 量级,觉得还行。但多轮训练后模型精度明显低于 FedAvg。
原因:定点数编码误差确实不大,但联邦学习的每一轮都会叠加误差。更隐蔽的原因是本地训练的批次顺序不同——参与方本地数据打乱方式不一致,导致每个参与方算出的梯度本身就有差异。这种差异被误认为 Shamir 恢复误差,容易掩盖真正的问题。
解决:验证协议正确性时要先固定所有参与方的随机种子和批次顺序,先确保 FedAvg 和 FedSTSS 在相同输入下产出相同结果。排除顺序漂移后,再单独评估编码精度的影响。如果是非 IID 场景,还要留意参与方本地数据分布差异导致的梯度方向不一致,这是正常现象,不应当归咎于聚合协议。
5.5 份额序列化方式不当,通信量白涨几倍
现象:第 4 章通信量对比里 FedSTSS 的实际通信量不是理论上 FedAvg 的 2 倍,而是 5 到 10 倍。排查发现是份额序列化方式的问题。
原因:Python 的 int 对象在内存里不只是 8 字节,它还包含对象头,pickle 序列化后一个 2^61 量级的整数可能要占 30 字节以上。如果用 JSON 或者 pickle 传输份额,通信量会大幅膨胀,对比实验的结果不再公平。
解决:把份额转换成标准 8 字节格式再传输。做法是用 numpy 的 uint64 数组存储份额,再用 tobytes() 序列化;接收端用 frombuffer() 还原。字节数就从 Python int 的 30 字节压到 8 字节。这一条要写进通信量统计代码里,否则对比实验的通信量数据没有任何说服力。
6. 把 FedSTSS 从“能跑”调到“可信”:验证、测通信、定阈值
最后一个环节是把实验做成“可信”的状态,而不是只跑通一个精度数字。我一般按三个步骤收尾。
第一步是验证协议正确性。在跑真实数据之前,先构造一个小规模的合成数据:随机生成一个梯度向量,分别用明文加聚合和 Shamir 份额聚合各算一遍,断言两者之差小于 10^-5。这个验证相当于给协议实现上保险,确保后面所有实验差异都来自协议设计而不是代码 bug。顺手保存一份恢复前后的梯度快照,翻车时这就是后悔药。
第二步是准确统计通信量。不要用字节数估算,直接在代码里对每次发送的数据调用 len(payload) 累加。FedSTSS 的单轮通信量应当稳定在 FedAvg 的 2 倍左右。如果高于 3 倍,回去查份额序列化方式;如果低于 1.8 倍,检查是不是有参与方没有完整上传份额。
第三步是按场景确定门限 t。通信量和安全性在 t 上是对立的:t 越小,需要的聚合份额越少,参与方掉线容忍度越高,但安全性越低。我会先测掉线率,再决定 t 的取值。掉线率低于 10% 的场景,t 可以取 n-1;掉线率高于 30% 的场景,t 只能取 n/2 附近。每换一个 t 值,都要重新跑一遍精度和攻击实验,不要沿用上一个配置的结果。
这套流程跑完,FedSTSS 的精度曲线、通信量曲线、隐私攻击实验结果三张表就能完整回答“值不值得用”这个问题:精度几乎无损、通信量翻倍、隐私从明文变成信息论安全。对大多数隐私敏感的联邦学习场景,这个代价是划算的。我自己的习惯是每次实验前固定 seed 矩阵和配置版本号,避免不同轮次的实验结果互相污染,这个习惯帮我躲过了不少自我怀疑的时刻。希望帮到你。
本文还有配套的精品资源,点击获取