refitgap 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.
- refitgap-0.1.0/CITATION.cff +34 -0
- refitgap-0.1.0/LICENSE +21 -0
- refitgap-0.1.0/MANIFEST.in +6 -0
- refitgap-0.1.0/PKG-INFO +128 -0
- refitgap-0.1.0/README.txt +95 -0
- refitgap-0.1.0/pyproject.toml +58 -0
- refitgap-0.1.0/setup.cfg +4 -0
- refitgap-0.1.0/src/refitgap/API.txt +150 -0
- refitgap-0.1.0/src/refitgap/__init__.py +29 -0
- refitgap-0.1.0/src/refitgap/__main__.py +6 -0
- refitgap-0.1.0/src/refitgap/analysis.py +399 -0
- refitgap-0.1.0/src/refitgap/cli.py +173 -0
- refitgap-0.1.0/src/refitgap/crossfit.py +275 -0
- refitgap-0.1.0/src/refitgap/example.py +96 -0
- refitgap-0.1.0/src/refitgap/fitters.py +114 -0
- refitgap-0.1.0/src/refitgap/identification.py +101 -0
- refitgap-0.1.0/src/refitgap/inference.py +319 -0
- refitgap-0.1.0/src/refitgap/plots.py +214 -0
- refitgap-0.1.0/src/refitgap/records.py +38 -0
- refitgap-0.1.0/src/refitgap/report.py +136 -0
- refitgap-0.1.0/src/refitgap/runs.py +53 -0
- refitgap-0.1.0/src/refitgap/tiny.py +68 -0
- refitgap-0.1.0/src/refitgap/twoscorer.py +504 -0
- refitgap-0.1.0/src/refitgap.egg-info/PKG-INFO +128 -0
- refitgap-0.1.0/src/refitgap.egg-info/SOURCES.txt +38 -0
- refitgap-0.1.0/src/refitgap.egg-info/dependency_links.txt +1 -0
- refitgap-0.1.0/src/refitgap.egg-info/entry_points.txt +2 -0
- refitgap-0.1.0/src/refitgap.egg-info/requires.txt +9 -0
- refitgap-0.1.0/src/refitgap.egg-info/top_level.txt +1 -0
- refitgap-0.1.0/tests/conftest.py +35 -0
- refitgap-0.1.0/tests/test_cli_and_report.py +441 -0
- refitgap-0.1.0/tests/test_crossfit.py +413 -0
- refitgap-0.1.0/tests/test_examples.py +107 -0
- refitgap-0.1.0/tests/test_external_pipeline.py +95 -0
- refitgap-0.1.0/tests/test_family_at.py +22 -0
- refitgap-0.1.0/tests/test_hand_checks.py +334 -0
- refitgap-0.1.0/tests/test_inputs_and_inference.py +375 -0
- refitgap-0.1.0/tests/test_known_answer.py +157 -0
- refitgap-0.1.0/tests/test_slow_examples.py +24 -0
- refitgap-0.1.0/tests/test_twoscorer.py +357 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use refitgap, please cite the paper below and this software."
|
|
3
|
+
title: "refitgap: score-based evaluation of thresholded forgetting in continual multi-label classification"
|
|
4
|
+
type: software
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
license: MIT
|
|
7
|
+
repository-code: "https://github.com/ihshaish/refitgap"
|
|
8
|
+
authors:
|
|
9
|
+
- family-names: Ihshaish
|
|
10
|
+
given-names: Hisham
|
|
11
|
+
email: hisham.ihshaish@uwe.ac.uk
|
|
12
|
+
orcid: "https://orcid.org/0000-0001-5530-4894"
|
|
13
|
+
affiliation: "School of Computing and Creative Technologies, University of the West of England, Bristol, UK; Department of Computer Science, Birzeit University, Birzeit, Palestine"
|
|
14
|
+
keywords:
|
|
15
|
+
- continual learning
|
|
16
|
+
- multi-label classification
|
|
17
|
+
- backward transfer
|
|
18
|
+
- attainable gap
|
|
19
|
+
- evaluation
|
|
20
|
+
preferred-citation:
|
|
21
|
+
type: article
|
|
22
|
+
title: "Decomposing Backward Transfer in Continual Multi-Label Text Classification"
|
|
23
|
+
status: submitted
|
|
24
|
+
notes: "Under review."
|
|
25
|
+
authors:
|
|
26
|
+
- family-names: Ihshaish
|
|
27
|
+
given-names: Hisham
|
|
28
|
+
affiliation: "University of the West of England; Birzeit University"
|
|
29
|
+
- family-names: Mayhew
|
|
30
|
+
given-names: Peter
|
|
31
|
+
affiliation: "University of the West of England; GE Aerospace"
|
|
32
|
+
- family-names: Aydin
|
|
33
|
+
given-names: Mehmet
|
|
34
|
+
affiliation: "University of the West of England"
|
refitgap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hisham Ihshaish
|
|
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.
|
refitgap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: refitgap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Split a continual learner's thresholded forgetting into what the scores lost and what the carried rule lost, from stored scores.
|
|
5
|
+
Author-email: Hisham Ihshaish <hisham.ihshaish@uwe.ac.uk>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ihshaish/refitgap
|
|
8
|
+
Project-URL: Repository, https://github.com/ihshaish/refitgap
|
|
9
|
+
Project-URL: Issues, https://github.com/ihshaish/refitgap/issues
|
|
10
|
+
Keywords: continual learning,multi-label classification,backward transfer,thresholds,evaluation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/plain
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
26
|
+
Requires-Dist: scipy>=1.10
|
|
27
|
+
Provides-Extra: plots
|
|
28
|
+
Requires-Dist: matplotlib>=3.5; extra == "plots"
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
31
|
+
Requires-Dist: scikit-learn>=1.3; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
refitgap
|
|
35
|
+
========
|
|
36
|
+
|
|
37
|
+
Train a multi-label classifier period by period, score the final model on an old period, and
|
|
38
|
+
its macro-F1 there drops. Part of the drop sits in the scores, which now separate the labels
|
|
39
|
+
less well, and part in the thresholds, carried from period one and now in the wrong place.
|
|
40
|
+
refitgap says how much of each, from stored scores alone:
|
|
41
|
+
|
|
42
|
+
deficit B = attainable gap A + change in mismatch dR
|
|
43
|
+
|
|
44
|
+
The attainable gap compares the two models after each refits its thresholds on equal terms
|
|
45
|
+
(split-half cross-fitting, same partitions); the change in mismatch is what each loses by
|
|
46
|
+
keeping the carried rule. Same trick for two methods: replay's lead over sequential training
|
|
47
|
+
at the inherited thresholds, after both refit, and the change. This is the code of
|
|
48
|
+
"Decomposing Backward Transfer in Continual Multi-Label Text Classification" (Ihshaish,
|
|
49
|
+
Mayhew and Aydin, under review), with its plans, stored results and scripts.
|
|
50
|
+
|
|
51
|
+
Install
|
|
52
|
+
-------
|
|
53
|
+
|
|
54
|
+
pip install refitgap # Python 3.10+, numpy and scipy, nothing else
|
|
55
|
+
pip install "refitgap[plots,test]" # matplotlib for figures, pytest etc. for the tests
|
|
56
|
+
|
|
57
|
+
(or pip install . from the clone). The entry points below read files in this repository, so
|
|
58
|
+
clone it too. On the command line, refitgap evaluate | report | example runs the same
|
|
59
|
+
evaluator on stored arrays (refitgap --help; API.txt, "Command line").
|
|
60
|
+
|
|
61
|
+
Reproduce Figure 2
|
|
62
|
+
------------------
|
|
63
|
+
|
|
64
|
+
python examples/figure2/reproduce_figure2.py # a few minutes on 4 cores
|
|
65
|
+
python examples/figure2/reproduce_figure2.py --from-levels # about a second
|
|
66
|
+
|
|
67
|
+
All five paired comparisons of Figure 2 (supplement Table C.1) over periods 2 to 7: lead at
|
|
68
|
+
the inherited thresholds, after both refit, and the change. Paired t intervals over six
|
|
69
|
+
DistilBERT pairs (from stored per-pair levels: their 30 score files, about 60 MB, do not ship),
|
|
70
|
+
percentile bootstrap over ten BiGRU pairs (from raw scores in paper/results/, or stored levels
|
|
71
|
+
with --from-levels). Every lead, mean and bound must match examples/figure2/expected.json to
|
|
72
|
+
1e-10.
|
|
73
|
+
|
|
74
|
+
python examples/two_scorer/table1.py
|
|
75
|
+
|
|
76
|
+
Table 1: two scorers with equal AUROC (6/11), average precision (5/18) and deficit (1/26),
|
|
77
|
+
one all change in mismatch, the other all attainable gap. Exact fractions, no luck involved!
|
|
78
|
+
|
|
79
|
+
Compare your own models
|
|
80
|
+
-----------------------
|
|
81
|
+
|
|
82
|
+
python examples/your_models/compare_your_models.py [DATA_DIR] --a mine --b baseline
|
|
83
|
+
|
|
84
|
+
DATA_DIR holds three kinds of CSV (examples/your_models/data/ is a tiny synthetic set):
|
|
85
|
+
|
|
86
|
+
labels.csv period, record_id, one 0/1 column per label
|
|
87
|
+
scores_<method>_seed<k>.csv period, record_id, one score column per label (the final
|
|
88
|
+
model of <method>, trained under seed k)
|
|
89
|
+
thresholds.csv method, seed, the carried rule (one threshold per label)
|
|
90
|
+
|
|
91
|
+
Out come a's leads over b at the carried rule and after both refit, and the change (refitted
|
|
92
|
+
lead minus carried lead), per seed and with paired t intervals. In Python: refitgap.compare()
|
|
93
|
+
and method_contrast(); API.txt has the lot.
|
|
94
|
+
|
|
95
|
+
Interface
|
|
96
|
+
---------
|
|
97
|
+
|
|
98
|
+
In: aligned scores and labels, the carried rule, and a seed for the common partitions.
|
|
99
|
+
- Scores: one (records, labels) array per period and model, probabilities in [0, 1]; logits go
|
|
100
|
+
through a sigmoid first, or declare score_scale="other" with thresholds on the same scale.
|
|
101
|
+
Labels: 0/1, same shape.
|
|
102
|
+
- Alignment: rows match by record id, columns by label name. A shape mismatch, a duplicate or
|
|
103
|
+
missing id, a different label set, or different labels for the two models raises. Nothing
|
|
104
|
+
is truncated or dropped quietly.
|
|
105
|
+
- Ties: a score equal to its threshold is a positive. A refitted threshold is an observed
|
|
106
|
+
score; of two equally good cutpoints the higher wins.
|
|
107
|
+
- A label with no positive in a period has F1 0 there and stays in the macro mean.
|
|
108
|
+
- Partitions: partition b of period id j under seed s is the b-th permutation of numpy
|
|
109
|
+
default_rng(7000 + 100*s + j); first half fits, second scores, then the reverse. 200 in the
|
|
110
|
+
paper. Both models of a pair share them, so give both the same seed and period ids. Period
|
|
111
|
+
ids are integers in [0, 100); the paper's periods 2 to 7 are ids 1 to 6.
|
|
112
|
+
- Out: carried level U, refitted level V, deficit, attainable gap and change in mismatch, per
|
|
113
|
+
period and averaged; for two methods, leads per seed with means and 95% intervals. Positive
|
|
114
|
+
means the first-named model scores higher. Numbers only: refitgap recommends nothing, and
|
|
115
|
+
the attainable gap describes the scores, not what the representation kept.
|
|
116
|
+
|
|
117
|
+
Repository layout
|
|
118
|
+
-----------------
|
|
119
|
+
|
|
120
|
+
src/refitgap/ the evaluator; src/refitgap/API.txt is the reference
|
|
121
|
+
examples/ Table 1, Figure 2, your own models, walkthrough.ipynb (seconds, the lot)
|
|
122
|
+
paper/ plans, scripts, stored results, checks; README.txt maps tables to scripts,
|
|
123
|
+
DATA.txt the corpora (public, not redistributed, bar a 3200-record ASRS sample)
|
|
124
|
+
paper/threshold_policy_survey.txt how 34 sampled studies report their thresholds
|
|
125
|
+
tests/ python -m pytest (fast, a minute or two); -m slow adds the full Figure 2
|
|
126
|
+
recomputation from raw scores (minutes)
|
|
127
|
+
|
|
128
|
+
MIT licence. Please cite the paper and the software (CITATION.cff has both). Have fun with it!
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
refitgap
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
Train a multi-label classifier period by period, score the final model on an old period, and
|
|
5
|
+
its macro-F1 there drops. Part of the drop sits in the scores, which now separate the labels
|
|
6
|
+
less well, and part in the thresholds, carried from period one and now in the wrong place.
|
|
7
|
+
refitgap says how much of each, from stored scores alone:
|
|
8
|
+
|
|
9
|
+
deficit B = attainable gap A + change in mismatch dR
|
|
10
|
+
|
|
11
|
+
The attainable gap compares the two models after each refits its thresholds on equal terms
|
|
12
|
+
(split-half cross-fitting, same partitions); the change in mismatch is what each loses by
|
|
13
|
+
keeping the carried rule. Same trick for two methods: replay's lead over sequential training
|
|
14
|
+
at the inherited thresholds, after both refit, and the change. This is the code of
|
|
15
|
+
"Decomposing Backward Transfer in Continual Multi-Label Text Classification" (Ihshaish,
|
|
16
|
+
Mayhew and Aydin, under review), with its plans, stored results and scripts.
|
|
17
|
+
|
|
18
|
+
Install
|
|
19
|
+
-------
|
|
20
|
+
|
|
21
|
+
pip install refitgap # Python 3.10+, numpy and scipy, nothing else
|
|
22
|
+
pip install "refitgap[plots,test]" # matplotlib for figures, pytest etc. for the tests
|
|
23
|
+
|
|
24
|
+
(or pip install . from the clone). The entry points below read files in this repository, so
|
|
25
|
+
clone it too. On the command line, refitgap evaluate | report | example runs the same
|
|
26
|
+
evaluator on stored arrays (refitgap --help; API.txt, "Command line").
|
|
27
|
+
|
|
28
|
+
Reproduce Figure 2
|
|
29
|
+
------------------
|
|
30
|
+
|
|
31
|
+
python examples/figure2/reproduce_figure2.py # a few minutes on 4 cores
|
|
32
|
+
python examples/figure2/reproduce_figure2.py --from-levels # about a second
|
|
33
|
+
|
|
34
|
+
All five paired comparisons of Figure 2 (supplement Table C.1) over periods 2 to 7: lead at
|
|
35
|
+
the inherited thresholds, after both refit, and the change. Paired t intervals over six
|
|
36
|
+
DistilBERT pairs (from stored per-pair levels: their 30 score files, about 60 MB, do not ship),
|
|
37
|
+
percentile bootstrap over ten BiGRU pairs (from raw scores in paper/results/, or stored levels
|
|
38
|
+
with --from-levels). Every lead, mean and bound must match examples/figure2/expected.json to
|
|
39
|
+
1e-10.
|
|
40
|
+
|
|
41
|
+
python examples/two_scorer/table1.py
|
|
42
|
+
|
|
43
|
+
Table 1: two scorers with equal AUROC (6/11), average precision (5/18) and deficit (1/26),
|
|
44
|
+
one all change in mismatch, the other all attainable gap. Exact fractions, no luck involved!
|
|
45
|
+
|
|
46
|
+
Compare your own models
|
|
47
|
+
-----------------------
|
|
48
|
+
|
|
49
|
+
python examples/your_models/compare_your_models.py [DATA_DIR] --a mine --b baseline
|
|
50
|
+
|
|
51
|
+
DATA_DIR holds three kinds of CSV (examples/your_models/data/ is a tiny synthetic set):
|
|
52
|
+
|
|
53
|
+
labels.csv period, record_id, one 0/1 column per label
|
|
54
|
+
scores_<method>_seed<k>.csv period, record_id, one score column per label (the final
|
|
55
|
+
model of <method>, trained under seed k)
|
|
56
|
+
thresholds.csv method, seed, the carried rule (one threshold per label)
|
|
57
|
+
|
|
58
|
+
Out come a's leads over b at the carried rule and after both refit, and the change (refitted
|
|
59
|
+
lead minus carried lead), per seed and with paired t intervals. In Python: refitgap.compare()
|
|
60
|
+
and method_contrast(); API.txt has the lot.
|
|
61
|
+
|
|
62
|
+
Interface
|
|
63
|
+
---------
|
|
64
|
+
|
|
65
|
+
In: aligned scores and labels, the carried rule, and a seed for the common partitions.
|
|
66
|
+
- Scores: one (records, labels) array per period and model, probabilities in [0, 1]; logits go
|
|
67
|
+
through a sigmoid first, or declare score_scale="other" with thresholds on the same scale.
|
|
68
|
+
Labels: 0/1, same shape.
|
|
69
|
+
- Alignment: rows match by record id, columns by label name. A shape mismatch, a duplicate or
|
|
70
|
+
missing id, a different label set, or different labels for the two models raises. Nothing
|
|
71
|
+
is truncated or dropped quietly.
|
|
72
|
+
- Ties: a score equal to its threshold is a positive. A refitted threshold is an observed
|
|
73
|
+
score; of two equally good cutpoints the higher wins.
|
|
74
|
+
- A label with no positive in a period has F1 0 there and stays in the macro mean.
|
|
75
|
+
- Partitions: partition b of period id j under seed s is the b-th permutation of numpy
|
|
76
|
+
default_rng(7000 + 100*s + j); first half fits, second scores, then the reverse. 200 in the
|
|
77
|
+
paper. Both models of a pair share them, so give both the same seed and period ids. Period
|
|
78
|
+
ids are integers in [0, 100); the paper's periods 2 to 7 are ids 1 to 6.
|
|
79
|
+
- Out: carried level U, refitted level V, deficit, attainable gap and change in mismatch, per
|
|
80
|
+
period and averaged; for two methods, leads per seed with means and 95% intervals. Positive
|
|
81
|
+
means the first-named model scores higher. Numbers only: refitgap recommends nothing, and
|
|
82
|
+
the attainable gap describes the scores, not what the representation kept.
|
|
83
|
+
|
|
84
|
+
Repository layout
|
|
85
|
+
-----------------
|
|
86
|
+
|
|
87
|
+
src/refitgap/ the evaluator; src/refitgap/API.txt is the reference
|
|
88
|
+
examples/ Table 1, Figure 2, your own models, walkthrough.ipynb (seconds, the lot)
|
|
89
|
+
paper/ plans, scripts, stored results, checks; README.txt maps tables to scripts,
|
|
90
|
+
DATA.txt the corpora (public, not redistributed, bar a 3200-record ASRS sample)
|
|
91
|
+
paper/threshold_policy_survey.txt how 34 sampled studies report their thresholds
|
|
92
|
+
tests/ python -m pytest (fast, a minute or two); -m slow adds the full Figure 2
|
|
93
|
+
recomputation from raw scores (minutes)
|
|
94
|
+
|
|
95
|
+
MIT licence. Please cite the paper and the software (CITATION.cff has both). Have fun with it!
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "refitgap"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Split a continual learner's thresholded forgetting into what the scores lost and what the carried rule lost, from stored scores."
|
|
9
|
+
readme = { file = "README.txt", content-type = "text/plain" }
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Hisham Ihshaish", email = "hisham.ihshaish@uwe.ac.uk" }]
|
|
14
|
+
keywords = ["continual learning", "multi-label classification", "backward transfer", "thresholds", "evaluation"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
27
|
+
]
|
|
28
|
+
dependencies = ["numpy>=1.24", "scipy>=1.10"]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
plots = ["matplotlib>=3.5"]
|
|
32
|
+
test = ["pytest>=7.0", "scikit-learn>=1.3"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/ihshaish/refitgap"
|
|
36
|
+
Repository = "https://github.com/ihshaish/refitgap"
|
|
37
|
+
Issues = "https://github.com/ihshaish/refitgap/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
refitgap = "refitgap.cli:main"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools]
|
|
43
|
+
package-dir = { "" = "src" }
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.package-data]
|
|
49
|
+
refitgap = ["*.txt"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.dynamic]
|
|
52
|
+
version = { attr = "refitgap.__version__" }
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
pythonpath = ["src"]
|
|
57
|
+
addopts = "-p no:cacheprovider -m 'not slow'"
|
|
58
|
+
markers = ["slow: recomputes stored paper numbers from score files (minutes to hours; run with -m slow)"]
|
refitgap-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
refitgap API
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
Stored per-period scores and labels in; cross-fitted macro-F1 levels, the split of a
|
|
5
|
+
thresholded deficit into attainable gap and change in mismatch, and seed-paired contrasts
|
|
6
|
+
out. numpy and scipy only; figures want matplotlib (pip install "refitgap[plots]").
|
|
7
|
+
|
|
8
|
+
The estimator
|
|
9
|
+
-------------
|
|
10
|
+
|
|
11
|
+
For period id j and seed s, partition b is the b-th permutation of
|
|
12
|
+
numpy.random.default_rng(7000 + 100*s + j); half 0 is the first n_j // 2 records. A refit
|
|
13
|
+
picks, per label, the F1-maximising cutpoint on one half and is scored on the other, both
|
|
14
|
+
ways round; a level is the mean over the partitions (200 in the paper). With U a model's
|
|
15
|
+
macro-F1 at the carried rule theta_1 and V its refitted (attainable) level,
|
|
16
|
+
|
|
17
|
+
B = U_ref - U_final = (V_ref - V_final) + (R_final - R_ref) = A + dR, R = V - U
|
|
18
|
+
|
|
19
|
+
U is taken on the whole period by default (U_full), or on the held-out halves (U_half).
|
|
20
|
+
For one rule shared across periods, R = C + M with C = V_sep - V_shared and
|
|
21
|
+
M = V_shared - U, exact per partition. A score equal to its threshold is a positive; a label
|
|
22
|
+
with no positive and no prediction has F1 0 and stays in the macro mean. Period ids are ints
|
|
23
|
+
in [0, 100) and seeds non-negative ints, so no two (seed, period) pairs share partitions.
|
|
24
|
+
|
|
25
|
+
Outcome rules
|
|
26
|
+
-------------
|
|
27
|
+
|
|
28
|
+
A contrast is a per-seed vector (method a minus method b, paired by seed). At margin 0.005:
|
|
29
|
+
- equivalent: TOST p < 0.05 (Holm-adjusted within a family);
|
|
30
|
+
- material: t p < 0.05 and |mean| >= margin;
|
|
31
|
+
- small non-zero: t p < 0.05 and |mean| < margin;
|
|
32
|
+
- unresolved: anything else, or a material / small non-zero call that the sign-flip test
|
|
33
|
+
(exact up to 20 seeds, Monte Carlo above) does not back;
|
|
34
|
+
- non-inferior / inferior (family_ni): one-sided t and sign-flip tests at -margin.
|
|
35
|
+
Intervals are marginal 95 % t intervals over seeds. Rows say "descriptive" unless you name
|
|
36
|
+
a family fixed in advance (--family NAME); the class words then describe your numbers under
|
|
37
|
+
the paper's rules, nothing grander.
|
|
38
|
+
|
|
39
|
+
Functions
|
|
40
|
+
---------
|
|
41
|
+
|
|
42
|
+
compare(scores_ref, scores_final, labels, rule, *, seed, splits=200, record_ids=None,
|
|
43
|
+
label_ids=None, period_ids=None, labels_final=None, rule_name="carried", halves=False,
|
|
44
|
+
score_scale=None, fixed_split=None, ...)
|
|
45
|
+
Two scorers on the same labelled records, (n, K) each (lists for several periods), and
|
|
46
|
+
the carried rule `rule`, one threshold per label -> dict(carried, refitted, components,
|
|
47
|
+
per_period, metadata, decomposition, evaluations). rule_name is the name the vector goes by in the
|
|
48
|
+
returned Evaluations (evaluations["ref"], evaluations["final"]), which is what
|
|
49
|
+
method_contrast takes. labels_final: the labels exported with the final scorer, checked
|
|
50
|
+
cell by cell against `labels`. Mismatched shapes, ids or labels raise; scores or rule
|
|
51
|
+
outside [0, 1] raise unless score_scale="other".
|
|
52
|
+
method_contrast(evals_a, evals_b, rule, *, periods=None, same_rule=True, names=("a", "b"), ...)
|
|
53
|
+
{seed: Evaluation} for two methods, and `rule`, the name of a rule held by every
|
|
54
|
+
Evaluation -> terms lead_at_carried_rule (G_U), lead_after_refit (G_V) and change
|
|
55
|
+
(G_V - G_U, the change after refitting, as in the paper), each with per-seed values,
|
|
56
|
+
mean, t interval and class. "placement" is G_U - G_V, the negative of change, kept for
|
|
57
|
+
compatibility. same_rule=False lets each method carry its own vector under that name.
|
|
58
|
+
evaluate(Ps, Ys, seed, period_ids=None, rules=None, shared_periods=None, splits=200, ...)
|
|
59
|
+
One model's per-period scores and labels, and rules={name: (K,) vector} -> Evaluation
|
|
60
|
+
(levels per partition, both directions, every period); .summary() and .per_period()
|
|
61
|
+
give the means.
|
|
62
|
+
decompose(ref, final, rule, periods=None, halves=False, same_rule=True)
|
|
63
|
+
Two Evaluations on the same records and a rule name -> B, A, dR (and dC, dM) per period
|
|
64
|
+
and mean. B and dR are also returned as D and R, their earlier names.
|
|
65
|
+
|
|
66
|
+
Two methods from Python, end to end (synthetic scores, two periods, five seeds):
|
|
67
|
+
|
|
68
|
+
import numpy as np, refitgap as rg
|
|
69
|
+
rng = np.random.default_rng(0); Y = [(rng.random((300, 3)) < 0.3).astype(int) for _ in range(2)]
|
|
70
|
+
ev = lambda sep: {s: rg.evaluate([1 / (1 + np.exp(1 - sep * y - rng.normal(size=y.shape))) for y in Y], Y, s,
|
|
71
|
+
rules={"carried": np.full(3, 0.5)}) for s in range(5)}
|
|
72
|
+
mc = rg.method_contrast(ev(2.5), ev(1.5), "carried", names=("mine", "baseline"))
|
|
73
|
+
print({t: round(mc["terms"][t]["mean"], 4) for t in ("lead_at_carried_rule", "lead_after_refit", "change")})
|
|
74
|
+
|
|
75
|
+
prints {'lead_at_carried_rule': 0.1484, 'lead_after_refit': 0.1903, 'change': 0.0419}.
|
|
76
|
+
fit_exact(P, Y), fit_grid(P, Y), fit_shared_exact(Ps, Ys, w=None), fit_validation_rule(Pval, Yval)
|
|
77
|
+
Scores and labels -> (K,) thresholds: best cutpoint per label; the 19-point grid
|
|
78
|
+
0.05..0.95 (how theta_1 was fitted); one vector for several periods; the grid on a
|
|
79
|
+
validation split.
|
|
80
|
+
f1_vec(P, Y, theta), macro_f1(P, Y, theta)
|
|
81
|
+
Per-label F1 and its mean at theta.
|
|
82
|
+
partitions(n, seed, j, splits=200)
|
|
83
|
+
The index permutations behind every level.
|
|
84
|
+
paired_stats(x), paired_contrast(a, b), family(members), family_ni(members), holm(ps)
|
|
85
|
+
Per-seed vectors (or {seed: value} dicts) -> mean, intervals, t / TOST / sign-flip p and
|
|
86
|
+
classes, as above.
|
|
87
|
+
analyse(runs_a, runs_b=None, rule="inherited", mean_over=None, u_convention="full", ...)
|
|
88
|
+
Runs (make_run, run_from_npz, run_from_arrays) -> levels, decomposition and contrast
|
|
89
|
+
tables; write_tables(result, dir) writes levels.csv, decomposition.csv, contrasts.csv and
|
|
90
|
+
result.json; plots.save_figures and report.write_report draw from the same tables.
|
|
91
|
+
make_run(final, labels, seed, rules, reference=None, ...), save_arrays(path, run)
|
|
92
|
+
A run from arrays; saved as an arrays file (scores_<j>, labels_<j>, optional
|
|
93
|
+
reference_<j>, rule_<name>, seed, label_names).
|
|
94
|
+
load_run(path), evaluate_npz(path, seed, ...)
|
|
95
|
+
Read or evaluate a stored run file (P_<i>_<j>: model i on period j; Y_<j>; thresholds).
|
|
96
|
+
align_to(reference_ids, ids, *arrays), drop_unlabelled(ids, Y, *arrays)
|
|
97
|
+
Reorder rows to a reference id order (missing or duplicate ids raise); drop records with
|
|
98
|
+
a missing (NaN) label.
|
|
99
|
+
identification_example()
|
|
100
|
+
The fourteen-record case in exact fractions: equal AUROC (6/11) and average precision
|
|
101
|
+
(5/18), equal deficits (1/26), one all mismatch, one all attainable gap.
|
|
102
|
+
f1_bounds_from_counts(tp, fp, fn, tn)
|
|
103
|
+
Sharp range of the best single-threshold F1 behind one confusion table
|
|
104
|
+
(40, 10, 10, 40 gives [0.8, 10/11]). A bound, not a confidence interval.
|
|
105
|
+
|
|
106
|
+
Command line
|
|
107
|
+
------------
|
|
108
|
+
|
|
109
|
+
refitgap evaluate --npz run_s0.npz run_s1.npz --seeds 0 1 [--npz-b ...] --out DIR
|
|
110
|
+
refitgap evaluate --arrays a_s0.npz ... [--arrays-b b_s0.npz ...] --out DIR
|
|
111
|
+
refitgap report (same inputs) --out DIR # adds three figures and report.html
|
|
112
|
+
refitgap example [--out DIR] # the tiny example below
|
|
113
|
+
refitgap --version
|
|
114
|
+
|
|
115
|
+
Settings: --periods, --mean-over, --shared-periods, --shared-weights, --splits (200),
|
|
116
|
+
--rule (inherited), --validation, --u-convention full|half, --no-reference, --margin
|
|
117
|
+
(0.005), --family NAME, --names, --workers. Two methods are paired by seed; stored label
|
|
118
|
+
names must agree. levels.csv: V_sep, V_shared, C, U_half, U_full, R, M per method, seed,
|
|
119
|
+
model, period and rule. decomposition.csv: B, A, dR per period against the per-period
|
|
120
|
+
reference. contrasts.csv: paired leads with per-seed values, mean, interval and p.
|
|
121
|
+
|
|
122
|
+
Tiny example
|
|
123
|
+
------------
|
|
124
|
+
|
|
125
|
+
Two periods of four records, two labels (a, b), seed 0, one partition, inherited thresholds 0.5.
|
|
126
|
+
The reference of period 1 is the final model itself.
|
|
127
|
+
|
|
128
|
+
period record labels a,b final a,b reference a,b
|
|
129
|
+
0 r0 1,0 0.4,0.6 0.9,0.3
|
|
130
|
+
0 r1 1,0 0.4,0.2 0.9,0.3
|
|
131
|
+
0 r2 0,1 0.1,0.8 0.2,0.8
|
|
132
|
+
0 r3 0,1 0.1,0.3 0.2,0.8
|
|
133
|
+
1 r0, r2 1,0 0.9,0.3 same as final
|
|
134
|
+
1 r1, r3 0,1 0.2,0.8 same as final
|
|
135
|
+
|
|
136
|
+
default_rng(7000).permutation(4) = [1, 2, 0, 3] and default_rng(7001) gives [3, 0, 1, 2],
|
|
137
|
+
so period 0 splits {r1, r2} | {r0, r3} and period 1 {r3, r0} | {r1, r2}. The reference
|
|
138
|
+
separates everything: V = U = 1. The final model on period 0 reaches V_sep = V_shared = 3/4
|
|
139
|
+
and U = 1/4 at 0.5, so C = 0 and M = R = 1/2; on period 1 it is the reference, V_sep = U = 1
|
|
140
|
+
and V_shared = 11/12. Period 0 thus splits B = 3/4 into A = 1/4 (all label b: positive r3 now
|
|
141
|
+
scores below negative r0) and dR = 1/2 (all label a: still separated, but below 0.5). The
|
|
142
|
+
tests check every printed value against these fractions to 1e-12.
|
|
143
|
+
|
|
144
|
+
Limits
|
|
145
|
+
------
|
|
146
|
+
|
|
147
|
+
A, C, M and R describe what thresholded predictions from the stored scores can reach: a
|
|
148
|
+
large A says the scores lost separation, not that the representation lost the information.
|
|
149
|
+
Every level survives any strictly increasing per-label map of scores and rules. The package
|
|
150
|
+
recommends nothing; retrain-or-recalibrate needs costs the scores do not carry.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""refitgap: thresholded forgetting, split into what the scores lost and what the thresholds lost.
|
|
2
|
+
|
|
3
|
+
Stored per-period scores and labels in; cross-fitted levels, the split
|
|
4
|
+
deficit = attainable gap + change in mismatch, and seed-paired contrasts out.
|
|
5
|
+
compare() handles two scorers on the same records, method_contrast() the paired lead
|
|
6
|
+
of one method over another. API.txt in this directory has the details. numpy and scipy
|
|
7
|
+
only; figures need matplotlib (extra "plots"). Command line: refitgap.
|
|
8
|
+
"""
|
|
9
|
+
from .fitters import GRID, f1_vec, fit_exact, fit_grid, fit_shared_exact, macro_f1, shared_objective
|
|
10
|
+
from .crossfit import (SPLITS, SPLIT_BASE, Evaluation, array_hash, decompose, evaluate, fit_validation_rule,
|
|
11
|
+
partitions)
|
|
12
|
+
from .inference import (ALPHA, MARGIN, classify, classify_unadjusted, family, family_ni, holm, paired_contrast,
|
|
13
|
+
paired_stats, paired_vector, dropped_seeds)
|
|
14
|
+
from .records import align_to, drop_unlabelled
|
|
15
|
+
from .runs import evaluate_npz, load_run
|
|
16
|
+
from .analysis import analyse, make_run, run_from_arrays, run_from_npz, save_arrays, write_tables
|
|
17
|
+
from .twoscorer import compare, method_contrast
|
|
18
|
+
from .identification import f1_bounds_from_counts, identification_example
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"GRID", "f1_vec", "fit_exact", "fit_grid", "fit_shared_exact", "macro_f1", "shared_objective", "SPLITS",
|
|
24
|
+
"SPLIT_BASE", "Evaluation", "array_hash", "decompose", "evaluate", "fit_validation_rule", "partitions",
|
|
25
|
+
"ALPHA", "MARGIN", "classify", "classify_unadjusted", "family", "family_ni", "holm", "paired_contrast",
|
|
26
|
+
"paired_stats", "paired_vector", "dropped_seeds", "align_to", "drop_unlabelled", "evaluate_npz",
|
|
27
|
+
"load_run", "analyse", "make_run", "run_from_arrays", "run_from_npz", "save_arrays", "write_tables",
|
|
28
|
+
"compare", "method_contrast", "f1_bounds_from_counts", "identification_example", "__version__",
|
|
29
|
+
]
|