anytimemonitor 0.9.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.
- anytimemonitor-0.9.0/LICENSE +21 -0
- anytimemonitor-0.9.0/PKG-INFO +269 -0
- anytimemonitor-0.9.0/README.md +236 -0
- anytimemonitor-0.9.0/anytimemonitor/__init__.py +21 -0
- anytimemonitor-0.9.0/anytimemonitor/avci.py +387 -0
- anytimemonitor-0.9.0/anytimemonitor/controller.py +1604 -0
- anytimemonitor-0.9.0/anytimemonitor/demo.py +93 -0
- anytimemonitor-0.9.0/anytimemonitor/ui/__init__.py +6 -0
- anytimemonitor-0.9.0/anytimemonitor/ui/app.py +1388 -0
- anytimemonitor-0.9.0/anytimemonitor/ui/plotting.py +299 -0
- anytimemonitor-0.9.0/anytimemonitor/ui/state.py +552 -0
- anytimemonitor-0.9.0/anytimemonitor/variance.py +316 -0
- anytimemonitor-0.9.0/anytimemonitor.egg-info/PKG-INFO +269 -0
- anytimemonitor-0.9.0/anytimemonitor.egg-info/SOURCES.txt +17 -0
- anytimemonitor-0.9.0/anytimemonitor.egg-info/dependency_links.txt +1 -0
- anytimemonitor-0.9.0/anytimemonitor.egg-info/requires.txt +10 -0
- anytimemonitor-0.9.0/anytimemonitor.egg-info/top_level.txt +1 -0
- anytimemonitor-0.9.0/pyproject.toml +46 -0
- anytimemonitor-0.9.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonathan Chevelu and Nelly Barbot (IRISA, Université de Rennes)
|
|
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,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: anytimemonitor
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Adaptive listening-test controller for TTS-MOS evaluation: anytime-valid confidence sequences, per-pair verdicts, two-lever (items/listeners) budget advice
|
|
5
|
+
Author: Jonathan Chevelu, Nelly Barbot
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://expression.gitlabpages.inria.fr/replication/2026-doing-mos-well/
|
|
8
|
+
Project-URL: Source, https://github.com/jchevelu/AnytimeMonitor
|
|
9
|
+
Project-URL: Repository, https://gitlab.inria.fr/expression/tools/anytimemonitor
|
|
10
|
+
Project-URL: Archive, https://doi.org/10.5281/zenodo.21326813
|
|
11
|
+
Keywords: speech-synthesis,MOS,listening-test,anytime-valid,confidence-sequence,evaluation
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy>=1.26
|
|
25
|
+
Requires-Dist: pandas>=2.1
|
|
26
|
+
Requires-Dist: scipy>=1.11
|
|
27
|
+
Requires-Dist: matplotlib>=3.8
|
|
28
|
+
Provides-Extra: ui
|
|
29
|
+
Requires-Dist: streamlit>=1.50; extra == "ui"
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# AnytimeMonitor — adaptive controller for TTS-MOS listening tests
|
|
35
|
+
|
|
36
|
+
AnytimeMonitor watches a MOS listening test *while it runs* and tells
|
|
37
|
+
the test admin, for every pair of systems, whether the data already
|
|
38
|
+
supports a verdict — and if not, which lever (more items, or more
|
|
39
|
+
listeners) closes the pair fastest.
|
|
40
|
+
|
|
41
|
+
The statistical core is an **anytime-valid confidence sequence**
|
|
42
|
+
(Howard, Ramdas, McAuliffe & Sekhon, 2021) on per-item paired score
|
|
43
|
+
differences. Anytime validity means the coverage guarantee holds at
|
|
44
|
+
*any* data-dependent stopping time: the admin can look at the verdicts
|
|
45
|
+
as often as they like, stop whenever a pair resolves, and the reported
|
|
46
|
+
confidence interval is still valid. No peeking penalty bookkeeping, no
|
|
47
|
+
pre-registered sample size required.
|
|
48
|
+
|
|
49
|
+
This is a research prototype (v0.9). It accompanies a position paper
|
|
50
|
+
on budget allocation in TTS evaluation (under submission, *Computer
|
|
51
|
+
Speech & Language* Special Issue on the evaluation of speech and
|
|
52
|
+
speech synthesis).
|
|
53
|
+
|
|
54
|
+
## What the admin sees
|
|
55
|
+
|
|
56
|
+
For each pair of systems (A, B), one of six verdicts:
|
|
57
|
+
|
|
58
|
+
| Verdict | Meaning |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `STOP` | The anytime-valid CI excludes 0 (0 ∉ CI): the pair is separated. |
|
|
61
|
+
| `STOP_EQUIV` | Strict TOST equivalence: CI ⊂ ±δ_equiv *and* the half-width Ψ_eff ≤ δ_equiv/2. The pair is statistically equivalent within tolerance. |
|
|
62
|
+
| `CONTINUE +N` | Neither verdict yet. The tool predicts how many more **items** (+N) or **listeners** (+L) would close the pair, and which terminal (STOP or STOP_EQUIV) it is heading for. The headline is the cheaper option. |
|
|
63
|
+
| `WAIT` | The listener×system variance component cannot be estimated yet (fewer than 3 listeners with ≥ 2 paired items). Structural: keep collecting before trusting any verdict. |
|
|
64
|
+
| `CORPUS_LIMIT` | The item budget is exhausted *and* the listener lever can no longer close the pair. Terminal. |
|
|
65
|
+
| `LISTENER_LIMIT` | The listener budget is exhausted *and* the item lever can no longer close the pair. Terminal. |
|
|
66
|
+
|
|
67
|
+
> **Verdict naming.** `STOP` is the separation verdict the companion paper
|
|
68
|
+
> calls `STOP_SEP`. `STOP` is the current public label and will be **renamed to
|
|
69
|
+
> `STOP_SEP` in a future release** (to pair symmetrically with `STOP_EQUIV`);
|
|
70
|
+
> code matching on `"STOP"` should expect that change.
|
|
71
|
+
|
|
72
|
+
Verdicts are recomputed from scratch at every call — the controller is
|
|
73
|
+
queried on demand (a handful of times over a test lifecycle), not in a
|
|
74
|
+
real-time loop. A `STOP` is **not sticky**: if you keep collecting
|
|
75
|
+
past it, a borderline pair can revert to `CONTINUE`. This is honest
|
|
76
|
+
anytime-CI behaviour (the guarantee is on coverage, not on the
|
|
77
|
+
monotonicity of the observed difference); in practice the admin acts
|
|
78
|
+
on the first `STOP`.
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install -e . # from the repository root (pyproject.toml)
|
|
84
|
+
pip install -e ".[ui]" # + Streamlit demo UI
|
|
85
|
+
pip install -e ".[test]" # + pytest; then run `pytest`
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Requires Python ≥ 3.10. Pinned versions used for development are in
|
|
89
|
+
`requirements.txt`.
|
|
90
|
+
|
|
91
|
+
## Quick start
|
|
92
|
+
|
|
93
|
+
### Programmatic
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import pandas as pd
|
|
97
|
+
from anytimemonitor import AnytimeMonitor
|
|
98
|
+
|
|
99
|
+
ctrl = AnytimeMonitor(mde_threshold=0.30) # δ_sep, in MOS units
|
|
100
|
+
|
|
101
|
+
# Feed ratings as they arrive — long format, one row per judgment
|
|
102
|
+
ctrl.add_ratings(pd.DataFrame({
|
|
103
|
+
"listener_id": ..., # who rated
|
|
104
|
+
"item": ..., # which sentence / stimulus
|
|
105
|
+
"system": ..., # which TTS system
|
|
106
|
+
"rating": ..., # the MOS score
|
|
107
|
+
}))
|
|
108
|
+
|
|
109
|
+
print(ctrl.report()) # all pairs
|
|
110
|
+
dec = ctrl.decision("sysA", "sysB") # one pair, structured
|
|
111
|
+
print(dec.verdict, dec.ci_lo, dec.ci_hi, dec.n_more_items)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### CLI demo
|
|
115
|
+
|
|
116
|
+
Feed a ratings CSV to the controller batch by batch and watch the per-pair
|
|
117
|
+
verdicts tighten. With no arguments it runs on a bundled synthetic sample:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
python -m anytimemonitor.demo # bundled sample
|
|
121
|
+
python -m anytimemonitor.demo --input my_ratings.csv # your own long-format CSV
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Streamlit demo UI
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
streamlit run anytimemonitor/ui/app.py
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Upload a ratings CSV (or pick a bundled synthetic example from
|
|
131
|
+
`sample_uploads/`), watch the controller replay the stream
|
|
132
|
+
batch-by-batch, and drag the time-machine slider to see the verdict
|
|
133
|
+
and two-lever advice at any past timestep. See `ui/README.md`.
|
|
134
|
+
|
|
135
|
+
## How it works
|
|
136
|
+
|
|
137
|
+
For a pair (A, B), the controller builds per-item paired differences
|
|
138
|
+
d_j = mean rating of item j under A minus under B, and maintains a
|
|
139
|
+
sub-Gaussian normal-mixture confidence sequence on their mean
|
|
140
|
+
(Howard et al. 2021). Two ingredients adapt this to listening-test
|
|
141
|
+
data, where ratings are clustered by listener as well as by item:
|
|
142
|
+
|
|
143
|
+
- **Bivariate cluster bootstrap SE.** Resampling *items* gives the
|
|
144
|
+
i.i.d.-part SE that the anytime boundary multiplies; resampling
|
|
145
|
+
*listeners* gives the listener-pool floor ("plancher") — the
|
|
146
|
+
precision ceiling that no amount of extra items can break, added in
|
|
147
|
+
quadrature. When the data is too sparse to bootstrap, the
|
|
148
|
+
controller falls back to the confidence sequence's internal running
|
|
149
|
+
variance proxy.
|
|
150
|
+
- **Variance decomposition layer.** Method-of-moments estimates of
|
|
151
|
+
the system×item and residual components (per system, with a
|
|
152
|
+
Half-Cauchy Bayesian regulariser that prevents the
|
|
153
|
+
truncation-at-zero bias of raw MoM), plus a per-pair estimate of
|
|
154
|
+
the listener×system component. These feed the two-lever
|
|
155
|
+
prediction: +N items shrinks the interaction term, +L listeners
|
|
156
|
+
lowers the floor.
|
|
157
|
+
|
|
158
|
+
The price of anytime validity is a modestly wider interval than a
|
|
159
|
+
fixed-n test (a √log log factor), and deliberate conservatism early
|
|
160
|
+
on: below `avci_t_min_ebernstein` items the variance proxy falls back
|
|
161
|
+
to the worst-case Hoeffding bound, so the first batches produce wide
|
|
162
|
+
intervals. The controller is therefore most useful from a few dozen
|
|
163
|
+
rated items per system onward.
|
|
164
|
+
|
|
165
|
+
The internal architecture is documented in
|
|
166
|
+
[`Architecture.md`](Architecture.md) (and
|
|
167
|
+
[`ui/Architecture.md`](ui/Architecture.md) for the demo UI); the deeper design
|
|
168
|
+
notes for the confidence sequence are in [`AVCI_PLAN.md`](AVCI_PLAN.md).
|
|
169
|
+
|
|
170
|
+
## Key parameters
|
|
171
|
+
|
|
172
|
+
All knobs are constructor arguments of `AnytimeMonitor`; the
|
|
173
|
+
defaults are deliberately conservative.
|
|
174
|
+
|
|
175
|
+
| Parameter | Default | Role |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `mde_threshold` (δ_sep) | (required) | The smallest effect worth *resolving* (MOS): seeds `delta_equiv` and the reported MDE. It does **not** gate `STOP` — separation fires on `0 ∉ CI` regardless. |
|
|
178
|
+
| `delta_equiv` | `None` (= `mde_threshold`) | δ_equiv — equivalence margin for `STOP_EQUIV`. Set it below `mde_threshold` to create an indifference zone. |
|
|
179
|
+
| `alpha` | 0.05 | Nominal level of the confidence sequence. |
|
|
180
|
+
| `power` | 0.80 | Only sets the reported-MDE scale factor (`z_{α/2} + z_power`); does not affect stopping. |
|
|
181
|
+
| `n_max_corpus`, `l_max_listeners` | `None` | Item / listener budgets; enable the `*_LIMIT` terminal verdicts. |
|
|
182
|
+
| `avci_n_planned` | `240` (§7 census median budget) | Planned item budget; the controller derives η = 1/√(N_planned) (Howard's a-priori rule). |
|
|
183
|
+
| `avci_eta` | `None` (→ 1/√`avci_n_planned`) | Set the Howard 2021 mixing parameter η by hand instead of via `avci_n_planned` — η is fixed a priori, never tuned to data. **Pass one or the other, not both** (raises otherwise). |
|
|
184
|
+
| `avci_lissys_floor_r0` | `0.36` (measured r_lisSys band frontier) | A-priori guard on the listener floor: bounds it below by √(2·σ²₀/L) with σ²₀ = 1.5·r₀·σ̂²_eval (the ×1.5 is an MC-calibrated validity margin). Protects coverage when a small pool under-estimates σ²_lisSys. `0` disables. |
|
|
185
|
+
| `avci_lissys_floor_sigma2` | `None` | Absolute σ²₀ override for the guard (expert path, no extra margin). **Exclusive with `avci_lissys_floor_r0`** (raises otherwise). The per-pair `Decision.lissys_prior_exceeded` flags when data-driven floor estimates exceed the declared guard (declared margin exhausted; consider raising r₀). |
|
|
186
|
+
| `avci_gamma_alpha_factor` | 1.0 | Multiplier `c` on the listener floor (√c on the plancher); `1.0` assumes σ²_lisSys ≤ σ²_int. Bypassed when the bootstrap plancher is available (the normal path). |
|
|
187
|
+
| `avci_proxy` | `"ebernstein"` | Variance proxy: running empirical variance (`ebernstein`) or worst-case `hoeffding`. |
|
|
188
|
+
| `avci_d_bounds` | `(-4, 4)` | Range of the per-item paired *difference* (not the raw rating scale): ±(rating max − min), so the default spans a rating range of 4. The normal path bootstraps the SE from the data and **ignores this** (it feeds only the sparse-data fallback and a reported diagnostic); widen it for a larger rating range if you drive `AnytimeCI` directly on sparse data. |
|
|
189
|
+
| `avci_t_min_ebernstein` | 10 | Below this item count the `ebernstein` proxy falls back to `hoeffding` (fallback path only). |
|
|
190
|
+
| `avci_ebernstein_floor_frac` | 0.01 | Relative floor on the empirical-variance proxy, as a fraction of the `hoeffding` bound (fallback path only). |
|
|
191
|
+
| `avci_bootstrap_B` | 200 | Bootstrap resamples per decision call. |
|
|
192
|
+
| `avci_bootstrap_seed` | 0 | PRNG seed for the bivariate cluster bootstrap. |
|
|
193
|
+
| `sigma2_ga_aggregator` | `"median"` | Which bootstrap-CI point anchors the listener floor: `median` / `mean` / `q95` (conservative) / `None`. |
|
|
194
|
+
| `cold_start_ga_ratio` | 2.7 | Conservative BTTF-anchored prior ratio (σ²_lisSys/σ²_int) used before the component is identifiable. |
|
|
195
|
+
| `avci_wait_min_listeners_kge2` | 3 | Structural WAIT gate: minimum listeners with ≥ 2 paired items. |
|
|
196
|
+
| `avci_wait_max_ci_ratio` | 5.0 | WAIT gate: max tolerated q95/median on the σ²_lisSys bootstrap CI before abstaining. |
|
|
197
|
+
|
|
198
|
+
> **Legacy naming.** Parameters and `Decision` fields carrying `ga` /
|
|
199
|
+
> `gamma_alpha` (e.g. `cold_start_ga_ratio`, `sigma2_ga_*`) name the
|
|
200
|
+
> listener×system variance the paper writes `σ²_lisSys`; the identifiers will be
|
|
201
|
+
> **renamed to `lissys` in a future release** (an API change, hence deferred).
|
|
202
|
+
|
|
203
|
+
## Input data requirements
|
|
204
|
+
|
|
205
|
+
- Long format, columns `listener_id`, `item`, `system`, `rating`
|
|
206
|
+
(extra columns are ignored).
|
|
207
|
+
- Ratings on any bounded scale. The controller works on per-item paired
|
|
208
|
+
*differences*, whose extremes are ±(rating max − min); the default
|
|
209
|
+
`avci_d_bounds = (-4, 4)` spans a rating range of 4. On the normal path the
|
|
210
|
+
standard error is bootstrapped from the data, so the scale is handled
|
|
211
|
+
automatically and this default is safe on any scale; `avci_d_bounds` only
|
|
212
|
+
enters the sparse-data fallback (when the cluster bootstrap cannot run).
|
|
213
|
+
- The design must let listeners rate **multiple items per system**
|
|
214
|
+
(K ≥ 2 somewhere): a design where each listener rates a single item
|
|
215
|
+
— or each item is rated by a single listener — cannot identify the
|
|
216
|
+
listener×system component, and the controller will honestly report
|
|
217
|
+
`WAIT` forever rather than emit verdicts blind to that term.
|
|
218
|
+
|
|
219
|
+
## Repository layout
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
anytimemonitor/
|
|
223
|
+
├── __init__.py ← public API: AnytimeMonitor, Decision,
|
|
224
|
+
│ VarianceEstimate, estimate_components
|
|
225
|
+
├── controller.py ← the AnytimeMonitor controller
|
|
226
|
+
├── avci.py ← AnytimeCI — Howard 2021 sub-Gaussian
|
|
227
|
+
│ normal-mixture confidence sequence
|
|
228
|
+
├── variance.py ← MoM variance components + Half-Cauchy
|
|
229
|
+
│ regulariser
|
|
230
|
+
├── demo.py ← CLI example (batch-fed on a bundled sample)
|
|
231
|
+
├── ui/ ← Streamlit demo (app.py; see ui/README.md + ui/Architecture.md)
|
|
232
|
+
├── sample_uploads/ ← synthetic example CSVs for the UI
|
|
233
|
+
├── Architecture.md ← internal architecture (controller + statistics)
|
|
234
|
+
├── AVCI_PLAN.md ← deeper design notes for the confidence sequence
|
|
235
|
+
├── TODO.md ← development roadmap
|
|
236
|
+
├── archive/ ← retired code, notes and frozen runs
|
|
237
|
+
├── LICENSE ← MIT
|
|
238
|
+
└── requirements.txt ← pinned development stack
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The validation and simulation harnesses (synthetic-data generator,
|
|
242
|
+
controller-vs-oracle simulator, false-positive-rate check) are not part
|
|
243
|
+
of the installed package; they ship with the companion paper's
|
|
244
|
+
reproduction code.
|
|
245
|
+
|
|
246
|
+
## Status and caveats
|
|
247
|
+
|
|
248
|
+
- **Research prototype.** The API may still change. A `pytest` smoke-test
|
|
249
|
+
suite (`anytimemonitor/tests/`) checks the package runs end to end; the
|
|
250
|
+
controller has additionally been validated by simulation
|
|
251
|
+
(coverage under H₀, controller-vs-oracle gap decomposition) and
|
|
252
|
+
exercised on Blizzard Challenge data; it has not yet been plugged
|
|
253
|
+
into a live test platform.
|
|
254
|
+
- **Planning aid first.** The anytime-valid CI is a valid inference
|
|
255
|
+
under optional stopping, so it can be reported as such — but the
|
|
256
|
+
two-lever predictions (+N items / +L listeners) are noisy
|
|
257
|
+
projections from running variance estimates, not guarantees.
|
|
258
|
+
- A full-scale validation campaign (multi-seed, multi-corpus
|
|
259
|
+
false-positive and power sweeps) is planned alongside the companion
|
|
260
|
+
paper; the numbers shipped here come from smaller smoke-scale runs.
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT — see [LICENSE](LICENSE).
|
|
265
|
+
|
|
266
|
+
## Citation
|
|
267
|
+
|
|
268
|
+
A companion paper is under submission. Until it is out, please cite
|
|
269
|
+
this repository directly.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# AnytimeMonitor — adaptive controller for TTS-MOS listening tests
|
|
2
|
+
|
|
3
|
+
AnytimeMonitor watches a MOS listening test *while it runs* and tells
|
|
4
|
+
the test admin, for every pair of systems, whether the data already
|
|
5
|
+
supports a verdict — and if not, which lever (more items, or more
|
|
6
|
+
listeners) closes the pair fastest.
|
|
7
|
+
|
|
8
|
+
The statistical core is an **anytime-valid confidence sequence**
|
|
9
|
+
(Howard, Ramdas, McAuliffe & Sekhon, 2021) on per-item paired score
|
|
10
|
+
differences. Anytime validity means the coverage guarantee holds at
|
|
11
|
+
*any* data-dependent stopping time: the admin can look at the verdicts
|
|
12
|
+
as often as they like, stop whenever a pair resolves, and the reported
|
|
13
|
+
confidence interval is still valid. No peeking penalty bookkeeping, no
|
|
14
|
+
pre-registered sample size required.
|
|
15
|
+
|
|
16
|
+
This is a research prototype (v0.9). It accompanies a position paper
|
|
17
|
+
on budget allocation in TTS evaluation (under submission, *Computer
|
|
18
|
+
Speech & Language* Special Issue on the evaluation of speech and
|
|
19
|
+
speech synthesis).
|
|
20
|
+
|
|
21
|
+
## What the admin sees
|
|
22
|
+
|
|
23
|
+
For each pair of systems (A, B), one of six verdicts:
|
|
24
|
+
|
|
25
|
+
| Verdict | Meaning |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `STOP` | The anytime-valid CI excludes 0 (0 ∉ CI): the pair is separated. |
|
|
28
|
+
| `STOP_EQUIV` | Strict TOST equivalence: CI ⊂ ±δ_equiv *and* the half-width Ψ_eff ≤ δ_equiv/2. The pair is statistically equivalent within tolerance. |
|
|
29
|
+
| `CONTINUE +N` | Neither verdict yet. The tool predicts how many more **items** (+N) or **listeners** (+L) would close the pair, and which terminal (STOP or STOP_EQUIV) it is heading for. The headline is the cheaper option. |
|
|
30
|
+
| `WAIT` | The listener×system variance component cannot be estimated yet (fewer than 3 listeners with ≥ 2 paired items). Structural: keep collecting before trusting any verdict. |
|
|
31
|
+
| `CORPUS_LIMIT` | The item budget is exhausted *and* the listener lever can no longer close the pair. Terminal. |
|
|
32
|
+
| `LISTENER_LIMIT` | The listener budget is exhausted *and* the item lever can no longer close the pair. Terminal. |
|
|
33
|
+
|
|
34
|
+
> **Verdict naming.** `STOP` is the separation verdict the companion paper
|
|
35
|
+
> calls `STOP_SEP`. `STOP` is the current public label and will be **renamed to
|
|
36
|
+
> `STOP_SEP` in a future release** (to pair symmetrically with `STOP_EQUIV`);
|
|
37
|
+
> code matching on `"STOP"` should expect that change.
|
|
38
|
+
|
|
39
|
+
Verdicts are recomputed from scratch at every call — the controller is
|
|
40
|
+
queried on demand (a handful of times over a test lifecycle), not in a
|
|
41
|
+
real-time loop. A `STOP` is **not sticky**: if you keep collecting
|
|
42
|
+
past it, a borderline pair can revert to `CONTINUE`. This is honest
|
|
43
|
+
anytime-CI behaviour (the guarantee is on coverage, not on the
|
|
44
|
+
monotonicity of the observed difference); in practice the admin acts
|
|
45
|
+
on the first `STOP`.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install -e . # from the repository root (pyproject.toml)
|
|
51
|
+
pip install -e ".[ui]" # + Streamlit demo UI
|
|
52
|
+
pip install -e ".[test]" # + pytest; then run `pytest`
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Requires Python ≥ 3.10. Pinned versions used for development are in
|
|
56
|
+
`requirements.txt`.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
### Programmatic
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import pandas as pd
|
|
64
|
+
from anytimemonitor import AnytimeMonitor
|
|
65
|
+
|
|
66
|
+
ctrl = AnytimeMonitor(mde_threshold=0.30) # δ_sep, in MOS units
|
|
67
|
+
|
|
68
|
+
# Feed ratings as they arrive — long format, one row per judgment
|
|
69
|
+
ctrl.add_ratings(pd.DataFrame({
|
|
70
|
+
"listener_id": ..., # who rated
|
|
71
|
+
"item": ..., # which sentence / stimulus
|
|
72
|
+
"system": ..., # which TTS system
|
|
73
|
+
"rating": ..., # the MOS score
|
|
74
|
+
}))
|
|
75
|
+
|
|
76
|
+
print(ctrl.report()) # all pairs
|
|
77
|
+
dec = ctrl.decision("sysA", "sysB") # one pair, structured
|
|
78
|
+
print(dec.verdict, dec.ci_lo, dec.ci_hi, dec.n_more_items)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### CLI demo
|
|
82
|
+
|
|
83
|
+
Feed a ratings CSV to the controller batch by batch and watch the per-pair
|
|
84
|
+
verdicts tighten. With no arguments it runs on a bundled synthetic sample:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python -m anytimemonitor.demo # bundled sample
|
|
88
|
+
python -m anytimemonitor.demo --input my_ratings.csv # your own long-format CSV
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Streamlit demo UI
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
streamlit run anytimemonitor/ui/app.py
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Upload a ratings CSV (or pick a bundled synthetic example from
|
|
98
|
+
`sample_uploads/`), watch the controller replay the stream
|
|
99
|
+
batch-by-batch, and drag the time-machine slider to see the verdict
|
|
100
|
+
and two-lever advice at any past timestep. See `ui/README.md`.
|
|
101
|
+
|
|
102
|
+
## How it works
|
|
103
|
+
|
|
104
|
+
For a pair (A, B), the controller builds per-item paired differences
|
|
105
|
+
d_j = mean rating of item j under A minus under B, and maintains a
|
|
106
|
+
sub-Gaussian normal-mixture confidence sequence on their mean
|
|
107
|
+
(Howard et al. 2021). Two ingredients adapt this to listening-test
|
|
108
|
+
data, where ratings are clustered by listener as well as by item:
|
|
109
|
+
|
|
110
|
+
- **Bivariate cluster bootstrap SE.** Resampling *items* gives the
|
|
111
|
+
i.i.d.-part SE that the anytime boundary multiplies; resampling
|
|
112
|
+
*listeners* gives the listener-pool floor ("plancher") — the
|
|
113
|
+
precision ceiling that no amount of extra items can break, added in
|
|
114
|
+
quadrature. When the data is too sparse to bootstrap, the
|
|
115
|
+
controller falls back to the confidence sequence's internal running
|
|
116
|
+
variance proxy.
|
|
117
|
+
- **Variance decomposition layer.** Method-of-moments estimates of
|
|
118
|
+
the system×item and residual components (per system, with a
|
|
119
|
+
Half-Cauchy Bayesian regulariser that prevents the
|
|
120
|
+
truncation-at-zero bias of raw MoM), plus a per-pair estimate of
|
|
121
|
+
the listener×system component. These feed the two-lever
|
|
122
|
+
prediction: +N items shrinks the interaction term, +L listeners
|
|
123
|
+
lowers the floor.
|
|
124
|
+
|
|
125
|
+
The price of anytime validity is a modestly wider interval than a
|
|
126
|
+
fixed-n test (a √log log factor), and deliberate conservatism early
|
|
127
|
+
on: below `avci_t_min_ebernstein` items the variance proxy falls back
|
|
128
|
+
to the worst-case Hoeffding bound, so the first batches produce wide
|
|
129
|
+
intervals. The controller is therefore most useful from a few dozen
|
|
130
|
+
rated items per system onward.
|
|
131
|
+
|
|
132
|
+
The internal architecture is documented in
|
|
133
|
+
[`Architecture.md`](Architecture.md) (and
|
|
134
|
+
[`ui/Architecture.md`](ui/Architecture.md) for the demo UI); the deeper design
|
|
135
|
+
notes for the confidence sequence are in [`AVCI_PLAN.md`](AVCI_PLAN.md).
|
|
136
|
+
|
|
137
|
+
## Key parameters
|
|
138
|
+
|
|
139
|
+
All knobs are constructor arguments of `AnytimeMonitor`; the
|
|
140
|
+
defaults are deliberately conservative.
|
|
141
|
+
|
|
142
|
+
| Parameter | Default | Role |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| `mde_threshold` (δ_sep) | (required) | The smallest effect worth *resolving* (MOS): seeds `delta_equiv` and the reported MDE. It does **not** gate `STOP` — separation fires on `0 ∉ CI` regardless. |
|
|
145
|
+
| `delta_equiv` | `None` (= `mde_threshold`) | δ_equiv — equivalence margin for `STOP_EQUIV`. Set it below `mde_threshold` to create an indifference zone. |
|
|
146
|
+
| `alpha` | 0.05 | Nominal level of the confidence sequence. |
|
|
147
|
+
| `power` | 0.80 | Only sets the reported-MDE scale factor (`z_{α/2} + z_power`); does not affect stopping. |
|
|
148
|
+
| `n_max_corpus`, `l_max_listeners` | `None` | Item / listener budgets; enable the `*_LIMIT` terminal verdicts. |
|
|
149
|
+
| `avci_n_planned` | `240` (§7 census median budget) | Planned item budget; the controller derives η = 1/√(N_planned) (Howard's a-priori rule). |
|
|
150
|
+
| `avci_eta` | `None` (→ 1/√`avci_n_planned`) | Set the Howard 2021 mixing parameter η by hand instead of via `avci_n_planned` — η is fixed a priori, never tuned to data. **Pass one or the other, not both** (raises otherwise). |
|
|
151
|
+
| `avci_lissys_floor_r0` | `0.36` (measured r_lisSys band frontier) | A-priori guard on the listener floor: bounds it below by √(2·σ²₀/L) with σ²₀ = 1.5·r₀·σ̂²_eval (the ×1.5 is an MC-calibrated validity margin). Protects coverage when a small pool under-estimates σ²_lisSys. `0` disables. |
|
|
152
|
+
| `avci_lissys_floor_sigma2` | `None` | Absolute σ²₀ override for the guard (expert path, no extra margin). **Exclusive with `avci_lissys_floor_r0`** (raises otherwise). The per-pair `Decision.lissys_prior_exceeded` flags when data-driven floor estimates exceed the declared guard (declared margin exhausted; consider raising r₀). |
|
|
153
|
+
| `avci_gamma_alpha_factor` | 1.0 | Multiplier `c` on the listener floor (√c on the plancher); `1.0` assumes σ²_lisSys ≤ σ²_int. Bypassed when the bootstrap plancher is available (the normal path). |
|
|
154
|
+
| `avci_proxy` | `"ebernstein"` | Variance proxy: running empirical variance (`ebernstein`) or worst-case `hoeffding`. |
|
|
155
|
+
| `avci_d_bounds` | `(-4, 4)` | Range of the per-item paired *difference* (not the raw rating scale): ±(rating max − min), so the default spans a rating range of 4. The normal path bootstraps the SE from the data and **ignores this** (it feeds only the sparse-data fallback and a reported diagnostic); widen it for a larger rating range if you drive `AnytimeCI` directly on sparse data. |
|
|
156
|
+
| `avci_t_min_ebernstein` | 10 | Below this item count the `ebernstein` proxy falls back to `hoeffding` (fallback path only). |
|
|
157
|
+
| `avci_ebernstein_floor_frac` | 0.01 | Relative floor on the empirical-variance proxy, as a fraction of the `hoeffding` bound (fallback path only). |
|
|
158
|
+
| `avci_bootstrap_B` | 200 | Bootstrap resamples per decision call. |
|
|
159
|
+
| `avci_bootstrap_seed` | 0 | PRNG seed for the bivariate cluster bootstrap. |
|
|
160
|
+
| `sigma2_ga_aggregator` | `"median"` | Which bootstrap-CI point anchors the listener floor: `median` / `mean` / `q95` (conservative) / `None`. |
|
|
161
|
+
| `cold_start_ga_ratio` | 2.7 | Conservative BTTF-anchored prior ratio (σ²_lisSys/σ²_int) used before the component is identifiable. |
|
|
162
|
+
| `avci_wait_min_listeners_kge2` | 3 | Structural WAIT gate: minimum listeners with ≥ 2 paired items. |
|
|
163
|
+
| `avci_wait_max_ci_ratio` | 5.0 | WAIT gate: max tolerated q95/median on the σ²_lisSys bootstrap CI before abstaining. |
|
|
164
|
+
|
|
165
|
+
> **Legacy naming.** Parameters and `Decision` fields carrying `ga` /
|
|
166
|
+
> `gamma_alpha` (e.g. `cold_start_ga_ratio`, `sigma2_ga_*`) name the
|
|
167
|
+
> listener×system variance the paper writes `σ²_lisSys`; the identifiers will be
|
|
168
|
+
> **renamed to `lissys` in a future release** (an API change, hence deferred).
|
|
169
|
+
|
|
170
|
+
## Input data requirements
|
|
171
|
+
|
|
172
|
+
- Long format, columns `listener_id`, `item`, `system`, `rating`
|
|
173
|
+
(extra columns are ignored).
|
|
174
|
+
- Ratings on any bounded scale. The controller works on per-item paired
|
|
175
|
+
*differences*, whose extremes are ±(rating max − min); the default
|
|
176
|
+
`avci_d_bounds = (-4, 4)` spans a rating range of 4. On the normal path the
|
|
177
|
+
standard error is bootstrapped from the data, so the scale is handled
|
|
178
|
+
automatically and this default is safe on any scale; `avci_d_bounds` only
|
|
179
|
+
enters the sparse-data fallback (when the cluster bootstrap cannot run).
|
|
180
|
+
- The design must let listeners rate **multiple items per system**
|
|
181
|
+
(K ≥ 2 somewhere): a design where each listener rates a single item
|
|
182
|
+
— or each item is rated by a single listener — cannot identify the
|
|
183
|
+
listener×system component, and the controller will honestly report
|
|
184
|
+
`WAIT` forever rather than emit verdicts blind to that term.
|
|
185
|
+
|
|
186
|
+
## Repository layout
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
anytimemonitor/
|
|
190
|
+
├── __init__.py ← public API: AnytimeMonitor, Decision,
|
|
191
|
+
│ VarianceEstimate, estimate_components
|
|
192
|
+
├── controller.py ← the AnytimeMonitor controller
|
|
193
|
+
├── avci.py ← AnytimeCI — Howard 2021 sub-Gaussian
|
|
194
|
+
│ normal-mixture confidence sequence
|
|
195
|
+
├── variance.py ← MoM variance components + Half-Cauchy
|
|
196
|
+
│ regulariser
|
|
197
|
+
├── demo.py ← CLI example (batch-fed on a bundled sample)
|
|
198
|
+
├── ui/ ← Streamlit demo (app.py; see ui/README.md + ui/Architecture.md)
|
|
199
|
+
├── sample_uploads/ ← synthetic example CSVs for the UI
|
|
200
|
+
├── Architecture.md ← internal architecture (controller + statistics)
|
|
201
|
+
├── AVCI_PLAN.md ← deeper design notes for the confidence sequence
|
|
202
|
+
├── TODO.md ← development roadmap
|
|
203
|
+
├── archive/ ← retired code, notes and frozen runs
|
|
204
|
+
├── LICENSE ← MIT
|
|
205
|
+
└── requirements.txt ← pinned development stack
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The validation and simulation harnesses (synthetic-data generator,
|
|
209
|
+
controller-vs-oracle simulator, false-positive-rate check) are not part
|
|
210
|
+
of the installed package; they ship with the companion paper's
|
|
211
|
+
reproduction code.
|
|
212
|
+
|
|
213
|
+
## Status and caveats
|
|
214
|
+
|
|
215
|
+
- **Research prototype.** The API may still change. A `pytest` smoke-test
|
|
216
|
+
suite (`anytimemonitor/tests/`) checks the package runs end to end; the
|
|
217
|
+
controller has additionally been validated by simulation
|
|
218
|
+
(coverage under H₀, controller-vs-oracle gap decomposition) and
|
|
219
|
+
exercised on Blizzard Challenge data; it has not yet been plugged
|
|
220
|
+
into a live test platform.
|
|
221
|
+
- **Planning aid first.** The anytime-valid CI is a valid inference
|
|
222
|
+
under optional stopping, so it can be reported as such — but the
|
|
223
|
+
two-lever predictions (+N items / +L listeners) are noisy
|
|
224
|
+
projections from running variance estimates, not guarantees.
|
|
225
|
+
- A full-scale validation campaign (multi-seed, multi-corpus
|
|
226
|
+
false-positive and power sweeps) is planned alongside the companion
|
|
227
|
+
paper; the numbers shipped here come from smaller smoke-scale runs.
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT — see [LICENSE](LICENSE).
|
|
232
|
+
|
|
233
|
+
## Citation
|
|
234
|
+
|
|
235
|
+
A companion paper is under submission. Until it is out, please cite
|
|
236
|
+
this repository directly.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""anytimemonitor — adaptive controller for TTS-MOS listening tests.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
AnytimeMonitor — stateful controller built on an anytime-valid
|
|
5
|
+
confidence sequence (per-item paired diffs).
|
|
6
|
+
Add ratings, query for per-pair verdicts and
|
|
7
|
+
two-lever (+items / +listeners) advice at any
|
|
8
|
+
data-dependent time.
|
|
9
|
+
Decision — dataclass returned by AnytimeMonitor.decision().
|
|
10
|
+
|
|
11
|
+
See README.md for use case and architecture.
|
|
12
|
+
"""
|
|
13
|
+
from .controller import Decision, AnytimeMonitor
|
|
14
|
+
from .variance import VarianceEstimate, estimate_components
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AnytimeMonitor",
|
|
18
|
+
"Decision",
|
|
19
|
+
"VarianceEstimate",
|
|
20
|
+
"estimate_components",
|
|
21
|
+
]
|