├── dataset ├── utils │ ├── __pycache__ │ │ └── dataset_utils.cpython-37.pyc │ └── dataset_utils.py ├── generate_fmnist.py ├── generate_cifar10.py ├── generate_mnist.py └── generate_svhn.py ├── system ├── utils │ ├── result_utils.py │ ├── data_utils.py │ ├── mem_utils.py │ └── compresion.py ├── flcore │ ├── servers │ │ ├── serverce.py │ │ └── serverbase.py │ ├── trainmodel │ │ └── models.py │ └── clients │ │ ├── clientce.py │ │ └── clientbase.py └── main.py ├── README.md ├── .gitignore ├── environment.yaml └── LICENSE /dataset/utils/__pycache__/dataset_utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhangIvan1/Efficient-Privacy-Preserving-Federated-Learning-With-Improved-Compressed-Sensing/HEAD/dataset/utils/__pycache__/dataset_utils.cpython-37.pyc -------------------------------------------------------------------------------- /system/utils/result_utils.py: -------------------------------------------------------------------------------- 1 | import h5py 2 | import numpy as np 3 | import os 4 | 5 | 6 | def average_data(algorithm="", dataset="", goal="", times=10, length=800): 7 | test_acc = get_all_results_for_one_algo( 8 | algorithm, dataset, goal, times, int(length)) 9 | test_acc_data = np.average(test_acc, axis=0) 10 | 11 | 12 | max_accurancy = [] 13 | for i in range(times): 14 | max_accurancy.append(test_acc[i].max()) 15 | 16 | print("std for best accurancy:", np.std(max_accurancy)) 17 | print("mean for best accurancy:", np.mean(max_accurancy)) 18 | 19 | 20 | def get_all_results_for_one_algo(algorithm="", dataset="", goal="", times=10, length=800): 21 | test_acc = np.zeros((times, length)) 22 | algorithms_list = [algorithm] * times 23 | for i in range(times): 24 | file_name = dataset + "_" + \ 25 | algorithms_list[i] + "_" + goal + "_" + str(i) 26 | test_acc[i, :] = np.array( 27 | read_data_then_delete(file_name, delete=False))[:length] 28 | 29 | return test_acc 30 | 31 | 32 | def read_data_then_delete(file_name, delete=False): 33 | file_path = "../results/" + file_name + ".h5" 34 | 35 | with h5py.File(file_path, 'r') as hf: 36 | rs_test_acc = np.array(hf.get('rs_test_acc')) 37 | 38 | if delete: 39 | os.remove(file_path) 40 | print("Length: ", len(rs_test_acc)) 41 | 42 | return rs_test_acc -------------------------------------------------------------------------------- /system/utils/data_utils.py: -------------------------------------------------------------------------------- 1 | import ujson 2 | import numpy as np 3 | import os 4 | import torch 5 | 6 | # IMAGE_SIZE = 28 7 | # IMAGE_PIXELS = IMAGE_SIZE * IMAGE_SIZE 8 | # NUM_CHANNELS = 1 9 | 10 | # IMAGE_SIZE_CIFAR = 32 11 | # NUM_CHANNELS_CIFAR = 3 12 | 13 | 14 | def read_data(dataset, idx, is_train=True): 15 | if is_train: 16 | train_data_dir = os.path.join('../dataset', dataset, 'train/') 17 | 18 | train_file = train_data_dir + str(idx) + '.npz' 19 | with open(train_file, 'rb') as f: 20 | train_data = np.load(f, allow_pickle=True)['data'].tolist() 21 | 22 | return train_data 23 | 24 | else: 25 | test_data_dir = os.path.join('../dataset', dataset, 'test/') 26 | 27 | test_file = test_data_dir + str(idx) + '.npz' 28 | with open(test_file, 'rb') as f: 29 | test_data = np.load(f, allow_pickle=True)['data'].tolist() 30 | 31 | return test_data 32 | 33 | 34 | def read_client_data(dataset, idx, is_train=True): 35 | if is_train: 36 | train_data = read_data(dataset, idx, is_train) 37 | X_train = torch.Tensor(train_data['x']).type(torch.float32) 38 | y_train = torch.Tensor(train_data['y']).type(torch.int64) 39 | 40 | train_data = [(x, y) for x, y in zip(X_train, y_train)] 41 | return train_data 42 | else: 43 | test_data = read_data(dataset, idx, is_train) 44 | X_test = torch.Tensor(test_data['x']).type(torch.float32) 45 | y_test = torch.Tensor(test_data['y']).type(torch.int64) 46 | test_data = [(x, y) for x, y in zip(X_test, y_test)] 47 | return test_data 48 | -------------------------------------------------------------------------------- /system/flcore/servers/serverce.py: -------------------------------------------------------------------------------- 1 | import time 2 | from flcore.clients.clientce import clientCE 3 | from flcore.servers.serverbase import Server 4 | 5 | 6 | class FedCE(Server): 7 | def __init__(self, args, times): 8 | super().__init__(args, times) 9 | 10 | self.set_clients(args, clientCE) 11 | 12 | print(f"\nJoin ratio / total clients: {self.join_ratio} / {self.num_clients}") 13 | print("Finished creating server and clients.") 14 | 15 | # self.load_model() 16 | self.Budget = [] 17 | 18 | 19 | def train(self): 20 | for i in range(self.global_rounds+1): 21 | s_t = time.time() 22 | self.selected_clients = self.select_clients() 23 | self.send_models() 24 | 25 | if i%self.eval_gap == 0: 26 | print(f"\n-------------Round number: {i}-------------") 27 | print("\nEvaluate global model") 28 | self.evaluate() 29 | 30 | for client in self.selected_clients: 31 | client.train() 32 | 33 | # self.receive_models() 34 | # self.aggregate_parameters() 35 | self.receive_models_c() 36 | self.aggregate_parameters_c() 37 | 38 | self.Budget.append(time.time() - s_t) 39 | print('-'*25, 'time cost', '-'*25, self.Budget[-1]) 40 | 41 | print("\nBest global accuracy.") 42 | # self.print_(max(self.rs_test_acc), max( 43 | # self.rs_train_acc), min(self.rs_train_loss)) 44 | print(max(self.rs_test_acc)) 45 | print("\nAverage time cost per round.") 46 | print(sum(self.Budget[1:])/len(self.Budget[1:])) 47 | print(self.rs_test_acc) 48 | 49 | self.save_results() 50 | # self.save_global_model() 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Efficient-Privacy-Preserving-Federated-Learning-With-Improved-Compressed-Sensing 2 | 3 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.8347455.svg)](https://doi.org/10.5281/zenodo.8347455) 4 | 5 | This is the code of paper [Efficient-Privacy-Preserving-Federated-Learning-With-Improved-Compressed-Sensing](https://ieeexplore.ieee.org/document/10235260). 6 | 7 | 8 | ## Environments 9 | With the installed conda, you can run this project in a conda virtual environment. 10 | * **Python**: 3.7 11 | * **Pytorch**: 1.13.1 12 | * **[seal](https://github.com/Huelse/SEAL-Python)**: 4.0.0 13 | 14 | ## Usage 15 | 16 | Note that the dataset needs to be prepared before running main.py 17 | in folder "dataset" 18 | ``` 19 | cd ./dataset 20 | python generate_mnist.py iid - - # for iid and unbalanced setting 21 | # python generate_mnist.py noniid - - # for pathological noniid setting 22 | # python generate_mnist.py noniid - dir # for practical noniid setting 23 | # python generate_mnist.py noniid - noise # for feature skew noniid setting 24 | ``` 25 | 26 | ## Comparison Schemes 27 | 28 | You can find the comparative studies in the repository below. 29 | 30 | * **[PFL-Non-IID(baseline)](https://github.com/TsingZ0/PFL-Non-IID/tree/0af30fc8665e04ea9200b041f0c457c2260cbc99)** 31 | * **[HeteroFL](https://github.com/diaoenmao/HeteroFL-Computation-and-Communication-Efficient-Federated-Learning-for-Heterogeneous-Clients)** 32 | * **[CEFL](https://github.com/AshwinRJ/Federated-Learning-PyTorch)** 33 | 34 | 35 | 36 | 37 | 38 | 39 | ## Citation 40 | If you find this repository useful, please cite our paper: 41 | 42 | ``` 43 | @ARTICLE{10235260, 44 | author={Zhang, Yifan and Miao, Yinbin and Li, Xinghua and Wei, Linfeng and Liu, Zhiquan and Choo, Kim-Kwang Raymond and Deng, Robert H.}, 45 | journal={IEEE Transactions on Industrial Informatics}, 46 | title={Efficient Privacy-Preserving Federated Learning With Improved Compressed Sensing}, 47 | year={2024}, 48 | volume={20}, 49 | number={3}, 50 | pages={3316-3326}, 51 | keywords={Computational modeling;Data models;Servers;Training;Privacy;Federated learning;Data privacy;CKKS;communication costs;compression sensing (CS);federated learning (FL);homomorphic encryption}, 52 | doi={10.1109/TII.2023.3297596}} 53 | 54 | ``` 55 | -------------------------------------------------------------------------------- /system/flcore/trainmodel/models.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | batch_size = 16 6 | 7 | 8 | # # https://github.com/katsura-jp/fedavg.pytorch/blob/master/src/models/cnn.py 9 | 10 | class FedAvgCNN(nn.Module): 11 | def __init__(self, in_features=1, num_classes=10, dim=1024): 12 | super().__init__() 13 | self.conv1 = nn.Sequential( 14 | nn.Conv2d(in_features, 15 | 32, 16 | kernel_size=5, 17 | padding=0, 18 | stride=1, 19 | bias=True), 20 | nn.ReLU(inplace=True), 21 | nn.MaxPool2d(kernel_size=(2, 2)) 22 | ) 23 | self.conv2 = nn.Sequential( 24 | nn.Conv2d(32, 25 | 64, 26 | kernel_size=5, 27 | padding=0, 28 | stride=1, 29 | bias=True), 30 | nn.ReLU(inplace=True), 31 | nn.MaxPool2d(kernel_size=(2, 2)) 32 | ) 33 | self.fc1 = nn.Sequential( 34 | nn.Linear(dim, 512), 35 | nn.ReLU(inplace=True) 36 | ) 37 | self.fc = nn.Linear(512, num_classes) 38 | 39 | def forward(self, x): 40 | out = self.conv1(x) 41 | out = self.conv2(out) 42 | out = torch.flatten(out, 1) 43 | out = self.fc1(out) 44 | out = self.fc(out) 45 | return out 46 | 47 | 48 | # https://github.com/katsura-jp/fedavg.pytorch/blob/master/src/models/mlp.py 49 | class FedAvgMLP(nn.Module): 50 | def __init__(self, in_features=784, num_classes=10, hidden_dim=200): 51 | super().__init__() 52 | self.fc1 = nn.Linear(in_features, hidden_dim) 53 | self.fc2 = nn.Linear(hidden_dim, num_classes) 54 | self.act = nn.ReLU(inplace=True) 55 | 56 | def forward(self, x): 57 | if x.ndim == 4: 58 | x = x.view(x.size(0), -1) 59 | x = self.act(self.fc1(x)) 60 | x = self.fc2(x) 61 | return x 62 | 63 | 64 | class DNN(nn.Module): 65 | def __init__(self, input_dim=1*28*28, mid_dim=100, num_classes=10): 66 | super(DNN, self).__init__() 67 | self.fc1 = nn.Linear(input_dim, mid_dim) 68 | self.fc = nn.Linear(mid_dim, num_classes) 69 | 70 | def forward(self, x): 71 | x = torch.flatten(x, 1) 72 | x = F.relu(self.fc1(x)) 73 | x = self.fc(x) 74 | x = F.log_softmax(x, dim=1) 75 | return x 76 | 77 | 78 | -------------------------------------------------------------------------------- /system/flcore/clients/clientce.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import numpy as np 4 | import time 5 | from flcore.clients.clientbase import Client 6 | from utils.compresion import * 7 | 8 | 9 | class clientCE(Client): 10 | def __init__(self, args, id, train_samples, test_samples, ckks, **kwargs): 11 | super().__init__(args, id, train_samples, test_samples, **kwargs) 12 | 13 | self.loss = nn.CrossEntropyLoss() 14 | self.optimizer = torch.optim.SGD( 15 | self.model.parameters(), lr=self.learning_rate) 16 | 17 | self.init = False 18 | self.r = args.r 19 | self.ckks_tools = ckks 20 | 21 | self.alpha = 1 22 | self.mu = 0.001 23 | 24 | def train(self): 25 | trainloader = self.load_train_data() 26 | 27 | start_time = time.time() 28 | 29 | # if self.init: 30 | # self.compressed_model.package_decompresion(self.r) 31 | # self.compressed_model.unpack(self.model,self.device) 32 | 33 | # self.init = True 34 | # self.compressed_model.package_de(self.ckks_tools) 35 | if self.compressed_model.is_Compressed is True: 36 | self.compressed_model.package_decompresion(self.r) 37 | self.model = copy.deepcopy( 38 | self.compressed_model.unpack(copy.deepcopy(self.model), self.device)) 39 | 40 | # self.model.to(self.device) 41 | self.model.train() 42 | 43 | fixed_model = copy.deepcopy(self.model) 44 | 45 | max_local_steps = self.local_steps 46 | 47 | for step in range(max_local_steps): 48 | for i, (x, y) in enumerate(trainloader): 49 | if type(x) == type([]): 50 | x[0] = x[0].to(self.device) 51 | else: 52 | x = x.to(self.device) 53 | y = y.to(self.device) 54 | self.optimizer.zero_grad() 55 | output = self.model(x) 56 | ce_loss = self.loss(output, y) 57 | 58 | reg_loss = 0 59 | fixed_params = {n: p for n, p in fixed_model.named_parameters()} 60 | for n, p in self.model.named_parameters(): 61 | reg_loss += ((p - fixed_params[n].detach()) ** 2).sum() 62 | 63 | loss = self.alpha * ce_loss + 0.5 * self.mu * reg_loss 64 | loss.backward() 65 | self.optimizer.step() 66 | 67 | self.compressed_model = Packages() 68 | self.compressed_model.pack_up(copy.deepcopy(self.model)) 69 | self.compressed_model.package_compresion(self.r) 70 | self.compressed_model.package_en(self.ckks_tools) 71 | 72 | 73 | # self.model.cpu() 74 | 75 | self.train_time_cost['num_rounds'] += 1 76 | self.train_time_cost['total_cost'] += time.time() - start_time 77 | -------------------------------------------------------------------------------- /dataset/generate_fmnist.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import random 5 | import torch 6 | import torchvision 7 | import torchvision.transforms as transforms 8 | from utils.dataset_utils import check, separate_data, split_data, save_file 9 | 10 | 11 | random.seed(1) 12 | np.random.seed(1) 13 | num_clients = 20 14 | num_classes = 10 15 | dir_path = "fmnist/" 16 | 17 | 18 | # Allocate data to users 19 | def generate_fmnist(dir_path, num_clients, num_classes, niid, balance, partition): 20 | if not os.path.exists(dir_path): 21 | os.makedirs(dir_path) 22 | 23 | # Setup directory for train/test data 24 | config_path = dir_path + "config.json" 25 | train_path = dir_path + "train/" 26 | test_path = dir_path + "test/" 27 | 28 | if check(config_path, train_path, test_path, num_clients, num_classes, niid, balance, partition): 29 | return 30 | 31 | # Get FashionMNIST data 32 | transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]) 33 | 34 | trainset = torchvision.datasets.FashionMNIST( 35 | root=dir_path+"rawdata", train=True, download=True, transform=transform) 36 | testset = torchvision.datasets.FashionMNIST( 37 | root=dir_path+"rawdata", train=False, download=True, transform=transform) 38 | trainloader = torch.utils.data.DataLoader( 39 | trainset, batch_size=len(trainset.data), shuffle=False) 40 | testloader = torch.utils.data.DataLoader( 41 | testset, batch_size=len(testset.data), shuffle=False) 42 | 43 | for _, train_data in enumerate(trainloader, 0): 44 | trainset.data, trainset.targets = train_data 45 | for _, test_data in enumerate(testloader, 0): 46 | testset.data, testset.targets = test_data 47 | 48 | dataset_image = [] 49 | dataset_label = [] 50 | 51 | dataset_image.extend(trainset.data.cpu().detach().numpy()) 52 | dataset_image.extend(testset.data.cpu().detach().numpy()) 53 | dataset_label.extend(trainset.targets.cpu().detach().numpy()) 54 | dataset_label.extend(testset.targets.cpu().detach().numpy()) 55 | dataset_image = np.array(dataset_image) 56 | dataset_label = np.array(dataset_label) 57 | 58 | # dataset = [] 59 | # for i in range(num_classes): 60 | # idx = dataset_label == i 61 | # dataset.append(dataset_image[idx]) 62 | 63 | X, y, statistic = separate_data((dataset_image, dataset_label), num_clients, num_classes, 64 | niid, balance, partition) 65 | train_data, test_data = split_data(X, y) 66 | save_file(config_path, train_path, test_path, train_data, test_data, num_clients, num_classes, 67 | statistic, niid, balance, partition) 68 | 69 | 70 | if __name__ == "__main__": 71 | niid = True if sys.argv[1] == "noniid" else False 72 | balance = True if sys.argv[2] == "balance" else False 73 | partition = sys.argv[3] if sys.argv[3] != "-" else None 74 | 75 | generate_fmnist(dir_path, num_clients, num_classes, niid, balance, partition) -------------------------------------------------------------------------------- /dataset/generate_cifar10.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import random 5 | import torch 6 | import torchvision 7 | import torchvision.transforms as transforms 8 | from utils.dataset_utils import check, separate_data, split_data, save_file 9 | 10 | 11 | random.seed(1) 12 | np.random.seed(1) 13 | num_clients = 20 14 | num_classes = 10 15 | dir_path = "cifar10/" 16 | 17 | 18 | # Allocate data to users 19 | def generate_cifar10(dir_path, num_clients, num_classes, niid, balance, partition): 20 | if not os.path.exists(dir_path): 21 | os.makedirs(dir_path) 22 | 23 | # Setup directory for train/test data 24 | config_path = dir_path + "config.json" 25 | train_path = dir_path + "train/" 26 | test_path = dir_path + "test/" 27 | 28 | if check(config_path, train_path, test_path, num_clients, num_classes, niid, balance, partition): 29 | return 30 | 31 | # Get Cifar10 data 32 | transform = transforms.Compose( 33 | [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) 34 | 35 | trainset = torchvision.datasets.CIFAR10( 36 | root=dir_path+"rawdata", train=True, download=True, transform=transform) 37 | testset = torchvision.datasets.CIFAR10( 38 | root=dir_path+"rawdata", train=False, download=True, transform=transform) 39 | trainloader = torch.utils.data.DataLoader( 40 | trainset, batch_size=len(trainset.data), shuffle=False) 41 | testloader = torch.utils.data.DataLoader( 42 | testset, batch_size=len(testset.data), shuffle=False) 43 | 44 | for _, train_data in enumerate(trainloader, 0): 45 | trainset.data, trainset.targets = train_data 46 | for _, test_data in enumerate(testloader, 0): 47 | testset.data, testset.targets = test_data 48 | 49 | dataset_image = [] 50 | dataset_label = [] 51 | 52 | dataset_image.extend(trainset.data.cpu().detach().numpy()) 53 | dataset_image.extend(testset.data.cpu().detach().numpy()) 54 | dataset_label.extend(trainset.targets.cpu().detach().numpy()) 55 | dataset_label.extend(testset.targets.cpu().detach().numpy()) 56 | dataset_image = np.array(dataset_image) 57 | dataset_label = np.array(dataset_label) 58 | 59 | # dataset = [] 60 | # for i in range(num_classes): 61 | # idx = dataset_label == i 62 | # dataset.append(dataset_image[idx]) 63 | 64 | X, y, statistic = separate_data((dataset_image, dataset_label), num_clients, num_classes, 65 | niid, balance, partition) 66 | train_data, test_data = split_data(X, y) 67 | save_file(config_path, train_path, test_path, train_data, test_data, num_clients, num_classes, 68 | statistic, niid, balance, partition) 69 | 70 | 71 | if __name__ == "__main__": 72 | niid = True if sys.argv[1] == "noniid" else False 73 | balance = True if sys.argv[2] == "balance" else False 74 | partition = sys.argv[3] if sys.argv[3] != "-" else None 75 | 76 | generate_cifar10(dir_path, num_clients, num_classes, niid, balance, partition) -------------------------------------------------------------------------------- /dataset/generate_mnist.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import random 5 | import torch 6 | import torchvision 7 | import torchvision.transforms as transforms 8 | from utils.dataset_utils import check, separate_data, split_data, save_file 9 | 10 | 11 | random.seed(1) 12 | np.random.seed(1) 13 | num_clients = 20 14 | num_classes = 10 15 | dir_path = "mnist/" 16 | 17 | 18 | # Allocate data to users 19 | def generate_mnist(dir_path, num_clients, num_classes, niid, balance, partition): 20 | if not os.path.exists(dir_path): 21 | os.makedirs(dir_path) 22 | 23 | # Setup directory for train/test data 24 | config_path = dir_path + "config.json" 25 | train_path = dir_path + "train/" 26 | test_path = dir_path + "test/" 27 | 28 | if check(config_path, train_path, test_path, num_clients, num_classes, niid, balance, partition): 29 | return 30 | 31 | # FIX HTTP Error 403: Forbidden 32 | from six.moves import urllib 33 | opener = urllib.request.build_opener() 34 | opener.addheaders = [('User-agent', 'Mozilla/5.0')] 35 | urllib.request.install_opener(opener) 36 | 37 | # Get MNIST data 38 | transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]) 39 | 40 | trainset = torchvision.datasets.MNIST( 41 | root=dir_path+"rawdata", train=True, download=True, transform=transform) 42 | testset = torchvision.datasets.MNIST( 43 | root=dir_path+"rawdata", train=False, download=True, transform=transform) 44 | trainloader = torch.utils.data.DataLoader( 45 | trainset, batch_size=len(trainset.data), shuffle=False) 46 | testloader = torch.utils.data.DataLoader( 47 | testset, batch_size=len(testset.data), shuffle=False) 48 | 49 | for _, train_data in enumerate(trainloader, 0): 50 | trainset.data, trainset.targets = train_data 51 | for _, test_data in enumerate(testloader, 0): 52 | testset.data, testset.targets = test_data 53 | 54 | dataset_image = [] 55 | dataset_label = [] 56 | 57 | dataset_image.extend(trainset.data.cpu().detach().numpy()) 58 | dataset_image.extend(testset.data.cpu().detach().numpy()) 59 | dataset_label.extend(trainset.targets.cpu().detach().numpy()) 60 | dataset_label.extend(testset.targets.cpu().detach().numpy()) 61 | dataset_image = np.array(dataset_image) 62 | dataset_label = np.array(dataset_label) 63 | 64 | # dataset = [] 65 | # for i in range(num_classes): 66 | # idx = dataset_label == i 67 | # dataset.append(dataset_image[idx]) 68 | 69 | X, y, statistic = separate_data((dataset_image, dataset_label), num_clients, num_classes, 70 | niid, balance, partition) 71 | train_data, test_data = split_data(X, y) 72 | save_file(config_path, train_path, test_path, train_data, test_data, num_clients, num_classes, 73 | statistic, niid, balance, partition) 74 | 75 | 76 | if __name__ == "__main__": 77 | niid = True if sys.argv[1] == "noniid" else False 78 | balance = True if sys.argv[2] == "balance" else False 79 | partition = sys.argv[3] if sys.argv[3] != "-" else None 80 | 81 | generate_mnist(dir_path, num_clients, num_classes, niid, balance, partition) -------------------------------------------------------------------------------- /dataset/generate_svhn.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import random 5 | import torch 6 | import torchvision 7 | import torchvision.transforms as transforms 8 | from utils.dataset_utils import check, separate_data, split_data, save_file 9 | 10 | 11 | random.seed(1) 12 | np.random.seed(1) 13 | num_clients = 20 14 | num_classes = 10 15 | dir_path = "svhn/" 16 | 17 | 18 | # Allocate data to users 19 | def generate_mnist(dir_path, num_clients, num_classes, niid, balance, partition): 20 | if not os.path.exists(dir_path): 21 | os.makedirs(dir_path) 22 | 23 | # Setup directory for train/test data 24 | config_path = dir_path + "config.json" 25 | train_path = dir_path + "train/" 26 | test_path = dir_path + "test/" 27 | 28 | if check(config_path, train_path, test_path, num_clients, num_classes, niid, balance, partition): 29 | return 30 | 31 | # FIX HTTP Error 403: Forbidden 32 | from six.moves import urllib 33 | opener = urllib.request.build_opener() 34 | opener.addheaders = [('User-agent', 'Mozilla/5.0')] 35 | urllib.request.install_opener(opener) 36 | 37 | # Get MNIST data 38 | transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]) 39 | 40 | trainset = torchvision.datasets.SVHN( 41 | root=dir_path+"rawdata", split='train', download=True, transform=transform) 42 | testset = torchvision.datasets.SVHN( 43 | root=dir_path+"rawdata", split='test', download=True, transform=transform) 44 | trainloader = torch.utils.data.DataLoader( 45 | trainset, batch_size=len(trainset.data), shuffle=False) 46 | testloader = torch.utils.data.DataLoader( 47 | testset, batch_size=len(testset.data), shuffle=False) 48 | 49 | for _, train_data in enumerate(trainloader, 0): 50 | trainset.data, trainset.targets = train_data 51 | for _, test_data in enumerate(testloader, 0): 52 | testset.data, testset.targets = test_data 53 | 54 | dataset_image = [] 55 | dataset_label = [] 56 | 57 | dataset_image.extend(trainset.data.cpu().detach().numpy()) 58 | dataset_image.extend(testset.data.cpu().detach().numpy()) 59 | dataset_label.extend(trainset.targets.cpu().detach().numpy()) 60 | dataset_label.extend(testset.targets.cpu().detach().numpy()) 61 | dataset_image = np.array(dataset_image) 62 | dataset_label = np.array(dataset_label) 63 | 64 | # dataset = [] 65 | # for i in range(num_classes): 66 | # idx = dataset_label == i 67 | # dataset.append(dataset_image[idx]) 68 | 69 | X, y, statistic = separate_data((dataset_image, dataset_label), num_clients, num_classes, 70 | niid, balance, partition) 71 | train_data, test_data = split_data(X, y) 72 | save_file(config_path, train_path, test_path, train_data, test_data, num_clients, num_classes, 73 | statistic, niid, balance, partition) 74 | 75 | 76 | if __name__ == "__main__": 77 | niid = True if sys.argv[1] == "noniid" else False 78 | balance = True if sys.argv[2] == "balance" else False 79 | partition = sys.argv[3] if sys.argv[3] != "-" else None 80 | 81 | generate_mnist(dir_path, num_clients, num_classes, niid, balance, partition) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /system/flcore/clients/clientbase.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import torch 3 | import torch.nn as nn 4 | import numpy as np 5 | import os 6 | import torch.nn.functional as F 7 | from torch.utils.data import DataLoader 8 | from sklearn.preprocessing import label_binarize 9 | from sklearn import metrics 10 | from utils.data_utils import read_client_data 11 | from utils.compresion import * 12 | 13 | 14 | class Client(object): 15 | """ 16 | Base class for clients in federated learning. 17 | """ 18 | 19 | def __init__(self, args, id, train_samples, test_samples, **kwargs): 20 | self.model = copy.deepcopy(args.model) 21 | self.dataset = args.dataset 22 | self.device = args.device 23 | self.id = id # integer 24 | self.save_folder_name = args.save_folder_name 25 | 26 | self.num_classes = args.num_classes 27 | self.train_samples = train_samples 28 | self.test_samples = test_samples 29 | self.batch_size = args.batch_size 30 | self.learning_rate = args.local_learning_rate 31 | self.local_steps = args.local_steps 32 | 33 | self.train_time_cost = {'num_rounds': 0, 'total_cost': 0.0} 34 | 35 | # check BatchNorm 36 | self.has_BatchNorm = False 37 | for layer in self.model.children(): 38 | if isinstance(layer, nn.BatchNorm2d): 39 | self.has_BatchNorm = True 40 | break 41 | 42 | 43 | 44 | self.sample_rate = self.batch_size / self.train_samples 45 | 46 | self.compressed_model = Packages() 47 | 48 | 49 | def load_train_data(self, batch_size=None): 50 | if batch_size == None: 51 | batch_size = self.batch_size 52 | train_data = read_client_data(self.dataset, self.id, is_train=True) 53 | return DataLoader(train_data, batch_size, drop_last=True, shuffle=True) 54 | 55 | def load_test_data(self, batch_size=None): 56 | if batch_size == None: 57 | batch_size = self.batch_size 58 | test_data = read_client_data(self.dataset, self.id, is_train=False) 59 | return DataLoader(test_data, batch_size, drop_last=False, shuffle=True) 60 | 61 | def set_parameters_c(self, model): 62 | self.compressed_model = copy.deepcopy(model) 63 | 64 | def set_parameters(self, model): 65 | for new_param, old_param in zip(model.parameters(), self.model.parameters()): 66 | old_param.data = new_param.data.clone() 67 | 68 | def clone_model(self, model, target): 69 | for param, target_param in zip(model.parameters(), target.parameters()): 70 | target_param.data = param.data.clone() 71 | 72 | def update_parameters(self, model, new_params): 73 | for param, new_param in zip(model.parameters(), new_params): 74 | param.data = new_param.data.clone() 75 | 76 | def test_metrics(self): 77 | testloaderfull = self.load_test_data() 78 | self.model.eval() 79 | 80 | test_acc = 0 81 | test_num = 0 82 | y_prob = [] 83 | y_true = [] 84 | 85 | with torch.no_grad(): 86 | for x, y in testloaderfull: 87 | if type(x) == type([]): 88 | x[0] = x[0].to(self.device) 89 | else: 90 | x = x.to(self.device) 91 | y = y.to(self.device) 92 | output = self.model(x) 93 | 94 | test_acc += (torch.sum(torch.argmax(output, dim=1) == y)).item() 95 | test_num += y.shape[0] 96 | 97 | y_prob.append(output.detach().cpu().numpy()) 98 | y_true.append(label_binarize(y.detach().cpu().numpy(), classes=np.arange(self.num_classes))) 99 | 100 | 101 | y_prob = np.concatenate(y_prob, axis=0) 102 | y_true = np.concatenate(y_true, axis=0) 103 | 104 | auc = metrics.roc_auc_score(y_true, y_prob, average='micro') 105 | 106 | return test_acc, test_num, auc 107 | 108 | def train_metrics(self): 109 | trainloader = self.load_train_data() 110 | self.model.eval() 111 | 112 | train_num = 0 113 | loss = 0 114 | for x, y in trainloader: 115 | if type(x) == type([]): 116 | x[0] = x[0].to(self.device) 117 | else: 118 | x = x.to(self.device) 119 | y = y.to(self.device) 120 | output = self.model(x) 121 | train_num += y.shape[0] 122 | loss += self.loss(output, y).item() * y.shape[0] 123 | 124 | 125 | return loss, train_num 126 | 127 | 128 | def save_item(self, item, item_name, item_path=None): 129 | if item_path == None: 130 | item_path = self.save_folder_name 131 | if not os.path.exists(item_path): 132 | os.makedirs(item_path) 133 | torch.save(item, os.path.join(item_path, "client_" + str(self.id) + "_" + item_name + ".pt")) 134 | 135 | def load_item(self, item_name, item_path=None): 136 | if item_path == None: 137 | item_path = self.save_folder_name 138 | return torch.load(os.path.join(item_path, "client_" + str(self.id) + "_" + item_name + ".pt")) 139 | 140 | -------------------------------------------------------------------------------- /system/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import copy 3 | import torch 4 | import argparse 5 | import os 6 | import time 7 | import warnings 8 | import numpy as np 9 | from torch.nn.functional import dropout 10 | import torchvision 11 | 12 | from flcore.servers.serverce import FedCE 13 | 14 | from flcore.trainmodel.models import * 15 | 16 | from utils.result_utils import average_data 17 | from utils.mem_utils import MemReporter 18 | 19 | warnings.simplefilter("ignore") 20 | torch.manual_seed(0) 21 | 22 | def run(args): 23 | 24 | time_list = [] 25 | reporter = MemReporter() 26 | model_str = args.model 27 | 28 | for i in range(args.prev, args.times): 29 | print(f"\n============= Running time: {i}th =============") 30 | print("Creating server and clients ...") 31 | start = time.time() 32 | 33 | # Generate args.model 34 | if model_str == "mlr": 35 | if args.dataset == "mnist" or args.dataset == "fmnist": 36 | args.model = FedAvgMLP(1*28*28, num_classes=args.num_classes).to(args.device) 37 | 38 | elif model_str == "cnn": 39 | if args.dataset == "mnist" or args.dataset == "fmnist": 40 | args.model = FedAvgCNN(in_features=1, num_classes=args.num_classes, dim=1024).to(args.device) 41 | elif args.dataset == "Cifar10": 42 | args.model = FedAvgCNN(in_features=3, num_classes=args.num_classes, dim=1600).to(args.device) 43 | 44 | 45 | elif model_str == "resnet": 46 | args.model = torchvision.models.resnet18(pretrained=False, num_classes=args.num_classes).to(args.device) 47 | 48 | else: 49 | raise NotImplementedError 50 | 51 | # select algorithm 52 | if args.algorithm == "FedCE": 53 | server = FedCE(args, i) 54 | 55 | else: 56 | raise NotImplementedError 57 | 58 | server.train() 59 | 60 | time_list.append(time.time()-start) 61 | 62 | print(f"\nAverage time cost: {round(np.average(time_list), 2)}s.") 63 | 64 | 65 | # Global average 66 | average_data(dataset=args.dataset, 67 | algorithm=args.algorithm, 68 | goal=args.goal, 69 | times=args.times, 70 | length=args.global_rounds/args.eval_gap+1) 71 | 72 | print("All done!") 73 | 74 | reporter.report() 75 | 76 | 77 | if __name__ == "__main__": 78 | total_start = time.time() 79 | 80 | parser = argparse.ArgumentParser() 81 | # general 82 | parser.add_argument('-dev', "--device", type=str, default="cuda", 83 | choices=["cpu", "cuda"]) 84 | parser.add_argument('-did', "--device_id", type=str, default="0") 85 | parser.add_argument('-data', "--dataset", type=str, default="mnist") 86 | parser.add_argument('-nb', "--num_classes", type=int, default=10) 87 | parser.add_argument('-m', "--model", type=str, default="cnn") 88 | parser.add_argument('-p', "--predictor", type=str, default="cnn") 89 | parser.add_argument('-lbs', "--batch_size", type=int, default=10) 90 | parser.add_argument('-lr', "--local_learning_rate", type=float, default=0.005, 91 | help="Local learning rate") 92 | parser.add_argument('-gr', "--global_rounds", type=int, default=1000) 93 | parser.add_argument('-ls', "--local_steps", type=int, default=20) 94 | parser.add_argument('-algo', "--algorithm", type=str, default="FedCE") 95 | parser.add_argument('-jr', "--join_ratio", type=float, default=1.0, 96 | help="Ratio of clients per round") 97 | parser.add_argument('-nc', "--num_clients", type=int, default=20, 98 | help="Total number of clients") 99 | parser.add_argument('-pv', "--prev", type=int, default=0, 100 | help="Previous Running times") 101 | parser.add_argument('-t', "--times", type=int, default=1, 102 | help="Running times") 103 | parser.add_argument('-eg', "--eval_gap", type=int, default=1, 104 | help="Rounds gap for evaluation") 105 | parser.add_argument('-sfn', "--save_folder_name", type=str, default='models') 106 | parser.add_argument('-r', "--r", type=float, default=0.00005) 107 | 108 | 109 | 110 | 111 | args = parser.parse_args() 112 | 113 | os.environ["CUDA_VISIBLE_DEVICES"] = args.device_id 114 | 115 | if args.device == "cuda" and not torch.cuda.is_available(): 116 | print("\ncuda is not avaiable.\n") 117 | args.device = "cpu" 118 | 119 | print("=" * 50) 120 | 121 | print("Algorithm: {}".format(args.algorithm)) 122 | print("Local batch size: {}".format(args.batch_size)) 123 | print("Local steps: {}".format(args.local_steps)) 124 | print("Local learing rate: {}".format(args.local_learning_rate)) 125 | print("Total number of clients: {}".format(args.num_clients)) 126 | print("Clients join in each round: {}".format(args.join_ratio)) 127 | 128 | print("Global rounds: {}".format(args.global_rounds)) 129 | print("Running times: {}".format(args.times)) 130 | print("Dataset: {}".format(args.dataset)) 131 | print("Local model: {}".format(args.model)) 132 | print("Using device: {}".format(args.device)) 133 | print("Compresion Rate: {}".format(args.r)) 134 | 135 | if args.device == "cuda": 136 | print("Cuda device id: {}".format(os.environ["CUDA_VISIBLE_DEVICES"])) 137 | print("=" * 50) 138 | 139 | run(args) 140 | -------------------------------------------------------------------------------- /environment.yaml: -------------------------------------------------------------------------------- 1 | name: epfl 2 | channels: 3 | - pytorch 4 | - nvidia 5 | - defaults 6 | dependencies: 7 | - _libgcc_mutex=0.1=main 8 | - _openmp_mutex=5.1=1_gnu 9 | - blas=1.0=mkl 10 | - bottleneck=1.3.5=py37h7deecbd_0 11 | - brotli=1.0.9=h5eee18b_7 12 | - brotli-bin=1.0.9=h5eee18b_7 13 | - brotlipy=0.7.0=py37h27cfd23_1003 14 | - bzip2=1.0.8=h7b6447c_0 15 | - ca-certificates=2023.08.22=h06a4308_0 16 | - certifi=2022.12.7=py37h06a4308_0 17 | - cffi=1.15.1=py37h5eee18b_3 18 | - charset-normalizer=2.0.4=pyhd3eb1b0_0 19 | - cryptography=39.0.1=py37h9ce1e76_0 20 | - cuda-cudart=11.7.99=0 21 | - cuda-cupti=11.7.101=0 22 | - cuda-libraries=11.7.1=0 23 | - cuda-nvrtc=11.7.99=0 24 | - cuda-nvtx=11.7.91=0 25 | - cuda-runtime=11.7.1=0 26 | - cycler=0.11.0=pyhd3eb1b0_0 27 | - cyrus-sasl=2.1.28=h9c0eb46_1 28 | - dbus=1.13.18=hb2f20db_0 29 | - expat=2.5.0=h6a678d5_0 30 | - ffmpeg=4.3=hf484d3e_0 31 | - fftw=3.3.9=h27cfd23_1 32 | - fontconfig=2.14.1=h4c34cd2_2 33 | - fonttools=4.25.0=pyhd3eb1b0_0 34 | - freetype=2.12.1=h4a9f257_0 35 | - giflib=5.2.1=h5eee18b_3 36 | - glib=2.69.1=he621ea3_2 37 | - gmp=6.2.1=h295c915_3 38 | - gnutls=3.6.15=he1e5248_0 39 | - gst-plugins-base=1.14.1=h6a678d5_1 40 | - gstreamer=1.14.1=h5eee18b_1 41 | - h5py=3.7.0=py37h737f45e_0 42 | - hdf5=1.10.6=h3ffc7dd_1 43 | - icu=58.2=he6710b0_3 44 | - idna=3.4=py37h06a4308_0 45 | - intel-openmp=2021.4.0=h06a4308_3561 46 | - joblib=1.1.1=py37h06a4308_0 47 | - jpeg=9e=h5eee18b_1 48 | - kiwisolver=1.4.4=py37h6a678d5_0 49 | - krb5=1.20.1=h568e23c_1 50 | - lame=3.100=h7b6447c_0 51 | - lcms2=2.12=h3be6417_0 52 | - ld_impl_linux-64=2.38=h1181459_1 53 | - lerc=3.0=h295c915_0 54 | - libbrotlicommon=1.0.9=h5eee18b_7 55 | - libbrotlidec=1.0.9=h5eee18b_7 56 | - libbrotlienc=1.0.9=h5eee18b_7 57 | - libclang=14.0.6=default_hc6dbbc7_1 58 | - libclang13=14.0.6=default_he11475f_1 59 | - libcublas=11.10.3.66=0 60 | - libcufft=10.7.2.124=h4fbf590_0 61 | - libcufile=1.7.2.10=0 62 | - libcups=2.4.2=ha637b67_0 63 | - libcurand=10.3.3.141=0 64 | - libcusolver=11.4.0.1=0 65 | - libcusparse=11.7.4.91=0 66 | - libdeflate=1.17=h5eee18b_0 67 | - libedit=3.1.20221030=h5eee18b_0 68 | - libevent=2.1.12=h8f2d780_0 69 | - libffi=3.4.4=h6a678d5_0 70 | - libgcc-ng=11.2.0=h1234567_1 71 | - libgfortran-ng=11.2.0=h00389a5_1 72 | - libgfortran5=11.2.0=h1234567_1 73 | - libgomp=11.2.0=h1234567_1 74 | - libiconv=1.16=h7f8727e_2 75 | - libidn2=2.3.4=h5eee18b_0 76 | - libllvm14=14.0.6=hdb19cb5_3 77 | - libnpp=11.7.4.75=0 78 | - libnvjpeg=11.8.0.2=0 79 | - libpng=1.6.39=h5eee18b_0 80 | - libpq=12.15=h37d81fd_1 81 | - libstdcxx-ng=11.2.0=h1234567_1 82 | - libtasn1=4.19.0=h5eee18b_0 83 | - libtiff=4.5.1=h6a678d5_0 84 | - libunistring=0.9.10=h27cfd23_0 85 | - libuuid=1.41.5=h5eee18b_0 86 | - libwebp=1.2.4=h11a3e52_1 87 | - libwebp-base=1.2.4=h5eee18b_1 88 | - libxcb=1.15=h7f8727e_0 89 | - libxkbcommon=1.0.1=h5eee18b_1 90 | - libxml2=2.10.4=hcbfbd50_0 91 | - libxslt=1.1.37=h2085143_0 92 | - lz4-c=1.9.4=h6a678d5_0 93 | - matplotlib=3.5.3=py37h06a4308_0 94 | - matplotlib-base=3.5.3=py37hf590b9c_0 95 | - mkl=2021.4.0=h06a4308_640 96 | - mkl-service=2.4.0=py37h7f8727e_0 97 | - mkl_fft=1.3.1=py37hd3c417c_0 98 | - mkl_random=1.2.2=py37h51133e4_0 99 | - munkres=1.1.4=py_0 100 | - mysql=5.7.24=he378463_2 101 | - ncurses=6.4=h6a678d5_0 102 | - nettle=3.7.3=hbbd107a_1 103 | - nspr=4.35=h6a678d5_0 104 | - nss=3.89.1=h6a678d5_0 105 | - numexpr=2.8.4=py37he184ba9_0 106 | - numpy=1.21.5=py37h6c91a56_3 107 | - numpy-base=1.21.5=py37ha15fc14_3 108 | - openh264=2.1.1=h4ff587b_0 109 | - openssl=1.1.1w=h7f8727e_0 110 | - packaging=22.0=py37h06a4308_0 111 | - pandas=1.3.5=py37h8c16a72_0 112 | - pcre=8.45=h295c915_0 113 | - pillow=9.4.0=py37h6a678d5_0 114 | - pip=22.3.1=py37h06a4308_0 115 | - ply=3.11=py37_0 116 | - pycparser=2.21=pyhd3eb1b0_0 117 | - pyopenssl=23.0.0=py37h06a4308_0 118 | - pyparsing=3.0.9=py37h06a4308_0 119 | - pyqt=5.15.7=py37h6a678d5_1 120 | - pyqt5-sip=12.11.0=py37h6a678d5_1 121 | - pysocks=1.7.1=py37_1 122 | - python=3.7.16=h7a1cb2a_0 123 | - python-dateutil=2.8.2=pyhd3eb1b0_0 124 | - pytorch=1.13.1=py3.7_cuda11.7_cudnn8.5.0_0 125 | - pytorch-cuda=11.7=h778d358_5 126 | - pytorch-mutex=1.0=cuda 127 | - pytz=2022.7=py37h06a4308_0 128 | - qt-main=5.15.2=h5b8104b_9 129 | - qt-webengine=5.15.9=h9ab4d14_7 130 | - qtwebkit=5.212=h3fafdc1_5 131 | - readline=8.2=h5eee18b_0 132 | - requests=2.28.1=py37h06a4308_0 133 | - scikit-learn=1.0.2=py37h51133e4_1 134 | - scipy=1.7.3=py37h6c91a56_2 135 | - seaborn=0.12.2=py37h06a4308_0 136 | - setuptools=65.6.3=py37h06a4308_0 137 | - sip=6.6.2=py37h6a678d5_0 138 | - six=1.16.0=pyhd3eb1b0_1 139 | - sqlite=3.41.2=h5eee18b_0 140 | - threadpoolctl=2.2.0=pyh0d69192_0 141 | - tk=8.6.12=h1ccaba5_0 142 | - toml=0.10.2=pyhd3eb1b0_0 143 | - torchaudio=0.13.1=py37_cu117 144 | - torchvision=0.14.1=py37_cu117 145 | - tornado=6.2=py37h5eee18b_0 146 | - typing_extensions=4.3.0=py37h06a4308_0 147 | - ujson=5.4.0=py37h6a678d5_0 148 | - urllib3=1.26.14=py37h06a4308_0 149 | - wheel=0.38.4=py37h06a4308_0 150 | - xz=5.4.2=h5eee18b_0 151 | - zlib=1.2.13=h5eee18b_0 152 | - zstd=1.5.5=hc292b87_0 153 | - pip: 154 | - calmsize==0.1.3 155 | - memory-profiler==0.61.0 156 | - opacus==1.4.0 157 | - opt-einsum==3.3.0 158 | - psutil==5.9.5 159 | - pybind11==2.11.1 160 | - seal==4.0.0 161 | prefix: /root/anaconda3/envs/epfl 162 | -------------------------------------------------------------------------------- /dataset/utils/dataset_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ujson 3 | import numpy as np 4 | import gc 5 | from sklearn.model_selection import train_test_split 6 | 7 | batch_size = 32 8 | train_size = 0.75 # merge original training set and test set, then split it manually. 9 | least_samples = batch_size / (1-train_size) # least samples for each client 10 | alpha = 0.1 # for Dirichlet distribution 11 | 12 | def check(config_path, train_path, test_path, num_clients, num_classes, niid=False, 13 | balance=True, partition=None): 14 | # check existing dataset 15 | if os.path.exists(config_path): 16 | with open(config_path, 'r') as f: 17 | config = ujson.load(f) 18 | if config['num_clients'] == num_clients and \ 19 | config['num_classes'] == num_classes and \ 20 | config['non_iid'] == niid and \ 21 | config['balance'] == balance and \ 22 | config['partition'] == partition and \ 23 | config['alpha'] == alpha and \ 24 | config['batch_size'] == batch_size: 25 | print("\nDataset already generated.\n") 26 | return True 27 | 28 | dir_path = os.path.dirname(train_path) 29 | if not os.path.exists(dir_path): 30 | os.makedirs(dir_path) 31 | dir_path = os.path.dirname(test_path) 32 | if not os.path.exists(dir_path): 33 | os.makedirs(dir_path) 34 | 35 | return False 36 | 37 | def separate_data(data, num_clients, num_classes, niid=False, balance=False, partition=None, class_per_client=2): 38 | X = [[] for _ in range(num_clients)] 39 | y = [[] for _ in range(num_clients)] 40 | statistic = [[] for _ in range(num_clients)] 41 | 42 | dataset_content, dataset_label = data 43 | 44 | dataidx_map = {} 45 | 46 | if not niid: 47 | partition = 'pat' 48 | class_per_client = num_classes 49 | 50 | if partition == 'pat': 51 | idxs = np.array(range(len(dataset_label))) 52 | idx_for_each_class = [] 53 | for i in range(num_classes): 54 | idx_for_each_class.append(idxs[dataset_label == i]) 55 | 56 | class_num_per_client = [class_per_client for _ in range(num_clients)] 57 | for i in range(num_classes): 58 | selected_clients = [] 59 | for client in range(num_clients): 60 | if class_num_per_client[client] > 0: 61 | selected_clients.append(client) 62 | selected_clients = selected_clients[:int(num_clients/num_classes*class_per_client)] 63 | 64 | num_all_samples = len(idx_for_each_class[i]) 65 | num_selected_clients = len(selected_clients) 66 | num_per = num_all_samples / num_selected_clients 67 | if balance: 68 | num_samples = [int(num_per) for _ in range(num_selected_clients-1)] 69 | else: 70 | num_samples = np.random.randint(max(num_per/10, least_samples/num_classes), num_per, num_selected_clients-1).tolist() 71 | num_samples.append(num_all_samples-sum(num_samples)) 72 | 73 | idx = 0 74 | for client, num_sample in zip(selected_clients, num_samples): 75 | if client not in dataidx_map.keys(): 76 | dataidx_map[client] = idx_for_each_class[i][idx:idx+num_sample] 77 | else: 78 | dataidx_map[client] = np.append(dataidx_map[client], idx_for_each_class[i][idx:idx+num_sample], axis=0) 79 | idx += num_sample 80 | class_num_per_client[client] -= 1 81 | 82 | elif partition == "dir": 83 | # https://github.com/IBM/probabilistic-federated-neural-matching/blob/master/experiment.py 84 | min_size = 0 85 | K = num_classes 86 | N = len(dataset_label) 87 | 88 | while min_size < least_samples: 89 | idx_batch = [[] for _ in range(num_clients)] 90 | for k in range(K): 91 | idx_k = np.where(dataset_label == k)[0] 92 | np.random.shuffle(idx_k) 93 | proportions = np.random.dirichlet(np.repeat(alpha, num_clients)) 94 | ## Balance 95 | proportions = np.array([p*(len(idx_j) 1: 135 | X_train, X_test, y_train, y_test = train_test_split( 136 | X[i], y[i], train_size=train_size, shuffle=True) 137 | else: 138 | X_train, X_test, y_train, y_test = train_test_split( 139 | X[i], y[i], train_size=train_size, shuffle=True) 140 | 141 | train_data.append({'x': X_train, 'y': y_train}) 142 | num_samples['train'].append(len(y_train)) 143 | test_data.append({'x': X_test, 'y': y_test}) 144 | num_samples['test'].append(len(y_test)) 145 | 146 | print("Total number of samples:", sum(num_samples['train'] + num_samples['test'])) 147 | print("The number of train samples:", num_samples['train']) 148 | print("The number of test samples:", num_samples['test']) 149 | print() 150 | del X, y 151 | # gc.collect() 152 | 153 | return train_data, test_data 154 | 155 | def save_file(config_path, train_path, test_path, train_data, test_data, num_clients, 156 | num_classes, statistic, niid=False, balance=True, partition=None): 157 | config = { 158 | 'num_clients': num_clients, 159 | 'num_classes': num_classes, 160 | 'non_iid': niid, 161 | 'balance': balance, 162 | 'partition': partition, 163 | 'Size of samples for labels in clients': statistic, 164 | 'alpha': alpha, 165 | 'batch_size': batch_size, 166 | } 167 | 168 | # gc.collect() 169 | print("Saving to disk.\n") 170 | 171 | for idx, train_dict in enumerate(train_data): 172 | with open(train_path + str(idx) + '.npz', 'wb') as f: 173 | np.savez_compressed(f, data=train_dict) 174 | for idx, test_dict in enumerate(test_data): 175 | with open(test_path + str(idx) + '.npz', 'wb') as f: 176 | np.savez_compressed(f, data=test_dict) 177 | with open(config_path, 'w') as f: 178 | ujson.dump(config, f) 179 | 180 | print("Finish generating dataset.\n") -------------------------------------------------------------------------------- /system/utils/mem_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import gc 3 | from collections import defaultdict 4 | from typing import Optional, Tuple, List 5 | 6 | import torch 7 | 8 | from math import isnan 9 | from calmsize import size as calmsize 10 | 11 | def readable_size(num_bytes: int) -> str: 12 | return '' if isnan(num_bytes) else '{:.2f}'.format(calmsize(num_bytes)) 13 | 14 | LEN = 79 15 | 16 | # some pytorch low-level memory management constant 17 | # the minimal allocate memory size (Byte) 18 | PYTORCH_MIN_ALLOCATE = 2 ** 9 19 | # the minimal cache memory size (Byte) 20 | PYTORCH_MIN_CACHE = 2 ** 20 21 | 22 | class MemReporter(): 23 | """A memory reporter that collects tensors and memory usages 24 | 25 | Parameters: 26 | - model: an extra nn.Module can be passed to infer the name 27 | of Tensors 28 | 29 | """ 30 | def __init__(self, model: Optional[torch.nn.Module] = None): 31 | self.tensor_name = {} 32 | self.device_mapping = defaultdict(list) 33 | self.device_tensor_stat = {} 34 | # to numbering the unknown tensors 35 | self.name_idx = 0 36 | 37 | tensor_names = defaultdict(list) 38 | if model is not None: 39 | assert isinstance(model, torch.nn.Module) 40 | # for model with tying weight, multiple parameters may share 41 | # the same underlying tensor 42 | for name, param in model.named_parameters(): 43 | tensor_names[param].append(name) 44 | 45 | for param, name in tensor_names.items(): 46 | self.tensor_name[id(param)] = '+'.join(name) 47 | 48 | def _get_tensor_name(self, tensor: torch.Tensor) -> str: 49 | tensor_id = id(tensor) 50 | if tensor_id in self.tensor_name: 51 | name = self.tensor_name[tensor_id] 52 | # use numbering if no name can be inferred 53 | else: 54 | name = type(tensor).__name__ + str(self.name_idx) 55 | self.tensor_name[tensor_id] = name 56 | self.name_idx += 1 57 | return name 58 | 59 | def collect_tensor(self): 60 | """Collect all tensor objects tracked by python 61 | 62 | NOTICE: 63 | - the buffers for backward which is implemented in C++ are 64 | not tracked by python's reference counting. 65 | - the gradients(.grad) of Parameters is not collected, and 66 | I don't know why. 67 | """ 68 | #FIXME: make the grad tensor collected by gc 69 | objects = gc.get_objects() 70 | tensors = [obj for obj in objects if isinstance(obj, torch.Tensor)] 71 | for t in tensors: 72 | self.device_mapping[t.device].append(t) 73 | 74 | def get_stats(self): 75 | """Get the memory stat of tensors and then release them 76 | 77 | As a memory profiler, we cannot hold the reference to any tensors, which 78 | causes possibly inaccurate memory usage stats, so we delete the tensors after 79 | getting required stats""" 80 | visited_data = {} 81 | self.device_tensor_stat.clear() 82 | 83 | def get_tensor_stat(tensor: torch.Tensor) -> List[Tuple[str, int, int, int]]: 84 | """Get the stat of a single tensor 85 | 86 | Returns: 87 | - stat: a tuple containing (tensor_name, tensor_size, 88 | tensor_numel, tensor_memory) 89 | """ 90 | assert isinstance(tensor, torch.Tensor) 91 | 92 | name = self._get_tensor_name(tensor) 93 | if tensor.is_sparse: 94 | indices_stat = get_tensor_stat(tensor._indices()) 95 | values_stat = get_tensor_stat(tensor._values()) 96 | return indices_stat + values_stat 97 | 98 | numel = tensor.numel() 99 | element_size = tensor.element_size() 100 | fact_numel = tensor.storage().size() 101 | fact_memory_size = fact_numel * element_size 102 | # since pytorch allocate at least 512 Bytes for any tensor, round 103 | # up to a multiple of 512 104 | memory_size = math.ceil(fact_memory_size / PYTORCH_MIN_ALLOCATE) \ 105 | * PYTORCH_MIN_ALLOCATE 106 | 107 | # tensor.storage should be the actual object related to memory 108 | # allocation 109 | data_ptr = tensor.storage().data_ptr() 110 | if data_ptr in visited_data: 111 | name = '{}(->{})'.format( 112 | name, 113 | visited_data[data_ptr], 114 | ) 115 | # don't count the memory for reusing same underlying storage 116 | memory_size = 0 117 | else: 118 | visited_data[data_ptr] = name 119 | 120 | size = tuple(tensor.size()) 121 | # torch scalar has empty size 122 | if not size: 123 | size = (1,) 124 | 125 | return [(name, size, numel, memory_size)] 126 | 127 | for device, tensors in self.device_mapping.items(): 128 | tensor_stats = [] 129 | for tensor in tensors: 130 | 131 | if tensor.numel() == 0: 132 | continue 133 | stat = get_tensor_stat(tensor) # (name, shape, numel, memory_size) 134 | tensor_stats += stat 135 | if isinstance(tensor, torch.nn.Parameter): 136 | if tensor.grad is not None: 137 | # manually specify the name of gradient tensor 138 | self.tensor_name[id(tensor.grad)] = '{}.grad'.format( 139 | self._get_tensor_name(tensor) 140 | ) 141 | stat = get_tensor_stat(tensor.grad) 142 | tensor_stats += stat 143 | 144 | self.device_tensor_stat[device] = tensor_stats 145 | 146 | self.device_mapping.clear() 147 | 148 | def print_stats(self, verbose: bool = False, target_device: Optional[torch.device] = None) -> None: 149 | # header 150 | # show_reuse = verbose 151 | # template_format = '{:<40s}{:>20s}{:>10s}' 152 | # print(template_format.format('Element type', 'Size', 'Used MEM') ) 153 | for device, tensor_stats in self.device_tensor_stat.items(): 154 | # By default, if the target_device is not specified, 155 | # print tensors on all devices 156 | if target_device is not None and device != target_device: 157 | continue 158 | # print('-' * LEN) 159 | print('\nStorage on {}'.format(device)) 160 | total_mem = 0 161 | total_numel = 0 162 | for stat in tensor_stats: 163 | name, size, numel, mem = stat 164 | # if not show_reuse: 165 | # name = name.split('(')[0] 166 | # print(template_format.format( 167 | # str(name), 168 | # str(size), 169 | # readable_size(mem), 170 | # )) 171 | total_mem += mem 172 | total_numel += numel 173 | 174 | print('-'*LEN) 175 | print('Total Tensors: {} \tUsed Memory: {}'.format( 176 | total_numel, readable_size(total_mem), 177 | )) 178 | 179 | if device != torch.device('cpu'): 180 | with torch.cuda.device(device): 181 | memory_allocated = torch.cuda.memory_allocated() 182 | print('The allocated memory on {}: {}'.format( 183 | device, readable_size(memory_allocated), 184 | )) 185 | if memory_allocated != total_mem: 186 | print('Memory differs due to the matrix alignment or' 187 | ' invisible gradient buffer tensors') 188 | print('-'*LEN) 189 | 190 | def report(self, verbose: bool = False, device: Optional[torch.device] = None) -> None: 191 | """Interface for end-users to directly print the memory usage 192 | 193 | args: 194 | - verbose: flag to show tensor.storage reuse information 195 | - device: `torch.device` object, specify the target device 196 | to report detailed memory usage. It will print memory usage 197 | on all devices if not specified. Usually we only want to 198 | print the memory usage on CUDA devices. 199 | 200 | """ 201 | self.collect_tensor() 202 | self.get_stats() 203 | self.print_stats(verbose, target_device=device) -------------------------------------------------------------------------------- /system/utils/compresion.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | import copy 4 | import scipy.fftpack as spfft 5 | import math 6 | import torch 7 | from seal import * 8 | 9 | 10 | def dct_2d(x): 11 | return torch.tensor(spfft.dct(spfft.dct(x.numpy().T, norm='ortho').T, norm='ortho')) 12 | 13 | 14 | def idct_2d(x): 15 | return torch.tensor(spfft.idct(spfft.idct(x.numpy().T, norm='ortho').T, norm='ortho')) 16 | 17 | 18 | def get_u_transpose(shape): 19 | u_transpose = np.zeros((shape[0]**2, shape[1]**2)) 20 | n = shape[0] 21 | k = 0 22 | i = 0 23 | for row in u_transpose: 24 | row[k+i] = 1 25 | k += n 26 | if k >= n*n: 27 | k = 0 28 | i += 1 29 | 30 | return u_transpose 31 | 32 | 33 | def get_transposed_diagonals(u_transposed): 34 | transposed_diagonals = np.zeros(u_transposed.shape) 35 | for i in range(u_transposed.shape[0]): 36 | a = np.diagonal(u_transposed, offset=i) 37 | b = np.diagonal(u_transposed, offset=u_transposed.shape[0]-i) 38 | transposed_diagonals[i] = np.concatenate([a, b]) 39 | 40 | return transposed_diagonals 41 | 42 | 43 | class Packages(object): 44 | def __init__(self, shape=(200, 200)): 45 | self.Packages_shape = shape # 包裹形状 46 | self.Packages_size = shape[0] * shape[1] # 包裹容量 47 | self.Packed_item = None # 打包好的数据 48 | # self.Additional_item = None #不符合打包条件的数据 49 | self.is_Compressed = False # 包裹是否被压缩 50 | self.Volume_of_Raw_Item = None # 包裹原始大小 51 | self.Volume_of_Compressed_Item = None # 包裹压缩后的大小 52 | self.Index_of_Item = {} # 打包索引 53 | self.Packed_item_en = None 54 | self.en_shape = None 55 | 56 | # def __add__(self, other): 57 | # self.Packed_item += other.Packed_item 58 | # return self 59 | 60 | # def __sub__(self, other): 61 | # self.Packed_item -= other.Packed_item 62 | # return self 63 | 64 | # def __truediv__(self, other): 65 | # self.Packed_item /= other 66 | # return self 67 | 68 | # def __mul__(self, other): 69 | # self.Packed_item *= other 70 | # return self 71 | 72 | def pack_up(self, client_model): 73 | self.Volume_of_Raw_Item = 0 74 | 75 | Item_left = 0 # 左指针 76 | Item_right = 0 # 右指针 77 | Package_pt = 0 # 包内偏移 78 | Package_idx = 0 # 包裹编号 79 | package = torch.zeros(self.Packages_shape, dtype=torch.float32) 80 | 81 | for name, param in client_model.named_parameters(): 82 | Item_left = 0 83 | data_shape = param.shape 84 | data = copy.deepcopy(param.data.view(-1)) 85 | size = data.size()[0] 86 | self.Volume_of_Raw_Item += size 87 | Item_right = Item_left + size 88 | 89 | if self.Packages_size - Package_pt == 0: 90 | self.package_push(package) 91 | Package_idx += 1 92 | Package_pt = 0 93 | package = torch.zeros(self.Packages_shape, dtype=torch.float32) 94 | 95 | self.Index_of_Item[name] = ( 96 | Package_idx, Package_pt, size, data_shape) # 包裹编号,包内偏移量,数据长度,原数据格式 97 | 98 | if (size <= self.Packages_size - Package_pt): # 包裹是否能装下 99 | package.view(-1)[Package_pt:Package_pt + size] = data 100 | Package_pt += size 101 | else: 102 | while (Item_right is not Item_left): 103 | a_size = Item_right - Item_left # 还需打包的数据长度 104 | b_size = self.Packages_size - Package_pt # 包裹剩余容量 105 | sub_size = a_size if a_size <= b_size else b_size # 本轮打包容量 106 | package.view(-1)[Package_pt:Package_pt + 107 | sub_size] = data[Item_left:Item_left + sub_size] 108 | Item_left += sub_size 109 | Package_pt += sub_size 110 | 111 | if self.Packages_size - Package_pt == 0: 112 | self.package_push(package) 113 | Package_idx += 1 114 | Package_pt = 0 115 | package = torch.zeros( 116 | self.Packages_shape, dtype=torch.float32) 117 | if a_size == 0: 118 | break 119 | 120 | self.package_push(package) 121 | 122 | def package_push(self, package): 123 | if self.Packed_item is None: 124 | self.Packed_item = copy.deepcopy(package) 125 | else: 126 | if self.Packed_item.dim() != 3: 127 | self.Packed_item = self.Packed_item.unsqueeze(0) 128 | self.Packed_item = torch.cat( 129 | (self.Packed_item, copy.deepcopy(package).unsqueeze(0))) 130 | return self.Packed_item.shape[0] # 返回下包裹编号 131 | 132 | def unpack(self, global_model, dev): 133 | if self.is_Compressed: 134 | print("请先对包裹进行重建后再解压!!!") 135 | return 0 136 | # unpacked_item = {} 137 | for name, param in global_model.named_parameters(): 138 | # for key in self.Index_of_Item: 139 | data = torch.tensor([], dtype=torch.float32) 140 | Package_idx, Package_pt, size, data_shape = self.Index_of_Item[name] 141 | 142 | # if size <= self.Packages_size-Package_pt: 143 | # data = self.Packed_item[Package_idx][Package_pt:Package_pt+size] 144 | # else: 145 | while size != 0: 146 | if size <= self.Packages_size - Package_pt: 147 | data = torch.cat( 148 | (data, self.Packed_item[Package_idx].view(-1)[Package_pt:Package_pt + size]), 0) 149 | size = 0 150 | else: 151 | data = torch.cat( 152 | (data, self.Packed_item[Package_idx].view(-1)[Package_pt:]), 0) 153 | size -= self.Packages_size - Package_pt 154 | Package_pt = 0 155 | Package_idx += 1 156 | 157 | param.data += data.reshape(data_shape).to(dev) 158 | return global_model 159 | 160 | def package_compresion(self, r): 161 | temp = [] 162 | self.Volume_of_Compressed_Item = 0 163 | for idx in range(self.Packed_item.shape[0]): 164 | if idx == 0: 165 | temp = dct_2d( 166 | self.Packed_item[idx]).view(-1)[:math.ceil(self.Packages_size * r)] 167 | else: 168 | if temp.dim() == 1: 169 | temp = temp.unsqueeze(0) 170 | temp = torch.cat( 171 | (temp, dct_2d( 172 | self.Packed_item[idx]).view(-1)[:math.ceil(self.Packages_size * r)].unsqueeze(0))) 173 | self.Volume_of_Compressed_Item += math.ceil(self.Packages_size * r) 174 | 175 | self.is_Compressed = True 176 | # temp = torch.tensor(temp).numpy() 177 | 178 | self.Packed_item = temp 179 | # print("1",self.Volume_of_Raw_Item) 180 | # print("2",self.Volume_of_Compressed_Item) 181 | 182 | def package_decompresion(self, r): 183 | res = [] 184 | for idx in range(self.Packed_item.shape[0]): 185 | temp = torch.zeros(self.Packages_shape, dtype=torch.float32) 186 | # print(idx,math.ceil(self.Packages_size * r)) 187 | temp.view(-1)[:math.ceil(self.Packages_size * r)] = self.Packed_item[idx].view(-1) 188 | if idx == 0: 189 | res = idct_2d(temp) 190 | else: 191 | if res.dim() != 3: 192 | res = res.unsqueeze(0) 193 | res = torch.cat((res, idct_2d(temp).unsqueeze(0)), dim=0) 194 | self.is_Compressed = False 195 | self.Packed_item = res 196 | 197 | def package_en(self, ckks_tools): 198 | matrix = copy.deepcopy(self.Packed_item.cpu().numpy()) 199 | # matrix = np.arange(1, n*n+1).reshape(n, n) 200 | # print(self.Packed_item.shape) 201 | self.en_shape = matrix.shape 202 | # print(self.Packed_item.shape) 203 | # u_transposed_diagonals = get_transposed_diagonals(u_transposed) 204 | # u_transposed_diagonals += 0.00000001 # Prevent is_transparent 205 | # # --------------------------------------------------------- 206 | # plain_u_diag = [] 207 | # for row in u_transposed_diagonals: 208 | # plain_u_diag.append( 209 | # ckks_tools["ckks_encoder"].encode(row, ckks_tools["scale"])) 210 | plain_matrix = ckks_tools["ckks_encoder"].encode( 211 | matrix.flatten(), ckks_tools["ckks_scale"]) 212 | cipher_matrix = ckks_tools["encryptor"].encrypt(plain_matrix) 213 | # --------------------------------------------------------- 214 | self.Packed_item_en = cipher_matrix 215 | self.Packed_item = None 216 | print("3",cipher_matrix.size()) 217 | 218 | def package_de(self, ckks_tools): 219 | p1 = ckks_tools["decryptor"].decrypt(self.Packed_item_en) 220 | vec = ckks_tools["ckks_encoder"].decode(p1) 221 | # --------------------------------------------------------- 222 | self.Packed_item = torch.from_numpy(copy.deepcopy( 223 | vec[:self.en_shape[0]*self.en_shape[1]].reshape(self.en_shape))) 224 | # print(self.Packed_item.shape) 225 | self.Packed_item_en = None 226 | -------------------------------------------------------------------------------- /system/flcore/servers/serverbase.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import os 3 | import numpy as np 4 | import h5py 5 | import copy 6 | 7 | from utils.compresion import * 8 | 9 | from utils.data_utils import read_client_data 10 | 11 | from seal import * 12 | 13 | 14 | def print_parameters(context): 15 | context_data = context.key_context_data() 16 | if context_data.parms().scheme() == scheme_type.bfv: 17 | scheme_name = 'bfv' 18 | elif context_data.parms().scheme() == scheme_type.ckks: 19 | scheme_name = 'ckks' 20 | else: 21 | scheme_name = 'none' 22 | print('/') 23 | print('| Encryption parameters') 24 | print('| scheme: ' + scheme_name) 25 | print( 26 | f'| poly_modulus_degree: {context_data.parms().poly_modulus_degree()}') 27 | coeff_modulus = context_data.parms().coeff_modulus() 28 | coeff_modulus_sum = 0 29 | for j in coeff_modulus: 30 | coeff_modulus_sum += j.bit_count() 31 | print(f'| coeff_modulus size: {coeff_modulus_sum}(', end='') 32 | for i in range(len(coeff_modulus) - 1): 33 | print(f'{coeff_modulus[i].bit_count()} + ', end='') 34 | print(f'{coeff_modulus[-1].bit_count()}) bits') 35 | if context_data.parms().scheme() == scheme_type.bfv: 36 | print( 37 | f'| plain_modulus: {context_data.parms().plain_modulus().value()}') 38 | print('\\') 39 | 40 | 41 | class Server(object): 42 | def __init__(self, args, times): 43 | # Set up the main attributes 44 | self.device = args.device 45 | self.dataset = args.dataset 46 | self.global_rounds = args.global_rounds 47 | self.local_steps = args.local_steps 48 | self.batch_size = args.batch_size 49 | self.learning_rate = args.local_learning_rate 50 | self.global_model = copy.deepcopy(args.model) 51 | self.num_clients = args.num_clients 52 | self.join_ratio = args.join_ratio 53 | self.join_clients = int(self.num_clients * self.join_ratio) 54 | self.algorithm = args.algorithm 55 | self.save_folder_name = args.save_folder_name 56 | self.top_cnt = 100 57 | 58 | self.clients = [] 59 | self.selected_clients = [] 60 | 61 | self.uploaded_weights = [] 62 | self.uploaded_ids = [] 63 | self.uploaded_models = [] 64 | 65 | self.rs_test_acc = [] 66 | self.rs_test_auc = [] 67 | self.rs_train_loss = [] 68 | 69 | self.times = times 70 | self.eval_gap = args.eval_gap 71 | 72 | parms = EncryptionParameters(scheme_type.ckks) 73 | poly_modulus_degree = 8192 74 | parms.set_poly_modulus_degree(poly_modulus_degree) 75 | parms.set_coeff_modulus(CoeffModulus.Create( 76 | poly_modulus_degree, [60, 40, 40, 60])) 77 | scale = 2.0**40 78 | context = SEALContext(parms) 79 | print_parameters(context) 80 | encoder = CKKSEncoder(context) 81 | slot_count = encoder.slot_count() 82 | keygen = KeyGenerator(context) 83 | public_key = keygen.create_public_key() 84 | secret_key = keygen.secret_key() 85 | galois_keys = keygen.create_galois_keys() 86 | encryptor = Encryptor(context, public_key) 87 | evaluator = Evaluator(context) 88 | decryptor = Decryptor(context, secret_key) 89 | self.ckks_tools = { 90 | "ckks_parms": parms, 91 | "ckks_poly_modulus_degree": poly_modulus_degree, 92 | "ckks_scale": scale, 93 | "ckks_context": context, 94 | "ckks_encoder": encoder, 95 | "slot_count": slot_count, 96 | "keygen": keygen, 97 | "public_key": public_key, 98 | "secret_key": secret_key, 99 | "galois_keys": galois_keys, 100 | "encryptor": encryptor, 101 | "evaluator": evaluator, 102 | "decryptor": decryptor 103 | } 104 | 105 | self.r = args.r 106 | self.global_model_c = Packages() 107 | self.global_model_c.pack_up(copy.deepcopy(self.global_model)) 108 | # print("1",self.global_model_c.Packed_item.shape) 109 | self.global_model_c.package_compresion(self.r) 110 | # print("2",self.global_model_c.Packed_item.shape) 111 | self.global_model_c.package_en(self.ckks_tools) 112 | self.init = False 113 | 114 | def set_clients(self, args, clientObj): 115 | for i in range(self.num_clients): 116 | train_data = read_client_data(self.dataset, i, is_train=True) 117 | test_data = read_client_data(self.dataset, i, is_train=False) 118 | client = clientObj(args, 119 | id=i, 120 | train_samples=len(train_data), 121 | test_samples=len(test_data), 122 | ckks=self.ckks_tools,) 123 | self.clients.append(client) 124 | 125 | 126 | def select_clients(self): 127 | selected_clients = list(np.random.choice( 128 | self.clients, self.join_clients, replace=False)) 129 | 130 | return selected_clients 131 | 132 | def send_models(self): 133 | assert (len(self.selected_clients) > 0) 134 | 135 | # if self.init is False: 136 | # global_model_c = Packages() 137 | # global_model_c.pack_up(self.global_model) 138 | # global_model_c.package_compresion(self.r) 139 | # self.init = True 140 | self.global_model_c.package_de(self.ckks_tools) 141 | self.global_model_c.is_Compressed = True 142 | # print(self.global_model_c) 143 | for client in self.selected_clients: 144 | # client.compressed_model = copy.deepcopy(self.global_model_c) 145 | client.compressed_model = self.global_model_c 146 | # client.set_parameters_c(self.global_model) 147 | 148 | def receive_models(self): 149 | assert (len(self.selected_clients) > 0) 150 | 151 | self.uploaded_weights = [] 152 | tot_samples = 0 153 | self.uploaded_ids = [] 154 | self.uploaded_models = [] 155 | for client in self.selected_clients: 156 | self.uploaded_weights.append(client.train_samples) 157 | tot_samples += client.train_samples 158 | self.uploaded_ids.append(client.id) 159 | self.uploaded_models.append(client.compressed_model) 160 | for i, w in enumerate(self.uploaded_weights): 161 | self.uploaded_weights[i] = w / tot_samples 162 | 163 | def receive_models_c(self): 164 | assert (len(self.selected_clients) > 0) 165 | 166 | self.uploaded_weights = [] 167 | tot_samples = 0 168 | self.uploaded_ids = [] 169 | self.uploaded_models = [] 170 | for client in self.selected_clients: 171 | self.uploaded_weights.append(client.train_samples) 172 | tot_samples += client.train_samples 173 | self.uploaded_ids.append(client.id) 174 | # self.uploaded_models.append(copy.deepcopy( 175 | # client.compressed_model.Packed_item)) 176 | self.uploaded_models.append(client.compressed_model.Packed_item_en) 177 | for i, w in enumerate(self.uploaded_weights): 178 | self.uploaded_weights[i] = w / tot_samples 179 | 180 | def aggregate_parameters_c(self): 181 | assert (len(self.uploaded_models) > 0) 182 | 183 | temp = None 184 | for i in range(len(self.uploaded_models)): 185 | if i is 0: 186 | plain_w = self.ckks_tools["ckks_encoder"].encode( 187 | self.uploaded_weights[i], self.ckks_tools["ckks_scale"]) 188 | temp = self.ckks_tools["evaluator"].multiply_plain(self.uploaded_models[i], plain_w) 189 | # temp = self.ckks_tools["evaluator"].rescale_to_next_inplace(temp) 190 | else: 191 | plain_w = self.ckks_tools["ckks_encoder"].encode( 192 | self.uploaded_weights[i], self.ckks_tools["ckks_scale"]) 193 | temp2 = self.ckks_tools["evaluator"].multiply_plain(self.uploaded_models[i], plain_w) 194 | # temp2 = self.ckks_tools["evaluator"].rescale_to_next_inplace(temp2) 195 | temp = self.ckks_tools["evaluator"].add(temp, temp2) 196 | self.global_model_c.Packed_item_en = temp 197 | 198 | # temp = copy.deepcopy(self.uploaded_models[0]) 199 | # for item in self.global_model_c.Packed_item: 200 | # item *= 0 201 | # self.global_model_c *= 0 202 | # temp = self.ckks_tools["evaluator"].multiply(temp, 0) 203 | 204 | # for w, client_model in zip(self.uploaded_weights, self.uploaded_models): 205 | # temp = self.ckks_tools["evaluator"].add( 206 | # temp, self.ckks_tools["evaluator"].multiply(client_model, w)) 207 | # for server_param, client_param in zip(self.global_model_c.Packed_item, client_model.Packed_item): 208 | # server_param += client_param * w 209 | 210 | def aggregate_parameters(self): 211 | assert (len(self.uploaded_models) > 0) 212 | 213 | self.global_model = copy.deepcopy(self.uploaded_models[0]) 214 | for param in self.global_model.parameters(): 215 | param.data.zero_() 216 | 217 | for w, client_model in zip(self.uploaded_weights, self.uploaded_models): 218 | self.add_parameters(w, client_model) 219 | 220 | def add_parameters(self, w, client_model): 221 | for server_param, client_param in zip(self.global_model.parameters(), client_model.parameters()): 222 | server_param.data += client_param.data.clone() * w 223 | 224 | def save_global_model(self): 225 | model_path = os.path.join("models", self.dataset) 226 | if not os.path.exists(model_path): 227 | os.makedirs(model_path) 228 | model_path = os.path.join( 229 | model_path, self.algorithm + "_server" + ".pt") 230 | torch.save(self.global_model, model_path) 231 | 232 | def load_model(self): 233 | model_path = os.path.join("models", self.dataset) 234 | model_path = os.path.join( 235 | model_path, self.algorithm + "_server" + ".pt") 236 | assert (os.path.exists(model_path)) 237 | self.global_model = torch.load(model_path) 238 | 239 | def model_exists(self): 240 | model_path = os.path.join("models", self.dataset) 241 | model_path = os.path.join( 242 | model_path, self.algorithm + "_server" + ".pt") 243 | return os.path.exists(model_path) 244 | 245 | def save_results(self): 246 | algo = self.dataset + "_" + self.algorithm 247 | result_path = "../results/" 248 | if not os.path.exists(result_path): 249 | os.makedirs(result_path) 250 | 251 | if (len(self.rs_test_acc)): 252 | algo = algo + "_" + str(self.times) 253 | file_path = result_path + "{}.h5".format(algo) 254 | print("File path: " + file_path) 255 | 256 | with h5py.File(file_path, 'w') as hf: 257 | hf.create_dataset('rs_test_acc', data=self.rs_test_acc) 258 | hf.create_dataset('rs_test_auc', data=self.rs_test_auc) 259 | hf.create_dataset('rs_train_loss', data=self.rs_train_loss) 260 | 261 | def save_item(self, item, item_name): 262 | if not os.path.exists(self.save_folder_name): 263 | os.makedirs(self.save_folder_name) 264 | torch.save(item, os.path.join( 265 | self.save_folder_name, "server_" + item_name + ".pt")) 266 | 267 | def load_item(self, item_name): 268 | return torch.load(os.path.join(self.save_folder_name, "server_" + item_name + ".pt")) 269 | 270 | def test_metrics(self): 271 | num_samples = [] 272 | tot_correct = [] 273 | tot_auc = [] 274 | for c in self.selected_clients: 275 | ct, ns, auc = c.test_metrics() 276 | tot_correct.append(ct * 1.0) 277 | tot_auc.append(auc * ns) 278 | num_samples.append(ns) 279 | 280 | ids = [c.id for c in self.selected_clients] 281 | 282 | return ids, num_samples, tot_correct, tot_auc 283 | 284 | def train_metrics(self): 285 | num_samples = [] 286 | losses = [] 287 | for c in self.selected_clients: 288 | cl, ns = c.train_metrics() 289 | num_samples.append(ns) 290 | losses.append(cl * 1.0) 291 | 292 | ids = [c.id for c in self.selected_clients] 293 | 294 | return ids, num_samples, losses 295 | 296 | # evaluate selected clients 297 | def evaluate(self, acc=None, loss=None): 298 | stats = self.test_metrics() 299 | stats_train = self.train_metrics() 300 | 301 | test_acc = sum(stats[2]) * 1.0 / sum(stats[1]) 302 | test_auc = sum(stats[3]) * 1.0 / sum(stats[1]) 303 | train_loss = sum(stats_train[2]) * 1.0 / sum(stats_train[1]) 304 | accs = [a / n for a, n in zip(stats[2], stats[1])] 305 | aucs = [a / n for a, n in zip(stats[3], stats[1])] 306 | 307 | if acc == None: 308 | self.rs_test_acc.append(test_acc) 309 | else: 310 | acc.append(test_acc) 311 | 312 | if loss == None: 313 | self.rs_train_loss.append(train_loss) 314 | else: 315 | loss.append(train_loss) 316 | 317 | print("Averaged Train Loss: {:.4f}".format(train_loss)) 318 | print("Averaged Test Accurancy: {:.4f}".format(test_acc)) 319 | print("Averaged Test AUC: {:.4f}".format(test_auc)) 320 | # self.print_(test_acc, train_acc, train_loss) 321 | print("Std Test Accurancy: {:.4f}".format(np.std(accs))) 322 | print("Std Test AUC: {:.4f}".format(np.std(aucs))) 323 | 324 | def print_(self, test_acc, test_auc, train_loss): 325 | print("Average Test Accurancy: {:.4f}".format(test_acc)) 326 | print("Average Test AUC: {:.4f}".format(test_auc)) 327 | print("Average Train Loss: {:.4f}".format(train_loss)) 328 | 329 | def check_done(self, acc_lss, top_cnt=None, div_value=None): 330 | for acc_ls in acc_lss: 331 | if top_cnt != None and div_value != None: 332 | find_top = len( 333 | acc_ls) - torch.topk(torch.tensor(acc_ls), 1).indices[0] > top_cnt 334 | find_div = len(acc_ls) > 1 and np.std( 335 | acc_ls[-top_cnt:]) < div_value 336 | if find_top and find_div: 337 | pass 338 | else: 339 | return False 340 | elif top_cnt != None: 341 | find_top = len( 342 | acc_ls) - torch.topk(torch.tensor(acc_ls), 1).indices[0] > top_cnt 343 | if find_top: 344 | pass 345 | else: 346 | return False 347 | elif div_value != None: 348 | find_div = len(acc_ls) > 1 and np.std( 349 | acc_ls[-top_cnt:]) < div_value 350 | if find_div: 351 | pass 352 | else: 353 | return False 354 | else: 355 | raise NotImplementedError 356 | return True 357 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------