CFLA 1.0.1__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.
Files changed (51) hide show
  1. cfla-1.0.1.dist-info/METADATA +249 -0
  2. cfla-1.0.1.dist-info/RECORD +51 -0
  3. cfla-1.0.1.dist-info/WHEEL +5 -0
  4. cfla-1.0.1.dist-info/top_level.txt +3 -0
  5. datasets/__init__.py +3 -0
  6. datasets/ag_news.py +198 -0
  7. datasets/dataset_utils.py +222 -0
  8. datasets/emnist.py +30 -0
  9. datasets/femnist.py +181 -0
  10. experiments/scripts/cgpfl.py +74 -0
  11. experiments/scripts/fedgroup.py +76 -0
  12. experiments/scripts/fedper.py +72 -0
  13. experiments/scripts/fesem.py +73 -0
  14. experiments/scripts/flhc.py +75 -0
  15. experiments/scripts/hcfl.py +93 -0
  16. experiments/scripts/ifca.py +73 -0
  17. experiments/scripts/lcfed.py +82 -0
  18. experiments/scripts/plot_results.py +396 -0
  19. experiments/scripts/run_all_agnews.py +305 -0
  20. experiments/scripts/run_all_cifar-10.py +164 -0
  21. experiments/scripts/run_all_femnist.py +341 -0
  22. experiments/scripts/run_all_mnist.py +164 -0
  23. framework/client/__init__.py +1 -0
  24. framework/client/client_cgpfl.py +166 -0
  25. framework/client/client_fedgroup.py +149 -0
  26. framework/client/client_fedper.py +168 -0
  27. framework/client/client_fesem.py +153 -0
  28. framework/client/client_flhc.py +154 -0
  29. framework/client/client_hcfl.py +241 -0
  30. framework/client/client_ifca.py +173 -0
  31. framework/client/client_lcfed.py +277 -0
  32. framework/client/client_madmtop.py +94 -0
  33. framework/client/clientbase.py +108 -0
  34. framework/common/__init__.py +1 -0
  35. framework/common/parameter_tree.py +89 -0
  36. framework/common/utils.py +55 -0
  37. framework/models/computer_vision.py +167 -0
  38. framework/models/large_language_models.py +42 -0
  39. framework/models/nlp_models.py +71 -0
  40. framework/server/__init__.py +1 -0
  41. framework/server/server_cgpfl.py +184 -0
  42. framework/server/server_fedavg.py +132 -0
  43. framework/server/server_fedgroup.py +229 -0
  44. framework/server/server_fedper.py +145 -0
  45. framework/server/server_fesem.py +188 -0
  46. framework/server/server_flhc.py +261 -0
  47. framework/server/server_hcfl.py +356 -0
  48. framework/server/server_ifca.py +184 -0
  49. framework/server/server_madmtop.py +228 -0
  50. framework/server/serverbase.py +28 -0
  51. framework/server/serveur_lcfed.py +319 -0
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.4
2
+ Name: CFLA
3
+ Version: 1.0.1
4
+ Summary: CFLA - a framework for clustered federated learning algorithms
5
+ Author-email: HOUNSI Antoine <antoinehounsi3@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Totorino02/CFLA
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: torch
14
+ Requires-Dist: numpy
15
+ Requires-Dist: tqdm
16
+ Requires-Dist: declearn
17
+ Requires-Dist: scikit-learn
18
+ Requires-Dist: pandas
19
+ Requires-Dist: matplotlib
20
+ Provides-Extra: energy
21
+
22
+ # CFLA — Clustered Federated Learning Algorithms
23
+
24
+ A Python library implementing and benchmarking **Clustered Federated Learning (CFL)** algorithms. It includes reproductions of state-of-the-art methods from the literature alongside **HCFL**, an original algorithm that automatically discovers the number of clusters without requiring it as a hyperparameter.
25
+
26
+ ---
27
+
28
+ ## Overview
29
+
30
+ **Federated Learning (FL)** enables training machine learning models on decentralized data without sharing it. Each client trains locally and only sends model updates to a central server.
31
+
32
+ **Clustered Federated Learning** extends FL by grouping similar clients and training one specialized model per cluster. This improves personalization when client data is heterogeneous (non-IID), which is the common case in practice.
33
+
34
+ CFLA provides a unified framework to implement, run, and compare CFL algorithms on standard benchmarks, with built-in support for energy consumption monitoring.
35
+
36
+ ---
37
+
38
+ ## Algorithms
39
+
40
+ ### Baselines from the literature
41
+
42
+ | Algorithm | Clustering strategy | Regularization | Reference |
43
+ |-----------|-------------------|----------------|-----------|
44
+ | **FedAvg** | None — single global model | None | McMahan et al., AISTATS 2017 |
45
+ | **FLHC** | Offline — agglomerative on gradient update vectors | None | Briggs et al., 2020 |
46
+ | **FedGroup** | Offline — agglomerative on model params (cosine) | None | Tan et al., 2022 |
47
+ | **FeSEM** | Online — L2 distance to cluster centers (EM) | None | Li et al., 2021 |
48
+ | **CGPFL** | Online — cosine similarity to cluster centers | `(μ/2)‖ω−Ω_k‖²` | Liu et al. |
49
+ | **IFCA** | Online — empirical risk (loss) per cluster | None | Ghosh et al., NeurIPS 2020 |
50
+ | **LCFed** | Online — PCA low-rank projection + cosine | `(μ/2)‖ω−Ω_k‖² + (λ/2)‖φ−Φ‖²` | Zhang et al., ICASSP 2025 |
51
+
52
+ ### HCFL (original contribution)
53
+
54
+ HCFL automatically discovers the number of clusters $K^*$ via agglomerative hierarchical clustering on client embedding update vectors, then trains cluster-specific models using a FedProx-style objective toward the cluster center:
55
+
56
+ $$
57
+ \mathcal{L}_i(\omega) = \mathcal{L}(\omega; D_i) + \frac{\mu}{2} \|\omega - \Omega_k\|^2
58
+ $$
59
+
60
+ Inter-cluster knowledge sharing is handled **server-side** via a scheduled blending of cluster models toward the global embedding Φ:
61
+
62
+ $$
63
+ \Omega_k^{(t)} \leftarrow (1 - \lambda^{(t)}) \cdot \text{Avg}\left(\{\omega_i : i \in S_t \cap C_k\}\right) + \lambda^{(t)} \cdot \Phi^{(t)}
64
+ $$
65
+ $$
66
+ \lambda^{(t)} = \frac{\lambda_0}{(1 + \alpha \cdot t)^p}
67
+ $$
68
+
69
+ This decouples two roles: **μ** controls client-to-cluster fidelity (local), **λ(t)** controls inter-cluster sharing (server-side, decaying).
70
+
71
+ **Training procedure:**
72
+ 1. **Pre-training** (`R_pre` rounds) — FedAvg warm-up to build a meaningful global representation
73
+ 2. **Cluster discovery** — each client computes δᵢ = φᵢ - φ⁽⁰⁾ after one local step; agglomerative clustering on {δᵢ} determines K* automatically
74
+ 3. **CFL phase** (`T` rounds) — per-cluster training with server-side embedding blending; clusters specialize progressively as λ(t) → 0
75
+
76
+ ---
77
+
78
+ ## Results
79
+
80
+ Evaluated on MNIST, FEMNIST, and AG News with N=50 clients.
81
+
82
+ | Method | MNIST Acc ± Std | FEMNIST Acc ± Std | AG News Acc ± Std |
83
+ |--------|----------------|-------------------|-------------------|
84
+ | IFCA† | 97.3 ± 0.5 | 11.4 ± 15.5 | 81.5 ± 19.2 |
85
+ | FLHC† | 97.1 ± 0.4 | **35.2 ± 15.1** | 79.1 ± 22.6 |
86
+ | LCFed† | 82.4 ± 10.5 | 6.9 ± 16.3 | 27.4 ± 30.5 |
87
+ | FeSEM | 90.4 ± 12.2 | 12.8 ± 19.2 | 79.4 ± 22.3 |
88
+ | FedGroup | 91.5 ± 2.1 | 5.2 ± 11.2 | 23.0 ± 25.6 |
89
+ | CGPFL | 50.7 ± 7.9 | 12.7 ± 20.5 | 27.9 ± 26.9 |
90
+ | **HCFL (ours)** | **94.3 ± 0.7** | **32.3 ± 16.7** | **82.0 ± 18.3** |
91
+
92
+ †: requires K a priori. **Bold**: best no-K method. **Bold + best overall**: AG News.
93
+
94
+ HCFL is the best no-K method on all three benchmarks, and achieves the best overall accuracy on AG News — surpassing oracle-K methods without any prior knowledge of K.
95
+
96
+ ---
97
+
98
+ ## Project Structure
99
+
100
+ ```
101
+ CFLA/
102
+ ├── framework/
103
+ │ ├── client/
104
+ │ │ ├── clientbase.py # Abstract Client base class
105
+ │ │ ├── client_hcfl.py # HCFL client
106
+ │ │ ├── client_flhc.py # FLHC client
107
+ │ │ ├── client_lcfed.py # LCFed client
108
+ │ │ ├── client_fesem.py # FeSEM client
109
+ │ │ ├── client_cgpfl.py # CGPFL client
110
+ │ │ └── client_ifca.py # IFCA client
111
+ │ ├── server/
112
+ │ │ ├── serverbase.py # Abstract Server base class
113
+ │ │ ├── server_hcfl.py # HCFL server
114
+ │ │ ├── server_flhc.py # FLHC server
115
+ │ │ ├── serveur_lcfed.py # LCFed server
116
+ │ │ ├── server_fesem.py # FeSEM server
117
+ │ │ ├── server_cgpfl.py # CGPFL server
118
+ │ │ └── server_ifca.py # IFCA server
119
+ │ ├── models/
120
+ │ │ ├── computer_vision.py # LeNet-5, SplitLeNet5, CNN variants
121
+ │ │ └── nlp_models.py # DistilBERT-based text encoder
122
+ │ └── common/
123
+ │ └── utils.py # flatten_params, average_state_dict, cosine_sim
124
+ ├── datasets/
125
+ │ ├── femnist.py # FEMNIST loader + Dirichlet partitioning
126
+ │ └── ag_news.py # AG News loader + Dirichlet partitioning
127
+ ├── experiments/
128
+ │ └── scripts/
129
+ │ ├── run_all_mnist.py # Run all algorithms on MNIST
130
+ │ ├── run_all_cifar-10.py # Run all algorithms on CIFAR-10
131
+ │ ├── run_all_femnist.py # Run all algorithms on FEMNIST
132
+ │ ├── run_all_agnews.py # Run all algorithms on AG News
133
+ │ ├── hcfl.py
134
+ │ ├── lcfed.py
135
+ │ ├── flhc.py
136
+ │ ├── fedgroup.py
137
+ │ ├── fesem.py
138
+ │ ├── cgpfl.py
139
+ │ ├── ifca.py
140
+ │ └── plot_results.py # Performance plots + comparison table
141
+ ├── pyproject.toml
142
+ └── requirements.dev.txt
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Installation
148
+
149
+ ```bash
150
+ git clone https://github.com/Totorino02/CFLA.git
151
+ cd CFLA
152
+ pip install -e .
153
+ pip install -r requirements.dev.txt
154
+ ```
155
+
156
+ or
157
+
158
+ ```bash
159
+ pip install cfla
160
+ ```
161
+
162
+ ### Energy monitoring (Linux only)
163
+
164
+ Energy tracking via RAPL (CPU) and NVML (GPU) is disabled by default (`monitor_energy: False`).
165
+ To enable it on Linux:
166
+
167
+ ```bash
168
+ sudo chmod 444 /sys/class/powercap/intel-rapl:*/energy_uj
169
+ sudo chmod 444 /sys/class/powercap/intel-rapl:*:*/energy_uj
170
+ ```
171
+
172
+ Then set `"monitor_energy": True` in the client args of your experiment script.
173
+
174
+ ---
175
+
176
+ ## Quick Start
177
+
178
+ Run HCFL on MNIST:
179
+
180
+ ```bash
181
+ python -m experiments.scripts.hcfl
182
+ ```
183
+
184
+ Run all algorithms on a specific dataset:
185
+
186
+ ```bash
187
+ python -m experiments.scripts.run_all_mnist
188
+ python -m experiments.scripts.run_all_femnist
189
+ python -m experiments.scripts.run_all_cifar-10
190
+ python -m experiments.scripts.run_all_agnews
191
+ ```
192
+
193
+ ### Generate plots and comparison table
194
+
195
+ ```bash
196
+ python -m experiments.scripts.plot_results --results_dir ./RESULTS/my_run --output_dir ./PLOTS/my_run
197
+ ```
198
+
199
+ Produces:
200
+ - `accuracy_curves.png` — mean accuracy per round with ±1σ band
201
+ - `loss_curves.png` — mean loss per round
202
+ - `accuracy_boxplot.png` — per-client accuracy distribution at the final round
203
+ - `convergence_speed.png` — rounds needed to reach a target accuracy
204
+ - `comparison_table.csv` — final metrics summary
205
+
206
+ ---
207
+
208
+ ## Supported Datasets
209
+
210
+ | Dataset | Classes | Input | Partitioning |
211
+ |---------|---------|-------|--------------|
212
+ | **MNIST** | 10 | 28×28 grayscale | Structured non-IID (disjoint class groups) |
213
+ | **CIFAR-10** | 10 | 32×32 RGB | Structured non-IID |
214
+ | **FEMNIST** | 62 | 28×28 grayscale | Dirichlet(α) |
215
+ | **AG News** | 4 | Text | Dirichlet(α) |
216
+
217
+ ---
218
+
219
+ ## Output Format
220
+
221
+ Each experiment writes results to a local `RESULTS/` directory (not tracked in git):
222
+
223
+ ```
224
+ result_{algo}_{dataset}_{timestamp}/
225
+ ├── server_metrics.csv # round, mean_acc, std_acc, mean_loss
226
+ └── client_{id}/
227
+ └── metrics.csv # round, loss, accuracy_before, accuracy_after, energy_consumed, energy_ratio
228
+ ```
229
+
230
+ ---
231
+
232
+ ## References
233
+
234
+ - McMahan, B. et al. (2017). Communication-efficient learning of deep networks from decentralized data. *AISTATS*. [arXiv:1602.05629](https://arxiv.org/abs/1602.05629)
235
+ - Briggs, C., Fan, Z., & Andras, P. (2020). Federated learning with hierarchical clustering of local updates to improve training on non-IID data. [arXiv:2004.11791](https://arxiv.org/abs/2004.11791)
236
+ - Ghosh, A. et al. (2020). An efficient framework for clustered federated learning. *NeurIPS*. [arXiv:2006.04088](https://arxiv.org/abs/2006.04088)
237
+ - Tan, Y. et al. (2022). Towards personalized federated learning. *IEEE TNNLS*. [arXiv:2103.00710](https://arxiv.org/abs/2103.00710)
238
+ - Li, X. et al. (2021). FeSEM: Federated learning via expectation maximization. *IEEE TPAMI*.
239
+ - Zhang, Y. et al. (2025). LCFed: An efficient clustered federated learning framework for heterogeneous data. *ICASSP*. [arXiv:2501.01850](https://arxiv.org/abs/2501.01850)
240
+ - Zhang, Y. et al. (2015). Character-level convolutional networks for text classification. *NeurIPS*. [arXiv:1509.01626](https://arxiv.org/abs/1509.01626)
241
+ - Caldas, S. et al. (2019). LEAF: A benchmark for federated settings. [arXiv:1812.01097](https://arxiv.org/abs/1812.01097)
242
+
243
+ ---
244
+
245
+ ## Author
246
+
247
+ Antoine Hounsi — [antoinehounsi3@gmail.com](mailto:antoinehounsi3@gmail.com)
248
+
249
+ Master's research project, Université de Lille.
@@ -0,0 +1,51 @@
1
+ datasets/__init__.py,sha256=lTKdRvLnWlrKZVTXKzxcIpxmM4DrXQ8FZEkpPh0RL4M,111
2
+ datasets/ag_news.py,sha256=qG8DErSyLrqJENOlPTStUSitjb6bSVA7cbFTv2cl7i0,6679
3
+ datasets/dataset_utils.py,sha256=WQZ6Egi4Zbga7Iuxo_O3gOs6zif8uTfvY5FlHvyMK_M,8580
4
+ datasets/emnist.py,sha256=iVvj3z8JDZSIzmpk_XAe-jnjex3wHy5z3Mk2niz1lfc,857
5
+ datasets/femnist.py,sha256=fhat3y3u3NyXW4l7ktj4hRFQQoPH0RXy2UEi9nHE2mk,6760
6
+ experiments/scripts/cgpfl.py,sha256=lO5v5yx-J1Kyz_LKDrgP5j5cA2CMtUZuErsolid-qxM,2205
7
+ experiments/scripts/fedgroup.py,sha256=iCCna7a2TqLETMnlxOMsLe8LlwTUIblb7IRDr40gMe0,2263
8
+ experiments/scripts/fedper.py,sha256=Q5PbD1lmJR8xUQfYRaD1XGRW-Kq1b7SzyxfIUWM3tdM,2179
9
+ experiments/scripts/fesem.py,sha256=EV2WRmxapqmrrIjb57r3cR81Vc6gJLme-eFC1xQg3yI,2182
10
+ experiments/scripts/flhc.py,sha256=c7Qcy6VTeYkfsXf4Dm9oGgFTJBvmFALtybfWgQmIVLA,2288
11
+ experiments/scripts/hcfl.py,sha256=SSTdeQ8yi_F5oZfg5pyYhYyjBx16e0RuFIKDRXGE0n0,2700
12
+ experiments/scripts/ifca.py,sha256=TzMCJuptKyiugHeQuUOTPDBqia_Y_8aw7OH5YGlt5IQ,2186
13
+ experiments/scripts/lcfed.py,sha256=RGGxiWwnQ7-GOmLj2QhKZ4pZ_R55ILflKm2BIPsGmd8,2517
14
+ experiments/scripts/plot_results.py,sha256=7i1bTfHARq4CJEjfbGoL-2NrRyXs5EJVXwl29_P8IKo,13313
15
+ experiments/scripts/run_all_agnews.py,sha256=DWBdObeJ5zD7WcSVhUwyKvfDc6cSz03z7yTLGfAMHfc,9486
16
+ experiments/scripts/run_all_cifar-10.py,sha256=7kccDeMiBZib9y8Lv9rXAYkR6asBijQLZzy8UQDp8i8,6549
17
+ experiments/scripts/run_all_femnist.py,sha256=bk19LrAkgWaaKJjWU-6bYaTg5IM02mCQDY2kCWyGZq8,10079
18
+ experiments/scripts/run_all_mnist.py,sha256=XMczm3GxCws9K6VlahGDI78IYXOBsQ-dVUveEgk7BxY,6547
19
+ framework/client/__init__.py,sha256=u-lHIoBqU4m6P42roML_MVUJ_ODgFyA8AVxaezmFKF8,40
20
+ framework/client/client_cgpfl.py,sha256=3YpN8I8OP2pofnBYH5Dqf-RkqYonX36HFnu-x_kY3oc,6415
21
+ framework/client/client_fedgroup.py,sha256=AUj2KpZ3mvdxGd4xzMzqLuN2abZJ97qq_dScY9IS9Wo,5690
22
+ framework/client/client_fedper.py,sha256=Ma_tSS9zv9t-jwBXARuX5m4WPVOAFMdwDm40TvILqIw,6613
23
+ framework/client/client_fesem.py,sha256=JDVNWLlcWwXXVp681TYnhFucZg-AfES-KTnC2YA4-mM,5883
24
+ framework/client/client_flhc.py,sha256=8qLWnoG-g9uxsY5Mv0uRRVhQST_fdn-0l7mMb_-VQzg,6280
25
+ framework/client/client_hcfl.py,sha256=Z2WI9p_iCxnYfLfkQeMWjq6qrYM9MW908YTEilK98Ew,10012
26
+ framework/client/client_ifca.py,sha256=oa0PJr_wLsKNoMxoio4_qz_E7z3GqwjF951KDlgu3Rs,6793
27
+ framework/client/client_lcfed.py,sha256=iUfaqk0D0m_QqQFkAP3QS_pfpQgU_BkmAP6VCsWSFHo,10985
28
+ framework/client/client_madmtop.py,sha256=cuowkBbOJZkHvJVmg_sOwD-zwIecMW02ymy0t_jpmYA,3781
29
+ framework/client/clientbase.py,sha256=5QqYVx7Dj8DwoTbSIIqzZt4_NS0lDuusnIPk1dOP3sA,4297
30
+ framework/common/__init__.py,sha256=tWIhSZoWTqEo41QfoyarVSd3_6FmVkVvsSebj0-ww3A,20
31
+ framework/common/parameter_tree.py,sha256=LiFwP2CoHepX_aQ-ruXo1YZvPMhbU5BFYP20NObtte8,3318
32
+ framework/common/utils.py,sha256=2Ep_SJ-hq6tmnIrC4hrGve1S77uVgE6y8N5UpRKrynI,1521
33
+ framework/models/computer_vision.py,sha256=8q0MNeskS0A2f34gyuxTHw6g5u6smFUUlf0XKHVf1OY,5305
34
+ framework/models/large_language_models.py,sha256=Ayksv66rqL7pWbJNbJi_qzYpwM6GBcxvYtYBMNbUIZo,1461
35
+ framework/models/nlp_models.py,sha256=0uoNbYVoeBNQ46w4grpfNxagdgj-CGuvv_icUU_HSPM,2427
36
+ framework/server/__init__.py,sha256=u-lfeZAy9weFoanj4lPeO-S7cJ6d9EtTWXX5erLXMLo,40
37
+ framework/server/server_cgpfl.py,sha256=LkZNPWnVNMxJB_BOvZuihtGAxwI0DsrhpbUMHp1oxBE,7116
38
+ framework/server/server_fedavg.py,sha256=0KEwgRhTk2H8FdNChEbsADAviqM_rrzJz84vQOodAeQ,4761
39
+ framework/server/server_fedgroup.py,sha256=Y_zbfwyb7O4yJ0e4u8cnyonklVp4_eN9Eyo4Z_Hjosg,9106
40
+ framework/server/server_fedper.py,sha256=02SAiQJiqi9TIErXgjkgq836h6Lco2mesLOxTkjqh2Y,5571
41
+ framework/server/server_fesem.py,sha256=2Y3Oi15rD6GeM1vRYd_2rdLcpvJ4feYuNgpSmOHAyMg,7429
42
+ framework/server/server_flhc.py,sha256=9M2zCaIhFUCxUfT2PIDdNdIkajg8BUb6oUXxiyU5vR0,10704
43
+ framework/server/server_hcfl.py,sha256=6G_dWfnu2RykXaIFOtAPZwMtOHBTuWu6Gd5pzbzi8RQ,14923
44
+ framework/server/server_ifca.py,sha256=b_SWbIY-dMmYVTK5Dx8famjpZkDI49nbrF1fsn0xm1g,7060
45
+ framework/server/server_madmtop.py,sha256=RUF_2ivYhDL7bp2HZHkOOgplWQwbU4lOUOg7nWzInZg,8700
46
+ framework/server/serverbase.py,sha256=5Fyzj6mxhXRjEFkh69ld5lUNprPZ1Pt4A1MQHFcQJSc,537
47
+ framework/server/serveur_lcfed.py,sha256=0RVaxGGXtWj48mLlx4QVv0ooSnRxoamPTmfrLMKAmqM,12239
48
+ cfla-1.0.1.dist-info/METADATA,sha256=yPMMg_LLbtQA2RWD7DJpzISKgLVD-QC9VTaivbjsu7k,10219
49
+ cfla-1.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
50
+ cfla-1.0.1.dist-info/top_level.txt,sha256=yuKiyKSdUXIksG2Lz_dat59cba7SaDBq0JN_Sq1W32k,31
51
+ cfla-1.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ datasets
2
+ experiments
3
+ framework
datasets/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .dataset_utils import pathological_partition
2
+
3
+ __all__ = ["dataset_utils", "emnist", "ag_news", "femnist"]
datasets/ag_news.py ADDED
@@ -0,0 +1,198 @@
1
+ """
2
+ AG News dataset utilities for Federated Learning.
3
+
4
+ AG News: 4 classes (World=0, Sports=1, Business=2, Sci/Tech=3),
5
+ 120 000 train samples, 7 600 test samples.
6
+
7
+ Loading strategy (in order of preference):
8
+ 1. Local CSV cache (data_root/AG_NEWS/{train,test}.csv)
9
+ 2. Direct download via requests (skips torchtext C extension)
10
+ """
11
+
12
+ import csv
13
+ import os
14
+ import re
15
+ from collections import Counter
16
+
17
+ import numpy as np
18
+ import requests
19
+ import torch
20
+ from torch.utils.data import DataLoader, Dataset, Subset
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Raw data loading — bypasses torchtext C extension
24
+ # ---------------------------------------------------------------------------
25
+
26
+ _AG_NEWS_URLS = {
27
+ "train": "https://raw.githubusercontent.com/mhjabreel/CharCnn_Keras/master/data/ag_news_csv/train.csv",
28
+ "test": "https://raw.githubusercontent.com/mhjabreel/CharCnn_Keras/master/data/ag_news_csv/test.csv",
29
+ }
30
+
31
+
32
+ def _load_ag_news_csv(split: str, data_root: str) -> list:
33
+ """
34
+ Load AG News from local CSV cache or download.
35
+ Returns list of (label_0indexed, text) tuples.
36
+ CSV format: class_index (1-4), title, description
37
+ """
38
+ cache_dir = os.path.join(data_root, "AG_NEWS")
39
+ os.makedirs(cache_dir, exist_ok=True)
40
+ local_path = os.path.join(cache_dir, f"{split}.csv")
41
+
42
+ if not os.path.exists(local_path):
43
+ print(f"Downloading AG News {split} set...")
44
+ resp = requests.get(_AG_NEWS_URLS[split], timeout=60)
45
+ resp.raise_for_status()
46
+ with open(local_path, "wb") as f:
47
+ f.write(resp.content)
48
+
49
+ rows = []
50
+ with open(local_path, newline="", encoding="utf-8") as f:
51
+ reader = csv.reader(f)
52
+ for row in reader:
53
+ if len(row) < 3:
54
+ continue
55
+ label = int(row[0]) - 1 # 1-indexed → 0-indexed
56
+ text = row[1] + " " + row[2] # title + description
57
+ rows.append((label, text))
58
+ return rows
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Tokenizer & vocabulary
63
+ # ---------------------------------------------------------------------------
64
+
65
+
66
+ def _tokenize(text: str) -> list:
67
+ """Lowercase + strip non-alphanumeric, split on whitespace."""
68
+ text = text.lower()
69
+ text = re.sub(r"[^a-z0-9\s]", " ", text)
70
+ return text.split()
71
+
72
+
73
+ def build_vocab(tokenized_texts: list, max_vocab: int = 25000) -> dict:
74
+ """
75
+ Build word→id mapping from a list of token lists.
76
+ PAD=0, UNK=1, then most-frequent words from index 2.
77
+ """
78
+ counter = Counter(tok for tokens in tokenized_texts for tok in tokens)
79
+ most_common = [w for w, _ in counter.most_common(max_vocab - 2)]
80
+ vocab = {"<pad>": 0, "<unk>": 1}
81
+ for i, w in enumerate(most_common, start=2):
82
+ vocab[w] = i
83
+ return vocab
84
+
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # PyTorch Dataset
88
+ # ---------------------------------------------------------------------------
89
+
90
+
91
+ class AGNewsDataset(Dataset):
92
+ """
93
+ AG News dataset as a PyTorch Dataset.
94
+
95
+ Each item is (token_ids: LongTensor[max_len], label: LongTensor scalar).
96
+ Labels are 0-indexed (original torchtext labels are 1-indexed).
97
+ """
98
+
99
+ def __init__(self, tokens: list, labels: list, vocab: dict, max_len: int = 128):
100
+ self.tokens = tokens
101
+ self.labels = labels
102
+ self.vocab = vocab
103
+ self.max_len = max_len
104
+ self._PAD = vocab["<pad>"]
105
+ self._UNK = vocab["<unk>"]
106
+
107
+ def __len__(self):
108
+ return len(self.labels)
109
+
110
+ def __getitem__(self, idx):
111
+ ids = [self.vocab.get(w, self._UNK) for w in self.tokens[idx][: self.max_len]]
112
+ ids += [self._PAD] * (self.max_len - len(ids))
113
+ return (
114
+ torch.tensor(ids, dtype=torch.long),
115
+ torch.tensor(self.labels[idx], dtype=torch.long),
116
+ )
117
+
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # Dataset builder — loads AG News, builds vocab, Dirichlet FL partitioning
121
+ # ---------------------------------------------------------------------------
122
+
123
+
124
+ def build_client_datasets_agnews(
125
+ base_seed: int,
126
+ run: int,
127
+ n_clients: int = 40,
128
+ alpha: float = 0.1,
129
+ max_len: int = 128,
130
+ max_vocab: int = 25000,
131
+ min_samples: int = 64,
132
+ data_root: str = "./data",
133
+ ):
134
+ """
135
+ Load AG News (CSV download/cache), build vocab, and partition training data
136
+ into n_clients via Dirichlet(alpha) on 4 classes.
137
+
138
+ Returns
139
+ -------
140
+ client_datasets : dict[int, Subset]
141
+ test_loader : DataLoader
142
+ num_classes : int (4)
143
+ vocab : dict (word → id, needed to build the model's vocab_size)
144
+ """
145
+ num_classes = 4
146
+
147
+ # ---- Load raw data (CSV download, no torchtext C extension needed) ----
148
+ train_raw = _load_ag_news_csv("train", data_root)
149
+ test_raw = _load_ag_news_csv("test", data_root)
150
+
151
+ # ---- Tokenize ----
152
+ train_tokens = [_tokenize(text) for _, text in train_raw]
153
+ test_tokens = [_tokenize(text) for _, text in test_raw]
154
+ train_labels = [lbl for lbl, _ in train_raw]
155
+ test_labels = [lbl for lbl, _ in test_raw]
156
+
157
+ # ---- Build vocabulary from training set only ----
158
+ vocab = build_vocab(train_tokens, max_vocab=max_vocab)
159
+
160
+ # ---- Full datasets ----
161
+ train_ds = AGNewsDataset(train_tokens, train_labels, vocab, max_len=max_len)
162
+ test_ds = AGNewsDataset(test_tokens, test_labels, vocab, max_len=max_len)
163
+
164
+ # ---- Dirichlet partitioning on 4 classes ----
165
+ targets = np.array(train_labels)
166
+ rng = np.random.default_rng(base_seed + run * 997)
167
+
168
+ proportions = rng.dirichlet(alpha * np.ones(num_classes), size=n_clients)
169
+
170
+ class_indices = []
171
+ for c in range(num_classes):
172
+ idx = np.where(targets == c)[0].tolist()
173
+ rng.shuffle(idx)
174
+ class_indices.append(idx)
175
+
176
+ client_indices = [[] for _ in range(n_clients)]
177
+ for c in range(num_classes):
178
+ idx = class_indices[c]
179
+ n = len(idx)
180
+ alloc = (proportions[:, c] / proportions[:, c].sum() * n).astype(int)
181
+ alloc[-1] = n - alloc[:-1].sum()
182
+ alloc = np.maximum(alloc, 0)
183
+ offset = 0
184
+ for k in range(n_clients):
185
+ end = offset + alloc[k]
186
+ client_indices[k].extend(idx[offset:end])
187
+ offset = end
188
+
189
+ for k in range(n_clients):
190
+ rng.shuffle(client_indices[k])
191
+
192
+ client_datasets = {}
193
+ for cid, indices in enumerate(client_indices):
194
+ if len(indices) >= min_samples:
195
+ client_datasets[cid] = Subset(train_ds, indices)
196
+
197
+ test_loader = DataLoader(test_ds, batch_size=64, shuffle=False)
198
+ return client_datasets, test_loader, num_classes, vocab