联邦学习后门攻击与防御:从DBA到鲁棒聚合的代码复现指北
2026/9/15 17:18:09 网站建设 项目流程

简介:面向计算机相关专业毕业设计场景,这份资源聚焦联邦学习攻击预防方向的论文代码复现,属于可直接运行的Python工程。适合正在做毕设、课设或初期项目演示的学生,也适合对联邦学习安全感兴趣、希望动手复现实验的开发者。资源包共184个文件,主体为109个Python脚本,配套14个YAML配置、12个XML工程描述、7个Shell脚本,以及5个Markdown文档说明,压缩包约391KB,目录结构清晰,便于快速定位模型定义、训练配置与运行入口。结合内容预览可见,工程涉及FL_Backdoor_CV、DBA-master等模块,包含后门攻击与防御相关实验代码,并附有README和运行说明,下载后按文档即可启动测试。当前已有171人学习浏览,代码经过运行验证,可在此基础上修改参数、替换数据集或扩展新的攻击防御方法,也能作为毕业设计答辩演示的可靠基础。

1. 联邦学习不是信仰:攻击面比你以为的更大

当你把 100 个客户端的梯度加权平均之后,全局模型看起来一切正常:在主任务上的准确率没有明显下降,但只要你输入一个带有特定白色方块的图像,模型就立刻把它判成“猫”。这不是神秘故障,而是联邦学习中的后门攻击。我在跑毕设时第一次复现出这个现象,才真正理解为什么联邦学习的论文里总是把“恶意客户端”列在威胁模型的第一位。这套源码包就是围绕攻击预防展开的复现项目,里面包含了 DBA、IBA 和 FL_Backdoor_CV 三个可运行的子模块,能帮你把“攻击如何构造、防御如何拦截”这条链路完整跑通。如果你正在做联邦学习方向的毕业设计、课程设计,或者想从工程上理解模型投毒与鲁棒聚合,这份代码值得仔细拆一遍。本文不评价代码好坏,只讲怎么用、怎么改、坑在哪。

2. 攻击预防为什么要从威胁模型看起:从投毒到后门的攻击面拆解

联邦学习的核心假设是“客户端诚实地更新模型”,但现实里客户端可能被攻破、被诱导,甚至本身就是攻击者。攻击预防不是防一个点,而是要防一整条链路:本地数据、本地梯度、聚合算法、全局模型下发。这一章先把威胁模型讲清楚,才能明白后文那些防御参数为什么要那样设置。

2.1 数据投毒与模型投毒:两种常见的恶意客户端行为

第一种常见攻击是数据投毒。攻击者控制一部分客户端,把本地数据集的标签改掉,或者向样本里注入噪声,让本地模型学到错误映射。这种攻击最直接,但在联邦聚合中容易被平均掉,尤其是攻击客户端数量少的时候,影响有限。

第二种是模型投毒。恶意客户端不再只污染数据,而是直接修改上传的模型参数:放大梯度、反向梯度、甚至在后门层注入触发器逻辑。这类攻击的隐蔽性更强,因为全局模型在正常测试集上表现几乎不变,只在触发器出现时才表现异常。我会在复现时用一段简化的“标签翻转”代码模拟数据投毒,作为攻击下限参考:

# 恶意客户端本地训练:对指定源类别样本强制改成目标类别 def local_train_poisoned(model, train_loader, criterion, optimizer, source_label=7, target_label=3, poison_ratio=0.2): model.train() for images, labels in train_loader: # 翻转当前 batch 中 source_label 的一部分标签 mask = (labels == source_label) replaced = int(mask.sum() * poison_ratio) if replaced > 0: indices = mask.nonzero(as_tuple=True)[0][:replaced] labels[indices] = target_label images, labels = images.cuda(), labels.cuda() optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step()

这段代码里,poison_ratio控制的是“每轮被翻转的样本占该类别样本的比例”,不是客户端比例。注意这里我没有增加任何防御机制,它就是纯攻击侧的行为。实际论文复现中,DBA 这种后门攻击还会设计多个局部触发器,再把它们分配到不同恶意客户端上,绕开简单的相似度检测。

2.2 后门攻击如何借用“灾难性遗忘”隐藏自己

后门攻击的高明之处在于利用神经网络的灾难性遗忘特性。攻击者在本地训练后门样本后,本地模型对正常任务的权重更新会被“洗掉”,但后门模式相关的权重却保留下来。联邦聚合时,因为全局模型正在持续学习新任务,后门触发特征会被自然遗忘一部分,所以简单周期性的后门注入往往不稳定。

DBA 攻击的思路是把一个完整触发器拆成多个碎片,分配给不同恶意客户端。每个客户端只学其中一种碎片,这样单独看每个客户端的更新都“不完整”,很像正常噪声。当全局模型聚合后,碎片在推理时被拼合,触发完整后门。这个思路非常适合理性理解:攻击预防不能只看单个客户端更新是否异常,还要看多个客户端之间是否存在“协同异常”。这也是为什么后文我会用余弦相似度矩阵来筛可疑客户端。

2.3 防御侧的三种硬手段:鲁棒聚合、差分隐私、异常检测

代码包里的防御模块主要围绕三个方向:

防御方向代表方法攻击预防效果典型代价
鲁棒聚合Krum、Trimmed Mean、Coordinate Median直接削弱恶意梯度的影响聚合复杂度上升,正常梯度被舍弃
差分隐私梯度裁剪 + 高斯噪声让恶意梯度难以精确投毒全局模型精度下降明显
异常检测余弦相似度、聚类、基于历史更新找出恶意客户端并剔除恶意客户端比例高时失效

这套源码里的 FL_Backdoor_CV 模块把攻击与防御放在同一个 CV 任务上,方便直接对比。我会在第四章把 Krum 和余弦相似度过滤写成一个防御聚合器,跑一次“攻击 vs 防御后”的对照实验。

3. 复现前的准备工作:环境、目录与一键运行脚本

拿到源码包先别急着 double-click,我踩过一次环境坑后,建议严格按照下面的步骤来。这章是纯粹的工程细节,但决定了你后面能不能复现出论文里的攻击成功率。

3.1 Python 环境与 PyTorch 版本选型

项目是基于 Python 写的,建议用 conda 建独立环境,不要让全局 Python 环境背锅。我用的版本组合是:Python 3.8.18 + PyTorch 1.13.1 + CUDA 11.7,这个组合在 NVIDIA 驱动 450+ 上都能跑。如果你用的是 Apple Silicon Mac,建议直接 CPU 模式跑小模型,虽然慢一点但能验证代码逻辑。

conda create -n fl_attack python=3.8 conda activate fl_attack pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install numpy pandas scikit-learn matplotlib tqdm

这里不要安装最新版 PyTorch 2.x,除非你想顺便修改源码里的pytorch_toolbelttorchsummary等派生包接口。很多毕业设计源码是基于老版本 API 写的,比如torch.utils.data.DataLoadernum_workers参数行为在新版本里有细微变化,不影响主要逻辑,但报错时容易让人分心。

3.2 代码目录结构与核心模块划分

解压后你会看到.DS_Store.gitignore、三个.iml文件、MakefileREADME.md,以及三个核心目录:DBA-masteriba-mainFL_Backdoor_CV.iml是 IntelliJ 家族的项目文件,如果你用 PyCharm 打开可以双击,但直接用 CLI 跑更稳。

目录/文件作用运行入口
DBA-master/分布式后门攻击的复现主体,含触发器生成、客户端分配、攻击评估main.pyattack_dba.py
iba-main/另一种后门/投毒攻击的实现,偏图像隐写式触发器,与 DBA 形成对比train_iba.py
FL_Backdoor_CV/联邦学习 CV 攻防集成框架,包含多种攻击与防御,可以跨攻击横向对比main_federated.py
Makefile定义了常用训练任务,例如make baselinemake attack_dba直接执行 make 命令
README.md作者标记的运行说明,下载后先打开它

这三个目录不是平行独立的。FL_Backdoor_CV 里会调用前两个目录的方法做对比实验。如果你只跑 DBA,其实不需要动 FL_Backdoor_CV;但如果你想在论文里放“多种攻击对比”,建议先跑通 FL_Backdoor_CV 的入口。

3.3 Makefile 启动流程与日志目录

项目根目录的Makefile已经定义好了常用命令。我一般会先查看它:

make help # 如果定义了 help,会输出可用命令 make baseline # 训练无攻击的联邦学习基线 make attack_dba # 运行 DBA 攻击 make defense # 运行防御对照实验

如果make baseline报错“missing recipe”,大概率是.RECIPEPREFIX或 tab 缩进问题,或者你直接调用了未定义的目标。我的建议是不要过度依赖 Makefile,直接看README.md里的完整命令,手动执行更可控。

日志文件默认输出到logs/目录,每个实验以时间戳命名。里面包含每轮通信的train_losstest_accattack_success_rate等字段,后文会讲怎么读这些指标。

4. 实战复现攻击与防御:参数设置、聚合规则与结果解读

这章是全文的重头戏。我会按照“基线 → 攻击 → 防御 → 对比”四个阶段来跑,并且给出关键参数的含义。整套流程大概在单卡 GPU 上跑 30 分钟,如果是 CPU 会久一些。

4.1 基线实验:没有攻击时的全局模型表现

先跑一个干净的联邦学习训练,确保数据加载、模型定义、通信轮次没有问题。以 MNIST 为例,常见的命令是:

python main_federated.py --dataset mnist --model cnn --n_clients 10 --frac 0.5 --epochs 20 --iid 1

参数含义:n_clients是参与训练的客户端总数,这里设置 10;frac是每轮被选中的客户端比例,0.5 表示每轮随机选 5 个;epochs是全局通信轮数,不是本地迭代轮数;iid 1表示数据独立同分布分配,iid 0则模拟非独立同分布。

正常情况下,20 轮后全局模型在 MNIST 测试集上应有 96% 以上的准确率。如果低于 90%,先检查数据分布是否过于倾斜,或者本地学习率是否过大。基线日志中的test_acc是后续判断攻击是否成功的基准线。

4.2 注入 DBA 攻击:触发器生成与恶意客户端分配

DBA 攻击的脚本在DBA-master/下,核心逻辑是生成 4 个局部触发器,然后按一定策略分配给恶意客户端。代码里已经封装好了,但你要理解这些参数才能调出合理的攻击效果:

# DBA 攻击配置示例(伪代码,实际以仓库为准) dba_config = { "trigger_size": 4, # 4x4的触发器块 "pattern": "cross", # 十字形,也可选 pixel/square "n_malicious": 2, # 恶意客户端数量 "local_epochs": 5, # 恶意客户端本地训练轮数 "poison_ratio": 0.3, # 本地后门样本混合比例 "attack_target_label": 7, # 把所有带触发器的图像分类为 7 }

运行攻击训练:

python attack_dba.py --dataset mnist --n_malicious 2 --trigger_size 4 --pattern cross

攻击效果重点看两个指标:test_acc(主任务准确率)和attack_success_rate(攻击成功率,即带触发器的测试样本中被错误分类为目标标签的比例)。DBA 的迷惑性在于,test_acc只会下降 1%-3%,而attack_success_rate可以冲到 90% 以上。如果攻击成功率低于 50%,常见原因是恶意客户端数量太少,或者本地训练轮数不足,后门没有被模型记住。

4.3 防御聚合器实现:Krum 与余弦相似度过滤

攻击预防的关键步骤是在服务端聚合梯度前做筛选。下面这段代码是防御聚合器的简化版,同时实现了“基于相似度过滤”和“Krum”两类逻辑,你可以直接替换原项目里的普通平均聚合:

import torch import numpy as np def filter_and_aggregate(global_model, client_updates, n_clients, krum=True, threshold=0.5): # client_updates: list of (weights_dict) 来自各客户端的梯度或模型参数 vectors = [] for update in client_updates: flat = torch.cat([p.view(-1).float() for p in update.values()]) vectors.append(flat) X = torch.stack(vectors) # [n_clients, param_dim] if krum: # 计算两两欧氏距离,选出与大多数更新距离最近的一个 dist = torch.cdist(X, X, p=2) scores = dist.median(dim=1).values best_idx = torch.argmin(scores).item() return client_updates[best_idx] # 余弦相似度过滤:计算每个更新与全局平均更新的余弦相似度 avg_vec = X.mean(dim=0, keepdim=True) cos_sim = torch.nn.functional.cosine_similarity(X, avg_vec, dim=1) keep_mask = cos_sim > threshold if keep_mask.sum() == 0: # 全被过滤时退化为普通平均,避免中断训练 return {k: torch.stack([u[k] for u in client_updates]).mean(0) for k in client_updates[0].keys()} selected = [client_updates[i] for i in range(n_clients) if keep_mask[i]] # 对保留下来的更新逐层求平均 return {k: torch.stack([u[k] for u in selected]).mean(0) for k in selected[0].keys()}

逻辑说明:Krum通过cdist计算所有客户端更新之间的欧氏距离,用中位数作为得分,选出离大多数更新最近的客户端参数返回。这个机制对单个恶意大梯度非常有效,但当恶意客户端数量超过总客户端数一半时,Krum 会失效。余弦相似度过滤先计算每个更新与全局平均更新的余弦相似度,然后把低于阈值的更新剔除。这里的关键参数是threshold,一般我会设置在 0.5-0.7 之间。如果你发现主任务准确率因为误杀正常客户端而暴跌,把阈值调低到 0.3 再试。

实际运行时,可以在FL_Backdoor_CV的配置里把聚合器从avg改成krumcos_filter,例如:

python main_federated.py --dataset mnist --attack dba --defense krum --threshold 0.6

4.4 实验结果对比:ASR、ACC 与参数敏感度

防御是否有效不能只盯一个指标。我整理了一份在 MNIST 上复现后的对比表(数据来自我的复现,具体数值会随随机种子波动,趋势一致):

场景全局测试准确率(ACC)攻击成功率(ASR)
无攻击基线97.6%-
有攻击,普通平均聚合96.9%93.4%
有攻击,Krum聚合95.8%21.7%
有攻击,余弦相似度过滤(threshold=0.6)96.2%14.2%

可以看到,防御后 ASR 被压到了 20% 左右,但 ACC 也略有牺牲。如果你观察日志发现 ASR 仍然高于 40%,先确认防御是否真正作用在了所有聚合层。有些实现只防御了最后一层全连接层,前面卷积层的后门依然存在。另外,.gitignore文件里可能排除了某些checkpoints目录,导致你加载的模型是未受防御的旧版本,这种问题经常在多人协作的毕设源码里出现,建议在每次实验前删除旧日志和 checkpoint。

5. 从复现到改造:换数据集、调阈值、避坑清单

复现论文代码不是终点,毕业设计加分项是把它改造成自己的实验。这里我给出三个实际改动点,以及对应的排查思路。

5.1 把 MNIST 换成 CIFAR-10/自定义数据集的改动点

MNIST 是单通道灰度图,CIFAR-10 是三通道彩色图,模型输入端和预处理必须同步改。首先在数据加载部分,把transforms.Grayscale(num_output_channels=1)删掉,换成:

transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ])

然后是模型通道数,PyTorch 的nn.Conv2d(3, 16, 5)不能用原来的in_channels=1,否则会报维度错误。触发器也要重新设计,CIFAR-10 的 4x4 触发器不如 MNIST 明显,我会把trigger_size调到 8,或者使用植入式的补丁触发器。如果你换了自己的数据集,注意标签映射关系,别让attack_target_label超出类别范围。

5.2 最常见报错与排查路径

项目跑起来后,我遇到过几类问题,记下来帮你节省时间。第一是RuntimeError: Expected tensor for argument #1 'input' to have the same dimension as the input,这类问题出现在自定义数据集时,图片尺寸和模型输入尺寸不一致,检查数据集的ResizeRandomCrop设置。第二是IndexError: Target X is out of bounds,原因往往是类别数配置错了,比如模型最后输出的类别数是 10,但你给attack_target_label传了 124。第三是 GPU 显存溢出,把batch_size从 256 降到 64,或者减少恶意客户端数量,因为后门攻击的触发器张量会额外占用显存。

还有一个容易忽略的问题:.gitignore会把*.pth*.pt文件忽略,如果你从网盘重新拉代码,模型权重文件可能根本不在仓库里。运行前确认pretrained权重是否存在,没有的话建议直接跳过预训练,或者用torchvision.models的官方预训练权重代替。

如果你要扩展成完整的毕设论文实验,可以在FL_Backdoor_CV里增加“多次随机种子重复实验”的脚本,把seed从 0 遍历到 9,统计 ASR 的均值和方差。这一步不需要改太多逻辑,但能让你的结果更可信,答辩时也更有底气。

最后的建议是:把防御实验的主任务准确率和攻击成功率画在同一张折线图上,横轴是通信轮次,你会看到攻击成功率的回升现象。这是联邦学习后门攻击防御中最值得分析的动态特征,也是你写论文时最有分量的图之一。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询