
简介面向毕业设计场景这套联邦学习攻击预防方向的Python代码复现资源包包含完整源码、文档说明与运行方法适合计算机、人工智能、通信工程等专业学生作为毕设、课设或项目入门参考。包内共184个文件以Python脚本109个py为核心辅以yaml/xml配置文件、shell辅助脚本、Markdown/TXT说明文档等整体压缩包约391KB结构清晰便于按模块检索。代码均已在测试环境中运行成功作者上传前经过多轮验证下载后配合README文档即可快速了解项目结构并复现实验流程资源覆盖联邦学习场景下多类攻击与防御子模块可用于理解攻击原理、防御策略及模型评估方法也适合在现有代码基础上二次修改扩展满足个性化毕设需求。目前已有171人学习下载特别适合用于毕业设计初期演示或中期检查汇报能直观体现系统实现与实验数据分析能力。1. 联邦学习攻击预防复现到底在复现什么这是一类典型的毕业设计题目论文作者给了算法却没给完整工程。你要补的不是模型本身而是把「训练框架、攻击注入、防御策略、评估指标」串成一条可运行的链路。标题里的关键词——联邦学习、攻击预防、Python源码、论文代码复现——排在一起意味着最终交付物通常包含四块能跑通的横向联邦训练流程、能模拟恶意客户端的行为注入、能对照防御效果的鲁棒聚合或隐私保护机制、以及一份能让评委按步骤把实验复现出来的文档说明与运行方法。适合做这个方向的人至少要碰过 PyTorch 或 TensorFlow知道什么是参数聚合也读过几篇联邦学习综述。如果只调过单机模型的 fit 接口直接上手会卡的第一个地方不是攻击算法有多复杂而是「多个客户端 一个服务器」的迭代逻辑到底怎么组织。把这个骨架搭对剩下的事情就是往里面加攻击、加防御、加指标逐步逼近论文里的曲线。下面的内容我按自己复现这类项目的习惯来写从理论、工程、代码到排错一次说清楚。2. 联邦学习攻击面与预防技术选型从FedAvg说起2.1 横向联邦训练的基线流程与聚合公式绝大多数攻击预防论文的基线都是 FedAvg。横向联邦的场景里数据分布在多个客户端本地服务器只收集梯度或模型权重不碰原始数据。一轮标准流程是服务器把全局模型下发各客户端用本地数据训练若干轮回传模型更新服务器按样本量加权求平均形成新的全局模型。用公式表示就是[ W_{t1} W_t - \eta \sum_{k1}^{K} \frac{n_k}{n} \Delta W_k ]其中 (n_k) 是第 (k) 个客户端的样本数(\Delta W_k) 是该客户端的本地更新。复现时最容易出差错的地方就在这里有些论文用梯度做聚合有些用权重做聚合还有的在聚合前做学习率缩放。你拿到源码后首先要确认它走的是哪条路子这决定了后续攻击注入时改的对象到底是梯度还是权重。一个实用的工程做法是把「下发」、「本地训练」、「回传」、「聚合」拆成四个独立方法而不是全部塞进同一个 for 循环里。这样你才能在不破坏主流程的情况下单独替换聚合函数或单独在某几个客户端上做手脚。项目结构上常见方案是server.py管下发与聚合client.py管本地训练attacks.py独立于这两个模块之外。2.2 恶意客户端常见攻击方式数据投毒、梯度投毒与后门攻击预防针对的目标基本可以归成三类复现论文前得先分清楚它们作用在哪个环节。数据投毒发生在客户端本地恶意客户端把自己持有的样本标签翻转或注入错误标注的数据然后正常参与训练。这类攻击最隐蔽因为梯度本身看起来是合法的只是在朝着错误方向优化。梯度投毒更高阶一点恶意客户端不修改数据而是直接篡改上传的梯度或权重比如把梯度乘以一个很大的系数或者干脆把梯度的方向反转。后门攻击则是让模型在正常任务上表现正常只在带有特定触发图案的样本上输出攻击者指定的类别。这三类攻击对防御算法的挑战完全不同。数据投毒需要靠统计异常来发现梯度投毒需要靠几何距离或范数来做鲁棒聚合后门攻击则需要额外引入触发集验证。复现源码时建议把攻击写成一个可配置模块支持攻击类型、恶意客户端占比、注入强度三个参数这样后面跑消融实验才方便。攻击类型作用环节典型实现手段检测难度常见防御方向数据投毒本地数据集标签翻转、错误样本注入中统计过滤、置信度评估梯度投毒上传梯度/权重梯度放大、方向反转低梯度裁剪、范数检测后门攻击本地训练过程触发器样本混入训练集高触发集验证、鲁棒聚合2.3 预防路线怎么选鲁棒聚合、异常检测与差分隐私复现过程中你会遇到一个岔路口论文里的“预防”到底走哪条技术路线。主流方案有三种。鲁棒聚合的思路是替换掉 FedAvg 的简单平均核心思想是让恶意客户端的更新在聚合阶段失去影响力。Krum 选择与其他客户端距离最小的那个更新作为全局更新Coordinate-wise Median 对每个参数维度取中位数Trimmed Mean 则先裁掉每维最大和最小的部分再求均值。这类方法不依赖对攻击类型的先验知识但对恶意客户端占比有硬性要求通常假设少于 50% 的客户端是恶意的。异常检测的思路是在聚合前对梯度或者权重做一轮筛查用范数、方向余弦、聚类距离等指标找出离群更新直接丢弃。实现成本低缺点是容易误伤非独立同分布数据下的正常客户端因为数据分布不均时客户端之间的梯度差异本来就很大。差分隐私则不走“找坏人”的路线而是在更新的过程中加入校准过的高斯噪声让攻击者无法从模型反推具体数据。它防的是推理攻击和成员推断对投毒攻击则没有直接抑制作用。如果一个项目同时声称防投毒又防推理常见做法是把差私加在防御链路的末端先做鲁棒聚合过滤掉恶意梯度再加噪声保护隐私。复现代码时我一般会在配置里保留defense_method参数允许设置为none、clip、krum、median、dp中的任意一个方便对照。2.4 复现论文时的组件划分与模块边界代码复现最容易翻车的地方是论文的算法描述和实际工程实现之间存在细节缺口。拿防御算法举例论文里一句“剔除梯度范数异常值”落到代码里就要回答三个问题范数用什么计算阈值怎么定剔除后剩余梯度怎么重新聚合。因此源码结构建议按功能划分而不是按论文章节划分。我会把客户端、服务器、攻击器、防御器、评估器、可视化做成六个独立模块。数据加载单独放一个文件统一处理划分逻辑配置用参数类或 YAML 管理每次实验的随机种子、攻击参数、防御参数都记录下来。这个框架搭好之后复现任何一篇横向联邦攻击防御论文都只是往对应模块里填代码不会伤筋动骨。3. Python源码复现的环境与工程结构3.1 Python环境与依赖清单毕业论文项目最怕评委现场跑不起来。复现环境的搭建原则是版本锁死依赖最小化。PyTorch、NumPy、scikit-learn 这三个是核心依赖数据处理用 Pandas 就够不建议在毕业设计里引入超过十个的第三方包。推荐直接用 conda 建隔离环境conda create -n fedsec python3.9 -y conda activate fedsec pip install torch2.0.1 torchvision0.15.2 numpy1.24.3 scikit-learn1.3.0 pandas2.0.3 matplotlib3.7.2这里把版本写死不是随便挑的PyTorch 2.0 系列的 API 稳定NumPy 1.24 兼容新旧两套随机接口Python 3.9 对所有依赖的版本约束最少。如果你用的是 MPS 或者 CUDA 机器可以再装对应的 torch 变体但代码里必须做设备检测import torch device torch.device(cuda if torch.cuda.is_available() else mps if torch.backends.mps.is_available() else cpu)依赖清单写进requirements.txt时每个包都固定到具体版本不要用。原因很实际你半年后回看这个项目或者答辩前重装环境时浮动的版本号会让实验无法复现。文档说明里要单独写一节“环境安装”把上述命令原样放进 Markdown 文件。3.2 工程目录与关键文件职责一个合格的论文复现项目目录结构要让没见过这个项目的人在 5 分钟之内知道每份文件的用途。我常用的布局fedattk/ ├── config/ │ └── default.yaml # 参数配置 ├── data/ │ └── dataset.py # 数据加载与切分 ├── models/ │ └── cnn.py # 模型定义 ├── attack/ │ └── poisoning.py # 攻击注入逻辑 ├── defense/ │ ├── clip.py # 梯度裁剪与范数检测 │ └── krum.py # 鲁棒聚合 ├── server.py # 服务器端流程 ├── client.py # 客户端训练流程 ├── evaluate.py # 评估指标计算 ├── main.py # 入口 ├── requirements.txt └── README.md # 运行方法注意几个边界攻击模块不能直接修改全局模型它只能修改特定客户端回传的更新防御模块不能感知攻击模块的存在它只能基于收到的更新做判断。这种隔离是论文代码复现的工程化要点——攻击和防御必须能单独开关否则你没法证明防御效果到底是算法起作用还是代码顺序碰巧对了。3.3 数据集与任务选择Fashion-MNIST复现联邦学习攻击类论文数据集的选择直接影响实验效果。MNIST 太容易模型随便一训就 98% 以上攻击造成的精度下降不明显防御效果展示不充分。Fashion-MNIST 是更合适的选择类别多、特征复杂模型的正常精度在 88% 到 92% 之间给攻击和防御都留出了可见的波动空间。数据划分要提前做对。横向联邦要求把数据按客户端编号切分常见做法是每个客户端分配 2 到 4 个类别模拟非独立同分布场景。PyTorch 里用 Subset 实现from torch.utils.data import Subset def partition_data(dataset, num_clients, num_classes_per_client2): indices_by_class [[] for _ in range(10)] for idx, (_, label) in enumerate(dataset): indices_by_class[label].append(idx) client_indices [[] for _ in range(num_clients)] for client_id in range(num_clients): for class_id in range(num_classes_per_client): assigned_class (client_id class_id * 3) % 10 chunk indices_by_class[assigned_class][client_id::num_clients] client_indices[client_id].extend(chunk) return [Subset(dataset, idxs) for idxs in client_indices]这个切分逻辑的关键参数是num_classes_per_client。设成 10 就是独立同分布所有客户端数据分布一致设成 2 就是严重的非独立同分布客户端之间数据差异大这时候做防御评估才贴近真实场景。报告中要写清楚非独立同分布程度越高异常检测类防御的误报率越高这是模型需要面对的客观边界。4. 攻击预防的核心代码复现注入攻击与多层防御4.1 FedAvg与恶意客户端模拟先实现最基础的 FedAvg。客户端回传的是更新差值而不是完整权重因为防御算法多半基于差值做检测。核心训练循环长这样def fed_avg_round(server_model, clients, selected_ids, lr0.1): global_weights {k: v.clone() for k, v in server_model.state_dict().items()} updates [] total_samples 0 for client_id in selected_ids: client_model copy.deepcopy(server_model) samples clients[client_id].train(client_model, epochs1) update {k: (server_model.state_dict()[k] - client_model.state_dict()[k]) * lr for k in global_weights.keys()} updates.append((client_id, samples, update)) total_samples samples aggregated {k: torch.zeros_like(v) for k, v in global_weights.items()} for client_id, samples, update in updates: weight samples / total_samples for k in aggregated.keys(): aggregated[k] weight * update[k] server_model.load_state_dict(aggregated) return updates这里的关键设计是把「更新」定义为服务器权重 - 客户端权重而不是直接回传客户端权重。这样做的原因是防御算法需要比较各客户端本轮究竟学了多少、往哪个方向学。如果回传完整权重客户端初始权重是同一个全局模型直接比较权重也成立但计算距离时多了一组无效的基线偏移。用差值后范数检测和余弦相似度计算都更直观。恶意客户端的模拟在attack/poisoning.py里实现一个包装函数。数据投毒最简单训练前把标签翻转梯度投毒则是在训练完成后手动修改 updatedef inject_gradient_poisoning(update, poison_scale10.0): malicious_update {} for k, v in update.items(): malicious_update[k] v * poison_scale return malicious_update参数poison_scale控制攻击强度。设成 1 就是正常客户端设成 10 意味着恶意客户端的更新在聚合时会被放大十倍直接压过正常客户端的贡献。跑对比实验时建议从 5 开始调观察精度曲线的下降斜率再决定报告里用什么强度。4.2 梯度范数异常检测与裁剪范数异常检测是防御梯度投毒的第一道防线。恶意客户端为了让自己的更新主导聚合通常会把梯度放大这导致其更新范数远大于正常客户端。实现一个动态阈值的裁剪逻辑def gradient_clip_by_norm(updates, clip_ratio2.0): norms [torch.norm(torch.cat([v.flatten() for v in update.values()])) for _, _, update in updates] median_norm float(np.median(norms)) threshold median_norm * clip_ratio filtered_updates [] for client_id, samples, update in updates: norm norms[clients.index(client_id)] # 实际代码用循环索引 if norm threshold: filtered_updates.append((client_id, samples, update)) return filtered_updates阈值不写死而是用中位数乘以系数动态计算原因在于不同轮次的梯度范数差异很大。早期训练梯度普遍偏大后期梯度变小固定阈值要么早期失效要么后期误杀。clip_ratio是防御强度的核心参数设为 2.0 表示只有范数超过中位数两倍的更新会被剔除调小到 1.5 会更激进误杀率也升高。注意这个防御只对梯度放大类攻击有效。如果恶意客户端做标签翻转数据投毒梯度范数可能完全正常直接放行。所以严谨的复现对比实验里需要分别报告不同攻击类型下的防御表现不能拿范数检测防住梯度投毒的结果去论证它能防住所有攻击。4.3 相似度检测与Krum鲁棒聚合梯度范数检测挡不住刻意控制范数的恶意更新。更强的做法是做方向检测正常客户端在相似数据分布下梯度方向应该接近恶意客户端的方向会明显偏离。计算两两之间的余弦相似度矩阵import torch.nn.functional as F def cosine_similarity_matrix(updates): flat_vectors [] for _, _, update in updates: vec torch.cat([v.flatten() for v in update.values()]) flat_vectors.append(vec) matrix torch.zeros(len(flat_vectors), len(flat_vectors)) for i in range(len(flat_vectors)): for j in range(i 1, len(flat_vectors)): sim F.cosine_similarity(flat_vectors[i].unsqueeze(0), flat_vectors[j].unsqueeze(0)).item() matrix[i][j] matrix[j][i] sim return matrix用余弦相似度做判定的逻辑是无论梯度范数怎么缩放只要方向反转或者偏转到其他区域余弦值就会显著低于正常客户端之间的相似度。实际操作中我会把相似度矩阵交给 Krum 算法做鲁棒聚合替代简单的相似度阈值筛选。核心代码如下def krum_select(updates, f, num_selected1): sim_matrix cosine_similarity_matrix(updates) scores [] for i in range(len(updates)): distances sorted([sim_matrix[i][j] for j in range(len(updates)) if j ! i]) scores.append((i, sum(distances[:len(updates) - f - 2]))) scores.sort(keylambda x: x[1]) return [idx for idx, _ in scores[:num_selected]]Krum 的核心思路是给定假设的恶意客户端数量f对每个客户端计算它与其他客户端距离之和然后剔除最远的f个再选距离和最小者作为可信更新。这个模型有个前提假设——恶意客户端数量必须小于总客户端数的一半。复现论文时如果这个条件不满足Krum 会失效源码里必须做显式的参数校验并抛出警告。4.4 差分隐私加噪如果论文的预防包含隐私保护差分隐私加噪是标准收尾模块。它不关心谁是恶意的只保证即使攻击者拿到全局模型也无法推断某个特定样本是否存在。工程实现使用高斯机制def add_dp_noise(update, epsilon, delta, l2_sensitivity): noise_scale l2_sensitivity * np.sqrt(2 * np.log(1.25 / delta)) / epsilon noisy_update {} for k, v in update.items(): noise torch.normal(mean0, stdnoise_scale, sizev.shape, devicev.device) noisy_update[k] v noise return noisy_updateepsilon的经济学含义是隐私预算越小噪声越大、隐私保护越好但模型精度会肉眼可见地下降。这个模块的调参策略和防御算法完全相反防御算法是越强越好差分隐私则要在精度和隐私之间找平衡。常见做法是把epsilon做成可配置参数文档中给一个推荐区间比如 2 到 8让使用者自己观察训练曲线。5. 完整运行方法与论文指标复现5.1 一键训练脚本与参数说明文档说明的价值在于让别人不需要读懂代码也能跑通实验。入口脚本约定好命令行参数python main.py \ --dataset fashion_mnist \ --num_clients 20 \ --malicious_ratio 0.2 \ --attack_type gradient_poisoning \ --defense_method krum \ --rounds 50 \ --seed 42每个参数的作用和推荐范围README 里要列成清单。num_clients不宜太少少于 10 时防御算法的统计基础不足恶意客户端的影响会被放大malicious_ratio从 0 到 0.4 区间取值比较合理超过 0.4 后大部分鲁棒聚合算法都会失效defense_method支持none、clip、krum、median、dp五种取值跑对照实验时逐一切换。seed参数值得单独强调。攻击注入和防御检测都涉及随机抽样种子不同同一配置下的结果也可能抖动。为了报告里的数字可复现每次跑实验前固定全局种子import random import numpy as np import torch def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)5.2 评估指标与可视化输出论文复现的核心评估指标是主任务精度Top-1 Accuracy即在正常测试集上的正确率。防御有效性的体现方式是攻击条件下精度下降幅度被抑制。另一种常见指标是恶意客户端影响率定义为防御前后模型精度的差值百分比。指标计算方式说明全局模型精度模型在测试集上的准确率每轮训练后评估攻击成功率恶意样本被预测为目标标签的比例评估后门攻击效果防御增益无防御精度 - 有防御精度差值越大防御越有效客户端剔除率被防御算法剔除的客户端比例衡量误杀程度训练结束后用 Matplotlib 绘制两组曲线横轴是通信轮数纵轴是全局精度分别绘制无防御、有防御两条线。如果论文复现成功两条线之间应该出现明显且稳定的间隔。5.3 攻击率与防御强度的对比实验设计对比实验是论文代码复现叙事的主线。建议按两个维度设计消融第一维度是固定防御强度改变恶意客户端占比观察不同攻击压力下的防御效果第二维度是固定恶意客户端占比改变防御参数如 Krum 的裁剪比例、范数检测的阈值倍数观察防御灵敏度的变化。运行实验时用脚本批量跑不要手动逐条执行for ratio in 0.0 0.1 0.2 0.3 0.4; do python main.py --malicious_ratio $ratio --defense_method krum --rounds 50 done批量跑出来的数据对应论文中的实验图。每个实验单独保存日志和曲线图用配置参数的哈希值作为文件名后缀避免中途遗忘哪条曲线对应哪组配置。6. 复现结果对不上的排查与验证技巧6.1 先跑单客户端基线再引入恶意节点复现结果和论文对不上的时候第一步不是怀疑论文而是验证自己的基线。常见做法先将malicious_ratio设为 0defense_method设为none跑一轮单客户端的普通训练。此时模型精度应该达到该数据集在所选网络结构下的正常水平。如果这一步就不对问题出在数据切分、学习率或者模型初始化上与攻击和防御无关。基线确认稳定后再逐步加攻击。先只注入一个恶意客户端观察精度变化是否符合预期。如果恶意客户端占比 5% 时精度不降说明攻击强度设置得太弱poison_scale需要上调如果加了防御后精度反而比无防御时还差优先怀疑的是防御误杀了正常客户端而不是防御算法失效。6.2 梯度权重的聚合顺序与随机种子这类复现项目里一个高频坑点是每次选客户端时的随机抽样。如果每轮训练的客户端子集不同聚合结果天然会有波动。严谨的做法是每一轮通信固定参与训练的客户端编号顺序攻击注入也基于同样的抽样结果。否则报告里的曲线抖动会掩盖真实的防御效果。另一个隐蔽问题是模型权重的深拷贝。客户端训练时如果直接操作服务器模型会污染全局状态。代码里必须用copy.deepcopy创建独立副本否则多客户端顺序执行时前面的训练结果会悄悄混入后面的模型参数。6.3 防御强度不是越大越好参数标定经验防御算法的参数标定是毕业设计里最容易被低估的工作。范数检测的阈值系数调到 1.2防御很强但非独立同分布数据下的正常客户端也经常被误杀调到 3.0误杀少了恶意缩放梯度可能漏过去。正确标定方法是做参数扫描固定攻击强度记录不同防御参数下的精度和剔除率找到误杀率开始显著上升之前的临界点。对 Krum 来说f参数必须精确设置为恶意客户端数量的上限。设大了会错误剔除正常更新设小了又挡不住恶意客户端。最后再强调一个容易被忽略的输出项实验复现说明文档中除了运行命令和环境配置把每轮通信的模型精度变化、恶意客户端影响率、防御参数设置这三样完整记录。如果评审现场提出任何结果相关的问题这三份数据能支撑你解释清楚整条因果链——远端的数据、本地的训练、聚合时的攻防、最终指标的变化缺一不可。本文还有配套的精品资源点击获取