fix: masked BinnedNLL gradient - #1137
Conversation
c09efff to
156f4bb
Compare
|
🤖 AI text below 🤖 Rebased onto |
|
@HDembinski This is needed to get the rest of the PRs to go green (the (Note: "draft" means I have not reviewed it yet, I'm taking these out of draft when I review; you can review at any time) |
|
Since this is AI generated, these should be individual focused PRs, not this hard to review collection of fixes. |
|
Let me pull out the one that helps with CI, then I'll split the rest up. |
|
(Fully doing one per fix would have hit the CI pretty hard, but I can break them up one at a time if that helps) |
156f4bb to
774e072
Compare
This comment was marked as low quality.
This comment was marked as low quality.
|
(Need to figure out how to stop Claude from commenting every rebase here, normally it doesn't do that) |
a69c1f6 to
ae5b41a
Compare
🤖 _AI text below_ 🤖 Split out of #1137. `scaled_pdf` must scale by the number of data points. For multivariate data of shape `(D, N)`, `np.prod(data.shape)` gives `D * N`, so the number density was too large by the factor `D`. `data.shape[-1]` is correct for 1D and multivariate data. Reported in #1132.
🤖 _AI text below_ 🤖 Split out of #1137. `plt.subplots(1, 1)` returns a bare Axes, not an array, so indexing it raised a `TypeError` when the sum contained exactly one component with a `visualize` method. The fix passes `squeeze=False`. Also removes the `**kwargs` paragraph from the docstring. The method does not accept `**kwargs`. Reported in #1132. Co-authored-by: Eduardo Rodrigues <eduardo.rodrigues@cern.ch>
🤖 _AI text below_ 🤖 Two bugs in the pull computation, split out of #1137. - An index-array mask was negated with `~`, which is bitwise NOT for integers, so the wrong bins became NaN. The new helper `_exclusion_from_mask` handles boolean masks and index masks. - The user mask replaced the `err == 0` mask instead of combining with it, so bins with zero error gave ±inf pulls instead of NaN. `LeastSquares.pulls` now also gives NaN for zero-error points when no mask is set (before: ±inf), consistent with `BinnedCost`. The `pulls` docstring now states this. Reported in #1132.
There was a problem hiding this comment.
Please consider the comment for the 2D case. It's correct for 1D already. I would have thought of checking this myself tbh so fable is probably right here. If it's right, you can add the change with a 2 line comment explaining it + a test for the 2D case.
| if ma is not None: | ||
| psum = np.sum(p[ma]) | ||
| pg = pg / psum - p * np.sum(pg[:, ma]) / psum**2 | ||
| pg = pg / psum - p * np.sum(pg[:, ma], axis=1)[:, np.newaxis] / psum**2 |
There was a problem hiding this comment.
This is correct for 1D but fable informs me that this is still wrong for 2D masked gradients. It suggested this change but I haven't out the bath myself.
| pg = pg / psum - p * np.sum(pg[:, ma], axis=1)[:, np.newaxis] / psum**2 | |
| corr = np.sum(pg[:, ma].reshape(pg.shape[0], -1), axis=1) | |
| pg = pg / psum - p * corr.reshape((-1,) + (1,) * p.ndim) / psum**2 |
There was a problem hiding this comment.
🤖 AI text below 🤖
Confirmed and applied. BinnedNLL.mask selects along the first axis only, so for a 2D histogram pg[:, ma] keeps the trailing bin axis and sum(axis=1) left one correction per column instead of one per parameter. It broadcast without an error, so the result was silently wrong.
Added test_BinnedNLL_2D_mask_grad, which compares a masked 2D gradient to the numerical gradient. It fails on the old code (all 4 elements mismatch) and passes now.
The renormalization correction summed the model gradient over both axes, which gave one scalar for all parameters. It must be summed over the bin axis only, so that each parameter gets its own correction. The existing mask test uses a one-parameter model, for which the two sums agree, so the bug was not seen. The new test compares against a numerical gradient with two parameters. Reported in #1132. Assisted-by: ClaudeCode:claude-opus-5
The mask selects along the first axis only, so the renormalization correction must also sum over the remaining bin axes and reshape to broadcast over the prediction. Assisted-by: ClaudeCode:claude-opus-5
ae5b41a to
0ebf54e
Compare
🤖 AI text below 🤖
The masked renormalization correction in
BinnedNLL._gradsummed the model gradient over both axes, which gave one scalar for all parameters. It must give one correction per parameter. Any maskedBinnedNLLfit that used the analytic gradient got silently wrong results.The mask selects along the first axis only, so for a multi-dimensional histogram the correction must also sum over the remaining bin axes, then reshape to broadcast over the prediction.
The existing
test_BinnedNLL_maskuses a one-parameter 1D model, for which the sums agree, so the bug was not seen. The new tests compare against a numerical gradient with two parameters in 1D and with a masked 2D histogram.Reported in #1132.