ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

联邦学习对抗攻击实战:Python复现FGSM投毒与防御策略对比

联邦学习对抗攻击实战:Python复现FGSM投毒与防御策略对比 简介这份资源是面向计算机、人工智能、通信工程等专业学生与教师的联邦学习对抗攻击课程大作业完整方案适合作为毕设、课设或项目立项演示的参考实现。项目围绕联邦学习场景下的对抗攻击与成员推理攻击展开包含孪生网络结构、攻击函数、距离度量与联邦基础模块等核心代码并配有详细中文注释便于理解算法流程与实验设计。压缩包共18个文件以6个Python源码文件、10个pth模型权重文件为主另有README说明与LICENSE协议整体约623KB体积轻便下载后可直接查看说明并运行验证。目前已有58人学习关注代码均经过测试运行成功答辩评审平均分达96分。读者可据此掌握联邦学习模型训练、对抗样本生成与攻击评估的完整链路并在此基础上修改扩展完成自己的课程设计或科研入门实验。1. 联邦学习遇上对抗攻击课程大作业里最容易被答辩老师追问的那个交叉点如果你正在做联邦学习方向的课程大作业大概率已经跑通了 FedAvg 的基本流程手里有一个能收敛的模型但总觉得缺了点什么——答辩时老师一问「你这个系统的安全性怎么保证」场面就容易冷下来。把对抗攻击引入联邦学习场景恰好能补上这块短板它既是一个有明确学术脉络的研究方向也是一个能在单机或少量 GPU 上复现的工程任务。你需要做的事情是在联邦学习的客户端本地训练环节注入对抗样本观察全局模型在攻击下的精度衰减再对比几种防御策略的效果。这套流程涉及 Python 源码、模型定义、攻击算法和联邦聚合逻辑代码量可控注释写清楚之后答辩时能讲的故事比单纯跑一个 FedAvg 多出好几倍。适合有 Python 基础、学过深度学习入门课、正在找课程设计题目的同学也适合想从集中式对抗攻击转向分布式场景的开发者。2. 联邦学习与对抗攻击的交叉点为什么不是简单叠加2.1 联邦学习的威胁模型和集中式有什么不同集中式对抗攻击的假设很直接攻击者能拿到完整训练集或模型梯度通过 FGSM、PGD 等方法生成对抗样本让模型在推理时出错。但联邦学习的架构决定了威胁面完全不同。在标准 FedAvg 流程里数据分散在 K 个客户端上服务器只负责聚合梯度或模型参数看不到原始数据。这意味着攻击者可能扮演的角色有三种恶意客户端在本地训练时投毒、恶意服务器篡改聚合结果、以及通信链路上的窃听者截获梯度更新。课程大作业里最常模拟的是第一种——恶意客户端。你在本地训练时对样本做对抗扰动让上传的梯度带有误导性全局模型聚合后精度下降。这种攻击在文献里叫「投毒攻击」的一个子类和推理阶段的对抗样本有本质区别前者污染的是训练过程后者攻击的是推理输入。理解这个区别很重要因为它决定了你代码里攻击注入的位置。如果你在客户端本地训练循环里加扰动那是投毒如果你在全局模型训练完之后生成对抗样本测试那是推理攻击。两者代码结构不同实验结论也不同。2.2 用 Python 搭一个最小可复现的联邦对抗攻击框架下面这段代码定义了一个简化的联邦学习客户端在本地训练时注入 FGSM 对抗扰动。依赖只有 PyTorch 和 NumPy不需要额外安装联邦学习框架。import torch import torch.nn as nn import torch.optim as optim import numpy as np from copy import deepcopy class FederatedClient: 联邦学习客户端支持在本地训练时注入FGSM对抗扰动 def __init__(self, client_id, model, train_loader, devicecpu): self.client_id client_id self.model deepcopy(model) # 每个客户端持有独立副本 self.train_loader train_loader self.device device self.model.to(device) def fgsm_attack(self, data, target, epsilon0.03): FGSM对抗样本生成 epsilon: 扰动强度课程作业常用0.01~0.1 data.requires_grad True output self.model(data) loss nn.CrossEntropyLoss()(output, target) self.model.zero_grad() loss.backward() # 取梯度的符号方向乘以扰动强度 perturbation epsilon * data.grad.sign() adversarial_data data perturbation # 将像素值裁剪回合法范围 adversarial_data torch.clamp(adversarial_data, 0, 1) return adversarial_data.detach() def local_train(self, epochs1, lr0.01, poison_ratio0.0, epsilon0.03): 本地训练 poison_ratio: 投毒样本比例0表示干净训练 epsilon: 对抗扰动强度 optimizer optim.SGD(self.model.parameters(), lrlr, momentum0.9) criterion nn.CrossEntropyLoss() self.model.train() for epoch in range(epochs): for batch_idx, (data, target) in enumerate(self.train_loader): data, target data.to(self.device), target.to(self.device) # 按比例将部分样本替换为对抗样本 if poison_ratio 0 and np.random.random() poison_ratio: data self.fgsm_attack(data, target, epsilon) optimizer.zero_grad() output self.model(data) loss criterion(output, target) loss.backward() optimizer.step() return self.model.state_dict()这段代码的核心逻辑是fgsm_attack方法在本地训练过程中动态生成对抗样本poison_ratio控制投毒比例。参数epsilon是关键——太小了攻击效果不明显太大了模型直接崩掉课程作业里建议从 0.03 开始试。poison_ratio一般设 0.1 到 0.5 之间模拟部分客户端被攻陷的场景。服务器端的聚合逻辑用标准 FedAvg 即可class FederatedServer: 联邦学习服务器负责聚合客户端上传的模型参数 def __init__(self, model, devicecpu): self.global_model model self.device device def aggregate(self, client_weights, client_sizes): 加权平均聚合 client_weights: 各客户端上传的state_dict列表 client_sizes: 各客户端样本数列表 total_samples sum(client_sizes) aggregated {} for key in client_weights[0].keys(): aggregated[key] torch.zeros_like(client_weights[0][key]) for i, weights in enumerate(client_weights): weight client_sizes[i] / total_samples aggregated[key] weights[key] * weight self.global_model.load_state_dict(aggregated) return aggregated聚合逻辑本身不复杂但有一个容易翻车的点client_sizes的权重计算。如果你的数据集是 Non-IID 划分的各客户端样本数差异很大加权平均会让样本多的客户端主导全局模型。课程作业里如果要做 Non-IID 实验建议同时记录每个客户端的本地精度观察聚合后的变化趋势。2.3 攻击效果怎么量化三个必看的指标跑完实验之后你需要用数据说话。课程大作业里至少报告以下三个指标指标含义干净训练参考值投毒后预期变化全局精度聚合模型在测试集上的准确率92%~95%下降 5%~30%攻击成功率对抗样本被误分类的比例接近 0%上升至 40%~80%收敛轮次达到目标精度所需通信轮数20~50 轮增加 1.5~3 倍这三个指标的计算代码不复杂但要注意测试集必须和训练集严格分离。我见过有同学把测试集混进客户端本地数据里结果全局精度虚高答辩时被老师一眼看穿。3. 从零跑通环境配置、数据划分与攻击注入的完整操作链3.1 环境配置和依赖安装的避坑清单课程大作业的代码要在答辩现场跑起来环境配置不能出岔子。推荐用 Python 3.8 到 3.10PyTorch 1.12 以上版本。如果你用 conda 管理环境下面这套命令可以直接抄# 创建虚拟环境 conda create -n fl_attack python3.9 -y conda activate fl_attack # 安装PyTorchCPU版本适合没有GPU的同学 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装其他依赖 pip install numpy matplotlib scikit-learn tensorboard如果你有 NVIDIA 显卡把--index-url换成 CUDA 版本对应的地址即可。注意不要混用 conda 和 pip 安装 PyTorch容易出现动态库冲突。VSCode 里配置 Python 环境时记得在.vscode/settings.json里指定解释器路径否则调试时可能调用到系统 Python。数据集的准备以 MNIST 和 CIFAR-10 为主这两个数据集足够支撑课程作业的实验量。MNIST 用torchvision.datasets.MNIST直接下载CIFAR-10 同理。如果实验室网络不稳定提前把数据下载到本地用downloadFalse加载。3.2 联邦数据划分IID 和 Non-IID 的代码实现联邦学习实验里数据划分方式直接影响结论。IID 划分就是随机均匀分给 K 个客户端Non-IID 划分常见做法是按标签分组每个客户端只拿少数几个类别的数据。def split_iid(dataset, num_clients): IID划分随机均匀分配 num_items len(dataset) // num_clients indices np.random.permutation(len(dataset)) client_data [] for i in range(num_clients): start i * num_items end start num_items client_data.append(torch.utils.data.Subset(dataset, indices[start:end])) return client_data def split_noniid(dataset, num_clients, classes_per_client2): Non-IID划分每个客户端只拿指定数量的类别 classes_per_client: 每个客户端持有的类别数 targets np.array(dataset.targets) num_classes len(np.unique(targets)) class_indices [np.where(targets c)[0] for c in range(num_classes)] client_data [] for i in range(num_clients): # 每个客户端随机选classes_per_client个类别 selected_classes np.random.choice(num_classes, classes_per_client, replaceFalse) indices [] for c in selected_classes: # 从该类别中取一部分样本 n len(class_indices[c]) // (num_clients // classes_per_client 1) chosen np.random.choice(class_indices[c], min(n, len(class_indices[c])), replaceFalse) indices.extend(chosen) client_data.append(torch.utils.data.Subset(dataset, indices)) return client_dataNon-IID 划分是联邦学习论文里的标配实验设置。课程作业里如果只做 IID答辩时老师大概率会问「Non-IID 下效果怎么样」。建议两种划分都跑一遍对比全局精度的下降幅度。classes_per_client一般设 2 或 3太小了任务太简单太大了接近 IID。3.3 攻击注入位置的选择本地训练 vs 梯度上传对抗攻击在联邦学习里的注入位置有两个选择一是在本地训练循环里生成对抗样本参与训练前面代码展示的方式二是在梯度上传前对梯度加扰动。两种方式的效果和检测难度不同。本地训练注入的实现更直观代码改动小适合课程作业快速出结果。梯度上传注入需要对梯度做处理实现稍复杂但更接近真实攻击场景。下面是一个梯度扰动的示例def gradient_perturbation(model, epsilon0.01): 对模型梯度施加高斯扰动 模拟恶意客户端在上传前篡改梯度 for param in model.parameters(): if param.grad is not None: noise torch.randn_like(param.grad) * epsilon param.grad noise return model这个函数在loss.backward()之后、optimizer.step()之前调用。epsilon控制扰动幅度一般设 0.01 到 0.05。注意扰动太大会导致本地模型无法收敛反而让攻击效果被稀释。选择哪种注入方式取决于你的实验目标。如果重点是「对抗样本对联邦模型的影响」选本地训练注入如果重点是「恶意客户端如何逃避检测」选梯度扰动。课程作业里建议先做第一种跑通之后再尝试第二种作为扩展。4. 避坑与排查联邦对抗攻击实验里最容易翻车的五个地方4.1 全局模型精度不降反升现象注入对抗攻击后全局模型在测试集上的精度反而比干净训练高了一两个百分点。原因对抗样本在本地训练时起到了正则化作用类似数据增强。如果epsilon设得太小比如 0.001扰动几乎不改变样本分布反而增加了训练多样性。解决把epsilon提高到 0.03 以上同时检查poison_ratio是否太低。如果poison_ratio低于 0.05大部分客户端还是干净训练攻击效果被平均掉了。建议poison_ratio至少设 0.2epsilon从 0.05 开始试。4.2 客户端本地模型发散现象恶意客户端的本地 loss 在几个 batch 之后变成 NaN上传的梯度全是异常值。原因FGSM 生成的对抗样本可能超出数据归一化范围导致模型输入分布偏移过大。另外如果epsilon超过 0.3扰动会彻底破坏图像语义。解决在fgsm_attack里加torch.clamp把像素值限制回 [0,1]同时检查数据预处理里的归一化参数。如果用的是 CIFAR-10归一化后的范围是 [-2.5, 2.5] 左右clamp 的边界要对应调整。epsilon不要超过 0.1。4.3 聚合时权重维度不匹配现象服务器聚合时报RuntimeError: The size of tensor a must match the size of tensor b。原因不同客户端的模型结构不一致或者某个客户端在本地训练时修改了模型层。课程作业里常见于复制粘贴代码时改错了模型定义。解决确保所有客户端从同一个global_model深拷贝初始化。在聚合前加一个检查def check_weights_compatibility(client_weights): 检查各客户端权重维度是否一致 ref_keys set(client_weights[0].keys()) for i, weights in enumerate(client_weights[1:], 1): if set(weights.keys()) ! ref_keys: raise ValueError(f客户端{i}的权重键不匹配) for key in ref_keys: if weights[key].shape ! client_weights[0][key].shape: raise ValueError(f客户端{i}的{key}维度不匹配) return True4.4 Non-IID 下精度波动过大现象每轮聚合后的全局精度忽高忽低方差超过 10 个百分点。原因Non-IID 划分下某些客户端的本地数据只包含一两个类别本地训练后模型严重偏向这些类别。聚合时如果这些客户端权重过大全局模型会被带偏。解决在聚合时对客户端权重做裁剪限制单个客户端的最大权重比例。或者增加通信轮数让模型有更多轮次来平衡。课程作业里可以在实验记录里注明「Non-IID 下精度波动是正常现象」并给出多轮平均结果。4.5 攻击成功率评估时用错测试集现象攻击成功率达到 90% 以上但全局精度只下降了 2%。原因攻击成功率的计算用的是对抗样本测试集而全局精度用的是干净测试集。两者评估对象不同不能直接对比。有些同学把对抗样本混进干净测试集里算精度导致指标失真。解决分开评估。干净测试集算全局精度对抗样本测试集算攻击成功率。在实验报告里明确标注每个指标的测试集来源。下面是一个标准的评估函数def evaluate(model, test_loader, attack_fnNone, epsilon0.05): 评估模型精度 attack_fn: 如果提供则生成对抗样本评估 model.eval() correct 0 total 0 for data, target in test_loader: if attack_fn is not None: data attack_fn(model, data, target, epsilon) output model(data) pred output.argmax(dim1) correct (pred target).sum().item() total target.size(0) return correct / total5. 让答辩加分防御策略对比实验与结果可视化技巧跑通攻击之后课程作业的加分项是加入防御策略对比。联邦学习里常见的防御手段有三种梯度裁剪、差分隐私、以及基于距离的异常客户端检测。你不需要全部实现选一到两种做对比实验就够了。梯度裁剪的实现最简单在客户端上传前对梯度做范数限制def gradient_clipping(model, max_norm1.0): 梯度裁剪限制梯度范数 total_norm 0.0 for param in model.parameters(): if param.grad is not None: total_norm param.grad.data.norm(2).item() ** 2 total_norm total_norm ** 0.5 clip_coef max_norm / (total_norm 1e-6) if clip_coef 1: for param in model.parameters(): if param.grad is not None: param.grad.data.mul_(clip_coef) return modelmax_norm一般设 1.0 到 5.0。裁剪太狠会拖慢收敛太松则防御效果不明显。建议在实验里对比max_norm取 1.0、3.0、5.0 三档的结果。差分隐私的防御思路是在梯度上加高斯噪声让攻击者难以从梯度反推数据。实现上可以用torch.distributions.Normal生成噪声def add_dp_noise(model, noise_multiplier0.1): 差分隐私在梯度上添加高斯噪声 for param in model.parameters(): if param.grad is not None: noise torch.randn_like(param.grad) * noise_multiplier param.grad noise return modelnoise_multiplier越大隐私保护越强但模型精度下降也越明显。课程作业里可以画一条「隐私预算 vs 全局精度」的曲线展示防御效果和精度损失的权衡。结果可视化方面建议至少画三张图全局精度随通信轮次的变化曲线干净训练 vs 攻击 vs 防御、攻击成功率对比柱状图、以及不同epsilon下的精度衰减曲线。用 matplotlib 画图时注意设置dpi300答辩 PPT 里图片清晰度很重要。我自己的习惯是每次跑完实验先把原始数据存成 CSV再用单独的脚本画图。这样调整图表样式时不用重新跑实验省时间。另外实验随机种子要固定torch.manual_seed(42)和np.random.seed(42)都加上否则每次跑出来的曲线都不一样答辩时说不清楚。最后说一个答辩技巧老师问「你这个攻击在真实场景下可行吗」不要只回答「可行」或「不可行」。你可以说「在恶意客户端比例低于 20% 时攻击效果有限但超过 40% 后全局精度下降明显这说明联邦学习对客户端信任度的依赖很强」。这种带条件的回答比绝对判断更有说服力。希望帮到你。本文还有配套的精品资源点击获取
返回列表