Correct superseded conformal claims
Browse filesThe conformal tab still carried pre-clean-split results: it claimed the bound held
at every risk level tested while the chart beside it shows three points above the
line. Now 6 of 9, with the exceedances given as 1.12/0.55/0.83 standard errors.
Also corrected: abstention 72%/3.4% -> 91.7%/12.8%; the monotonicity minimum
(lambda ~ 0.57 "falls to zero" -> 0.029 at lambda ~ 0.461, first rise 0.486);
"the bound holds at whatever value is chosen"; and the figure caption.
The header conflated micro-F1 with top-1 accuracy, which are the same metric here
-- the real difference is two training runs. Verified the served checkpoint at
172/180 = 95.6% against the notebook's 94.1%, and corrected train.py's comments
claiming it IS the notebook's model.
Mirrors llm-wiki commit edfa1a9.
|
@@ -134,8 +134,10 @@ with gr.Blocks(title="Explainable & Auditable Alloy Defect Detection",
|
|
| 134 |
"hooks, following the course's own notebook).\n\n"
|
| 135 |
"*The classifier itself is deliberately ordinary* — a compact 164k-parameter "
|
| 136 |
"CNN, trained by the same recipe as the notebook: a stratified 70/10/10/10 split "
|
| 137 |
-
"where the test images are never used to fit weights or pick a checkpoint "
|
| 138 |
-
"
|
|
|
|
|
|
|
| 139 |
"point: the question is not whether a CNN can score well on NEU-DET (it can), but "
|
| 140 |
"whether the explanation sitting on top of it means anything. Tabs 2 and 3 test "
|
| 141 |
"exactly that."
|
|
@@ -223,8 +225,14 @@ with gr.Blocks(title="Explainable & Auditable Alloy Defect Detection",
|
|
| 223 |
The guarantee is arithmetic, not optimism.
|
| 224 |
|
| 225 |
**It was validated, not just asserted:** empirical error stayed at or below
|
| 226 |
-
target at **
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
---
|
| 230 |
|
|
@@ -240,8 +248,9 @@ with gr.Blocks(title="Explainable & Auditable Alloy Defect Detection",
|
|
| 240 |
| Empty | Nothing clears the bar | **Abstain** — the model declines |
|
| 241 |
|
| 242 |
The plant picks α. That single dial trades throughput against escalation
|
| 243 |
-
volume
|
| 244 |
-
|
|
|
|
| 245 |
auditor.**
|
| 246 |
|
| 247 |
#### Three honest limits
|
|
@@ -256,26 +265,29 @@ with gr.Blocks(title="Explainable & Auditable Alloy Defect Detection",
|
|
| 256 |
- **At lenient α the metric is dominated by abstentions.** We adapted a
|
| 257 |
method built for pixel-level segmentation, where a prediction set spans
|
| 258 |
thousands of pixels and is essentially never empty. With six classes, empty
|
| 259 |
-
sets are common at strict bars —
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
|
|
|
| 263 |
- **A precondition of the theorem is not strictly met.** Conformal Risk Control
|
| 264 |
requires the loss to be *monotone non-increasing* — as you flag more, the
|
| 265 |
risk must never climb back. False-negative rate satisfies that; **false
|
| 266 |
-
discovery rate does not.** Ours is U-shaped: it falls to
|
| 267 |
-
λ ≈ 0.
|
| 268 |
-
false positives
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
| 274 |
"""
|
| 275 |
)
|
| 276 |
gr.Image(
|
| 277 |
value="assets/conformal-guarantee.png",
|
| 278 |
-
label="Conformal Risk Control:
|
| 279 |
interactive=False,
|
| 280 |
)
|
| 281 |
|
|
|
|
| 134 |
"hooks, following the course's own notebook).\n\n"
|
| 135 |
"*The classifier itself is deliberately ordinary* — a compact 164k-parameter "
|
| 136 |
"CNN, trained by the same recipe as the notebook: a stratified 70/10/10/10 split "
|
| 137 |
+
"where the test images are never used to fit weights or pick a checkpoint. "
|
| 138 |
+
"The notebook's run scores 94.1% on that test split; the separately trained "
|
| 139 |
+
"checkpoint served here scores 95.6% on the same split — same recipe and "
|
| 140 |
+
"same split, different training run. That is the "
|
| 141 |
"point: the question is not whether a CNN can score well on NEU-DET (it can), but "
|
| 142 |
"whether the explanation sitting on top of it means anything. Tabs 2 and 3 test "
|
| 143 |
"exactly that."
|
|
|
|
| 225 |
The guarantee is arithmetic, not optimism.
|
| 226 |
|
| 227 |
**It was validated, not just asserted:** empirical error stayed at or below
|
| 228 |
+
target at **six of the nine** risk levels tested (α = 0.1 through 0.9).
|
| 229 |
+
The three that came in over — α = 0.10, 0.20 and 0.90 — miss by
|
| 230 |
+
**1.12, 0.55 and 0.83 standard errors** on 180 test images. The bound is on
|
| 231 |
+
*expected* error, so realisations scatter either side of the line; near-misses in
|
| 232 |
+
both directions are what a correctly behaving bound looks like. An earlier version
|
| 233 |
+
of this project reported nine out of nine, which looked stronger and was weaker
|
| 234 |
+
— that model had been checkpoint-selected on the very images it was scored
|
| 235 |
+
against.
|
| 236 |
|
| 237 |
---
|
| 238 |
|
|
|
|
| 248 |
| Empty | Nothing clears the bar | **Abstain** — the model declines |
|
| 249 |
|
| 250 |
The plant picks α. That single dial trades throughput against escalation
|
| 251 |
+
volume. Pick it at the **strict end**: α = 0.10 abstains on 12.8% of images,
|
| 252 |
+
α = 0.90 on 91.7% — within budget and operationally useless. **This, not
|
| 253 |
+
the accuracy number and not the heatmap, is what we would put in front of an
|
| 254 |
auditor.**
|
| 255 |
|
| 256 |
#### Three honest limits
|
|
|
|
| 265 |
- **At lenient α the metric is dominated by abstentions.** We adapted a
|
| 266 |
method built for pixel-level segmentation, where a prediction set spans
|
| 267 |
thousands of pixels and is essentially never empty. With six classes, empty
|
| 268 |
+
sets are common at strict bars — 91.7% of images at the most lenient
|
| 269 |
+
α — and an empty set is scored as a full error. So at that end the
|
| 270 |
+
reported "false discovery rate" is very nearly a synonym for "abstention rate."
|
| 271 |
+
The useful operating range here is the **low-α end**, where empty sets are
|
| 272 |
+
far rarer (12.8%).
|
| 273 |
- **A precondition of the theorem is not strictly met.** Conformal Risk Control
|
| 274 |
requires the loss to be *monotone non-increasing* — as you flag more, the
|
| 275 |
risk must never climb back. False-negative rate satisfies that; **false
|
| 276 |
+
discovery rate does not.** Ours is U-shaped: it falls from 0.949 to a minimum of
|
| 277 |
+
0.029 around λ ≈ 0.461, then climbs back to 0.755 as the
|
| 278 |
+
flagged set fills with false positives — rising at 80 of 399 grid steps.
|
| 279 |
+
Every threshold we actually selected (0.004–0.219) sits on the *descending*
|
| 280 |
+
branch, where the condition effectively holds; the first rise is not until
|
| 281 |
+
λ = 0.486. Capping the search grid at λ ≤ 0.48
|
| 282 |
+
would satisfy the precondition outright **without changing a single reported
|
| 283 |
+
number**. But that is a property of this data, not a proof. **It is why the chart
|
| 284 |
+
above is load-bearing rather than decorative:** with a precondition in question,
|
| 285 |
+
checking the bound empirically on held-out data is doing real work.
|
| 286 |
"""
|
| 287 |
)
|
| 288 |
gr.Image(
|
| 289 |
value="assets/conformal-guarantee.png",
|
| 290 |
+
label="Conformal Risk Control: empirical FDR against target at nine risk levels; under target at six, over by ~1 standard error at three (precomputed in the notebook)",
|
| 291 |
interactive=False,
|
| 292 |
)
|
| 293 |
|
|
@@ -17,7 +17,7 @@ from torch.utils.data import DataLoader, Dataset
|
|
| 17 |
|
| 18 |
from model import CLASS_NAMES, IMAGE_SIZE, MEAN, STD, DefectCNN
|
| 19 |
|
| 20 |
-
SEED = 41 # matches the notebook, so this checkpoint
|
| 21 |
DATA_ROOT = os.path.join("..", "data", "neu-det-src", "IMAGES")
|
| 22 |
CHECKPOINT_PATH = "model.pt"
|
| 23 |
EXAMPLES_DIR = "examples"
|
|
@@ -74,9 +74,11 @@ def main():
|
|
| 74 |
for name, count in Counter(labels_by_path.values()).items():
|
| 75 |
print(f" {name:16s} {count:4d}")
|
| 76 |
|
| 77 |
-
# Stratified 70/10/10/10 per class, identical to the notebook's Section 2 so the
|
| 78 |
-
# served checkpoint is
|
| 79 |
-
#
|
|
|
|
|
|
|
| 80 |
rng = np.random.default_rng(SEED)
|
| 81 |
train_paths, val_paths, eval_paths = [], [], []
|
| 82 |
for cls in CLASS_NAMES:
|
|
|
|
| 17 |
|
| 18 |
from model import CLASS_NAMES, IMAGE_SIZE, MEAN, STD, DefectCNN
|
| 19 |
|
| 20 |
+
SEED = 41 # matches the notebook, so this checkpoint sees the notebook's exact splits
|
| 21 |
DATA_ROOT = os.path.join("..", "data", "neu-det-src", "IMAGES")
|
| 22 |
CHECKPOINT_PATH = "model.pt"
|
| 23 |
EXAMPLES_DIR = "examples"
|
|
|
|
| 74 |
for name, count in Counter(labels_by_path.values()).items():
|
| 75 |
print(f" {name:16s} {count:4d}")
|
| 76 |
|
| 77 |
+
# Stratified 70/10/10/10 per class, identical to the notebook's Section 2, so the
|
| 78 |
+
# served checkpoint is trained and scored on exactly the notebook's splits. It is a
|
| 79 |
+
# separate training run, so its weights and test score differ from the notebook's
|
| 80 |
+
# (95.6% here vs 94.1% there). Only train and the final test split are needed here;
|
| 81 |
+
# val is used for checkpoint selection.
|
| 82 |
rng = np.random.default_rng(SEED)
|
| 83 |
train_paths, val_paths, eval_paths = [], [], []
|
| 84 |
for cls in CLASS_NAMES:
|