Skip to main content

Certified selective classification on top of any classifier. Your model shouldn't be forced to move.

Project description

zugzwang

ci

Certified selective classification on top of any classifier. Your model shouldn't be forced to move.

In chess, zugzwang is the obligation to move when every move worsens your position. A standard classifier lives in permanent zugzwang: forced to predict even when every prediction is bad. zugzwang gives your model the move the rules deny it — abstention — with statistical risk guarantees. It does not train models: it wraps the one you already have.

from zugzwang import SelectiveClassifier

clf = SelectiveClassifier(LGBMClassifier())              # any predict_proba
clf.fit(X_train, y_train)                                # standard sklearn fit
clf.certify(X_select, y_select, risk=0.02, delta=0.05)   # risk <= 2%, conf. 95%
pred = clf.predict(X)                                    # labels or ABSTAIN (-1)

No wrapper needed (neural nets, LLM logprobs, legacy systems):

from zugzwang.core import certify_threshold

thr = certify_threshold(scores_select, errors_select, risk=0.02, delta=0.05,
                        method="sgr")                    # or "crc"
accept = scores_test >= thr.threshold

The falsifiable contract — every verdict published, win or lose

The product was benchmarked against a contract frozen before the first run: 15 datasets (12 from the preceding research trilogy + 3 external OpenML-CC18), 5 seeds, r* ∈ {0.01, 0.02, 0.05, 0.10}, paired Wilcoxon p<0.05 pre-declared. Full operationalization, amendment trail and losing verdicts included: benchmarks/reports/.

Criterion Verdict Key numbers
B1 validity (blocking) PASS sgr 9/217 violations (16 allowed); crc 15/294; conformal 21/295
B2 floor vs artisanal GRAY Pareto-undominated: +13.7 pp coverage vs the risk-respecting recipe (p=8e-34); the coverage-matching recipe violates risk 38% of the time
B3 ceiling vs MAPIE REFUTED MAPIE's sets certify more median coverage; we document when to use MAPIE
B4 budget refund REFUTED claim retired; diagnostic: the calibration tax is ~nil — select-split size is what matters

An exploratory follow-up on non-frozen data (X1) decomposed the B3 gap: the method gap vs MAPIE is exactly zero (identical LAC machinery, 300 paired runs) — the whole gap is the price of the high-probability flavor. What that price buys: at the same r*, marginal set readings exceeded the target selective risk on 62% of eval runs; sgr on 3.8%. Score choice and the hybrid budget split moved certified coverage ≤ 0.2 pp: the defaults stand.

With and without, in one picture

XGBoost with and without zugzwang on satimage

XGBoost on satimage (5 seeds, exploratory protocol — the frozen test stays untouched): alone, the model is forced to answer everything at ~9–10% error (black stars). Wrapped, it slides down the risk–coverage frontier and each zugzwang point sits at its certified target (dashed line of its color). The MAPIE selective reading answers a bit more but lands where the marginal guarantee lands it — sometimes past the target. Reproduce: python benchmarks/fig_demo.py satimage.

Three risk notions, one promise

All defaults carry the same high-probability flavor — with confidence ≥ 1−δ over the select sampling, the certified quantity is controlled — and spend at most half the stated error budget (structural safety margin: the stated confidence is a floor, never an estimate).

Method Certified quantity (confidence ≥ 1−δ) Machinery
sgr (default) P(error | accepted) ≤ r* Exact binomial bound, hybrid search (Geifman & El-Yaniv 2017)
crc P(error AND accepted) ≤ r* RCPS (Bates et al. 2021)
conformal set coverage ≥ 1−r* PAC / training-conditional (Vovk 2012; Park et al. 2020)

In-expectation / marginal variants (classic CRC, classic split conformal) remain available as explicit opt-in via delta=None, labeled verbatim in the certificate.

The honest data protocol

Train with your full budget; reserve a small labeled select split only to certify. Our own benchmark refuted the stronger version of this claim (B4): what actually matters is the select-split size — certifying power scales with it. UncertifiableRiskError.min_certifiable_risk tells you the floor your data can back, and min_select_size(risk, delta, p_err) plans the budget before you reserve anything:

from zugzwang import min_select_size

min_select_size(risk=0.02, delta=0.05)              # 183 — absolute floor
min_select_size(risk=0.02, delta=0.05, p_err=0.01)  # 851 — realistic budget

Loud, early errors

  • Unreachable risk → UncertifiableRiskError reporting the minimum certifiable risk for your data. Never silent thresholds.
  • Select rows duplicating training rows → contamination error before any certificate is issued.

What zugzwang is NOT

  • It does not train engines (LightGBM/XGBoost are optional extras, never dependencies).
  • It does not learn uncertainty scores — nothing learned on top of the model's own confidence paid, measured three ways in the trilogy behind this product. Bring a custom score as a callable if you want.
  • It does not detect distribution drift. The production monitor (v1.0) watches observed coverage/risk against the certificate — measurable quantities — and alarms on deviation, with no epistemic claims.

Install

pip install zugzwang          # coming with v0.1.0

Core dependencies: numpy, scipy, scikit-learn. Docs: quickstart, tutorials (sklearn / LightGBM / raw scores), theory per guarantee, and the benchmark pages — see docs/ (GitHub Pages once public).

Phases

Phase Deliverable Release Status
F0 Packaging, CI, frozen benchmark data
F1 Certified core + SelectiveClassifier + contract B1–B4 run and published v0.1 → PyPI
F2 Opt-in calibration + self-contained HTML report v0.2
F3 DecisionGate + monitoring v1.0

The companion book (Spanish): book/libro.pdf.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zugzwang-1.1.0.tar.gz (5.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zugzwang-1.1.0-py3-none-any.whl (25.8 kB view details)

Uploaded Python 3

File details

Details for the file zugzwang-1.1.0.tar.gz.

File metadata

  • Download URL: zugzwang-1.1.0.tar.gz
  • Upload date:
  • Size: 5.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for zugzwang-1.1.0.tar.gz
Algorithm Hash digest
SHA256 5cc0bec2408d92c07963b4b521c86150c57639d2eb16b84d19e2c957c6865ee5
MD5 49281c775038bc6b33aff56a97e1d14f
BLAKE2b-256 7603fc8abea32caa075249e369b80afe920df7dbc1339b545e2033b3b34cc2bd

See more details on using hashes here.

File details

Details for the file zugzwang-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: zugzwang-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 25.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for zugzwang-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ddf6ad3a7915246635bf2d7232dc5dbd341cf80ba09e6691b3a21a7c142c62e1
MD5 e217d39abb03fa87db1ee135fc427166
BLAKE2b-256 c0cf4937c9b744ca5faa42d4457497adb304254b57650255a11f89eb59169d21

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page