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.
- {fedops-0.2.2 → fedops-0.12.1}/PKG-INFO +1 -1
- fedops-0.12.1/fedops/client/app.py +218 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/client/client_api.py +1 -1
- fedops-0.12.1/fedops/client/client_fl.py +242 -0
- fedops-0.12.1/fedops/client/client_utils.py +234 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/client/client_wandb.py +4 -4
- {fedops-0.2.2 → fedops-0.12.1}/fedops/server/__init__.py +5 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/server/app.py +103 -37
- fedops-0.12.1/fedops/server/mobile_app.py +132 -0
- fedops-0.12.1/fedops/server/mobile_strategy.py +100 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/server/server_api.py +2 -2
- fedops-0.12.1/fedops/server/server_utils.py +209 -0
- fedops-0.12.1/fedops/simulation/__init__.py +26 -0
- fedops-0.12.1/fedops/simulation/app.py +101 -0
- fedops-0.12.1/fedops/simulation/fl_strategy/__init__.py +31 -0
- fedops-0.12.1/fedops/simulation/fl_strategy/aggregation.py +190 -0
- fedops-0.12.1/fedops/simulation/fl_strategy/client.py +133 -0
- fedops-0.12.1/fedops/simulation/fl_strategy/selection.py +60 -0
- fedops-0.12.1/fedops/simulation/fl_strategy/server.py +292 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/PKG-INFO +1 -1
- {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/SOURCES.txt +9 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/requires.txt +2 -2
- {fedops-0.2.2 → fedops-0.12.1}/setup.py +4 -4
- fedops-0.2.2/fedops/client/app.py +0 -175
- fedops-0.2.2/fedops/client/client_fl.py +0 -155
- fedops-0.2.2/fedops/client/client_utils.py +0 -129
- fedops-0.2.2/fedops/server/server_utils.py +0 -97
- {fedops-0.2.2 → fedops-0.12.1}/fedops/__init__.py +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/client/__init__.py +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/client/app_test.py +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/client/test.py +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/utils/__init__.py +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops/utils/version.py +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/dependency_links.txt +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/fedops.egg-info/top_level.txt +0 -0
- {fedops-0.2.2 → fedops-0.12.1}/setup.cfg +0 -0
|
@@ -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 = '
|
|
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
|