@takk/bayesoutputgate 1.0.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/CHANGELOG.md +92 -0
- package/LICENSE +190 -0
- package/NOTICE +45 -0
- package/README.md +403 -0
- package/SECURITY.md +98 -0
- package/SPEC.md +467 -0
- package/dist/adapter/index.cjs +411 -0
- package/dist/adapter/index.d.cts +29 -0
- package/dist/adapter/index.d.ts +29 -0
- package/dist/adapter/index.js +404 -0
- package/dist/audit/index.cjs +82 -0
- package/dist/audit/index.d.cts +40 -0
- package/dist/audit/index.d.ts +40 -0
- package/dist/audit/index.js +77 -0
- package/dist/bayesfactor/index.cjs +152 -0
- package/dist/bayesfactor/index.d.cts +15 -0
- package/dist/bayesfactor/index.d.ts +15 -0
- package/dist/bayesfactor/index.js +149 -0
- package/dist/beta/index.cjs +180 -0
- package/dist/beta/index.d.cts +45 -0
- package/dist/beta/index.d.ts +45 -0
- package/dist/beta/index.js +178 -0
- package/dist/calibration/index.cjs +339 -0
- package/dist/calibration/index.d.cts +53 -0
- package/dist/calibration/index.d.ts +53 -0
- package/dist/calibration/index.js +333 -0
- package/dist/cli/index.cjs +968 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +966 -0
- package/dist/dimensions/index.cjs +106 -0
- package/dist/dimensions/index.d.cts +33 -0
- package/dist/dimensions/index.d.ts +33 -0
- package/dist/dimensions/index.js +104 -0
- package/dist/edge/index.cjs +1141 -0
- package/dist/edge/index.d.cts +12 -0
- package/dist/edge/index.d.ts +12 -0
- package/dist/edge/index.js +1109 -0
- package/dist/gate/index.cjs +803 -0
- package/dist/gate/index.d.cts +77 -0
- package/dist/gate/index.d.ts +77 -0
- package/dist/gate/index.js +799 -0
- package/dist/hypothesis/index.cjs +268 -0
- package/dist/hypothesis/index.d.cts +38 -0
- package/dist/hypothesis/index.d.ts +38 -0
- package/dist/hypothesis/index.js +266 -0
- package/dist/index.cjs +1141 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +1109 -0
- package/dist/likelihood/index.cjs +137 -0
- package/dist/likelihood/index.d.cts +23 -0
- package/dist/likelihood/index.d.ts +23 -0
- package/dist/likelihood/index.js +132 -0
- package/dist/node/index.cjs +1282 -0
- package/dist/node/index.d.cts +24 -0
- package/dist/node/index.d.ts +24 -0
- package/dist/node/index.js +1246 -0
- package/dist/policy/index.cjs +88 -0
- package/dist/policy/index.d.cts +11 -0
- package/dist/policy/index.d.ts +11 -0
- package/dist/policy/index.js +85 -0
- package/dist/types-bMjn1j4e.d.cts +159 -0
- package/dist/types-bMjn1j4e.d.ts +159 -0
- package/package.json +142 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var BayesOutputGateError = class _BayesOutputGateError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "BayesOutputGateError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
Object.setPrototypeOf(this, _BayesOutputGateError.prototype);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function invariant(condition, code, message) {
|
|
12
|
+
if (!condition) {
|
|
13
|
+
throw new BayesOutputGateError(code, message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/dimensions/index.ts
|
|
18
|
+
function dependenceDiagnostic(history, options = {}) {
|
|
19
|
+
const threshold = options.threshold ?? 0.5;
|
|
20
|
+
invariant(
|
|
21
|
+
Number.isFinite(threshold) && threshold >= 0 && threshold <= 1,
|
|
22
|
+
"INVALID_CONFIG",
|
|
23
|
+
`threshold must be in [0, 1], got ${threshold}`
|
|
24
|
+
);
|
|
25
|
+
invariant(
|
|
26
|
+
history.length >= 2,
|
|
27
|
+
"INVALID_OBSERVATION",
|
|
28
|
+
"dependence diagnostic needs at least two score vectors"
|
|
29
|
+
);
|
|
30
|
+
const dimensionSet = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const vector of history) {
|
|
32
|
+
for (const score of vector) {
|
|
33
|
+
dimensionSet.add(score.dimension);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const dimensions = [...dimensionSet].sort();
|
|
37
|
+
const rows = history.map((vector) => {
|
|
38
|
+
const map = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const score of vector) {
|
|
40
|
+
map.set(score.dimension, score.value);
|
|
41
|
+
}
|
|
42
|
+
return map;
|
|
43
|
+
});
|
|
44
|
+
const pairs = [];
|
|
45
|
+
for (let i = 0; i < dimensions.length; i++) {
|
|
46
|
+
for (let j = i + 1; j < dimensions.length; j++) {
|
|
47
|
+
const a = dimensions[i];
|
|
48
|
+
const b = dimensions[j];
|
|
49
|
+
const xs = [];
|
|
50
|
+
const ys = [];
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
const x = row.get(a);
|
|
53
|
+
const y = row.get(b);
|
|
54
|
+
if (x !== void 0 && y !== void 0) {
|
|
55
|
+
xs.push(x);
|
|
56
|
+
ys.push(y);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (xs.length >= 2) {
|
|
60
|
+
const correlation = pearson(xs, ys);
|
|
61
|
+
if (Number.isFinite(correlation)) {
|
|
62
|
+
pairs.push({ a, b, correlation, samples: xs.length });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
pairs.sort((left, right) => Math.abs(right.correlation) - Math.abs(left.correlation));
|
|
68
|
+
const maxAbsCorrelation = pairs.length > 0 ? Math.abs(pairs[0].correlation) : 0;
|
|
69
|
+
const flagged = pairs.filter((pair) => Math.abs(pair.correlation) >= threshold);
|
|
70
|
+
return {
|
|
71
|
+
dimensions,
|
|
72
|
+
pairs,
|
|
73
|
+
flagged,
|
|
74
|
+
maxAbsCorrelation,
|
|
75
|
+
independenceAssumptionSafe: flagged.length === 0
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function pearson(xs, ys) {
|
|
79
|
+
const n = xs.length;
|
|
80
|
+
let sumX = 0;
|
|
81
|
+
let sumY = 0;
|
|
82
|
+
for (let i = 0; i < n; i++) {
|
|
83
|
+
sumX += xs[i];
|
|
84
|
+
sumY += ys[i];
|
|
85
|
+
}
|
|
86
|
+
const meanX = sumX / n;
|
|
87
|
+
const meanY = sumY / n;
|
|
88
|
+
let covariance = 0;
|
|
89
|
+
let varianceX = 0;
|
|
90
|
+
let varianceY = 0;
|
|
91
|
+
for (let i = 0; i < n; i++) {
|
|
92
|
+
const dx = xs[i] - meanX;
|
|
93
|
+
const dy = ys[i] - meanY;
|
|
94
|
+
covariance += dx * dy;
|
|
95
|
+
varianceX += dx * dx;
|
|
96
|
+
varianceY += dy * dy;
|
|
97
|
+
}
|
|
98
|
+
if (varianceX === 0 || varianceY === 0) {
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
return covariance / Math.sqrt(varianceX * varianceY);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/mathspecial.ts
|
|
105
|
+
var LN_SQRT_2PI = 0.9189385332046728;
|
|
106
|
+
var LANCZOS_G = 7;
|
|
107
|
+
var LANCZOS_COEFFICIENTS = [
|
|
108
|
+
0.9999999999998099,
|
|
109
|
+
676.5203681218851,
|
|
110
|
+
-1259.1392167224028,
|
|
111
|
+
771.3234287776531,
|
|
112
|
+
-176.6150291621406,
|
|
113
|
+
12.507343278686905,
|
|
114
|
+
-0.13857109526572012,
|
|
115
|
+
9984369578019572e-21,
|
|
116
|
+
15056327351493116e-23
|
|
117
|
+
];
|
|
118
|
+
function lgamma(x) {
|
|
119
|
+
if (!Number.isFinite(x) || x <= 0) {
|
|
120
|
+
throw new BayesOutputGateError(
|
|
121
|
+
"NUMERIC",
|
|
122
|
+
`lgamma requires a positive finite argument, got ${x}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const z = x - 1;
|
|
126
|
+
let acc = LANCZOS_COEFFICIENTS[0];
|
|
127
|
+
for (let i = 1; i < LANCZOS_COEFFICIENTS.length; i++) {
|
|
128
|
+
acc += LANCZOS_COEFFICIENTS[i] / (z + i);
|
|
129
|
+
}
|
|
130
|
+
const t = z + LANCZOS_G + 0.5;
|
|
131
|
+
return LN_SQRT_2PI + (z + 0.5) * Math.log(t) - t + Math.log(acc);
|
|
132
|
+
}
|
|
133
|
+
function regularizedIncompleteBeta(x, a, b) {
|
|
134
|
+
if (a <= 0 || b <= 0) {
|
|
135
|
+
throw new BayesOutputGateError(
|
|
136
|
+
"NUMERIC",
|
|
137
|
+
`incomplete beta requires positive shapes, got a=${a}, b=${b}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (x <= 0) return 0;
|
|
141
|
+
if (x >= 1) return 1;
|
|
142
|
+
const logFront = lgamma(a + b) - lgamma(a) - lgamma(b) + a * Math.log(x) + b * Math.log1p(-x);
|
|
143
|
+
const front = Math.exp(logFront);
|
|
144
|
+
if (x < (a + 1) / (a + b + 2)) {
|
|
145
|
+
return front * betaContinuedFraction(x, a, b) / a;
|
|
146
|
+
}
|
|
147
|
+
return 1 - front * betaContinuedFraction(1 - x, b, a) / b;
|
|
148
|
+
}
|
|
149
|
+
function betaCdf(x, a, b) {
|
|
150
|
+
return regularizedIncompleteBeta(x, a, b);
|
|
151
|
+
}
|
|
152
|
+
function betaContinuedFraction(x, a, b) {
|
|
153
|
+
const maxIterations = 200;
|
|
154
|
+
const epsilon = 3e-12;
|
|
155
|
+
const tiny = 1e-300;
|
|
156
|
+
const qab = a + b;
|
|
157
|
+
const qap = a + 1;
|
|
158
|
+
const qam = a - 1;
|
|
159
|
+
let c = 1;
|
|
160
|
+
let d = 1 - qab * x / qap;
|
|
161
|
+
if (Math.abs(d) < tiny) d = tiny;
|
|
162
|
+
d = 1 / d;
|
|
163
|
+
let h = d;
|
|
164
|
+
for (let m = 1; m <= maxIterations; m++) {
|
|
165
|
+
const m2 = 2 * m;
|
|
166
|
+
let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
|
|
167
|
+
d = 1 + aa * d;
|
|
168
|
+
if (Math.abs(d) < tiny) d = tiny;
|
|
169
|
+
c = 1 + aa / c;
|
|
170
|
+
if (Math.abs(c) < tiny) c = tiny;
|
|
171
|
+
d = 1 / d;
|
|
172
|
+
h *= d * c;
|
|
173
|
+
aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
|
|
174
|
+
d = 1 + aa * d;
|
|
175
|
+
if (Math.abs(d) < tiny) d = tiny;
|
|
176
|
+
c = 1 + aa / c;
|
|
177
|
+
if (Math.abs(c) < tiny) c = tiny;
|
|
178
|
+
d = 1 / d;
|
|
179
|
+
const delta = d * c;
|
|
180
|
+
h *= delta;
|
|
181
|
+
if (Math.abs(delta - 1) < epsilon) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return h;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/calibration/index.ts
|
|
189
|
+
function goodnessOfFit(samples, params, alpha = 0.05) {
|
|
190
|
+
invariant(
|
|
191
|
+
samples.length >= 5,
|
|
192
|
+
"INVALID_OBSERVATION",
|
|
193
|
+
"goodnessOfFit needs at least five samples"
|
|
194
|
+
);
|
|
195
|
+
invariant(params.a > 0 && params.b > 0, "INVALID_CONFIG", "Beta parameters must be positive");
|
|
196
|
+
const sorted = [...samples].sort((left, right) => left - right);
|
|
197
|
+
const n = sorted.length;
|
|
198
|
+
let ks = 0;
|
|
199
|
+
for (let i = 0; i < n; i++) {
|
|
200
|
+
const value = sorted[i];
|
|
201
|
+
invariant(
|
|
202
|
+
Number.isFinite(value) && value >= 0 && value <= 1,
|
|
203
|
+
"INVALID_SCORE",
|
|
204
|
+
`samples must be finite and in [0, 1], got ${value}`
|
|
205
|
+
);
|
|
206
|
+
const cdf = betaCdf(value, params.a, params.b);
|
|
207
|
+
const upper = (i + 1) / n - cdf;
|
|
208
|
+
const lower = cdf - i / n;
|
|
209
|
+
ks = Math.max(ks, upper, lower);
|
|
210
|
+
}
|
|
211
|
+
const coefficient = alpha <= 0.01 ? 1.628 : alpha <= 0.05 ? 1.358 : 1.224;
|
|
212
|
+
const criticalValue = coefficient / Math.sqrt(n);
|
|
213
|
+
return { ksStatistic: ks, criticalValue, adequate: ks <= criticalValue, samples: n };
|
|
214
|
+
}
|
|
215
|
+
function brierScore(predictions) {
|
|
216
|
+
invariant(
|
|
217
|
+
predictions.length > 0,
|
|
218
|
+
"INVALID_OBSERVATION",
|
|
219
|
+
"brierScore needs at least one prediction"
|
|
220
|
+
);
|
|
221
|
+
let sum = 0;
|
|
222
|
+
for (const prediction of predictions) {
|
|
223
|
+
validatePrediction(prediction);
|
|
224
|
+
sum += (prediction.probability - prediction.outcome) ** 2;
|
|
225
|
+
}
|
|
226
|
+
return sum / predictions.length;
|
|
227
|
+
}
|
|
228
|
+
function reliability(predictions, bins = 10) {
|
|
229
|
+
invariant(
|
|
230
|
+
Number.isInteger(bins) && bins >= 1,
|
|
231
|
+
"INVALID_CONFIG",
|
|
232
|
+
`bins must be a positive integer, got ${bins}`
|
|
233
|
+
);
|
|
234
|
+
invariant(
|
|
235
|
+
predictions.length > 0,
|
|
236
|
+
"INVALID_OBSERVATION",
|
|
237
|
+
"reliability needs at least one prediction"
|
|
238
|
+
);
|
|
239
|
+
const sums = new Array(bins).fill(0);
|
|
240
|
+
const hits = new Array(bins).fill(0);
|
|
241
|
+
const counts = new Array(bins).fill(0);
|
|
242
|
+
for (const prediction of predictions) {
|
|
243
|
+
validatePrediction(prediction);
|
|
244
|
+
const index = Math.min(bins - 1, Math.floor(prediction.probability * bins));
|
|
245
|
+
sums[index] = sums[index] + prediction.probability;
|
|
246
|
+
hits[index] = hits[index] + prediction.outcome;
|
|
247
|
+
counts[index] = counts[index] + 1;
|
|
248
|
+
}
|
|
249
|
+
const out = [];
|
|
250
|
+
for (let b = 0; b < bins; b++) {
|
|
251
|
+
const count = counts[b];
|
|
252
|
+
out.push({
|
|
253
|
+
lower: b / bins,
|
|
254
|
+
upper: (b + 1) / bins,
|
|
255
|
+
count,
|
|
256
|
+
meanPredicted: count > 0 ? sums[b] / count : 0,
|
|
257
|
+
empiricalRate: count > 0 ? hits[b] / count : 0
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
function expectedCalibrationError(predictions, bins = 10) {
|
|
263
|
+
const diagram = reliability(predictions, bins);
|
|
264
|
+
const total = predictions.length;
|
|
265
|
+
let ece = 0;
|
|
266
|
+
for (const bin of diagram) {
|
|
267
|
+
if (bin.count > 0) {
|
|
268
|
+
ece += bin.count / total * Math.abs(bin.empiricalRate - bin.meanPredicted);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return ece;
|
|
272
|
+
}
|
|
273
|
+
function scoresForDimension(history, dimension, label) {
|
|
274
|
+
const out = [];
|
|
275
|
+
for (const observation of history) {
|
|
276
|
+
if (observation.label !== label) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
for (const score of observation.scores) {
|
|
280
|
+
if (score.dimension === dimension) {
|
|
281
|
+
out.push(score.value);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return out;
|
|
286
|
+
}
|
|
287
|
+
function assessAssumptions(history, models, options = {}) {
|
|
288
|
+
const alpha = options.alpha ?? 0.05;
|
|
289
|
+
const minSamples = options.minSamples ?? 5;
|
|
290
|
+
const inadequateDimensions = [];
|
|
291
|
+
for (const model of models) {
|
|
292
|
+
const high = scoresForDimension(history, model.dimension, "high");
|
|
293
|
+
const low = scoresForDimension(history, model.dimension, "low");
|
|
294
|
+
const highBad = high.length >= minSamples && !goodnessOfFit(high, model.high, alpha).adequate;
|
|
295
|
+
const lowBad = low.length >= minSamples && !goodnessOfFit(low, model.low, alpha).adequate;
|
|
296
|
+
if (highBad || lowBad) {
|
|
297
|
+
inadequateDimensions.push(model.dimension);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const vectors = history.map((observation) => observation.scores);
|
|
301
|
+
let independenceAssumptionSafe = true;
|
|
302
|
+
const dependentPairs = [];
|
|
303
|
+
if (vectors.length >= 2) {
|
|
304
|
+
const diagnostic = dependenceDiagnostic(
|
|
305
|
+
vectors,
|
|
306
|
+
options.dependenceThreshold !== void 0 ? { threshold: options.dependenceThreshold } : {}
|
|
307
|
+
);
|
|
308
|
+
independenceAssumptionSafe = diagnostic.independenceAssumptionSafe;
|
|
309
|
+
for (const pair of diagnostic.flagged) {
|
|
310
|
+
dependentPairs.push(`${pair.a}~${pair.b}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
goodnessOfFitAdequate: inadequateDimensions.length === 0,
|
|
315
|
+
independenceAssumptionSafe,
|
|
316
|
+
inadequateDimensions,
|
|
317
|
+
dependentPairs
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function validatePrediction(prediction) {
|
|
321
|
+
invariant(
|
|
322
|
+
Number.isFinite(prediction.probability) && prediction.probability >= 0 && prediction.probability <= 1,
|
|
323
|
+
"INVALID_SCORE",
|
|
324
|
+
`prediction probability must be in [0, 1], got ${prediction.probability}`
|
|
325
|
+
);
|
|
326
|
+
invariant(
|
|
327
|
+
prediction.outcome === 0 || prediction.outcome === 1,
|
|
328
|
+
"INVALID_OBSERVATION",
|
|
329
|
+
`prediction outcome must be 0 or 1, got ${prediction.outcome}`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export { assessAssumptions, brierScore, expectedCalibrationError, goodnessOfFit, reliability };
|