scorequant 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.
- scorequant-0.1.0/LICENSE +21 -0
- scorequant-0.1.0/PKG-INFO +394 -0
- scorequant-0.1.0/README.md +359 -0
- scorequant-0.1.0/pyproject.toml +125 -0
- scorequant-0.1.0/pyproject.toml.orig +106 -0
- scorequant-0.1.0/src/scorequant/__init__.py +139 -0
- scorequant-0.1.0/src/scorequant/_binstats.py +88 -0
- scorequant-0.1.0/src/scorequant/_chunking.py +34 -0
- scorequant-0.1.0/src/scorequant/_execution.py +414 -0
- scorequant-0.1.0/src/scorequant/_json.py +55 -0
- scorequant-0.1.0/src/scorequant/_typing.py +9 -0
- scorequant-0.1.0/src/scorequant/_validation.py +144 -0
- scorequant-0.1.0/src/scorequant/api.py +775 -0
- scorequant-0.1.0/src/scorequant/artifact.py +452 -0
- scorequant-0.1.0/src/scorequant/certify.py +405 -0
- scorequant-0.1.0/src/scorequant/components.py +354 -0
- scorequant-0.1.0/src/scorequant/config.py +430 -0
- scorequant-0.1.0/src/scorequant/criteria.py +130 -0
- scorequant-0.1.0/src/scorequant/information.py +673 -0
- scorequant-0.1.0/src/scorequant/partition.py +1786 -0
- scorequant-0.1.0/src/scorequant/providers.py +465 -0
- scorequant-0.1.0/src/scorequant/py.typed +0 -0
- scorequant-0.1.0/src/scorequant/quantizers.py +57 -0
- scorequant-0.1.0/src/scorequant/ratios.py +347 -0
- scorequant-0.1.0/src/scorequant/reports.py +427 -0
- scorequant-0.1.0/src/scorequant/result.py +466 -0
- scorequant-0.1.0/src/scorequant/solvers/__init__.py +1 -0
- scorequant-0.1.0/src/scorequant/solvers/common.py +72 -0
- scorequant-0.1.0/src/scorequant/solvers/kmeans.py +234 -0
- scorequant-0.1.0/src/scorequant/solvers/scalar.py +142 -0
- scorequant-0.1.0/src/scorequant/solvers/soft.py +374 -0
- scorequant-0.1.0/src/scorequant/sources.py +362 -0
- scorequant-0.1.0/src/scorequant/transforms.py +154 -0
- scorequant-0.1.0/src/scorequant/visualization.py +257 -0
scorequant-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitaly Vorobyev
|
|
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,394 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scorequant
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Information-preserving binning for statistical inference
|
|
5
|
+
Keywords: binning,fisher-information,jax,statistics
|
|
6
|
+
Author: Vitaly Vorobyev
|
|
7
|
+
Author-email: Vitaly Vorobyev <vit.vorobiev@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Requires-Dist: jax>=0.7 ; sys_platform != 'emscripten'
|
|
17
|
+
Requires-Dist: numpy>=2.0
|
|
18
|
+
Requires-Dist: optax>=0.2 ; sys_platform != 'emscripten'
|
|
19
|
+
Requires-Dist: ipykernel>=6.29 ; extra == 'examples'
|
|
20
|
+
Requires-Dist: matplotlib>=3.10 ; extra == 'examples'
|
|
21
|
+
Requires-Dist: nbclient>=0.10 ; extra == 'examples'
|
|
22
|
+
Requires-Dist: nbformat>=5.10 ; extra == 'examples'
|
|
23
|
+
Requires-Dist: pandas>=2.3,<4 ; extra == 'examples'
|
|
24
|
+
Requires-Dist: scikit-learn>=1.9,<2 ; extra == 'examples'
|
|
25
|
+
Requires-Dist: matplotlib>=3.10 ; extra == 'viz'
|
|
26
|
+
Requires-Python: >=3.12
|
|
27
|
+
Project-URL: Homepage, https://vitalyvorobyev.github.io/scorequant/
|
|
28
|
+
Project-URL: Documentation, https://vitalyvorobyev.github.io/scorequant/
|
|
29
|
+
Project-URL: Changelog, https://github.com/VitalyVorobyev/scorequant/blob/main/CHANGELOG.md
|
|
30
|
+
Project-URL: Issues, https://github.com/VitalyVorobyev/scorequant/issues
|
|
31
|
+
Project-URL: Repository, https://github.com/VitalyVorobyev/scorequant
|
|
32
|
+
Provides-Extra: examples
|
|
33
|
+
Provides-Extra: viz
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# ScoreQuant
|
|
37
|
+
|
|
38
|
+
**Information-optimal hard compression for parametric inference.**
|
|
39
|
+
|
|
40
|
+
ScoreQuant replaces continuous event-level information with a small number of hard bins while
|
|
41
|
+
preserving as much Fisher information as possible for the parameters you actually want to measure.
|
|
42
|
+
|
|
43
|
+
[](https://github.com/VitalyVorobyev/scorequant/actions/workflows/ci.yml)
|
|
44
|
+
[](https://vitalyvorobyev.github.io/scorequant/)
|
|
45
|
+
|
|
46
|
+
Many statistical workflows eventually reduce rich observations to counts in a few named categories:
|
|
47
|
+
template and component fits, gated cell populations, binned likelihoods, trigger tiers, tables a
|
|
48
|
+
collaborator can reproduce by hand. That compression is usually chosen for convenience — equal
|
|
49
|
+
width, equal population, a threshold on one classifier output — even though the goal is parameter
|
|
50
|
+
estimation, and it silently discards sensitivity the experiment already paid for.
|
|
51
|
+
|
|
52
|
+
ScoreQuant chooses the categories from the inference problem instead. For a regular parametric
|
|
53
|
+
model $X\sim p(x\mid\theta)$, the local information an event carries at a reference point
|
|
54
|
+
$\theta_0$ is summarized by its **score**
|
|
55
|
+
|
|
56
|
+
$$
|
|
57
|
+
s(x) = \nabla_\theta \log p(x\mid\theta)\big|_{\theta_0}.
|
|
58
|
+
$$
|
|
59
|
+
|
|
60
|
+
ScoreQuant partitions score space into a few hard cells and optimizes the Fisher information
|
|
61
|
+
retained by their counts. Binning is still lossy; the point is to make the loss a quantity you
|
|
62
|
+
choose, measure and report rather than one you inherit from the axis ticks.
|
|
63
|
+
|
|
64
|
+
The model does **not** have to be a mixture. Mixture fractions, Gaussian means, calibration
|
|
65
|
+
parameters, cross sections, rates, shape and nuisance parameters all enter through the same score
|
|
66
|
+
representation.
|
|
67
|
+
|
|
68
|
+
## Three independent choices
|
|
69
|
+
|
|
70
|
+
Using ScoreQuant means answering three separate questions:
|
|
71
|
+
|
|
72
|
+
1. **What are you optimizing?** Labels for one finite sample, or a reusable rule for future events?
|
|
73
|
+
2. **How do you obtain the score?** Already available, computed from an explicit model, or
|
|
74
|
+
estimated through density ratios?
|
|
75
|
+
3. **Which parameter information matters?** All of it through $D$-optimality, or only declared
|
|
76
|
+
parameters of interest after profiling nuisance parameters through $D_s$?
|
|
77
|
+
|
|
78
|
+
The axes are independent.
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
SCORE ACCESS
|
|
82
|
+
┌──────────────────────────────┐
|
|
83
|
+
│ precomputed scores │
|
|
84
|
+
TASK │ exact model / score oracle │ OBJECTIVE
|
|
85
|
+
│ density ratios / classifier │
|
|
86
|
+
└──────────────────────────────┘
|
|
87
|
+
|
|
88
|
+
sample partition ─────────────────────────────── D
|
|
89
|
+
space quantizer ─────────────────────────────── Ds
|
|
90
|
+
normalized trace / baselines
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The optimizer ultimately sees weighted score vectors. Everything before that is model access;
|
|
94
|
+
everything after it is hard compression.
|
|
95
|
+
|
|
96
|
+
## 1. Choose the task
|
|
97
|
+
|
|
98
|
+
### A. Sample partitioning — *the best labels for the sample I have*
|
|
99
|
+
|
|
100
|
+
<!-- snippet: skip -->
|
|
101
|
+
```python
|
|
102
|
+
partition = sq.optimize_partition(scores, n_bins=8, criterion=sq.DOptimality())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This is **transductive**. The decision variables are the labels of the supplied rows, and the
|
|
106
|
+
result is a `PartitionResult`. A labeling of one finite table does not by itself say what should
|
|
107
|
+
happen to an event you have not seen, so `PartitionResult` deliberately has **no** generic predict
|
|
108
|
+
method.
|
|
109
|
+
|
|
110
|
+
There is one sanctioned crossing, and it is a theorem rather than a convenience: an
|
|
111
|
+
exchange-stable, nonsingular $D$-optimal partition already *is* a strict self-consistent Voronoi
|
|
112
|
+
partition in the $I_B^{-1}$-Mahalanobis metric. Such a result compiles into exactly that rule, and
|
|
113
|
+
refuses when it is unstable or degenerate.
|
|
114
|
+
|
|
115
|
+
<!-- snippet: skip -->
|
|
116
|
+
```python
|
|
117
|
+
rule = partition.compile_quantizer()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
An arbitrary finite $D_s$-optimal partition should **not** be assumed to define a reusable Voronoi
|
|
121
|
+
quantizer.
|
|
122
|
+
|
|
123
|
+
### B. Space quantization — *the rule I apply to future events*
|
|
124
|
+
|
|
125
|
+
<!-- snippet: skip -->
|
|
126
|
+
```python
|
|
127
|
+
fit = sq.fit_quantizer(source, provider=provider, n_bins=8, criterion=sq.DOptimality())
|
|
128
|
+
bins = fit.quantizer.predict_scores(provider.score(future_events))
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This is the **inductive** problem. Use it when the bins themselves are the deliverable: a histogram
|
|
132
|
+
definition, an event categorizer, a gating rule, or any analysis that must process observations
|
|
133
|
+
that were not present during optimization.
|
|
134
|
+
|
|
135
|
+
## 2. Choose how the score is obtained
|
|
136
|
+
|
|
137
|
+
The common interface is $x \mapsto s(x)$, and there are three routes to it.
|
|
138
|
+
|
|
139
|
+
| Score access | You already have | Interface |
|
|
140
|
+
| --- | --- | --- |
|
|
141
|
+
| **Precomputed scores** | score vectors $s_i$ | `ScoreSample` |
|
|
142
|
+
| **Exact model / score oracle** | a likelihood, component model, analytic or autodiff score | `ScoreFunction`, `LinearComponentScore` |
|
|
143
|
+
| **Density ratios** | analytic ratios, a direct ratio estimator, a calibrated classifier | `DensityRatioScore`, `CentralLogRatioScore` |
|
|
144
|
+
|
|
145
|
+
These are alternative upstream routes to the *same* downstream optimization problem. Absolute
|
|
146
|
+
densities are never required: the score is the gradient of a log density *ratio*, so a ratio oracle
|
|
147
|
+
is enough — but it must be a calibrated one. A ranking score or an arbitrary monotonic classifier
|
|
148
|
+
output is not, since the construction needs ratios rather than event ordering. See
|
|
149
|
+
[Three doors](https://vitalyvorobyev.github.io/scorequant/three-doors/) for the derivation.
|
|
150
|
+
|
|
151
|
+
### Sources and providers are different things
|
|
152
|
+
|
|
153
|
+
Fitting a reusable rule needs both a **reference measure** — which observations occur, with what
|
|
154
|
+
weight — and a **score map**.
|
|
155
|
+
|
|
156
|
+
| Source | Meaning |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `ScoreSample(scores, weights)` | a finite weighted sample already in score space |
|
|
159
|
+
| `ObservationSample(X, weights)` | finite weighted observations |
|
|
160
|
+
| `IntegrationSource(bounds, density=...)` | deterministic quadrature over a bounded model |
|
|
161
|
+
|
|
162
|
+
Observation-space sources require a provider; a `ScoreSample` rejects one. **Model density ratios**
|
|
163
|
+
construct scores and enter through providers; **importance ratios** modify the reference measure
|
|
164
|
+
and enter as source weights. The two never share an argument.
|
|
165
|
+
|
|
166
|
+
`ScoreProvider` is a public protocol, so an external estimator is a provider without being wrapped:
|
|
167
|
+
|
|
168
|
+
<!-- snippet: skip -->
|
|
169
|
+
```python
|
|
170
|
+
class MyExternalScore:
|
|
171
|
+
provenance = sq.ScoreProvenance(kind="estimated_ratio")
|
|
172
|
+
|
|
173
|
+
def score(self, observations):
|
|
174
|
+
return my_package.evaluate(observations)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 3. Choose what information to preserve
|
|
178
|
+
|
|
179
|
+
For a hard rule with cells $b=1,\ldots,K$, let $W_b=P(q(S)=b)$ and $\mu_b=E[S\mid q(S)=b]$. The
|
|
180
|
+
information retained by the bin label is $I_B=\sum_b W_b\,\mu_b\mu_b^\top$.
|
|
181
|
+
|
|
182
|
+
- **`DOptimality()`** maximizes $\log\det I_B$, treating all score directions symmetrically. Use it
|
|
183
|
+
when the complete parameter vector matters.
|
|
184
|
+
- **`ProfiledDOptimality(interest=...)`** maximizes the log determinant of the Schur complement
|
|
185
|
+
after profiling nuisance parameters. This is **not** a generally better $D$; it answers a
|
|
186
|
+
different question, and can deliberately sacrifice large amounts of nuisance information to
|
|
187
|
+
answer it.
|
|
188
|
+
- **`NormalizedTrace()`** maximizes the Fisher-normalized retained trace. After whitening this is
|
|
189
|
+
weighted $k$-means — an interpretable alternative and a baseline.
|
|
190
|
+
|
|
191
|
+
Parameters of interest can be named rather than indexed, which matters as soon as a model has more
|
|
192
|
+
than a handful of components:
|
|
193
|
+
|
|
194
|
+
<!-- snippet: skip -->
|
|
195
|
+
```python
|
|
196
|
+
sample = sq.ScoreSample(
|
|
197
|
+
scores, weights, schema=sq.ScoreSchema(("T", "B", "monocyte", "mast", "HSPC"))
|
|
198
|
+
)
|
|
199
|
+
criterion = sq.ProfiledDOptimality(interest=("HSPC",))
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Reports then say `interest: HSPC` and `nuisance: T, B, monocyte, mast`.
|
|
203
|
+
|
|
204
|
+
Whichever route supplies the scores, `optimize_partition` always takes score rows — so routes 2
|
|
205
|
+
and 3 reach it through an explicit `provider.score(X)`. The observation-to-score transformation
|
|
206
|
+
never hides inside a fitting call, and prediction never silently recomputes scores.
|
|
207
|
+
|
|
208
|
+
## Quick start
|
|
209
|
+
|
|
210
|
+
A Gaussian location model $x\sim\mathcal N(\mu, I_2)$ has $s(x)=x-\mu_0$, so at $\mu_0=0$ the
|
|
211
|
+
observations *are* the score vectors.
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
import numpy as np
|
|
215
|
+
import scorequant as sq
|
|
216
|
+
|
|
217
|
+
rng = np.random.default_rng(7)
|
|
218
|
+
sample = sq.ScoreSample(
|
|
219
|
+
rng.normal(size=(4_000, 2)),
|
|
220
|
+
schema=sq.ScoreSchema(("mu_x", "mu_y")),
|
|
221
|
+
provenance=sq.ScoreProvenance(kind="exact", reference_point=(0.0, 0.0)),
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Partition this finite sample:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
partition = sq.optimize_partition(
|
|
229
|
+
sample, n_bins=6, criterion=sq.DOptimality(), config=sq.DExchangeConfig(seed=7)
|
|
230
|
+
)
|
|
231
|
+
assert partition.exchange_stable
|
|
232
|
+
print(round(float(partition.train_report.geometric_mean_retention), 3))
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Six bins retain a $D$-efficiency of about 0.75 — the geometric mean of the retained-information
|
|
236
|
+
eigenvalues. `partition.labels` belongs to these rows and nowhere else.
|
|
237
|
+
|
|
238
|
+
Fit a reusable rule instead, and deploy it:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
fit = sq.fit_quantizer(
|
|
242
|
+
sample, n_bins=6, criterion=sq.DOptimality(), config=sq.DExchangeConfig(seed=7)
|
|
243
|
+
)
|
|
244
|
+
assert fit.information_kind == "exact_fisher"
|
|
245
|
+
|
|
246
|
+
rule = fit.quantizer
|
|
247
|
+
future_bins = rule.predict_scores(rng.normal(loc=0.25, size=(1_000, 2)))
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`rule` is the deployable object: a transform, centers and a metric, with no training data attached.
|
|
251
|
+
It saves to a versioned, non-pickle artifact that loads and predicts in a process with no JAX
|
|
252
|
+
installed — fit on the accelerated backend, deploy anywhere.
|
|
253
|
+
|
|
254
|
+
<!-- snippet: skip -->
|
|
255
|
+
```python
|
|
256
|
+
rule.save("gaussian-6bins.sqz")
|
|
257
|
+
rule = sq.Quantizer.load("gaussian-6bins.sqz")
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Real-data showcase: FlowCyt
|
|
261
|
+
|
|
262
|
+
The [FlowCyt study](https://vitalyvorobyev.github.io/scorequant/usecases/flowcyt/) is the main
|
|
263
|
+
end-to-end real-data example. Flow cytometry produces individual cells described by twelve marker
|
|
264
|
+
measurements, while the scientific result is a vector of population fractions. The study uses all
|
|
265
|
+
30 patients: 20 reference, 10 frozen held-out, 600,000 sampled real cells drawn from 21,254,866
|
|
266
|
+
upstream events.
|
|
267
|
+
|
|
268
|
+
```text
|
|
269
|
+
12-dimensional cell measurements → calibrated classifier → density ratios
|
|
270
|
+
→ 5-dimensional mixture score → ScoreQuant → 8 frozen hard bins
|
|
271
|
+
→ integer bin counts → downstream mixture fit → cell-population fractions
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
At the eight-bin operating point the learned quantizer retains **98.5%** of the supplied-score
|
|
275
|
+
surrogate information and reaches a **0.00193** macro RMSE on the ten held-out patients; the
|
|
276
|
+
selected unbinned classifier-ratio baseline reaches 0.00173.
|
|
277
|
+
|
|
278
|
+
The study is useful beyond cytometry because it draws the boundaries explicitly: *the classifier is
|
|
279
|
+
not ScoreQuant, and the downstream mixture fitter is not ScoreQuant.* ScoreQuant is the
|
|
280
|
+
information-preserving hard-compression layer between them.
|
|
281
|
+
|
|
282
|
+
It also contains a real profiled-$D_s$ experiment, treating one cell fraction as the parameter of
|
|
283
|
+
interest. Interestingly, $D_s$ does **not** materially improve the final measurement there: plain
|
|
284
|
+
$D$ already lies close to a certified efficient-score ceiling. That is a useful negative result,
|
|
285
|
+
and it illustrates why $D_s$ is a different inferential objective rather than an automatically
|
|
286
|
+
superior one.
|
|
287
|
+
|
|
288
|
+
## Solvers
|
|
289
|
+
|
|
290
|
+
Unsupported task/criterion combinations are rejected before any optimization runs.
|
|
291
|
+
|
|
292
|
+
| Configuration | `optimize_partition` | `fit_quantizer` | Contract |
|
|
293
|
+
| --- | --- | --- | --- |
|
|
294
|
+
| `DExchangeConfig` | `DOptimality`, `ProfiledDOptimality` | `DOptimality` | Exact positive-gain relocations; monotone objective; terminates exchange-stable |
|
|
295
|
+
| `MahalanobisLloydConfig` | `DOptimality`, `ProfiledDOptimality` | `DOptimality` | A batch is adopted only if the exactly rebuilt objective improves; optional exact-exchange guard |
|
|
296
|
+
| `SoftVoronoiConfig` | — | `DOptimality`, `ProfiledDOptimality` | Differentiable soft optimization then hardening, with the hardening gap reported |
|
|
297
|
+
| `KMeansConfig` | — | `NormalizedTrace` | Weighted $k$-means in whitened score space |
|
|
298
|
+
| `ScalarDPConfig` | — | `DOptimality` | The exact global interval solution for rank-one score space |
|
|
299
|
+
|
|
300
|
+
The strong finite-sample bridge *exchange stable $\Rightarrow I_B^{-1}$-Voronoi* is specific to
|
|
301
|
+
full $D$-optimality and should not be assumed for profiled $D_s$.
|
|
302
|
+
|
|
303
|
+
## Certificates and diagnostics
|
|
304
|
+
|
|
305
|
+
Certificates are explicit operations; none runs silently during fitting.
|
|
306
|
+
`exchange_stability_report` scans any supplied labeling exactly and reports the best remaining
|
|
307
|
+
gain; `certify_partition` gives a branch-and-bound global certificate for full $D$, or an explicit
|
|
308
|
+
outstanding gap when its budget runs out; `efficient_score_bound` gives a certified ceiling on
|
|
309
|
+
profiled information for one parameter of interest; and `PartitionResult.geometry` measures the
|
|
310
|
+
Voronoi violation a result leaves unclaimed. Validation data is diagnostic only — it never touches
|
|
311
|
+
gradients, stopping, or checkpoint selection.
|
|
312
|
+
|
|
313
|
+
## Score provenance
|
|
314
|
+
|
|
315
|
+
There is a difference between optimizing supplied vectors exactly and claiming those vectors *are*
|
|
316
|
+
the exact statistical score. ScoreQuant records which it has: exact or autodiff provenance lets a
|
|
317
|
+
result report `information_kind == "exact_fisher"`, while classifier- or ratio-derived scores
|
|
318
|
+
produce `"supplied_score_surrogate"`. The optimization can be exact even when the vectors are
|
|
319
|
+
estimates, and the distinction matters when reading a retained-information number.
|
|
320
|
+
|
|
321
|
+
## Install
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
uv add scorequant
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
or, outside a `uv` project, `pip install scorequant`. To work on a checkout instead:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
git clone https://github.com/VitalyVorobyev/scorequant.git
|
|
331
|
+
cd scorequant
|
|
332
|
+
uv sync --all-extras --all-groups
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Python 3.12 or newer; JAX and Optax are the required numerical dependencies. ScoreQuant never sets
|
|
336
|
+
global JAX configuration at import, so 64-bit precision is your application's call
|
|
337
|
+
(`JAX_ENABLE_X64=1`). NumPy is a supported portable runtime, which is what lets a saved rule predict
|
|
338
|
+
where JAX is absent.
|
|
339
|
+
|
|
340
|
+
## Where ScoreQuant sits
|
|
341
|
+
|
|
342
|
+
```text
|
|
343
|
+
data → likelihood, component model, ratio estimator or classifier → SCORE
|
|
344
|
+
→ [ ScoreQuant: score → hard label ] → counts → template fit / profile likelihood / report
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
ScoreQuant does not train the classifier and does not perform the final parameter fit. It answers
|
|
348
|
+
one question well:
|
|
349
|
+
|
|
350
|
+
> Given a limited number of hard categories, how should they be chosen so the downstream inference
|
|
351
|
+
> loses as little relevant information as possible?
|
|
352
|
+
|
|
353
|
+
## How it relates to prior work
|
|
354
|
+
|
|
355
|
+
Choosing a quantizer to preserve Fisher information is established territory, and ScoreQuant does
|
|
356
|
+
not claim to have invented it. Venkitasubramaniam, Tong and Swami introduced score-function
|
|
357
|
+
quantizers for distributed estimation ([CISS 2006](https://doi.org/10.1109/CISS.2006.286494));
|
|
358
|
+
Farias and Brossier developed the scalar high-resolution theory of Fisher-optimal quantization
|
|
359
|
+
([arXiv:1310.6945](https://arxiv.org/abs/1310.6945)); Barnes, Han and Özgür characterized quantized
|
|
360
|
+
Fisher information geometrically through conditional score means
|
|
361
|
+
([Allerton 2018](https://doi.org/10.1109/ALLERTON.2018.8635899)); Dülek proved convex-polytope
|
|
362
|
+
optimality for sufficient-statistic quantizers under a trace criterion
|
|
363
|
+
([IEEE TPAMI 2023](https://doi.org/10.1109/TPAMI.2022.3172282)). Determinant criteria for
|
|
364
|
+
partitions date to Friedman and Rubin (1967) and Scott and Symons (1971), and D-optimality itself
|
|
365
|
+
to Kiefer and Wolfowitz (1960). Inference-aware categorization is an active line of its own —
|
|
366
|
+
INFERNO, ThickBrick, and the recent GATO/BOBR binning optimizers.
|
|
367
|
+
|
|
368
|
+
What ScoreQuant contributes is narrower and concrete: the exact finite-sample geometry of
|
|
369
|
+
*full-matrix* D-optimal hard quantization. Relocating one weighted row is a rank-two update whose
|
|
370
|
+
log-determinant gain is available in closed form, which makes the exchange monotone and its
|
|
371
|
+
termination a stability certificate; exchange stability implies a strict self-consistent
|
|
372
|
+
$I_B^{-1}$-Mahalanobis-Voronoi rule, which is what licenses compiling a finite partition into a
|
|
373
|
+
reusable one; profiled $D_s$ comes with certified efficient-score upper bounds; and small instances
|
|
374
|
+
can be closed with branch-and-bound global certificates. See
|
|
375
|
+
[Related work](https://vitalyvorobyev.github.io/scorequant/related-work/) for the full map,
|
|
376
|
+
including which pipeline stage each comparable package occupies.
|
|
377
|
+
|
|
378
|
+
## Documentation
|
|
379
|
+
|
|
380
|
+
[Why ScoreQuant](https://vitalyvorobyev.github.io/scorequant/motivation/) ·
|
|
381
|
+
[Method overview](https://vitalyvorobyev.github.io/scorequant/method/) ·
|
|
382
|
+
[Three doors](https://vitalyvorobyev.github.io/scorequant/three-doors/) ·
|
|
383
|
+
[Choosing your workflow](https://vitalyvorobyev.github.io/scorequant/user-workflow/) ·
|
|
384
|
+
[The book](https://vitalyvorobyev.github.io/scorequant/book/) ·
|
|
385
|
+
[Examples](https://vitalyvorobyev.github.io/scorequant/examples/) ·
|
|
386
|
+
[API guide](https://vitalyvorobyev.github.io/scorequant/api/) and
|
|
387
|
+
[reference](https://vitalyvorobyev.github.io/scorequant/reference/) ·
|
|
388
|
+
[FlowCyt study](https://vitalyvorobyev.github.io/scorequant/usecases/flowcyt/) ·
|
|
389
|
+
[Related work](https://vitalyvorobyev.github.io/scorequant/related-work/)
|
|
390
|
+
|
|
391
|
+
The book develops the statistical theory independently of this package's API; the FlowCyt study is
|
|
392
|
+
a reproducible end-to-end evaluation on a frozen patient split.
|
|
393
|
+
|
|
394
|
+
ScoreQuant is available under the [MIT license](LICENSE).
|