certifi-learn 0.2.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.
- certifi_learn-0.2.0/.github/workflows/wheels.yml +81 -0
- certifi_learn-0.2.0/.gitignore +42 -0
- certifi_learn-0.2.0/Cargo.lock +228 -0
- certifi_learn-0.2.0/Cargo.toml +18 -0
- certifi_learn-0.2.0/LICENSE +21 -0
- certifi_learn-0.2.0/PKG-INFO +216 -0
- certifi_learn-0.2.0/README.md +185 -0
- certifi_learn-0.2.0/docs/subgroup_pruning_proof.tex +466 -0
- certifi_learn-0.2.0/examples/detect_and_train.py +53 -0
- certifi_learn-0.2.0/examples/folktables_loader.py +77 -0
- certifi_learn-0.2.0/pyproject.toml +34 -0
- certifi_learn-0.2.0/python/certifi_learn/__init__.py +23 -0
- certifi_learn-0.2.0/python/certifi_learn/_dnf.py +333 -0
- certifi_learn-0.2.0/python/certifi_learn/_exact_backend.py +206 -0
- certifi_learn-0.2.0/python/certifi_learn/_gurobi.py +10 -0
- certifi_learn-0.2.0/python/certifi_learn/_linear.py +356 -0
- certifi_learn-0.2.0/python/certifi_learn/_mio.py +410 -0
- certifi_learn-0.2.0/python/certifi_learn/classifiers.py +228 -0
- certifi_learn-0.2.0/python/certifi_learn/detection.py +190 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/__init__.py +57 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/binarizer.py +267 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/DataHandler.py +372 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/__init__.py +2 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/features/Binary.py +112 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/features/Categorical.py +163 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/features/Contiguous.py +107 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/features/Feature.py +105 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/features/Mixed.py +147 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/features/__init__.py +7 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/features/utils.py +68 -0
- certifi_learn-0.2.0/python/certifi_learn/encoding/data_handler/types.py +20 -0
- certifi_learn-0.2.0/python/certifi_learn/exact/__init__.py +11 -0
- certifi_learn-0.2.0/python/certifi_learn/exact/_api.py +347 -0
- certifi_learn-0.2.0/python/certifi_learn/exact/_common.py +323 -0
- certifi_learn-0.2.0/python/certifi_learn/measures.py +56 -0
- certifi_learn-0.2.0/src/lib.rs +524 -0
- certifi_learn-0.2.0/tests/test_classifiers.py +66 -0
- certifi_learn-0.2.0/tests/test_detection.py +95 -0
- certifi_learn-0.2.0/tests/test_exact_api.py +474 -0
- certifi_learn-0.2.0/tests/test_exact_correctness.py +341 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Builds wheels (one abi3 wheel per platform covers Python >= 3.9), runs the tests,
|
|
2
|
+
# and publishes to PyPI when a version tag (v*) is pushed.
|
|
3
|
+
name: Wheels
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
pull_request:
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
name: ${{ matrix.os }} ${{ matrix.target }}
|
|
18
|
+
runs-on: ${{ matrix.os }}
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
include:
|
|
23
|
+
- { os: ubuntu-latest, target: x86_64, test: true }
|
|
24
|
+
- { os: ubuntu-latest, target: aarch64, test: false }
|
|
25
|
+
- { os: macos-15-intel, target: x86_64, test: true }
|
|
26
|
+
- { os: macos-latest, target: aarch64, test: true }
|
|
27
|
+
- { os: windows-latest, target: x64, test: true }
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v6
|
|
30
|
+
- uses: actions/setup-python@v6
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.12"
|
|
33
|
+
- name: Build wheel
|
|
34
|
+
uses: PyO3/maturin-action@v1
|
|
35
|
+
with:
|
|
36
|
+
target: ${{ matrix.target }}
|
|
37
|
+
args: --release --out dist
|
|
38
|
+
manylinux: auto
|
|
39
|
+
- name: Test wheel
|
|
40
|
+
if: ${{ matrix.test }}
|
|
41
|
+
shell: bash
|
|
42
|
+
run: |
|
|
43
|
+
python -m pip install --upgrade pip
|
|
44
|
+
python -m pip install "$(ls dist/*.whl)[highs,test]"
|
|
45
|
+
python -m pytest tests -q
|
|
46
|
+
- uses: actions/upload-artifact@v6
|
|
47
|
+
with:
|
|
48
|
+
name: wheels-${{ matrix.os }}-${{ matrix.target }}
|
|
49
|
+
path: dist
|
|
50
|
+
|
|
51
|
+
sdist:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v6
|
|
55
|
+
- name: Build sdist
|
|
56
|
+
uses: PyO3/maturin-action@v1
|
|
57
|
+
with:
|
|
58
|
+
command: sdist
|
|
59
|
+
args: --out dist
|
|
60
|
+
- uses: actions/upload-artifact@v6
|
|
61
|
+
with:
|
|
62
|
+
name: sdist
|
|
63
|
+
path: dist
|
|
64
|
+
|
|
65
|
+
publish:
|
|
66
|
+
name: Publish to PyPI
|
|
67
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
68
|
+
needs: [build, sdist]
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
environment:
|
|
71
|
+
name: pypi
|
|
72
|
+
url: https://pypi.org/p/certifi-learn
|
|
73
|
+
permissions:
|
|
74
|
+
id-token: write # trusted publishing, no API token needed
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/download-artifact@v7
|
|
77
|
+
with:
|
|
78
|
+
pattern: "*"
|
|
79
|
+
merge-multiple: true
|
|
80
|
+
path: dist
|
|
81
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ipynb_checkpoints/
|
|
7
|
+
.venv/
|
|
8
|
+
|
|
9
|
+
# Rust / maturin build
|
|
10
|
+
/target
|
|
11
|
+
/dist
|
|
12
|
+
*.so
|
|
13
|
+
*.pyd
|
|
14
|
+
|
|
15
|
+
# Data downloaded by folktables
|
|
16
|
+
data/
|
|
17
|
+
|
|
18
|
+
# Local working material (paper experiments, results); see tag neurips2026
|
|
19
|
+
outputs/
|
|
20
|
+
multirun/
|
|
21
|
+
batch_out/
|
|
22
|
+
batch_from_cluster/
|
|
23
|
+
plots/
|
|
24
|
+
NeurIPS_rebuttal/
|
|
25
|
+
bias_analysis/
|
|
26
|
+
maxviol/
|
|
27
|
+
maxviol_package_1.zip
|
|
28
|
+
bias_detect_maxviol.py
|
|
29
|
+
experiment_eval_maxviol.py
|
|
30
|
+
bias_detect_cluster.py
|
|
31
|
+
msd.py
|
|
32
|
+
spsf_mio.py
|
|
33
|
+
NN_fair_classifier.py
|
|
34
|
+
NN_plot.py
|
|
35
|
+
eval_plot.py
|
|
36
|
+
train_plot.py
|
|
37
|
+
plot_gap.py
|
|
38
|
+
parse_eval.py
|
|
39
|
+
.claude/
|
|
40
|
+
paper.pdf
|
|
41
|
+
*.png
|
|
42
|
+
*.pdf
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "autocfg"
|
|
7
|
+
version = "1.5.1"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "certifi_learn"
|
|
13
|
+
version = "0.2.0"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"numpy",
|
|
16
|
+
"pyo3",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[package]]
|
|
20
|
+
name = "heck"
|
|
21
|
+
version = "0.5.0"
|
|
22
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
23
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
24
|
+
|
|
25
|
+
[[package]]
|
|
26
|
+
name = "libc"
|
|
27
|
+
version = "0.2.191"
|
|
28
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
29
|
+
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
|
30
|
+
|
|
31
|
+
[[package]]
|
|
32
|
+
name = "matrixmultiply"
|
|
33
|
+
version = "0.3.11"
|
|
34
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
35
|
+
checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7"
|
|
36
|
+
dependencies = [
|
|
37
|
+
"autocfg",
|
|
38
|
+
"rawpointer",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[[package]]
|
|
42
|
+
name = "ndarray"
|
|
43
|
+
version = "0.17.2"
|
|
44
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
45
|
+
checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
|
|
46
|
+
dependencies = [
|
|
47
|
+
"matrixmultiply",
|
|
48
|
+
"num-complex",
|
|
49
|
+
"num-integer",
|
|
50
|
+
"num-traits",
|
|
51
|
+
"portable-atomic",
|
|
52
|
+
"portable-atomic-util",
|
|
53
|
+
"rawpointer",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[[package]]
|
|
57
|
+
name = "num-complex"
|
|
58
|
+
version = "0.4.6"
|
|
59
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
60
|
+
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
|
61
|
+
dependencies = [
|
|
62
|
+
"num-traits",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[[package]]
|
|
66
|
+
name = "num-integer"
|
|
67
|
+
version = "0.1.47"
|
|
68
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
69
|
+
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
|
70
|
+
dependencies = [
|
|
71
|
+
"num-traits",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[[package]]
|
|
75
|
+
name = "num-traits"
|
|
76
|
+
version = "0.2.19"
|
|
77
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
78
|
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
79
|
+
dependencies = [
|
|
80
|
+
"autocfg",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[[package]]
|
|
84
|
+
name = "numpy"
|
|
85
|
+
version = "0.29.0"
|
|
86
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
87
|
+
checksum = "6a5b15d63a5ff39e378daed0e1340d3a5964703ea9712eb09a0dc66fade996f4"
|
|
88
|
+
dependencies = [
|
|
89
|
+
"libc",
|
|
90
|
+
"ndarray",
|
|
91
|
+
"num-complex",
|
|
92
|
+
"num-integer",
|
|
93
|
+
"num-traits",
|
|
94
|
+
"pyo3",
|
|
95
|
+
"pyo3-build-config",
|
|
96
|
+
"rustc-hash",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[[package]]
|
|
100
|
+
name = "once_cell"
|
|
101
|
+
version = "1.21.4"
|
|
102
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
103
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
104
|
+
|
|
105
|
+
[[package]]
|
|
106
|
+
name = "portable-atomic"
|
|
107
|
+
version = "1.15.0"
|
|
108
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
109
|
+
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
|
110
|
+
|
|
111
|
+
[[package]]
|
|
112
|
+
name = "portable-atomic-util"
|
|
113
|
+
version = "0.2.8"
|
|
114
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
115
|
+
checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
|
|
116
|
+
dependencies = [
|
|
117
|
+
"portable-atomic",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
[[package]]
|
|
121
|
+
name = "proc-macro2"
|
|
122
|
+
version = "1.0.107"
|
|
123
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
124
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
125
|
+
dependencies = [
|
|
126
|
+
"unicode-ident",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[[package]]
|
|
130
|
+
name = "pyo3"
|
|
131
|
+
version = "0.29.3"
|
|
132
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
133
|
+
checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655"
|
|
134
|
+
dependencies = [
|
|
135
|
+
"libc",
|
|
136
|
+
"once_cell",
|
|
137
|
+
"portable-atomic",
|
|
138
|
+
"pyo3-build-config",
|
|
139
|
+
"pyo3-ffi",
|
|
140
|
+
"pyo3-macros",
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
[[package]]
|
|
144
|
+
name = "pyo3-build-config"
|
|
145
|
+
version = "0.29.3"
|
|
146
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
147
|
+
checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb"
|
|
148
|
+
dependencies = [
|
|
149
|
+
"target-lexicon",
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
[[package]]
|
|
153
|
+
name = "pyo3-ffi"
|
|
154
|
+
version = "0.29.3"
|
|
155
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
156
|
+
checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330"
|
|
157
|
+
dependencies = [
|
|
158
|
+
"libc",
|
|
159
|
+
"pyo3-build-config",
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
[[package]]
|
|
163
|
+
name = "pyo3-macros"
|
|
164
|
+
version = "0.29.3"
|
|
165
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
166
|
+
checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6"
|
|
167
|
+
dependencies = [
|
|
168
|
+
"proc-macro2",
|
|
169
|
+
"pyo3-macros-backend",
|
|
170
|
+
"quote",
|
|
171
|
+
"syn",
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
[[package]]
|
|
175
|
+
name = "pyo3-macros-backend"
|
|
176
|
+
version = "0.29.3"
|
|
177
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
178
|
+
checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3"
|
|
179
|
+
dependencies = [
|
|
180
|
+
"heck",
|
|
181
|
+
"proc-macro2",
|
|
182
|
+
"quote",
|
|
183
|
+
"syn",
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
[[package]]
|
|
187
|
+
name = "quote"
|
|
188
|
+
version = "1.0.47"
|
|
189
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
190
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
191
|
+
dependencies = [
|
|
192
|
+
"proc-macro2",
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
[[package]]
|
|
196
|
+
name = "rawpointer"
|
|
197
|
+
version = "0.2.1"
|
|
198
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
199
|
+
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
|
200
|
+
|
|
201
|
+
[[package]]
|
|
202
|
+
name = "rustc-hash"
|
|
203
|
+
version = "2.1.3"
|
|
204
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
205
|
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
206
|
+
|
|
207
|
+
[[package]]
|
|
208
|
+
name = "syn"
|
|
209
|
+
version = "2.0.119"
|
|
210
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
211
|
+
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
|
212
|
+
dependencies = [
|
|
213
|
+
"proc-macro2",
|
|
214
|
+
"quote",
|
|
215
|
+
"unicode-ident",
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
[[package]]
|
|
219
|
+
name = "target-lexicon"
|
|
220
|
+
version = "0.13.5"
|
|
221
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
222
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
223
|
+
|
|
224
|
+
[[package]]
|
|
225
|
+
name = "unicode-ident"
|
|
226
|
+
version = "1.0.27"
|
|
227
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
228
|
+
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "certifi_learn"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[lib]
|
|
9
|
+
name = "_core"
|
|
10
|
+
crate-type = ["cdylib"]
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
pyo3 = { version = "0.29", features = ["extension-module", "abi3-py39"] }
|
|
14
|
+
numpy = "0.29"
|
|
15
|
+
|
|
16
|
+
[profile.release]
|
|
17
|
+
opt-level = 3
|
|
18
|
+
lto = true
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jiří Němeček, Mark Kozdoba, Illia Kryvoviaz, Tomáš Pevný, Jakub Mareček
|
|
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,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: certifi-learn
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: Programming Language :: Rust
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
8
|
+
Requires-Dist: numpy>=1.21
|
|
9
|
+
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: scikit-learn
|
|
11
|
+
Requires-Dist: folktables ; extra == 'examples'
|
|
12
|
+
Requires-Dist: pyomo>=6.7 ; extra == 'gurobi'
|
|
13
|
+
Requires-Dist: gurobipy ; extra == 'gurobi'
|
|
14
|
+
Requires-Dist: pyomo>=6.7 ; extra == 'highs'
|
|
15
|
+
Requires-Dist: highspy ; extra == 'highs'
|
|
16
|
+
Requires-Dist: pyomo>=6.7 ; extra == 'mio'
|
|
17
|
+
Requires-Dist: pytest ; extra == 'test'
|
|
18
|
+
Provides-Extra: examples
|
|
19
|
+
Provides-Extra: gurobi
|
|
20
|
+
Provides-Extra: highs
|
|
21
|
+
Provides-Extra: mio
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Summary: Certifiably Intersectionally Fair & Interpretable models: detection of the most biased subgroup and training of fair interpretable classifiers
|
|
25
|
+
Author: Jiří Němeček
|
|
26
|
+
License: MIT
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
29
|
+
Project-URL: Repository, https://github.com/Epanemu/CertIFI
|
|
30
|
+
|
|
31
|
+
# CertIFI: Certifiably Intersectionally Fair & Interpretable models
|
|
32
|
+
|
|
33
|
+
`certifi-learn` finds the **most biased subgroup** in data or in a model's predictions,
|
|
34
|
+
and trains **interpretable classifiers** (linear models and rule sets) that are
|
|
35
|
+
**certifiably fair** on all intersectional subgroups, for example all combinations
|
|
36
|
+
of sex, race and age group.
|
|
37
|
+
|
|
38
|
+
> [!NOTE]
|
|
39
|
+
> This library extends the NeurIPS 2026 paper
|
|
40
|
+
> *Intersectional Fairness via Mixed-Integer Optimization* (see [Citation](#citation)).
|
|
41
|
+
> Notably, it adds an exact branch-and-bound subgroup search (implemented in Rust)
|
|
42
|
+
> that replaces the Mixed-Integer Optimization (MIO) search for conjunction
|
|
43
|
+
> subgroups by default. The code used for the experiments in the paper is available
|
|
44
|
+
> at the tag [`neurips2026`](https://github.com/Epanemu/CertIFI/tree/neurips2026).
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **Bias detection.** Find the subgroup with the largest violation of
|
|
49
|
+
- Subgroup Discrepancy / MSD,
|
|
50
|
+
- Statistical Parity Subgroup Fairness (SPSF),
|
|
51
|
+
- False Positive Subgroup Fairness (FPSF).
|
|
52
|
+
|
|
53
|
+
Subgroups can be
|
|
54
|
+
- **conjunctions** of protected attribute values (e.g. `SEX = Female AND RAC1P = Black`). The exact search is fast, needs no solver, and its result is proven optimal.
|
|
55
|
+
- **linear threshold functions** of the protected attributes, found by MIO.
|
|
56
|
+
- **Fair training.** Linear classifiers and DNFs (rule sets), trained to optimality
|
|
57
|
+
under the constraint that *every* subgroup has bias at most γ. Fairness
|
|
58
|
+
constraints are generated lazily: only the subgroups that are actually violated
|
|
59
|
+
are added, so there's no need to enumerate the exponentially many subgroups.
|
|
60
|
+
- A scikit-learn-style API that works with pandas DataFrames. Categorical
|
|
61
|
+
features are encoded automatically.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install "certifi-learn[gurobi]"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Prebuilt wheels are available for Linux, macOS and Windows (Python 3.9+). To install
|
|
70
|
+
the latest version from GitHub instead, you need a [Rust toolchain](https://rustup.rs/):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install "certifi-learn[gurobi] @ git+https://github.com/Epanemu/CertIFI"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Optional dependencies (extras):
|
|
77
|
+
|
|
78
|
+
| Extra | Needed for |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `gurobi` | Training classifiers (Gurobi lazy-constraint callbacks) and MIO detection. Requires a [Gurobi license](https://www.gurobi.com/academia/academic-program-and-licenses/), free for academic use. |
|
|
81
|
+
| `highs` | MIO detection with the open-source [HiGHS](https://highs.dev/) solver, an alternative to Gurobi |
|
|
82
|
+
| `mio` | Pyomo only, if you bring your own solver installation |
|
|
83
|
+
| `examples` | The folktables example |
|
|
84
|
+
|
|
85
|
+
Bias detection on conjunction subgroups needs no extras.
|
|
86
|
+
|
|
87
|
+
## Quickstart
|
|
88
|
+
|
|
89
|
+
### Finding the most biased subgroup
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from certifi_learn import find_subgroup
|
|
93
|
+
from certifi_learn.encoding import TabularEncoder
|
|
94
|
+
|
|
95
|
+
# protected: DataFrame of protected attributes, y: binary labels, y_pred: model predictions
|
|
96
|
+
enc = TabularEncoder(protected) # binarizes into literals like "SEX = Female"
|
|
97
|
+
P = enc.binary(protected, negations=False)
|
|
98
|
+
names = enc.binary_names(negations=False)
|
|
99
|
+
|
|
100
|
+
# bias in the data: subgroup whose share differs most between positive and negative labels
|
|
101
|
+
sg = find_subgroup(P, y, measure="msd")
|
|
102
|
+
print(f"MSD = {sg.value:.3f}, proven optimal: {sg.optimal}")
|
|
103
|
+
print(sg.describe(names))
|
|
104
|
+
# e.g., on ACS Income (examples/detect_and_train.py):
|
|
105
|
+
# MSD = 0.194, proven optimal: True
|
|
106
|
+
# AGEP between [17, 24.2)
|
|
107
|
+
|
|
108
|
+
# certify that no subgroup has bias above a threshold
|
|
109
|
+
sg = find_subgroup(P, y, measure="msd", gamma=0.25) # None: no subgroup has MSD > 0.25
|
|
110
|
+
|
|
111
|
+
# bias of a classifier
|
|
112
|
+
sg = find_subgroup(P, y_pred, measure="fpsf", y_true=y)
|
|
113
|
+
|
|
114
|
+
# also allow negated literals, e.g. "NOT RAC1P = White"
|
|
115
|
+
sg = find_subgroup(P, y_pred, measure="spsf", negations=True)
|
|
116
|
+
|
|
117
|
+
# linear subgroups (MIO, requires a solver)
|
|
118
|
+
sg = find_subgroup(P, y_pred, measure="spsf", subgroups="linear", solver="highs", time_limit=300)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`find_subgroup` returns a `DetectedSubgroup` with **the violation `value`** (of the
|
|
122
|
+
chosen measure, on the given data), the membership `mask`, `optimal` (whether the subgroup is proven to be the most biased),
|
|
123
|
+
and `describe()` / `contains(X_new)`. If you pass `gamma=...`, it returns a subgroup
|
|
124
|
+
only if one violates the threshold, and `None` otherwise. That `None` certifies
|
|
125
|
+
γ-fairness, unless a warning reports that the search was stopped by the time limit.
|
|
126
|
+
|
|
127
|
+
### Training a fair classifier
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from certifi_learn import FairLinearClassifier, FairDNFClassifier
|
|
131
|
+
|
|
132
|
+
clf = FairLinearClassifier(gamma=0.01, measure="fpsf", time_limit=600)
|
|
133
|
+
clf.fit(X, y, X_protected) # X may include the protected attributes
|
|
134
|
+
y_pred = clf.predict(X_test)
|
|
135
|
+
|
|
136
|
+
print(clf.n_cuts_) # number of subgroups constrained during training
|
|
137
|
+
print(clf.describe_cut_subgroups()) # which ones
|
|
138
|
+
|
|
139
|
+
rules = FairDNFClassifier(n_terms=3, gamma=0.01, measure="fpsf").fit(X, y, X_protected)
|
|
140
|
+
print(rules.describe())
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Common parameters:
|
|
144
|
+
|
|
145
|
+
| Parameter | Values |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `measure` | `"fpsf"`, `"fpsf_by_sd"` (FPSF constraints, with violating subgroups found by maximizing Subgroup Discrepancy on negative samples), `"spsf"`, `"msd"`, or `None` (no fairness constraint) |
|
|
148
|
+
| `subgroups` | `"conjunction"` or `"linear"` |
|
|
149
|
+
| `subgroup_negations` | Also allow negated literals in conjunction subgroups (e.g. `RAC1P != White`), which constrains a larger set of subgroups. Default `False`. |
|
|
150
|
+
| `gamma` | Fairness threshold. Every subgroup must have bias ≤ γ on the training data. |
|
|
151
|
+
| `detection_backend` | `"exact"` (default) or `"mio"`, for finding violating conjunction subgroups. Linear subgroups always use MIO. |
|
|
152
|
+
| `time_limit` | Solver time limit in seconds. The best solution found so far is returned. |
|
|
153
|
+
|
|
154
|
+
Model-specific parameters:
|
|
155
|
+
|
|
156
|
+
| Model | Parameter | Values |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `FairLinearClassifier` | `sparsity` | Weight of the penalty on the proportion of non-zero coefficients (default 0) |
|
|
159
|
+
| `FairDNFClassifier` | `n_terms` | Number of terms (rules) of the DNF (default 3) |
|
|
160
|
+
|
|
161
|
+
γ-fairness is guaranteed on the training data. On new data, the bias can be
|
|
162
|
+
somewhat higher. Audit your model on held-out data with `find_subgroup`.
|
|
163
|
+
|
|
164
|
+
## Bias measures
|
|
165
|
+
|
|
166
|
+
For a classifier h and a subgroup S:
|
|
167
|
+
|
|
168
|
+
| Measure | Definition |
|
|
169
|
+
|---|---|
|
|
170
|
+
| SPSF | P(S) · \|P(h=1) − P(h=1 \| S)\| |
|
|
171
|
+
| FPSF | P(S, y=0) · \|P(h=1 \| y=0) − P(h=1 \| S, y=0)\| |
|
|
172
|
+
| SD / MSD | \|P(S \| y=1) − P(S \| y=0)\|, i.e. how differently S is represented among positive and negative samples. Maximized over conjunctions, this is the [Maximum Subgroup Discrepancy](https://github.com/Epanemu/MSD). |
|
|
173
|
+
|
|
174
|
+
`certifi_learn.measures` evaluates these for a given subgroup mask.
|
|
175
|
+
`certifi_learn.exact` exposes the Rust search directly (`msd`, `spsf`, `fpsf`), with
|
|
176
|
+
options such as sample weights, negated literals and grouped one-hot columns. See
|
|
177
|
+
[`docs/subgroup_pruning_proof.tex`](docs/subgroup_pruning_proof.tex) for the
|
|
178
|
+
algorithm and its proof of correctness.
|
|
179
|
+
|
|
180
|
+
## Example
|
|
181
|
+
|
|
182
|
+
[`examples/detect_and_train.py`](examples/detect_and_train.py) runs end to end on
|
|
183
|
+
the US Census data (ACS Income, via [folktables](https://github.com/socialfoundations/folktables)).
|
|
184
|
+
It finds the most biased subgroup in the data, trains an unconstrained and a fair
|
|
185
|
+
linear model, and audits both on test data:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pip install "certifi-learn[gurobi,examples]"
|
|
189
|
+
python examples/detect_and_train.py
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Development
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pip install -e ".[gurobi,highs,test,examples]" # builds the Rust extension
|
|
196
|
+
pytest
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Tests that need HiGHS or a Gurobi license are skipped if those are not available.
|
|
200
|
+
|
|
201
|
+
## Citation
|
|
202
|
+
|
|
203
|
+
```bibtex
|
|
204
|
+
@inproceedings{nemecek2026intersectional,
|
|
205
|
+
author = {N\v{e}me\v{c}ek, Ji\v{r}\'{\i} and Kozdoba, Mark and Kryvoviaz, Illia and Pevn\'{y}, Tom\'{a}\v{s} and Mare\v{c}ek, Jakub},
|
|
206
|
+
booktitle = {Advances in Neural Information Processing Systems},
|
|
207
|
+
title = {Intersectional Fairness via Mixed-Integer Optimization},
|
|
208
|
+
volume = {39, Main Conference},
|
|
209
|
+
year = {2026}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
[MIT](LICENSE)
|
|
216
|
+
|