fair-kmeans 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.
- fair_kmeans-0.1.0/LICENSE +21 -0
- fair_kmeans-0.1.0/PKG-INFO +139 -0
- fair_kmeans-0.1.0/README.md +120 -0
- fair_kmeans-0.1.0/build_extension.py +44 -0
- fair_kmeans-0.1.0/fair_kmeans/__init__.py +3 -0
- fair_kmeans-0.1.0/fair_kmeans/_core.cpp +218 -0
- fair_kmeans-0.1.0/fair_kmeans/core.py +312 -0
- fair_kmeans-0.1.0/fair_kmeans/fair_clustering_tools.cpp +440 -0
- fair_kmeans-0.1.0/fair_kmeans/fair_clustering_tools.h +65 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/LICENSE +32 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/assert.h +214 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bin_heap.h +347 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/alteration_notifier.h +472 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/array_map.h +351 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/default_map.h +182 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/enable_if.h +131 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/graph_extender.h +1413 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/lock.h +65 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/map_extender.h +332 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/stl_iterators.h +99 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/traits.h +388 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/bits/vector_map.h +244 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/capacity_scaling.h +1015 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/concept_check.h +77 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/concepts/maps.h +225 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/config.h +26 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/core.h +2505 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/error.h +276 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/maps.h +4110 -0
- fair_kmeans-0.1.0/fair_kmeans/lemon/static_graph.h +485 -0
- fair_kmeans-0.1.0/fair_kmeans/point.cpp +143 -0
- fair_kmeans-0.1.0/fair_kmeans/point.h +40 -0
- fair_kmeans-0.1.0/fair_kmeans/util.py +44 -0
- fair_kmeans-0.1.0/pyproject.toml +42 -0
- fair_kmeans-0.1.0/setup.py +31 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Algorithms and Data Structures Group of the Heinrich Heine University Düsseldorf
|
|
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,139 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fair-kmeans
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fair K-Means produces a fair clustering assignment according to the fairness definition of Chierichetti et al. Each point has a binary color, and the goal is to assign the points to clusters such that the number of points with different colors in each cluster is the same and the cost of the clusters is minimized.
|
|
5
|
+
License: LICENSE
|
|
6
|
+
Author: Melanie Schmidt
|
|
7
|
+
Author-email: mschmidt@hhu.de
|
|
8
|
+
Requires-Python: >=3.9,<4.0
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: numpy (>=1.26.4,<2.0.0)
|
|
16
|
+
Requires-Dist: scikit-learn (>=1.4.2,<2.0.0)
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
[](https://github.com/algo-hhu/fair-kmeans/actions)
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
[](https://www.python.org/downloads/release/python-390/)
|
|
22
|
+
[](https://pypi.org/project/fair-kmeans/)
|
|
23
|
+
|
|
24
|
+
# Fair K-Means
|
|
25
|
+
|
|
26
|
+
Fair K-Means produces a fair clustering assignment according to the fairness definition of Chierichetti et al. [1]. Each point has a binary color assigned to it. The goal is to assign the points to clusters such that the number of points with different colors in each cluster is the same. The algorithm also works with weights, so each point can participate with a different weight in the coloring.
|
|
27
|
+
|
|
28
|
+
The algorithm works as follows, assuming that the binary colors are red and blue:
|
|
29
|
+
1. A matching between the red and blue points is computed such that the cost (the point distances) of the matching is minimized.
|
|
30
|
+
2. The mean of each matched pair is computed.
|
|
31
|
+
3. A K-Means++ clustering of all the means is computed, and the point pairs are assigned to the clusters of their means.
|
|
32
|
+
|
|
33
|
+
The matching between the red and blue points is computed using the [Lemon C++ Library](https://lemon.cs.elte.hu/trac/lemon). The library is included in the package and does not need to be installed separately. Only the needed files were included, and a complete version of the library can be found [here](https://lemon.cs.elte.hu/trac/lemon). A copyright notice is included [here](fair_kmeans/lemon/LICENSE).
|
|
34
|
+
|
|
35
|
+
### References
|
|
36
|
+
|
|
37
|
+
[1] Flavio Chierichetti, Ravi Kumar, Silvio Lattanzi, and Sergei Vassilvitskii, Fair clustering through fairlets, Proceedings of the 30th Annual Conference on Neural Information Processing Systems (NIPS), 2017, pp. 5036–5044.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install fair-kmeans
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Example
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from fair_kmeans import FairKMeans
|
|
49
|
+
|
|
50
|
+
example_data = [
|
|
51
|
+
[1.0, 1.0, 1.0],
|
|
52
|
+
[1.1, 1.1, 1.1],
|
|
53
|
+
[1.2, 1.2, 1.2],
|
|
54
|
+
[2.0, 2.0, 2.0],
|
|
55
|
+
[2.1, 2.1, 2.1],
|
|
56
|
+
[2.2, 2.2, 2.2],
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
example_colors = [1, 1, 1, 0, 0, 0]
|
|
60
|
+
|
|
61
|
+
km = FairKMeans(n_clusters=2, random_state=0)
|
|
62
|
+
km.fit(example_data, color=example_colors)
|
|
63
|
+
labels = km.labels_
|
|
64
|
+
centers = km.cluster_centers_
|
|
65
|
+
|
|
66
|
+
print(labels) # [1, 0, 0, 1, 0, 0]
|
|
67
|
+
print(centers) # [[1.65, 1.65, 1.65], [1.5, 1.5, 1.5]]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Example with Weights
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from fair_kmeans import FairKMeans
|
|
74
|
+
|
|
75
|
+
example_data = [
|
|
76
|
+
[1.0, 1.0, 1.0],
|
|
77
|
+
[1.1, 1.1, 1.1],
|
|
78
|
+
[1.2, 1.2, 1.2],
|
|
79
|
+
[2.0, 2.0, 2.0],
|
|
80
|
+
[2.1, 2.1, 2.1],
|
|
81
|
+
[2.2, 2.2, 2.2],
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
example_colors = [1, 1, 1, 0, 0, 0]
|
|
85
|
+
example_weights = [2, 2, 1, 1, 1, 3]
|
|
86
|
+
|
|
87
|
+
km = FairKMeans(n_clusters=2, random_state=0)
|
|
88
|
+
km.fit(example_data, color=example_colors, sample_weight=example_weights)
|
|
89
|
+
labels = km.labels_
|
|
90
|
+
centers = km.cluster_centers_
|
|
91
|
+
|
|
92
|
+
print(labels) # [1, 1, 0, 1, 1, 0]
|
|
93
|
+
print(centers) # [[0.85, 0.85, 0.85], [1.28, 1.28, 1.28]]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
Install [poetry](https://python-poetry.org/docs/#installation)
|
|
99
|
+
```bash
|
|
100
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Install clang
|
|
104
|
+
```bash
|
|
105
|
+
sudo apt-get install clang
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Set clang variables
|
|
109
|
+
```bash
|
|
110
|
+
export CXX=/usr/bin/clang++
|
|
111
|
+
export CC=/usr/bin/clang
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Install the package
|
|
115
|
+
```bash
|
|
116
|
+
poetry install
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If the installation does not work and you do not see the C++ output, you can build the package to see the stack trace
|
|
120
|
+
```bash
|
|
121
|
+
poetry build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Run the tests
|
|
125
|
+
```bash
|
|
126
|
+
poetry run python -m unittest discover tests -v
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Citation
|
|
130
|
+
|
|
131
|
+
If you use this code, please cite [the following paper](https://arxiv.org/abs/2406.02739v1):
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Melanie Schmidt, Chris Schwiegelshohn and Christian Sohler.
|
|
135
|
+
"Fair Coresets and Streaming Algorithms for Fair k-Means Clustering" (2020).
|
|
136
|
+
Approximation and Online Algorithms. WAOA 2019.
|
|
137
|
+
Lecture Notes in Computer Science, vol 11926. Springer.
|
|
138
|
+
```
|
|
139
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
[](https://github.com/algo-hhu/fair-kmeans/actions)
|
|
2
|
+
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
[](https://www.python.org/downloads/release/python-390/)
|
|
4
|
+
[](https://pypi.org/project/fair-kmeans/)
|
|
5
|
+
|
|
6
|
+
# Fair K-Means
|
|
7
|
+
|
|
8
|
+
Fair K-Means produces a fair clustering assignment according to the fairness definition of Chierichetti et al. [1]. Each point has a binary color assigned to it. The goal is to assign the points to clusters such that the number of points with different colors in each cluster is the same. The algorithm also works with weights, so each point can participate with a different weight in the coloring.
|
|
9
|
+
|
|
10
|
+
The algorithm works as follows, assuming that the binary colors are red and blue:
|
|
11
|
+
1. A matching between the red and blue points is computed such that the cost (the point distances) of the matching is minimized.
|
|
12
|
+
2. The mean of each matched pair is computed.
|
|
13
|
+
3. A K-Means++ clustering of all the means is computed, and the point pairs are assigned to the clusters of their means.
|
|
14
|
+
|
|
15
|
+
The matching between the red and blue points is computed using the [Lemon C++ Library](https://lemon.cs.elte.hu/trac/lemon). The library is included in the package and does not need to be installed separately. Only the needed files were included, and a complete version of the library can be found [here](https://lemon.cs.elte.hu/trac/lemon). A copyright notice is included [here](fair_kmeans/lemon/LICENSE).
|
|
16
|
+
|
|
17
|
+
### References
|
|
18
|
+
|
|
19
|
+
[1] Flavio Chierichetti, Ravi Kumar, Silvio Lattanzi, and Sergei Vassilvitskii, Fair clustering through fairlets, Proceedings of the 30th Annual Conference on Neural Information Processing Systems (NIPS), 2017, pp. 5036–5044.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install fair-kmeans
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from fair_kmeans import FairKMeans
|
|
31
|
+
|
|
32
|
+
example_data = [
|
|
33
|
+
[1.0, 1.0, 1.0],
|
|
34
|
+
[1.1, 1.1, 1.1],
|
|
35
|
+
[1.2, 1.2, 1.2],
|
|
36
|
+
[2.0, 2.0, 2.0],
|
|
37
|
+
[2.1, 2.1, 2.1],
|
|
38
|
+
[2.2, 2.2, 2.2],
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
example_colors = [1, 1, 1, 0, 0, 0]
|
|
42
|
+
|
|
43
|
+
km = FairKMeans(n_clusters=2, random_state=0)
|
|
44
|
+
km.fit(example_data, color=example_colors)
|
|
45
|
+
labels = km.labels_
|
|
46
|
+
centers = km.cluster_centers_
|
|
47
|
+
|
|
48
|
+
print(labels) # [1, 0, 0, 1, 0, 0]
|
|
49
|
+
print(centers) # [[1.65, 1.65, 1.65], [1.5, 1.5, 1.5]]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Example with Weights
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from fair_kmeans import FairKMeans
|
|
56
|
+
|
|
57
|
+
example_data = [
|
|
58
|
+
[1.0, 1.0, 1.0],
|
|
59
|
+
[1.1, 1.1, 1.1],
|
|
60
|
+
[1.2, 1.2, 1.2],
|
|
61
|
+
[2.0, 2.0, 2.0],
|
|
62
|
+
[2.1, 2.1, 2.1],
|
|
63
|
+
[2.2, 2.2, 2.2],
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
example_colors = [1, 1, 1, 0, 0, 0]
|
|
67
|
+
example_weights = [2, 2, 1, 1, 1, 3]
|
|
68
|
+
|
|
69
|
+
km = FairKMeans(n_clusters=2, random_state=0)
|
|
70
|
+
km.fit(example_data, color=example_colors, sample_weight=example_weights)
|
|
71
|
+
labels = km.labels_
|
|
72
|
+
centers = km.cluster_centers_
|
|
73
|
+
|
|
74
|
+
print(labels) # [1, 1, 0, 1, 1, 0]
|
|
75
|
+
print(centers) # [[0.85, 0.85, 0.85], [1.28, 1.28, 1.28]]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
Install [poetry](https://python-poetry.org/docs/#installation)
|
|
81
|
+
```bash
|
|
82
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Install clang
|
|
86
|
+
```bash
|
|
87
|
+
sudo apt-get install clang
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Set clang variables
|
|
91
|
+
```bash
|
|
92
|
+
export CXX=/usr/bin/clang++
|
|
93
|
+
export CC=/usr/bin/clang
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Install the package
|
|
97
|
+
```bash
|
|
98
|
+
poetry install
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If the installation does not work and you do not see the C++ output, you can build the package to see the stack trace
|
|
102
|
+
```bash
|
|
103
|
+
poetry build
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Run the tests
|
|
107
|
+
```bash
|
|
108
|
+
poetry run python -m unittest discover tests -v
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Citation
|
|
112
|
+
|
|
113
|
+
If you use this code, please cite [the following paper](https://arxiv.org/abs/2406.02739v1):
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
Melanie Schmidt, Chris Schwiegelshohn and Christian Sohler.
|
|
117
|
+
"Fair Coresets and Streaming Algorithms for Fair k-Means Clustering" (2020).
|
|
118
|
+
Approximation and Online Algorithms. WAOA 2019.
|
|
119
|
+
Lecture Notes in Computer Science, vol 11926. Springer.
|
|
120
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from setuptools import Extension
|
|
4
|
+
from setuptools.command.build_ext import build_ext
|
|
5
|
+
from setuptools.errors import CompileError
|
|
6
|
+
|
|
7
|
+
extension = Extension(
|
|
8
|
+
name="fair_kmeans._core",
|
|
9
|
+
sources=[
|
|
10
|
+
"fair_kmeans/_core.cpp",
|
|
11
|
+
"fair_kmeans/fair_clustering_tools.cpp",
|
|
12
|
+
"fair_kmeans/point.cpp",
|
|
13
|
+
],
|
|
14
|
+
include_dirs=["fair_kmeans"],
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Thank you https://github.com/dstein64/kmeans1d!
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BuildExt(build_ext):
|
|
21
|
+
"""A custom build extension for adding -stdlib arguments for clang++."""
|
|
22
|
+
|
|
23
|
+
def build_extensions(self) -> None:
|
|
24
|
+
# '-std=c++11' is added to `extra_compile_args` so the code can compile
|
|
25
|
+
# with clang++. This works across compilers (ignored by MSVC).
|
|
26
|
+
for extension in self.extensions:
|
|
27
|
+
extension.extra_compile_args.append("-std=c++11")
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
build_ext.build_extensions(self)
|
|
31
|
+
except CompileError:
|
|
32
|
+
# Workaround Issue #2.
|
|
33
|
+
# '-stdlib=libc++' is added to `extra_compile_args` and `extra_link_args`
|
|
34
|
+
# so the code can compile on macOS with Anaconda.
|
|
35
|
+
for extension in self.extensions:
|
|
36
|
+
extension.extra_compile_args.append("-stdlib=libc++")
|
|
37
|
+
extension.extra_link_args.append("-stdlib=libc++")
|
|
38
|
+
build_ext.build_extensions(self)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build(setup_kwargs: Dict[str, Any]) -> None:
|
|
42
|
+
setup_kwargs.update(
|
|
43
|
+
{"ext_modules": [extension], "cmdclass": {"build_ext": BuildExt}}
|
|
44
|
+
)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#include <Python.h>
|
|
2
|
+
#include <iostream>
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
|
|
5
|
+
#include "fair_clustering_tools.h"
|
|
6
|
+
|
|
7
|
+
typedef unsigned int uint;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
bool checkWeightValidity(std::vector<int> &weightsum)
|
|
12
|
+
{
|
|
13
|
+
int first_sum = weightsum[0];
|
|
14
|
+
|
|
15
|
+
for (size_t i = 1; i < weightsum.size(); ++i)
|
|
16
|
+
{
|
|
17
|
+
if (weightsum[i] != first_sum)
|
|
18
|
+
{
|
|
19
|
+
std::cout << "Total weight sum of color 0 is " << first_sum << ", but weight sum of color " << i << " is " << weightsum[1] << ". All weights must be the same." << std::endl;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Splits points into weight, color and coordinates.
|
|
27
|
+
// Processes the data into a n-color-dim array points,
|
|
28
|
+
// where each index is one color.
|
|
29
|
+
// Number and weightsum give the number
|
|
30
|
+
// and total weight of points in either array.
|
|
31
|
+
void postprocessData(double *array, double *sampleWeights, int *colors, uint n, uint d, std::vector<std::vector<Point>> &points, std::vector<int> &number, std::vector<int> &weightsum)
|
|
32
|
+
{
|
|
33
|
+
int j = 0;
|
|
34
|
+
for (uint i = 0; i < n * d; i += d)
|
|
35
|
+
{
|
|
36
|
+
double w = sampleWeights[j];
|
|
37
|
+
int c = colors[j];
|
|
38
|
+
number[c] += 1;
|
|
39
|
+
weightsum[c] += w;
|
|
40
|
+
std::vector<double> coords(&array[i], &array[i] + d);
|
|
41
|
+
// Add j as the position of the point in the array
|
|
42
|
+
points[c].push_back(Point(w, c, j, coords));
|
|
43
|
+
j++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Convert the array of centers to a vector of points
|
|
48
|
+
// Postprocess the center data into a vector of Point objects
|
|
49
|
+
// centers have weight 1 and no color (-1).
|
|
50
|
+
void postprocessCenters(double *centers, std::vector<Point> ¢ersVector, uint k, uint d)
|
|
51
|
+
{
|
|
52
|
+
// Create center vector with points objects by copying from centerdata
|
|
53
|
+
int j = 0;
|
|
54
|
+
for (uint i = 0; i < k * d; i += d)
|
|
55
|
+
{
|
|
56
|
+
std::vector<double> coords(¢ers[i], ¢ers[i] + d);
|
|
57
|
+
// Centers have weight 1 and no color (-1)
|
|
58
|
+
centersVector[j] = Point(1, -1, coords);
|
|
59
|
+
j++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Copy the centers back to the centers array to return to Python
|
|
64
|
+
void copyBackCenters(double *centers, std::vector<Point> ¢ersVector)
|
|
65
|
+
{
|
|
66
|
+
for (size_t i = 0; i < centersVector.size(); ++i)
|
|
67
|
+
{
|
|
68
|
+
size_t d = centersVector[i].getDimension();
|
|
69
|
+
for (size_t j = 0; j < d; ++j)
|
|
70
|
+
{
|
|
71
|
+
centers[i * d + j] = centersVector[i].getCoordinatePointer()[j];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void assignLabels(std::vector<std::vector<Point>> &points, int *labels)
|
|
77
|
+
{
|
|
78
|
+
for (size_t c = 0; c < points.size(); ++c)
|
|
79
|
+
{
|
|
80
|
+
for (size_t j = 0; j < points[c].size(); ++j)
|
|
81
|
+
{
|
|
82
|
+
labels[points[c][j].getPosition()] = points[c][j].getLabel();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
// Thank you https://github.com/dstein64/kmeans1d!
|
|
89
|
+
|
|
90
|
+
extern "C"
|
|
91
|
+
{
|
|
92
|
+
// "__declspec(dllexport)" causes the function to be exported when compiling on Windows.
|
|
93
|
+
// Otherwise, the function is not exported and the code raises
|
|
94
|
+
// "AttributeError: function 'cluster' not found".
|
|
95
|
+
// Exporting is a Windows platform requirement, not just a Visual Studio requirement
|
|
96
|
+
// (https://stackoverflow.com/a/22288874/1509433). The _WIN32 macro covers the Visual
|
|
97
|
+
// Studio compiler (MSVC) and MinGW. The __CYGWIN__ macro covers gcc and clang under
|
|
98
|
+
// Cygwin.
|
|
99
|
+
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
100
|
+
__declspec(dllexport)
|
|
101
|
+
#endif
|
|
102
|
+
double
|
|
103
|
+
fairKMeans(double *array,
|
|
104
|
+
double *sampleWeights,
|
|
105
|
+
int *colors,
|
|
106
|
+
uint n,
|
|
107
|
+
uint d,
|
|
108
|
+
uint k,
|
|
109
|
+
uint nc,
|
|
110
|
+
uint maxIterations,
|
|
111
|
+
double tolerance,
|
|
112
|
+
std::uint64_t seed,
|
|
113
|
+
int *labels,
|
|
114
|
+
double *centers,
|
|
115
|
+
bool updateCenters,
|
|
116
|
+
uint *iterations)
|
|
117
|
+
{
|
|
118
|
+
|
|
119
|
+
std::vector<int> weightsum(nc, 0);
|
|
120
|
+
std::vector<int> number(nc, 0);
|
|
121
|
+
// One vector of points per color
|
|
122
|
+
std::vector<std::vector<Point>> points(nc);
|
|
123
|
+
|
|
124
|
+
postprocessData(array, sampleWeights, colors, n, d, points, number, weightsum);
|
|
125
|
+
|
|
126
|
+
// std::cout << "Points:" << std::endl;
|
|
127
|
+
// for (size_t c = 0; c < nc; ++c)
|
|
128
|
+
// {
|
|
129
|
+
// for (size_t i = 0; i < points[c].size(); ++i)
|
|
130
|
+
// {
|
|
131
|
+
// std::cout << points[c][i].print() << std::endl;
|
|
132
|
+
// }
|
|
133
|
+
// }
|
|
134
|
+
|
|
135
|
+
std::vector<Point> centersVector(k);
|
|
136
|
+
postprocessCenters(centers, centersVector, k, d);
|
|
137
|
+
|
|
138
|
+
if (!checkWeightValidity(weightsum))
|
|
139
|
+
{
|
|
140
|
+
return -1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// std::cout << "Initial Centers:" << std::endl;
|
|
144
|
+
// for (size_t i = 0; i < centersVector.size(); ++i)
|
|
145
|
+
// {
|
|
146
|
+
// std::cout << centersVector[i].print() << std::endl;
|
|
147
|
+
// }
|
|
148
|
+
|
|
149
|
+
double cost = std::numeric_limits<double>::max();
|
|
150
|
+
double previousCost = cost;
|
|
151
|
+
bool change = true;
|
|
152
|
+
|
|
153
|
+
while (*iterations < maxIterations && change)
|
|
154
|
+
{
|
|
155
|
+
|
|
156
|
+
// Computing the fair assignment and better centers
|
|
157
|
+
// If we don't want to update the centers, then we shoudn't fill all clusters
|
|
158
|
+
CentersValueChange result = compute_fair_assignment(points, centersVector, number, weightsum, updateCenters, seed);
|
|
159
|
+
|
|
160
|
+
// If the returned cost is negative, the CapacityScaling algorithm failed
|
|
161
|
+
if (std::get<1>(result) < 0.0)
|
|
162
|
+
{
|
|
163
|
+
return std::get<1>(result);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
std::vector<Point> &newCenters = std::get<0>(result);
|
|
167
|
+
|
|
168
|
+
// std::cout << std::endl;
|
|
169
|
+
// std::cout << "New Centers:" << std::endl;
|
|
170
|
+
// for (size_t i = 0; i < newCenters.size(); ++i)
|
|
171
|
+
// {
|
|
172
|
+
// std::cout << "Center " << i << ": " << newCenters[i].print() << std::endl;
|
|
173
|
+
// }
|
|
174
|
+
|
|
175
|
+
centersVector = newCenters;
|
|
176
|
+
cost = std::get<1>(result);
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// std::cout << "Cost: " << cost << std::endl;
|
|
180
|
+
// std::cout << "Previous Cost: " << previousCost << std::endl;
|
|
181
|
+
|
|
182
|
+
++*iterations;
|
|
183
|
+
change = false;
|
|
184
|
+
|
|
185
|
+
// If we don't see any improvement anymore, we stop
|
|
186
|
+
if (cost < (1 - tolerance) * previousCost)
|
|
187
|
+
{
|
|
188
|
+
previousCost = cost;
|
|
189
|
+
change = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (cost >= 0)
|
|
194
|
+
{
|
|
195
|
+
if (updateCenters) copyBackCenters(centers, centersVector);
|
|
196
|
+
|
|
197
|
+
assignLabels(points, labels);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return cost;
|
|
201
|
+
}
|
|
202
|
+
} // extern "C"
|
|
203
|
+
|
|
204
|
+
static PyMethodDef module_methods[] = {
|
|
205
|
+
{NULL, NULL, 0, NULL}};
|
|
206
|
+
|
|
207
|
+
static struct PyModuleDef _coremodule = {
|
|
208
|
+
PyModuleDef_HEAD_INIT,
|
|
209
|
+
"fair_kmeans._core",
|
|
210
|
+
NULL,
|
|
211
|
+
-1,
|
|
212
|
+
module_methods,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
PyMODINIT_FUNC PyInit__core(void)
|
|
216
|
+
{
|
|
217
|
+
return PyModule_Create(&_coremodule);
|
|
218
|
+
}
|