今日は、FedCut の補足資料にあるビザンチン攻撃比較テストのコードを詳しく読み始めました。
2023.3.31 更新:
このブログの精読コードはこちら にリンクされています。
記事ディレクトリ
1 マルチクラム集約アルゴリズム
1.1 インポートパッケージ
import argparse, os, sys, csv, shutil, time, random, operator, pickle, ast, math
import numpy as np
import pandas as pd
from torch.optim import Optimizer
import torch.nn.functional as F
import torch
import pickle
import torch.nn as nn
import torch.nn.parallel
import torch.backends.cudnn as cudnn
import torch.optim as optim
import torch.utils.data as data
import torch.multiprocessing as mp
os.chdir(sys.path[0])
sys.path.append("..")
sys.path.insert(0,'./../utils/')
from utils.logger import *
from utils.eval import *
from utils.misc import *
from cifar10_normal_train import *
from cifar10_util import *
from adam import Adam
from sgd import SGD
ここには小さなエピソードもあります。兄弟ファイルのコード ファイルをインポートするときに、VScode はこれらのファイルを見つけることができません。実行上の問題を解決した後も、デバッグの問題が残ります。解決策については、前の記事を読むことをお勧めします。
1.2 Cifar10 データセットを処理して 50 人のユーザーに分割する
# 获取cifar10数据,并以IID的形式划分给50个客户
import torchvision.transforms as transforms
import torchvision.datasets as datasets
data_loc='D:/FedAffine/data/cifar' # 自己改一改
# 加载训练集和测试机
train_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
])
cifar10_train = datasets.CIFAR10(root=data_loc, train=True, download=True, transform=train_transform)
cifar10_test = datasets.CIFAR10(root=data_loc, train=False, download=True, transform=train_transform)
これは一般的な読み込みデータ セットで、トレーニング画像が 50,000 枚、テスト画像が 10,000 枚あります。
X=[]
Y=[]
for i in range(len(cifar10_train)):
X.append(cifar10_train[i][0].numpy())
Y.append(cifar10_train[i][1])
for i in range(len(cifar10_test)):
X.append(cifar10_test[i][0].numpy())
Y.append(cifar10_test[i][1])
X=np.array(X)
Y=np.array(Y)
print('total data len: ',len(X)) #长度60000
if not os.path.isfile('./cifar10_shuffle.pkl'):
all_indices = np.arange(len(X))
np.random.shuffle(all_indices)
pickle.dump(all_indices,open('./cifar10_shuffle.pkl','wb'))
else:
all_indices=pickle.load(open('./cifar10_shuffle.pkl','rb'))
X=X[all_indices]
Y=Y[all_indices]
ここでは、画像データとラベルを保存してX(60000, 3, 32, 32)
保存Y(60000,)
します。次に、シャッフルされたインデックスであるファイル「cifar10_shuffle.pkl」がロードされます。その後、このインデックスに従って合計がX
スクランブルされますY
。
# data loading
nusers=50
user_tr_len=1000
total_tr_len=user_tr_len*nusers
val_len=5000
te_len=5000
# 取前50000个作为训练集
total_tr_data=X[:total_tr_len]
total_tr_label=Y[:total_tr_len]
# 取之后5000个作为评估集
val_data=X[total_tr_len:(total_tr_len+val_len)]
val_label=Y[total_tr_len:(total_tr_len+val_len)]
# 取最后5000个作为评估集
te_data=X[(total_tr_len+val_len):(total_tr_len+val_len+te_len)]
te_label=Y[(total_tr_len+val_len):(total_tr_len+val_len+te_len)]
#全部转成tensor
total_tr_data_tensor=torch.from_numpy(total_tr_data).type(torch.FloatTensor)
total_tr_label_tensor=torch.from_numpy(total_tr_label).type(torch.LongTensor)
val_data_tensor=torch.from_numpy(val_data).type(torch.FloatTensor)
val_label_tensor=torch.from_numpy(val_label).type(torch.LongTensor)
te_data_tensor=torch.from_numpy(te_data).type(torch.FloatTensor)
te_label_tensor=torch.from_numpy(te_label).type(torch.LongTensor)
print('total tr len %d | val len %d | test len %d'%(len(total_tr_data_tensor),len(val_data_tensor),len(te_data_tensor)))
したがって、分割後は次のようになります。total tr len 50000 | val len 5000 | test len 5000
user_tr_data_tensors=[]
user_tr_label_tensors=[]
for i in range(nusers): # 遍历五十个users
user_tr_data_tensor=torch.from_numpy(total_tr_data[user_tr_len*i:user_tr_len*(i+1)]).type(torch.FloatTensor)
user_tr_label_tensor=torch.from_numpy(total_tr_label[user_tr_len*i:user_tr_len*(i+1)]).type(torch.LongTensor)
user_tr_data_tensors.append(user_tr_data_tensor)
user_tr_label_tensors.append(user_tr_label_tensor)
print('user %d tr len %d'%(i,len(user_tr_data_tensor)))
この動きは、50 人のユーザーのそれぞれに 1000 個のトレーニング データを割り当てるというもので、これは完了したばかりです。
1.3 Krumアルゴリズム(Multi-Krum)の主題
まず第一に、アルゴリズムのプロセスが何であるかを知る必要があります。ここでは、この記事の多くを引用します: Krum および Multi-Krum
アルゴリズムの仮定:
私たちはnを持っていますnクライアント{ c 1 , c 2 , ⋯ , cn } \{c_1,c_2,\cdots,c_n\}{ c1、c2、⋯、cん}、およびサーバー、各クライアントci c_ic私はデータ付きD i D_iD私は。一般に、データは独立して同一に分散されている、つまり IID であると想定されます。
ここにいますn 人のクライアントのうちff がfはビザンチン攻撃者であり、次の条件を満たす: 2 f + 2 < n 2f+2<n2f _+2<n
、つまり、n = 10 の場合、n=10n=10時fff の最大値は 3、n = 100 の場合、n=100n=100、ff最大fは48です。
Krum アルゴリズムの手順:
- サーバーはグローバルパラメータWWになりますW はすべてのクライアントに配布されます。
- 各クライアントci c_iについてc私は同時に:
ローカル勾配gi g_iを計算します。g私は、そしてサーバーに送信されます。- サーバーはクライアントから勾配を受信した後、勾配のペア間の距離を計算します:
dij = ∥ gi − gj ∥ F 2 d_{ij}=\Vert g_i-g_j\Vert_F^2dイジ=∥g _私は−gj∥F2- 各勾配gi g_iについてg私は、彼に最も近いn − f − 1 nf-1を選択します。n−f−1距離、すなわち{ di , 1 , di , 2 , ⋯ , di , n } \{d_{i,1},d_{i,2},\cdots,d_{i,n}\}{ d私、 1、d私、 2、⋯、d私、 n}最小のn − f − 1 nf-1n−f−1、 { di , 1 , di , 2 , ⋯ , di , n − f − 1 } \{d_{i,1},d_{i,2},\cdots,d_{i,nf- と設定してもよいでしょう1}\}{ d私、 1、d私、 2、⋯、di , n − f − 1}を計算し、勾配スコアとして合計しますK r ( i ) = ∑ j = 1 n − f − 1 dij Kr(i)=\sum^{nf-1}_{j=1}d_{ij}Kr ( i )=j = 1∑n − f − 1dイジ
- すべての勾配のスコアを計算した後、最小のスコアを持つ勾配g ∗ g^*を見つけます。g∗;
- 更新:W = W − lr × g ∗ W=W-lr\times g^*W=W−lr _×g∗
マルチクラム アルゴリズムの 5 番目のステップでは、スコアが最小のmmを選択します。m勾配、最終的な勾配はこのmmmの平均
次はコード部分です。
# Code for Multi-krum aggregation
def multi_krum(all_updates, n_attackers, multi_k=False):
candidates = []
candidate_indices = []
remaining_updates = all_updates
all_indices = np.arange(len(all_updates))
#
while len(remaining_updates) > 2 * n_attackers + 2:
torch.cuda.empty_cache()
distances = []
for update in remaining_updates:
distance = []
for update_ in remaining_updates:
# 计算距离
distance.append(torch.norm((update - update_)) ** 2)
distance = torch.Tensor(distance).float()
# None的作用主要是在使用None的位置新增一个维度
distances = distance[None, :] if not len(distances) else torch.cat((distances, distance[None, :]), 0)
distances = torch.sort(distances, dim=1)[0] # 排序
scores = torch.sum(distances[:, :len(remaining_updates) - 2 - n_attackers], dim=1) # 计算得分,上述算法的第四步
indices = torch.argsort(scores)[:len(remaining_updates) - 2 - n_attackers] # 返回排序后的值所对应的下标
candidate_indices.append(all_indices[indices[0].cpu().numpy()]) # 添加一个下标
all_indices = np.delete(all_indices, indices[0].cpu().numpy())
candidates = remaining_updates[indices[0]][None, :] if not len(candidates) else torch.cat((candidates, remaining_updates[indices[0]][None, :]), 0)
remaining_updates = torch.cat((remaining_updates[:indices[0]], remaining_updates[indices[0] + 1:]), 0)
if not multi_k: # 如果不是multi-krum算法,就只取一个分数最好的candidate
break
# print(len(remaining_updates))
aggregate = torch.mean(candidates, dim=0)
return aggregate, np.array(candidate_indices)
上記のコードは、より微妙なループ構造を使用し、クラム アルゴリズムとマルチクラム アルゴリズムを同時に実現します。
1.4 ファングアタック勾配の生成
次に、攻撃を防ぐためのコードの設計が始まります。1 つ目はラムダを計算するユーティリティ関数です。
# Code for Fang attack on Multi-krum
def compute_lambda_fang(all_updates, model_re, n_attackers):
distances = []
n_benign, d = all_updates.shape
# 计算每个梯度到其他梯度的距离
for update in all_updates:
distance = torch.norm((all_updates - update), dim=1)
distances = distance[None, :] if not len(distances) else torch.cat((distances, distance[None, :]), 0)
# 将所有为零的距离改为10000
distances[distances == 0] = 10000
distances = torch.sort(distances, dim=1)[0] # 选出距离最小的点
scores = torch.sum(distances[:, :n_benign - 2 - n_attackers], dim=1)
min_score = torch.min(scores)
term_1 = min_score / ((n_benign - n_attackers - 1) * torch.sqrt(torch.Tensor([d]))[0])
max_wre_dist = torch.max(torch.norm((all_updates - model_re), dim=1)) / (torch.sqrt(torch.Tensor([d]))[0])
return (term_1 + max_wre_dist)
次にアタック勾配を取得します。
def get_malicious_updates_fang(all_updates, model_re, deviation, n_attackers):
lamda = compute_lambda_fang(all_updates, model_re, n_attackers)
threshold = 1e-5
mal_updates = []
while lamda > threshold:
mal_update = (- lamda * deviation)
mal_updates = torch.stack([mal_update] * n_attackers)
mal_updates = torch.cat((mal_updates, all_updates), 0)
agg_grads, krum_candidate = multi_krum(mal_updates, n_attackers, multi_k=False)
if krum_candidate < n_attackers:
return mal_updates
lamda *= 0.5
if not len(mal_updates):
print(lamda, threshold)
mal_update = (model_re - lamda * deviation)
mal_updates = torch.stack([mal_update] * n_attackers)
mal_updates = torch.cat((mal_updates, all_updates), 0)
return mal_updates