MEDfl 2.0.10__tar.gz → 2.0.10.dev1__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.
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/xgboost/strategy.py +303 -15
- {medfl-2.0.10 → medfl-2.0.10.dev1}/PKG-INFO +1 -1
- {medfl-2.0.10 → medfl-2.0.10.dev1}/medfl.egg-info/PKG-INFO +1 -1
- {medfl-2.0.10 → medfl-2.0.10.dev1}/medfl.egg-info/requires.txt +1 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/setup.py +1 -1
- {medfl-2.0.10 → medfl-2.0.10.dev1}/LICENSE +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/__init__.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/client.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/dynamicModal.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/federated_dataset.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/flpipeline.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/model.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/params.yaml +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/params_optimiser.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/plot.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/server.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/strategy.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/LearningManager/utils.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/__init__.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/database_connector.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/dataset.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/flsetup.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/net_helper.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/net_manager_queries.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/network.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/NetManager/node.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/__init__.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/__init__.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/client.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/model.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/server.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/strategy.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/xgboost/__init__.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/xgboost/client.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/xgboost/config.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/xgboost/data.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/xgboost/metrics.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/rw/xgboost/serialization.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/scripts/__init__.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/scripts/base.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/MEDfl/scripts/create_db.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/README.md +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/alembic/__init__.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/alembic/env.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/medfl.egg-info/SOURCES.txt +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/medfl.egg-info/dependency_links.txt +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/medfl.egg-info/top_level.txt +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/setup.cfg +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/tests/test_dataset.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/tests/test_federated_dataset.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/tests/test_flpipeline.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/tests/test_flsetup.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/tests/test_network.py +0 -0
- {medfl-2.0.10 → medfl-2.0.10.dev1}/tests/test_node.py +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
from typing import Dict, Any, Optional
|
|
4
|
+
import tempfile
|
|
5
|
+
import xgboost as xgb
|
|
4
6
|
|
|
5
7
|
import flwr as fl
|
|
6
8
|
from flwr.common import (
|
|
@@ -13,7 +15,7 @@ from flwr.common import GetPropertiesIns
|
|
|
13
15
|
from MEDfl.rw.xgboost.config import XGBoostConfig
|
|
14
16
|
from MEDfl.rw.xgboost.metrics import aggregate_weighted_metrics
|
|
15
17
|
from MEDfl.rw.xgboost.serialization import (
|
|
16
|
-
parameters_to_booster,
|
|
18
|
+
parameters_to_booster,booster_to_parameters,
|
|
17
19
|
save_booster,
|
|
18
20
|
XGBOOST_TENSOR_TYPE,
|
|
19
21
|
)
|
|
@@ -108,6 +110,8 @@ class XGBoostStrategy:
|
|
|
108
110
|
self.total_rounds = total_rounds
|
|
109
111
|
|
|
110
112
|
self.strategy_object = None
|
|
113
|
+
self.current_booster: Optional[xgb.Booster] = None
|
|
114
|
+
self.current_num_trees: int = 0
|
|
111
115
|
|
|
112
116
|
def create_strategy(self) -> None:
|
|
113
117
|
"""
|
|
@@ -278,19 +282,14 @@ class XGBoostStrategy:
|
|
|
278
282
|
f"Client:{client.cid}: {fit_res.metrics}"
|
|
279
283
|
)
|
|
280
284
|
|
|
285
|
+
if failures:
|
|
286
|
+
print(f"[Server/XGBoost] ⚠️ Round {server_round} failures: {failures}")
|
|
287
|
+
|
|
281
288
|
if not results:
|
|
282
289
|
print("[Server/XGBoost] No fit results received.")
|
|
283
290
|
return None, {}
|
|
284
291
|
|
|
285
|
-
#
|
|
286
|
-
best_client, best_fit_res = max(
|
|
287
|
-
results,
|
|
288
|
-
key=lambda item: item[1].num_examples,
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
agg_parameters = best_fit_res.parameters
|
|
292
|
-
|
|
293
|
-
# Manual weighted metric aggregation
|
|
292
|
+
# Aggregate metrics manually, same as before.
|
|
294
293
|
metric_results = []
|
|
295
294
|
|
|
296
295
|
for _, fit_res in results:
|
|
@@ -303,18 +302,95 @@ class XGBoostStrategy:
|
|
|
303
302
|
|
|
304
303
|
metrics = aggregate_weighted_metrics(metric_results)
|
|
305
304
|
|
|
305
|
+
previous_num_trees = int(self.current_num_trees)
|
|
306
|
+
new_local_boosters = []
|
|
307
|
+
|
|
306
308
|
print(
|
|
307
|
-
f"[Server/XGBoost]
|
|
308
|
-
f"
|
|
309
|
-
f"with {best_fit_res.num_examples} examples."
|
|
309
|
+
f"[Server/XGBoost] Previous global tree count before round "
|
|
310
|
+
f"{server_round}: {previous_num_trees}"
|
|
310
311
|
)
|
|
311
312
|
|
|
313
|
+
for client, fit_res in results:
|
|
314
|
+
client_booster = parameters_to_booster(fit_res.parameters)
|
|
315
|
+
|
|
316
|
+
if client_booster is None:
|
|
317
|
+
print(
|
|
318
|
+
f"[Server/XGBoost] Client {client.cid} returned no booster. Skipping."
|
|
319
|
+
)
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
client_total_trees = self._num_boosted_rounds(client_booster)
|
|
323
|
+
|
|
324
|
+
print(
|
|
325
|
+
f"[Server/XGBoost] Client {client.cid} returned booster with "
|
|
326
|
+
f"{client_total_trees} trees."
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# In round 1, previous_num_trees is 0, so this keeps all local trees.
|
|
330
|
+
# In later rounds, this keeps only the new trees added by the client.
|
|
331
|
+
new_trees_booster = self._slice_new_trees(
|
|
332
|
+
client_booster=client_booster,
|
|
333
|
+
previous_num_trees=previous_num_trees,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if new_trees_booster is None:
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
new_tree_count = self._num_boosted_rounds(new_trees_booster)
|
|
340
|
+
|
|
341
|
+
print(
|
|
342
|
+
f"[Server/XGBoost] Client {client.cid} contributed "
|
|
343
|
+
f"{new_tree_count} new trees."
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
new_local_boosters.append(new_trees_booster)
|
|
347
|
+
|
|
348
|
+
if not new_local_boosters:
|
|
349
|
+
print(
|
|
350
|
+
"[Server/XGBoost] No new local trees received. "
|
|
351
|
+
"Keeping previous global booster."
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if self.current_booster is None:
|
|
355
|
+
return None, metrics
|
|
356
|
+
|
|
357
|
+
agg_parameters = booster_to_parameters(self.current_booster)
|
|
358
|
+
return agg_parameters, metrics
|
|
359
|
+
|
|
360
|
+
boosters_to_combine = []
|
|
361
|
+
|
|
362
|
+
if self.current_booster is not None:
|
|
363
|
+
boosters_to_combine.append(self.current_booster)
|
|
364
|
+
|
|
365
|
+
boosters_to_combine.extend(new_local_boosters)
|
|
366
|
+
|
|
367
|
+
combined_booster = self._combine_boosters_by_json(boosters_to_combine)
|
|
368
|
+
|
|
369
|
+
if combined_booster is None:
|
|
370
|
+
print("[Server/XGBoost] Failed to combine boosters.")
|
|
371
|
+
return None, metrics
|
|
372
|
+
|
|
373
|
+
self.current_booster = combined_booster
|
|
374
|
+
self.current_num_trees = self._num_boosted_rounds(combined_booster)
|
|
375
|
+
|
|
376
|
+
print(
|
|
377
|
+
f"[Server/XGBoost] ✅ Round {server_round} - Flower-style bagging aggregation done."
|
|
378
|
+
)
|
|
379
|
+
print(
|
|
380
|
+
f"[Server/XGBoost] Global booster now has {self.current_num_trees} trees."
|
|
381
|
+
)
|
|
312
382
|
print(f"[Server/XGBoost] Aggregated metrics: {metrics}\n")
|
|
313
383
|
|
|
384
|
+
# Optional: print importance directly, no saving required.
|
|
385
|
+
if server_round == self.total_rounds:
|
|
386
|
+
self._print_feature_importance(combined_booster, server_round)
|
|
387
|
+
|
|
388
|
+
agg_parameters = booster_to_parameters(combined_booster)
|
|
389
|
+
|
|
390
|
+
# Optional: keep your existing save behavior if savingPath is provided.
|
|
314
391
|
self._save_if_needed(server_round, agg_parameters)
|
|
315
392
|
|
|
316
393
|
return agg_parameters, metrics
|
|
317
|
-
|
|
318
394
|
strategy.aggregate_fit = wrapped_aggregate_fit
|
|
319
395
|
|
|
320
396
|
|
|
@@ -377,4 +453,216 @@ class XGBoostStrategy:
|
|
|
377
453
|
|
|
378
454
|
save_booster(booster, filepath)
|
|
379
455
|
|
|
380
|
-
print(f"[Server/XGBoost] 💾 Saved booster to: {filepath}")
|
|
456
|
+
print(f"[Server/XGBoost] 💾 Saved booster to: {filepath}")
|
|
457
|
+
def _num_boosted_rounds(self, booster: xgb.Booster) -> int:
|
|
458
|
+
"""
|
|
459
|
+
Return the number of boosted rounds/trees in the booster.
|
|
460
|
+
"""
|
|
461
|
+
try:
|
|
462
|
+
return int(booster.num_boosted_rounds())
|
|
463
|
+
except Exception:
|
|
464
|
+
try:
|
|
465
|
+
return len(booster.get_dump())
|
|
466
|
+
except Exception:
|
|
467
|
+
return 0
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _slice_new_trees(
|
|
471
|
+
self,
|
|
472
|
+
client_booster: xgb.Booster,
|
|
473
|
+
previous_num_trees: int,
|
|
474
|
+
) -> Optional[xgb.Booster]:
|
|
475
|
+
"""
|
|
476
|
+
Extract only the trees added by the client in this round.
|
|
477
|
+
|
|
478
|
+
Client booster = previous global booster + new local trees.
|
|
479
|
+
We keep only new local trees.
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
total_trees = self._num_boosted_rounds(client_booster)
|
|
483
|
+
|
|
484
|
+
if total_trees <= previous_num_trees:
|
|
485
|
+
print(
|
|
486
|
+
"[Server/XGBoost] Client booster has no new trees. "
|
|
487
|
+
f"total_trees={total_trees}, previous_num_trees={previous_num_trees}"
|
|
488
|
+
)
|
|
489
|
+
return None
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
return client_booster[previous_num_trees:total_trees]
|
|
493
|
+
except Exception as exc:
|
|
494
|
+
print(f"[Server/XGBoost] Failed to slice new trees: {exc}")
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _booster_to_raw_bytes(self, booster: xgb.Booster) -> bytes:
|
|
499
|
+
"""
|
|
500
|
+
Serialize booster to raw UBJ bytes.
|
|
501
|
+
"""
|
|
502
|
+
with tempfile.NamedTemporaryFile(suffix=".ubj", delete=False) as tmp:
|
|
503
|
+
tmp_path = tmp.name
|
|
504
|
+
|
|
505
|
+
try:
|
|
506
|
+
booster.save_model(tmp_path)
|
|
507
|
+
with open(tmp_path, "rb") as f:
|
|
508
|
+
return f.read()
|
|
509
|
+
finally:
|
|
510
|
+
try:
|
|
511
|
+
os.remove(tmp_path)
|
|
512
|
+
except Exception:
|
|
513
|
+
pass
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _booster_from_raw_bytes(self, raw: bytes) -> Optional[xgb.Booster]:
|
|
517
|
+
"""
|
|
518
|
+
Deserialize booster from raw UBJ bytes.
|
|
519
|
+
"""
|
|
520
|
+
if not raw:
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
with tempfile.NamedTemporaryFile(suffix=".ubj", delete=False) as tmp:
|
|
524
|
+
tmp_path = tmp.name
|
|
525
|
+
tmp.write(raw)
|
|
526
|
+
|
|
527
|
+
try:
|
|
528
|
+
booster = xgb.Booster()
|
|
529
|
+
booster.load_model(tmp_path)
|
|
530
|
+
return booster
|
|
531
|
+
except Exception as exc:
|
|
532
|
+
print(f"[Server/XGBoost] Failed to load booster from bytes: {exc}")
|
|
533
|
+
return None
|
|
534
|
+
finally:
|
|
535
|
+
try:
|
|
536
|
+
os.remove(tmp_path)
|
|
537
|
+
except Exception:
|
|
538
|
+
pass
|
|
539
|
+
|
|
540
|
+
def _combine_boosters_by_json(
|
|
541
|
+
self,
|
|
542
|
+
boosters: list[xgb.Booster],
|
|
543
|
+
) -> Optional[xgb.Booster]:
|
|
544
|
+
"""
|
|
545
|
+
Combine multiple XGBoost boosters by concatenating their trees.
|
|
546
|
+
|
|
547
|
+
Fixes:
|
|
548
|
+
- updates trees
|
|
549
|
+
- updates tree_info
|
|
550
|
+
- updates gbtree_model_param["num_trees"]
|
|
551
|
+
- updates iteration_indptr
|
|
552
|
+
|
|
553
|
+
This is the missing part that caused:
|
|
554
|
+
Check failed: model.iteration_indptr.back() == model.param.num_trees
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
boosters = [b for b in boosters if b is not None]
|
|
558
|
+
|
|
559
|
+
if not boosters:
|
|
560
|
+
return None
|
|
561
|
+
|
|
562
|
+
if len(boosters) == 1:
|
|
563
|
+
return boosters[0]
|
|
564
|
+
|
|
565
|
+
model_jsons = []
|
|
566
|
+
|
|
567
|
+
for booster in boosters:
|
|
568
|
+
raw_json = booster.save_raw(raw_format="json")
|
|
569
|
+
if isinstance(raw_json, bytes):
|
|
570
|
+
raw_json = raw_json.decode("utf-8")
|
|
571
|
+
model_jsons.append(json.loads(raw_json))
|
|
572
|
+
|
|
573
|
+
base = model_jsons[0]
|
|
574
|
+
|
|
575
|
+
base_model = base["learner"]["gradient_booster"]["model"]
|
|
576
|
+
base_trees = base_model["trees"]
|
|
577
|
+
base_tree_info = base_model["tree_info"]
|
|
578
|
+
|
|
579
|
+
# Start from the base model iteration structure
|
|
580
|
+
base_iteration_indptr = base_model.get("iteration_indptr", None)
|
|
581
|
+
|
|
582
|
+
if base_iteration_indptr is None:
|
|
583
|
+
# Safe fallback for binary/regression: one tree per iteration
|
|
584
|
+
combined_iteration_indptr = list(range(0, len(base_trees) + 1))
|
|
585
|
+
else:
|
|
586
|
+
combined_iteration_indptr = list(base_iteration_indptr)
|
|
587
|
+
|
|
588
|
+
next_tree_id = len(base_trees)
|
|
589
|
+
|
|
590
|
+
for extra in model_jsons[1:]:
|
|
591
|
+
extra_model = extra["learner"]["gradient_booster"]["model"]
|
|
592
|
+
|
|
593
|
+
extra_trees = extra_model["trees"]
|
|
594
|
+
extra_tree_info = extra_model["tree_info"]
|
|
595
|
+
|
|
596
|
+
# Append trees and reassign IDs
|
|
597
|
+
for tree in extra_trees:
|
|
598
|
+
tree["id"] = next_tree_id
|
|
599
|
+
base_trees.append(tree)
|
|
600
|
+
next_tree_id += 1
|
|
601
|
+
|
|
602
|
+
# Append tree_info
|
|
603
|
+
base_tree_info.extend(extra_tree_info)
|
|
604
|
+
|
|
605
|
+
# Append iteration_indptr correctly
|
|
606
|
+
extra_iteration_indptr = extra_model.get("iteration_indptr", None)
|
|
607
|
+
|
|
608
|
+
if extra_iteration_indptr is None:
|
|
609
|
+
# Fallback: one tree per boosting iteration
|
|
610
|
+
extra_iteration_indptr = list(range(0, len(extra_trees) + 1))
|
|
611
|
+
|
|
612
|
+
offset = combined_iteration_indptr[-1]
|
|
613
|
+
|
|
614
|
+
# Skip the first 0 from the extra booster
|
|
615
|
+
for ptr in extra_iteration_indptr[1:]:
|
|
616
|
+
combined_iteration_indptr.append(offset + ptr)
|
|
617
|
+
|
|
618
|
+
# Write back merged model fields
|
|
619
|
+
base_model["trees"] = base_trees
|
|
620
|
+
base_model["tree_info"] = base_tree_info
|
|
621
|
+
base_model["iteration_indptr"] = combined_iteration_indptr
|
|
622
|
+
|
|
623
|
+
# Update num_trees
|
|
624
|
+
if "gbtree_model_param" in base_model:
|
|
625
|
+
base_model["gbtree_model_param"]["num_trees"] = str(len(base_trees))
|
|
626
|
+
|
|
627
|
+
# Save/load to validate the merged model
|
|
628
|
+
with tempfile.NamedTemporaryFile(
|
|
629
|
+
mode="w",
|
|
630
|
+
suffix=".json",
|
|
631
|
+
delete=False,
|
|
632
|
+
encoding="utf-8",
|
|
633
|
+
) as tmp:
|
|
634
|
+
tmp_path = tmp.name
|
|
635
|
+
json.dump(base, tmp)
|
|
636
|
+
|
|
637
|
+
try:
|
|
638
|
+
combined = xgb.Booster()
|
|
639
|
+
combined.load_model(tmp_path)
|
|
640
|
+
return combined
|
|
641
|
+
finally:
|
|
642
|
+
try:
|
|
643
|
+
os.remove(tmp_path)
|
|
644
|
+
except Exception:
|
|
645
|
+
pass
|
|
646
|
+
def _print_feature_importance(
|
|
647
|
+
self,
|
|
648
|
+
booster: xgb.Booster,
|
|
649
|
+
server_round: int,
|
|
650
|
+
) -> None:
|
|
651
|
+
"""
|
|
652
|
+
Print feature importance for the current global booster.
|
|
653
|
+
"""
|
|
654
|
+
if booster is None:
|
|
655
|
+
return
|
|
656
|
+
|
|
657
|
+
importance_gain = booster.get_score(importance_type="gain")
|
|
658
|
+
importance_weight = booster.get_score(importance_type="weight")
|
|
659
|
+
importance_cover = booster.get_score(importance_type="cover")
|
|
660
|
+
|
|
661
|
+
print(f"\n[Server/XGBoost] 📊 Round {server_round} Global Feature Importance - Gain:")
|
|
662
|
+
print(importance_gain)
|
|
663
|
+
|
|
664
|
+
print(f"[Server/XGBoost] 📊 Round {server_round} Global Feature Importance - Weight:")
|
|
665
|
+
print(importance_weight)
|
|
666
|
+
|
|
667
|
+
print(f"[Server/XGBoost] 📊 Round {server_round} Global Feature Importance - Cover:")
|
|
668
|
+
print(importance_cover)
|
|
@@ -14,7 +14,7 @@ with open('requirements.txt') as f:
|
|
|
14
14
|
|
|
15
15
|
setup(
|
|
16
16
|
name="medfl",
|
|
17
|
-
version="2.0.10",
|
|
17
|
+
version="2.0.10.dev1",
|
|
18
18
|
author="MEDomics consortium",
|
|
19
19
|
author_email="medomics.info@gmail.com",
|
|
20
20
|
description="Python Open-source package for simulating federated learning and differential privacy",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|