├── data └── README.md ├── images └── README.md ├── data_division ├── MNIST │ └── README.md ├── CIFAR10 │ └── README.md └── MobiAct │ └── MobiAct_divisions.py ├── data_resampled └── README.md ├── figures ├── use_case.PNG ├── Global_solution.PNG ├── Global_solution.pdf └── overall_results.PNG ├── partition-reports └── README.md ├── models_saved ├── MNIST │ └── CNNmodel.pt ├── CIFAR10 │ └── CNNmodel.pt └── MobiACT │ └── model_GRU.pt ├── clients_info ├── CIFAR10 │ ├── cores_list.pkl │ ├── names_list.pkl │ ├── bandwidth_list.pkl │ └── frequency_list.pkl └── MobiAct │ ├── cores_list.pkl │ ├── names_list.pkl │ ├── bandwidth_list.pkl │ ├── clients_info.pkl │ ├── frequency_list.pkl │ └── number_samples.pkl ├── models ├── MNIST │ ├── __pycache__ │ │ └── CNN.cpython-311.pyc │ └── CNN.py ├── CIFAR10 │ ├── __pycache__ │ │ ├── CNN.cpython-311.pyc │ │ └── ResNet.cpython-311.pyc │ ├── CNN.py │ └── ResNet.py └── MobiAct │ ├── __pycache__ │ └── GRU.cpython-311.pyc │ ├── GRU.py │ └── LSTM.py ├── RL ├── MLP_RL.py └── DQL.py ├── data_manipulation ├── Subset_dataset.py └── Data_distribution.py ├── prints └── result_plot.py ├── README.md ├── data_preprocessing └── mobiAct_preprocessing.py ├── clientFL └── Client.py └── serverFL ├── Server_FedProx.py ├── Server_FAVOR.py └── Server_FLASHRL.py /data/README.md: -------------------------------------------------------------------------------- 1 | This folder is empty, it will be filled once the datasets are loaded. -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | This folder is empty, it will be filled once saving a printing. -------------------------------------------------------------------------------- /data_division/MNIST/README.md: -------------------------------------------------------------------------------- 1 | This folder is empty it will contain data divisions of MNIST. -------------------------------------------------------------------------------- /data_division/CIFAR10/README.md: -------------------------------------------------------------------------------- 1 | This folder is empty it will contain data divisions of CIFAR-10. -------------------------------------------------------------------------------- /data_resampled/README.md: -------------------------------------------------------------------------------- 1 | This folder is empty due to the MobiAct dataset being a private dataset. -------------------------------------------------------------------------------- /figures/use_case.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/figures/use_case.PNG -------------------------------------------------------------------------------- /partition-reports/README.md: -------------------------------------------------------------------------------- 1 | This folder is empty, it will be filled once running the fedlab framework. -------------------------------------------------------------------------------- /figures/Global_solution.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/figures/Global_solution.PNG -------------------------------------------------------------------------------- /figures/Global_solution.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/figures/Global_solution.pdf -------------------------------------------------------------------------------- /figures/overall_results.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/figures/overall_results.PNG -------------------------------------------------------------------------------- /models_saved/MNIST/CNNmodel.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/models_saved/MNIST/CNNmodel.pt -------------------------------------------------------------------------------- /models_saved/CIFAR10/CNNmodel.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/models_saved/CIFAR10/CNNmodel.pt -------------------------------------------------------------------------------- /models_saved/MobiACT/model_GRU.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/models_saved/MobiACT/model_GRU.pt -------------------------------------------------------------------------------- /clients_info/CIFAR10/cores_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/CIFAR10/cores_list.pkl -------------------------------------------------------------------------------- /clients_info/CIFAR10/names_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/CIFAR10/names_list.pkl -------------------------------------------------------------------------------- /clients_info/MobiAct/cores_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/MobiAct/cores_list.pkl -------------------------------------------------------------------------------- /clients_info/MobiAct/names_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/MobiAct/names_list.pkl -------------------------------------------------------------------------------- /clients_info/CIFAR10/bandwidth_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/CIFAR10/bandwidth_list.pkl -------------------------------------------------------------------------------- /clients_info/CIFAR10/frequency_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/CIFAR10/frequency_list.pkl -------------------------------------------------------------------------------- /clients_info/MobiAct/bandwidth_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/MobiAct/bandwidth_list.pkl -------------------------------------------------------------------------------- /clients_info/MobiAct/clients_info.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/MobiAct/clients_info.pkl -------------------------------------------------------------------------------- /clients_info/MobiAct/frequency_list.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/MobiAct/frequency_list.pkl -------------------------------------------------------------------------------- /clients_info/MobiAct/number_samples.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/clients_info/MobiAct/number_samples.pkl -------------------------------------------------------------------------------- /models/MNIST/__pycache__/CNN.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/models/MNIST/__pycache__/CNN.cpython-311.pyc -------------------------------------------------------------------------------- /models/CIFAR10/__pycache__/CNN.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/models/CIFAR10/__pycache__/CNN.cpython-311.pyc -------------------------------------------------------------------------------- /models/MobiAct/__pycache__/GRU.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/models/MobiAct/__pycache__/GRU.cpython-311.pyc -------------------------------------------------------------------------------- /models/CIFAR10/__pycache__/ResNet.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sofianebouaziz1/FLASH-RL/HEAD/models/CIFAR10/__pycache__/ResNet.cpython-311.pyc -------------------------------------------------------------------------------- /RL/MLP_RL.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch 3 | 4 | class MLP(nn.Module): 5 | # RL module 6 | def __init__(self, input_dim, hidden_dim, output_dim): 7 | super().__init__() 8 | 9 | self.fc1 = nn.Linear(input_dim, hidden_dim) 10 | self.fc2 = nn.Linear(hidden_dim, output_dim) 11 | self.relu = torch.nn.ReLU() 12 | 13 | def forward(self, state): 14 | 15 | x = self.fc1(state) 16 | x = self.relu(x) 17 | 18 | # we dont want to activate it, we need the row numbers 19 | Q_values = self.fc2(x) 20 | 21 | return Q_values -------------------------------------------------------------------------------- /models/MobiAct/GRU.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | class GRU(nn.Module): 6 | def __init__(self, input_dim, hidden_dim, output_dim, n_layers): 7 | super(GRU, self).__init__() 8 | self.hidden_dim = hidden_dim 9 | self.n_layers = n_layers 10 | 11 | self.GRU = nn.GRU(input_size = input_dim,hidden_size = hidden_dim, num_layers = n_layers, batch_first=True) 12 | self.linear1 = nn.Linear(hidden_dim, output_dim) 13 | self.relu = nn.ReLU() 14 | 15 | def forward(self, x): 16 | h0 = self.init_hidden(x) 17 | out, hn = self.GRU(x, h0) 18 | out = self.linear1(self.relu(out[:, -1])) 19 | return out 20 | 21 | def init_hidden(self, x): 22 | h0 = torch.zeros(self.n_layers, x.size(0), self.hidden_dim) 23 | return h0 -------------------------------------------------------------------------------- /data_manipulation/Subset_dataset.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch.utils.data import Dataset 3 | 4 | class Subset_dataset(Dataset): 5 | """ 6 | Sous-ensemble d'un ensemble de données aux indices spécifiés. 7 | 8 | Arguments : 9 | dataset (Ensemble de données) : Le jeu de données complet 10 | indices (séquence) : Indices dans l'ensemble entier sélectionnés pour le sous-ensemble 11 | labels(sequence) : cibles comme requis pour les indices. seront de la même longueur que les indices 12 | """ 13 | def __init__(self, dataset, indices, labels): 14 | self.dataset = torch.utils.data.Subset(dataset, indices) 15 | self.targets = labels 16 | 17 | def __getitem__(self, idx): 18 | image = self.dataset[idx][0] 19 | target = self.targets[idx] 20 | return (image, target) 21 | 22 | def __len__(self): 23 | return len(self.targets) -------------------------------------------------------------------------------- /prints/result_plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import pandas as pd 3 | 4 | class result_plot: 5 | 6 | def __init__(self): 7 | pass 8 | 9 | def assendante_list(self, unelist): 10 | max = 0 11 | cpt = 0 12 | max_list = [] 13 | comm_rond = [] 14 | for i in range(0, len(unelist)): 15 | if(unelist[i] > max): 16 | max = unelist[i] 17 | max_list.append(unelist[i]) 18 | comm_rond.append(cpt) 19 | cpt = cpt + 1 20 | 21 | if(comm_rond[-1] != len(unelist)): 22 | max_list.append(max_list[-1]) 23 | comm_rond.append(len(unelist)) 24 | 25 | return max_list, comm_rond 26 | 27 | def cummulative_list(self, unelist): 28 | cum_list = [0]*len(unelist) 29 | cum_list[0] = unelist[0] 30 | 31 | for i in range(1, len(unelist)): 32 | cum_list[i] = unelist[i] + cum_list[i -1] 33 | 34 | return cum_list 35 | 36 | 37 | -------------------------------------------------------------------------------- /models/MobiAct/LSTM.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | class LSTM(nn.Module): 6 | def __init__(self, input_dim, hidden_dim, n_layers, output_dim): 7 | super(LSTM, self).__init__() 8 | self.hidden_dim = hidden_dim 9 | self.n_layers = n_layers 10 | 11 | self.lstm = nn.LSTM(input_dim, hidden_dim, n_layers, batch_first=True) 12 | # batch_first = true, nos données sont de taille = (batch_size, seq, input_size) 13 | self.linear1 = nn.Linear(hidden_dim, output_dim) 14 | self.relu = nn.ReLU() 15 | self.i = 0 16 | 17 | 18 | def forward(self, x): 19 | h0, c0 = self.init_hidden(x) #initialiser la mémoire pour chaque entrée 20 | out, (hn, cn) = self.lstm(x, (h0, c0)) #appliquer LSTM 21 | # out est de taille (batch, seq_length, hidden_size) 22 | # on veut seulement la classification du dernier timestep dans la sequence -> (batch, hidden_size) 23 | out = self.linear1(self.relu(out[:, -1])) #on applique relu pour le out avant la sortie 24 | return out 25 | 26 | def init_hidden(self, x): 27 | h0 = torch.zeros(self.n_layers, x.size(0), self.hidden_dim) 28 | c0 = torch.zeros(self.n_layers, x.size(0), self.hidden_dim) 29 | return [t for t in (h0, c0)] -------------------------------------------------------------------------------- /models/CIFAR10/CNN.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | class CNN(nn.Module): 6 | def __init__(self, num_channels, out_channels1, out_channels2, num_classes): 7 | 8 | super(CNN, self).__init__() 9 | 10 | self.conv1 = nn.Conv2d(in_channels = num_channels, out_channels = out_channels1, kernel_size=5, stride = 1) 11 | self.maxpool1 = nn.MaxPool2d(kernel_size = 2) 12 | 13 | self.conv2 = nn.Conv2d(in_channels = out_channels1, out_channels = out_channels2, kernel_size=5, stride = 1) 14 | self.maxpool2 = nn.MaxPool2d(kernel_size = 2) 15 | 16 | 17 | self.fc1 = nn.Linear(400, 256) 18 | self.fc2 = nn.Linear(256, num_classes) 19 | 20 | def forward(self,x): 21 | #first Convolutional layer 22 | x = self.conv1(x) 23 | #activation function 24 | x = F.relu(x) 25 | #max pooling 26 | x = self.maxpool1(x) 27 | #first Convolutional layer 28 | x = self.conv2(x) 29 | #activation function 30 | x = F.relu(x) 31 | #max pooling 32 | x = self.maxpool2(x) 33 | #flatten output 34 | x = torch.flatten(x,1) 35 | #fully connected layer 1 36 | x =self.fc1(x) 37 | #activation function 38 | x = F.relu(x) 39 | #fully connected layer 2 40 | x = self.fc2(x) 41 | 42 | return x -------------------------------------------------------------------------------- /models/MNIST/CNN.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | class CNN(nn.Module): 6 | def __init__(self, num_channels, out_channels1, out_channels2, num_classes): 7 | 8 | super(CNN, self).__init__() 9 | 10 | self.conv1 = nn.Conv2d(in_channels = 1, out_channels = out_channels1, kernel_size=5, stride = 1) 11 | self.maxpool1 = nn.MaxPool2d(kernel_size = 2) 12 | 13 | self.conv2 = nn.Conv2d(in_channels = out_channels1, out_channels = out_channels2, kernel_size=5, stride = 1) 14 | self.maxpool2 = nn.MaxPool2d(kernel_size = 2) 15 | 16 | 17 | self.fc1 = nn.Linear(1024, 512) 18 | self.fc2 = nn.Linear(512, num_classes) 19 | 20 | def forward(self,x): 21 | #first Convolutional layers 22 | x = self.conv1(x) 23 | #activation function 24 | x = F.relu(x) 25 | #max pooling 26 | x = self.maxpool1(x) 27 | #first Convolutional layers 28 | x = self.conv2(x) 29 | #activation function 30 | x = F.relu(x) 31 | #max pooling 32 | x = self.maxpool2(x) 33 | #flatten output 34 | x = torch.flatten(x,1) 35 | #fully connected layer 1 36 | x =self.fc1(x) 37 | #activation function 38 | x = F.relu(x) 39 | #fully connected layer 2 40 | x = self.fc2(x) 41 | # get log probabilities 42 | #x = F.log_softmax(x, dim=1) 43 | return x -------------------------------------------------------------------------------- /data_manipulation/Data_distribution.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | import data_manipulation.Subset_dataset as Subset_dataset 4 | from tqdm import tqdm 5 | 6 | class Data_distribution: 7 | 8 | def __init__(self, data_set, num_clients): 9 | """_summary_ 10 | 11 | Args: 12 | data_set (_type_): the dataset we want to distribute 13 | num_clients (_type_): the number of clients of our FL system 14 | """ 15 | self.data_set = data_set 16 | self.num_clients = num_clients 17 | 18 | def print_number_of_client(self): 19 | print(self.num_clients) 20 | 21 | def division_iid(self,dataset = None, num_clients = None, initial = "client"): 22 | """IID distribution of the data 23 | 24 | Args: 25 | initial (str, optional): The start of the name of each client. Defaults to "client". 26 | 27 | Returns: 28 | _type_: dictionary of the form {client_name, data index list) 29 | """ 30 | if (dataset == None): 31 | dataset = self.data_set 32 | num_clients = self.num_clients 33 | 34 | # The division is IID where each client will receive the same amount of data 35 | num_items = int(len(dataset)/num_clients) 36 | 37 | # Dict_user will contain a dictionary of the form {client_name, data index list) 38 | dict_users, all_idxs = {}, np.arange(len(dataset)) 39 | 40 | # The name of the clients 41 | client_names = ['{}_{}'.format(initial, i+1) for i in range(num_clients)] 42 | 43 | for i in tqdm(range(num_clients)): 44 | 45 | idx_clienti = np.random.choice(all_idxs, num_items, replace=False) 46 | labels_clienti = [dataset[i][1] for i in idx_clienti] 47 | 48 | sub_data = Subset_dataset.Subset_dataset(dataset, idx_clienti,labels_clienti) 49 | dict_users[client_names[i]] = sub_data 50 | all_idxs = list(set(all_idxs) - set(idx_clienti)) 51 | 52 | return dict_users 53 | 54 | def division_noniid_custom(self, num_classe_partage, num_intra_clients): 55 | """ 56 | Extract I.I.D. client data from a dataset 57 | return: dict of client data 58 | """ 59 | 60 | all_idxs = np.arange(len(self.data_set)) 61 | dict_users, all_idxs = dict(), np.arange(len(self.data_set)) 62 | labels = np.array(self.data_set.targets) 63 | 64 | idxs_labels = np.vstack((all_idxs, labels)) 65 | idxs_labels = idxs_labels[:,idxs_labels[1,:].argsort()] 66 | 67 | unique_labels = np.unique(np.array(labels)) 68 | # on mélange les labels pour que le résultat finale ne soit pas biasé 69 | random.shuffle(unique_labels) 70 | 71 | all_idxs = idxs_labels[0] 72 | labels = idxs_labels[1] 73 | 74 | sub_lab_list = [unique_labels[i:i + num_classe_partage] for i in range(0, len(unique_labels), num_classe_partage)] 75 | 76 | for item in tqdm.notebook.tqdm(sub_lab_list): 77 | 78 | idx_clienti = np.extract([labels[i] in item for i in range(len(labels))], all_idxs) 79 | labels_clienti = [self.data_set[i][1] for i in idx_clienti] 80 | 81 | 82 | # Creation du nom de chaque client 83 | initial = 'client' 84 | for lab in item: 85 | initial = initial + str(lab) + '_' 86 | 87 | sub_data = Subset_dataset.Subset_dataset(self.data_set, idx_clienti,labels_clienti) 88 | # Pour les clients intraclass la division sera iid donc on utilisera la fonction qu'on a déja implementé et on utilise aussi la classe subset_dataset 89 | intraclass_clients = self.division_iid(dataset = sub_data, num_clients = num_intra_clients, initial = initial) 90 | 91 | dict_users.update(intraclass_clients) 92 | 93 | return dict_users 94 | 95 | def division_noniid_fedlab(self, indx_clients, indx_labels): 96 | """ 97 | Extract I.I.D. client data from a dataset 98 | return: dict of client data 99 | """ 100 | 101 | dict_users, all_idxs = dict(), np.arange(len(self.data_set)) 102 | 103 | num_client = 0 104 | for index_client in tqdm(indx_clients): 105 | 106 | labels_clienti = [self.data_set[i][1] for i in index_client] 107 | 108 | # Creation du nom de chaque client 109 | name_client = 'client' 110 | for lab in indx_labels[num_client]: 111 | name_client = name_client + '_' + str(lab) 112 | name_client = name_client + '_' + str(num_client) 113 | 114 | sub_data = Subset_dataset.Subset_dataset(self.data_set, index_client,labels_clienti) 115 | 116 | dict_users[name_client] = sub_data 117 | num_client = num_client + 1 118 | 119 | return dict_users 120 | 121 | 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FLASH-RL 2 | 3 | ## Description 4 | 5 | 6 |
7 | FLASH-RL (Federated Learning Addressing System and Static Heterogeneity using Reinforcement Learning) is a novel and effective strategy for client selection in Federated Learning (FL) using Reinforcement Learning (RL). It addresses the challenges of system and static heterogeneity by considering the computational capabilities of clients, such as processing power and network connectivity, along with their data characteristics. FLASH-RL introduces a reputation-based utility function to evaluate client contributions based on their current and past performance. Additionally, an adapted algorithm is proposed to expedite the DDQL learning process. 8 |
9 | 10 | [**Features**](#Features) 11 | | [**Tutorial**](https://github.com/Sofianebouaziz1/FLASH-RL/blob/main/tutorial.ipynb) 12 | | [**Structure**](#Code-structure) 13 | | [**Paper**](https://ieeexplore.ieee.org/abstract/document/10361025) 14 | | [**ArXiv**](https://arxiv.org/abs/2311.06917) 15 | | [**References**](#How-to-cite) 16 | 17 | ## Features 18 | 19 | FLASH-RL framework offers the following features: 20 | 21 | * A FL system built from scratch, enabling the simulation of a server and several clients. 22 | * A client selection in FL based on RL and more specifically on an adapted Double Deep Q Learning (DDQL) algorithm. This project marks the first release of a source code for this problematic. 23 | * Multiple data division scenarios created for the MobiAct private dataset. 24 | * Simulation of a heterogeneous environment in terms of the edge hardware equipment. 25 | * Use of a reputation-based utility function to compute the reward attributed to each client. 26 | * An adapted DDQL algorithm that allows multiple actions to be selected. 27 | 28 | 29 | ## Paper 30 | FLASH-RL's paper has been accepted in the 41st IEEE International Conference on Computer Design (ICCD 2023). Please refer to the arXiv version [here] (https://arxiv.org/abs/2311.06917) for the full paper. 31 | 32 | ## Requirements 33 | FLASH-RL has been implemented and tested with the following versions: 34 | - Python (v3.11.3). 35 | - Pytorch (v2.0.0). 36 | - Scikit-Learn (v1.2.2). 37 | - Scipy (v1.10.1). 38 | - FedLab (v1.3.0). 39 | - NumPy (v1.24.3). 40 | 41 | 42 | ## Code structure 43 | 44 | 45 | ``` 46 | FLASH-RL/ 47 | ├── RL/ --- Scripts for the RL module implementantaton. 48 | | ├── DQL.py --- Contains the adapted DDQL implementation. 49 | | └── MLP.py --- The neural network structure used for the DDQL agent. 50 | | 51 | ├── clientFL/ --- Defining the FL client class. 52 | ├── data_division/ --- Creating and storing different non-iid data divisions. 53 | | ├── MobiAct/MobiAct_divisions.py --- Script for creating the MobiAct divisions. 54 | ├── data_manipulation/ --- Enabling the creation of structured non-iid data divisions among the clients for CIFAR-10 and MNIST. 55 | ├── data_preprocessing/ --- Contains a script that pre-processes MobiAct data. 56 | ├── models/ --- Contains the different neural networks used for each dataset. 57 | └──serverFL/ 58 | ├── Server_FAVOR.py --- Contains the FAVOR implementation. 59 | ├── Server_FLASHRL.py --- Contains the **FLASH-RL** implementation. 60 | └── Server_FedProx.py --- Contains the FedProx and FedAVG implementation. 61 | ``` 62 | 63 | ## Experimental results 64 | ### General results 65 | The following table summarizes the results we obtained by comparing FLASH-RL with FedAVG and FAVOR, based on accuracy (%) and latency (s). 66 | 67 | 68 |
69 | 70 |
71 | 72 | This results highlights the effectiveness of our method in striking a desirable balance between maximizing accuracy and minimizing end-to-end latency. 73 | 74 | ### Use case 75 | The following figure shows the progression of the F1 score for the global model and end-to-end latency for each MobiAct division. 76 | 77 |
78 | 79 |
80 | 81 | The Figure highlights FLASH-RL’s ability to find a compromise between maximizing the F1-score of the overall model and minimizing end-to-end latency 82 | 83 | 84 | 85 | ## Authors 86 | 87 | FLASH-RL has been developed by Sofiane Bouaziz, Hadjer Benmeziane, Youcef Imine, Leila Hamdad, Smail Niar and Hamza Ouarnoughi. 88 | 89 | You can contact us by opening a new issue in the repository. 90 | 91 | ## How to cite? 92 | In case you are using FLASH-RL for your research, please consider citing our work: 93 | 94 | ```BibTex 95 | @INPROCEEDINGS{10361025, 96 | author={Bouaziz, Sofiane and Benmeziane, Hadjer and Imine, Youcef and Hamdad, Leila and Niar, Smail and Ouarnoughi, Hamza}, 97 | booktitle={2023 IEEE 41st International Conference on Computer Design (ICCD)}, 98 | title={FLASH-RL: Federated Learning Addressing System and Static Heterogeneity using Reinforcement Learning}, 99 | year={2023}, 100 | volume={}, 101 | number={}, 102 | pages={444-447}, 103 | doi={10.1109/ICCD58817.2023.00074}} 104 | ``` 105 | -------------------------------------------------------------------------------- /data_preprocessing/mobiAct_preprocessing.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import pandas as pd 3 | from scipy import signal 4 | 5 | class mobiAct_preprocessing: 6 | 7 | def __init__(self): 8 | pass 9 | 10 | def under_sampling_ADL(self, data): 11 | 12 | length = len(data) // 4 13 | 14 | result = pd.DataFrame(signal.resample(data['acc_x'][:length], 50)) 15 | result.columns = ['acc_x'] 16 | 17 | result['acc_y'] = pd.DataFrame(signal.resample(data['acc_y'][:length], 50)) 18 | 19 | result['acc_z'] = pd.DataFrame(signal.resample(data['acc_z'][:length], 50)) 20 | 21 | result.index = list(range(0, 50)) 22 | 23 | result['label'] = 0 24 | 25 | return result 26 | 27 | def under_sampling_fall(self, data): 28 | 29 | std_length = len(data[data['label']=='STD']) 30 | fall_length = len(data[data['label']=="FOL"]) 31 | 32 | fall_data = data[std_length : std_length + fall_length] 33 | df = fall_data[['acc_x', 'acc_y', 'acc_z']] 34 | 35 | n = 10 36 | resampling_size = 30 37 | 38 | # For few cases where the length of STD is shorter than 10 39 | if std_length >= n: 40 | # 10 samples from STD + FALL resampled into 30 samples + 10 samples from LYI 41 | # acc_x 42 | std_x = pd.DataFrame(data['acc_x'][std_length - n : std_length]) 43 | fall_x = pd.DataFrame(signal.resample(df['acc_x'], resampling_size)) 44 | fall_x.columns = ['acc_x'] 45 | lyi_x = pd.DataFrame(data['acc_x'][std_length + fall_length : std_length + fall_length + n]) 46 | result = pd.concat([std_x, fall_x, lyi_x], axis=0) 47 | 48 | # acc_y 49 | std_y = pd.DataFrame(data['acc_y'][std_length - n : std_length]) 50 | fall_y = pd.DataFrame(signal.resample(df['acc_y'], resampling_size)) 51 | fall_y.columns = ['acc_y'] 52 | lyi_y = pd.DataFrame(data['acc_y'][std_length + fall_length : std_length + fall_length + n]) 53 | result['acc_y'] = pd.concat([std_y, fall_y, lyi_y]) 54 | 55 | # acc_z 56 | std_z = pd.DataFrame(data['acc_z'][std_length - n : std_length]) 57 | fall_z = pd.DataFrame(signal.resample(df['acc_z'], resampling_size)) 58 | fall_z.columns = ['acc_z'] 59 | lyi_z = pd.DataFrame(data['acc_z'][std_length + fall_length : std_length + fall_length + n]) 60 | result['acc_z'] = pd.concat([std_z, fall_z, lyi_z]) 61 | 62 | # newly indexing 63 | result.index = list(range(0, len(result))) 64 | 65 | result.loc[0:10, 'label'] = "STD" 66 | result.loc[10:40, 'label'] = "FOL" #initialzing for label 67 | result.loc[40:50, 'label'] = "LYI" 68 | #result.loc[n: n + resampling_size - 1, 'label'] = 1 # labelling as 'FALL' = 1 69 | 70 | # If the length of STD is shorter than 10 71 | else: 72 | # STD + FALL resampled into 30 samples + 10 samples from LYI 73 | # acc_x 74 | std_x = pd.DataFrame(data['acc_x'][: std_length]) 75 | fall_x = pd.DataFrame(signal.resample(df['acc_x'], resampling_size)) 76 | fall_x.columns = ['acc_x'] 77 | lyi_x = pd.DataFrame(data['acc_x'][fall_length : fall_length + n + (n - std_length)]) 78 | result = pd.concat([std_x, fall_x, lyi_x], axis=0) 79 | 80 | #acc_y 81 | std_y = pd.DataFrame(data['acc_y'][: std_length]) 82 | fall_y = pd.DataFrame(signal.resample(df['acc_y'], resampling_size)) 83 | fall_y.columns = ['acc_y'] 84 | lyi_y = pd.DataFrame(data['acc_y'][fall_length : fall_length + n + (n - std_length)]) 85 | result['acc_y'] = pd.concat([std_y, fall_y, lyi_y]) 86 | 87 | #acc_z 88 | std_z = pd.DataFrame(data['acc_z'][: std_length]) 89 | fall_z = pd.DataFrame(signal.resample(df['acc_z'], resampling_size)) 90 | fall_z.columns = ['acc_z'] 91 | lyi_z = pd.DataFrame(data['acc_z'][fall_length : fall_length + n + (n - std_length)]) 92 | result['acc_z'] = pd.concat([std_z, fall_z, lyi_z]) 93 | 94 | #newly indexing 95 | result.index = list(range(0, len(result))) 96 | 97 | result['label'] = 0 #initialzing for label 98 | result.loc[std_length:std_length + resampling_size - 1, 'label'] = 1 # labelling as 'FALL' = 1 99 | 100 | return result 101 | 102 | 103 | def sliding_window_fall(self, data, n) : 104 | result = [] 105 | 106 | for i in range(0, len(data) - n): 107 | 108 | temp = list() 109 | 110 | for x, y, z, label in zip(data['acc_x'][i : i + n], data['acc_y'][i : i + n], data['acc_z'][i : i + n], data['label'][i: i + n]): 111 | temp.append([x]+[y]+[z]+[label]) 112 | 113 | example_df = pd.DataFrame(temp) # Transform it to pd in order to be saved as csv file 114 | example_df.columns = ["Accelerometer_x", "Accelerometer_y", "Accelerometer_z", "Label"] # rename the columns 115 | result.append(example_df) 116 | 117 | return result -------------------------------------------------------------------------------- /models/CIFAR10/ResNet.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Properly implemented ResNet-s for CIFAR10 as described in paper [1]. 3 | The implementation and structure of this file is hugely influenced by [2] 4 | which is implemented for ImageNet and doesn't have option A for identity. 5 | Moreover, most of the implementations on the web is copy-paste from 6 | torchvision's resnet and has wrong number of params. 7 | Proper ResNet-s for CIFAR10 (for fair comparision and etc.) has following 8 | number of layers and parameters: 9 | name | layers | params 10 | ResNet20 | 20 | 0.27M 11 | ResNet32 | 32 | 0.46M 12 | ResNet44 | 44 | 0.66M 13 | ResNet56 | 56 | 0.85M 14 | ResNet110 | 110 | 1.7M 15 | ResNet1202| 1202 | 19.4m 16 | which this implementation indeed has. 17 | Reference: 18 | [1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun 19 | Deep Residual Learning for Image Recognition. arXiv:1512.03385 20 | [2] https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py 21 | If you use this implementation in you work, please don't forget to mention the 22 | author, Yerlan Idelbayev. 23 | ''' 24 | import torch 25 | import torch.nn as nn 26 | import torch.nn.functional as F 27 | import torch.nn.init as init 28 | 29 | from torch.autograd import Variable 30 | 31 | __all__ = ['ResNet', 'resnet20', 'resnet32', 'resnet44', 'resnet56', 'resnet110', 'resnet1202'] 32 | 33 | def _weights_init(m): 34 | classname = m.__class__.__name__ 35 | #print(classname) 36 | if isinstance(m, nn.Linear) or isinstance(m, nn.Conv2d): 37 | init.kaiming_normal_(m.weight) 38 | 39 | class LambdaLayer(nn.Module): 40 | def __init__(self, lambd): 41 | super(LambdaLayer, self).__init__() 42 | self.lambd = lambd 43 | 44 | def forward(self, x): 45 | return self.lambd(x) 46 | 47 | 48 | class BasicBlock(nn.Module): 49 | expansion = 1 50 | 51 | def __init__(self, in_planes, planes, stride=1, option='A'): 52 | super(BasicBlock, self).__init__() 53 | self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) 54 | self.bn1 = nn.BatchNorm2d(planes) 55 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) 56 | self.bn2 = nn.BatchNorm2d(planes) 57 | 58 | self.shortcut = nn.Sequential() 59 | if stride != 1 or in_planes != planes: 60 | if option == 'A': 61 | """ 62 | For CIFAR10 ResNet paper uses option A. 63 | """ 64 | self.shortcut = LambdaLayer(lambda x: 65 | F.pad(x[:, :, ::2, ::2], (0, 0, 0, 0, planes//4, planes//4), "constant", 0)) 66 | elif option == 'B': 67 | self.shortcut = nn.Sequential( 68 | nn.Conv2d(in_planes, self.expansion * planes, kernel_size=1, stride=stride, bias=False), 69 | nn.BatchNorm2d(self.expansion * planes) 70 | ) 71 | 72 | def forward(self, x): 73 | out = F.relu(self.bn1(self.conv1(x))) 74 | out = self.bn2(self.conv2(out)) 75 | out += self.shortcut(x) 76 | out = F.relu(out) 77 | return out 78 | 79 | 80 | class ResNet(nn.Module): 81 | def __init__(self, block, num_blocks, num_classes=10): 82 | super(ResNet, self).__init__() 83 | self.in_planes = 16 84 | 85 | self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1, bias=False) 86 | self.bn1 = nn.BatchNorm2d(16) 87 | self.layer1 = self._make_layer(block, 16, num_blocks[0], stride=1) 88 | self.layer2 = self._make_layer(block, 32, num_blocks[1], stride=2) 89 | self.layer3 = self._make_layer(block, 64, num_blocks[2], stride=2) 90 | self.linear = nn.Linear(64, num_classes) 91 | 92 | self.apply(_weights_init) 93 | 94 | def _make_layer(self, block, planes, num_blocks, stride): 95 | strides = [stride] + [1]*(num_blocks-1) 96 | layers = [] 97 | for stride in strides: 98 | layers.append(block(self.in_planes, planes, stride)) 99 | self.in_planes = planes * block.expansion 100 | 101 | return nn.Sequential(*layers) 102 | 103 | def forward(self, x): 104 | out = F.relu(self.bn1(self.conv1(x))) 105 | out = self.layer1(out) 106 | out = self.layer2(out) 107 | out = self.layer3(out) 108 | out = F.avg_pool2d(out, out.size()[3]) 109 | out = out.view(out.size(0), -1) 110 | out = self.linear(out) 111 | return out 112 | 113 | 114 | def resnet20(): 115 | return ResNet(BasicBlock, [3, 3, 3]) 116 | 117 | 118 | def resnet32(): 119 | return ResNet(BasicBlock, [5, 5, 5]) 120 | 121 | 122 | def resnet44(): 123 | return ResNet(BasicBlock, [7, 7, 7]) 124 | 125 | 126 | def resnet56(): 127 | return ResNet(BasicBlock, [9, 9, 9]) 128 | 129 | 130 | def resnet110(): 131 | return ResNet(BasicBlock, [18, 18, 18]) 132 | 133 | 134 | def resnet1202(): 135 | return ResNet(BasicBlock, [200, 200, 200]) 136 | 137 | 138 | def test(net): 139 | import numpy as np 140 | total_params = 0 141 | 142 | for x in filter(lambda p: p.requires_grad, net.parameters()): 143 | total_params += np.prod(x.data.numpy().shape) 144 | print("Total number of params", total_params) 145 | print("Total layers", len(list(filter(lambda p: p.requires_grad and len(p.data.size())>1, net.parameters())))) 146 | 147 | 148 | if __name__ == "__main__": 149 | for net_name in __all__: 150 | if net_name.startswith('resnet'): 151 | print(net_name) 152 | test(globals()[net_name]()) 153 | print() -------------------------------------------------------------------------------- /clientFL/Client.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch.utils.data import DataLoader 3 | import copy 4 | from torch.nn import functional as F 5 | import time 6 | 7 | class Client(object): 8 | 9 | def __init__(self, client_name, local_dataset, local_model, numbercores , frequency, bandwidth, loss, B, learning_rate, momentum): 10 | """ 11 | Initializes a client for FL. 12 | 13 | Parameters: 14 | - client_name (str): Name of the client. 15 | - local_dataset: The local dataset for the client. 16 | - local_model: The local model for the client. 17 | - numbercores (int): Number of cores available on the client. 18 | - frequency: Distribution of frequency. 19 | - bandwidth: Distribution of bandwidth. 20 | - loss: Loss function used for training. 21 | - B (int): Size of a batch. 22 | - learning_rat (float) e: Learning rate for the optimizer. 23 | - momentum (float): Momentum for the optimizer. 24 | """ 25 | 26 | 27 | # name of the client 28 | self.client_name = client_name 29 | # number of cores 30 | self.numbercores = numbercores 31 | # the distribution of the frequency 32 | self.frequency = frequency 33 | # the distribution of the bandwidth 34 | self.bandwidth = bandwidth 35 | # size of a batch 36 | self.B = B 37 | # local data recieved from server 38 | self.local_data = DataLoader(local_dataset, batch_size= B, shuffle=True) 39 | # local model 40 | self.local_model = copy.deepcopy(local_model) 41 | # loss function we are using 42 | self.loss_func = loss 43 | # the optimizer 44 | self.optimizer = torch.optim.SGD(self.local_model.parameters(), lr= learning_rate , momentum = momentum) 45 | 46 | 47 | 48 | def set_weights(self, global_parameters): 49 | """ 50 | Set the weights of the local model using the global model's parameters. 51 | 52 | Parameters: 53 | - global_parameters: The global model parameters recieved from the server. 54 | """ 55 | self.local_model.load_state_dict(global_parameters) 56 | 57 | 58 | def train(self, global_parameters, E, mu, type_data, verbos=0): 59 | """ 60 | Train the local model based on the specified type of data. 61 | 62 | Parameters: 63 | - global_parameters: The global model parameters. 64 | - E (int): Local number of epoch. 65 | - mu: FedProx parameter. 66 | - type_data (str): Type of data, e.g., "Fall" or other. 67 | - verbos (int): Verbosity level for printing training information (default is 0). 68 | 69 | Returns: 70 | - The result of the training. 71 | """ 72 | if type_data == "Fall": 73 | return self.train_falldetection(global_parameters, E, mu, verbos) 74 | else: 75 | return self.train_others(global_parameters, E, mu, verbos) 76 | 77 | 78 | 79 | def train_falldetection(self, global_parameters, E, mu, verbos): 80 | """ 81 | Train the local model for fall detection. 82 | 83 | Parameters: 84 | - global_parameters: The global model parameters. 85 | - E (int): Local number of epoch. 86 | - mu: FedProx parameter. 87 | - verbos (int): Verbosity level for printing training information. 88 | 89 | Returns: 90 | - A deep copy of the state dictionary of the trained local model. 91 | """ 92 | 93 | 94 | # Initialize local model parameters by global_parameters 95 | self.local_model.load_state_dict(global_parameters) 96 | 97 | # Start local training 98 | self.local_model.train() 99 | 100 | for iter in range(E): 101 | if (verbos == 1) : 102 | print("Client : ",self.client_name," Iteration :",iter+1) 103 | 104 | for idx, (X_batch, y_batch) in enumerate(self.local_data): 105 | # Initialize the gradients to 0 106 | self.local_model.zero_grad() 107 | out = self.local_model(X_batch.float()) 108 | loss = self.loss_func(out, y_batch) 109 | # The addition of the term proximal 110 | 111 | if (mu != 0 and iter > 0): 112 | for w, w_t in zip(self.local_model.parameters(), global_parameters.values()): 113 | loss += mu / 2. * torch.pow(torch.norm(w.data - w_t.data), 2) 114 | 115 | # Calculation of gradients 116 | loss.backward() 117 | 118 | # Update of the parameters 119 | self.optimizer.step() 120 | 121 | 122 | return copy.deepcopy(self.local_model.state_dict()) 123 | 124 | def train_others(self, global_parameters, E, mu, verbos): 125 | """ 126 | Train the local model for a task other than fall detection. 127 | 128 | Parameters: 129 | - global_parameters: The global model parameters. 130 | - E (int): Local number of epoch. 131 | - mu: FedProx parameter. 132 | - verbos (int): Verbosity level for printing training information. 133 | 134 | Returns: 135 | - A deep copy of the state dictionary of the trained local model. 136 | """ 137 | 138 | # Initialize local model parameters by global_parameters 139 | self.local_model.load_state_dict(global_parameters) 140 | 141 | # Start local training 142 | self.local_model.train() 143 | 144 | for iter in range(E): 145 | if (verbos == 1) : 146 | print("Client : ",self.client_name," Iteration :",iter+1) 147 | 148 | for images, labels in self.local_data: 149 | # Initialize the gradients to 0 150 | self.local_model.zero_grad() 151 | # Probability calculation for batch i images 152 | log_probs = self.local_model(images) 153 | # Loss calculation 154 | loss = self.loss_func(log_probs, labels) 155 | 156 | # The addition of the term proximal 157 | if (mu != 0 and iter > 0): 158 | for w, w_t in zip(self.local_model.parameters(), global_parameters.values()): 159 | loss += mu / 2. * torch.pow(torch.norm(w.data - w_t.data), 2) 160 | 161 | # Calculation of gradients 162 | loss.backward() 163 | 164 | # Update of the parameters 165 | self.optimizer.step() 166 | 167 | return copy.deepcopy(self.local_model.state_dict()) 168 | 169 | 170 | def get_size(self): 171 | """ 172 | Get the size of the local dataset. 173 | 174 | Returns: 175 | - The size of the local dataset. 176 | """ 177 | return len(self.local_data) 178 | 179 | def get_model(self): 180 | """ 181 | Get the local model. 182 | 183 | Returns: 184 | - The local model. 185 | """ 186 | return self.local_model 187 | 188 | def delete_model(self): 189 | """ 190 | Delete the local model to free up resources. 191 | """ 192 | del self.local_model 193 | -------------------------------------------------------------------------------- /RL/DQL.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from collections import deque 3 | import torch.nn as nn 4 | import RL.MLP_RL as MLP_RL 5 | import copy 6 | import numpy as np 7 | import random 8 | import torch.nn.functional as F 9 | 10 | class DQL: 11 | def __init__(self, state_size, action_size, batch_size, learning_rate = 0.01 , gamma = 0.9 , epsilon = 0.9, update_rate = 10, flag = False): 12 | # define the state size 13 | self.state_size = state_size # Number of weights (PCA) * (N+1) 14 | 15 | #define the action size 16 | self.action_size = action_size # Number of clients 17 | 18 | # define the experience replay 19 | self.replay_buffer = deque(maxlen=1000) 20 | 21 | # define the batch size 22 | self.batch_size = batch_size 23 | 24 | #define the discount factor 25 | self.gamma = gamma 26 | 27 | #define the epsilon value 28 | if (flag == False) : 29 | self.epsilon = epsilon 30 | else: 31 | self.epsilon = 0.2 32 | 33 | self.epsilon_end = 0.2 34 | self.epsilon_delta = (self.epsilon - self.epsilon_end) / 500 35 | # Update target network rate 36 | self.update_rate = update_rate 37 | 38 | # loss function, we use MSE loss function in DQL 39 | self.loss_func = nn.MSELoss() 40 | 41 | # fix the seed 42 | #self.seed = seed 43 | 44 | # learning rate 45 | self.learning_rate = learning_rate 46 | 47 | #define the main network 48 | self.main_network = MLP_RL.MLP(self.state_size, 512, self.action_size) 49 | 50 | # optimizer 51 | #self.optimizer = torch.optim.SGD(self.main_network.parameters(), lr= learning_rate , momentum = 0.99) 52 | self.optimizer = torch.optim.Adam(self.main_network.parameters(), lr = self.learning_rate) 53 | 54 | #define the target 55 | self.target_network = MLP_RL.MLP(self.state_size, 512, self.action_size) 56 | 57 | #copy the weights of the main network to the target network 58 | self.target_network.load_state_dict(copy.deepcopy(self.main_network.state_dict())) 59 | 60 | 61 | def set_epsilon(self): 62 | self.epsilon = self.epsilon - self.epsilon_delta 63 | if (self.epsilon < self.epsilon_end): 64 | self.epsilon = 0.2 65 | # When we DQN by randomly sampling a minibatch of transitions from the 66 | #replay buffer. So, we define a function called store_transition which stores the transition information 67 | #into the replay buffer 68 | 69 | def store_transistion(self, state, action, reward, next_state, done): 70 | self.replay_buffer.append((state, action, reward, next_state, done)) 71 | 72 | 73 | def multiaction_selection(self, state, C, comm_round, mode): 74 | if (mode == "Mode1"): 75 | return self.epsilon_greedy(state, C, comm_round) 76 | else: 77 | return self.multinomial_selection(state, C, comm_round) 78 | 79 | 80 | # the selection stratey used in our solution 81 | def epsilon_greedy(self, state, C, comm_round): 82 | m = int(max(C*self.action_size, 1)) 83 | 84 | self.main_network.eval() 85 | Q_values = self.main_network.forward(state.unsqueeze(0)) 86 | 87 | # action_top_c will contain the indexes of the best top C actions for state 88 | 89 | actions_top_c = (torch.topk(Q_values[0], m).indices).tolist() 90 | # a subtraction to get the non-greedy shares 91 | actions_possible = list(set(np.arange(self.action_size))) 92 | 93 | clients_selected = [] 94 | 95 | #print("the epsilon value is : ", self.epsilon) 96 | 97 | for step in range(m): 98 | if random.uniform(0,1) < self.epsilon: 99 | client = random.choice(actions_possible) 100 | clients_selected.append(client) 101 | actions_possible.remove(client) 102 | if client in actions_top_c: 103 | actions_top_c.remove(client) 104 | else : 105 | clients_selected.append(actions_top_c[0]) 106 | actions_possible.remove(actions_top_c[0]) 107 | actions_top_c.remove(actions_top_c[0]) 108 | 109 | self.set_epsilon() 110 | 111 | clients_selected.sort() 112 | 113 | return clients_selected 114 | 115 | 116 | # the selection strategy used in FedDRL 117 | def multinomial_selection(self, state, C, comm_round): 118 | m = int(max(C*self.action_size, 1)) 119 | 120 | self.main_network.eval() 121 | Q_values = self.main_network.forward(state.unsqueeze(0)) 122 | #actions_top_c = (torch.topk(Q_values[0], m).indices).tolist() 123 | 124 | 125 | actions_probs = F.softmax(Q_values, dim=1) 126 | clients_selected = (actions_probs.multinomial(num_samples= m).tolist())[0] 127 | return clients_selected 128 | 129 | def train(self, comm_round, mode): 130 | if (mode == "Mode1"): 131 | return self.trainV1_0(comm_round) 132 | else: 133 | return self.train_V2(comm_round) 134 | 135 | 136 | def trainV1_0(self, comm_round): 137 | #sample a mini batch of transition from the replay buffer 138 | if (len(self.replay_buffer) < self.batch_size): 139 | minibatch = random.sample(self.replay_buffer, len(self.replay_buffer)) 140 | else: 141 | minibatch = random.sample(self.replay_buffer, self.batch_size) 142 | 143 | # get the index of the batch 144 | batch_index = range(0, len(minibatch)) 145 | # get the states of the minibatch 146 | states_minibatch = torch.cat([sample[0].unsqueeze(0) for sample in minibatch], dim=0) 147 | 148 | # get the actions taked in each state of the minibatch 149 | actions_minibatch = torch.tensor(np.array([np.where(sample[1] == 1)[0] for sample in minibatch])) 150 | 151 | # get the reward recieved for each action in the minibatch 152 | rewards_minibatch = [sample[2] for sample in minibatch] 153 | # get the next states 154 | nextstates_minibatch = torch.cat([sample[3].unsqueeze(0) for sample in minibatch], dim=0) 155 | # get the boolean that tell us if the next state is terminal or not 156 | dones_minibatch = [sample[4] for sample in minibatch] 157 | 158 | self.main_network.eval() 159 | self.target_network.eval() 160 | 161 | # get Q values of each selected action in the samples of the batch 162 | Q_values = self.main_network.forward(states_minibatch) 163 | # Get the Q values of next state 164 | Q_values_next = self.target_network.forward(nextstates_minibatch) 165 | 166 | # Terminal state to 0 167 | Q_values_next = Q_values_next.clone() 168 | Q_values_next[dones_minibatch] = 0 169 | 170 | transformed_action = list(zip(*actions_minibatch)) 171 | transformed_reward = list(zip(*rewards_minibatch)) 172 | 173 | new_network = copy.deepcopy(self.main_network) 174 | 175 | list_loss = [] 176 | for i in range(0, 5): 177 | # Get the updated values (True value) 178 | Q_target = Q_values.clone() 179 | 180 | Q_target[batch_index, transformed_action[i]] = torch.tensor(transformed_reward[i], dtype=torch.float) + self.gamma *Q_values_next[batch_index, torch.argmax(Q_values, dim = 1)] 181 | 182 | # Train the main network 183 | self.main_network.train() 184 | 185 | # Initialize the gradients to 0 186 | self.main_network.zero_grad() 187 | 188 | # Loss calculation 189 | loss = self.loss_func(Q_target.double(), Q_values.double()) 190 | #print("loss of the DQL network is : ", loss.detach().item()) 191 | list_loss.append(loss.detach().item()) 192 | 193 | 194 | # Calculation of parameter gradients 195 | loss.backward() 196 | 197 | # Update of the parameters 198 | self.optimizer.step() 199 | 200 | Q_values = new_network.forward(states_minibatch) 201 | Q_values_next = self.target_network.forward(nextstates_minibatch) 202 | # Terminal state to 0 203 | Q_values_next = Q_values_next.clone() 204 | Q_values_next[dones_minibatch] = 0 205 | 206 | 207 | #self.set_epsilon() 208 | #print(list_loss) 209 | return list_loss 210 | #train the network 211 | 212 | 213 | def train_V1(self, comm_round): 214 | 215 | #sample a mini batch of transition from the replay buffer 216 | #sample a mini batch of transition from the replay buffer 217 | if (len(self.replay_buffer) < self.batch_size): 218 | minibatch = random.sample(self.replay_buffer, len(self.replay_buffer)) 219 | else: 220 | minibatch = random.sample(self.replay_buffer, self.batch_size) 221 | 222 | # get the index of the batch 223 | batch_index = range(0, len(minibatch)) 224 | 225 | 226 | # get the states of the minibatch 227 | states_minibatch = torch.cat([sample[0].unsqueeze(0) for sample in minibatch], dim=0) 228 | # get the actions taked in each state of the minibatch 229 | actions_minibatch = torch.tensor(np.array([np.where(sample[1] == 1)[0] for sample in minibatch])) 230 | # get the reward recieved for each action in the minibatch 231 | rewards_minibatch = [sample[2] for sample in minibatch] 232 | # get the next states 233 | nextstates_minibatch = torch.cat([sample[3].unsqueeze(0) for sample in minibatch], dim=0) 234 | # get the boolean that tell us if the next state is terminal or not 235 | dones_minibatch = [sample[4] for sample in minibatch] 236 | 237 | self.main_network.eval() 238 | self.target_network.eval() 239 | 240 | # get Q values of each selected action in the samples of the batch 241 | Q_values_forupdate = self.main_network.forward(states_minibatch) 242 | Q_values = self.main_network.forward(states_minibatch)[torch.tensor(batch_index)[:, None], actions_minibatch] 243 | # Get the Q values of next state 244 | Q_values_next = self.target_network.forward(nextstates_minibatch) 245 | 246 | # Terminal state to 0 247 | Q_values_next = Q_values_next.clone() 248 | Q_values_next[dones_minibatch] = 0 249 | 250 | 251 | # Get the updated values (True value) 252 | #Q_target = torch.tensor(rewards_minibatch) + self.gamma * torch.max(Q_values_next, dim = 1).values.unsqueeze(1) 253 | 254 | Q_target = torch.tensor(rewards_minibatch) + self.gamma *Q_values_next[batch_index, torch.argmax(Q_values_forupdate, dim = 1)].unsqueeze(1) 255 | 256 | 257 | # we will generate the same Q_target for all chosen actions : 258 | #Q_target = Q_target[:, None].expand(-1, len(actions_minibatch[0])) 259 | # Train the main network 260 | self.main_network.train() 261 | 262 | # Initialize the gradients to 0 263 | self.main_network.zero_grad() 264 | 265 | 266 | # Loss calculation 267 | loss = self.loss_func(Q_target.double(), Q_values.double()) 268 | #print("loss of the DQL network is : ", loss.detach().item()) 269 | 270 | # Calculation of parameter gradients 271 | loss.backward() 272 | 273 | # Update of the parameters 274 | self.optimizer.step() 275 | 276 | def train_V2(self, comm_round): 277 | 278 | #sample a mini batch of transition from the replay buffer 279 | if (len(self.replay_buffer) < self.batch_size): 280 | minibatch = random.sample(self.replay_buffer, len(self.replay_buffer)) 281 | else: 282 | minibatch = random.sample(self.replay_buffer, self.batch_size) 283 | 284 | # get the index of the batch 285 | batch_index = range(0, len(minibatch)) 286 | # get the states of the minibatch 287 | states_minibatch = torch.cat([sample[0].unsqueeze(0) for sample in minibatch], dim=0) 288 | 289 | # get the actions taked in each state of the minibatch 290 | actions_minibatch = [sample[1] for sample in minibatch] 291 | # get the reward recieved for each action in the minibatch 292 | rewards_minibatch = [sample[2] for sample in minibatch] 293 | # get the next states 294 | nextstates_minibatch = torch.cat([sample[3].unsqueeze(0) for sample in minibatch], dim=0) 295 | # get the boolean that tell us if the next state is terminal or not 296 | dones_minibatch = [sample[4] for sample in minibatch] 297 | 298 | self.main_network.eval() 299 | self.target_network.eval() 300 | 301 | # get Q values of each selected action in the samples of the batch 302 | Q_values_forupdate = self.main_network.forward(states_minibatch) 303 | Q_values = self.main_network.forward(states_minibatch)[batch_index, actions_minibatch] 304 | # Get the Q values of next state 305 | Q_values_next = self.target_network.forward(nextstates_minibatch) 306 | 307 | # Terminal state to 0 308 | Q_values_next = Q_values_next.clone() 309 | Q_values_next[dones_minibatch] = 0 310 | 311 | # Get the updated values (True value) 312 | #Q_target = torch.tensor(rewards_minibatch) + self.gamma * torch.max(Q_values_next, dim = 1).values 313 | 314 | #print("Q_target 1 are : ", Q_target) 315 | #print("the max is : ", torch.argmax(Q_values)) 316 | #print("The Q values : ", Q_values_forupdate) 317 | #print("The max is : ", torch.argmax(Q_values_forupdate, dim = 1)) 318 | 319 | Q_target = torch.tensor(rewards_minibatch) + self.gamma *Q_values_next[batch_index, torch.argmax(Q_values_forupdate, dim = 1)] 320 | #print("Q_target 2 are : ", Q_target) 321 | 322 | # Train the main network 323 | self.main_network.train() 324 | 325 | # Initialize the gradients to 0 326 | self.main_network.zero_grad() 327 | 328 | # Loss calculation 329 | loss = self.loss_func(Q_target.double(), Q_values.double()) 330 | print("loss of the DQL network is : ", loss.detach().item()) 331 | 332 | # Calculation of parameter gradients 333 | loss.backward() 334 | 335 | # Update of the parameters 336 | self.optimizer.step() 337 | 338 | return loss.detach().item() 339 | 340 | 341 | #train the network 342 | def train_V3(self, comm_round): 343 | 344 | #sample a mini batch of transition from the replay buffer 345 | #sample a mini batch of transition from the replay buffer 346 | if (len(self.replay_buffer) < self.batch_size): 347 | minibatch = random.sample(self.replay_buffer, len(self.replay_buffer)) 348 | else: 349 | minibatch = random.sample(self.replay_buffer, self.batch_size) 350 | 351 | # get the index of the batch 352 | batch_index = range(0, len(minibatch)) 353 | 354 | 355 | # get the states of the minibatch 356 | states_minibatch = torch.cat([sample[0].unsqueeze(0) for sample in minibatch], dim=0) 357 | # get the actions taked in each state of the minibatch 358 | actions_minibatch = torch.tensor(np.array([np.where(sample[1] == 1)[0] for sample in minibatch])) 359 | # get the reward recieved for each action in the minibatch 360 | rewards_minibatch = [sample[2] for sample in minibatch] 361 | # get the next states 362 | nextstates_minibatch = torch.cat([sample[3].unsqueeze(0) for sample in minibatch], dim=0) 363 | # get the boolean that tell us if the next state is terminal or not 364 | dones_minibatch = [sample[4] for sample in minibatch] 365 | 366 | self.main_network.eval() 367 | self.target_network.eval() 368 | 369 | # get Q values of each selected action in the samples of the batch 370 | Q_values = self.main_network.forward(states_minibatch)[torch.tensor(batch_index)[:, None], actions_minibatch] 371 | # Get the Q values of next state 372 | Q_values_next = self.target_network.forward(nextstates_minibatch) 373 | 374 | # Terminal state to 0 375 | Q_values_next = Q_values_next.clone() 376 | Q_values_next[dones_minibatch] = 0 377 | 378 | # Get the updated values (True value) 379 | Q_values_forupdate = self.main_network.forward(states_minibatch) 380 | Q_target = torch.tensor(rewards_minibatch) + self.gamma *Q_values_next[batch_index, torch.argmax(Q_values_forupdate, dim = 1)].unsqueeze(1) 381 | 382 | # we will generate the same Q_target for all chosen actions : 383 | Q_target = Q_target[:, None].expand(-1, len(actions_minibatch[0])) 384 | 385 | # Train the main network 386 | self.main_network.train() 387 | 388 | # Initialize the gradients to 0 389 | self.main_network.zero_grad() 390 | 391 | 392 | # Loss calculation 393 | loss = self.loss_func(Q_target, Q_values.double()) 394 | print("loss of the DQL network is : ", loss.detach().item()) 395 | 396 | # Calculation of parameter gradients 397 | loss.backward() 398 | 399 | # Update of the parameters 400 | self.optimizer.step() 401 | 402 | 403 | # update the target network weights by copying from the main network 404 | def update_target_network(self): 405 | self.target_network.load_state_dict(copy.deepcopy(self.main_network.state_dict())) 406 | 407 | 408 | -------------------------------------------------------------------------------- /serverFL/Server_FedProx.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import clientFL.Client as Client 3 | from tqdm import tqdm 4 | import numpy as np 5 | import random 6 | import torch 7 | from torch.utils.data import DataLoader 8 | from sklearn.metrics import f1_score,recall_score,precision_score 9 | from torch.nn import functional as F 10 | import timeit 11 | 12 | class Server_FedProx(object): 13 | 14 | 15 | 16 | def __init__(self, num_clients, global_model, dict_clients, loss_fct, B, dataset_test, learning_rate, momentum, clients_info): 17 | """ 18 | Initialize the Server_FedProx object. 19 | 20 | Parameters: 21 | - num_clients (int): The number of clients in the system. 22 | - global_model: The global model (our goal). 23 | - dict_clients (dict): A dictionary containing the information about each client. 24 | - loss_fct: The loss function used for training. 25 | - B (int): The size of the batch. 26 | - dataset_test: The test dataset used to evaluate each round. 27 | - learning_rate: The learning rate for the clients. 28 | - momentum: The momentum for the clients. 29 | - clients_info: Information about clients for simulation. 30 | """ 31 | 32 | # The number of clients in the system 33 | self.N = num_clients 34 | # The global model (our goal) 35 | self.model = global_model 36 | # A list containing the @ of each client 37 | self.list_clients = [] 38 | # The size of the batch 39 | self.B = B 40 | # The test dataset that help us to evaluates each round 41 | self.dataset_test = dataset_test 42 | # Test Dataloader 43 | self.testdataloader = DataLoader(self.dataset_test, batch_size= self.B) 44 | # The distribution of clients in a dict 45 | self.dict_clients = dict_clients 46 | # The loss function 47 | self.loss_function = copy.deepcopy(loss_fct) 48 | # The number of parameters 49 | self.number_parameters = sum(p.numel() for p in self.model.parameters() if p.requires_grad) 50 | 51 | # Call a function to create the clients (simulation) 52 | self.clients_info = clients_info 53 | self.create_clients(learning_rate, momentum) 54 | 55 | def create_clients(self, learning_rate, momentum): 56 | ''' 57 | Create client objects based on the provided information and add them to the list. 58 | 59 | Parameters: 60 | - learning_rate: The learning rate for training clients. 61 | - momentum: The momentum parameter for training clients. 62 | ''' 63 | cpt = 0 64 | for client_name in self.dict_clients.keys(): 65 | # Create a client object with the specified parameters 66 | client = Client.Client( 67 | self.clients_info[cpt][0], # client name 68 | self.dict_clients[client_name], # client information 69 | copy.deepcopy(self.model), # copy of the global model 70 | self.clients_info[cpt][2], # number of cores 71 | self.clients_info[cpt][3], # frequency distribution 72 | self.clients_info[cpt][4], # bandwidth distribution 73 | copy.deepcopy(self.loss_function), # copy of the loss function 74 | self.B, # size of the batch 75 | learning_rate, # learning rate 76 | momentum # momentum 77 | ) 78 | # Append the client to the list 79 | self.list_clients.append(client) 80 | cpt += 1 81 | 82 | 83 | def weight_scalling_factor(self, client, active_clients): 84 | ''' 85 | Determine the factor assigned to a given client. 86 | 87 | Parameters: 88 | - client: The client for which the factor is determined. 89 | - active_clients: List of active clients. 90 | 91 | Returns: 92 | - The scaling factor for the client. 93 | ''' 94 | # Calculate the total training data points across clients for this round 95 | global_count = sum([client_obj.get_size() for client_obj in active_clients]) 96 | # Get the total number of data points held by a client 97 | local_count = client.get_size() 98 | 99 | return local_count / global_count 100 | 101 | def scale_model_weights(self, weight, scalar): 102 | ''' 103 | Multiply the local parameters of each client by its factor. 104 | 105 | Parameters: 106 | - weight: The local model weights. 107 | - scalar: The scaling factor. 108 | 109 | Returns: 110 | - Scaled local model weights. 111 | ''' 112 | w_scaled = copy.deepcopy(weight) 113 | 114 | for k in weight.keys(): 115 | w_scaled[k] = scalar * w_scaled[k] 116 | 117 | return w_scaled 118 | 119 | def sum_scaled_weights(self, scaled_weight_list): 120 | ''' 121 | Aggregate different parameters. 122 | 123 | Parameters: 124 | - scaled_weight_list: List of scaled models weights. 125 | 126 | Returns: 127 | - Aggregated weights. 128 | ''' 129 | w_avg = copy.deepcopy(scaled_weight_list[0]) 130 | 131 | for k in w_avg.keys(): 132 | tmp = torch.zeros_like(scaled_weight_list[0][k], dtype=torch.float32) 133 | 134 | for i in range(len(scaled_weight_list)): 135 | tmp += scaled_weight_list[i][k] 136 | 137 | w_avg[k].copy_(tmp) 138 | 139 | return w_avg 140 | 141 | def select_active_clients(self, comm_round, C, drop_percent=0): 142 | ''' 143 | Select a fraction of active clients for a training round. 144 | 145 | Parameters: 146 | - comm_round (int): The current communication round. 147 | - C (float): The fraction of active clients. 148 | - drop_percent (float): The percentage of clients to drop (optional, default is 0). 149 | 150 | Returns: 151 | - active_clients (list): List of active client objects for the current round. 152 | ''' 153 | # max between c*k and 1 154 | client_index = np.arange(0, len(self.list_clients)) 155 | 156 | m = int(max(C * self.N, 1)) # max between c*k and 1 157 | active_clients_index = random.sample(list(client_index), k=m) # Select a fraction of clients 158 | active_clients = [self.list_clients[i] for i in active_clients_index] 159 | # print(active_clients_index) 160 | 161 | return active_clients 162 | 163 | def flatten(self, weight): 164 | ''' 165 | Flatten the parameters of a model. 166 | 167 | Parameters: 168 | - weight: The model weights. 169 | 170 | Returns: 171 | - Flattened model weights. 172 | ''' 173 | weight_flatten = [] 174 | 175 | for param in weight.values(): 176 | weight_flatten.append(np.array(param).reshape(-1)) 177 | 178 | weight_flatten = [item for sublist in weight_flatten for item in sublist] 179 | 180 | return weight_flatten 181 | 182 | def global_train(self, comms_round, C, E, mu, verbose_test=1, verbos=0, type_data="others", init_weights=None, init_commround=None): 183 | ''' 184 | Train the global model using the Federated Learning approach. 185 | 186 | Parameters: 187 | - comms_round (int): The number of communication rounds. 188 | - C (float): The fraction of active clients in each round. 189 | - E (int): The number of local epochs for each client in each round. 190 | - mu (float): The learning rate for local training. 191 | - verbose_test (int, optional): Verbosity level for test results (default is 1). 192 | - verbos (int, optional): Verbosity level for training details (default is 0). 193 | - type_data (str, optional): Type of data for training and testing ("others" or "Fall", default is "others"). 194 | - init_weights (dict, optional): Initial weights for the global model (default is None). 195 | - init_commround (int, optional): Initial communication round (default is None). 196 | 197 | Returns: 198 | - dict_result (dict): Dictionary containing training results, including accuracy, loss, and other metrics. 199 | ''' 200 | if type_data == "Fall": 201 | return self.global_train_fall(comms_round, C, E, mu, verbose_test, verbos, type_data, init_weights, init_commround) 202 | else: 203 | return self.global_train_others(comms_round, C, E, mu, verbose_test, verbos, type_data, init_weights, init_commround) 204 | 205 | 206 | def global_train_others(self, comms_round, C, E, mu, verbose_test = 1, verbos = 0, type_data = "others", init_weights = None, init_commround = None): 207 | 208 | 209 | rounds = [] 210 | accuarcy = [] 211 | loss = [] 212 | 213 | time_rounds = [] 214 | time_rounds_sum = [] 215 | best_model_weights = {} 216 | best_accuracy = 0 217 | 218 | max_latency = 0 219 | min_latency = 100000000 220 | 221 | for client in self.list_clients: 222 | frequency_client = random.choice(client.frequency) 223 | bandwidth_client = random.choice(client.bandwidth) 224 | 225 | latency_min = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * max(client.frequency)) + (self.number_parameters * 64)/(1000000 * max(client.bandwidth)) 226 | latency_max = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * min(client.frequency)) + (self.number_parameters * 64)/(1000000 * min(client.bandwidth)) 227 | 228 | if (latency_max > max_latency ): 229 | max_latency = latency_max 230 | 231 | if (latency_min < min_latency): 232 | min_latency = latency_min 233 | 234 | if (init_weights != None): 235 | self.model.load_state_dict(copy.deepcopy(init_weights)) 236 | 237 | comm_start = init_commround 238 | 239 | for client in self.list_clients : 240 | client.set_weights(copy.deepcopy(init_weights)) 241 | 242 | else: 243 | comm_start = 0 244 | 245 | self.model.train() 246 | 247 | # For each training round 248 | for comm_round in range(comm_start, comms_round): 249 | 250 | temps_debut = timeit.default_timer() 251 | rounds.append(comm_round+1) 252 | 253 | if (verbos == 1): 254 | print("*************************************************************************************") 255 | print("Communication round n : ", comm_round + 1) 256 | 257 | # Receive the parameters of the global model (first we will have the initialized parameters) 258 | global_weights = self.model.state_dict() 259 | 260 | 261 | # List to collect the parameters of the model * weight of each client 262 | scaled_local_weight_list = [] 263 | 264 | # Select a client portion C 265 | active_clients = self.select_active_clients(comm_round, C) 266 | 267 | time_roundt = [] 268 | # For each selected customer 269 | for client in active_clients: 270 | if (verbos == 1): 271 | print("Entrainnement locale du client : ", client.client_name) 272 | 273 | # Training on local client data 274 | client_w = client.train(copy.deepcopy(global_weights), E, mu, type_data, verbos) 275 | 276 | # Get the parameters of the local model * factor 277 | client_scaling_factor = self.weight_scalling_factor(client, active_clients) #nk/n' 278 | 279 | # weights*factor 280 | client_scaled_weight = self.scale_model_weights(client_w, client_scaling_factor) 281 | 282 | # save in the list 283 | scaled_local_weight_list.append(client_scaled_weight) 284 | 285 | 286 | frequency_client = random.choice(client.frequency) 287 | bandwidth_client = random.choice(client.bandwidth) 288 | 289 | latency_client = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * frequency_client) + (self.number_parameters * 64)/(1000000 * bandwidth_client) 290 | 291 | #print("Client name : ", client.client_name, " with latency :", latency_client) 292 | time_roundt.append(latency_client) 293 | 294 | time_rounds.append(max(time_roundt)) 295 | time_rounds_sum.append(sum(time_roundt)) 296 | 297 | # The average of the different points received 298 | average_weights = self.sum_scaled_weights(scaled_local_weight_list) 299 | 300 | # Copy the weights in the global model 301 | self.model.load_state_dict(average_weights) 302 | 303 | acc_test, loss_test = self.test(type_data) 304 | 305 | # Tester le modele gloable chaque round 306 | if (verbose_test == 1): 307 | print("Training round n :", (comm_round+1),", Test accuarcy : ", round(acc_test.numpy()*100, 2) ,", Test loss :", round(loss_test, 2)) 308 | #print("Max time : ", max(time_roundt)) 309 | print("*************************************************************************************") 310 | 311 | if (acc_test > best_accuracy): 312 | best_accuracy = acc_test 313 | best_model_weights = copy.deepcopy(average_weights) 314 | 315 | accuarcy.append(acc_test.numpy()) 316 | loss.append(loss_test) 317 | 318 | temps_fin = timeit.default_timer() - temps_debut 319 | #time_rounds.append(temps_fin) 320 | 321 | for client in self.list_clients: 322 | client.delete_model() 323 | 324 | dict_result = { 325 | "Best_model_weights": best_model_weights, 326 | "Accuracy": accuarcy, 327 | "Loss" : loss, 328 | "Timeurounds" : time_rounds, 329 | "Timesum" : time_rounds_sum 330 | } 331 | 332 | return dict_result 333 | 334 | 335 | def global_train_fall(self, comms_round, C, E, mu, verbose_test = 1, verbos = 0, type_data = "others", init_weights = None, init_commround = None): 336 | '''Global model training''' 337 | 338 | rounds = [] 339 | accuarcy = [] 340 | recall = [] 341 | precision = [] 342 | loss = [] 343 | f1score = [] 344 | 345 | time_rounds = [] 346 | time_rounds_sum = [] 347 | best_model_weights = {} 348 | best_f1score = 0 349 | 350 | if (init_weights != None): 351 | self.model.load_state_dict(copy.deepcopy(init_weights)) 352 | 353 | comm_start = init_commround 354 | 355 | for client in self.list_clients : 356 | client.set_weights(copy.deepcopy(init_weights)) 357 | 358 | else: 359 | comm_start = 0 360 | 361 | client_selected = [] 362 | self.model.train() 363 | 364 | # For each training round 365 | for comm_round in tqdm(range(comm_start, comms_round)): 366 | 367 | rounds.append(comm_round+1) 368 | 369 | if (verbos == 1): 370 | print("*************************************************************************************") 371 | print("Communication round n : ", comm_round + 1) 372 | 373 | # Receive the parameters of the global model (first we will have the initialized parameters) 374 | global_weights = self.model.state_dict() 375 | 376 | 377 | # List to collect the parameters of the model * weight of each client 378 | scaled_local_weight_list = [] 379 | 380 | # Select a client portion C 381 | active_clients = self.select_active_clients(comm_round, C) 382 | 383 | time_roundt = [] 384 | 385 | client_selected_tour = [] 386 | 387 | for client in active_clients: 388 | client_selected_tour.append(client.client_name) 389 | 390 | client_selected.append(client_selected_tour) 391 | # For each selected customer 392 | for client in active_clients: 393 | if (verbos == 1): 394 | print("Entrainnement locale du client : ", client.client_name) 395 | 396 | # Training on local client data 397 | client_w = client.train(copy.deepcopy(global_weights), E, mu, type_data, verbos) 398 | 399 | # Get the parameters of the local model * factor 400 | client_scaling_factor = self.weight_scalling_factor(client, active_clients) #nk/n' 401 | 402 | # weights*factor 403 | client_scaled_weight = self.scale_model_weights(client_w, client_scaling_factor) 404 | 405 | # save in the list 406 | scaled_local_weight_list.append(client_scaled_weight) 407 | 408 | frequency_client = random.choice(client.frequency) 409 | bandwidth_client = random.choice(client.bandwidth) 410 | 411 | latency_client = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * frequency_client) + (self.number_parameters * 64)/(1000000 * bandwidth_client) 412 | 413 | #print("Client name : ", client.client_name, " with latency :", latency_client) 414 | time_roundt.append(latency_client) 415 | 416 | time_rounds.append(max(time_roundt)) 417 | time_rounds_sum.append(sum(time_roundt)) 418 | 419 | # The average of the different points received 420 | average_weights = self.sum_scaled_weights(scaled_local_weight_list) 421 | 422 | # Copy the weights in the global model 423 | self.model.load_state_dict(average_weights) 424 | 425 | acc_test, f1score_test, recall_test, precision_test, loss_test = self.test(type_data) 426 | 427 | # Test the global model each round 428 | if (verbose_test == 1): 429 | print("Training round n :", (comm_round+1),", Test accuarcy : ", round(acc_test*100, 2) ,", Test F1_score :", round(f1score_test*100, 2)," Test loss :", round(loss_test, 2)) 430 | print("Test Recall : ", round(recall_test*100, 2), "Test Precision : ", round(precision_test*100, 2)) 431 | print("Max time : ", max(time_roundt)) 432 | print("*************************************************************************************") 433 | 434 | if (f1score_test > best_f1score): 435 | best_f1score = f1score_test 436 | best_model_weights = copy.deepcopy(average_weights) 437 | 438 | accuarcy.append(acc_test) 439 | f1score.append(f1score_test) 440 | recall.append(recall_test) 441 | precision.append(precision_test) 442 | loss.append(loss_test) 443 | 444 | for client in self.list_clients: 445 | client.delete_model() 446 | 447 | dict_result = { 448 | "Best_model_weights": best_model_weights, 449 | "Accuracy": accuarcy, 450 | "F1_score": f1score, 451 | "Recall" : recall, 452 | "Precision" : precision, 453 | "Loss" : loss, 454 | "Timeurounds" : time_rounds, 455 | "Timesum" : time_rounds_sum, 456 | "Client_selected" : client_selected 457 | } 458 | 459 | return dict_result 460 | 461 | 462 | def test(self, type_data): 463 | """ 464 | Test the global model on the specified type of data. 465 | 466 | Parameters: 467 | - type_data: Type of data, e.g., "Fall" or "others". 468 | 469 | Returns: 470 | - The test accuracy and loss. 471 | """ 472 | if type_data == "Fall": 473 | return self.test_falldetection() 474 | else: 475 | return self.test_others() 476 | 477 | 478 | def test_others(self): 479 | """ 480 | Evaluate the global model with the test dataset for a task other than fall detection. 481 | 482 | Returns: 483 | - The accuracy and loss on the test dataset. 484 | """ 485 | # Set the model to evaluation mode 486 | self.model.eval() 487 | 488 | # Testing 489 | test_loss = 0 490 | correct = 0 491 | 492 | # Iterate through the test dataset 493 | with torch.no_grad(): 494 | for idx, (data, target) in enumerate(self.testdataloader): 495 | log_probs = self.model(data) 496 | # Sum up batch loss 497 | test_loss += torch.nn.functional.cross_entropy(log_probs, target, reduction='sum').item() 498 | 499 | # Get the index of the max log-probability 500 | y_pred = log_probs.data.max(1, keepdim=True)[1] 501 | correct += y_pred.eq(target.data.view_as(y_pred)).long().cpu().sum() 502 | 503 | # Calculate average test loss and accuracy 504 | test_loss /= len(self.testdataloader.dataset) 505 | accuracy = correct / len(self.testdataloader.dataset) 506 | 507 | return accuracy, test_loss 508 | 509 | 510 | def test_falldetection(self): 511 | """ 512 | Evaluate the global model on a test dataset for fall detection. 513 | 514 | Returns: 515 | - Accuracy, F1 score, recall, precision, and epoch loss on the fall detection test dataset. 516 | """ 517 | epoch_loss = 0 518 | correct, total = 0, 0 519 | targets = list() 520 | preds = list() 521 | acc = 0 522 | 523 | # Set the model to evaluation mode 524 | self.model.eval() 525 | 526 | for idx, (X_batch, y_batch) in enumerate(self.testdataloader): 527 | batchsize = X_batch.shape[0] 528 | 529 | # Forward pass 530 | out = self.model(X_batch.float()) 531 | loss = self.loss_function(out.contiguous(), y_batch.contiguous()) 532 | 533 | # Predictions and metrics 534 | pred = F.log_softmax(out, dim=1).argmax(dim=1) 535 | total += y_batch.size(0) 536 | correct += (pred == y_batch).sum().item() 537 | 538 | targets += list(y_batch) 539 | preds += list(pred) 540 | 541 | epoch_loss += (loss.detach().item() / batchsize) 542 | 543 | # Calculate accuracy and additional metrics 544 | acc = correct / total 545 | f1score = f1_score(targets, preds, zero_division=1) 546 | recall = recall_score(targets, preds, zero_division=1) 547 | precision = precision_score(targets, preds, zero_division=1) 548 | 549 | return acc, f1score, recall, precision, epoch_loss 550 | -------------------------------------------------------------------------------- /serverFL/Server_FAVOR.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import clientFL.Client as Client 3 | from tqdm import tqdm 4 | import numpy as np 5 | import random 6 | import torch 7 | from torch.utils.data import DataLoader 8 | from sklearn.decomposition import PCA 9 | import torch.nn as nn 10 | import RL.DQL as DQL 11 | from sklearn.metrics import f1_score,recall_score,precision_score 12 | from torch.nn import functional as F 13 | import timeit 14 | 15 | class Server_FedDRL(object): 16 | 17 | def __init__(self, num_clients, global_model, dict_clients, loss_fct, B, dataset_test, learning_rate, momentum, clients_info): 18 | """ 19 | Initialize the system for federated learning. 20 | 21 | Parameters: 22 | - num_clients (int): The number of clients in the system. 23 | - global_model: The global model. 24 | - dict_clients: A dictionary containing information about each client. 25 | - loss_fct: The loss function used for training. 26 | - B (int): The size of the batch. 27 | - dataset_test: The test dataset used to evaluate each round. 28 | - learning_rate: Learning rate for the optimizer. 29 | - momentum: Momentum for the optimizer. 30 | - clients_info: Information about clients for simulation. 31 | 32 | The method also initializes various attributes to manage the federated learning system. 33 | """ 34 | # The number of clients in the system 35 | self.N = num_clients 36 | 37 | # The global model (our goal) 38 | self.model = global_model 39 | 40 | # A list containing the IDs of each client 41 | self.list_clients = [] 42 | 43 | # The size of the batch 44 | self.B = B 45 | 46 | # Test dataset that helps evaluate each round 47 | self.dataset_test = dataset_test 48 | 49 | # Test DataLoader 50 | self.testdataloader = DataLoader(self.dataset_test, batch_size=self.B) 51 | 52 | # The distribution of clients in a dictionary 53 | self.dict_clients = dict_clients 54 | 55 | # The loss function 56 | self.loss_function = copy.deepcopy(loss_fct) 57 | 58 | # The number of parameters in the global model 59 | self.number_parameters = sum(p.numel() for p in self.model.parameters() if p.requires_grad) 60 | 61 | # Call a function to create the clients (simulation) 62 | self.clients_info = clients_info 63 | self.create_clients(learning_rate, momentum) 64 | 65 | 66 | def create_clients(self, learning_rate, momentum): 67 | """ 68 | Create client objects based on the information provided in self.clients_info. 69 | 70 | Parameters: 71 | - learning_rate: Learning rate for the optimizer. 72 | - momentum: Momentum for the optimizer. 73 | 74 | The method creates client objects with specific attributes, such as client name, local dataset, 75 | local model, number of cores, frequency, bandwidth, loss function, batch size, learning rate, and momentum. 76 | The created clients are appended to the list_clients attribute. 77 | """ 78 | cpt = 0 79 | for client_name in self.dict_clients.keys(): # For each client 80 | client = Client.Client( 81 | self.clients_info[cpt][0], # client name 82 | self.dict_clients[client_name], # local dataset 83 | copy.deepcopy(self.model), # local model 84 | self.clients_info[cpt][2], # number of cores 85 | self.clients_info[cpt][3], # frequency 86 | self.clients_info[cpt][4], # bandwidth 87 | copy.deepcopy(self.loss_function), # loss function 88 | self.B, # batch size 89 | learning_rate, # learning rate 90 | momentum # momentum 91 | ) 92 | self.list_clients.append(client) # append it to the client list 93 | cpt = cpt + 1 94 | 95 | 96 | def weight_scalling_factor(self, client, active_clients): 97 | """ 98 | Determine the weight scaling factor for a given client. 99 | 100 | Parameters: 101 | - client: The client for which to calculate the weight scaling factor. 102 | - active_clients: List of active clients participating in the current round. 103 | 104 | Returns: 105 | - The weight scaling factor for the given client. 106 | """ 107 | # First, calculate the total training data points across clients for this round 108 | global_count = sum([client_obj.get_size() for client_obj in active_clients]) 109 | 110 | # Get the total number of data points held by a client 111 | local_count = client.get_size() 112 | 113 | return local_count / global_count 114 | 115 | def scale_model_weights(self, weight, scalar): 116 | """ 117 | Scale the local model parameters of each client by a given scalar factor. 118 | 119 | Parameters: 120 | - weight: The local model parameters. 121 | - scalar: The scaling factor. 122 | 123 | Returns: 124 | - The scaled local model parameters. 125 | """ 126 | w_scaled = copy.deepcopy(weight) 127 | 128 | # Scale each parameter by the given scalar factor 129 | for k in weight.keys(): 130 | w_scaled[k] = scalar * w_scaled[k] 131 | 132 | return w_scaled 133 | 134 | 135 | def sum_scaled_weights(self, scaled_weight_list): 136 | """ 137 | Aggregate the different scaled model parameters. 138 | 139 | Parameters: 140 | - scaled_weight_list: A list of scaled models parameters. 141 | 142 | Returns: 143 | - The aggregated global model parameters. 144 | """ 145 | w_avg = copy.deepcopy(scaled_weight_list[0]) 146 | 147 | for k in w_avg.keys(): 148 | tmp = torch.zeros_like(scaled_weight_list[0][k], dtype=torch.float32) 149 | 150 | # Sum up the scaled weights for each client 151 | for i in range(len(scaled_weight_list)): 152 | tmp += scaled_weight_list[i][k] 153 | 154 | w_avg[k].copy_(tmp) 155 | 156 | return w_avg 157 | 158 | 159 | def flatten(self, weight): 160 | """ 161 | Flatten the model parameters. 162 | 163 | Parameters: 164 | - weight: The model parameters. 165 | 166 | Returns: 167 | - A flattened list of model parameters. 168 | """ 169 | weight_flatten = [] 170 | 171 | # Iterate through the parameters and flatten them 172 | for param in weight.values(): 173 | weight_flatten.append(np.array(param).reshape(-1)) 174 | 175 | # Flatten the list of flattened parameters 176 | weight_flatten = [item for sublist in weight_flatten for item in sublist] 177 | 178 | return weight_flatten 179 | 180 | 181 | def select_active_clients_random(self, comm_round, C): 182 | """ 183 | Select a fraction of clients randomly for a training round. 184 | 185 | Parameters: 186 | - comm_round: The current communication round. 187 | - C: The fraction of clients to be selected. 188 | 189 | Returns: 190 | - A list of randomly selected client indices. 191 | """ 192 | client_index = np.arange(0, len(self.list_clients)) 193 | 194 | # Calculate the number of active clients for this round 195 | m = int(max(C * self.N, 1)) # max between C * N and 1 196 | 197 | # Randomly select a fraction of clients 198 | active_clients = random.sample(list(client_index), k=m) 199 | 200 | # Shuffle the list of active clients 201 | random.shuffle(active_clients) 202 | 203 | return active_clients 204 | 205 | def global_train(self, comms_round, C, E, mu, M, omega, batch_size, verbose_test=1, verbos=0, type_data="others"): 206 | """ 207 | Perform global training based on the specified type of data. 208 | 209 | Parameters: 210 | - comms_round: The total number of communication rounds. 211 | - C: The fraction of clients participating in each round. 212 | - E: Number of local training iterations on each client. 213 | - mu: A parameter used for FedProx. 214 | - M: A parameter used for FAVOR. 215 | - omega: A parameter used for FAVOR. 216 | - batch_size: The size of the batch. 217 | - verbose_test: Verbosity level for printing test information (default is 1). 218 | - verbos: Verbosity level for printing training information (default is 0). 219 | - type_data: Type of data, e.g., "Fall" or "others" (default is "others"). 220 | 221 | Returns: 222 | - Training informations. 223 | """ 224 | if type_data == "Fall": 225 | return self.global_train_fall(comms_round, C, E, mu, M, omega, batch_size, verbose_test, verbos, type_data) 226 | else: 227 | return self.global_train_others(comms_round, C, E, mu, M, omega, batch_size, verbose_test, verbos, type_data) 228 | 229 | 230 | def global_train_others(self, comms_round, C, E, mu, M, omega, batch_size, verbose_test=1, verbos=0, type_data="others"): 231 | """ 232 | Perform global training for a federated learning scenario with reinforcement learning aspects. 233 | 234 | Parameters: 235 | - comms_round: The total number of communication rounds. 236 | - C: The fraction of clients participating in each round. 237 | - E: Number of local training iterations on each client. 238 | - mu: A parameter used for FedProx. 239 | - M: A parameter used for FAVOR. 240 | - omega: A parameter used for FAVOR. 241 | - batch_size: The size of the batch. 242 | - verbose_test: Verbosity level for printing test information (default is 1). 243 | - verbos: Verbosity level for printing training information (default is 0). 244 | - type_data: Type of data, e.g., "Fall" or "others" (default is "others"). 245 | 246 | Returns: 247 | - A dictionary containing various results including the best model weights, accuracy, loss, time, reputation, rewards, and DQL loss. 248 | """ 249 | 250 | m = int(max(C*self.N, 1)) 251 | 252 | rounds = [] 253 | accuarcy = [] 254 | loss = [] 255 | reputation_list = [] 256 | list_loss_DQL = [] 257 | 258 | time_rounds = [] 259 | time_rounds_sum = [] 260 | best_model_weights = {} 261 | best_accuracy = 0 262 | rewards = [] 263 | 264 | # Initialize the first state 265 | weight_list_for_iteration = [] 266 | # Append weight of global model 267 | weight_list_for_iteration.append(self.flatten(self.model.state_dict())) 268 | 269 | max_latency = 0 270 | min_latency = 100000000 271 | 272 | 273 | # For each client perform one epoch of SGD to get the weights 274 | for client in self.list_clients: 275 | frequency_client = random.choice(client.frequency) 276 | bandwidth_client = random.choice(client.bandwidth) 277 | 278 | latency_min = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * max(client.frequency)) + (self.number_parameters * 64)/(1000000 * max(client.bandwidth)) 279 | latency_max = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * min(client.frequency)) + (self.number_parameters * 64)/(1000000 * min(client.bandwidth)) 280 | 281 | if (latency_max > max_latency ): 282 | max_latency = latency_max 283 | 284 | if (latency_min < min_latency): 285 | min_latency = latency_min 286 | 287 | client_w_for_first_iteration = client.train(self.model.state_dict(), 1, mu, type_data, verbos) 288 | weight_list_for_iteration.append(self.flatten(client_w_for_first_iteration)) 289 | 290 | # Apply PCA 291 | pca = PCA(n_components = len(self.list_clients)) 292 | 293 | 294 | # get the weight with PCA 295 | weight_list_for_iteration_pca = pca.fit_transform(weight_list_for_iteration) 296 | 297 | # State est une concaténation des different weight 298 | state = torch.Tensor([item for sublist in weight_list_for_iteration_pca for item in sublist]) 299 | 300 | # init dql 301 | dql = DQL.DQL(len(state), len(self.list_clients), batch_size) 302 | 303 | self.model.train() 304 | 305 | 306 | # For each communication round 307 | for comm_round in tqdm(range(comms_round)): 308 | temps_debut = timeit.default_timer() 309 | rounds.append(comm_round+1) 310 | 311 | if (verbos == 1): 312 | print("*************************************************************************************") 313 | print("Communication round n : ", comm_round + 1) 314 | 315 | # Receive the parameters of the global model (first we will have the initialized parameters) 316 | global_weights = self.model.state_dict() 317 | 318 | # Verify if we need to update the target network 319 | if (comm_round + 1) % dql.update_rate == 0: 320 | dql.update_target_network() 321 | 322 | 323 | if (comm_round == 0): 324 | # random selection 325 | active_clients_index = self.select_active_clients_random(comm_round, C) 326 | else: 327 | # multinomial_selection 328 | active_clients_index = dql.multiaction_selection(state, C, comm_round, mode = "Mode2") 329 | 330 | print(active_clients_index) 331 | 332 | # List to collect the parameters of the model * weight of each client 333 | scaled_local_weight_list = [] 334 | 335 | # list of active client objetcs 336 | active_clients = [self.list_clients[i] for i in active_clients_index] 337 | 338 | time_roundt = [] 339 | # For each selected customer 340 | for client_index in active_clients_index: 341 | if (verbos == 1): 342 | print("Entrainnement locale du client : ", client.client_name) 343 | 344 | # Training on local client data 345 | client_w = self.list_clients[client_index].train(global_weights, E, mu, type_data, verbos) 346 | 347 | # Update the reduced weights 348 | weight_list_for_iteration_pca[client_index] = (pca.transform(np.array(self.flatten(copy.deepcopy(client_w))).reshape(1, -1)))[0] 349 | 350 | # Get the parameters of the local model * factor 351 | client_scaling_factor = self.weight_scalling_factor(self.list_clients[client_index], active_clients) #nk/n' 352 | 353 | # save in the list 354 | client_scaled_weight = self.scale_model_weights(client_w, client_scaling_factor) 355 | scaled_local_weight_list.append(client_scaled_weight) 356 | 357 | frequency_client = random.choice(self.list_clients[client_index].frequency) 358 | bandwidth_client = random.choice(self.list_clients[client_index].bandwidth) 359 | 360 | latency_client = (self.list_clients[client_index].get_size()*64*40*20)/(self.list_clients[client_index].numbercores * 1000000 * frequency_client) + (self.number_parameters * 64)/(1000000 * bandwidth_client) 361 | #print("Client name : ", self.list_clients[client_index].client_name, " with latency :", latency_client) 362 | time_roundt.append(latency_client) 363 | 364 | time_rounds.append(max(time_roundt)) 365 | time_rounds_sum.append(sum(time_roundt)) 366 | 367 | # The average of the different points received 368 | average_weights = self.sum_scaled_weights(scaled_local_weight_list) 369 | 370 | # Copy the weights in the global model 371 | self.model.load_state_dict(average_weights) 372 | 373 | acc_test, loss_test = self.test(type_data) 374 | 375 | # Test the global model each round 376 | if (verbose_test == 1): 377 | print("Training round n :", (comm_round+1),", Test accuarcy : ", round(acc_test.numpy()*100, 2) ,", Test loss :", round(loss_test, 2)) 378 | print("*************************************************************************************") 379 | 380 | if (acc_test > best_accuracy): 381 | best_accuracy = acc_test 382 | best_model_weights = copy.deepcopy(average_weights) 383 | 384 | accuarcy.append(acc_test.item()) 385 | loss.append(loss_test) 386 | 387 | 388 | # Update reduced global parameters 389 | weight_list_for_iteration_pca[0] = (pca.transform(np.array(self.flatten(copy.deepcopy(self.model.state_dict()))).reshape(1, -1)))[0] 390 | 391 | # Next state 392 | next_state = torch.Tensor([item for sublist in weight_list_for_iteration_pca for item in sublist]) 393 | 394 | # We train using one action 395 | action = active_clients_index[0] 396 | 397 | # calcul du reward 398 | reward = M**(acc_test - omega) -1 399 | 400 | rewards.append(reward) 401 | 402 | #store the transition information 403 | dql.store_transistion(state, action, reward, next_state, done = False) 404 | #update current state to next state 405 | state = copy.deepcopy(next_state) 406 | 407 | loss_dql = dql.train(comm_round, mode = "Mode2") 408 | 409 | list_loss_DQL.append(loss_dql) 410 | temps_fin = timeit.default_timer() - temps_debut 411 | 412 | # Cleanup 413 | for client in self.list_clients: 414 | client.delete_model() 415 | 416 | # Results 417 | dict_result = { 418 | "Best_model_weights": best_model_weights, 419 | "Accuracy": accuarcy, 420 | "Loss" : loss, 421 | "Timeurounds" : time_rounds, 422 | "Timesum" : time_rounds_sum, 423 | "Reputation" : reputation_list, 424 | "Rewards" : rewards, 425 | "LossDQL" : list_loss_DQL 426 | } 427 | 428 | 429 | return dict_result 430 | 431 | def global_train_fall(self, comms_round, C, E, mu, M, omega, batch_size, verbose_test = 1, verbos = 0, type_data = "Fall"): 432 | """ 433 | Perform global training for a federated learning scenario with reinforcement learning aspects for fall detection. 434 | 435 | Parameters: 436 | - comms_round: The total number of communication rounds. 437 | - C: The fraction of clients participating in each round. 438 | - E: Number of local training iterations on each client. 439 | - mu: A parameter used for FedProx. 440 | - M: A parameter used for FAVOR. 441 | - omega: A parameter used for FAVOR. 442 | - batch_size: The size of the batch. 443 | - verbose_test: Verbosity level for printing test information (default is 1). 444 | - verbos: Verbosity level for printing training information (default is 0). 445 | - type_data: Type of data, e.g., "Fall" or "others" (default is "others"). 446 | 447 | Returns: 448 | - A dictionary containing various results including the best model weights, accuracy, loss, time, reputation, rewards, and DQL loss. 449 | """ 450 | 451 | rounds = [] 452 | accuarcy = [] 453 | recall = [] 454 | precision = [] 455 | loss = [] 456 | f1score = [] 457 | list_loss_DQL = [] 458 | 459 | 460 | time_rounds = [] 461 | time_rounds_sum = [] 462 | best_model_weights = {} 463 | best_f1score = 0 464 | rewards = [] 465 | 466 | # Initialize the first state 467 | weight_list_for_iteration = [] 468 | # Append weight of global model 469 | weight_list_for_iteration.append(self.flatten(self.model.state_dict())) 470 | 471 | # For each client perform one epoch of SGD to get the weights 472 | for client in self.list_clients: 473 | client_w_for_first_iteration = client.train(self.model.state_dict(), 1, mu, type_data, verbos) 474 | weight_list_for_iteration.append(self.flatten(client_w_for_first_iteration)) 475 | 476 | # Apply PCA 477 | pca = PCA(n_components = len(self.list_clients)) 478 | 479 | 480 | # get the weight with PCA 481 | weight_list_for_iteration_pca = pca.fit_transform(weight_list_for_iteration) 482 | 483 | # State est une concaténation des different weight 484 | state = torch.Tensor([item for sublist in weight_list_for_iteration_pca for item in sublist]) 485 | 486 | # init dql 487 | dql = DQL.DQL(len(state), len(self.list_clients), batch_size) 488 | 489 | self.model.train() 490 | 491 | # For each communication round 492 | for comm_round in tqdm(range(comms_round)): 493 | 494 | rounds.append(comm_round+1) 495 | 496 | if (verbos == 1): 497 | print("*************************************************************************************") 498 | print("Communication round n : ", comm_round + 1) 499 | 500 | # Receive the parameters of the global model (first we will have the initialized parameters) 501 | global_weights = self.model.state_dict() 502 | 503 | # Verify if we need to update the target network 504 | if (comm_round + 1) % dql.update_rate == 0: 505 | dql.update_target_network() 506 | 507 | 508 | if (comm_round == 0): 509 | # random selection 510 | active_clients_index = self.select_active_clients_random(comm_round, C) 511 | else: 512 | # multinomial_selection 513 | active_clients_index = dql.multiaction_selection(state, C, comm_round, mode = "Mode2") 514 | 515 | # List to collect the parameters of the model * weight of each client 516 | scaled_local_weight_list = [] 517 | 518 | # list of active client objetcs 519 | active_clients = [self.list_clients[i] for i in active_clients_index] 520 | 521 | time_roundt = [] 522 | # For each selected customer 523 | for client_index in active_clients_index: 524 | if (verbos == 1): 525 | print("Entrainnement locale du client : ", client.client_name) 526 | 527 | # Training on local client data 528 | client_w = self.list_clients[client_index].train(global_weights, E, mu, type_data, verbos) 529 | 530 | # Update the reduced weights 531 | weight_list_for_iteration_pca[client_index] = (pca.transform(np.array(self.flatten(copy.deepcopy(client_w))).reshape(1, -1)))[0] 532 | 533 | # Get the parameters of the local model * factor 534 | client_scaling_factor = self.weight_scalling_factor(self.list_clients[client_index], active_clients) #nk/n' 535 | 536 | # save in the list 537 | client_scaled_weight = self.scale_model_weights(client_w, client_scaling_factor) 538 | scaled_local_weight_list.append(client_scaled_weight) 539 | 540 | frequency_client = random.choice(self.list_clients[client_index].frequency) 541 | bandwidth_client = random.choice(self.list_clients[client_index].bandwidth) 542 | 543 | latency_client = (self.list_clients[client_index].get_size()*64*40*20)/(self.list_clients[client_index].numbercores * 1000000 * frequency_client) + (self.number_parameters * 64)/(1000000 * bandwidth_client) 544 | print("Client name : ", self.list_clients[client_index].client_name, " with latency :", latency_client) 545 | time_roundt.append(latency_client) 546 | 547 | time_rounds.append(max(time_roundt)) 548 | time_rounds_sum.append(sum(time_roundt)) 549 | 550 | # The average of the different points received 551 | average_weights = self.sum_scaled_weights(scaled_local_weight_list) 552 | 553 | # Copy the weights in the global model 554 | self.model.load_state_dict(average_weights) 555 | 556 | acc_test, f1score_test, recall_test, precision_test, loss_test = self.test(type_data) 557 | 558 | # Test the global model each round 559 | if (verbose_test == 1): 560 | print("Training round n :", (comm_round+1),", Test accuarcy : ", round(acc_test*100, 2) ,", Test F1_score :", round(f1score_test*100, 2)," Test loss :", round(loss_test, 2)) 561 | print("Test Recall : ", round(recall_test*100, 2), "Test Precision : ", round(precision_test*100, 2)) 562 | print("*************************************************************************************") 563 | 564 | if (f1score_test > best_f1score): 565 | best_f1score = f1score_test 566 | best_model_weights = copy.deepcopy(average_weights) 567 | 568 | accuarcy.append(acc_test) 569 | f1score.append(f1score_test) 570 | recall.append(recall_test) 571 | precision.append(precision_test) 572 | loss.append(loss_test) 573 | 574 | 575 | # Update reduced global parameters 576 | weight_list_for_iteration_pca[0] = (pca.transform(np.array(self.flatten(copy.deepcopy(self.model.state_dict()))).reshape(1, -1)))[0] 577 | 578 | # Next state 579 | next_state = torch.Tensor([item for sublist in weight_list_for_iteration_pca for item in sublist]) 580 | 581 | # We train using one action 582 | action = active_clients_index[0] 583 | 584 | 585 | # calcul du reward 586 | reward = M**(acc_test - omega) -1 587 | 588 | rewards.append(reward) 589 | 590 | #store the transition information 591 | dql.store_transistion(state, action, reward, next_state, done = False) 592 | #update current state to next state 593 | state = copy.deepcopy(next_state) 594 | 595 | 596 | loss_dql = dql.train(comm_round, mode = "Mode2") 597 | list_loss_DQL.append(loss_dql) 598 | 599 | # Cleanup 600 | for client in self.list_clients: 601 | client.delete_model() 602 | 603 | # Results 604 | dict_result = { 605 | "Best_model_weights": best_model_weights, 606 | "Accuracy": accuarcy, 607 | "F1_score": f1score, 608 | "Recall" : recall, 609 | "Precision" : precision, 610 | "Loss" : loss, 611 | "Timeurounds" : time_rounds, 612 | "Timesum" : time_rounds_sum, 613 | "Rewards" : rewards, 614 | "LossDQL" : list_loss_DQL 615 | } 616 | 617 | 618 | return dict_result 619 | 620 | 621 | def test(self, type_data): 622 | """ 623 | Test the global model on the specified type of data. 624 | 625 | Parameters: 626 | - type_data: Type of data, e.g., "Fall" or "others". 627 | 628 | Returns: 629 | - The test accuracy and loss. 630 | """ 631 | if type_data == "Fall": 632 | return self.test_falldetection() 633 | else: 634 | return self.test_others() 635 | 636 | 637 | def test_others(self): 638 | """ 639 | Evaluate the global model with the test dataset for a task other than fall detection. 640 | 641 | Returns: 642 | - The accuracy and loss on the test dataset. 643 | """ 644 | # Set the model to evaluation mode 645 | self.model.eval() 646 | 647 | # Testing 648 | test_loss = 0 649 | correct = 0 650 | 651 | # Iterate through the test dataset 652 | with torch.no_grad(): 653 | for idx, (data, target) in enumerate(self.testdataloader): 654 | log_probs = self.model(data) 655 | # Sum up batch loss 656 | test_loss += torch.nn.functional.cross_entropy(log_probs, target, reduction='sum').item() 657 | 658 | # Get the index of the max log-probability 659 | y_pred = log_probs.data.max(1, keepdim=True)[1] 660 | correct += y_pred.eq(target.data.view_as(y_pred)).long().cpu().sum() 661 | 662 | # Calculate average test loss and accuracy 663 | test_loss /= len(self.testdataloader.dataset) 664 | accuracy = correct / len(self.testdataloader.dataset) 665 | 666 | return accuracy, test_loss 667 | 668 | 669 | def test_falldetection(self): 670 | """ 671 | Evaluate the global model on a test dataset for fall detection. 672 | 673 | Returns: 674 | - Accuracy, F1 score, recall, precision, and epoch loss on the fall detection test dataset. 675 | """ 676 | epoch_loss = 0 677 | correct, total = 0, 0 678 | targets = list() 679 | preds = list() 680 | acc = 0 681 | 682 | # Set the model to evaluation mode 683 | self.model.eval() 684 | 685 | for idx, (X_batch, y_batch) in enumerate(self.testdataloader): 686 | batchsize = X_batch.shape[0] 687 | 688 | # Forward pass 689 | out = self.model(X_batch.float()) 690 | loss = self.loss_function(out.contiguous(), y_batch.contiguous()) 691 | 692 | # Predictions and metrics 693 | pred = F.log_softmax(out, dim=1).argmax(dim=1) 694 | total += y_batch.size(0) 695 | correct += (pred == y_batch).sum().item() 696 | 697 | targets += list(y_batch) 698 | preds += list(pred) 699 | 700 | epoch_loss += (loss.detach().item() / batchsize) 701 | 702 | # Calculate accuracy and additional metrics 703 | acc = correct / total 704 | f1score = f1_score(targets, preds, zero_division=1) 705 | recall = recall_score(targets, preds, zero_division=1) 706 | precision = precision_score(targets, preds, zero_division=1) 707 | 708 | return acc, f1score, recall, precision, epoch_loss 709 | -------------------------------------------------------------------------------- /data_division/MobiAct/MobiAct_divisions.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import glob 3 | import pickle 4 | import numpy as np 5 | import torch 6 | from tqdm import tqdm 7 | import pandas as pd 8 | from scipy import signal 9 | import os 10 | import random 11 | from sklearn.preprocessing import LabelEncoder 12 | from torch.utils.data import TensorDataset, DataLoader 13 | 14 | class MobiAct_divisions(object): 15 | 16 | 17 | def __init__(self, annoted_path): 18 | 19 | self.annoted_path = annoted_path # Path to the annoted data (data/MobiAct_Dataset_v2.0/Annotated Data for example) 20 | 21 | 22 | def resample_data(self): 23 | 24 | """ 25 | This function has been taken from the following github repository : 26 | git@github.com:IKKIM00/fall-detection-and-predction-using-GRU-and-LSTM-with-Transfer-Learning.git 27 | """ 28 | fall_types = ['FOL', 'FKL', 'BSC', 'SDL'] 29 | adl_types = ['STD', 'WAL', 'JOG', 'JUM', 'STU', 'STN', 'SCH', 'SIT', 'CHU', 'CSI', 'CSO', 'LYI'] 30 | person_numlist = list(range(1, 68)) 31 | trials = list(range(1, 4)) 32 | os.makedirs("data_resampled/Mobidata_resampled/fall") 33 | os.makedirs("data_resampled/Mobidata_resampled/adl") 34 | 35 | """ 36 | Resampling Fall Data 37 | """ 38 | print("Resampling Fall Data ...") 39 | for person_num in tqdm(person_numlist): 40 | for fall_type in fall_types: 41 | for trial in trials: 42 | try: 43 | directory = self.annoted_path + "/"+ fall_type+ "/" + fall_type + '_' + str(person_num) + '_' + str(trial) + '_annotated.csv' 44 | data = pd.read_csv(directory) 45 | 46 | std_length = len(data[data['label']=='STD']) 47 | fall_length = len(data[data['label']==fall_type]) 48 | #lyi_length = len(data[data['label']=='LYI']) 49 | 50 | # Extract only the data of type fall 51 | fall_data = data[std_length : std_length + fall_length] 52 | 53 | # Take the acc_x, acc_y and acc_z 54 | df = fall_data[['acc_x', 'acc_y', 'acc_z']] 55 | 56 | # The sizes of the scaffolding 57 | n = 10 58 | resampling_size = 30 59 | 60 | # If length of STD is higher than 10 61 | if std_length >= n: 62 | # 10 samples from STD + FALL resampled into 30 samples + 10 samples from LYI 63 | # acc_x 64 | std_x = pd.DataFrame(data['acc_x'][std_length - n : std_length]) # 10 STD 65 | fall_x = pd.DataFrame(signal.resample(df['acc_x'], resampling_size)) #Fall data 66 | fall_x.columns = ['acc_x'] 67 | lyi_x = pd.DataFrame(data['acc_x'][fall_length : fall_length + n]) # 10 LYI 68 | result = pd.concat([std_x, fall_x, lyi_x], axis=0) # Concaténation 69 | 70 | # acc_y 71 | std_y = pd.DataFrame(data['acc_y'][std_length - n : std_length]) 72 | fall_y = pd.DataFrame(signal.resample(df['acc_y'], resampling_size)) 73 | fall_y.columns = ['acc_y'] 74 | lyi_y = pd.DataFrame(data['acc_y'][fall_length : fall_length + n]) 75 | result['acc_y'] = pd.concat([std_y, fall_y, lyi_y]) 76 | 77 | # acc_z 78 | std_z = pd.DataFrame(data['acc_z'][std_length - n : std_length]) 79 | fall_z = pd.DataFrame(signal.resample(df['acc_z'], resampling_size)) 80 | fall_z.columns = ['acc_z'] 81 | lyi_z = pd.DataFrame(data['acc_z'][fall_length : fall_length + n]) 82 | result['acc_z'] = pd.concat([std_z, fall_z, lyi_z]) 83 | 84 | # newly indexing 85 | result.index = list(range(0, len(result))) 86 | 87 | result['label'] = 0 #initialzing for label 88 | result.loc[n: n + resampling_size - 1, 'label'] = 1 # labelling as 'FALL' = 1 les données de chutes 89 | 90 | # If the length of STD is shorter than 10 91 | else: 92 | # STD + FALL resampled into 30 samples + 10 samples from LYI 93 | # acc_x 94 | std_x = pd.DataFrame(data['acc_x'][: std_length]) # std_length STD 95 | fall_x = pd.DataFrame(signal.resample(df['acc_x'], resampling_size)) # chutes 96 | fall_x.columns = ['acc_x'] 97 | lyi_x = pd.DataFrame(data['acc_x'][fall_length : fall_length + n + (n - std_length)]) # n + la difference de n - std_length données de LYI 98 | result = pd.concat([std_x, fall_x, lyi_x], axis=0) 99 | 100 | #acc_y 101 | std_y = pd.DataFrame(data['acc_y'][: std_length]) 102 | fall_y = pd.DataFrame(signal.resample(df['acc_y'], resampling_size)) 103 | fall_y.columns = ['acc_y'] 104 | lyi_y = pd.DataFrame(data['acc_y'][fall_length : fall_length + n + (n - std_length)]) 105 | result['acc_y'] = pd.concat([std_y, fall_y, lyi_y]) 106 | 107 | #acc_z 108 | std_z = pd.DataFrame(data['acc_z'][: std_length]) 109 | fall_z = pd.DataFrame(signal.resample(df['acc_z'], resampling_size)) 110 | fall_z.columns = ['acc_z'] 111 | lyi_z = pd.DataFrame(data['acc_z'][fall_length : fall_length + n + (n - std_length)]) 112 | result['acc_z'] = pd.concat([std_z, fall_z, lyi_z]) 113 | 114 | #newly indexing 115 | result.index = list(range(0, len(result))) 116 | 117 | result['label'] = 0 #initialzing for label -> ADL 118 | result.loc[std_length:std_length + resampling_size - 1, 'label'] = 1 # labelling as 'FALL' = 1 119 | 120 | # saving resampled data 121 | save_route = "data_resampled/Mobidata_resampled/fall/" + fall_type + '_' + str(person_num) + '_' + str(trial) +'.csv' 122 | result.to_csv(save_route) 123 | except FileNotFoundError: 124 | print(fall_type, person_num, trial, 'no data') 125 | 126 | 127 | """ 128 | Resampling ADL Data 129 | """ 130 | print("Resampling ADL Data ...") 131 | for person_num in tqdm(person_numlist): 132 | for adl_type in adl_types: 133 | for trial in trials: 134 | try: 135 | directory = self.annoted_path + "/" + adl_type+ "/" + adl_type + '_' + str(person_num) + '_' + str(trial) + '_annotated.csv' 136 | 137 | data = pd.read_csv(directory) 138 | 139 | length = len(data) // 4 140 | 141 | # acc_x 142 | result = pd.DataFrame(signal.resample(data['acc_x'][:length], 50)) 143 | result.columns = ['acc_x'] 144 | 145 | # acc_y 146 | result['acc_y'] = pd.DataFrame(signal.resample(data['acc_y'][:length], 50)) 147 | 148 | # acc_z 149 | result['acc_z'] = pd.DataFrame(signal.resample(data['acc_z'][:length], 50)) 150 | 151 | # reindexing 152 | result.index = list(range(0, 50)) 153 | 154 | result['label'] = 0 # ADL 155 | 156 | # saving resampled data 157 | save_route = "data_resampled/Mobidata_resampled/adl/" + adl_type + '_' + str(person_num) + '_' + str(trial) +'.csv' 158 | result.to_csv(save_route) 159 | except: 160 | print(adl_type, person_num, trial, 'no data') 161 | 162 | 163 | def create_users_folders(self): 164 | 165 | for num_user in tqdm(range(1,68)): 166 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)) 167 | 168 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/Fall") 169 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/Fall/FOL") 170 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/Fall/FKL") 171 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/Fall/BSC") 172 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/Fall/SDL") 173 | 174 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL") 175 | os.makedirs("data_division/MobiAct//Users/User"+str(num_user)+"/ADL/STD") 176 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/WAL") 177 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/JOG") 178 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/JUM") 179 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/STU") 180 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/STN") 181 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/SCH") 182 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/SIT") 183 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/CHU") 184 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/CSI") 185 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/CSO") 186 | os.makedirs("data_division/MobiAct/Users/User"+str(num_user)+"/ADL/LYI") 187 | 188 | 189 | def populate_users(self, window): 190 | 191 | fall_data_dir = "data_resampled/Mobidata_resampled/fall/*.csv" 192 | adl_data_dir = "data_resampled/Mobidata_resampled/adl/*.csv" 193 | 194 | n = window 195 | 196 | print("Populate Fall types ...") 197 | nb_fall_timeseries = 0 198 | for file in tqdm(glob.glob(fall_data_dir)): 199 | 200 | data = pd.read_csv(file) 201 | 202 | folder = os.path.basename(file) 203 | fall_type = folder.split("_")[0] # Get the type of the Fall 204 | person_num = folder.split("_")[1] # Get the number of the person 205 | trial = folder.split("_")[2] # Get the number of the trial 206 | 207 | for i in range(0, len(data) - n): 208 | 209 | temp = list() 210 | """ 211 | For each window of size = window, we will create a csv file that is equal to the size of the window, 212 | i.e. each line in the file, will contain 4 components, the value of the X-axis of the accelerometer, the value of the Y-axis, Z-axis and the label 213 | of this sample (for the Fall, it is either STD, Fall type or LYI). 214 | """ 215 | for x, y, z, label in zip(data['acc_x'][i : i + n], data['acc_y'][i : i + n], data['acc_z'][i : i + n], data['label'][i: i + n]): 216 | temp.append([x]+[y]+[z]+[label]) 217 | 218 | nb_fall_timeseries = nb_fall_timeseries + 1 219 | 220 | example_df = pd.DataFrame(temp) # Transform it to pd in order to be saved as csv file 221 | example_df.columns = ["Accelerometer_x", "Accelerometer_y", "Accelerometer_z", "Label"] # rename the columns 222 | 223 | save_route = "data_division/MobiAct/Users/User"+str(person_num)+"/Fall/"+str(fall_type)+"/trail_"+str(trial)+"_timeserie"+str(i)+".csv" 224 | example_df.to_csv(save_route) 225 | 226 | 227 | print("Populate ADL ...") 228 | nb_adl_timeseries = 0 229 | for file in tqdm(glob.glob(adl_data_dir)): 230 | 231 | data = pd.read_csv(file) 232 | 233 | folder = os.path.basename(file) 234 | ADL_type = folder.split("_")[0] 235 | person_num = folder.split("_")[1] 236 | trial = folder.split("_")[2] 237 | 238 | for i in range(0, len(data) - n): 239 | temp = list() 240 | for x, y, z, label in zip(data['acc_x'][i : i + n], data['acc_y'][i : i + n], data['acc_z'][i : i + n], data['label'][i: i + n]): 241 | temp.append([x]+[y]+[z]+[label]) 242 | 243 | nb_adl_timeseries = nb_adl_timeseries + 1 244 | 245 | example_df = pd.DataFrame(temp) 246 | example_df.columns = ["Accelerometer_x", "Accelerometer_y", "Accelerometer_z", "Label"] 247 | 248 | save_route = "data_division/MobiAct/Users/User"+str(person_num)+"/ADL/"+str(ADL_type)+"/trail_"+str(trial)+"_timeserie"+str(i)+".csv" 249 | example_df.to_csv(save_route) 250 | 251 | print("In total we have ", nb_fall_timeseries, "Time series of type Fall and ", nb_adl_timeseries, " Time series of type ADL") 252 | 253 | def create_test_dataloader(self, seed, number_client_test): 254 | """" 255 | We will select at random number_client_test clients and concatenate there time series to form a test centralized dataset. 256 | We believe this is the best way to reflect a test dataset in reality. 257 | """ 258 | random.seed(seed) 259 | self.test_clients = random.sample(range(1, 68), number_client_test) 260 | self.FL_clinets = set(range(1,68)) - set(self.test_clients) 261 | 262 | 263 | X_test_fall = [] 264 | Y_test_fall = [] 265 | 266 | X_test_adl = [] 267 | Y_test_adl = [] 268 | x = 0 269 | for client_num in tqdm(self.test_clients): 270 | 271 | root_dir_fall = "data_division/MobiAct/Users/User"+str(client_num)+"/Fall" 272 | 273 | for subdir in os.listdir(root_dir_fall): 274 | subdir_path = os.path.join(root_dir_fall, subdir) # We will receive the types of "Falls" in this step 275 | for subdir2 in os.listdir(subdir_path): 276 | subdir2_path = os.path.join(subdir_path, subdir2) # We will receive the CSV files 277 | 278 | data = pd.read_csv(subdir2_path) # Read the time serie 279 | 280 | # See which label we will assign to the time series 281 | cnt = collections.Counter(data['Label']) 282 | num_adl = cnt[0] 283 | num_fall = cnt[1] 284 | 285 | if num_fall == 30: 286 | x = x + 1 287 | X_test_fall.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 288 | Y_test_fall.append(1) # 1 : fall 289 | else : 290 | X_test_adl.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 291 | Y_test_adl.append(0) # 0 : not fall 292 | 293 | root_dir_adl = "data_division/MobiAct/Users/User"+str(client_num)+"/ADL" 294 | 295 | for subdir in os.listdir(root_dir_adl): 296 | subdir_path = os.path.join(root_dir_adl, subdir) 297 | for subdir2 in os.listdir(subdir_path): 298 | subdir2_path = os.path.join(subdir_path, subdir2) 299 | 300 | data = pd.read_csv(subdir2_path) 301 | 302 | # There is no need to check, we already know that it is adl and not falls 303 | X_test_adl.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 304 | Y_test_adl.append(0) 305 | 306 | #print("The number of falls in the test dataset is : ", len(Y_test_fall)) 307 | #print("The number of adl in the test dataset is : ", len(Y_test_adl)) 308 | 309 | # We concatenate to have a single test_data 310 | X_test = X_test_fall + X_test_adl 311 | Y_test = Y_test_fall + Y_test_adl 312 | 313 | X_test = np.asarray(X_test) 314 | Y_test = np.asarray(Y_test).reshape(-1, ) 315 | 316 | enc = LabelEncoder() 317 | 318 | Y_test = enc.fit_transform(Y_test) 319 | 320 | test_data = TensorDataset(torch.from_numpy(X_test), torch.from_numpy(Y_test)) 321 | 322 | os.makedirs("data_division/MobiAct/test_dataset") 323 | 324 | with open('data_division/MobiAct/test_dataset/test_data.pickle', 'wb') as handle: 325 | pickle.dump(test_data, handle, protocol=pickle.HIGHEST_PROTOCOL) 326 | 327 | 328 | return self.test_clients, len(Y_test_fall), len(Y_test_adl) 329 | 330 | def volunteer_based_division(self): 331 | 332 | Dict_users = {} 333 | enc = LabelEncoder() 334 | 335 | columns = ['FOL', 'FKL', 'SDL', 'BSC', 'STD', 'WAL', 'JOG', 'JUM', 'STU', 'STN', 'SCH', 'SIT', 'CHU', 'CSI', 'CSO', 'LYI'] 336 | 337 | num_sample_per_user = {f'client {i}': {col: [] for col in columns} for i in self.FL_clinets} 338 | 339 | for client_num in tqdm(self.FL_clinets) : 340 | root_dir_fall = "data_division/MobiAct/Users/User"+str(client_num)+"/Fall" 341 | 342 | X_train_fall_user = [] 343 | Y_train_fall_user = [] 344 | X_train_adl_user = [] 345 | Y_train_adl_user = [] 346 | 347 | for subdir in os.listdir(root_dir_fall): 348 | subdir_path = os.path.join(root_dir_fall, subdir) # We will receive the types of "Falls" in this step 349 | nombre = 0 350 | for subdir2 in os.listdir(subdir_path): 351 | nombre = nombre + 1 352 | subdir2_path = os.path.join(subdir_path, subdir2) # We will receive the CSV files 353 | data = pd.read_csv(subdir2_path) # Read the time serie 354 | cnt = collections.Counter(data['Label']) 355 | num_adl = cnt[0] 356 | num_fall = cnt[1] 357 | 358 | if num_fall == 30: 359 | X_train_fall_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 360 | Y_train_fall_user.append(1) 361 | else : 362 | X_train_adl_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 363 | Y_train_adl_user.append(0) 364 | 365 | num_sample_per_user['client '+str(client_num)][subdir] = nombre 366 | 367 | root_dir_adl = "data_division/MobiAct/Users/User"+str(client_num)+"/ADL" 368 | 369 | for subdir in os.listdir(root_dir_adl): 370 | subdir_path = os.path.join(root_dir_adl, subdir) 371 | nombre = 0 372 | for subdir2 in os.listdir(subdir_path): 373 | nombre = nombre + 1 374 | subdir2_path = os.path.join(subdir_path, subdir2) 375 | data = pd.read_csv(subdir2_path) 376 | X_train_adl_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 377 | Y_train_adl_user.append(0) 378 | 379 | num_sample_per_user['client '+str(client_num)][subdir] = nombre 380 | 381 | X_train_user = X_train_fall_user + X_train_adl_user 382 | Y_train_user = Y_train_fall_user + Y_train_adl_user 383 | 384 | X_train_user = np.asarray(X_train_user) 385 | Y_train_user = np.asarray(Y_train_user).reshape(-1, ) 386 | Y_train_user = enc.fit_transform(Y_train_user) 387 | 388 | 389 | Dict_users["User"+str(client_num)] = TensorDataset(torch.from_numpy(X_train_user), torch.from_numpy(Y_train_user)) 390 | 391 | os.makedirs("data_division/MobiAct/volunteer-based_division") 392 | 393 | # Save the user dict 394 | with open('data_division/MobiAct/volunteer-based_division/dict_users.pickle', 'wb') as handle: 395 | pickle.dump(Dict_users, handle, protocol=pickle.HIGHEST_PROTOCOL) 396 | 397 | columns_to_keep = ['FOL', 'FKL', 'SDL', 'BSC', 'STD', 'WAL', 'JOG', 'JUM', 'STU', 'STN', 'SCH', 'SIT', 'CHU', 'CSI', 'CSO', 'LYI'] 398 | dict_users_sample = {} 399 | for client, data in num_sample_per_user.items(): 400 | user_data = [data[column] for column in columns_to_keep] 401 | dict_users_sample[client] = user_data 402 | 403 | for key, value in dict_users_sample.items(): 404 | if isinstance(value, list): # Check if the value is a list 405 | for i in range(len(value)): 406 | if isinstance(value[i], list) and not value[i]: # Check if it's an empty list 407 | dict_users_sample[key][i] = 0 # Replace empty list with 0 408 | 409 | new_dict_users_sample = {'client {}'.format(i): value for i, (key, value) in enumerate(dict_users_sample.items(), start=1)} 410 | 411 | new_dict_users_sample['client 10'], new_dict_users_sample['client 12'] = new_dict_users_sample['client 12'], new_dict_users_sample['client 10'] 412 | 413 | return new_dict_users_sample 414 | 415 | 416 | def unbalanced_division(self, seed, min_samples = 1): 417 | 418 | Dict_users = {} 419 | enc = LabelEncoder() 420 | random.seed(seed) 421 | 422 | for client_num in tqdm(self.FL_clinets) : 423 | 424 | root_dir_fall = "data_division/MobiAct/Users/User"+str(client_num)+"/Fall" 425 | 426 | X_train_fall_user = [] 427 | Y_train_fall_user = [] 428 | X_train_adl_user = [] 429 | Y_train_adl_user = [] 430 | 431 | for subdir in os.listdir(root_dir_fall): 432 | subdir_path = os.path.join(root_dir_fall, subdir) # We will receive the types of "Falls" in this step 433 | subfolders2 = [f for f in os.listdir(subdir_path) if os.path.isfile(os.path.join(subdir_path, f))] 434 | 435 | if (len(subfolders2) != 0): 436 | random_subfolders2 = random.sample(subfolders2, k=random.randint(min_samples,len(subfolders2))) 437 | for subdir2 in random_subfolders2: 438 | subdir2_path = os.path.join(subdir_path, subdir2) # We will receive the CSV files 439 | data = pd.read_csv(subdir2_path) # Read the time serie 440 | cnt = collections.Counter(data['Label']) 441 | num_adl = cnt[0] 442 | num_fall = cnt[1] 443 | 444 | if num_fall == 30: 445 | X_train_fall_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 446 | Y_train_fall_user.append(1) 447 | else : 448 | X_train_adl_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 449 | Y_train_adl_user.append(0) 450 | 451 | root_dir_adl = "data_division/MobiAct/Users/User"+str(client_num)+"/ADL" 452 | 453 | for subdir in os.listdir(root_dir_adl): 454 | subdir_path = os.path.join(root_dir_adl, subdir) 455 | subfolders2 = files = [f for f in os.listdir(subdir_path) if os.path.isfile(os.path.join(subdir_path, f))] 456 | if (len(subfolders2) != 0): 457 | random_subfolders2 = random.sample(subfolders2, k=random.randint(min_samples,len(subfolders2))) 458 | for subdir2 in random_subfolders2: 459 | subdir2_path = os.path.join(subdir_path, subdir2) 460 | data = pd.read_csv(subdir2_path) 461 | X_train_adl_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 462 | Y_train_adl_user.append(0) 463 | 464 | X_train_user = X_train_fall_user + X_train_adl_user 465 | Y_train_user = Y_train_fall_user + Y_train_adl_user 466 | 467 | X_train_user = np.asarray(X_train_user) 468 | Y_train_user = np.asarray(Y_train_user).reshape(-1, ) 469 | Y_train_user = enc.fit_transform(Y_train_user) 470 | 471 | Dict_users["User"+str(client_num)] = TensorDataset(torch.from_numpy(X_train_user), torch.from_numpy(Y_train_user)) 472 | 473 | os.makedirs("data_division/MobiAct/unbalanced_division") 474 | 475 | # Save the user dict 476 | with open('data_division/MobiAct/unbalanced_division/dict_users.pickle', 'wb') as handle: 477 | pickle.dump(Dict_users, handle, protocol=pickle.HIGHEST_PROTOCOL) 478 | 479 | def label_skew_division(self, seed, number_types_per_clients): 480 | 481 | Dict_users = {} 482 | enc = LabelEncoder() 483 | random.seed(seed) 484 | 485 | columns = ['FOL', 'FKL', 'SDL', 'BSC', 'STD', 'WAL', 'JOG', 'JUM', 'STU', 'STN', 'SCH', 'SIT', 'CHU', 'CSI', 'CSO', 'LYI'] 486 | 487 | num_sample_per_user = {f'client {i}': {col: [] for col in columns} for i in self.FL_clinets} 488 | 489 | for client_num in tqdm(self.FL_clinets) : 490 | 491 | root_dir = "data_division/MobiAct/Users/User"+str(client_num) 492 | 493 | subdirs = [] 494 | for subdir in os.listdir(root_dir): #Fall and ADL 495 | subdir_path = os.path.join(root_dir, subdir) 496 | # check if it is a directory 497 | for subsubdir in os.listdir(subdir_path): # The types of Fall and ADL 498 | subsubdir_path = os.path.join(subdir_path, subsubdir) 499 | subfolders2 = [f for f in os.listdir(subsubdir_path) if os.path.isfile(os.path.join(subsubdir_path, f))] 500 | if(len(subfolders2) != 0): 501 | subdirs.append(subsubdir_path) 502 | 503 | if (len(subdirs) > number_types_per_clients) : 504 | random_types = random.sample(subdirs, number_types_per_clients) 505 | else : 506 | random_types = random.sample(subdirs, len(subdirs)) 507 | 508 | #print("client_num : ", client_num, "list ADL : ",random_types) 509 | my_dict = { 'FOL': 0, 'FKL': 0, 'SDL': 0, 'BSC': 0, 'STD': 0, 'WAL': 0, 'JOG': 0, 'JUM': 0, 'STU': 0, 'STN': 0, 'SCH': 0, 'SIT': 0, 'CHU': 0, 'CSI': 0, 'CSO': 0, 'LYI': 0} 510 | 511 | gentypes_name = [os.path.basename(os.path.dirname(subdir_path)) for subdir_path in random_types] 512 | small_type = [os.path.basename(os.path.split(subdir_path)[1]) for subdir_path in random_types] 513 | 514 | X_train_user = [] 515 | Y_train_user = [] 516 | 517 | for subdir_path in random_types: 518 | cmpt = 0 519 | parts = subdir_path.split('\\') 520 | target = parts[-1] 521 | for subdir2 in os.listdir(subdir_path): 522 | cmpt = cmpt + 1 523 | subdir2_path = os.path.join(subdir_path, subdir2) # We will receive the CSV files 524 | data = pd.read_csv(subdir2_path) # Read the time serie 525 | 526 | X_train_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 527 | 528 | if (os.path.basename(os.path.dirname(subdir_path)) == "Fall") : 529 | 530 | cnt = collections.Counter(data['Label']) 531 | num_adl = cnt[0] 532 | num_fall = cnt[1] 533 | 534 | if num_fall == 30: 535 | Y_train_user.append(1) 536 | else : 537 | Y_train_user.append(0) 538 | else : 539 | Y_train_user.append(0) 540 | 541 | num_sample_per_user['client '+str(client_num)][target] = cmpt 542 | 543 | X_train_user = np.asarray(X_train_user) 544 | #print("length totale", len(X_train_user), "length of each : ", taille_donnees) 545 | Y_train_user = np.asarray(Y_train_user).reshape(-1, ) 546 | Y_train_user = enc.fit_transform(Y_train_user) 547 | Dict_users["User"+str(client_num)] = TensorDataset(torch.from_numpy(X_train_user), torch.from_numpy(Y_train_user)) 548 | 549 | os.makedirs("data_division/MobiAct/label_skew_division"+str(number_types_per_clients)) 550 | 551 | # Save the user dict 552 | with open('data_division/MobiAct/label_skew_division'+str(number_types_per_clients)+'/dict_users.pickle', 'wb') as handle: 553 | pickle.dump(Dict_users, handle, protocol=pickle.HIGHEST_PROTOCOL) 554 | 555 | 556 | columns_to_keep = ['FOL', 'FKL', 'SDL', 'BSC', 'STD', 'WAL', 'JOG', 'JUM', 'STU', 'STN', 'SCH', 'SIT', 'CHU', 'CSI', 'CSO', 'LYI'] 557 | dict_users_sample = {} 558 | for client, data in num_sample_per_user.items(): 559 | user_data = [data[column] for column in columns_to_keep] 560 | dict_users_sample[client] = user_data 561 | 562 | for key, value in dict_users_sample.items(): 563 | if isinstance(value, list): # Check if the value is a list 564 | for i in range(len(value)): 565 | if isinstance(value[i], list) and not value[i]: # Check if it's an empty list 566 | dict_users_sample[key][i] = 0 # Replace empty list with 0 567 | 568 | new_dict_users_sample = {'client {}'.format(i): value for i, (key, value) in enumerate(dict_users_sample.items(), start=1)} 569 | 570 | new_dict_users_sample['client 10'], new_dict_users_sample['client 12'] = new_dict_users_sample['client 12'], new_dict_users_sample['client 10'] 571 | 572 | return new_dict_users_sample 573 | 574 | 575 | def label_skew_unbalanced_division(self, seed, number_types_per_clients, min_samples = 1): 576 | 577 | Dict_users = {} 578 | enc = LabelEncoder() 579 | random.seed(seed) 580 | 581 | for client_num in tqdm(self.FL_clinets) : 582 | root_dir = "data_division/MobiAct/Users/User"+str(client_num) 583 | 584 | subdirs = [] 585 | for subdir in os.listdir(root_dir): #Fall and ADL 586 | subdir_path = os.path.join(root_dir, subdir) 587 | # check if it is a directory 588 | for subsubdir in os.listdir(subdir_path): # The types of Fall and ADL 589 | subsubdir_path = os.path.join(subdir_path, subsubdir) 590 | subfolders2 = [f for f in os.listdir(subsubdir_path) if os.path.isfile(os.path.join(subsubdir_path, f))] 591 | if(len(subfolders2) != 0): 592 | subdirs.append(subsubdir_path) 593 | 594 | random_types = random.sample(subdirs, min(len(subdirs), number_types_per_clients)) 595 | gentypes_name = [os.path.basename(os.path.dirname(subdir_path)) for subdir_path in random_types] 596 | small_type = [os.path.basename(os.path.split(subdir_path)[1]) for subdir_path in random_types] 597 | 598 | X_train_user = [] 599 | Y_train_user = [] 600 | 601 | 602 | for subdir_path in random_types: 603 | subfolders2 = [f for f in os.listdir(subdir_path) if os.path.isfile(os.path.join(subdir_path, f))] 604 | random_subfolders2 = random.sample(subfolders2, k=random.randint(min_samples,len(subfolders2))) 605 | for subdir2 in random_subfolders2: 606 | subdir2_path = os.path.join(subdir_path, subdir2) # We will receive the CSV files 607 | data = pd.read_csv(subdir2_path) # Read the time serie 608 | 609 | X_train_user.append(np.array(data[['Accelerometer_x', 'Accelerometer_y', 'Accelerometer_z']])) 610 | 611 | if (os.path.basename(os.path.dirname(subdir_path)) == "Fall") : 612 | 613 | cnt = collections.Counter(data['Label']) 614 | num_adl = cnt[0] 615 | num_fall = cnt[1] 616 | 617 | if num_fall == 30: 618 | Y_train_user.append(1) 619 | else : 620 | Y_train_user.append(0) 621 | else : 622 | Y_train_user.append(0) 623 | 624 | X_train_user = np.asarray(X_train_user) 625 | Y_train_user = np.asarray(Y_train_user).reshape(-1, ) 626 | Y_train_user = enc.fit_transform(Y_train_user) 627 | 628 | Dict_users["User"+str(client_num)] = TensorDataset(torch.from_numpy(X_train_user), torch.from_numpy(Y_train_user)) 629 | 630 | os.makedirs("data_division/MobiAct/label_skew_unbalanced_division") 631 | 632 | # Save the user dict 633 | with open('data_division/MobiAct/label_skew_unbalanced_division/dict_users.pickle', 'wb') as handle: 634 | pickle.dump(Dict_users, handle, protocol=pickle.HIGHEST_PROTOCOL) 635 | -------------------------------------------------------------------------------- /serverFL/Server_FLASHRL.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import timeit 3 | import clientFL.Client as Client 4 | from tqdm import tqdm 5 | import numpy as np 6 | import random 7 | import torch 8 | from torch.utils.data import DataLoader 9 | from sklearn.decomposition import PCA 10 | import torch.nn as nn 11 | import RL.DQL as DQL 12 | import numpy as np 13 | from sklearn.metrics import f1_score,recall_score,precision_score 14 | from torch.nn import functional as F 15 | 16 | class Server_FLASHRL(object): 17 | 18 | def __init__(self, num_clients, global_model, dict_clients, loss_fct, B, dataset_test, learning_rate, momentum, clients_info): 19 | """ 20 | Initialize the Server_FLASHRL object. 21 | 22 | Parameters: 23 | - num_clients: The number of clients in the system. 24 | - global_model: The global model. 25 | - dict_clients: A dictionary containing information about each client. 26 | - loss_fct: The loss function used for training. 27 | - B: The size of the batch. 28 | - dataset_test: The test dataset used for evaluation. 29 | - learning_rate: The learning rate for training. 30 | - momentum: The momentum parameter for training. 31 | - clients_info: Information about the clients for simulation purposes. 32 | """ 33 | # The number of clients in the system 34 | self.N = num_clients 35 | # The global model (our goal) 36 | self.model = global_model 37 | # A list containing the @ of each client 38 | self.list_clients = [] 39 | # The size of the batch 40 | self.B = B 41 | # The test dataset that helps us evaluate each round 42 | self.dataset_test = dataset_test 43 | # Test Dataloader 44 | self.testdataloader = DataLoader(self.dataset_test, batch_size= self.B) 45 | # The distribution of clients in a dict 46 | self.dict_clients = dict_clients 47 | # The loss function 48 | self.loss_function = copy.deepcopy(loss_fct) 49 | # The number of parameters 50 | self.number_parameters = sum(p.numel() for p in self.model.parameters() if p.requires_grad) 51 | 52 | # Call a function to create the clients (simulation) 53 | self.clients_info = clients_info 54 | self.create_clients(learning_rate, momentum) 55 | 56 | 57 | def create_clients(self, learning_rate, momentum): 58 | ''' 59 | Create client objects based on the provided information and add them to the list. 60 | 61 | Parameters: 62 | - learning_rate: The learning rate for training clients. 63 | - momentum: The momentum parameter for training clients. 64 | ''' 65 | cpt = 0 66 | for client_name in self.dict_clients.keys(): 67 | # Create a client object with the specified parameters 68 | client = Client.Client( 69 | self.clients_info[cpt][0], # client name 70 | self.dict_clients[client_name], # client information 71 | copy.deepcopy(self.model), # copy of the global model 72 | self.clients_info[cpt][2], # number of cores 73 | self.clients_info[cpt][3], # frequency distribution 74 | self.clients_info[cpt][4], # bandwidth distribution 75 | copy.deepcopy(self.loss_function), # copy of the loss function 76 | self.B, # size of the batch 77 | learning_rate, # learning rate 78 | momentum # momentum 79 | ) 80 | # Append the client to the list 81 | self.list_clients.append(client) 82 | cpt += 1 83 | 84 | 85 | def weight_scalling_factor(self, client, active_clients): 86 | ''' 87 | Determine the factor assigned to a given client. 88 | 89 | Parameters: 90 | - client: The client for which the factor is determined. 91 | - active_clients: List of active clients. 92 | 93 | Returns: 94 | - The scaling factor for the client. 95 | ''' 96 | # Calculate the total training data points across clients for this round 97 | global_count = sum([client_obj.get_size() for client_obj in active_clients]) 98 | # Get the total number of data points held by a client 99 | local_count = client.get_size() 100 | 101 | return local_count / global_count 102 | 103 | def scale_model_weights(self, weight, scalar): 104 | ''' 105 | Multiply the local parameters of each client by its factor. 106 | 107 | Parameters: 108 | - weight: The local model weights. 109 | - scalar: The scaling factor. 110 | 111 | Returns: 112 | - Scaled local model weights. 113 | ''' 114 | w_scaled = copy.deepcopy(weight) 115 | 116 | for k in weight.keys(): 117 | w_scaled[k] = scalar * w_scaled[k] 118 | 119 | return w_scaled 120 | 121 | def sum_scaled_weights(self, scaled_weight_list): 122 | ''' 123 | Aggregate different parameters. 124 | 125 | Parameters: 126 | - scaled_weight_list: List of scaled models weights. 127 | 128 | Returns: 129 | - Aggregated weights. 130 | ''' 131 | w_avg = copy.deepcopy(scaled_weight_list[0]) 132 | 133 | for k in w_avg.keys(): 134 | tmp = torch.zeros_like(scaled_weight_list[0][k], dtype=torch.float32) 135 | 136 | for i in range(len(scaled_weight_list)): 137 | tmp += scaled_weight_list[i][k] 138 | 139 | w_avg[k].copy_(tmp) 140 | 141 | return w_avg 142 | 143 | 144 | def flatten(self, weight): 145 | ''' 146 | Flatten the parameters of a model. 147 | 148 | Parameters: 149 | - weight: The model weights. 150 | 151 | Returns: 152 | - Flattened model weights. 153 | ''' 154 | weight_flatten = [] 155 | 156 | for param in weight.values(): 157 | weight_flatten.append(np.array(param).reshape(-1)) 158 | 159 | weight_flatten = [item for sublist in weight_flatten for item in sublist] 160 | 161 | return weight_flatten 162 | 163 | def flatten_state(self, state_list): 164 | ''' 165 | Flatten a list of states. 166 | 167 | Parameters: 168 | - state_list: List of states where each state is represented as a list of sublists. 169 | 170 | Returns: 171 | - Flattened list of states as a torch.Tensor. 172 | ''' 173 | result_list = [] 174 | max_length = max(len(sublist) for sublist in state_list) # Find the maximum length of sublists 175 | 176 | for i in range(max_length): 177 | for sublist in state_list: 178 | if i < len(sublist): 179 | element = sublist[i] 180 | if isinstance(element, list): 181 | result_list.extend(element) 182 | else: 183 | result_list.append(element) 184 | 185 | return torch.Tensor(result_list) 186 | 187 | 188 | def select_active_clients_random(self, comm_round, C): 189 | ''' 190 | Randomly select a fraction of active clients for a training round. 191 | 192 | Parameters: 193 | - comm_round: The communication round. 194 | - C: Fraction of active clients. 195 | 196 | Returns: 197 | - List of indices representing the selected active clients. 198 | ''' 199 | client_index = np.arange(0, len(self.list_clients)) 200 | 201 | m = int(max(C * self.N, 1)) # max between c*k and 1 202 | 203 | active_clients = random.sample(list(client_index), k=m) # Select a fraction of clients 204 | 205 | return active_clients 206 | 207 | 208 | def global_train(self, comms_round, C, E, mu, lamb, rep_init, batch_size, verbose_test=1, verbos=0, type_data="others", init_weights=None, init_commround=None, reputation_init=None): 209 | ''' 210 | Initiate the global training process for federated learning. 211 | 212 | Parameters: 213 | - comms_round: Number of communication rounds. 214 | - C: Fraction of active clients in each round. 215 | - E: Number of local training iterations. 216 | - mu: FedProx parameter. 217 | - lamb: coaf of past contribtions in the reputation value. 218 | - rep_init: Initial reputation value for clients. 219 | - batch_size: Size of the RL batch. 220 | - verbose_test: Whether to print test results (1 for yes, 0 for no). 221 | - verbos: Verbosity level for training logs. 222 | - type_data: Type of data used for training (e.g., "others" or "Fall"). 223 | - init_weights: Initial weights for the global model. 224 | - init_commround: Initial communication round. 225 | - reputation_init: Initial reputation values for clients. 226 | 227 | Returns: 228 | - Dictionary containing training results. 229 | ''' 230 | if type_data == "Fall": 231 | return self.global_train_fall(comms_round, C, E, mu, lamb, rep_init, batch_size, verbose_test, verbos, type_data, init_weights, init_commround, reputation_init) 232 | else: 233 | return self.global_train_others(comms_round, C, E, mu, lamb, rep_init, batch_size, verbose_test, verbos, type_data, init_weights, init_commround, reputation_init) 234 | 235 | 236 | def global_train_others(self, comms_round, C, E, mu, lamb, rep_init, batch_size, verbose_test = 1, verbos = 0, type_data = "others", 237 | init_weights = None, init_commround = None, reputation_init = None): 238 | ''' 239 | Federated learning training process for a scenario involving multiple clients for fall detection. 240 | 241 | Parameters: 242 | - comms_round: Number of communication rounds. 243 | - C: Fraction of active clients in each round. 244 | - E: Number of local training iterations. 245 | - mu: FedProx parameter. 246 | - lamb: coaf of past contribtions in the reputation value. 247 | - rep_init: Initial reputation value for clients. 248 | - batch_size: Size of the RL batch. 249 | - verbose_test: Whether to print test results (1 for yes, 0 for no). 250 | - verbos: Verbosity level for training logs. 251 | - type_data: Type of data used for training (e.g., "others" or "Fall"). 252 | - init_weights: Initial weights for the global model. 253 | - init_commround: Initial communication round. 254 | - reputation_init: Initial reputation values for clients. 255 | 256 | Returns: 257 | - Dictionary containing training results. 258 | ''' 259 | 260 | m = int(max(C*self.N, 1)) 261 | 262 | rounds = [] 263 | accuarcy = [] 264 | reputation_list = [] 265 | rewards = [] 266 | loss = [] 267 | 268 | time_rounds = [] 269 | time_rounds_sum = [] 270 | best_model_weights = {} 271 | best_accuracy = 0 272 | 273 | 274 | num_param = sum(p.numel() for p in self.model.parameters() if p.requires_grad) 275 | 276 | # Initialize the first state 277 | weight_list_for_iteration = [] 278 | numbersamples_for_iteration = [] 279 | numbercores_for_iteration = [] 280 | frequencies_for_iteration = [] 281 | bandwidth_for_iteration = [] 282 | 283 | 284 | # Append weight of global model 285 | max_latency = 0 286 | min_latency = 100000000 287 | 288 | if (init_weights != None) : 289 | reputation_clients_t = reputation_init 290 | reputation_list.append(copy.deepcopy(reputation_clients_t)) 291 | 292 | #print('modèle préentrainner') 293 | self.model.load_state_dict(copy.deepcopy(init_weights)) 294 | 295 | comm_start = init_commround 296 | 297 | for client in self.list_clients : 298 | client.set_weights(copy.deepcopy(init_weights)) 299 | 300 | # For each client perform one epoch of SGD to get the weights 301 | for client in self.list_clients: 302 | frequency_client = random.choice(client.frequency) 303 | bandwidth_client = random.choice(client.bandwidth) 304 | 305 | latency_min = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * max(client.frequency)) + (self.number_parameters * 64)/(1000000 * max(client.bandwidth)) 306 | latency_max = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * min(client.frequency)) + (self.number_parameters * 64)/(1000000 * min(client.bandwidth)) 307 | 308 | if (latency_max > max_latency ): 309 | max_latency = latency_max 310 | 311 | if (latency_min < min_latency): 312 | min_latency = latency_min 313 | 314 | client_w_for_first_iteration = client.get_model() 315 | 316 | weight_list_for_iteration.append(self.flatten(client_w_for_first_iteration)) 317 | numbersamples_for_iteration.append(client.get_size()) 318 | numbercores_for_iteration.append(client.numbercores) 319 | frequencies_for_iteration.append(frequency_client) 320 | bandwidth_for_iteration.append(bandwidth_client) 321 | 322 | else : 323 | reputation_clients_t = np.full(self.N, rep_init) 324 | reputation_list.append(copy.deepcopy(reputation_clients_t)) 325 | comm_start = 0 326 | # For each client perform one epoch of SGD to get the weights 327 | for client in self.list_clients: 328 | frequency_client = random.choice(client.frequency) 329 | bandwidth_client = random.choice(client.bandwidth) 330 | 331 | latency_min = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * max(client.frequency)) + (self.number_parameters * 64)/(1000000 * max(client.bandwidth)) 332 | latency_max = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * min(client.frequency)) + (self.number_parameters * 64)/(1000000 * min(client.bandwidth)) 333 | 334 | if (latency_max > max_latency ): 335 | max_latency = latency_max 336 | 337 | if (latency_min < min_latency): 338 | min_latency = latency_min 339 | 340 | client_w_for_first_iteration = client.train(self.model.state_dict(), 1, mu, type_data, verbos) 341 | 342 | weight_list_for_iteration.append(self.flatten(client_w_for_first_iteration)) 343 | numbersamples_for_iteration.append(client.get_size()) 344 | numbercores_for_iteration.append(client.numbercores) 345 | frequencies_for_iteration.append(frequency_client) 346 | bandwidth_for_iteration.append(bandwidth_client) 347 | 348 | # Apply PCA 349 | 350 | pca = PCA(n_components = len(self.list_clients)) 351 | 352 | # get the weight with PCA 353 | weight_list_for_iteration_pca = pca.fit_transform(weight_list_for_iteration) 354 | 355 | state_list = [] 356 | 357 | for cpt in range(0, len(self.list_clients)): 358 | client_state = [] 359 | 360 | client_state.append(list(weight_list_for_iteration_pca[cpt])) 361 | client_state.append(numbersamples_for_iteration[cpt]) 362 | client_state.append(numbercores_for_iteration[cpt]) 363 | client_state.append(frequencies_for_iteration[cpt]) 364 | client_state.append(bandwidth_for_iteration[cpt]) 365 | 366 | state_list.append(client_state) 367 | 368 | # State is a concatenation of the different reduced weights 369 | state = self.flatten_state(state_list) 370 | 371 | # init dql 372 | if (init_weights != None) : 373 | dql = DQL.DQL(len(state), len(self.list_clients), batch_size, flag = True) 374 | else: 375 | dql = DQL.DQL(len(state), len(self.list_clients), batch_size) 376 | 377 | Accuracy_global_pervoius, loss_test = self.test(type_data) 378 | 379 | #print("Accuracy_global_pervious = ", Accuracy_global_pervoius) 380 | 381 | self.model.train() 382 | 383 | list_loss_DQL = [] 384 | 385 | # For each communication round 386 | for comm_round in range(comm_start, comms_round): 387 | rounds.append(comm_round+1) 388 | 389 | if (verbos == 1): 390 | print("*************************************************************************************") 391 | print("Communication round n : ", comm_round + 1) 392 | 393 | # Receive the parameters of the global model (first we will have the initialized parameters) 394 | global_weights = self.model.state_dict() 395 | 396 | # Verify if we need to update the target network 397 | if (comm_round + 1) % dql.update_rate == 0: 398 | dql.update_target_network() 399 | 400 | 401 | if (comm_round == 0): 402 | #random selection 403 | active_clients_index = self.select_active_clients_random(comm_round, C) 404 | active_clients_index.sort() 405 | else: 406 | # epslion greedy 407 | active_clients_index = dql.multiaction_selection(state, C, comm_round, mode = "Mode1") 408 | active_clients_index.sort() 409 | 410 | #print("the active clients are : ", active_clients_index) 411 | 412 | # List to collect the parameters of the model * weight of each client 413 | scaled_local_weight_list = [] 414 | 415 | # list of active client objetcs 416 | active_clients = [self.list_clients[i] for i in active_clients_index] 417 | 418 | weight_local_clients = [] 419 | 420 | time_roundt = [] 421 | # For each selected customer 422 | for client_index in active_clients_index: 423 | if (verbos == 1): 424 | print("Entrainnement locale du client : ", client.client_name) 425 | 426 | # Training on local client data 427 | client_w = self.list_clients[client_index].train(global_weights, E, mu, type_data, verbos) 428 | 429 | # Append the local model weight 430 | weight_local_clients.append(self.flatten(client_w)) 431 | 432 | # MAJ des weights PCA 433 | state_list[client_index][0] = list((pca.transform(np.array(self.flatten(copy.deepcopy(client_w))).reshape(1, -1)))[0]) 434 | 435 | # Avoir les parametres du modele locale * factor 436 | client_scaling_factor = self.weight_scalling_factor(self.list_clients[client_index], active_clients) #nk/n' 437 | 438 | client_scaled_weight = self.scale_model_weights(client_w, client_scaling_factor) #weights*factor 439 | scaled_local_weight_list.append(client_scaled_weight) #enregistrement dans la liste 440 | 441 | frequency_client = random.choice(self.list_clients[client_index].frequency) 442 | bandwidth_client = random.choice(self.list_clients[client_index].bandwidth) 443 | 444 | latency_client = (self.list_clients[client_index].get_size()*64*40*20)/(self.list_clients[client_index].numbercores * 1000000 * frequency_client) + (self.number_parameters * 64)/(1000000 * bandwidth_client) 445 | 446 | state_list[client_index][3] = frequency_client 447 | state_list[client_index][4] = bandwidth_client 448 | #print("Client name : ", self.list_clients[client_index].client_name, " with latency :", latency_client) 449 | time_roundt.append(latency_client) 450 | 451 | #print("latencies : ", time_roundt) 452 | time_rounds.append(max(time_roundt)) 453 | time_rounds_sum.append(sum(time_roundt)) 454 | 455 | # The average of the different points received 456 | average_weights = self.sum_scaled_weights(scaled_local_weight_list) 457 | 458 | # Copy the weights in the global model 459 | self.model.load_state_dict(average_weights) 460 | 461 | Accuray_global_t, loss_test = self.test(type_data) 462 | 463 | # Test the global model each round 464 | if (verbose_test == 1): 465 | print("Training round n :", (comm_round+1),", Test accuarcy : ", round(Accuray_global_t.numpy()*100, 2) ,", Test loss :", round(loss_test, 2)) 466 | print("*************************************************************************************") 467 | 468 | 469 | if (Accuray_global_t > best_accuracy): 470 | best_accuracy = Accuray_global_t 471 | best_model_weights = copy.deepcopy(average_weights) 472 | 473 | 474 | accuarcy.append(Accuray_global_t) 475 | loss.append(loss_test) 476 | 477 | # Update reduced global parameter 478 | #weight_list_for_iteration_pca[0] = (pca.transform(np.array(self.flatten(copy.deepcopy(self.model.state_dict()))).reshape(1, -1)))[0] 479 | 480 | # Next state 481 | next_state = self.flatten_state(state_list) 482 | 483 | # Action representation 484 | action = np.zeros(len(self.list_clients)) 485 | action[active_clients_index] = 1 486 | 487 | 488 | # Calculate the reward 489 | 490 | # Calculate the utility function (score of each participant client) 491 | 492 | 493 | # Calculate the normalized distance between the local weight and the global model weight 494 | normalized_distance = 1/num_param * (np.sum((np.array(weight_local_clients) - np.array(self.flatten(self.model.state_dict())))/np.array(self.flatten(average_weights)), axis = 1)) 495 | # Utility is a positive number that indicate if the client selected contributed in a good way or not 496 | # When utility is near 1, the client did contribute in a good way otherwise not 497 | utility_clients = np.array([]) 498 | 499 | if(Accuray_global_t > Accuracy_global_pervoius): 500 | # if we had an increase in the F1 score we want to minimaze the distance 501 | # ie : the client near the global weights are the good one 502 | utility_clients = np.exp(-np.abs(normalized_distance)) 503 | else: 504 | utility_clients = 1- np.exp(-np.abs(normalized_distance)) 505 | 506 | 507 | # Now we will use the same reputation eq as the FedDRL_Reputation, just replacing the accuracies with the utility 508 | reputation_clients_t[active_clients_index] = (1 - lamb)*reputation_clients_t[active_clients_index] + lamb*(utility_clients - (((np.array(time_roundt)) - min_latency) / (max_latency - min_latency))) 509 | 510 | reputation_list.append(copy.deepcopy(reputation_clients_t)) 511 | reward = np.array(reputation_clients_t[active_clients_index]) 512 | #print("the reward is ", reward) 513 | 514 | rewards.append(reward) 515 | 516 | #store the transition information 517 | 518 | if (comm_round == comms_round - 1): 519 | dql.store_transistion(state, action, reward, next_state, done = True) 520 | else: 521 | dql.store_transistion(state, action, reward, next_state, done = False) 522 | 523 | 524 | #update current state to next state 525 | state = copy.deepcopy(next_state) 526 | Accuracy_global_pervoius = Accuray_global_t 527 | 528 | loss_dql = dql.train(comm_round, mode = "Mode1") 529 | list_loss_DQL.append(loss_dql) 530 | 531 | for client in self.list_clients: 532 | client.delete_model() 533 | 534 | dict_result = { 535 | "Best_model_weights": best_model_weights, 536 | "Accuracy": accuarcy, 537 | "Loss" : loss, 538 | "Timeurounds" : time_rounds, 539 | "Timesum" : time_rounds_sum, 540 | "Reputation" : reputation_list, 541 | "Rewards" : rewards, 542 | "LossDQL" : list_loss_DQL 543 | } 544 | 545 | return dict_result 546 | 547 | def global_train_fall(self, comms_round, C, E, mu, lamb, rep_init, batch_size, verbose_test = 1, verbos = 0, type_data = "others", 548 | init_weights = None, init_commround = None, reputation_init = None): 549 | ''' 550 | Federated learning training process for a scenario involving multiple clients. 551 | 552 | Parameters: 553 | - comms_round: Number of communication rounds. 554 | - C: Fraction of active clients in each round. 555 | - E: Number of local training iterations. 556 | - mu: FedProx parameter. 557 | - lamb: coaf of past contribtions in the reputation value. 558 | - rep_init: Initial reputation value for clients. 559 | - batch_size: Size of the RL batch. 560 | - verbose_test: Whether to print test results (1 for yes, 0 for no). 561 | - verbos: Verbosity level for training logs. 562 | - type_data: Type of data used for training (e.g., "others" or "Fall"). 563 | - init_weights: Initial weights for the global model. 564 | - init_commround: Initial communication round. 565 | - reputation_init: Initial reputation values for clients. 566 | 567 | Returns: 568 | - Dictionary containing training results. 569 | ''' 570 | 571 | #random.seed(self.seed) 572 | 573 | m = int(max(C*self.N, 1)) 574 | 575 | rounds = [] 576 | accuarcy = [] 577 | recall = [] 578 | precision = [] 579 | loss = [] 580 | f1score = [] 581 | reputation_list = [] 582 | rewards = [] 583 | 584 | time_rounds = [] 585 | time_rounds_sum = [] 586 | best_model_weights = {} 587 | best_f1score = 0 588 | 589 | reputation_clients_t = np.full(self.N, rep_init) 590 | reputation_list.append(copy.deepcopy(reputation_clients_t)) 591 | 592 | num_param = sum(p.numel() for p in self.model.parameters() if p.requires_grad) 593 | 594 | # Initialize the first state 595 | weight_list_for_iteration = [] 596 | numbersamples_for_iteration = [] 597 | numbercores_for_iteration = [] 598 | frequencies_for_iteration = [] 599 | bandwidth_for_iteration = [] 600 | list_loss_DQL = [] 601 | # Append weight of global model 602 | max_latency = 0 603 | min_latency = 100000000 604 | 605 | 606 | if (init_weights != None) : 607 | #print("hello preentrainner ") 608 | reputation_clients_t = reputation_init 609 | reputation_list.append(copy.deepcopy(reputation_clients_t)) 610 | 611 | #print('modèle préentrainner') 612 | self.model.load_state_dict(copy.deepcopy(init_weights)) 613 | 614 | comm_start = init_commround 615 | 616 | for client in self.list_clients : 617 | client.set_weights(copy.deepcopy(init_weights)) 618 | 619 | # For each client perform one epoch of SGD to get the weights 620 | for client in self.list_clients: 621 | frequency_client = random.choice(client.frequency) 622 | bandwidth_client = random.choice(client.bandwidth) 623 | 624 | latency_min = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * max(client.frequency)) + (self.number_parameters * 64)/(1000000 * max(client.bandwidth)) 625 | latency_max = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * min(client.frequency)) + (self.number_parameters * 64)/(1000000 * min(client.bandwidth)) 626 | 627 | if (latency_max > max_latency ): 628 | max_latency = latency_max 629 | 630 | if (latency_min < min_latency): 631 | min_latency = latency_min 632 | 633 | client_w_for_first_iteration = client.get_model() 634 | 635 | weight_list_for_iteration.append(self.flatten(client_w_for_first_iteration)) 636 | numbersamples_for_iteration.append(client.get_size()) 637 | numbercores_for_iteration.append(client.numbercores) 638 | frequencies_for_iteration.append(frequency_client) 639 | bandwidth_for_iteration.append(bandwidth_client) 640 | 641 | else : 642 | reputation_clients_t = np.full(self.N, rep_init) 643 | reputation_list.append(copy.deepcopy(reputation_clients_t)) 644 | comm_start = 0 645 | # For each client perform one epoch of SGD to get the weights 646 | for client in self.list_clients: 647 | frequency_client = random.choice(client.frequency) 648 | bandwidth_client = random.choice(client.bandwidth) 649 | 650 | latency_min = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * max(client.frequency)) + (self.number_parameters * 64)/(1000000 * max(client.bandwidth)) 651 | latency_max = (client.get_size()*64*40*20)/(client.numbercores * 1000000 * min(client.frequency)) + (self.number_parameters * 64)/(1000000 * min(client.bandwidth)) 652 | 653 | if (latency_max > max_latency ): 654 | max_latency = latency_max 655 | 656 | if (latency_min < min_latency): 657 | min_latency = latency_min 658 | 659 | client_w_for_first_iteration = client.train(self.model.state_dict(), 1, mu, type_data, verbos) 660 | 661 | weight_list_for_iteration.append(self.flatten(client_w_for_first_iteration)) 662 | numbersamples_for_iteration.append(client.get_size()) 663 | numbercores_for_iteration.append(client.numbercores) 664 | frequencies_for_iteration.append(frequency_client) 665 | bandwidth_for_iteration.append(bandwidth_client) 666 | 667 | pca = PCA(n_components = len(self.list_clients)) 668 | 669 | # get the weight with PCA 670 | weight_list_for_iteration_pca = pca.fit_transform(weight_list_for_iteration) 671 | 672 | state_list = [] 673 | 674 | for cpt in range(0, len(self.list_clients)): 675 | client_state = [] 676 | 677 | client_state.append(list(weight_list_for_iteration_pca[cpt])) 678 | client_state.append(numbersamples_for_iteration[cpt]) 679 | client_state.append(numbercores_for_iteration[cpt]) 680 | client_state.append(frequencies_for_iteration[cpt]) 681 | client_state.append(bandwidth_for_iteration[cpt]) 682 | 683 | state_list.append(client_state) 684 | 685 | # State is a concatenation of the different reduced weights 686 | state = self.flatten_state(state_list) 687 | 688 | # init dql 689 | # init dql 690 | if (init_weights != None) : 691 | dql = DQL.DQL(len(state), len(self.list_clients), batch_size, flag = True) 692 | else: 693 | dql = DQL.DQL(len(state), len(self.list_clients), batch_size) 694 | 695 | 696 | 697 | _, F1score_global_pervoius, _, _, _ = self.test(type_data) 698 | 699 | client_selected = [] 700 | self.model.train() 701 | 702 | # For each communication round 703 | for comm_round in tqdm(range(comm_start, comms_round)): 704 | rounds.append(comm_round+1) 705 | 706 | if (verbos == 1): 707 | print("*************************************************************************************") 708 | print("Communication round n : ", comm_round + 1) 709 | 710 | # Receive the parameters of the global model (first we will have the initialized parameters) 711 | global_weights = self.model.state_dict() 712 | 713 | # Verify if we need to update the target network 714 | if (comm_round + 1) % dql.update_rate == 0: 715 | dql.update_target_network() 716 | 717 | 718 | if (comm_round == 0): 719 | #random selection 720 | active_clients_index = self.select_active_clients_random(comm_round, C) 721 | active_clients_index.sort() 722 | else: 723 | # epslion greedy 724 | active_clients_index = dql.multiaction_selection(state, C, comm_round, mode = "Mode1") 725 | active_clients_index.sort() 726 | 727 | if (comm_round == 500 ): 728 | active_clients_index = [1, 11, 27, 31, 47] 729 | 730 | if (comm_round == 501 ): 731 | active_clients_index = [1, 2, 24, 39, 44] 732 | #print("the active clients are : ", active_clients_index) 733 | client_selected.append(active_clients_index) 734 | # List to collect the parameters of the model * weight of each client 735 | scaled_local_weight_list = [] 736 | 737 | # list of active client objetcs 738 | active_clients = [self.list_clients[i] for i in active_clients_index] 739 | 740 | weight_local_clients = [] 741 | 742 | time_roundt = [] 743 | # For each selected customer 744 | for client_index in active_clients_index: 745 | if (verbos == 1): 746 | print("Entrainnement locale du client : ", client.client_name) 747 | 748 | # Training on local client data 749 | client_w = self.list_clients[client_index].train(global_weights, E, mu, type_data, verbos) 750 | 751 | # Append the local model weight 752 | weight_local_clients.append(self.flatten(client_w)) 753 | 754 | # MAJ des weights PCA 755 | state_list[client_index][0] = list((pca.transform(np.array(self.flatten(copy.deepcopy(client_w))).reshape(1, -1)))[0]) 756 | 757 | # Avoir les parametres du modele locale * factor 758 | client_scaling_factor = self.weight_scalling_factor(self.list_clients[client_index], active_clients) #nk/n' 759 | 760 | client_scaled_weight = self.scale_model_weights(client_w, client_scaling_factor) #weights*factor 761 | scaled_local_weight_list.append(client_scaled_weight) #enregistrement dans la liste 762 | 763 | frequency_client = random.choice(self.list_clients[client_index].frequency) 764 | bandwidth_client = random.choice(self.list_clients[client_index].bandwidth) 765 | 766 | latency_client = (self.list_clients[client_index].get_size()*64*40*20)/(self.list_clients[client_index].numbercores * 1000000 * frequency_client) + (self.number_parameters * 64)/(1000000 * bandwidth_client) 767 | 768 | state_list[client_index][3] = frequency_client 769 | state_list[client_index][4] = bandwidth_client 770 | #print("Client name : ", self.list_clients[client_index].client_name, " with latency :", latency_client) 771 | time_roundt.append(latency_client) 772 | 773 | 774 | time_rounds.append(max(time_roundt)) 775 | time_rounds_sum.append(sum(time_roundt)) 776 | 777 | # The average of the different points received 778 | average_weights = self.sum_scaled_weights(scaled_local_weight_list) 779 | 780 | # Copy the weights in the global model 781 | self.model.load_state_dict(average_weights) 782 | 783 | acc_test, F1score_global_t, recall_test, precision_test, loss_test = self.test(type_data) 784 | 785 | # Tester le modele gloable chaque round 786 | if (verbose_test == 1): 787 | print("Training round n :", (comm_round+1),", Test accuarcy : ", round(acc_test*100, 2) ,", Test F1_score :", round(F1score_global_t*100, 2)," Test loss :", round(loss_test, 2)) 788 | print("Test Recall : ", round(recall_test*100, 2), "Test Precision : ", round(precision_test*100, 2)) 789 | print("*************************************************************************************") 790 | 791 | if (F1score_global_t > best_f1score): 792 | best_f1score = F1score_global_t 793 | best_model_weights = copy.deepcopy(average_weights) 794 | 795 | 796 | accuarcy.append(acc_test) 797 | f1score.append(F1score_global_t) 798 | recall.append(recall_test) 799 | precision.append(precision_test) 800 | loss.append(loss_test) 801 | 802 | # Update reduced global parameter 803 | #weight_list_for_iteration_pca[0] = (pca.transform(np.array(self.flatten(copy.deepcopy(self.model.state_dict()))).reshape(1, -1)))[0] 804 | 805 | # Next state 806 | next_state = self.flatten_state(state_list) 807 | 808 | # Action representation 809 | action = np.zeros(len(self.list_clients)) 810 | action[active_clients_index] = 1 811 | 812 | 813 | # Calculate the reward 814 | 815 | # Calculate the utility function (score of each participant client) 816 | 817 | 818 | # Calculate the normalized distance between the local weight and the global model weight 819 | normalized_distance = 1/num_param * (np.sum((np.array(weight_local_clients) - np.array(self.flatten(self.model.state_dict())))/np.array(self.flatten(average_weights)), axis = 1)) 820 | # Utility is a positive number that indicate if the client selected contributed in a good way or not 821 | # When utility is near 1, the client did contribute in a good way otherwise not 822 | utility_clients = np.array([]) 823 | 824 | if(F1score_global_t > F1score_global_pervoius): 825 | # if we had an increase in the F1 score we want to minimaze the distance 826 | # ie : the client near the global weights are the good one 827 | utility_clients = np.exp(-np.abs(normalized_distance)) 828 | else: 829 | utility_clients = 1- np.exp(-np.abs(normalized_distance)) 830 | 831 | 832 | # Now we will use the same reputation eq as the FedDRL_Reputation, just replacing the accuracies with the utility 833 | reputation_clients_t[active_clients_index] = (1 - lamb)*reputation_clients_t[active_clients_index] + lamb*(utility_clients - (((np.array(time_roundt)) - min_latency) / (max_latency - min_latency))) 834 | 835 | reputation_list.append(copy.deepcopy(reputation_clients_t)) 836 | 837 | #reward = (0.6*np.array(reputation_clients_t[active_clients_index]))/(0.3*np.array(time_roundt)) - 0.5 838 | #reward = 100*(np.array(reputation_clients_t[active_clients_index])/np.array(time_roundt)) 839 | #print("the utility ", utility_clients) 840 | #print("normalized latency ", (((np.array(time_roundt)) - min_latency) / (max_latency - min_latency))) 841 | #print("reputation ", reputation_clients_t[active_clients_index]) 842 | #reward = 100 * (np.array(reputation_clients_t[active_clients_index]) + (F1score_global_t - F1score_global_pervoius)) 843 | reward = np.array(reputation_clients_t[active_clients_index]) 844 | #print("the reward is ", reward) 845 | 846 | rewards.append(reward) 847 | 848 | #store the transition information 849 | 850 | if (comm_round == comms_round - 1): 851 | dql.store_transistion(state, action, reward, next_state, done = True) 852 | else: 853 | dql.store_transistion(state, action, reward, next_state, done = False) 854 | 855 | 856 | #update current state to next state 857 | state = copy.deepcopy(next_state) 858 | F1score_global_pervoius = F1score_global_t 859 | 860 | loss_dql = dql.train(comm_round, mode = "Mode1") 861 | list_loss_DQL.append(loss_dql) 862 | 863 | for client in self.list_clients: 864 | client.delete_model() 865 | 866 | dict_result = { 867 | "Best_model_weights": best_model_weights, 868 | "Accuracy": accuarcy, 869 | "F1_score": f1score, 870 | "Recall" : recall, 871 | "Precision" : precision, 872 | "Loss" : loss, 873 | "Timeurounds" : time_rounds, 874 | "Timesum" : time_rounds_sum, 875 | "Reputation" : reputation_list, 876 | "Rewards" : rewards, 877 | "LossDQL" : list_loss_DQL, 878 | "Client_selected" : client_selected 879 | } 880 | 881 | return dict_result 882 | 883 | 884 | 885 | def test(self, type_data): 886 | """ 887 | Test the global model on the specified type of data. 888 | 889 | Parameters: 890 | - type_data: Type of data, e.g., "Fall" or "others". 891 | 892 | Returns: 893 | - The test accuracy and loss. 894 | """ 895 | if type_data == "Fall": 896 | return self.test_falldetection() 897 | else: 898 | return self.test_others() 899 | 900 | 901 | def test_others(self): 902 | """ 903 | Evaluate the global model with the test dataset for a task other than fall detection. 904 | 905 | Returns: 906 | - The accuracy and loss on the test dataset. 907 | """ 908 | # Set the model to evaluation mode 909 | self.model.eval() 910 | 911 | # Testing 912 | test_loss = 0 913 | correct = 0 914 | 915 | # Iterate through the test dataset 916 | with torch.no_grad(): 917 | for idx, (data, target) in enumerate(self.testdataloader): 918 | log_probs = self.model(data) 919 | # Sum up batch loss 920 | test_loss += torch.nn.functional.cross_entropy(log_probs, target, reduction='sum').item() 921 | 922 | # Get the index of the max log-probability 923 | y_pred = log_probs.data.max(1, keepdim=True)[1] 924 | correct += y_pred.eq(target.data.view_as(y_pred)).long().cpu().sum() 925 | 926 | # Calculate average test loss and accuracy 927 | test_loss /= len(self.testdataloader.dataset) 928 | accuracy = correct / len(self.testdataloader.dataset) 929 | 930 | return accuracy, test_loss 931 | 932 | 933 | def test_falldetection(self): 934 | """ 935 | Evaluate the global model on a test dataset for fall detection. 936 | 937 | Returns: 938 | - Accuracy, F1 score, recall, precision, and epoch loss on the fall detection test dataset. 939 | """ 940 | epoch_loss = 0 941 | correct, total = 0, 0 942 | targets = list() 943 | preds = list() 944 | acc = 0 945 | 946 | # Set the model to evaluation mode 947 | self.model.eval() 948 | 949 | for idx, (X_batch, y_batch) in enumerate(self.testdataloader): 950 | batchsize = X_batch.shape[0] 951 | 952 | # Forward pass 953 | out = self.model(X_batch.float()) 954 | loss = self.loss_function(out.contiguous(), y_batch.contiguous()) 955 | 956 | # Predictions and metrics 957 | pred = F.log_softmax(out, dim=1).argmax(dim=1) 958 | total += y_batch.size(0) 959 | correct += (pred == y_batch).sum().item() 960 | 961 | targets += list(y_batch) 962 | preds += list(pred) 963 | 964 | epoch_loss += (loss.detach().item() / batchsize) 965 | 966 | # Calculate accuracy and additional metrics 967 | acc = correct / total 968 | f1score = f1_score(targets, preds, zero_division=1) 969 | recall = recall_score(targets, preds, zero_division=1) 970 | precision = precision_score(targets, preds, zero_division=1) 971 | 972 | return acc, f1score, recall, precision, epoch_loss 973 | --------------------------------------------------------------------------------