blinklinmult.train.calibration¶
Per-corpus calibration of the eye-state score.
The frame-wise model ranks closed frames above open ones on the video corpora – threshold-free AUC 0.857 on RN, 0.917 on TalkingFace – but places its decision boundary in the wrong place there. Measured, a genuinely closed RN eye scores a median of 0.19, against 0.98 F1 on the still corpora.
The cause is a class-prior mismatch, not blindness: the sampler shows the model a ~50% closed world (CEW is 49.2% closed, MRL 81.9%) while RN is 1.3-1.6%. A model fitted to the first prior systematically under-predicts under the second.
Because the ranking is already good, one scalar per corpus recovers most of the loss without touching the weights. Two estimators are provided:
fit_temperature()– one temperature on the logit, the standard recipe (Guo et al., 2017). Monotone, so it cannot change the ranking or the AUC; it moves only where the boundary falls.fit_threshold()– the cheaper baseline: pick the threshold that maximises validation F1 directly.
Prefer the threshold unless temperature clearly beats it. They address the same failure, and a threshold is one number a reader can interpret. Temperature earns its place only when the whole probability curve is needed – for a downstream expected-value decision, say – rather than a single cut.
Every parameter here is fitted on validation and applied to test. Fitting on test would report a number no deployment could reproduce. Corpora with no validation split (TalkingFace ships 0 valid windows, and MPEblink annotates no closure at all) therefore cannot be calibrated and must keep the universal parameter – which is exactly why the universal one is reported beside it.
Measured result: calibration does not improve the decision on RN.¶
Over ten recording-level splits of fw-focal’s RN test signals, fitting on
half the recordings and scoring the other half:
method |
test F1 |
beats universal |
|---|---|---|
universal 0.37 |
0.4439 +/- 0.035 |
– |
per-corpus threshold |
0.4269 +/- 0.027 |
1 of 10 |
temperature + threshold |
0.4261 +/- 0.027 |
1 of 10 |
The fitted thresholds are bimodal across splits – 0.30-0.36 on six, 0.55-0.56 on four – so the validation optimum is unstable and does not transfer. The temperature, by contrast, is stable at 0.58-0.71 and does what it claims: it cuts expected calibration error from 0.0355 to 0.0109.
So the probabilities really were mis-scaled, and temperature really does fix them – but the decision was already near its best at the universal threshold, and re-fitting per corpus buys nothing while adding a parameter and its variance.
This module is therefore not wired into the benchmark. It is kept because the ECE numbers are worth reporting and because the video models in Part 3 may sit in a different regime; it should be re-measured there rather than assumed useless. The lesson is that the prior mismatch has to be fixed where it is caused – in the sampler (Task 4) or by temporal context (Task 6) – not patched at the output.
Attributes¶
Module-level logger. |
|
Clamp keeping |
|
Temperature grid searched by |
|
Threshold grid searched by |
Exceptions¶
Raised when calibration is asked for something it cannot fit. |
Functions¶
|
Inverse sigmoid, clamped away from the asymptotes. |
|
Rescale probabilities by a temperature on the logit. |
|
Pick the decision boundary that maximises F1. |
|
Fit one temperature by validation negative log-likelihood. |
|
Gap between confidence and accuracy, averaged over score bins. |
Module Contents¶
- blinklinmult.train.calibration.logger¶
Module-level logger.
- blinklinmult.train.calibration.EPSILON = 1e-06¶
Clamp keeping
logitfinite at probabilities of exactly 0 or 1.
- blinklinmult.train.calibration.DEFAULT_TEMPERATURES¶
Temperature grid searched by
fit_temperature().Geometric rather than linear: temperature acts multiplicatively on the logit, so 0.1-to-0.2 is the same size of change as 5-to-10 and a linear grid would spend most of its points where they alter the score least.
- blinklinmult.train.calibration.DEFAULT_THRESHOLDS¶
Threshold grid searched by
fit_threshold().
- exception blinklinmult.train.calibration.CalibrationError[source]¶
Bases:
ExceptionRaised when calibration is asked for something it cannot fit.
Initialize self. See help(type(self)) for accurate signature.
- blinklinmult.train.calibration.logit(probability: numpy.ndarray) numpy.ndarray[source]¶
Inverse sigmoid, clamped away from the asymptotes.
- Parameters:
probability (np.ndarray) – Values in
[0, 1].- Returns:
Log-odds, finite even at exactly 0 or 1.
- Return type:
np.ndarray
- blinklinmult.train.calibration.apply_temperature(probability: numpy.ndarray, temperature: float) numpy.ndarray[source]¶
Rescale probabilities by a temperature on the logit.
- Parameters:
probability (np.ndarray) – Uncalibrated values in
[0, 1].temperature (float) – Divisor on the logit. Below 1 sharpens, above 1 softens; 1 is the identity.
- Returns:
Calibrated probabilities, same shape.
- Return type:
np.ndarray
- Raises:
CalibrationError – If the temperature is not positive.
- blinklinmult.train.calibration.fit_threshold(probability: numpy.ndarray, target: numpy.ndarray, thresholds: numpy.ndarray | None = None) tuple[float, float][source]¶
Pick the decision boundary that maximises F1.
- Parameters:
probability (np.ndarray) – Validation scores in
[0, 1].target (np.ndarray) – Validation labels.
thresholds (np.ndarray | None) – Grid to search;
DEFAULT_THRESHOLDSby default.
- Returns:
(threshold, its F1).- Return type:
tuple[float, float]
- Raises:
CalibrationError – If the inputs disagree in length or hold no positive.
- blinklinmult.train.calibration.fit_temperature(probability: numpy.ndarray, target: numpy.ndarray, temperatures: numpy.ndarray | None = None) tuple[float, float][source]¶
Fit one temperature by validation negative log-likelihood.
NLL rather than F1: temperature is a probability correction, and fitting it to a thresholded score would tune it to one operating point and leave the rest of the curve arbitrary. The threshold is then chosen separately, on the calibrated scores.
- Parameters:
probability (np.ndarray) – Validation scores in
[0, 1].target (np.ndarray) – Validation labels.
temperatures (np.ndarray | None) – Grid to search;
DEFAULT_TEMPERATURESby default.
- Returns:
(temperature, its mean NLL).- Return type:
tuple[float, float]
- Raises:
CalibrationError – If the inputs disagree in length or are empty.
- blinklinmult.train.calibration.expected_calibration_error(probability: numpy.ndarray, target: numpy.ndarray, bins: int = 15) float[source]¶
Gap between confidence and accuracy, averaged over score bins.
Reported because F1 alone cannot show why a threshold had to move: a model can be perfectly ranked and badly calibrated at the same time, which is exactly the state measured on RN.
- Parameters:
probability (np.ndarray) – Scores in
[0, 1].target (np.ndarray) – Binary labels.
bins (int) – Equal-width bins over
[0, 1].
- Returns:
Weighted mean
|confidence - accuracy|; 0 is perfect.- Return type:
float