├── 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 |
--------------------------------------------------------------------------------