fl-data-generator 0.1.0__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.
- fl_data_generator-0.1.0/LICENSE +21 -0
- fl_data_generator-0.1.0/PKG-INFO +233 -0
- fl_data_generator-0.1.0/README.md +198 -0
- fl_data_generator-0.1.0/pyproject.toml +83 -0
- fl_data_generator-0.1.0/setup.cfg +4 -0
- fl_data_generator-0.1.0/src/fl_data_generator/__init__.py +16 -0
- fl_data_generator-0.1.0/src/fl_data_generator/_validation.py +60 -0
- fl_data_generator-0.1.0/src/fl_data_generator/dataset.py +422 -0
- fl_data_generator-0.1.0/src/fl_data_generator/dirichlet.py +123 -0
- fl_data_generator-0.1.0/src/fl_data_generator/hybrid.py +138 -0
- fl_data_generator-0.1.0/src/fl_data_generator/iid.py +49 -0
- fl_data_generator-0.1.0/src/fl_data_generator/pathological.py +137 -0
- fl_data_generator-0.1.0/src/fl_data_generator/quantity.py +83 -0
- fl_data_generator-0.1.0/src/fl_data_generator.egg-info/PKG-INFO +233 -0
- fl_data_generator-0.1.0/src/fl_data_generator.egg-info/SOURCES.txt +25 -0
- fl_data_generator-0.1.0/src/fl_data_generator.egg-info/dependency_links.txt +1 -0
- fl_data_generator-0.1.0/src/fl_data_generator.egg-info/requires.txt +10 -0
- fl_data_generator-0.1.0/src/fl_data_generator.egg-info/top_level.txt +1 -0
- fl_data_generator-0.1.0/tests/test_dataset.py +254 -0
- fl_data_generator-0.1.0/tests/test_dirichlet.py +198 -0
- fl_data_generator-0.1.0/tests/test_hybrid.py +131 -0
- fl_data_generator-0.1.0/tests/test_iid.py +71 -0
- fl_data_generator-0.1.0/tests/test_pathological.py +206 -0
- fl_data_generator-0.1.0/tests/test_properties.py +200 -0
- fl_data_generator-0.1.0/tests/test_quantity.py +100 -0
- fl_data_generator-0.1.0/tests/test_readme.py +20 -0
- fl_data_generator-0.1.0/tests/test_validation.py +92 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mostafa Khastkhodaei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fl-data-generator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight, framework-agnostic library for IID and non-IID data partitioning in Federated Learning.
|
|
5
|
+
Author-email: Mostafa Khastkhodaei <dev.khastkhodaei@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/khastkhodaei/fl-data-generator
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/khastkhodaei/fl-data-generator/issues
|
|
9
|
+
Project-URL: Source Code, https://github.com/khastkhodaei/fl-data-generator
|
|
10
|
+
Keywords: federated-learning,data-partitioning,non-iid,dirichlet,label-skew,quantity-skew,machine-learning
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.20.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: hypothesis>=6.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff~=0.17.0; extra == "dev"
|
|
32
|
+
Provides-Extra: viz
|
|
33
|
+
Requires-Dist: matplotlib>=3.5.0; extra == "viz"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# fl-data-generator
|
|
37
|
+
|
|
38
|
+
[](https://opensource.org/licenses/MIT)
|
|
39
|
+
[](https://www.python.org/downloads/)
|
|
40
|
+
|
|
41
|
+
A lightweight, framework-agnostic Python library for **IID** and **Non-IID** data partitioning in **Federated Learning (FL)**.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Overview
|
|
46
|
+
|
|
47
|
+
In Federated Learning research and benchmarks, simulating realistic data heterogeneity across distributed clients is essential. **fl-data-generator** simplifies partitioning arbitrary datasets into realistic client distributions without tying you to any specific deep learning framework.
|
|
48
|
+
|
|
49
|
+
Whether you are using PyTorch, TensorFlow, JAX, or federated frameworks like Flower, FedML, or PySyft, `fl-data-generator` provides clean sample indices and dataset wrappers for your clients.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **IID Partitioning**: Uniform random sample allocation across all clients.
|
|
56
|
+
- **Dirichlet Partitioning**: Continuous label distribution skew ($\text{Dir}(\alpha)$) over class proportions, with client minimum size constraints (`min_size`).
|
|
57
|
+
- **Pathological Non-IID Partitioning**: Allocation of a fixed number of classes ($k$ classes per client, following the NIID-Bench benchmark scheme).
|
|
58
|
+
- **Quantity Skew Partitioning**: Volume heterogeneity where client dataset sizes vary according to a Dirichlet distribution ($\text{Dir}(\beta)$).
|
|
59
|
+
- **Hybrid Partitioning (Experimental)**: Sequential joint label skew and quantity skew allocation combining Dirichlet $\alpha$ and $\beta$.
|
|
60
|
+
- **FederatedDataset Wrapper**: High-level wrapper supporting built-in computer vision datasets (`mnist`, `cifar10`, `cifar100`, `fashion_mnist`) as well as custom `(X, y)` tuples and PyTorch `Dataset` instances.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
### From Source
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/khastkhodaei/fl-data-generator.git
|
|
70
|
+
cd fl-data-generator
|
|
71
|
+
pip install -e .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### With Optional Extras
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Development dependencies (pytest, hypothesis, ruff, etc.)
|
|
78
|
+
pip install -e ".[dev]"
|
|
79
|
+
|
|
80
|
+
# Visualization support (matplotlib)
|
|
81
|
+
pip install -e ".[viz]"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Runnable Examples
|
|
87
|
+
|
|
88
|
+
### 1. IID Partitioning
|
|
89
|
+
|
|
90
|
+
Uniformly partitions sample indices across clients.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import numpy as np
|
|
94
|
+
from fl_data_generator import iid_partition
|
|
95
|
+
|
|
96
|
+
labels = np.array([0, 1] * 50) # 100 samples
|
|
97
|
+
client_dict = iid_partition(labels, num_clients=4, seed=42)
|
|
98
|
+
|
|
99
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
100
|
+
# Real Output: Client sample counts: [25, 25, 25, 25]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Dirichlet Label Skew Partitioning
|
|
104
|
+
|
|
105
|
+
Simulates non-IID label skew parameterized by concentration parameter `alpha`. Smaller $\alpha$ generates severe distribution skew, while larger $\alpha$ approaches uniform class distributions.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import numpy as np
|
|
109
|
+
from fl_data_generator import dirichlet_partition
|
|
110
|
+
|
|
111
|
+
labels = np.repeat(np.arange(4), 50) # 200 samples across 4 classes
|
|
112
|
+
client_dict = dirichlet_partition(labels, num_clients=3, alpha=0.5, seed=42)
|
|
113
|
+
|
|
114
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
115
|
+
# Real Output: Client sample counts: [114, 42, 44]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. Pathological Non-IID Partitioning ($k$ Classes per Client)
|
|
119
|
+
|
|
120
|
+
Partitions data such that each client receives samples from exactly $k$ distinct classes (NIID-Bench #C=k scheme).
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import numpy as np
|
|
124
|
+
from fl_data_generator import pathological_partition
|
|
125
|
+
|
|
126
|
+
labels = np.repeat(np.arange(4), 50) # 200 samples across 4 classes
|
|
127
|
+
client_dict = pathological_partition(labels, num_clients=4, classes_per_client=2, seed=42)
|
|
128
|
+
|
|
129
|
+
client_0_labels = labels[client_dict[0]]
|
|
130
|
+
print("Client 0 distinct classes:", np.unique(client_0_labels).tolist())
|
|
131
|
+
# Real Output: Client 0 distinct classes: [1, 2]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 4. Quantity Skew Partitioning (Volume Heterogeneity)
|
|
135
|
+
|
|
136
|
+
Allocates varying numbers of samples to clients according to a Dirichlet distribution parameterized by concentration parameter `beta`.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import numpy as np
|
|
140
|
+
from fl_data_generator import quantity_skew_partition
|
|
141
|
+
|
|
142
|
+
labels = np.arange(200) # 200 samples
|
|
143
|
+
client_dict = quantity_skew_partition(labels, num_clients=4, beta=0.5, seed=42)
|
|
144
|
+
|
|
145
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
146
|
+
# Real Output: Client sample counts: [52, 72, 16, 60]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 5. Hybrid Partitioning (Experimental)
|
|
150
|
+
|
|
151
|
+
Combines quantity skew ($\beta$) and label skew ($\alpha$) via sequential allocation.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
import numpy as np
|
|
155
|
+
from fl_data_generator import hybrid_partition
|
|
156
|
+
|
|
157
|
+
labels = np.repeat(np.arange(4), 50) # 200 samples across 4 classes
|
|
158
|
+
client_dict = hybrid_partition(labels, num_clients=4, alpha=0.5, beta=0.5, seed=42)
|
|
159
|
+
|
|
160
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
161
|
+
# Real Output: Client sample counts: [56, 89, 2, 53]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 6. FederatedDataset Interface
|
|
165
|
+
|
|
166
|
+
High-level interface for creating client-specific datasets and PyTorch DataLoaders.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
import numpy as np
|
|
170
|
+
from fl_data_generator import FederatedDataset
|
|
171
|
+
|
|
172
|
+
X = np.random.default_rng(42).standard_normal((100, 5))
|
|
173
|
+
y = np.array([0, 1] * 50)
|
|
174
|
+
|
|
175
|
+
fed_data = FederatedDataset((X, y), num_clients=4, partition="iid", seed=42)
|
|
176
|
+
client_0_ds = fed_data.get_client_dataset(0)
|
|
177
|
+
|
|
178
|
+
print(f"Client 0 sample count: {len(client_0_ds)}")
|
|
179
|
+
features, label = client_0_ds[0]
|
|
180
|
+
print(f"Feature shape: {features.shape}, Label: {label}")
|
|
181
|
+
# Real Output:
|
|
182
|
+
# Client 0 sample count: 25
|
|
183
|
+
# Feature shape: (5,), Label: 1
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Roadmap
|
|
189
|
+
|
|
190
|
+
The following experimental features and partition strategies are planned for future releases:
|
|
191
|
+
- **Feature Skew (Covariate Shift)**: Partitioning based on continuous feature space clusters or synthetic domain perturbations.
|
|
192
|
+
- **Power-Law Quantity Skew**: Heavy-tailed sample volume distribution across clients.
|
|
193
|
+
- **Asymmetric Class Topologies**: Custom directed class sharing graphs.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Project Structure
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
fl-data-generator/
|
|
201
|
+
├── .github/
|
|
202
|
+
│ └── workflows/
|
|
203
|
+
│ └── ci.yml # GitHub Actions CI matrix workflow
|
|
204
|
+
├── src/
|
|
205
|
+
│ └── fl_data_generator/ # Core library package
|
|
206
|
+
│ ├── __init__.py # Public API exports
|
|
207
|
+
│ ├── _validation.py # Input validation utilities
|
|
208
|
+
│ ├── dataset.py # FederatedDataset interface & torchvision loaders
|
|
209
|
+
│ ├── dirichlet.py # Dirichlet label skew partitioner
|
|
210
|
+
│ ├── hybrid.py # Hybrid label & quantity skew partitioner
|
|
211
|
+
│ ├── iid.py # Uniform IID partitioner
|
|
212
|
+
│ ├── pathological.py # Pathological k-class partitioner
|
|
213
|
+
│ └── quantity.py # Quantity skew partitioner
|
|
214
|
+
├── tests/ # Unit, integration, and property test suite
|
|
215
|
+
│ ├── test_dataset.py
|
|
216
|
+
│ ├── test_dirichlet.py
|
|
217
|
+
│ ├── test_hybrid.py
|
|
218
|
+
│ ├── test_iid.py
|
|
219
|
+
│ ├── test_pathological.py
|
|
220
|
+
│ ├── test_properties.py
|
|
221
|
+
│ ├── test_quantity.py
|
|
222
|
+
│ ├── test_readme.py
|
|
223
|
+
│ └── test_validation.py
|
|
224
|
+
├── pyproject.toml # Package build configuration & Ruff settings
|
|
225
|
+
├── LICENSE # MIT License
|
|
226
|
+
└── README.md # Project documentation
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# fl-data-generator
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
|
|
6
|
+
A lightweight, framework-agnostic Python library for **IID** and **Non-IID** data partitioning in **Federated Learning (FL)**.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
In Federated Learning research and benchmarks, simulating realistic data heterogeneity across distributed clients is essential. **fl-data-generator** simplifies partitioning arbitrary datasets into realistic client distributions without tying you to any specific deep learning framework.
|
|
13
|
+
|
|
14
|
+
Whether you are using PyTorch, TensorFlow, JAX, or federated frameworks like Flower, FedML, or PySyft, `fl-data-generator` provides clean sample indices and dataset wrappers for your clients.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **IID Partitioning**: Uniform random sample allocation across all clients.
|
|
21
|
+
- **Dirichlet Partitioning**: Continuous label distribution skew ($\text{Dir}(\alpha)$) over class proportions, with client minimum size constraints (`min_size`).
|
|
22
|
+
- **Pathological Non-IID Partitioning**: Allocation of a fixed number of classes ($k$ classes per client, following the NIID-Bench benchmark scheme).
|
|
23
|
+
- **Quantity Skew Partitioning**: Volume heterogeneity where client dataset sizes vary according to a Dirichlet distribution ($\text{Dir}(\beta)$).
|
|
24
|
+
- **Hybrid Partitioning (Experimental)**: Sequential joint label skew and quantity skew allocation combining Dirichlet $\alpha$ and $\beta$.
|
|
25
|
+
- **FederatedDataset Wrapper**: High-level wrapper supporting built-in computer vision datasets (`mnist`, `cifar10`, `cifar100`, `fashion_mnist`) as well as custom `(X, y)` tuples and PyTorch `Dataset` instances.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
### From Source
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/khastkhodaei/fl-data-generator.git
|
|
35
|
+
cd fl-data-generator
|
|
36
|
+
pip install -e .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### With Optional Extras
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Development dependencies (pytest, hypothesis, ruff, etc.)
|
|
43
|
+
pip install -e ".[dev]"
|
|
44
|
+
|
|
45
|
+
# Visualization support (matplotlib)
|
|
46
|
+
pip install -e ".[viz]"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Runnable Examples
|
|
52
|
+
|
|
53
|
+
### 1. IID Partitioning
|
|
54
|
+
|
|
55
|
+
Uniformly partitions sample indices across clients.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import numpy as np
|
|
59
|
+
from fl_data_generator import iid_partition
|
|
60
|
+
|
|
61
|
+
labels = np.array([0, 1] * 50) # 100 samples
|
|
62
|
+
client_dict = iid_partition(labels, num_clients=4, seed=42)
|
|
63
|
+
|
|
64
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
65
|
+
# Real Output: Client sample counts: [25, 25, 25, 25]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Dirichlet Label Skew Partitioning
|
|
69
|
+
|
|
70
|
+
Simulates non-IID label skew parameterized by concentration parameter `alpha`. Smaller $\alpha$ generates severe distribution skew, while larger $\alpha$ approaches uniform class distributions.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import numpy as np
|
|
74
|
+
from fl_data_generator import dirichlet_partition
|
|
75
|
+
|
|
76
|
+
labels = np.repeat(np.arange(4), 50) # 200 samples across 4 classes
|
|
77
|
+
client_dict = dirichlet_partition(labels, num_clients=3, alpha=0.5, seed=42)
|
|
78
|
+
|
|
79
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
80
|
+
# Real Output: Client sample counts: [114, 42, 44]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Pathological Non-IID Partitioning ($k$ Classes per Client)
|
|
84
|
+
|
|
85
|
+
Partitions data such that each client receives samples from exactly $k$ distinct classes (NIID-Bench #C=k scheme).
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import numpy as np
|
|
89
|
+
from fl_data_generator import pathological_partition
|
|
90
|
+
|
|
91
|
+
labels = np.repeat(np.arange(4), 50) # 200 samples across 4 classes
|
|
92
|
+
client_dict = pathological_partition(labels, num_clients=4, classes_per_client=2, seed=42)
|
|
93
|
+
|
|
94
|
+
client_0_labels = labels[client_dict[0]]
|
|
95
|
+
print("Client 0 distinct classes:", np.unique(client_0_labels).tolist())
|
|
96
|
+
# Real Output: Client 0 distinct classes: [1, 2]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 4. Quantity Skew Partitioning (Volume Heterogeneity)
|
|
100
|
+
|
|
101
|
+
Allocates varying numbers of samples to clients according to a Dirichlet distribution parameterized by concentration parameter `beta`.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import numpy as np
|
|
105
|
+
from fl_data_generator import quantity_skew_partition
|
|
106
|
+
|
|
107
|
+
labels = np.arange(200) # 200 samples
|
|
108
|
+
client_dict = quantity_skew_partition(labels, num_clients=4, beta=0.5, seed=42)
|
|
109
|
+
|
|
110
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
111
|
+
# Real Output: Client sample counts: [52, 72, 16, 60]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 5. Hybrid Partitioning (Experimental)
|
|
115
|
+
|
|
116
|
+
Combines quantity skew ($\beta$) and label skew ($\alpha$) via sequential allocation.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import numpy as np
|
|
120
|
+
from fl_data_generator import hybrid_partition
|
|
121
|
+
|
|
122
|
+
labels = np.repeat(np.arange(4), 50) # 200 samples across 4 classes
|
|
123
|
+
client_dict = hybrid_partition(labels, num_clients=4, alpha=0.5, beta=0.5, seed=42)
|
|
124
|
+
|
|
125
|
+
print("Client sample counts:", [len(indices) for indices in client_dict.values()])
|
|
126
|
+
# Real Output: Client sample counts: [56, 89, 2, 53]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 6. FederatedDataset Interface
|
|
130
|
+
|
|
131
|
+
High-level interface for creating client-specific datasets and PyTorch DataLoaders.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
import numpy as np
|
|
135
|
+
from fl_data_generator import FederatedDataset
|
|
136
|
+
|
|
137
|
+
X = np.random.default_rng(42).standard_normal((100, 5))
|
|
138
|
+
y = np.array([0, 1] * 50)
|
|
139
|
+
|
|
140
|
+
fed_data = FederatedDataset((X, y), num_clients=4, partition="iid", seed=42)
|
|
141
|
+
client_0_ds = fed_data.get_client_dataset(0)
|
|
142
|
+
|
|
143
|
+
print(f"Client 0 sample count: {len(client_0_ds)}")
|
|
144
|
+
features, label = client_0_ds[0]
|
|
145
|
+
print(f"Feature shape: {features.shape}, Label: {label}")
|
|
146
|
+
# Real Output:
|
|
147
|
+
# Client 0 sample count: 25
|
|
148
|
+
# Feature shape: (5,), Label: 1
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Roadmap
|
|
154
|
+
|
|
155
|
+
The following experimental features and partition strategies are planned for future releases:
|
|
156
|
+
- **Feature Skew (Covariate Shift)**: Partitioning based on continuous feature space clusters or synthetic domain perturbations.
|
|
157
|
+
- **Power-Law Quantity Skew**: Heavy-tailed sample volume distribution across clients.
|
|
158
|
+
- **Asymmetric Class Topologies**: Custom directed class sharing graphs.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Project Structure
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
fl-data-generator/
|
|
166
|
+
├── .github/
|
|
167
|
+
│ └── workflows/
|
|
168
|
+
│ └── ci.yml # GitHub Actions CI matrix workflow
|
|
169
|
+
├── src/
|
|
170
|
+
│ └── fl_data_generator/ # Core library package
|
|
171
|
+
│ ├── __init__.py # Public API exports
|
|
172
|
+
│ ├── _validation.py # Input validation utilities
|
|
173
|
+
│ ├── dataset.py # FederatedDataset interface & torchvision loaders
|
|
174
|
+
│ ├── dirichlet.py # Dirichlet label skew partitioner
|
|
175
|
+
│ ├── hybrid.py # Hybrid label & quantity skew partitioner
|
|
176
|
+
│ ├── iid.py # Uniform IID partitioner
|
|
177
|
+
│ ├── pathological.py # Pathological k-class partitioner
|
|
178
|
+
│ └── quantity.py # Quantity skew partitioner
|
|
179
|
+
├── tests/ # Unit, integration, and property test suite
|
|
180
|
+
│ ├── test_dataset.py
|
|
181
|
+
│ ├── test_dirichlet.py
|
|
182
|
+
│ ├── test_hybrid.py
|
|
183
|
+
│ ├── test_iid.py
|
|
184
|
+
│ ├── test_pathological.py
|
|
185
|
+
│ ├── test_properties.py
|
|
186
|
+
│ ├── test_quantity.py
|
|
187
|
+
│ ├── test_readme.py
|
|
188
|
+
│ └── test_validation.py
|
|
189
|
+
├── pyproject.toml # Package build configuration & Ruff settings
|
|
190
|
+
├── LICENSE # MIT License
|
|
191
|
+
└── README.md # Project documentation
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fl-data-generator"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A lightweight, framework-agnostic library for IID and non-IID data partitioning in Federated Learning."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Mostafa Khastkhodaei", email = "dev.khastkhodaei@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"federated-learning",
|
|
17
|
+
"data-partitioning",
|
|
18
|
+
"non-iid",
|
|
19
|
+
"dirichlet",
|
|
20
|
+
"label-skew",
|
|
21
|
+
"quantity-skew",
|
|
22
|
+
"machine-learning"
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"Intended Audience :: Science/Research",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: 3.13",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
36
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
37
|
+
]
|
|
38
|
+
dependencies = [
|
|
39
|
+
"numpy>=1.20.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"hypothesis>=6.0.0",
|
|
45
|
+
"pytest>=7.0.0",
|
|
46
|
+
"pytest-cov>=4.0.0",
|
|
47
|
+
"ruff~=0.17.0",
|
|
48
|
+
]
|
|
49
|
+
viz = [
|
|
50
|
+
"matplotlib>=3.5.0",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
"Homepage" = "https://github.com/khastkhodaei/fl-data-generator"
|
|
55
|
+
"Bug Tracker" = "https://github.com/khastkhodaei/fl-data-generator/issues"
|
|
56
|
+
"Source Code" = "https://github.com/khastkhodaei/fl-data-generator"
|
|
57
|
+
|
|
58
|
+
[tool.setuptools]
|
|
59
|
+
package-dir = {"" = "src"}
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.packages.find]
|
|
62
|
+
where = ["src"]
|
|
63
|
+
include = ["fl_data_generator*"]
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
pythonpath = ["src"]
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 88
|
|
71
|
+
target-version = "py38"
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint]
|
|
74
|
+
select = [
|
|
75
|
+
"E4",
|
|
76
|
+
"E7",
|
|
77
|
+
"E9",
|
|
78
|
+
"F",
|
|
79
|
+
]
|
|
80
|
+
ignore = [
|
|
81
|
+
"FA100",
|
|
82
|
+
]
|
|
83
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .dataset import FederatedDataset, NumpyDataset
|
|
2
|
+
from .dirichlet import dirichlet_partition
|
|
3
|
+
from .hybrid import hybrid_partition
|
|
4
|
+
from .iid import iid_partition
|
|
5
|
+
from .pathological import pathological_partition
|
|
6
|
+
from .quantity import quantity_skew_partition
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"FederatedDataset",
|
|
10
|
+
"NumpyDataset",
|
|
11
|
+
"dirichlet_partition",
|
|
12
|
+
"hybrid_partition",
|
|
13
|
+
"iid_partition",
|
|
14
|
+
"pathological_partition",
|
|
15
|
+
"quantity_skew_partition",
|
|
16
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate_labels(labels: Sequence | np.ndarray) -> np.ndarray:
|
|
9
|
+
"""Validate and cast dataset labels to a 1D int64 numpy array.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
labels : Sequence or np.ndarray
|
|
14
|
+
Dataset labels as an array-like or sequence.
|
|
15
|
+
|
|
16
|
+
Returns
|
|
17
|
+
-------
|
|
18
|
+
np.ndarray
|
|
19
|
+
A 1D numpy array with dtype np.int64.
|
|
20
|
+
|
|
21
|
+
Raises
|
|
22
|
+
------
|
|
23
|
+
ValueError
|
|
24
|
+
If labels is empty, not 1-dimensional, contains NaN or Inf, or
|
|
25
|
+
cannot be converted to integer values without loss of information.
|
|
26
|
+
TypeError
|
|
27
|
+
If labels elements cannot be converted to numeric values.
|
|
28
|
+
"""
|
|
29
|
+
arr = np.asarray(labels)
|
|
30
|
+
|
|
31
|
+
if arr.ndim != 1:
|
|
32
|
+
raise ValueError("labels must be 1-dimensional; consider using labels.ravel()")
|
|
33
|
+
|
|
34
|
+
if arr.size == 0:
|
|
35
|
+
raise ValueError("labels cannot be empty")
|
|
36
|
+
|
|
37
|
+
if not np.issubdtype(arr.dtype, np.number):
|
|
38
|
+
# Could be object array with strings or other non-numeric types
|
|
39
|
+
try:
|
|
40
|
+
arr = arr.astype(np.float64)
|
|
41
|
+
except (ValueError, TypeError) as exc:
|
|
42
|
+
raise TypeError("labels must contain numeric values") from exc
|
|
43
|
+
|
|
44
|
+
# Check for NaN or Inf
|
|
45
|
+
if np.isnan(arr).any():
|
|
46
|
+
raise ValueError("labels contains NaN values")
|
|
47
|
+
|
|
48
|
+
if np.isinf(arr).any():
|
|
49
|
+
raise ValueError("labels contains infinite values")
|
|
50
|
+
|
|
51
|
+
# Check if integer-valued
|
|
52
|
+
if np.issubdtype(arr.dtype, np.floating):
|
|
53
|
+
if not np.all(np.floor(arr) == arr):
|
|
54
|
+
raise ValueError("labels must be integer-valued")
|
|
55
|
+
arr = arr.astype(np.int64)
|
|
56
|
+
elif np.issubdtype(arr.dtype, np.integer):
|
|
57
|
+
if arr.dtype != np.int64:
|
|
58
|
+
arr = arr.astype(np.int64)
|
|
59
|
+
|
|
60
|
+
return arr
|