Fix division by zero NaN in categorical_crossentropy - #23552
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the categorical cross-entropy and sparse categorical cross-entropy implementations across multiple backends (JAX, NumPy, OpenVINO, TensorFlow, Torch, and legacy) to add backend epsilon to the denominator during normalization, preventing potential division-by-zero errors. The review feedback suggests improving consistency and robustness by storing the epsilon value in a variable cast to the output's data type (using convert_to_tensor) instead of calling backend.epsilon() multiple times.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #23552 +/- ##
===========================================
- Coverage 84.93% 73.52% -11.41%
===========================================
Files 468 478 +10
Lines 71147 71678 +531
Branches 11788 11838 +50
===========================================
- Hits 60426 52702 -7724
- Misses 7717 16050 +8333
+ Partials 3004 2926 -78
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
e66e3ae to
95f6d7e
Compare
hertschuh
left a comment
There was a problem hiding this comment.
Thanks for looking into this!
95f6d7e to
76af0f0
Compare
|
done with requested modification 👍 |
d54659c to
a0604f2
Compare
hertschuh
left a comment
There was a problem hiding this comment.
The JAX tests are failing, apparently, there are still NaNs in the result. Can you look into this?
|
Hi @hertschuh , trying to figure out a fix for this, any suggestions? |
Can you try if |
91c4074 to
9a560be
Compare
|
@hertschuh PTAL :) |
This prevents catastrophic gradient poisoning when predictions sum to exactly zero by adding `backend.epsilon()` to the denominator across legacy, PyTorch, TF, OpenVINO and NumPy backends using `convert_to_tensor(backend.epsilon(), dtype=output.dtype)`. For JAX backend, we utilize `divide_no_nan` to explicitly protect against subnormal float16 hardware FTZ behaviors that cause 0.0/0.0 NaNs. Ported from tensorflow/tensorflow#123494 Fixes keras-team#23551
9a560be to
9825ea7
Compare
Oh, I was thinking you would do |
I tried Using divide_no_nan but in NumPy eagerly evaluates the division before masking, which still throws a RuntimeWarning: divide by zero on execution. Furthermore, PyTorch and TF divide_no_nan implementations allocate multiple intermediate mask tensors, making them slower than a single highly-optimized maximum kernel. So maximum preserves faster continuous gradients for those backends, making divide_no_nan strictly a JAX-specific compiler workaround. |
Hmm... Ok, looking at the math, I like the epsilon approach better actually. But now I don't like that JAX is inconsistent in the approach. Sorry for the back and forth, but can you revert back the JAX implementation to the epsilon approach? Let's debug why it's not passing the tests. |
…ossentropy via custom_vjp
|
the tests are failing due to float16 underflow I guess , I tried to resolve it by adding custom_vjp to compute in float32 |
| ) | ||
| from keras.src.backend.jax.core import cast | ||
| from keras.src.backend.jax.core import convert_to_tensor | ||
| from keras.src.backend.jax.numpy import divide_no_nan |
There was a problem hiding this comment.
unused import — not referenced anywhere in this diff, grep confirms it
There was a problem hiding this comment.
Iteratively working on thr pr
| return -jnp.sum(target * log_prob, axis=axis) | ||
|
|
||
|
|
||
| categorical_crossentropy_vjp = jax.custom_vjp( |
There was a problem hiding this comment.
this isn't mentioned in the PR description
There was a problem hiding this comment.
I will change the description once the fix is confirmed
this isn't mentioned in the PR description
This PR fixes the zero-day division by zero NaN vulnerability described in #23551.
By adding
backend.epsilon()to the denominator when normalizing predictions across all backends (Legacy, TF, Torch, JAX, NumPy, OpenVINO), we prevent catastrophic gradient poisoning when predictions sum to exactly zero.This issue was originally identified and patched in TensorFlow (tensorflow/tensorflow#123494) and is being ported upstream here per the maintainers' request.
Fixes #23551
Contributor Agreement