fedops 0.2.2__tar.gz → 0.12.1__tar.gz

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.
Files changed (36) hide show
  1. {fedops-0.2.2 → fedops-0.12.1}/PKG-INFO +1 -1
  2. fedops-0.12.1/fedops/client/app.py +218 -0
  3. {fedops-0.2.2 → fedops-0.12.1}/fedops/client/client_api.py +1 -1
  4. fedops-0.12.1/fedops/client/client_fl.py +242 -0
  5. fedops-0.12.1/fedops/client/client_utils.py +234 -0
  6. {fedops-0.2.2 → fedops-0.12.1}/fedops/client/client_wandb.py +4 -4
  7. {fedops-0.2.2 → fedops-0.12.1}/fedops/server/__init__.py +5 -0
  8. {fedops-0.2.2 → fedops-0.12.1}/fedops/server/app.py +103 -37
  9. fedops-0.12.1/fedops/server/mobile_app.py +132 -0
  10. fedops-0.12.1/fedops/server/mobile_strategy.py +100 -0
  11. {fedops-0.2.2 → fedops-0.12.1}/fedops/server/server_api.py +2 -2
  12. fedops-0.12.1/fedops/server/server_utils.py +209 -0
  13. fedops-0.12.1/fedops/simulation/__init__.py +26 -0
  14. fedops-0.12.1/fedops/simulation/app.py +101 -0
  15. fedops-0.12.1/fedops/simulation/fl_strategy/__init__.py +31 -0
  16. fedops-0.12.1/fedops/simulation/fl_strategy/aggregation.py +190 -0
  17. fedops-0.12.1/fedops/simulation/fl_strategy/client.py +133 -0
  18. fedops-0.12.1/fedops/simulation/fl_strategy/selection.py +60 -0
  19. fedops-0.12.1/fedops/simulation/fl_strategy/server.py +292 -0
  20. {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/PKG-INFO +1 -1
  21. {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/SOURCES.txt +9 -0
  22. {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/requires.txt +2 -2
  23. {fedops-0.2.2 → fedops-0.12.1}/setup.py +4 -4
  24. fedops-0.2.2/fedops/client/app.py +0 -175
  25. fedops-0.2.2/fedops/client/client_fl.py +0 -155
  26. fedops-0.2.2/fedops/client/client_utils.py +0 -129
  27. fedops-0.2.2/fedops/server/server_utils.py +0 -97
  28. {fedops-0.2.2 → fedops-0.12.1}/fedops/__init__.py +0 -0
  29. {fedops-0.2.2 → fedops-0.12.1}/fedops/client/__init__.py +0 -0
  30. {fedops-0.2.2 → fedops-0.12.1}/fedops/client/app_test.py +0 -0
  31. {fedops-0.2.2 → fedops-0.12.1}/fedops/client/test.py +0 -0
  32. {fedops-0.2.2 → fedops-0.12.1}/fedops/utils/__init__.py +0 -0
  33. {fedops-0.2.2 → fedops-0.12.1}/fedops/utils/version.py +0 -0
  34. {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/dependency_links.txt +0 -0
  35. {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/top_level.txt +0 -0
  36. {fedops-0.2.2 → fedops-0.12.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fedops
3
- Version: 0.2.2
3
+ Version: 0.12.1
4
4
  Summary: FL Lifecycle Operations Management Platform
5
5
  Home-page: https://github.com/gachon-CCLab/FedOps.git
6
6
  Author: Semo Yang
@@ -0,0 +1,218 @@
1
+ import logging, json
2
+ import time
3
+ from fastapi import FastAPI, BackgroundTasks
4
+ import asyncio
5
+ import uvicorn
6
+ from datetime import datetime
7
+
8
+ from . import client_utils
9
+ from . import client_fl
10
+ from . import client_wandb
11
+ from . import client_api
12
+
13
+
14
+ class FLClientTask():
15
+ def __init__(self, cfg, fl_task=None, FL_client_port=None):
16
+ self.app = FastAPI()
17
+ self.status = client_utils.FLClientStatus()
18
+ self.cfg = cfg
19
+ self.client_port = FL_client_port
20
+ self.task_id = cfg.task_id
21
+ self.client_name = cfg.client.name
22
+ self.dataset_name = cfg.dataset.name
23
+ self.output_size = cfg.model.output_size
24
+ self.validation_split = cfg.dataset.validation_split
25
+ self.wandb_use = cfg.wandb.use
26
+ self.model_type = cfg.model_type
27
+ self.model = fl_task["model"]
28
+ self.model_name = fl_task["model_name"]
29
+ self.y_label_counter = fl_task["y_label_counter"]
30
+
31
+ logging.info(f'init model_type: {self.model_type}')
32
+
33
+ if self.wandb_use:
34
+ self.wandb_key = cfg.wandb.key
35
+ self.wandb_account = cfg.wandb.account
36
+ self.wandb_project = cfg.wandb.project
37
+
38
+ if self.model_type=="Tensorflow":
39
+ self.x_train = fl_task["x_train"]
40
+ self.x_test = fl_task["x_test"]
41
+ self.y_train = fl_task["y_train"]
42
+ self.y_test = fl_task["y_test"]
43
+
44
+ elif self.model_type == "Pytorch":
45
+ self.train_loader = fl_task["train_loader"]
46
+ self.val_loader = fl_task["val_loader"]
47
+ self.test_loader = fl_task["test_loader"]
48
+ self.criterion = fl_task["criterion"]
49
+ self.optimizer = fl_task["optimizer"]
50
+ self.train_torch = fl_task["train_torch"]
51
+ self.test_torch = fl_task["test_torch"]
52
+
53
+
54
+ async def fl_client_start(self):
55
+ logging.info('FL learning ready')
56
+
57
+ logging.info(f'fl_task_id: {self.task_id}')
58
+ logging.info(f'dataset: {self.dataset_name}')
59
+ logging.info(f'output_size: {self.output_size}')
60
+ logging.info(f'validation_split: {self.validation_split}')
61
+ logging.info(f'model_type: {self.model_type}')
62
+
63
+ """
64
+ Before running this code,
65
+ set wandb api and account in the config.yaml
66
+ """
67
+ if self.wandb_use:
68
+ logging.info(f'wandb_key: {self.wandb_key}')
69
+ logging.info(f'wandb_account: {self.wandb_account}')
70
+ # Set the name in the wandb project
71
+ wandb_name = f"client-v{self.status.next_gl_model}({datetime.now()})"
72
+ # Login and init wandb project
73
+ wandb_run = client_wandb.start_wandb(self.wandb_key, self.wandb_project, wandb_name)
74
+ else:
75
+ wandb_run=None
76
+
77
+ # Initialize wandb_config, client object
78
+ wandb_config = {}
79
+ # client = None
80
+
81
+ try:
82
+ loop = asyncio.get_event_loop()
83
+ if self.model_type == "Tensorflow":
84
+ # Update wandb config
85
+ wandb_config = {"learning_rate": self.model.optimizer.learning_rate.numpy(),
86
+ "optimizer": self.model.optimizer,
87
+ "loss_function": self.model.loss,
88
+ "dataset": self.dataset_name, "model_architecture": self.model_name}
89
+
90
+ client = client_fl.FLClient(model=self.model, x_train=self.x_train, y_train=self.y_train, x_test=self.x_test,
91
+ y_test=self.y_test,
92
+ validation_split=self.validation_split, fl_task_id=self.task_id, client_name=self.status.client_mac,
93
+ fl_round=1, next_gl_model=self.status.next_gl_model, wandb_use=self.wandb_use,
94
+ wandb_run=wandb_run, model_name=self.model_name, model_type=self.model_type)
95
+
96
+ elif self.model_type == "Pytorch":
97
+ wandb_config = {"learning_rate": self.optimizer.param_groups[0]['lr'],
98
+ "optimizer": self.optimizer.__class__.__name__,
99
+ "loss_function": self.criterion,
100
+ "dataset": self.dataset_name, "model_architecture": self.model_name}
101
+
102
+ client = client_fl.FLClient(model=self.model, validation_split=self.validation_split,
103
+ fl_task_id=self.task_id, client_name=self.status.client_name,
104
+ fl_round=1, next_gl_model=self.status.next_gl_model, wandb_use=self.wandb_use,
105
+ wandb_run=wandb_run, model_name=self.model_name, model_type=self.model_type,
106
+ train_loader=self.train_loader, val_loader=self.val_loader, test_loader=self.test_loader,
107
+ criterion=self.criterion, optimizer=self.optimizer,
108
+ train_torch=self.train_torch, test_torch=self.test_torch)
109
+
110
+
111
+
112
+ # Check data fl client data status in the wandb
113
+ label_values = [[i, self.y_label_counter[i]] for i in range(self.output_size)]
114
+ logging.info(f'label_values: {label_values}')
115
+
116
+ # client_start object
117
+ client_start = client_fl.flower_client_start(self.status.server_IP, client)
118
+
119
+ # FL client start time
120
+ fl_start_time = time.time()
121
+
122
+ # Run asynchronously FL client
123
+ await loop.run_in_executor(None, client_start)
124
+
125
+ logging.info('fl learning finished')
126
+
127
+ # FL client end time
128
+ fl_end_time = time.time() - fl_start_time
129
+
130
+
131
+ if self.wandb_use:
132
+ wandb_run.config.update(wandb_config, allow_val_change=True)
133
+ client_wandb.data_status_wandb(wandb_run, label_values)
134
+ # Wandb log(Client round end time)
135
+ wandb_run.log({"operation_time": fl_end_time, "next_gl_model_v": self.status.next_gl_model},step=self.status.next_gl_model)
136
+ # close wandb
137
+ wandb_run.finish()
138
+
139
+ # Get client system result from wandb and send it to client_performance pod
140
+ client_wandb.client_system_wandb(self.task_id, self.status.client_name, self.status.next_gl_model,
141
+ wandb_name, self.wandb_account)
142
+
143
+ client_all_time_result = {"fl_task_id": self.task_id, "client_mac": self.status.client_name,
144
+ "operation_time": fl_end_time,
145
+ "next_gl_model_v": self.status.next_gl_model}
146
+ json_result = json.dumps(client_all_time_result)
147
+ logging.info(f'client_operation_time - {json_result}')
148
+
149
+ # Send client_time_result to client_performance pod
150
+ client_api.ClientServerAPI(self.task_id).put_client_time_result(json_result)
151
+
152
+ # Delete client object
153
+ del client
154
+
155
+ # Complete Client training
156
+ self.status.client_start = await client_utils.notify_fin()
157
+ logging.info('FL Client Learning Finish')
158
+
159
+ except Exception as e:
160
+ logging.info('[E][PC0002] learning', e)
161
+ self.status.client_fail = True
162
+ self.status.client_start = await client_utils.notify_fail()
163
+ raise e
164
+
165
+ def start(self):
166
+ # Configure routes, endpoints, and other FastAPI-related logic here
167
+ # Example:
168
+ @self.app.get('/online')
169
+ async def get_info():
170
+ return self.status
171
+
172
+ # asynchronously start client
173
+ @self.app.post("/start")
174
+ async def client_start_trigger(background_tasks: BackgroundTasks):
175
+
176
+ # client_manager address
177
+ # client_res = client_api.ClientMangerAPI().get_info()
178
+
179
+ # # # latest global model version
180
+ # latest_gl_model_v = client_res.json()['GL_Model_V']
181
+
182
+ # # next global model version
183
+ # self.status.next_gl_model = latest_gl_model_v + 1
184
+ self.status.next_gl_model = 1
185
+
186
+ logging.info('bulid model')
187
+
188
+ logging.info('FL start')
189
+ self.status.client_start = True
190
+
191
+ # self.status.FL_server_IP = manager_data.server_ip
192
+ self.status.task_id = self.task_id
193
+ self.status.client_name = client_utils.get_mac_address()
194
+ self.status.client_name = self.client_name
195
+
196
+ # get the FL server IP
197
+ # self.status.FL_server_IP = client_api.ClientServerAPI(self.task_id).get_port()
198
+ self.status.server_IP = "0.0.0.0:8080"
199
+
200
+ # start FL Client
201
+ background_tasks.add_task(self.fl_client_start)
202
+
203
+ return self.status
204
+
205
+ try:
206
+ # create client api => to connect client manager
207
+ uvicorn.run(self.app, host='0.0.0.0', port=self.client_port)
208
+
209
+ except Exception as e:
210
+ # Handle any exceptions that occur during the execution
211
+ logging.error(f'An error occurred during execution: {e}')
212
+
213
+ finally:
214
+ # FL client out
215
+ client_api.ClientMangerAPI().get_client_out()
216
+ logging.info(f'{self.status.client_name}-client close')
217
+
218
+
@@ -45,7 +45,7 @@ class ClientMangerAPI():
45
45
  class ClientServerAPI():
46
46
  def __init__(self, task_id):
47
47
  self.task_id = task_id
48
- self.ccl_address = 'ccljhub.gachon.ac.kr'
48
+ self.ccl_address = 'ccl.gachon.ac.kr'
49
49
  self.server_manager_port = '40019'
50
50
  self.client_performance_port = '40015'
51
51
 
@@ -0,0 +1,242 @@
1
+ from collections import OrderedDict
2
+ import json, logging
3
+ import flwr as fl
4
+ import time
5
+ from functools import partial
6
+ from . import client_api
7
+ from . import client_utils
8
+
9
+ # set log format
10
+ handlers_list = [logging.StreamHandler()]
11
+
12
+ # if os.environ["MONITORING"] == '1':
13
+ # handlers_list.append(logging.FileHandler('./fedops/fl_client.log'))
14
+ # else:
15
+ # pass
16
+
17
+ logging.basicConfig(level=logging.DEBUG, format="%(asctime)s [%(levelname)8.8s] %(message)s",
18
+ handlers=handlers_list)
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ class FLClient(fl.client.NumPyClient):
23
+
24
+ def __init__(self, model, validation_split, fl_task_id, client_name, fl_round,next_gl_model, wandb_use,
25
+ wandb_run=None, model_name=None, model_type=None, x_train=None, y_train=None, x_test=None, y_test=None,
26
+ train_loader=None, val_loader=None, test_loader=None, criterion=None, optimizer=None, train_torch=None, test_torch=None):
27
+ self.model_type = model_type
28
+ self.model = model
29
+ self.validation_split = validation_split
30
+ self.fl_task_id = fl_task_id
31
+ self.client_name = client_name
32
+ self.fl_round = fl_round
33
+ self.next_gl_model = next_gl_model
34
+ self.model_name = model_name
35
+ self.wandb_use = wandb_use
36
+
37
+ if self.wandb_use:
38
+ self.wandb_run = wandb_run
39
+
40
+ if self.model_type == "Tensorflow":
41
+ self.x_train, self.y_train = x_train, y_train
42
+ self.x_test, self.y_test = x_test, y_test
43
+
44
+ elif self.model_type == "Pytorch":
45
+ self.train_loader = train_loader
46
+ self.val_loader = val_loader
47
+ self.test_loader = test_loader
48
+ self.criterion = criterion
49
+ self.optimizer = optimizer
50
+ self.train_torch = train_torch
51
+ self.test_torch = test_torch
52
+
53
+
54
+ def set_parameters(self, parameters):
55
+ import torch
56
+ """Loads a efficientnet model and replaces it parameters with the ones
57
+ given."""
58
+ keys = [k for k in self.model.state_dict().keys() if "bn" not in k] # Excluding parameters of BN layers
59
+ params_dict = zip(keys, parameters)
60
+ state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict})
61
+ # self.model.load_state_dict(state_dict, strict=True)
62
+ self.model.load_state_dict(state_dict, strict=False)
63
+
64
+ def get_parameters(self):
65
+ """Get parameters of the local model."""
66
+ if self.model_type == "Tensorflow":
67
+ raise Exception("Not implemented (server-side parameter initialization)")
68
+
69
+ elif self.model_type == "Pytorch":
70
+ return [
71
+ val.cpu().numpy() for name, val in self.model.state_dict().items()
72
+ if "bn" not in name # Excluding parameters of BN layers
73
+ ]
74
+
75
+ def get_properties(self, config):
76
+ """Get properties of client."""
77
+ raise Exception("Not implemented")
78
+
79
+ def fit(self, parameters, config):
80
+ """Train parameters on the locally held training set."""
81
+
82
+ print(f"config: {config}")
83
+ # Get hyperparameters for this round
84
+ batch_size: int = config["batch_size"]
85
+ epochs: int = config["local_epochs"]
86
+ num_rounds: int = config["num_rounds"]
87
+
88
+ if self.wandb_use:
89
+ # add wandb config
90
+ self.wandb_run.config.update({"batch_size": batch_size, "epochs": epochs, "num_rounds": num_rounds}, allow_val_change=True)
91
+
92
+ # start round time
93
+ round_start_time = time.time()
94
+
95
+ # model path for saving local model
96
+ model_path = f'./local_model/{self.fl_task_id}/{self.model_name}_local_model_V{self.next_gl_model}'
97
+
98
+ # Initialize results
99
+ results = {}
100
+
101
+ # Training Tensorflow
102
+ if self.model_type == "Tensorflow":
103
+ # Update local model parameters
104
+ self.model.set_weights(parameters)
105
+
106
+ # Train the model using hyperparameters from config
107
+ history = self.model.fit(
108
+ self.x_train,
109
+ self.y_train,
110
+ batch_size,
111
+ epochs,
112
+ validation_split=self.validation_split,
113
+ )
114
+
115
+ train_loss = history.history["loss"][len(history.history["loss"])-1]
116
+ train_accuracy = history.history["accuracy"][len(history.history["accuracy"])-1]
117
+ results = {
118
+ "train_loss": train_loss,
119
+ "train_accuracy": train_accuracy,
120
+ "val_loss": history.history["val_loss"][len(history.history["val_loss"])-1],
121
+ "val_accuracy": history.history["val_accuracy"][len(history.history["val_accuracy"])-1],
122
+ }
123
+
124
+ # Return updated model parameters
125
+ parameters_prime = self.model.get_weights()
126
+ num_examples_train = len(self.x_train)
127
+
128
+
129
+ # save local model
130
+ self.model.save(model_path+'.h5')
131
+
132
+
133
+ # Training Torch
134
+ elif self.model_type == "Pytorch":
135
+ # Update local model parameters
136
+ self.set_parameters(parameters)
137
+
138
+ # results = client_utils.torch_train(self.model, self.train_dataset, self.criterion, self.optimizer,
139
+ # self.validation_split, epochs, batch_size)
140
+ trained_model = self.train_torch(self.model, self.train_loader, self.criterion, self.optimizer,
141
+ epochs)
142
+
143
+ train_loss, train_accuracy = self.test_torch(trained_model, self.train_loader, self.criterion)
144
+ val_loss, val_accuracy = self.test_torch(trained_model, self.val_loader, self.criterion)
145
+
146
+ results = {"train_loss": train_loss,
147
+ "train_accuracy": train_accuracy,
148
+ "val_loss": val_loss,
149
+ "val_accuracy": val_accuracy,
150
+ }
151
+
152
+ # Return updated model parameters
153
+ parameters_prime = client_utils.get_model_params(self.model)
154
+ num_examples_train = len(self.train_loader)
155
+
156
+ # Save model weights
157
+ import torch
158
+ torch.save(self.model.state_dict(), model_path+'.pth')
159
+ else:
160
+ raise ValueError("Unsupported model_type")
161
+
162
+
163
+ # end round time
164
+ round_end_time = time.time() - round_start_time
165
+
166
+ if self.wandb_use:
167
+ # wandb train log
168
+ self.wandb_run.log({"train_loss": results["train_loss"], "round": self.fl_round}, step=self.fl_round) # train_loss
169
+ self.wandb_run.log({"train_accuracy": results["train_accuracy"], "round": self.fl_round}, step=self.fl_round) # train_accuracy
170
+ self.wandb_run.log({"train_time": round_end_time, "round": self.fl_round}, step=self.fl_round) # train time
171
+
172
+ # wandb test log
173
+ self.wandb_run.log({"val_loss": results["val_loss"], "round": self.fl_round}, step=self.fl_round) # test_loss
174
+ self.wandb_run.log({"val_accuracy": results["val_accuracy"], "round": self.fl_round}, step=self.fl_round) # test_accuracy
175
+
176
+
177
+ # Training: model performance by round
178
+ train_result = {"fl_task_id": self.fl_task_id, "client_name": self.client_name, "round": self.fl_round,
179
+ "train_loss": results["train_loss"], "train_accuracy": results["train_accuracy"],
180
+ "test_loss": results["val_loss"], "test_accuracy": results["val_accuracy"],
181
+ "train_time": round_end_time, "next_gl_model_v": self.next_gl_model}
182
+ json_result = json.dumps(train_result)
183
+ logger.info(f'train_performance - {json_result}')
184
+
185
+ # send train_result to client_performance pod
186
+ client_api.ClientServerAPI(self.fl_task_id).put_train_result(json_result)
187
+
188
+ return parameters_prime, num_examples_train, results
189
+
190
+
191
+ def evaluate(self, parameters, config):
192
+ """Evaluate parameters on the locally held test set."""
193
+
194
+ # Get config values
195
+ batch_size: int = config["batch_size"]
196
+
197
+ # Initialize test_loss, test_accuracy
198
+ test_loss = 0.0
199
+ test_accuracy = 0.0
200
+
201
+ if self.model_type == "Tensorflow":
202
+ # Update local model with global parameters
203
+ self.model.set_weights(parameters)
204
+
205
+ # Evaluate global model parameters on the local test data and return results
206
+ test_loss, test_accuracy = self.model.evaluate(x=self.x_test, y=self.y_test, batch_size=batch_size)
207
+
208
+ num_examples_test = len(self.x_test)
209
+
210
+ elif self.model_type == "Pytorch":
211
+ # Update local model parameters
212
+ self.set_parameters(parameters)
213
+
214
+ # Evaluate global model parameters on the local test data and return results
215
+ test_loss, test_accuracy = self.test_torch(self.model, self.test_loader, self.criterion)
216
+ num_examples_test = len(self.test_loader)
217
+ else:
218
+ raise ValueError("Unsupported model_type")
219
+
220
+ if self.wandb_use:
221
+ # wandb log
222
+ self.wandb_run.log({"test_loss": test_loss, "round": self.fl_round}, step=self.fl_round) # loss
223
+ self.wandb_run.log({"test_acc": test_accuracy, "round": self.fl_round}, step=self.fl_round) # acc
224
+
225
+ # Test: model performance by round
226
+ test_result = {"fl_task_id": self.fl_task_id, "client_name": self.client_name, "round": self.fl_round,
227
+ "test_loss": test_loss, "test_acc": test_accuracy, "next_gl_model_v": self.next_gl_model}
228
+ json_result = json.dumps(test_result)
229
+ logger.info(f'test - {json_result}')
230
+
231
+ # send test_result to client_performance pod
232
+ client_api.ClientServerAPI(self.fl_task_id).put_test_result(json_result)
233
+
234
+ # increase next round
235
+ self.fl_round += 1
236
+
237
+ return test_loss, num_examples_test, {"accuracy": test_accuracy}
238
+
239
+
240
+ def flower_client_start(FL_server_IP, client):
241
+ client_start = partial(fl.client.start_numpy_client, server_address=FL_server_IP, client=client)
242
+ return client_start