📦 WP-135 — feat(losses): Laplace-NLL ablation baseline for RLE¶
Engineering log · Phase 12 — Keypoint detection, v0.5.0
WP-135 — a control arm, because one number states no direction¶
WP-125's acceptance is not a pose figure. It is RLE's mechanism claim: that the learned residual density is what buys the improvement, rather than the reparameterization, the sigmoid-bounded per-point scale, or the log sigma_hat Jacobian that come with it. A claim of that shape cannot be settled by a run, however good the number, because one figure is equally consistent with the flow doing all the work, none of it, and two terms cancelling. It needs a paired run whose only difference is the mechanism under test -- and the tier had no second arm to pair against. This row is that arm, and nothing else: it builds the instrument and does not read it.
The comparison is R14's own, not this project's invention. R14 Table 7 ablates exactly this: "Laplace, learnable variance" -- the NLL over a learned per-point scale with no normalizing flow -- scores 67.4 AP against full RLE's 70.5 AP on COCO. Verified this session against the paper itself (arXiv:2107.11291, read through the ar5iv HTML rendering), not recalled. So the direction of effect is already published, and what a paired run here measures is whether it reproduces at this scale on this recipe -- a much smaller question than the one it would be if the baseline were invented. That is also why no assumption id accompanies this row: an assumption records an open project decision made without a source, and this one has a source with numbers in it.
The loss is RLELoss's formula with one term deleted. R14 Eq. 8 (with the log s normalization R14's own implementation drops) is -log Q(x_bar) - log G_phi(x_bar) + log sigma_hat over the standardized residual x_bar = (mu_g - mu_hat) / sigma_hat. LaplaceNLLLoss is that expression without G_phi. Everything else is held: the residual is formed the same way, sigma_hat comes through the same R14 sec. 3.3 sigmoid (A65), the log sigma_hat term is the same reparameterization log-Jacobian, the sum is over the same two axes, A66's visibility mask is the same mask, and the zero-positives reduction is the same one. Holding all of it fixed is the entire point -- a control that differed in a second place would make the result unattributable, and every one of those clauses is a place it could have differed silently.
The identity is asserted rather than argued: test_rle_loss_with_exactly_flow_term_removed scores both losses on shared inputs and checks the difference equals the flow's own log-density term to 1e-5. A drifted residual, a different sigma activation or a different reduction in either module turns that test red, and nothing else in either suite would have reported it.
"Learnable variance" names what is kept. sigma_hat is still a head output and still trained -- the log sigma_hat term is what gives the likelihood a reason to prefer a small scale where the model is accurate, and test_gradient_reaches_both_location_and_learned_scale pins that both mu_hat and sigma_raw receive gradient. What the ablation removes is the learned shape of the error distribution. The further-degenerate baseline with a fixed scale too is not this row, is not built here, and is just L1; R14 lists it separately.
Zero parameters is the structural difference, and it is load-bearing twice. RLELoss is the one loss in this repository carrying weights -- 42 flow tensors on the tiny test module -- which is why it is held as a submodule at all. LaplaceNLLLoss has none: .parameters() is empty, state_dict() is empty, and construction draws no RNG. The second consequence is the useful one. The module docstring's construction-order rule exists because the flow's Linear layers consume RNG and would perturb every parameter drawn after them; an ablation module draws nothing, so its weights are bit-for-bit a same-seed detection module's, and test_ablation_adds_no_parameters_to_model pins its state dict as a detection module's plus the point stems and no third thing.
The attribute keeps the name rle_loss, deliberately. It is an nn.Module attribute, so it prefixes the loss's state-dict keys, and renaming it to something task-neutral would move rle_loss.flow.* to a new prefix and stop every keypoints checkpoint produced before this row from loading -- including the one behind the frozen goldens/gpu/overfit_micro_kp.json. That is the same key-stability rule WP-087 established and this module keeps its stages flat for, arriving from a direction it had not been tested from. The name is now slightly wrong under one of two arms; a broken checkpoint is worse than a slightly wrong name, and the attribute says so in a comment rather than being quietly left to look like an oversight.
Selection is additive, and the default is what makes it so. DetectionLitModule(keypoint_loss=...) defaults to "rle", so every shipped config, every checkpoint on disk and every accepted figure describes exactly what it described before. The two names are validated against one mapping that also builds them, mirroring how task and rotated_iou_form are validated in the same constructor -- one dict rather than a name tuple beside a constructor branch, so the accepted set and the thing each name builds cannot drift apart. An unrecognized value raises at construction, which matters more here than the pattern usually does: a silent fallback would produce an "ablation" run that trained the very objective it was meant to control for, and a comparison whose two arms are the same arm looks exactly like a null result.
pose_smoke_ablation.yaml is pose_smoke.yaml with one line added. Seed, variant, epochs, callbacks, every gain and the data placeholders are identical, keypoint_gain included -- it stays at A68's un-tuned 1.0 not because that is right but because moving it would make the arms differ twice. Verified by dry-parse only (tests/ptl/test_cli.py::test_config_dry_parses[pose_smoke_ablation.yaml]), the same DoD every shipped config carries.
One claim this row makes about numerics, and its limit. The ablation degrades more gently than RLE outside the O(1) residual range A71 normalizes into: the unit Laplace's log-density is linear in |x_bar|, where the flow's latent is exponential in a compounding log-scale and its base density quadratic in that latent -- A72's non-finite run. test_far_out_residual_stays_finite_rather_than_overflowing measures it at a residual two orders out rather than leaving it as an argument. What that does not say is that the ablation is safe unnormalized: a loss dominated by a handful of far-out points is still training the wrong thing, and both arms take the same A71 box frame for the same reason.
Nothing here was run against real data. There is no COCO checkout and no GPU on this machine, so this row ships a tool and a set of unit gates, not a result. The 67.4-vs-70.5 figures are R14's measurement quoted, not this project's; the arithmetic of the comparison is a prediction of what a paired run should show, and the first honest reading of it is WP-125's, which is [GPU][PRINCIPAL]-tagged and belongs to the user's machine.