fast-ivap 1.0.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.
- fast_ivap-1.0.0/.gitignore +18 -0
- fast_ivap-1.0.0/CHANGELOG.md +35 -0
- fast_ivap-1.0.0/CMakeLists.txt +48 -0
- fast_ivap-1.0.0/LICENSE +21 -0
- fast_ivap-1.0.0/PKG-INFO +188 -0
- fast_ivap-1.0.0/README.md +166 -0
- fast_ivap-1.0.0/examples/cpp/example.cpp +21 -0
- fast_ivap-1.0.0/examples/quickstart.py +34 -0
- fast_ivap-1.0.0/include/fast_ivap/ivap.hpp +220 -0
- fast_ivap-1.0.0/pyproject.toml +70 -0
- fast_ivap-1.0.0/src/bindings/core.cpp +66 -0
- fast_ivap-1.0.0/src/fast_ivap/__init__.py +30 -0
- fast_ivap-1.0.0/src/fast_ivap/_api.py +162 -0
- fast_ivap-1.0.0/src/fast_ivap/_core.pyi +14 -0
- fast_ivap-1.0.0/src/fast_ivap/_legacy.py +34 -0
- fast_ivap-1.0.0/src/fast_ivap/_reference.py +141 -0
- fast_ivap-1.0.0/src/fast_ivap/py.typed +0 -0
- fast_ivap-1.0.0/src/fast_ivap/sklearn.py +81 -0
- fast_ivap-1.0.0/tests/cpp/test_ivap.cpp +123 -0
- fast_ivap-1.0.0/tests/test_ivap.py +195 -0
- fast_ivap-1.0.0/tests/test_sklearn.py +76 -0
- fast_ivap-1.0.0/uv.lock +540 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 - 2026-10-27
|
|
4
|
+
|
|
5
|
+
Complete rewrite, published under the name `fast-ivap` (import `fast_ivap`).
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- `p0` and `p1` were wrong for a test score equal to one of the calibration
|
|
9
|
+
scores.
|
|
10
|
+
- `p1` was wrong for a test score above all calibration scores (it must be 1).
|
|
11
|
+
- The pure-Python implementation failed on NumPy 2 (`np.cross` on 2-D vectors).
|
|
12
|
+
- Removed the module-level global state, so separate predictors no longer
|
|
13
|
+
interfere with each other.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- New API: `fast_ivap.IVAP` (`fit`, `predict_p0_p1`, `predict_proba`) and
|
|
17
|
+
`fast_ivap.sklearn.VennAbersCalibrator`, whose `predict_proba` now returns
|
|
18
|
+
an `(n, 2)` array as scikit-learn expects. `ScoresToMultiProbs` is kept as a
|
|
19
|
+
deprecated shim.
|
|
20
|
+
- The C++ code is now a dependency-free header-only library, with nanobind
|
|
21
|
+
bindings (zero-copy NumPy input, GIL released) built by scikit-build-core.
|
|
22
|
+
- Portable wheels for Linux, macOS and Windows via cibuildwheel.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `merge_log_minimax` (IVAP and CVAP) and `merge_brier`.
|
|
26
|
+
- pytest suite checking both backends against IVAP computed from its
|
|
27
|
+
definition; C++ tests via CTest; CI on Linux, macOS and Windows.
|
|
28
|
+
|
|
29
|
+
## 0.2 - 2020-07
|
|
30
|
+
- Fixed bug affecting the `p0` calculation; added a test notebook.
|
|
31
|
+
- (2024, branches `cpp_version` / `pip_package`) C++ port with pybind11 and a
|
|
32
|
+
scikit-learn style wrapper.
|
|
33
|
+
|
|
34
|
+
## 0.1
|
|
35
|
+
- Initial pure-Python implementation.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.18...3.31)
|
|
2
|
+
project(fast_ivap LANGUAGES CXX)
|
|
3
|
+
|
|
4
|
+
# Header-only C++ library: fast_ivap::fast_ivap
|
|
5
|
+
add_library(fast_ivap INTERFACE)
|
|
6
|
+
add_library(fast_ivap::fast_ivap ALIAS fast_ivap)
|
|
7
|
+
target_include_directories(
|
|
8
|
+
fast_ivap INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
9
|
+
$<INSTALL_INTERFACE:include>)
|
|
10
|
+
target_compile_features(fast_ivap INTERFACE cxx_std_20)
|
|
11
|
+
|
|
12
|
+
if(SKBUILD)
|
|
13
|
+
# Python extension module fast_ivap._core, built by scikit-build-core.
|
|
14
|
+
find_package(Python 3.11 REQUIRED
|
|
15
|
+
COMPONENTS Interpreter Development.Module ${SKBUILD_SABI_COMPONENT})
|
|
16
|
+
find_package(nanobind CONFIG REQUIRED)
|
|
17
|
+
nanobind_add_module(_core STABLE_ABI NB_STATIC src/bindings/core.cpp)
|
|
18
|
+
target_link_libraries(_core PRIVATE fast_ivap)
|
|
19
|
+
install(TARGETS _core LIBRARY DESTINATION fast_ivap)
|
|
20
|
+
return()
|
|
21
|
+
endif()
|
|
22
|
+
|
|
23
|
+
# Standalone C++ build: tests, example and install rules for find_package().
|
|
24
|
+
option(FAST_IVAP_BUILD_TESTS "Build the C++ tests" ${PROJECT_IS_TOP_LEVEL})
|
|
25
|
+
option(FAST_IVAP_BUILD_EXAMPLES "Build the C++ example" ${PROJECT_IS_TOP_LEVEL})
|
|
26
|
+
|
|
27
|
+
if(FAST_IVAP_BUILD_TESTS)
|
|
28
|
+
enable_testing()
|
|
29
|
+
add_executable(test_ivap tests/cpp/test_ivap.cpp)
|
|
30
|
+
target_link_libraries(test_ivap PRIVATE fast_ivap)
|
|
31
|
+
add_test(NAME test_ivap COMMAND test_ivap)
|
|
32
|
+
endif()
|
|
33
|
+
|
|
34
|
+
if(FAST_IVAP_BUILD_EXAMPLES)
|
|
35
|
+
add_executable(ivap_example examples/cpp/example.cpp)
|
|
36
|
+
target_link_libraries(ivap_example PRIVATE fast_ivap)
|
|
37
|
+
endif()
|
|
38
|
+
|
|
39
|
+
include(GNUInstallDirs)
|
|
40
|
+
include(CMakePackageConfigHelpers)
|
|
41
|
+
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
|
42
|
+
install(TARGETS fast_ivap EXPORT fast_ivapTargets)
|
|
43
|
+
install(EXPORT fast_ivapTargets NAMESPACE fast_ivap::
|
|
44
|
+
DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/fast_ivap)
|
|
45
|
+
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/fast_ivapConfig.cmake
|
|
46
|
+
"include(\"\${CMAKE_CURRENT_LIST_DIR}/fast_ivapTargets.cmake\")\n")
|
|
47
|
+
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fast_ivapConfig.cmake
|
|
48
|
+
DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/fast_ivap)
|
fast_ivap-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Paolo Toccaceli, Royal Holloway, Univ. of London
|
|
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.
|
fast_ivap-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fast-ivap
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Fast inductive Venn-ABERS predictors: distribution-free calibration of classifier scores
|
|
5
|
+
Keywords: calibration,conformal prediction,venn-abers,isotonic regression,probability
|
|
6
|
+
Author: Paolo Toccaceli
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: C++
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Project-URL: Homepage, https://github.com/ptocca/VennABERS
|
|
16
|
+
Project-URL: Paper, https://arxiv.org/abs/1511.00213
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: numpy>=1.25
|
|
19
|
+
Provides-Extra: sklearn
|
|
20
|
+
Requires-Dist: scikit-learn>=1.6; extra == "sklearn"
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# fast-ivap: fast inductive Venn-ABERS predictors
|
|
24
|
+
|
|
25
|
+
[](https://github.com/ptocca/VennABERS/actions/workflows/ci.yml)
|
|
26
|
+
|
|
27
|
+
`fast-ivap` turns the scores of any binary classifier into **well-calibrated
|
|
28
|
+
probabilities**, with no assumption beyond the calibration and test data being
|
|
29
|
+
i.i.d. It implements the inductive Venn-ABERS predictor (IVAP) with the
|
|
30
|
+
O(k log k) algorithm of
|
|
31
|
+
|
|
32
|
+
> V. Vovk, I. Petej, V. Fedorova. *Large-scale probabilistic predictors with
|
|
33
|
+
> and without guarantees of validity.* NeurIPS 2015.
|
|
34
|
+
> [arXiv:1511.00213](https://arxiv.org/abs/1511.00213)
|
|
35
|
+
|
|
36
|
+
The core is a small header-only C++20 library with Python bindings
|
|
37
|
+
([nanobind](https://github.com/wjakob/nanobind)), plus a pure-Python
|
|
38
|
+
reference implementation. A gentle introduction to Venn-ABERS predictors is in
|
|
39
|
+
this [tutorial](https://cml.rhul.ac.uk/people/ptocca/HomePage/Toccaceli_CP___Venn_Tutorial.pdf).
|
|
40
|
+
|
|
41
|
+
## What it computes
|
|
42
|
+
|
|
43
|
+
Given calibration scores and labels, and the score `s` of a test object, an
|
|
44
|
+
IVAP outputs two probabilities that the label is 1:
|
|
45
|
+
|
|
46
|
+
- `p0`: the isotonic-regression fit at `s` after adding `(s, 0)` to the
|
|
47
|
+
calibration set,
|
|
48
|
+
- `p1`: the same after adding `(s, 1)`.
|
|
49
|
+
|
|
50
|
+
`p0 <= p1` always holds, and one of the two (which one depends on the unknown
|
|
51
|
+
true label) is perfectly calibrated. The gap `p1 - p0` is typically small and
|
|
52
|
+
shrinks as the calibration set grows. A single probability can be
|
|
53
|
+
obtained with the log-loss minimax merge
|
|
54
|
+
|
|
55
|
+
$$p = \frac{p_1}{1 - p_0 + p_1}.$$
|
|
56
|
+
|
|
57
|
+
Fitting takes O(k log k) for k calibration examples; each prediction takes
|
|
58
|
+
O(log k).
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install fast-ivap # NumPy API
|
|
64
|
+
pip install "fast-ivap[sklearn]" # plus the scikit-learn estimator
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Until the package is on PyPI, install from GitHub (this builds from source, so
|
|
68
|
+
it needs a C++20 compiler; CMake is fetched automatically if missing):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install git+https://github.com/ptocca/VennABERS
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Prebuilt wheels are produced for Linux (x86-64, aarch64; glibc and musl),
|
|
75
|
+
macOS (Intel and Apple silicon) and Windows (x86-64, ARM64), for CPython 3.11
|
|
76
|
+
and a single stable-ABI wheel for CPython 3.12 and later.
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### NumPy
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from fast_ivap import IVAP
|
|
84
|
+
|
|
85
|
+
ivap = IVAP().fit(cal_scores, cal_labels) # labels are 0/1 or booleans
|
|
86
|
+
p0, p1 = ivap.predict_p0_p1(test_scores) # Venn-ABERS pair
|
|
87
|
+
p = ivap.predict_proba(test_scores) # merged probability of label 1
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
See [`examples/quickstart.py`](examples/quickstart.py) for a complete example
|
|
91
|
+
with a scikit-learn classifier.
|
|
92
|
+
|
|
93
|
+
For a cross Venn-ABERS predictor (CVAP), fit one IVAP per fold, stack the
|
|
94
|
+
outputs and merge them with geometric means:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from fast_ivap import merge_log_minimax
|
|
98
|
+
|
|
99
|
+
p = merge_log_minimax(p0_folds, p1_folds, axis=0) # arrays of shape (K, n)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`merge_brier` gives the Brier-loss minimax merge instead.
|
|
103
|
+
|
|
104
|
+
### scikit-learn
|
|
105
|
+
|
|
106
|
+
`VennAbersCalibrator` takes one score per example and follows the
|
|
107
|
+
scikit-learn classifier API (`predict_proba` returns `[1 - p, p]`):
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from fast_ivap.sklearn import VennAbersCalibrator
|
|
111
|
+
|
|
112
|
+
cal = VennAbersCalibrator().fit(clf.decision_function(X_cal), y_cal)
|
|
113
|
+
proba = cal.predict_proba(clf.decision_function(X_test))
|
|
114
|
+
p0, p1 = cal.predict_p0_p1(clf.decision_function(X_test))
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### C++
|
|
118
|
+
|
|
119
|
+
The C++ library is the single header
|
|
120
|
+
[`include/fast_ivap/ivap.hpp`](include/fast_ivap/ivap.hpp) with no
|
|
121
|
+
dependencies. Copy it into your project, or install it and use CMake:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cmake -S . -B build && cmake --install build --prefix /your/prefix
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```cmake
|
|
128
|
+
find_package(fast_ivap REQUIRED)
|
|
129
|
+
target_link_libraries(my_app PRIVATE fast_ivap::fast_ivap)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```cpp
|
|
133
|
+
#include <fast_ivap/ivap.hpp>
|
|
134
|
+
|
|
135
|
+
auto cal = fast_ivap::fit(scores, labels); // any contiguous range of doubles
|
|
136
|
+
auto [p0, p1] = fast_ivap::predict(cal, test_scores);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Implementation notes
|
|
140
|
+
|
|
141
|
+
- The C++ backend is used when available. `IVAP(backend="python")` or the
|
|
142
|
+
environment variable `FAST_IVAP_BACKEND=python` select the pure-Python
|
|
143
|
+
implementation in [`_reference.py`](src/fast_ivap/_reference.py), which
|
|
144
|
+
follows the paper step by step. Both are tested against each other and
|
|
145
|
+
against a brute-force IVAP computed from its definition.
|
|
146
|
+
- `p1` is computed with Algorithms 1 and 2 of the paper. Instead of the
|
|
147
|
+
mirror-image Algorithms 3 and 4, `p0` is obtained from the same code by the
|
|
148
|
+
symmetry noted in the paper: negate the scores, flip the labels, and take
|
|
149
|
+
`1 - p1`.
|
|
150
|
+
- The paper defines the extra CSD point for Algorithms 3–4 as
|
|
151
|
+
`P[k'+1] = P[k'] + (1, 1)`; it should be `P[k'] + (1, 0)`, because that
|
|
152
|
+
segment represents the test object with label 0. The original
|
|
153
|
+
implementation already used `(1, 0)`.
|
|
154
|
+
- A test score equal to a calibration score, and a test score outside the
|
|
155
|
+
range of the calibration scores, are handled exactly as in the definition
|
|
156
|
+
(two bugs in versions up to 0.2 affected these cases).
|
|
157
|
+
- A NaN test score yields NaN probabilities. Calibration scores must be finite.
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
uv sync --group dev # build the extension and install dev tools
|
|
163
|
+
uv run pytest # Python tests (both backends)
|
|
164
|
+
uv run ruff check . && uv run ruff format --check .
|
|
165
|
+
|
|
166
|
+
cmake -S . -B build && cmake --build build && ctest --test-dir build # C++ tests
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The GitHub Actions workflows run the tests on Linux, macOS and Windows, and
|
|
170
|
+
build wheels with [cibuildwheel](https://cibuildwheel.pypa.io/). Publishing a
|
|
171
|
+
GitHub release uploads the wheels and sdist to PyPI (trusted publishing).
|
|
172
|
+
|
|
173
|
+
## Version history
|
|
174
|
+
|
|
175
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
176
|
+
|
|
177
|
+
## Acknowledgements
|
|
178
|
+
|
|
179
|
+
- The algorithm was invented by Prof. Vladimir Vovk et al. at the
|
|
180
|
+
[Centre for Reliable Machine Learning, Royal Holloway, University of London](https://cml.rhul.ac.uk/).
|
|
181
|
+
- This work was supported in part by funding from the European Union's Horizon
|
|
182
|
+
2020 Research and Innovation programme under Grant Agreement no. 671555
|
|
183
|
+
(ExCAPE).
|
|
184
|
+
- We are grateful to the Ministry of Education, Youth and Sports (Czech
|
|
185
|
+
Republic) that supports the Large Infrastructures for Research, Experimental
|
|
186
|
+
Development and Innovations project "IT4Innovations National Supercomputing
|
|
187
|
+
Center - LM2015070" for providing access to the Solomon supercomputing
|
|
188
|
+
cluster.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# fast-ivap: fast inductive Venn-ABERS predictors
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ptocca/VennABERS/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
`fast-ivap` turns the scores of any binary classifier into **well-calibrated
|
|
6
|
+
probabilities**, with no assumption beyond the calibration and test data being
|
|
7
|
+
i.i.d. It implements the inductive Venn-ABERS predictor (IVAP) with the
|
|
8
|
+
O(k log k) algorithm of
|
|
9
|
+
|
|
10
|
+
> V. Vovk, I. Petej, V. Fedorova. *Large-scale probabilistic predictors with
|
|
11
|
+
> and without guarantees of validity.* NeurIPS 2015.
|
|
12
|
+
> [arXiv:1511.00213](https://arxiv.org/abs/1511.00213)
|
|
13
|
+
|
|
14
|
+
The core is a small header-only C++20 library with Python bindings
|
|
15
|
+
([nanobind](https://github.com/wjakob/nanobind)), plus a pure-Python
|
|
16
|
+
reference implementation. A gentle introduction to Venn-ABERS predictors is in
|
|
17
|
+
this [tutorial](https://cml.rhul.ac.uk/people/ptocca/HomePage/Toccaceli_CP___Venn_Tutorial.pdf).
|
|
18
|
+
|
|
19
|
+
## What it computes
|
|
20
|
+
|
|
21
|
+
Given calibration scores and labels, and the score `s` of a test object, an
|
|
22
|
+
IVAP outputs two probabilities that the label is 1:
|
|
23
|
+
|
|
24
|
+
- `p0`: the isotonic-regression fit at `s` after adding `(s, 0)` to the
|
|
25
|
+
calibration set,
|
|
26
|
+
- `p1`: the same after adding `(s, 1)`.
|
|
27
|
+
|
|
28
|
+
`p0 <= p1` always holds, and one of the two (which one depends on the unknown
|
|
29
|
+
true label) is perfectly calibrated. The gap `p1 - p0` is typically small and
|
|
30
|
+
shrinks as the calibration set grows. A single probability can be
|
|
31
|
+
obtained with the log-loss minimax merge
|
|
32
|
+
|
|
33
|
+
$$p = \frac{p_1}{1 - p_0 + p_1}.$$
|
|
34
|
+
|
|
35
|
+
Fitting takes O(k log k) for k calibration examples; each prediction takes
|
|
36
|
+
O(log k).
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install fast-ivap # NumPy API
|
|
42
|
+
pip install "fast-ivap[sklearn]" # plus the scikit-learn estimator
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Until the package is on PyPI, install from GitHub (this builds from source, so
|
|
46
|
+
it needs a C++20 compiler; CMake is fetched automatically if missing):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install git+https://github.com/ptocca/VennABERS
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Prebuilt wheels are produced for Linux (x86-64, aarch64; glibc and musl),
|
|
53
|
+
macOS (Intel and Apple silicon) and Windows (x86-64, ARM64), for CPython 3.11
|
|
54
|
+
and a single stable-ABI wheel for CPython 3.12 and later.
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### NumPy
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from fast_ivap import IVAP
|
|
62
|
+
|
|
63
|
+
ivap = IVAP().fit(cal_scores, cal_labels) # labels are 0/1 or booleans
|
|
64
|
+
p0, p1 = ivap.predict_p0_p1(test_scores) # Venn-ABERS pair
|
|
65
|
+
p = ivap.predict_proba(test_scores) # merged probability of label 1
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
See [`examples/quickstart.py`](examples/quickstart.py) for a complete example
|
|
69
|
+
with a scikit-learn classifier.
|
|
70
|
+
|
|
71
|
+
For a cross Venn-ABERS predictor (CVAP), fit one IVAP per fold, stack the
|
|
72
|
+
outputs and merge them with geometric means:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from fast_ivap import merge_log_minimax
|
|
76
|
+
|
|
77
|
+
p = merge_log_minimax(p0_folds, p1_folds, axis=0) # arrays of shape (K, n)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`merge_brier` gives the Brier-loss minimax merge instead.
|
|
81
|
+
|
|
82
|
+
### scikit-learn
|
|
83
|
+
|
|
84
|
+
`VennAbersCalibrator` takes one score per example and follows the
|
|
85
|
+
scikit-learn classifier API (`predict_proba` returns `[1 - p, p]`):
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from fast_ivap.sklearn import VennAbersCalibrator
|
|
89
|
+
|
|
90
|
+
cal = VennAbersCalibrator().fit(clf.decision_function(X_cal), y_cal)
|
|
91
|
+
proba = cal.predict_proba(clf.decision_function(X_test))
|
|
92
|
+
p0, p1 = cal.predict_p0_p1(clf.decision_function(X_test))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### C++
|
|
96
|
+
|
|
97
|
+
The C++ library is the single header
|
|
98
|
+
[`include/fast_ivap/ivap.hpp`](include/fast_ivap/ivap.hpp) with no
|
|
99
|
+
dependencies. Copy it into your project, or install it and use CMake:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cmake -S . -B build && cmake --install build --prefix /your/prefix
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```cmake
|
|
106
|
+
find_package(fast_ivap REQUIRED)
|
|
107
|
+
target_link_libraries(my_app PRIVATE fast_ivap::fast_ivap)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```cpp
|
|
111
|
+
#include <fast_ivap/ivap.hpp>
|
|
112
|
+
|
|
113
|
+
auto cal = fast_ivap::fit(scores, labels); // any contiguous range of doubles
|
|
114
|
+
auto [p0, p1] = fast_ivap::predict(cal, test_scores);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Implementation notes
|
|
118
|
+
|
|
119
|
+
- The C++ backend is used when available. `IVAP(backend="python")` or the
|
|
120
|
+
environment variable `FAST_IVAP_BACKEND=python` select the pure-Python
|
|
121
|
+
implementation in [`_reference.py`](src/fast_ivap/_reference.py), which
|
|
122
|
+
follows the paper step by step. Both are tested against each other and
|
|
123
|
+
against a brute-force IVAP computed from its definition.
|
|
124
|
+
- `p1` is computed with Algorithms 1 and 2 of the paper. Instead of the
|
|
125
|
+
mirror-image Algorithms 3 and 4, `p0` is obtained from the same code by the
|
|
126
|
+
symmetry noted in the paper: negate the scores, flip the labels, and take
|
|
127
|
+
`1 - p1`.
|
|
128
|
+
- The paper defines the extra CSD point for Algorithms 3–4 as
|
|
129
|
+
`P[k'+1] = P[k'] + (1, 1)`; it should be `P[k'] + (1, 0)`, because that
|
|
130
|
+
segment represents the test object with label 0. The original
|
|
131
|
+
implementation already used `(1, 0)`.
|
|
132
|
+
- A test score equal to a calibration score, and a test score outside the
|
|
133
|
+
range of the calibration scores, are handled exactly as in the definition
|
|
134
|
+
(two bugs in versions up to 0.2 affected these cases).
|
|
135
|
+
- A NaN test score yields NaN probabilities. Calibration scores must be finite.
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
uv sync --group dev # build the extension and install dev tools
|
|
141
|
+
uv run pytest # Python tests (both backends)
|
|
142
|
+
uv run ruff check . && uv run ruff format --check .
|
|
143
|
+
|
|
144
|
+
cmake -S . -B build && cmake --build build && ctest --test-dir build # C++ tests
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The GitHub Actions workflows run the tests on Linux, macOS and Windows, and
|
|
148
|
+
build wheels with [cibuildwheel](https://cibuildwheel.pypa.io/). Publishing a
|
|
149
|
+
GitHub release uploads the wheels and sdist to PyPI (trusted publishing).
|
|
150
|
+
|
|
151
|
+
## Version history
|
|
152
|
+
|
|
153
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
154
|
+
|
|
155
|
+
## Acknowledgements
|
|
156
|
+
|
|
157
|
+
- The algorithm was invented by Prof. Vladimir Vovk et al. at the
|
|
158
|
+
[Centre for Reliable Machine Learning, Royal Holloway, University of London](https://cml.rhul.ac.uk/).
|
|
159
|
+
- This work was supported in part by funding from the European Union's Horizon
|
|
160
|
+
2020 Research and Innovation programme under Grant Agreement no. 671555
|
|
161
|
+
(ExCAPE).
|
|
162
|
+
- We are grateful to the Ministry of Education, Youth and Sports (Czech
|
|
163
|
+
Republic) that supports the Large Infrastructures for Research, Experimental
|
|
164
|
+
Development and Innovations project "IT4Innovations National Supercomputing
|
|
165
|
+
Center - LM2015070" for providing access to the Solomon supercomputing
|
|
166
|
+
cluster.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Minimal example of using the header-only C++ library.
|
|
2
|
+
//
|
|
3
|
+
// cmake -B build && cmake --build build && ./build/ivap_example
|
|
4
|
+
|
|
5
|
+
#include <fast_ivap/ivap.hpp>
|
|
6
|
+
|
|
7
|
+
#include <cstdio>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
int main() {
|
|
11
|
+
const std::vector<double> scores{2.0, 3.0, 4.0, 5.0, 7.0, 8.0};
|
|
12
|
+
const std::vector<double> labels{0.0, 1.0, 0.0, 0.0, 1.0, 1.0};
|
|
13
|
+
const std::vector<double> test{1.0, 2.5, 7.2};
|
|
14
|
+
|
|
15
|
+
const fast_ivap::Calibrator cal = fast_ivap::fit(scores, labels);
|
|
16
|
+
const auto [p0, p1] = fast_ivap::predict(cal, test);
|
|
17
|
+
|
|
18
|
+
for (std::size_t i = 0; i < test.size(); ++i) {
|
|
19
|
+
std::printf("score %4.1f -> p0 = %.3f, p1 = %.3f\n", test[i], p0[i], p1[i]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Calibrate a classifier's scores with an inductive Venn-ABERS predictor.
|
|
2
|
+
|
|
3
|
+
Run with: python examples/quickstart.py (needs scikit-learn)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from sklearn.datasets import make_classification
|
|
8
|
+
from sklearn.model_selection import train_test_split
|
|
9
|
+
from sklearn.svm import LinearSVC
|
|
10
|
+
|
|
11
|
+
from fast_ivap import IVAP
|
|
12
|
+
|
|
13
|
+
X, y = make_classification(n_samples=20_000, n_features=20, flip_y=0.1, random_state=0)
|
|
14
|
+
X_train, X_rest, y_train, y_rest = train_test_split(X, y, train_size=0.5, random_state=0)
|
|
15
|
+
X_cal, X_test, y_cal, y_test = train_test_split(X_rest, y_rest, train_size=0.5, random_state=0)
|
|
16
|
+
|
|
17
|
+
# 1. Train any scoring classifier on the proper training set.
|
|
18
|
+
clf = LinearSVC().fit(X_train, y_train)
|
|
19
|
+
|
|
20
|
+
# 2. Fit the IVAP on the classifier's scores for a held-out calibration set.
|
|
21
|
+
ivap = IVAP().fit(clf.decision_function(X_cal), y_cal)
|
|
22
|
+
|
|
23
|
+
# 3. Predict: a pair (p0, p1) per test example, or a single merged probability.
|
|
24
|
+
test_scores = clf.decision_function(X_test)
|
|
25
|
+
p0, p1 = ivap.predict_p0_p1(test_scores)
|
|
26
|
+
p = ivap.predict_proba(test_scores)
|
|
27
|
+
|
|
28
|
+
print("first five test examples:")
|
|
29
|
+
for s, a, b, q, label in zip(test_scores[:5], p0, p1, p, y_test, strict=False):
|
|
30
|
+
print(f" score {s:+.2f} p0 {a:.3f} p1 {b:.3f} p {q:.3f} true label {label}")
|
|
31
|
+
|
|
32
|
+
print(f"\nmean width p1 - p0: {np.mean(p1 - p0):.4f}")
|
|
33
|
+
print(f"Brier score: {np.mean((p - y_test) ** 2):.4f}")
|
|
34
|
+
print(f"mean predicted prob.: {p.mean():.4f} observed frequency: {y_test.mean():.4f}")
|