jevkit-calibrate 0.1.0
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.
- package/README.md +93 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +150 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics.d.ts +131 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +251 -0
- package/dist/metrics.js.map +1 -0
- package/dist/records.d.ts +26 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +37 -0
- package/dist/records.js.map +1 -0
- package/dist/thresholds.d.ts +58 -0
- package/dist/thresholds.d.ts.map +1 -0
- package/dist/thresholds.js +97 -0
- package/dist/thresholds.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# jevkit-calibrate
|
|
2
|
+
|
|
3
|
+
Calibration is Jev's central claim: the probabilities are meant to track real
|
|
4
|
+
frequencies, so that among answers given 0.8, about 80% are right. TypeSafe
|
|
5
|
+
measures this across groups of predictions and says plainly that it does not
|
|
6
|
+
guarantee any individual answer.
|
|
7
|
+
|
|
8
|
+
This package checks the claim on **your** data, which is the part nobody else
|
|
9
|
+
can do for you, and turns the result into a threshold you can defend.
|
|
10
|
+
|
|
11
|
+
> Unofficial and unaffiliated with TypeSafe.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install jevkit-calibrate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Pure standard library. No numpy, no pandas, no plotting stack. A calibration
|
|
18
|
+
check that needs a build toolchain is a calibration check that does not get run.
|
|
19
|
+
|
|
20
|
+
## Measure
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from jevkit_core import read_records
|
|
24
|
+
from jevkit_calibrate import calibrate, observations_from_records
|
|
25
|
+
|
|
26
|
+
observations = observations_from_records(read_records("labeled.jevl"))
|
|
27
|
+
report = calibrate(observations)
|
|
28
|
+
|
|
29
|
+
print(report.summary())
|
|
30
|
+
print(report.diagram())
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
observations: 4000
|
|
35
|
+
accuracy: 0.7485
|
|
36
|
+
mean claimed: 0.7469 (underconfident by 0.0016)
|
|
37
|
+
ECE: 0.0094 (over 10 bins)
|
|
38
|
+
MCE: 0.0167
|
|
39
|
+
Brier: 0.1681
|
|
40
|
+
log loss: 0.5043
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`ECE` is the average gap between claimed and observed, weighted by how many
|
|
44
|
+
observations fall in each bin. `MCE` is the worst gap in any one bin, which is
|
|
45
|
+
what catches a healthy-looking ECE hiding one badly wrong region. `Brier` and
|
|
46
|
+
`log loss` are proper scoring rules: they reward being calibrated **and**
|
|
47
|
+
decisive, so a model that always says 0.5 scores badly even though it is
|
|
48
|
+
perfectly calibrated.
|
|
49
|
+
|
|
50
|
+
## Pick a threshold
|
|
51
|
+
|
|
52
|
+
TypeSafe's confidence page recommends three bands and says where you draw them
|
|
53
|
+
depends on your data. This draws them from the data.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from jevkit_calibrate import recommend_for_accuracy
|
|
57
|
+
|
|
58
|
+
point = recommend_for_accuracy(observations, target_accuracy=0.95)
|
|
59
|
+
if point is None:
|
|
60
|
+
print("no threshold reaches 95% on this data")
|
|
61
|
+
else:
|
|
62
|
+
print(f"threshold {point.threshold:.2f}: "
|
|
63
|
+
f"covers {point.coverage:.1%} at {point.accuracy:.1%}, "
|
|
64
|
+
f"{point.errors} wrong answers acted on")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`None` is a real answer, not a failure. It means this question cannot be
|
|
68
|
+
automated at that bar, and the honest move is to change the question rather
|
|
69
|
+
than lower the threshold.
|
|
70
|
+
|
|
71
|
+
## CLI
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
jevkit-calibrate labeled.jevl
|
|
75
|
+
jevkit-calibrate labeled.jevl --target-accuracy 0.95
|
|
76
|
+
jevkit-calibrate labeled.jevl --min-coverage 0.80
|
|
77
|
+
jevkit-calibrate labeled.jevl --max-ece 0.05 # CI gate
|
|
78
|
+
jevkit-calibrate labeled.jevl --format json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Which quantity gets calibrated
|
|
82
|
+
|
|
83
|
+
By default, the probability mass on the chosen outcome, which is what "when it
|
|
84
|
+
says 0.8, is it right 80% of the time" means.
|
|
85
|
+
|
|
86
|
+
`--use-confidence` calibrates the API's `confidence` statistic instead. That is
|
|
87
|
+
a different question, and the one to ask when you want to know whether your
|
|
88
|
+
*routing* threshold sits in the right place. Nouls carry no confidence, so they
|
|
89
|
+
fall back to probability either way.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** `jevkit-calibrate` command line interface. */
|
|
3
|
+
export declare const EXIT_OK = 0;
|
|
4
|
+
export declare const EXIT_FAILED_GATE = 1;
|
|
5
|
+
export declare const EXIT_USAGE = 2;
|
|
6
|
+
export declare function main(argv?: string[]): number;
|
|
7
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,iDAAiD;AAQjD,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,UAAU,IAAI,CAAC;AAgB5B,wBAAgB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,MAAM,CAuGnE"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** `jevkit-calibrate` command line interface. */
|
|
3
|
+
import { RecordFormatError, readRecords } from "jevkit-core";
|
|
4
|
+
import { calibrate } from "./metrics.js";
|
|
5
|
+
import { observationsFromRecords } from "./records.js";
|
|
6
|
+
import { recommendForAccuracy, recommendForCoverage } from "./thresholds.js";
|
|
7
|
+
export const EXIT_OK = 0;
|
|
8
|
+
export const EXIT_FAILED_GATE = 1;
|
|
9
|
+
export const EXIT_USAGE = 2;
|
|
10
|
+
const USAGE = `usage: jevkit-calibrate <labeled.jevl...> [options]
|
|
11
|
+
|
|
12
|
+
Measure how well Jev's probabilities match outcomes on your labeled data, and
|
|
13
|
+
pick a confidence threshold from it. Never calls the API.
|
|
14
|
+
|
|
15
|
+
--bins N reliability bins (default 10)
|
|
16
|
+
--question ID restrict to this question id (repeatable)
|
|
17
|
+
--use-confidence calibrate the API's confidence, not the probability
|
|
18
|
+
--target-accuracy F recommend the lowest threshold reaching this accuracy
|
|
19
|
+
--min-coverage F recommend the highest threshold still covering this
|
|
20
|
+
--max-ece F exit non-zero if ECE exceeds this
|
|
21
|
+
--format text|json
|
|
22
|
+
--no-diagram`;
|
|
23
|
+
export function main(argv = process.argv.slice(2)) {
|
|
24
|
+
const files = [];
|
|
25
|
+
const questions = [];
|
|
26
|
+
let bins = 10;
|
|
27
|
+
let useConfidence = false;
|
|
28
|
+
let targetAccuracy = null;
|
|
29
|
+
let minCoverage = null;
|
|
30
|
+
let maxEce = null;
|
|
31
|
+
let format = "text";
|
|
32
|
+
let noDiagram = false;
|
|
33
|
+
for (let i = 0; i < argv.length; i++) {
|
|
34
|
+
const arg = argv[i];
|
|
35
|
+
switch (arg) {
|
|
36
|
+
case "--bins":
|
|
37
|
+
bins = Number(argv[++i]);
|
|
38
|
+
break;
|
|
39
|
+
case "--question":
|
|
40
|
+
questions.push(String(argv[++i]));
|
|
41
|
+
break;
|
|
42
|
+
case "--use-confidence":
|
|
43
|
+
useConfidence = true;
|
|
44
|
+
break;
|
|
45
|
+
case "--target-accuracy":
|
|
46
|
+
targetAccuracy = Number(argv[++i]);
|
|
47
|
+
break;
|
|
48
|
+
case "--min-coverage":
|
|
49
|
+
minCoverage = Number(argv[++i]);
|
|
50
|
+
break;
|
|
51
|
+
case "--max-ece":
|
|
52
|
+
maxEce = Number(argv[++i]);
|
|
53
|
+
break;
|
|
54
|
+
case "--no-diagram":
|
|
55
|
+
noDiagram = true;
|
|
56
|
+
break;
|
|
57
|
+
case "--format": {
|
|
58
|
+
const v = argv[++i];
|
|
59
|
+
if (v !== "text" && v !== "json") {
|
|
60
|
+
console.error("jevkit-calibrate: --format expects text or json");
|
|
61
|
+
return EXIT_USAGE;
|
|
62
|
+
}
|
|
63
|
+
format = v;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "-h":
|
|
67
|
+
case "--help":
|
|
68
|
+
console.log(USAGE);
|
|
69
|
+
return EXIT_OK;
|
|
70
|
+
default:
|
|
71
|
+
if (arg.startsWith("--")) {
|
|
72
|
+
console.error(`jevkit-calibrate: unknown option ${arg}`);
|
|
73
|
+
return EXIT_USAGE;
|
|
74
|
+
}
|
|
75
|
+
files.push(arg);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!files.length) {
|
|
79
|
+
console.error(USAGE);
|
|
80
|
+
return EXIT_USAGE;
|
|
81
|
+
}
|
|
82
|
+
const records = [];
|
|
83
|
+
try {
|
|
84
|
+
for (const path of files)
|
|
85
|
+
records.push(...readRecords(path));
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const message = err instanceof RecordFormatError ? err.message : err.message;
|
|
89
|
+
console.error(`jevkit-calibrate: ${message}`);
|
|
90
|
+
return EXIT_USAGE;
|
|
91
|
+
}
|
|
92
|
+
const observations = observationsFromRecords(records, {
|
|
93
|
+
questionIds: questions.length ? questions : undefined,
|
|
94
|
+
useConfidence,
|
|
95
|
+
});
|
|
96
|
+
if (!observations.length) {
|
|
97
|
+
console.error("jevkit-calibrate: no labeled observations found. Records need a 'label' object " +
|
|
98
|
+
"keyed by question id.");
|
|
99
|
+
return EXIT_USAGE;
|
|
100
|
+
}
|
|
101
|
+
const report = calibrate(observations, bins);
|
|
102
|
+
const recommendation = targetAccuracy !== null
|
|
103
|
+
? { kind: "accuracy", target: targetAccuracy, point: recommendForAccuracy(observations, targetAccuracy) }
|
|
104
|
+
: minCoverage !== null
|
|
105
|
+
? { kind: "coverage", target: minCoverage, point: recommendForCoverage(observations, minCoverage) }
|
|
106
|
+
: null;
|
|
107
|
+
if (format === "json") {
|
|
108
|
+
const payload = { ...report.toJSON() };
|
|
109
|
+
if (recommendation) {
|
|
110
|
+
const p = recommendation.point;
|
|
111
|
+
payload["recommendation"] = {
|
|
112
|
+
for: recommendation.kind, target: recommendation.target,
|
|
113
|
+
threshold: p?.threshold ?? null, coverage: p?.coverage ?? null,
|
|
114
|
+
accuracy: p?.accuracy ?? null, errors: p?.errors ?? null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(report.summary());
|
|
121
|
+
if (!noDiagram) {
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(report.diagram());
|
|
124
|
+
}
|
|
125
|
+
if (recommendation) {
|
|
126
|
+
console.log();
|
|
127
|
+
const { kind, target, point } = recommendation;
|
|
128
|
+
if (!point) {
|
|
129
|
+
console.log(kind === "accuracy"
|
|
130
|
+
? `No threshold reaches ${(target * 100).toFixed(1)}% accuracy on this data. The ` +
|
|
131
|
+
`question cannot be automated at that bar; change the question rather than the threshold.`
|
|
132
|
+
: `No threshold covers ${(target * 100).toFixed(1)}% of cases.`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(`threshold ${point.threshold.toFixed(2)}: covers ${(point.coverage * 100).toFixed(1)}% ` +
|
|
136
|
+
`(${point.covered}/${point.total}) at ${(point.accuracy * 100).toFixed(1)}% accuracy, ` +
|
|
137
|
+
`${point.errors} wrong answer(s) acted on, ${point.escalated} escalated`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (maxEce !== null && report.ece > maxEce) {
|
|
142
|
+
console.error(`jevkit-calibrate: ECE ${report.ece.toFixed(4)} exceeds ${maxEce}`);
|
|
143
|
+
return EXIT_FAILED_GATE;
|
|
144
|
+
}
|
|
145
|
+
return EXIT_OK;
|
|
146
|
+
}
|
|
147
|
+
const invokedDirectly = process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`;
|
|
148
|
+
if (invokedDirectly)
|
|
149
|
+
process.exit(main());
|
|
150
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Jev's calibration on your own data, and pick thresholds from it.
|
|
3
|
+
*
|
|
4
|
+
* Calibration is the central claim behind a System One model, and the one thing
|
|
5
|
+
* a user cannot check without tooling. This package computes reliability
|
|
6
|
+
* diagrams, ECE, MCE, Brier and log loss over labeled answers, and turns a
|
|
7
|
+
* labeled set into a defensible confidence threshold.
|
|
8
|
+
*
|
|
9
|
+
* No dependencies beyond jevkit-core. A calibration check that drags in a
|
|
10
|
+
* numerical stack is a calibration check that does not get run.
|
|
11
|
+
*/
|
|
12
|
+
export { Bin, CalibrationReport, Observation, brierScore, calibrate, expectedCalibrationError, logLoss, maximumCalibrationError, reliabilityBins, type ObservationInit, } from "./metrics.js";
|
|
13
|
+
export { observationsFromRecords, type ExtractOptions } from "./records.js";
|
|
14
|
+
export { ThresholdPoint, recommendForAccuracy, recommendForCoverage, sweep, } from "./thresholds.js";
|
|
15
|
+
export declare const VERSION = "0.1.0";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAC1D,wBAAwB,EAAE,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAC3E,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,uBAAuB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EACL,cAAc,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,KAAK,GAClE,MAAM,iBAAiB,CAAC;AAEzB,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Jev's calibration on your own data, and pick thresholds from it.
|
|
3
|
+
*
|
|
4
|
+
* Calibration is the central claim behind a System One model, and the one thing
|
|
5
|
+
* a user cannot check without tooling. This package computes reliability
|
|
6
|
+
* diagrams, ECE, MCE, Brier and log loss over labeled answers, and turns a
|
|
7
|
+
* labeled set into a defensible confidence threshold.
|
|
8
|
+
*
|
|
9
|
+
* No dependencies beyond jevkit-core. A calibration check that drags in a
|
|
10
|
+
* numerical stack is a calibration check that does not get run.
|
|
11
|
+
*/
|
|
12
|
+
export { Bin, CalibrationReport, Observation, brierScore, calibrate, expectedCalibrationError, logLoss, maximumCalibrationError, reliabilityBins, } from "./metrics.js";
|
|
13
|
+
export { observationsFromRecords } from "./records.js";
|
|
14
|
+
export { ThresholdPoint, recommendForAccuracy, recommendForCoverage, sweep, } from "./thresholds.js";
|
|
15
|
+
export const VERSION = "0.1.0";
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAC1D,wBAAwB,EAAE,OAAO,EAAE,uBAAuB,EAAE,eAAe,GAE5E,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,uBAAuB,EAAuB,MAAM,cAAc,CAAC;AAC5E,OAAO,EACL,cAAc,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,KAAK,GAClE,MAAM,iBAAiB,CAAC;AAEzB,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calibration metrics over labeled jev answers.
|
|
3
|
+
*
|
|
4
|
+
* Calibration is Jev's central claim: the probabilities are meant to reflect
|
|
5
|
+
* real-world frequencies, so that among answers given 0.8, about 80% are right.
|
|
6
|
+
* TypeSafe measures this across groups of predictions and says plainly that it
|
|
7
|
+
* does not guarantee any individual answer. This module is how you check the
|
|
8
|
+
* claim on *your* data, which is the part nobody else can do for you.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is pure arithmetic over recorded answers. Nothing calls the
|
|
11
|
+
* API and there are no dependencies: a calibration check that drags in a
|
|
12
|
+
* numerical stack is a calibration check that does not get run.
|
|
13
|
+
*/
|
|
14
|
+
export interface ObservationInit {
|
|
15
|
+
probability: number;
|
|
16
|
+
correct: boolean;
|
|
17
|
+
questionId?: string;
|
|
18
|
+
requestId?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One labeled prediction.
|
|
22
|
+
*
|
|
23
|
+
* `probability` is the model's stated probability for the outcome it chose;
|
|
24
|
+
* `correct` is whether that outcome matched the label.
|
|
25
|
+
*/
|
|
26
|
+
export declare class Observation {
|
|
27
|
+
readonly probability: number;
|
|
28
|
+
readonly correct: boolean;
|
|
29
|
+
readonly questionId: string;
|
|
30
|
+
readonly requestId: string;
|
|
31
|
+
constructor(init: ObservationInit);
|
|
32
|
+
}
|
|
33
|
+
/** One bucket of a reliability diagram. */
|
|
34
|
+
export declare class Bin {
|
|
35
|
+
readonly lower: number;
|
|
36
|
+
readonly upper: number;
|
|
37
|
+
readonly observations: Observation[];
|
|
38
|
+
constructor(lower: number, upper: number);
|
|
39
|
+
get count(): number;
|
|
40
|
+
/** What the model claimed, on average. */
|
|
41
|
+
get meanProbability(): number;
|
|
42
|
+
/** What actually happened. */
|
|
43
|
+
get accuracy(): number;
|
|
44
|
+
/** Claimed minus observed. Positive means overconfident. */
|
|
45
|
+
get gap(): number;
|
|
46
|
+
label(): string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Bucket observations into equal-width probability bins.
|
|
50
|
+
*
|
|
51
|
+
* Equal-width rather than equal-count, because the question being asked is
|
|
52
|
+
* "when the model says 0.9, is it right 90% of the time", and that question is
|
|
53
|
+
* about fixed probability ranges. Empty bins are kept so the diagram does not
|
|
54
|
+
* silently mislead about coverage.
|
|
55
|
+
*/
|
|
56
|
+
export declare function reliabilityBins(observations: Observation[], nBins?: number): Bin[];
|
|
57
|
+
/**
|
|
58
|
+
* ECE: average gap between claimed and observed, weighted by bin population.
|
|
59
|
+
*
|
|
60
|
+
* 0 is perfect. ECE depends on the bin count, so compare values only when they
|
|
61
|
+
* were computed with the same `nBins`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function expectedCalibrationError(observations: Observation[], nBins?: number): number;
|
|
64
|
+
/**
|
|
65
|
+
* MCE: the worst gap in any populated bin.
|
|
66
|
+
*
|
|
67
|
+
* ECE can look healthy while one region is badly wrong. MCE catches that, and
|
|
68
|
+
* it is the number that matters when one region is where your high-stakes
|
|
69
|
+
* decisions live.
|
|
70
|
+
*/
|
|
71
|
+
export declare function maximumCalibrationError(observations: Observation[], nBins?: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* Mean squared error between probability and outcome. Lower is better.
|
|
74
|
+
*
|
|
75
|
+
* Unlike ECE this is a proper scoring rule: it rewards being both calibrated
|
|
76
|
+
* and decisive, so a model that always says 0.5 scores poorly even though it is
|
|
77
|
+
* perfectly calibrated.
|
|
78
|
+
*/
|
|
79
|
+
export declare function brierScore(observations: Observation[]): number;
|
|
80
|
+
/**
|
|
81
|
+
* Mean negative log likelihood. Lower is better.
|
|
82
|
+
*
|
|
83
|
+
* Punishes confident mistakes far harder than Brier does. `eps` clamps the
|
|
84
|
+
* probability away from 0 and 1, since an unclamped confident miss is infinite
|
|
85
|
+
* and would swamp every other observation.
|
|
86
|
+
*/
|
|
87
|
+
export declare function logLoss(observations: Observation[], eps?: number): number;
|
|
88
|
+
export declare class CalibrationReport {
|
|
89
|
+
readonly observations: Observation[];
|
|
90
|
+
readonly nBins: number;
|
|
91
|
+
constructor(observations: Observation[], nBins?: number);
|
|
92
|
+
get count(): number;
|
|
93
|
+
get accuracy(): number;
|
|
94
|
+
get meanProbability(): number;
|
|
95
|
+
get bins(): Bin[];
|
|
96
|
+
get ece(): number;
|
|
97
|
+
get mce(): number;
|
|
98
|
+
get brier(): number;
|
|
99
|
+
get logLoss(): number;
|
|
100
|
+
/** Claimed probability exceeds observed accuracy overall. */
|
|
101
|
+
get overconfident(): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* A text reliability diagram.
|
|
104
|
+
*
|
|
105
|
+
* Each row shows a bin's claimed probability against what actually happened,
|
|
106
|
+
* so a miscalibrated region is visible without plotting.
|
|
107
|
+
*/
|
|
108
|
+
diagram(width?: number): string;
|
|
109
|
+
summary(): string;
|
|
110
|
+
toJSON(): {
|
|
111
|
+
count: number;
|
|
112
|
+
accuracy: number;
|
|
113
|
+
meanProbability: number;
|
|
114
|
+
overconfident: boolean;
|
|
115
|
+
ece: number;
|
|
116
|
+
mce: number;
|
|
117
|
+
brier: number;
|
|
118
|
+
logLoss: number;
|
|
119
|
+
nBins: number;
|
|
120
|
+
bins: {
|
|
121
|
+
lower: number;
|
|
122
|
+
upper: number;
|
|
123
|
+
count: number;
|
|
124
|
+
meanProbability: number;
|
|
125
|
+
accuracy: number;
|
|
126
|
+
gap: number;
|
|
127
|
+
}[];
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export declare function calibrate(observations: Iterable<Observation>, nBins?: number): CalibrationReport;
|
|
131
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,IAAI,EAAE,eAAe;CASlC;AAED,2CAA2C;AAC3C,qBAAa,GAAG;IAGF,QAAQ,CAAC,KAAK,EAAE,MAAM;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM;IAF1D,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE,CAAM;gBAErB,KAAK,EAAE,MAAM,EAAW,KAAK,EAAE,MAAM;IAE1D,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,0CAA0C;IAC1C,IAAI,eAAe,IAAI,MAAM,CAG5B;IAED,8BAA8B;IAC9B,IAAI,QAAQ,IAAI,MAAM,CAGrB;IAED,4DAA4D;IAC5D,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,KAAK,IAAI,MAAM;CAGhB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAK,GAAG,GAAG,EAAE,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAK,GAAG,MAAM,CAMxF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAK,GAAG,MAAM,CAIvF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAM9D;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,GAAG,SAAQ,GAAG,MAAM,CAQxE;AAED,qBAAa,iBAAiB;IAChB,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE;IAAE,QAAQ,CAAC,KAAK;gBAA3C,YAAY,EAAE,WAAW,EAAE,EAAW,KAAK,SAAK;IAErE,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,QAAQ,IAAI,MAAM,CAGrB;IAED,IAAI,eAAe,IAAI,MAAM,CAG5B;IAED,IAAI,IAAI,IAAI,GAAG,EAAE,CAEhB;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,6DAA6D;IAC7D,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;;;OAKG;IACH,OAAO,CAAC,KAAK,SAAK,GAAG,MAAM;IA6B3B,OAAO,IAAI,MAAM;IAejB,MAAM;;;;;;;;;;;;;;;;;;;CAiBP;AAED,wBAAgB,SAAS,CAAC,YAAY,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,SAAK,GAAG,iBAAiB,CAE5F"}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calibration metrics over labeled jev answers.
|
|
3
|
+
*
|
|
4
|
+
* Calibration is Jev's central claim: the probabilities are meant to reflect
|
|
5
|
+
* real-world frequencies, so that among answers given 0.8, about 80% are right.
|
|
6
|
+
* TypeSafe measures this across groups of predictions and says plainly that it
|
|
7
|
+
* does not guarantee any individual answer. This module is how you check the
|
|
8
|
+
* claim on *your* data, which is the part nobody else can do for you.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is pure arithmetic over recorded answers. Nothing calls the
|
|
11
|
+
* API and there are no dependencies: a calibration check that drags in a
|
|
12
|
+
* numerical stack is a calibration check that does not get run.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* One labeled prediction.
|
|
16
|
+
*
|
|
17
|
+
* `probability` is the model's stated probability for the outcome it chose;
|
|
18
|
+
* `correct` is whether that outcome matched the label.
|
|
19
|
+
*/
|
|
20
|
+
export class Observation {
|
|
21
|
+
probability;
|
|
22
|
+
correct;
|
|
23
|
+
questionId;
|
|
24
|
+
requestId;
|
|
25
|
+
constructor(init) {
|
|
26
|
+
if (!(init.probability >= 0 && init.probability <= 1)) {
|
|
27
|
+
throw new RangeError(`probability must be in [0, 1], got ${init.probability}`);
|
|
28
|
+
}
|
|
29
|
+
this.probability = init.probability;
|
|
30
|
+
this.correct = init.correct;
|
|
31
|
+
this.questionId = init.questionId ?? "";
|
|
32
|
+
this.requestId = init.requestId ?? "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** One bucket of a reliability diagram. */
|
|
36
|
+
export class Bin {
|
|
37
|
+
lower;
|
|
38
|
+
upper;
|
|
39
|
+
observations = [];
|
|
40
|
+
constructor(lower, upper) {
|
|
41
|
+
this.lower = lower;
|
|
42
|
+
this.upper = upper;
|
|
43
|
+
}
|
|
44
|
+
get count() {
|
|
45
|
+
return this.observations.length;
|
|
46
|
+
}
|
|
47
|
+
/** What the model claimed, on average. */
|
|
48
|
+
get meanProbability() {
|
|
49
|
+
if (!this.count)
|
|
50
|
+
return 0;
|
|
51
|
+
return this.observations.reduce((a, o) => a + o.probability, 0) / this.count;
|
|
52
|
+
}
|
|
53
|
+
/** What actually happened. */
|
|
54
|
+
get accuracy() {
|
|
55
|
+
if (!this.count)
|
|
56
|
+
return 0;
|
|
57
|
+
return this.observations.filter((o) => o.correct).length / this.count;
|
|
58
|
+
}
|
|
59
|
+
/** Claimed minus observed. Positive means overconfident. */
|
|
60
|
+
get gap() {
|
|
61
|
+
return this.meanProbability - this.accuracy;
|
|
62
|
+
}
|
|
63
|
+
label() {
|
|
64
|
+
return `[${this.lower.toFixed(2)}, ${this.upper.toFixed(2)}${this.upper === 1 ? "]" : ")"}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Bucket observations into equal-width probability bins.
|
|
69
|
+
*
|
|
70
|
+
* Equal-width rather than equal-count, because the question being asked is
|
|
71
|
+
* "when the model says 0.9, is it right 90% of the time", and that question is
|
|
72
|
+
* about fixed probability ranges. Empty bins are kept so the diagram does not
|
|
73
|
+
* silently mislead about coverage.
|
|
74
|
+
*/
|
|
75
|
+
export function reliabilityBins(observations, nBins = 10) {
|
|
76
|
+
if (nBins < 1)
|
|
77
|
+
throw new RangeError("nBins must be at least 1");
|
|
78
|
+
const bins = [];
|
|
79
|
+
for (let i = 0; i < nBins; i++)
|
|
80
|
+
bins.push(new Bin(i / nBins, (i + 1) / nBins));
|
|
81
|
+
for (const obs of observations) {
|
|
82
|
+
// The top bin is closed so a probability of exactly 1 lands in it.
|
|
83
|
+
const index = Math.min(Math.floor(obs.probability * nBins), nBins - 1);
|
|
84
|
+
bins[index].observations.push(obs);
|
|
85
|
+
}
|
|
86
|
+
return bins;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* ECE: average gap between claimed and observed, weighted by bin population.
|
|
90
|
+
*
|
|
91
|
+
* 0 is perfect. ECE depends on the bin count, so compare values only when they
|
|
92
|
+
* were computed with the same `nBins`.
|
|
93
|
+
*/
|
|
94
|
+
export function expectedCalibrationError(observations, nBins = 10) {
|
|
95
|
+
if (!observations.length)
|
|
96
|
+
return 0;
|
|
97
|
+
const total = observations.length;
|
|
98
|
+
return reliabilityBins(observations, nBins)
|
|
99
|
+
.filter((b) => b.count)
|
|
100
|
+
.reduce((sum, b) => sum + (b.count / total) * Math.abs(b.gap), 0);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* MCE: the worst gap in any populated bin.
|
|
104
|
+
*
|
|
105
|
+
* ECE can look healthy while one region is badly wrong. MCE catches that, and
|
|
106
|
+
* it is the number that matters when one region is where your high-stakes
|
|
107
|
+
* decisions live.
|
|
108
|
+
*/
|
|
109
|
+
export function maximumCalibrationError(observations, nBins = 10) {
|
|
110
|
+
if (!observations.length)
|
|
111
|
+
return 0;
|
|
112
|
+
const populated = reliabilityBins(observations, nBins).filter((b) => b.count);
|
|
113
|
+
return populated.reduce((m, b) => Math.max(m, Math.abs(b.gap)), 0);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Mean squared error between probability and outcome. Lower is better.
|
|
117
|
+
*
|
|
118
|
+
* Unlike ECE this is a proper scoring rule: it rewards being both calibrated
|
|
119
|
+
* and decisive, so a model that always says 0.5 scores poorly even though it is
|
|
120
|
+
* perfectly calibrated.
|
|
121
|
+
*/
|
|
122
|
+
export function brierScore(observations) {
|
|
123
|
+
if (!observations.length)
|
|
124
|
+
return 0;
|
|
125
|
+
return (observations.reduce((sum, o) => sum + (o.probability - (o.correct ? 1 : 0)) ** 2, 0) /
|
|
126
|
+
observations.length);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Mean negative log likelihood. Lower is better.
|
|
130
|
+
*
|
|
131
|
+
* Punishes confident mistakes far harder than Brier does. `eps` clamps the
|
|
132
|
+
* probability away from 0 and 1, since an unclamped confident miss is infinite
|
|
133
|
+
* and would swamp every other observation.
|
|
134
|
+
*/
|
|
135
|
+
export function logLoss(observations, eps = 1e-15) {
|
|
136
|
+
if (!observations.length)
|
|
137
|
+
return 0;
|
|
138
|
+
let total = 0;
|
|
139
|
+
for (const o of observations) {
|
|
140
|
+
const p = Math.min(Math.max(o.probability, eps), 1 - eps);
|
|
141
|
+
total -= o.correct ? Math.log(p) : Math.log(1 - p);
|
|
142
|
+
}
|
|
143
|
+
return total / observations.length;
|
|
144
|
+
}
|
|
145
|
+
export class CalibrationReport {
|
|
146
|
+
observations;
|
|
147
|
+
nBins;
|
|
148
|
+
constructor(observations, nBins = 10) {
|
|
149
|
+
this.observations = observations;
|
|
150
|
+
this.nBins = nBins;
|
|
151
|
+
}
|
|
152
|
+
get count() {
|
|
153
|
+
return this.observations.length;
|
|
154
|
+
}
|
|
155
|
+
get accuracy() {
|
|
156
|
+
if (!this.count)
|
|
157
|
+
return 0;
|
|
158
|
+
return this.observations.filter((o) => o.correct).length / this.count;
|
|
159
|
+
}
|
|
160
|
+
get meanProbability() {
|
|
161
|
+
if (!this.count)
|
|
162
|
+
return 0;
|
|
163
|
+
return this.observations.reduce((a, o) => a + o.probability, 0) / this.count;
|
|
164
|
+
}
|
|
165
|
+
get bins() {
|
|
166
|
+
return reliabilityBins(this.observations, this.nBins);
|
|
167
|
+
}
|
|
168
|
+
get ece() {
|
|
169
|
+
return expectedCalibrationError(this.observations, this.nBins);
|
|
170
|
+
}
|
|
171
|
+
get mce() {
|
|
172
|
+
return maximumCalibrationError(this.observations, this.nBins);
|
|
173
|
+
}
|
|
174
|
+
get brier() {
|
|
175
|
+
return brierScore(this.observations);
|
|
176
|
+
}
|
|
177
|
+
get logLoss() {
|
|
178
|
+
return logLoss(this.observations);
|
|
179
|
+
}
|
|
180
|
+
/** Claimed probability exceeds observed accuracy overall. */
|
|
181
|
+
get overconfident() {
|
|
182
|
+
return this.meanProbability > this.accuracy;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A text reliability diagram.
|
|
186
|
+
*
|
|
187
|
+
* Each row shows a bin's claimed probability against what actually happened,
|
|
188
|
+
* so a miscalibrated region is visible without plotting.
|
|
189
|
+
*/
|
|
190
|
+
diagram(width = 28) {
|
|
191
|
+
const lines = [
|
|
192
|
+
`${"bin".padEnd(14)} ${"n".padStart(5)} ${"claimed".padStart(8)} ` +
|
|
193
|
+
`${"actual".padStart(8)} ${"gap".padStart(7)}`,
|
|
194
|
+
"-".repeat(46),
|
|
195
|
+
];
|
|
196
|
+
for (const b of this.bins) {
|
|
197
|
+
if (!b.count) {
|
|
198
|
+
lines.push(`${b.label().padEnd(14)} ${String(0).padStart(5)} ${"-".padStart(8)} ` +
|
|
199
|
+
`${"-".padStart(8)} ${"-".padStart(7)}`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const barLen = Math.floor(b.accuracy * width);
|
|
203
|
+
const marker = Math.floor(b.meanProbability * width);
|
|
204
|
+
const cells = Array.from({ length: width }, (_, i) => (i < barLen ? "#" : " "));
|
|
205
|
+
if (marker >= 0 && marker < width)
|
|
206
|
+
cells[marker] = cells[marker] === " " ? "|" : "+";
|
|
207
|
+
const gap = `${b.gap >= 0 ? "+" : ""}${b.gap.toFixed(3)}`;
|
|
208
|
+
lines.push(`${b.label().padEnd(14)} ${String(b.count).padStart(5)} ` +
|
|
209
|
+
`${b.meanProbability.toFixed(3).padStart(8)} ${b.accuracy.toFixed(3).padStart(8)} ` +
|
|
210
|
+
`${gap.padStart(7)} ${cells.join("")}`);
|
|
211
|
+
}
|
|
212
|
+
lines.push("", " # observed accuracy, | claimed probability, + both");
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
summary() {
|
|
216
|
+
if (!this.count)
|
|
217
|
+
return "no labeled observations";
|
|
218
|
+
const direction = this.overconfident ? "overconfident" : "underconfident";
|
|
219
|
+
const gap = Math.abs(this.meanProbability - this.accuracy);
|
|
220
|
+
return [
|
|
221
|
+
`observations: ${this.count}`,
|
|
222
|
+
`accuracy: ${this.accuracy.toFixed(4)}`,
|
|
223
|
+
`mean claimed: ${this.meanProbability.toFixed(4)} (${direction} by ${gap.toFixed(4)})`,
|
|
224
|
+
`ECE: ${this.ece.toFixed(4)} (over ${this.nBins} bins)`,
|
|
225
|
+
`MCE: ${this.mce.toFixed(4)}`,
|
|
226
|
+
`Brier: ${this.brier.toFixed(4)}`,
|
|
227
|
+
`log loss: ${this.logLoss.toFixed(4)}`,
|
|
228
|
+
].join("\n");
|
|
229
|
+
}
|
|
230
|
+
toJSON() {
|
|
231
|
+
return {
|
|
232
|
+
count: this.count,
|
|
233
|
+
accuracy: this.accuracy,
|
|
234
|
+
meanProbability: this.meanProbability,
|
|
235
|
+
overconfident: this.overconfident,
|
|
236
|
+
ece: this.ece,
|
|
237
|
+
mce: this.mce,
|
|
238
|
+
brier: this.brier,
|
|
239
|
+
logLoss: this.logLoss,
|
|
240
|
+
nBins: this.nBins,
|
|
241
|
+
bins: this.bins.map((b) => ({
|
|
242
|
+
lower: b.lower, upper: b.upper, count: b.count,
|
|
243
|
+
meanProbability: b.meanProbability, accuracy: b.accuracy, gap: b.gap,
|
|
244
|
+
})),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
export function calibrate(observations, nBins = 10) {
|
|
249
|
+
return new CalibrationReport([...observations], nBins);
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IACb,WAAW,CAAS;IACpB,OAAO,CAAU;IACjB,UAAU,CAAS;IACnB,SAAS,CAAS;IAE3B,YAAY,IAAqB;QAC/B,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,UAAU,CAAC,sCAAsC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;IACxC,CAAC;CACF;AAED,2CAA2C;AAC3C,MAAM,OAAO,GAAG;IAGO;IAAwB;IAFpC,YAAY,GAAkB,EAAE,CAAC;IAE1C,YAAqB,KAAa,EAAW,KAAa;QAArC,UAAK,GAAL,KAAK,CAAQ;QAAW,UAAK,GAAL,KAAK,CAAQ;IAAG,CAAC;IAE9D,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;IAClC,CAAC;IAED,0CAA0C;IAC1C,IAAI,eAAe;QACjB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;IAC/E,CAAC;IAED,8BAA8B;IAC9B,IAAI,QAAQ;QACV,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;IACxE,CAAC;IAED,4DAA4D;IAC5D,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC9C,CAAC;IAED,KAAK;QACH,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAC9F,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,YAA2B,EAAE,KAAK,GAAG,EAAE;IACrE,IAAI,KAAK,GAAG,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,0BAA0B,CAAC,CAAC;IAChE,MAAM,IAAI,GAAU,EAAE,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC/E,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,mEAAmE;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,KAAK,CAAE,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,YAA2B,EAAE,KAAK,GAAG,EAAE;IAC9E,IAAI,CAAC,YAAY,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC;IAClC,OAAO,eAAe,CAAC,YAAY,EAAE,KAAK,CAAC;SACxC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;SACtB,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,YAA2B,EAAE,KAAK,GAAG,EAAE;IAC7E,IAAI,CAAC,YAAY,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,eAAe,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9E,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,YAA2B;IACpD,IAAI,CAAC,YAAY,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACnC,OAAO,CACL,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpF,YAAY,CAAC,MAAM,CACpB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,YAA2B,EAAE,GAAG,GAAG,KAAK;IAC9D,IAAI,CAAC,YAAY,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC;QAC1D,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC;AACrC,CAAC;AAED,MAAM,OAAO,iBAAiB;IACP;IAAsC;IAA3D,YAAqB,YAA2B,EAAW,QAAQ,EAAE;QAAhD,iBAAY,GAAZ,YAAY,CAAe;QAAW,UAAK,GAAL,KAAK,CAAK;IAAG,CAAC;IAEzE,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;IAClC,CAAC;IAED,IAAI,QAAQ;QACV,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;IACxE,CAAC;IAED,IAAI,eAAe;QACjB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;IAC/E,CAAC;IAED,IAAI,IAAI;QACN,OAAO,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,GAAG;QACL,OAAO,wBAAwB,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IAED,IAAI,GAAG;QACL,OAAO,uBAAuB,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,KAAK;QACP,OAAO,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,OAAO;QACT,OAAO,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC;IAED,6DAA6D;IAC7D,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC9C,CAAC;IAED;;;;;OAKG;IACH,OAAO,CAAC,KAAK,GAAG,EAAE;QAChB,MAAM,KAAK,GAAG;YACZ,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;gBAChE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;YAChD,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;SACf,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC1B,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CACR,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;oBACpE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAC1C,CAAC;gBACF,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC,CAAC;YACrD,MAAM,KAAK,GAAa,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1F,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,GAAG,KAAK;gBAAE,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YACrF,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,KAAK,CAAC,IAAI,CACR,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;gBACvD,GAAG,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;gBACnF,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,sDAAsD,CAAC,CAAC;QACvE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO;QACL,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,yBAAyB,CAAC;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAC;QAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3D,OAAO;YACL,iBAAiB,IAAI,CAAC,KAAK,EAAE;YAC7B,iBAAiB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YAC3C,iBAAiB,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,SAAS,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;YACvF,iBAAiB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,KAAK,QAAQ;YACjE,iBAAiB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACtC,iBAAiB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACxC,iBAAiB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;SAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAED,MAAM;QACJ,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1B,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK;gBAC9C,eAAe,EAAE,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG;aACrE,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,SAAS,CAAC,YAAmC,EAAE,KAAK,GAAG,EAAE;IACvE,OAAO,IAAI,iBAAiB,CAAC,CAAC,GAAG,YAAY,CAAC,EAAE,KAAK,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Turning labeled `.jevl` records into calibration observations. */
|
|
2
|
+
import { type Record as JevlRecord } from "jevkit-core";
|
|
3
|
+
import { Observation } from "./metrics.js";
|
|
4
|
+
export interface ExtractOptions {
|
|
5
|
+
questionIds?: Iterable<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Which quantity to calibrate.
|
|
8
|
+
*
|
|
9
|
+
* The default, `false`, uses the probability mass on the chosen outcome,
|
|
10
|
+
* which is what "when it says 0.8, is it right 80% of the time" means.
|
|
11
|
+
* `true` calibrates the API's `confidence` statistic instead, which is a
|
|
12
|
+
* different question and answers whether your *routing* threshold is well
|
|
13
|
+
* placed. Nouls have no confidence, so they fall back to probability either
|
|
14
|
+
* way.
|
|
15
|
+
*/
|
|
16
|
+
useConfidence?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extract one observation per labeled answer.
|
|
20
|
+
*
|
|
21
|
+
* Records with no `label` are skipped: calibration needs ground truth, and
|
|
22
|
+
* silently treating an unlabeled record as correct or incorrect would poison
|
|
23
|
+
* every number downstream.
|
|
24
|
+
*/
|
|
25
|
+
export declare function observationsFromRecords(records: Iterable<JevlRecord>, options?: ExtractOptions): Observation[];
|
|
26
|
+
//# sourceMappingURL=records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAErE,OAAO,EAAE,KAAK,MAAM,IAAI,UAAU,EAAgB,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC7B,OAAO,GAAE,cAAmB,GAC3B,WAAW,EAAE,CA0Bf"}
|
package/dist/records.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Turning labeled `.jevl` records into calibration observations. */
|
|
2
|
+
import { parseAnswers } from "jevkit-core";
|
|
3
|
+
import { Observation } from "./metrics.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extract one observation per labeled answer.
|
|
6
|
+
*
|
|
7
|
+
* Records with no `label` are skipped: calibration needs ground truth, and
|
|
8
|
+
* silently treating an unlabeled record as correct or incorrect would poison
|
|
9
|
+
* every number downstream.
|
|
10
|
+
*/
|
|
11
|
+
export function observationsFromRecords(records, options = {}) {
|
|
12
|
+
const wanted = options.questionIds ? new Set(options.questionIds) : null;
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const record of records) {
|
|
15
|
+
if (!record.label || !Object.keys(record.label).length)
|
|
16
|
+
continue;
|
|
17
|
+
const answers = parseAnswers(record.answers);
|
|
18
|
+
for (const [qid, label] of Object.entries(record.label)) {
|
|
19
|
+
if (wanted && !wanted.has(qid))
|
|
20
|
+
continue;
|
|
21
|
+
const answer = answers[qid];
|
|
22
|
+
if (!answer)
|
|
23
|
+
continue;
|
|
24
|
+
const probability = options.useConfidence && answer.confidence !== null
|
|
25
|
+
? answer.confidence
|
|
26
|
+
: answer.topProbability;
|
|
27
|
+
out.push(new Observation({
|
|
28
|
+
probability,
|
|
29
|
+
correct: answer.isCorrect(label),
|
|
30
|
+
questionId: qid,
|
|
31
|
+
requestId: record.requestId,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAErE,OAAO,EAA6B,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAiB3C;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA6B,EAC7B,UAA0B,EAAE;IAE5B,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,MAAM,GAAG,GAAkB,EAAE,CAAC;IAE9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM;YAAE,SAAS;QACjE,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YACzC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,WAAW,GACf,OAAO,CAAC,aAAa,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI;gBACjD,CAAC,CAAC,MAAM,CAAC,UAAU;gBACnB,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC;YAC5B,GAAG,CAAC,IAAI,CACN,IAAI,WAAW,CAAC;gBACd,WAAW;gBACX,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;gBAChC,UAAU,EAAE,GAAG;gBACf,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC,CACH,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choosing a confidence threshold from labeled data instead of guessing.
|
|
3
|
+
*
|
|
4
|
+
* TypeSafe's confidence page recommends three bands: act automatically, proceed
|
|
5
|
+
* with caution, escalate. It also says plainly that where you draw those lines
|
|
6
|
+
* depends on your domain and your data. This module draws them from the data.
|
|
7
|
+
*
|
|
8
|
+
* The trade is always the same. Raising the threshold means acting on fewer
|
|
9
|
+
* cases but being right more often on the ones you do act on. That is a curve,
|
|
10
|
+
* not a number, so `sweep` returns the whole curve and the `recommendFor*`
|
|
11
|
+
* functions pick a point on it against a constraint you state.
|
|
12
|
+
*/
|
|
13
|
+
import type { Observation } from "./metrics.js";
|
|
14
|
+
/** What happens if you auto-handle everything at or above `threshold`. */
|
|
15
|
+
export declare class ThresholdPoint {
|
|
16
|
+
readonly threshold: number;
|
|
17
|
+
readonly covered: number;
|
|
18
|
+
readonly total: number;
|
|
19
|
+
readonly correct: number;
|
|
20
|
+
constructor(threshold: number, covered: number, total: number, correct: number);
|
|
21
|
+
/** Fraction of cases handled automatically. */
|
|
22
|
+
get coverage(): number;
|
|
23
|
+
/**
|
|
24
|
+
* Accuracy on the automatically handled cases.
|
|
25
|
+
*
|
|
26
|
+
* Defined as 1 when nothing is covered: a threshold that acts on nothing is
|
|
27
|
+
* never wrong. Read it together with `coverage`, never alone.
|
|
28
|
+
*/
|
|
29
|
+
get accuracy(): number;
|
|
30
|
+
get escalated(): number;
|
|
31
|
+
/** Wrong answers you acted on. Usually the number that actually costs. */
|
|
32
|
+
get errors(): number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Coverage and accuracy at every threshold from 0 to 1.
|
|
36
|
+
*
|
|
37
|
+
* Uses the probability the model put on the outcome it chose, so this works for
|
|
38
|
+
* Choice and Score confidence and for a Noul's distance from 0.5 alike, as long
|
|
39
|
+
* as the caller is consistent about which it fed in.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sweep(observations: Observation[], steps?: number): ThresholdPoint[];
|
|
42
|
+
/**
|
|
43
|
+
* Lowest threshold reaching `targetAccuracy`, so coverage stays highest.
|
|
44
|
+
*
|
|
45
|
+
* Returns `null` when no threshold reaches the target, which is a real answer:
|
|
46
|
+
* it means this question cannot be automated at that accuracy and the honest
|
|
47
|
+
* move is to change the question rather than the threshold.
|
|
48
|
+
*/
|
|
49
|
+
export declare function recommendForAccuracy(observations: Observation[], targetAccuracy: number, steps?: number): ThresholdPoint | null;
|
|
50
|
+
/**
|
|
51
|
+
* Highest threshold still covering `minCoverage`, so accuracy is best.
|
|
52
|
+
*
|
|
53
|
+
* The mirror of `recommendForAccuracy`: use it when throughput is the binding
|
|
54
|
+
* constraint and you want the most accurate threshold that still keeps enough
|
|
55
|
+
* volume out of the review queue.
|
|
56
|
+
*/
|
|
57
|
+
export declare function recommendForCoverage(observations: Observation[], minCoverage: number, steps?: number): ThresholdPoint | null;
|
|
58
|
+
//# sourceMappingURL=thresholds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thresholds.d.ts","sourceRoot":"","sources":["../src/thresholds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,0EAA0E;AAC1E,qBAAa,cAAc;IAEvB,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM;gBAHf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM;IAG1B,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;;;;OAKG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,0EAA0E;IAC1E,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAM,GAAG,cAAc,EAAE,CAiBhF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,MAAM,EACtB,KAAK,SAAM,GACV,cAAc,GAAG,IAAI,CASvB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,EAC3B,WAAW,EAAE,MAAM,EACnB,KAAK,SAAM,GACV,cAAc,GAAG,IAAI,CAOvB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choosing a confidence threshold from labeled data instead of guessing.
|
|
3
|
+
*
|
|
4
|
+
* TypeSafe's confidence page recommends three bands: act automatically, proceed
|
|
5
|
+
* with caution, escalate. It also says plainly that where you draw those lines
|
|
6
|
+
* depends on your domain and your data. This module draws them from the data.
|
|
7
|
+
*
|
|
8
|
+
* The trade is always the same. Raising the threshold means acting on fewer
|
|
9
|
+
* cases but being right more often on the ones you do act on. That is a curve,
|
|
10
|
+
* not a number, so `sweep` returns the whole curve and the `recommendFor*`
|
|
11
|
+
* functions pick a point on it against a constraint you state.
|
|
12
|
+
*/
|
|
13
|
+
/** What happens if you auto-handle everything at or above `threshold`. */
|
|
14
|
+
export class ThresholdPoint {
|
|
15
|
+
threshold;
|
|
16
|
+
covered;
|
|
17
|
+
total;
|
|
18
|
+
correct;
|
|
19
|
+
constructor(threshold, covered, total, correct) {
|
|
20
|
+
this.threshold = threshold;
|
|
21
|
+
this.covered = covered;
|
|
22
|
+
this.total = total;
|
|
23
|
+
this.correct = correct;
|
|
24
|
+
}
|
|
25
|
+
/** Fraction of cases handled automatically. */
|
|
26
|
+
get coverage() {
|
|
27
|
+
return this.total ? this.covered / this.total : 0;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Accuracy on the automatically handled cases.
|
|
31
|
+
*
|
|
32
|
+
* Defined as 1 when nothing is covered: a threshold that acts on nothing is
|
|
33
|
+
* never wrong. Read it together with `coverage`, never alone.
|
|
34
|
+
*/
|
|
35
|
+
get accuracy() {
|
|
36
|
+
return this.covered ? this.correct / this.covered : 1;
|
|
37
|
+
}
|
|
38
|
+
get escalated() {
|
|
39
|
+
return this.total - this.covered;
|
|
40
|
+
}
|
|
41
|
+
/** Wrong answers you acted on. Usually the number that actually costs. */
|
|
42
|
+
get errors() {
|
|
43
|
+
return this.covered - this.correct;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Coverage and accuracy at every threshold from 0 to 1.
|
|
48
|
+
*
|
|
49
|
+
* Uses the probability the model put on the outcome it chose, so this works for
|
|
50
|
+
* Choice and Score confidence and for a Noul's distance from 0.5 alike, as long
|
|
51
|
+
* as the caller is consistent about which it fed in.
|
|
52
|
+
*/
|
|
53
|
+
export function sweep(observations, steps = 101) {
|
|
54
|
+
if (steps < 2)
|
|
55
|
+
throw new RangeError("steps must be at least 2");
|
|
56
|
+
const total = observations.length;
|
|
57
|
+
const points = [];
|
|
58
|
+
for (let i = 0; i < steps; i++) {
|
|
59
|
+
const threshold = i / (steps - 1);
|
|
60
|
+
const covered = observations.filter((o) => o.probability >= threshold);
|
|
61
|
+
points.push(new ThresholdPoint(threshold, covered.length, total, covered.filter((o) => o.correct).length));
|
|
62
|
+
}
|
|
63
|
+
return points;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Lowest threshold reaching `targetAccuracy`, so coverage stays highest.
|
|
67
|
+
*
|
|
68
|
+
* Returns `null` when no threshold reaches the target, which is a real answer:
|
|
69
|
+
* it means this question cannot be automated at that accuracy and the honest
|
|
70
|
+
* move is to change the question rather than the threshold.
|
|
71
|
+
*/
|
|
72
|
+
export function recommendForAccuracy(observations, targetAccuracy, steps = 101) {
|
|
73
|
+
if (!(targetAccuracy >= 0 && targetAccuracy <= 1)) {
|
|
74
|
+
throw new RangeError("targetAccuracy must be in [0, 1]");
|
|
75
|
+
}
|
|
76
|
+
const candidates = sweep(observations, steps).filter((p) => p.covered > 0 && p.accuracy >= targetAccuracy);
|
|
77
|
+
if (!candidates.length)
|
|
78
|
+
return null;
|
|
79
|
+
return candidates.reduce((best, p) => (p.threshold < best.threshold ? p : best));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Highest threshold still covering `minCoverage`, so accuracy is best.
|
|
83
|
+
*
|
|
84
|
+
* The mirror of `recommendForAccuracy`: use it when throughput is the binding
|
|
85
|
+
* constraint and you want the most accurate threshold that still keeps enough
|
|
86
|
+
* volume out of the review queue.
|
|
87
|
+
*/
|
|
88
|
+
export function recommendForCoverage(observations, minCoverage, steps = 101) {
|
|
89
|
+
if (!(minCoverage >= 0 && minCoverage <= 1)) {
|
|
90
|
+
throw new RangeError("minCoverage must be in [0, 1]");
|
|
91
|
+
}
|
|
92
|
+
const candidates = sweep(observations, steps).filter((p) => p.coverage >= minCoverage);
|
|
93
|
+
if (!candidates.length)
|
|
94
|
+
return null;
|
|
95
|
+
return candidates.reduce((best, p) => (p.threshold > best.threshold ? p : best));
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=thresholds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thresholds.js","sourceRoot":"","sources":["../src/thresholds.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jevkit-calibrate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Verify TypeSafe Jev's calibration on your own data. Reliability diagrams, ECE, Brier, and confidence thresholds derived from labeled outcomes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jev",
|
|
7
|
+
"typesafe",
|
|
8
|
+
"system-one",
|
|
9
|
+
"calibration",
|
|
10
|
+
"ece",
|
|
11
|
+
"reliability",
|
|
12
|
+
"thresholds"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"jevkit-calibrate": "./dist/cli.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/pjdurden/jevkit-js.git"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"jevkit-core": "^0.2.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -b"
|
|
43
|
+
}
|
|
44
|
+
}
|