MEDfl 2.0.4.dev1__py3-none-any.whl → 2.0.4.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- MEDfl/rw/client.py +98 -29
- MEDfl/rw/model.py +28 -0
- MEDfl/rw/server.py +71 -18
- MEDfl/rw/strategy.py +73 -78
- {MEDfl-2.0.4.dev1.dist-info → MEDfl-2.0.4.dev2.dist-info}/METADATA +1 -1
- MEDfl-2.0.4.dev2.dist-info/RECORD +36 -0
- MEDfl/rw/rwConfig.py +0 -21
- MEDfl/rw/verbose_server.py +0 -21
- MEDfl-2.0.4.dev1.dist-info/RECORD +0 -62
- Medfl/LearningManager/__init__.py +0 -13
- Medfl/LearningManager/client.py +0 -150
- Medfl/LearningManager/dynamicModal.py +0 -287
- Medfl/LearningManager/federated_dataset.py +0 -60
- Medfl/LearningManager/flpipeline.py +0 -192
- Medfl/LearningManager/model.py +0 -223
- Medfl/LearningManager/params.yaml +0 -14
- Medfl/LearningManager/params_optimiser.py +0 -442
- Medfl/LearningManager/plot.py +0 -229
- Medfl/LearningManager/server.py +0 -181
- Medfl/LearningManager/strategy.py +0 -82
- Medfl/LearningManager/utils.py +0 -331
- Medfl/NetManager/__init__.py +0 -10
- Medfl/NetManager/database_connector.py +0 -43
- Medfl/NetManager/dataset.py +0 -92
- Medfl/NetManager/flsetup.py +0 -320
- Medfl/NetManager/net_helper.py +0 -254
- Medfl/NetManager/net_manager_queries.py +0 -142
- Medfl/NetManager/network.py +0 -194
- Medfl/NetManager/node.py +0 -184
- Medfl/__init__.py +0 -3
- Medfl/scripts/__init__.py +0 -2
- Medfl/scripts/base.py +0 -30
- Medfl/scripts/create_db.py +0 -126
- {MEDfl-2.0.4.dev1.dist-info → MEDfl-2.0.4.dev2.dist-info}/LICENSE +0 -0
- {MEDfl-2.0.4.dev1.dist-info → MEDfl-2.0.4.dev2.dist-info}/WHEEL +0 -0
- {MEDfl-2.0.4.dev1.dist-info → MEDfl-2.0.4.dev2.dist-info}/top_level.txt +0 -0
@@ -1,192 +0,0 @@
|
|
1
|
-
import datetime
|
2
|
-
from typing import List
|
3
|
-
import json
|
4
|
-
import pandas as pd
|
5
|
-
|
6
|
-
|
7
|
-
# File: create_query.py
|
8
|
-
from sqlalchemy import text
|
9
|
-
from torch.utils.data import DataLoader, TensorDataset
|
10
|
-
import torch
|
11
|
-
|
12
|
-
from MEDfl.LearningManager.server import FlowerServer
|
13
|
-
from MEDfl.LearningManager.utils import params, test
|
14
|
-
from MEDfl.NetManager.net_helper import get_flpipeline_from_name
|
15
|
-
from MEDfl.NetManager.net_manager_queries import (CREATE_FLPIPELINE_QUERY,
|
16
|
-
DELETE_FLPIPELINE_QUERY , CREATE_TEST_RESULTS_QUERY)
|
17
|
-
from MEDfl.NetManager.database_connector import DatabaseManager
|
18
|
-
|
19
|
-
def create_query(name, description, creation_date, result):
|
20
|
-
query = text(
|
21
|
-
f"INSERT INTO FLpipeline(name, description, creation_date, results) "
|
22
|
-
f"VALUES ('{name}', '{description}', '{creation_date}', '{result}')"
|
23
|
-
)
|
24
|
-
return query
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
class FLpipeline:
|
29
|
-
"""
|
30
|
-
FLpipeline class for managing Federated Learning pipelines.
|
31
|
-
|
32
|
-
Attributes:
|
33
|
-
name (str): The name of the FLpipeline.
|
34
|
-
description (str): A description of the FLpipeline.
|
35
|
-
server (FlowerServer): The FlowerServer object associated with the FLpipeline.
|
36
|
-
|
37
|
-
Methods:
|
38
|
-
__init__(self, name: str, description: str, server: FlowerServer) -> None:
|
39
|
-
Initialize FLpipeline with the specified name, description, and server.
|
40
|
-
|
41
|
-
|
42
|
-
"""
|
43
|
-
|
44
|
-
def __init__(
|
45
|
-
self, name: str, description: str, server: FlowerServer
|
46
|
-
) -> None:
|
47
|
-
self.name = name
|
48
|
-
self.description = description
|
49
|
-
self.server = server
|
50
|
-
self.validate()
|
51
|
-
|
52
|
-
db_manager = DatabaseManager()
|
53
|
-
db_manager.connect()
|
54
|
-
self.eng = db_manager.get_connection()
|
55
|
-
|
56
|
-
def validate(self) -> None:
|
57
|
-
"""
|
58
|
-
Validate the name, description, and server attributes.
|
59
|
-
Raises:
|
60
|
-
TypeError: If the name is not a string, the description is not a string,
|
61
|
-
or the server is not a FlowerServer object.
|
62
|
-
"""
|
63
|
-
if not isinstance(self.name, str):
|
64
|
-
raise TypeError("name argument must be a string")
|
65
|
-
|
66
|
-
if not isinstance(self.description, str):
|
67
|
-
raise TypeError("description argument must be a string")
|
68
|
-
|
69
|
-
# if not isinstance(self.server, FlowerServer):
|
70
|
-
# raise TypeError("server argument must be a FlowerServer")
|
71
|
-
|
72
|
-
def create(self, result: str) -> None:
|
73
|
-
"""
|
74
|
-
Create a new FLpipeline entry in the database with the given result.
|
75
|
-
|
76
|
-
Args:
|
77
|
-
result (str): The result string to store in the database.
|
78
|
-
|
79
|
-
"""
|
80
|
-
creation_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
81
|
-
query = CREATE_FLPIPELINE_QUERY.format(
|
82
|
-
name=self.name,
|
83
|
-
description=self.description,
|
84
|
-
creation_date=creation_date,
|
85
|
-
result=result,
|
86
|
-
)
|
87
|
-
self.eng.execute(text(query))
|
88
|
-
self.id = get_flpipeline_from_name(self.name)
|
89
|
-
try:
|
90
|
-
self.server.fed_dataset.update(
|
91
|
-
FLpipeId=self.id, FedId=self.server.fed_dataset.id
|
92
|
-
)
|
93
|
-
except:
|
94
|
-
pass
|
95
|
-
|
96
|
-
def delete(self) -> None:
|
97
|
-
"""
|
98
|
-
Delete the FLpipeline entry from the database based on its name.
|
99
|
-
|
100
|
-
Note: This is a placeholder method and needs to be implemented based on your specific database setup.
|
101
|
-
|
102
|
-
"""
|
103
|
-
# Placeholder code for deleting the FLpipeline entry from the database based on the name.
|
104
|
-
# You need to implement the actual deletion based on your database setup.
|
105
|
-
self.eng.execute(DELETE_FLPIPELINE_QUERY.format(self.name))
|
106
|
-
|
107
|
-
|
108
|
-
def test_by_node(self, node_name: str, test_frac=1) -> dict:
|
109
|
-
"""
|
110
|
-
Test the FLpipeline by node with the specified test_frac.
|
111
|
-
|
112
|
-
Args:
|
113
|
-
node_name (str): The name of the node to test.
|
114
|
-
test_frac (float, optional): The fraction of the test data to use. Default is 1.
|
115
|
-
|
116
|
-
Returns:
|
117
|
-
dict: A dictionary containing the node name and the classification report.
|
118
|
-
|
119
|
-
"""
|
120
|
-
idx = self.server.fed_dataset.test_nodes.index(node_name)
|
121
|
-
global_model, test_loader = (
|
122
|
-
self.server.global_model,
|
123
|
-
self.server.fed_dataset.testloaders[idx],
|
124
|
-
)
|
125
|
-
|
126
|
-
# Move model to GPU if available
|
127
|
-
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
128
|
-
global_model.model.to(device)
|
129
|
-
|
130
|
-
# Prepare test data
|
131
|
-
test_data = test_loader.dataset
|
132
|
-
num_samples = int(test_frac * len(test_data))
|
133
|
-
test_data = TensorDataset(test_data[:num_samples][0].to(device), test_data[:num_samples][1].to(device))
|
134
|
-
|
135
|
-
# Create DataLoader for test data
|
136
|
-
test_loader = DataLoader(test_data, batch_size=params["test_batch_size"])
|
137
|
-
|
138
|
-
# Perform testing
|
139
|
-
classification_report = test(model=global_model.model, test_loader=test_loader, device=device)
|
140
|
-
|
141
|
-
return {
|
142
|
-
"node_name": node_name,
|
143
|
-
"classification_report": str(classification_report),
|
144
|
-
}
|
145
|
-
|
146
|
-
|
147
|
-
def auto_test(self, test_frac=1) -> List[dict]:
|
148
|
-
"""
|
149
|
-
Automatically test the FLpipeline on all nodes with the specified test_frac.
|
150
|
-
|
151
|
-
Args:
|
152
|
-
test_frac (float, optional): The fraction of the test data to use. Default is 1.
|
153
|
-
|
154
|
-
Returns:
|
155
|
-
List[dict]: A list of dictionaries containing the node names and the classification reports.
|
156
|
-
|
157
|
-
"""
|
158
|
-
result = [
|
159
|
-
self.test_by_node(node, test_frac)
|
160
|
-
for node in self.server.fed_dataset.test_nodes
|
161
|
-
]
|
162
|
-
self.create("\n".join(str(res).replace("'", '"') for res in result))
|
163
|
-
|
164
|
-
# stockage des resultats des tests
|
165
|
-
for entry in result:
|
166
|
-
node_name = entry['node_name']
|
167
|
-
classification_report_str = entry['classification_report']
|
168
|
-
|
169
|
-
# Convert the 'classification_report' string to a dictionary
|
170
|
-
classification_report_dict = json.loads(classification_report_str.replace("'", "\""))
|
171
|
-
try:
|
172
|
-
# Insert record into the 'testResults' table
|
173
|
-
query = CREATE_TEST_RESULTS_QUERY.format(
|
174
|
-
pipelineId = self.id,
|
175
|
-
nodeName = node_name ,
|
176
|
-
confusion_matrix = json.dumps(classification_report_dict['confusion matrix']),
|
177
|
-
accuracy =classification_report_dict['Accuracy'] ,
|
178
|
-
sensivity = classification_report_dict['Sensitivity/Recall'] ,
|
179
|
-
ppv = classification_report_dict['PPV/Precision'] ,
|
180
|
-
npv= classification_report_dict['NPV'] ,
|
181
|
-
f1score= classification_report_dict['F1-score'] ,
|
182
|
-
fpr= classification_report_dict['False positive rate'] ,
|
183
|
-
tpr= classification_report_dict['True positive rate']
|
184
|
-
)
|
185
|
-
self.eng.execute(text(query))
|
186
|
-
except Exception as e:
|
187
|
-
# This block will catch any other exceptions
|
188
|
-
print(f"An unexpected error occurred: {e}")
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
return result
|
Medfl/LearningManager/model.py
DELETED
@@ -1,223 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
# froked from https://github.com/pythonlessons/mltu/blob/main/mltu/torch/model.py
|
3
|
-
|
4
|
-
import typing
|
5
|
-
from collections import OrderedDict
|
6
|
-
from typing import Dict, List, Optional, Tuple
|
7
|
-
|
8
|
-
import numpy as np
|
9
|
-
import torch
|
10
|
-
import torch.nn as nn
|
11
|
-
from sklearn.metrics import accuracy_score,roc_auc_score
|
12
|
-
|
13
|
-
from .utils import params
|
14
|
-
|
15
|
-
|
16
|
-
class Model:
|
17
|
-
"""
|
18
|
-
Model class for training and testing PyTorch neural networks.
|
19
|
-
|
20
|
-
Attributes:
|
21
|
-
model (torch.nn.Module): PyTorch neural network.
|
22
|
-
optimizer (torch.optim.Optimizer): PyTorch optimizer.
|
23
|
-
criterion (typing.Callable): Loss function.
|
24
|
-
"""
|
25
|
-
|
26
|
-
def __init__(
|
27
|
-
self,
|
28
|
-
model: torch.nn.Module,
|
29
|
-
optimizer: torch.optim.Optimizer,
|
30
|
-
criterion: typing.Callable,
|
31
|
-
) -> None:
|
32
|
-
"""
|
33
|
-
Initialize Model class with the specified model, optimizer, and criterion.
|
34
|
-
|
35
|
-
Args:
|
36
|
-
model (torch.nn.Module): PyTorch neural network.
|
37
|
-
optimizer (torch.optim.Optimizer): PyTorch optimizer.
|
38
|
-
criterion (typing.Callable): Loss function.
|
39
|
-
"""
|
40
|
-
self.model = model
|
41
|
-
self.optimizer = optimizer
|
42
|
-
self.criterion = criterion
|
43
|
-
# Get device on which model is running
|
44
|
-
self.validate()
|
45
|
-
|
46
|
-
def validate(self) -> None:
|
47
|
-
"""
|
48
|
-
Validate model and optimizer.
|
49
|
-
"""
|
50
|
-
if not isinstance(self.model, torch.nn.Module):
|
51
|
-
raise TypeError("model argument must be a torch.nn.Module")
|
52
|
-
|
53
|
-
if not isinstance(self.optimizer, torch.optim.Optimizer):
|
54
|
-
raise TypeError(
|
55
|
-
"optimizer argument must be a torch.optim.Optimizer"
|
56
|
-
)
|
57
|
-
|
58
|
-
def get_parameters(self) -> List[np.ndarray]:
|
59
|
-
"""
|
60
|
-
Get the parameters of the model as a list of NumPy arrays.
|
61
|
-
|
62
|
-
Returns:
|
63
|
-
List[np.ndarray]: The parameters of the model as a list of NumPy arrays.
|
64
|
-
"""
|
65
|
-
return [
|
66
|
-
val.cpu().numpy() for _, val in self.model.state_dict().items()
|
67
|
-
]
|
68
|
-
|
69
|
-
def set_parameters(self, parameters: List[np.ndarray]) -> None:
|
70
|
-
"""
|
71
|
-
Set the parameters of the model from a list of NumPy arrays.
|
72
|
-
|
73
|
-
Args:
|
74
|
-
parameters (List[np.ndarray]): The parameters to be set.
|
75
|
-
"""
|
76
|
-
params_dict = zip(self.model.state_dict().keys(), parameters)
|
77
|
-
state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict})
|
78
|
-
self.model.load_state_dict(state_dict, strict=True)
|
79
|
-
|
80
|
-
def train(
|
81
|
-
self, train_loader, epoch, device, privacy_engine, diff_priv=False
|
82
|
-
) -> float:
|
83
|
-
"""
|
84
|
-
Train the model on the given train_loader for one epoch.
|
85
|
-
|
86
|
-
Args:
|
87
|
-
train_loader: The data loader for training data.
|
88
|
-
epoch (int): The current epoch number.
|
89
|
-
device: The device on which to perform the training.
|
90
|
-
privacy_engine: The privacy engine used for differential privacy (if enabled).
|
91
|
-
diff_priv (bool, optional): Whether differential privacy is used. Default is False.
|
92
|
-
|
93
|
-
Returns:
|
94
|
-
float: The value of epsilon used in differential privacy.
|
95
|
-
"""
|
96
|
-
self.model.train()
|
97
|
-
epsilon = 0
|
98
|
-
losses = []
|
99
|
-
top1_acc = []
|
100
|
-
|
101
|
-
for i, (X_train, y_train) in enumerate(train_loader):
|
102
|
-
X_train, y_train = X_train.to(device), y_train.to(device)
|
103
|
-
|
104
|
-
self.optimizer.zero_grad()
|
105
|
-
|
106
|
-
# compute output
|
107
|
-
y_hat = torch.squeeze(self.model(X_train), 1)
|
108
|
-
loss = self.criterion(y_hat, y_train)
|
109
|
-
|
110
|
-
preds = np.argmax(y_hat.detach().cpu().numpy(), axis=0)
|
111
|
-
labels = y_train.detach().cpu().numpy()
|
112
|
-
|
113
|
-
# measure accuracy and record loss
|
114
|
-
acc = (preds == labels).mean()
|
115
|
-
|
116
|
-
losses.append(loss.item())
|
117
|
-
top1_acc.append(acc)
|
118
|
-
|
119
|
-
loss.backward()
|
120
|
-
self.optimizer.step()
|
121
|
-
|
122
|
-
if diff_priv:
|
123
|
-
epsilon = privacy_engine.get_epsilon(float(params["DELTA"]))
|
124
|
-
|
125
|
-
if (i + 1) % 10 == 0:
|
126
|
-
if diff_priv:
|
127
|
-
epsilon = privacy_engine.get_epsilon(float(params["DELTA"]))
|
128
|
-
print(
|
129
|
-
f"\tTrain Epoch: {epoch} \t"
|
130
|
-
f"Loss: {np.mean(losses):.6f} "
|
131
|
-
f"Acc@1: {np.mean(top1_acc) * 100:.6f} "
|
132
|
-
f"(ε = {epsilon:.2f}, δ = {params['DELTA']})"
|
133
|
-
)
|
134
|
-
else:
|
135
|
-
print(
|
136
|
-
f"\tTrain Epoch: {epoch} \t"
|
137
|
-
f"Loss: {np.mean(losses):.6f} "
|
138
|
-
f"Acc@1: {np.mean(top1_acc) * 100:.6f}"
|
139
|
-
)
|
140
|
-
|
141
|
-
return epsilon
|
142
|
-
|
143
|
-
def evaluate(self, val_loader, device=torch.device("cpu")) -> Tuple[float, float]:
|
144
|
-
"""
|
145
|
-
Evaluate the model on the given validation data.
|
146
|
-
|
147
|
-
Args:
|
148
|
-
val_loader: The data loader for validation data.
|
149
|
-
device: The device on which to perform the evaluation. Default is 'cpu'.
|
150
|
-
|
151
|
-
Returns:
|
152
|
-
Tuple[float, float]: The evaluation loss and accuracy.
|
153
|
-
"""
|
154
|
-
correct, total, loss, accuracy, auc = 0, 0, 0.0, [], []
|
155
|
-
self.model.eval()
|
156
|
-
|
157
|
-
with torch.no_grad():
|
158
|
-
for X_test, y_test in val_loader:
|
159
|
-
X_test, y_test = X_test.to(device), y_test.to(device) # Move data to device
|
160
|
-
|
161
|
-
y_hat = torch.squeeze(self.model(X_test), 1)
|
162
|
-
|
163
|
-
|
164
|
-
criterion = self.criterion.to(y_hat.device)
|
165
|
-
loss += criterion(y_hat, y_test).item()
|
166
|
-
|
167
|
-
|
168
|
-
# Move y_hat to CPU for accuracy computation
|
169
|
-
y_hat_cpu = y_hat.cpu().detach().numpy()
|
170
|
-
accuracy.append(accuracy_score(y_test.cpu().numpy(), y_hat_cpu.round()))
|
171
|
-
|
172
|
-
# Move y_test to CPU for AUC computation
|
173
|
-
y_test_cpu = y_test.cpu().numpy()
|
174
|
-
y_prob_cpu = y_hat.cpu().detach().numpy()
|
175
|
-
if (len(np.unique(y_test_cpu)) != 1):
|
176
|
-
auc.append(roc_auc_score(y_test_cpu, y_prob_cpu))
|
177
|
-
|
178
|
-
total += y_test.size(0)
|
179
|
-
correct += np.sum(y_hat_cpu.round() == y_test_cpu)
|
180
|
-
|
181
|
-
loss /= len(val_loader.dataset)
|
182
|
-
return loss, np.mean(accuracy), np.mean(auc)
|
183
|
-
|
184
|
-
|
185
|
-
@staticmethod
|
186
|
-
def save_model(model , model_name:str):
|
187
|
-
"""
|
188
|
-
Saves a PyTorch model to a file.
|
189
|
-
|
190
|
-
Args:
|
191
|
-
model (torch.nn.Module): PyTorch model to be saved.
|
192
|
-
model_name (str): Name of the model file.
|
193
|
-
|
194
|
-
Raises:
|
195
|
-
Exception: If there is an issue during the saving process.
|
196
|
-
|
197
|
-
Returns:
|
198
|
-
None
|
199
|
-
"""
|
200
|
-
try:
|
201
|
-
torch.save(model, '../../notebooks/.ipynb_checkpoints/trainedModels/' + model_name + ".pth")
|
202
|
-
except Exception as e:
|
203
|
-
raise Exception(f"Error saving the model: {str(e)}")
|
204
|
-
|
205
|
-
@staticmethod
|
206
|
-
def load_model(model_path: str):
|
207
|
-
"""
|
208
|
-
Loads a PyTorch model from a file.
|
209
|
-
|
210
|
-
Args:
|
211
|
-
model_path (str): Path to the model file to be loaded.
|
212
|
-
|
213
|
-
Returns:
|
214
|
-
torch.nn.Module: Loaded PyTorch model.
|
215
|
-
"""
|
216
|
-
# Ensure models are loaded onto the CPU when CUDA is not available
|
217
|
-
if torch.cuda.is_available():
|
218
|
-
loaded_model = torch.load(model_path)
|
219
|
-
else:
|
220
|
-
loaded_model = torch.load(model_path, map_location=torch.device('cpu'))
|
221
|
-
return loaded_model
|
222
|
-
|
223
|
-
|
@@ -1,14 +0,0 @@
|
|
1
|
-
DELTA: 1.0e-05
|
2
|
-
EPSILON: 5.0
|
3
|
-
MAX_GRAD_NORM: 1.0
|
4
|
-
diff_privacy: true
|
5
|
-
lr: 0.01
|
6
|
-
min_evalclient: 2
|
7
|
-
num_rounds: 12
|
8
|
-
optimizer: SGD
|
9
|
-
path_to_master_csv: /home/local/USHERBROOKE/saho6810/MEDfl/code/MEDfl/notebooks/data/masterDataSet/Mimic_ouael.csv
|
10
|
-
path_to_test_csv: /home/local/USHERBROOKE/saho6810/MEDfl/code/MEDfl/notebooks/data/masterDataSet/Mimic_train.csv
|
11
|
-
task: BinaryClassification
|
12
|
-
test_batch_size: 1
|
13
|
-
train_batch_size: 32
|
14
|
-
train_epochs: 116
|