这篇论文提出了一个框架,来防御联邦学习中的后门攻击,称之为FLAME
1. 方法总览:
FLAME在联邦学习设置中估计后门移除所需的噪声级,而无需广泛的经验评估和访问训练数据。此外,为了有效地限制所需的噪声量,FLAME使用一种新颖的基于聚类的方法来识别和去除具有高影响的对手模型更新,并应用一种动态权重裁剪方法来限制对手为了提高性能而放大的模型的影响。我们的聚类方法旨在去除具有高攻击影响(具有较大角度偏差)的模型,而不是所有恶意模型。三个组件组成了FLAME的高级思想:滤波、限幅和噪声。
1.1 设计挑战
- 滤除具有较大角偏差的后门模型
- 限制放大后门的影响
- 选择合适的噪音水平消除后门
2. 算法流程
用中文解释每一个循环做的检查内容:
- 首先计算n个模型上传上来的参数的余弦距离(cosindistance),如果有n个模型,那么就需要计算 n × ( n − 1 ) 2 \frac{n\times (n-1)}{2} 2n×(n−1)个余弦距离 n 11 , n 12 . . . . n n n n_{11},n_{12}....n_{nn} n11,n12....nnn
- 聚类模型距离,得到 ( b 1 … … b n ) (b_1……b_n) (b1……bn),L是筛选出来的良性模型集合
- 计算全局模型和每个局部更新模型的欧几里得距离,选择其中的中值。
- 利用这个中值进行范数裁剪,距离小于中值的模型参数不变,大于中值的模型参数会被一定程度上裁剪,距离中值越远被裁剪越大。
- 聚合模型,在聚合模型上添加噪声
3. 算法详细
3.1 聚类
过去算法一般都将客户端的模型更新分为两类,把较小的一簇当做恶意模型去除,这样的坏处是如果不存在恶意模型,很容易将正常的客户端模型参数去除。论文采用了HDBSCAN的聚类方法来区分良性与恶意模型。使用成对余弦距离来测量所有模型更新之间的角度差异,并应用HDBSCAN聚类算法。这里的优点在于,即使对手按比例增加模型更新以增强其影响,余弦距离也不受影响,因为这不改变更新的权重向量之间的角度。由于HDBSCAN算法根据模型的余弦距离分布密度对模型进行聚类,并动态确定所需的聚类数,因此我们将其用于动态聚类方法。
论文的大致思路就是,将聚类内客户端个数大于
n
2
+
1
\frac{n}{2}+1
2n+1的聚类视为良性客户端,其余聚类被筛除。我们可以使用python里的HDBSCAN包来实现这一内容。
首先我们需要获取客户端的参数,并且展开成一维向量
clients_weight = []
for c in candidates:
client_weight = torch.tensor([])
diff = c.local_train()
# 计算每一层参数的差值
for name, params in server.global_model.state_dict().items():
client_weight = torch.cat((client_weight, params.reshape(-1).cpu()))
clients_weight.append(client_weight)
# 获得了每个客户端模型的参数,矩阵大小为(客户端数, 参数个数)
clients_weight = torch.stack(clients_weight)
这样,我们就拥有了一个维度为(客户端数,模型参数长度)的tensor,那么这个时候我们只需要调用hdbscan库就可以快速地实现hdbscan聚类算法。hdbscan包安装可以参考此博客hdbscan安装
# 1. HDBSCAN余弦相似度聚类
num_clients = clients_weight_total.shape[0]
clients_weight_total = clients_weight_total.double()
cluster = hdbscan.HDBSCAN(metric="cosine", algorithm="generic", min_cluster_size=num_clients//2+1, min_samples=1, allow_single_cluster=True)
cluster.fit(clients_weight_total)
print(cluster.labels_)
在聚类的时候我发现,客户端太少HDBSCAN效果不是很好,大部分时候甚至都是-1,算是一个待解决的问题,论文附录中只给出了min_cluster_szie和min_sample。
3.2 范数裁剪
这部分就比较简单,就是计算每一个模型和全局模型的欧几里得距离。获得欧几里得距离们的中值
S
t
S_t
St,其中模型距离小于中值的点不会被缩小,也不会被放大,就保持原样就好。如果模型距离大于这个中值,那么就会被范数裁剪,裁剪的比例就是
γ
=
S
t
γ
\gamma = \frac{S_t}{\gamma}
γ=γSt
很好理解吧,如果上传的后门参数距离全局模型的距离都是中值的五倍,那最后他会就被乘
1
5
\frac{1}{5}
51
euclidean = (clients_weight_**2).sum(1).sqrt()
med = euclidean.median()
for i, data in enumerate(clients_weight):
gama = med.div(euclidean[i])
if gama > 1:
gama = 1
for name, params in self.global_model.state_dict().items():
data[name] = (data[name] * gama).to(data[name].dtype)
3.3 添加噪声
这一部分就是文章介绍篇幅最多的部分,因为涉及到最多的数学公式和推理。不过说白了也就是把差分隐私的概念,以及敏感度这些概念介绍了一遍,为了填充论文篇幅而来。
文章首先证明了给每个客户端模型加噪等同于给全局模型加噪,最后得出了一个给全局模型添加噪声的公式:
σ
G
≈
S
t
ϵ
2
ln
1.25
δ
\sigma_G \approx \frac{S_t}{\epsilon} \sqrt{2\ln \frac{1.25}{\delta}}
σG≈ϵSt2lnδ1.25
这样就得到了高斯模糊的标准差,均值应该是0,然后就可以生成高斯分布的随机数来给模型添加噪声了。
文章附录里有对不同数据集添加不同的噪声尺度设置。
这部分代码实现就只需要根据公式,把
σ
\sigma
σ计算出来,使用torch.normal生成高斯随机数即可。
具体的代码实现后续会上传