Ground Truth

Three Bugs That Look Like One Miss

Mostafa DhouibMostafa Dhouib·10 min read
The short answer

A detector that failed to fire has at least three causes that are indistinguishable from outside the system. The signal was removed by a transform before it arrived, the threshold was calibrated on clean material and is wrong for real material, or the feature path on the device drifted from the one used in training. Two of the three are not in the model. The way to separate them is a break-map: apply one controlled condition at a time and measure under each.

Three Bugs That Look Like One Miss

The short answer. When a detector fails to fire on something it should have caught, the observation is a single bit and the possible causes are not. At least three produce an identical miss. The signal may have been destroyed by a transform somewhere upstream, in which case no detector could have caught it. The decision threshold may have been set on clean material and be in the wrong place for material that has been through real-world processing. Or the feature path running on the device may have drifted from the one the model was trained against, so the model is being shown inputs unlike anything it learned from. Two of those three are not in the model, and retraining will not touch them. Attribution requires applying one controlled condition at a time and measuring under each.

The report arrives as a single sentence. It did not detect the thing. Sometimes with an example attached, usually not.

The instinct is to treat that as a model problem, because the model is the component that makes the decision and therefore the component that appears to have failed. More data, more training, a bigger architecture. That work is expensive, slow, and in two of the three cases it is aimed at the wrong component entirely.

The three causes, and why they are identical from outside

A miss is the absence of an event. There is no diagnostic content in an absence, which is what makes this class of bug so durable.

The signal was removed before it arrived. Something in the chain between the source and the detector applied a transform that destroyed the thing the detector looks for. A lossy codec. A resampling step. A noise suppressor. An automatic gain stage. A compression pass. A format conversion with an implicit filter. By the time the material reaches the model, the evidence is gone. The detector is working correctly and reporting honestly on an input that no longer contains what it was asked about.

This is the case people find hardest to accept, because it means the correct engineering answer is sometimes that the detection is impossible under that condition, and no amount of work on the detector changes it.

The threshold is in the wrong place for real material. The model produces a score and something converts that score into a decision. That conversion was almost certainly tuned on a clean evaluation set, because that is the set that existed when the threshold was chosen. Real material has been through processing that shifts the score distribution. The scores on genuine positives slide down, the threshold stays where it was, and detections stop happening. The model's ranking may be entirely intact. The cut point is simply no longer where the distributions separate.

The tell for this one is that the model's score on the missed item is often only slightly below the threshold, which nobody notices because the output is a boolean.

The feature path drifted. The model was trained on features computed one way and the device computes them another way. Different window length, different normalisation, different filter coefficients, a different library version, a fixed-point implementation standing in for a floating-point one, a different convention for how edges are handled. Each difference is individually defensible and the composite input is outside the distribution the model learned.

This one is especially nasty because it produces misses that are not systematic in any obvious way. Some inputs survive the mismatch and some do not, so the failure looks random, and random-looking failure gets attributed to model quality.

All three end with the same observation. No detection. Nothing logged as an error.

The signal was removed before it arrivednot in the modela lossy codec, a resampling step, a noise suppressor, a gain stage, a format conversion with an implicit filter, and the detector is reporting honestly on an input that no longer contains what it was asked about
What that impliesdetection may be impossiblethe case people find hardest to accept, because no amount of work on the detector changes it
The threshold is in the wrong placenot in the modeltuned on the clean evaluation set that existed when it was chosen, while real processing slides the scores on genuine positives down past a cut point that never moved
The tell for that onethe score sits just belowwhich nobody notices, because the output is a boolean
The feature path driftednot in the modeldifferent window length, normalisation, filter coefficients, library version, a fixed point implementation standing in for a floating point one, each difference individually defensible
Why that one looks like model qualityit looks randomsome inputs survive the mismatch and some do not, and random looking failure gets attributed to the model
All three end with the same observation. No detection, and nothing logged as an error.
FigureA miss is the absence of an event, and there is no diagnostic content in an absence. Three causes produce an identical non event, two of them are not in the model, and retraining will not touch either. That is why the instinct to treat a miss as a model problem is aimed at the wrong component most of the time.

The break-map: one condition at a time

The method is deliberately unclever. You construct a grid of conditions, apply exactly one at a time, and measure detection performance under each.

The reason it has to be one at a time is that the conditions interact, and a composite condition tells you nothing about which component caused the loss. Running your detector against real-world material is a composite of a dozen transforms, which is why running it against real-world material produces a number and not a diagnosis.

Start with the intact baseline. Original material, straight into the detector, feature path identical to training. This is the ceiling. If performance is poor here, stop, because the problem is in the model or the labels and the rest of the grid is wasted effort.

Add one transform at a time to the material. Each candidate step from the real-world chain, applied alone to the baseline material. Each codec and bitrate separately. Each resampling ratio separately. Each enhancement or suppression stage separately. Each gain or normalisation stage separately. The output is a column of detection figures, one per condition, directly comparable to the baseline.

Where performance falls off a cliff under one transform, you have located a signal-destroying step. That is not a bug in your detector. It is a property of the transform, and the question it raises is about the deployment, not the model.

Sweep the threshold under each condition. For every condition, do not just record whether detection happened. Record the score distribution and ask whether there exists any threshold that would have separated positives from negatives under that condition. If there is, the signal survived and the only problem is the cut point. If there is not, the signal is gone. This single step separates cause two from cause one, and it is the step most often skipped because the production system only exposes a boolean.

Run the device feature path against the training feature path on identical input. Not detection performance. The features themselves, compared numerically, on the same input bytes. Any divergence beyond numerical noise is cause three, and it is found in minutes by comparison and in weeks by retraining.

This last check deserves to be a permanent fixture rather than a debugging step. A stored set of input samples with their expected feature vectors, asserted on every firmware and model build, catches feature path drift at the moment it is introduced rather than after deployment.

Start with the intact baseline
original material, training feature path, straight into the detector. If performance is poor here the problem is the model or the labels and the rest of the grid is wasted effort
Add one transform at a time
each codec and bitrate separately, each resampling ratio separately, each enhancement stage separately. A cliff under one transform locates a signal destroying step
Sweep the threshold under each condition
record the score distribution and ask whether any threshold would have separated positives from negatives. This is what separates a wrong cut point from a destroyed signal, and it is the step most often skipped because production only exposes a boolean
Compare the device features against the training features
the vectors themselves, numerically, on the same input bytes. Divergence beyond numerical noise is found in minutes by comparison and in weeks by retraining
Only now is it a model problem
a weak baseline justifies the expensive work, because the cheap explanations have been eliminated
Keep the feature comparison as a permanent assertion on stored samples across firmware and model builds, so drift is caught at the moment it is introduced. The ordering is not thoroughness for its own sake, it is expected value: comparing vectors is an afternoon and retraining is weeks.
FigureThe method is deliberately unclever. Conditions interact, so a composite tells you nothing about which component caused the loss, which is exactly why running a detector against real world material produces a number rather than a diagnosis.

Reading the map

Once the grid is filled in, the attribution is usually immediate.

Baseline strong, one transform catastrophic, no threshold recovers it: the transform destroys the signal. Build the response around that condition rather than against it. Detect the condition and abstain, or move detection upstream of the transform, or accept the gap and document it.

Baseline strong, performance degrades under transforms but a threshold exists that separates: recalibrate per condition. The model is fine. What you need is condition detection plus condition-specific thresholds, or a threshold set on realistic material rather than clean material.

Baseline strong in the lab, weak on the device, feature comparison shows divergence: fix the feature path. Nothing else will help and everything else will waste the quarter.

Baseline weak: now it is a model problem, and now the expensive work is justified, because you have eliminated the cheap explanations.

The order matters because the costs are wildly asymmetric. Comparing feature vectors is an afternoon. Sweeping thresholds on existing scores is an afternoon. Running a transform grid is a day or two of compute. Retraining is weeks and changes everything downstream. Doing the cheap eliminations first is not thoroughness for its own sake, it is a sensible ordering by expected value.

What this changes about how you report

There is a secondary benefit that is easy to overlook. A break-map gives you a defensible way to describe what your system can and cannot do.

A single performance figure invites the assumption that it holds everywhere, which it never does. A table of figures against named conditions is a specification. It tells an integrator which conditions are supported, tells a product owner what the real coverage is, and gives a regulator or a customer something auditable.

It also changes what a robustness target looks like. Instead of a number to hit, you get a list of conditions to survive, each with its own target, and the conditions are chosen from the deployment rather than from a benchmark.

The honest limit

A break-map is only as good as its grid, and the grid is a list of conditions somebody thought of. The real world applies transforms you did not enumerate, in combinations you did not test, and a condition absent from the grid is invisible to the method. The map is a lower bound on what you know, not a statement of coverage.

Single-condition isolation also deliberately ignores interaction. Two transforms that are each individually survivable can compose into one that is not, and the grid as described will not show that. Testing pairs is the usual response and it grows quadratically, so in practice you test the pairs you expect from the deployment chain and accept that you have not covered the space.

And the three causes are not exhaustive. Label error, a mismatch between what the model was trained to detect and what users expect it to detect, and plain integration bugs such as the detector reading the wrong buffer all produce the same silent miss. The value of the break-map is that it removes the three most common and most cheaply testable explanations before anyone commits to the expensive one.

FAQ

Why can't I tell from a missed detection what went wrong? Because a miss is an absence, and an absence has no diagnostic content. At least three distinct causes produce an identical non-event: an upstream transform removed the signal, the threshold is calibrated for cleaner material than it is seeing, or the device feature path has drifted from the training one. Two of those are not in the model, so model work addresses neither.

How do I separate a destroyed signal from a badly placed threshold? Record the score distribution rather than the boolean decision, then ask whether any threshold would have separated positives from negatives under that condition. If such a threshold exists, the signal survived the transform and only the cut point is wrong. If no threshold separates them, the information is gone and recalibration cannot recover it.

What is the fastest check for feature path drift? Run the same input bytes through the device feature extractor and the training feature extractor and compare the resulting vectors numerically. Divergence beyond numerical noise is the answer. Keep that comparison as a permanent assertion on stored samples across firmware and model builds, so drift is caught when introduced rather than after deployment.

Why apply only one condition at a time? Because conditions interact, so a composite gives you a number instead of a diagnosis. Running against real-world material is already a composite of many transforms, which is exactly why it tells you that performance is poor without telling you which step caused it. One transform per measurement is what makes each column interpretable.

When is it actually a model problem? When the intact baseline is weak. If the detector underperforms on original material through the training feature path with a swept threshold, the cheap explanations are eliminated and the expensive work is justified. Doing it in that order matters because comparing feature vectors takes an afternoon and retraining takes weeks.

Does a break-map prove my detector is robust? No. It covers the conditions somebody enumerated, applied one at a time, so it is a lower bound on what you know rather than a statement of coverage. Unlisted conditions are invisible to it, and two individually survivable transforms can compose into one that is not. What it does give you is a condition-by-condition specification instead of a single figure that quietly implies it holds everywhere.

Carrying a program like this one?

Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.

Bring us the program