【コードリーディング】ビザンチン比較テストコード解釈

  今日は、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\}{ c1c2c}、およびサーバー、各クライアントci c_ic私はデータ付きD i D_iD私は一般に、データは独立して同一に分散されている、つまり IID であると想定されます。
  ここにいますn 人のクライアントのうちff がfはビザンチン攻撃者であり、次の条件を満たす: 2 f + 2 < n 2f+2<n2f _+2<n
  、つまり、n = 10 の場合、n=10n=10fff の最大値は 3、n = 100 の場合、n=100n=100ff最大fは48です。

  Krum アルゴリズムの手順:

  1. サーバーはグローバルパラメータWWになりますW はすべてのクライアントに配布されます。
  2. 各クライアントci c_iについてc私は同時に:
    ローカル勾配gi g_iを計算します。g私は、そしてサーバーに送信されます。
  3. サーバーはクライアントから勾配を受信した後、勾配のペア間の距離を計算します:
    dij = ∥ gi − gj ∥ F 2 d_{ij}=\Vert g_i-g_j\Vert_F^2dイジ=∥g _私はgjF2
  4. 各勾配gi g_iについてg私は、彼に最も近いn − f − 1 nf-1を選択します。nf1距離、すなわち{ di , 1 , di , 2 , ⋯ , di , n } \{d_{i,1},d_{i,2},\cdots,d_{i,n}\}{ d 1d 2d n}最小のn − f − 1 nf-1nf1、 { di , 1 , di , 2 , ⋯ , di , n − f − 1 } \{d_{i,1},d_{i,2},\cdots,d_{i,nf- と設定してもよいでしょ1}\}{ d 1d 2di , n f 1}を計算し、勾配スコアとして合計しますK r ( i ) = ∑ j = 1 n − f − 1 dij Kr(i)=\sum^{nf-1}_{j=1}d_{ij}Kr ( i )=j = 1n f 1dイジ
  5. すべての勾配のスコアを計算した後、最小のスコアを持つ勾配g ∗ g^*を見つけます。g
  6. 更新:W = W − lr × g ∗ W=W-lr\times g^*W=Wlr _×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

1.5 アルゴリズムのテストを開始する

おすすめ

転載: blog.csdn.net/m0_51562349/article/details/129560575