Skip to content

Fix: Validate class_weight keys in model.fit#23222

Open
Hasnaathussain wants to merge 4 commits into
keras-team:masterfrom
Hasnaathussain:fix/23220-class-weight-validation
Open

Fix: Validate class_weight keys in model.fit#23222
Hasnaathussain wants to merge 4 commits into
keras-team:masterfrom
Hasnaathussain:fix/23220-class-weight-validation

Conversation

@Hasnaathussain

@Hasnaathussain Hasnaathussain commented Jul 5, 2026

Copy link
Copy Markdown

This PR introduces validation logic to ensure that keys passed to class_weight in model.fit() are valid class indices present in the target labels, raising a clear ValueError for invalid keys instead of silently ignoring them. Fixes #23220.

Contributor Agreement

Please review our AI-Assisted Contribution Policy and check all boxes below before submitting your PR for review:

  • I am a human, and not a bot.
  • I will be responsible for responding to review comments in a timely manner.
  • I will work with the maintainers to push this PR forward until submission.

Note: Failing to adhere to this agreement may result in your future PRs no longer being reviewed.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces validation for class_weight keys to ensure they are integers and match the target labels, along with a type check for the trainable field during deserialization. The feedback highlights that the trainable validation is out of scope and fragile, potentially breaking backward compatibility. Additionally, the reviewer points out a performance bottleneck caused by duplicate NumPy conversions of target labels and an unhandled ValueError when processing empty class_weight dictionaries in the TensorFlow dataset adapter.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread keras/src/saving/serialization_lib.py Outdated
Comment on lines +774 to +781
if "trainable" in inner_config and not isinstance(
inner_config["trainable"], bool
):
raise TypeError(
"Invalid type for `trainable` field. "
f"Expected bool. Received: {inner_config['trainable']} "
f"of type {type(inner_config['trainable'])}."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This validation logic is completely unrelated to the PR's scope, which is to validate class_weight keys in model.fit(). Unrelated changes should be removed from this PR.\n\nFurthermore, this validation is fragile and can break backward compatibility. In Python, bool is a subclass of int, but isinstance(1, bool) is False. If any legacy or third-party serialized model config uses 1 or 0 for trainable (which is common in some JSON serializers), this check will raise a TypeError and prevent loading the model. Please remove this change.

Comment on lines +143 to +161
if y is not None:
y_numpy = ops.convert_to_numpy(y)
if len(y_numpy.shape) > 1:
if y_numpy.shape[-1] != 1:
y_numpy = np.argmax(y_numpy, axis=-1)
else:
y_numpy = np.squeeze(y_numpy, axis=-1)
y_numpy = np.round(y_numpy).astype("int32")

unique_classes = set(int(c) for c in np.unique(y_numpy))
if unique_classes and class_weight_int:
intersection = unique_classes.intersection(class_weight_int.keys())
if not intersection:
raise ValueError(
f"None of the keys in `class_weight` match any class index "
f"present in the target labels. "
f"class_weight keys: {list(class_weight_int.keys())}. "
f"Unique classes in target data: {list(unique_classes)}."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Converting y to a NumPy array and processing its shape is duplicated exactly between validate_class_weight and class_weight_to_sample_weights. Since ops.convert_to_numpy(y) can be an expensive operation (especially when transferring data from GPU/TPU to host memory), doing it twice introduces a significant performance bottleneck.\n\nWe can optimize this by performing the conversion and shape processing once in class_weight_to_sample_weights, and then passing the processed 1D NumPy array of class indices directly to validate_class_weight.

    if y is not None:\n        unique_classes = set(int(c) for c in np.unique(y))\n        if unique_classes and class_weight_int:\n            intersection = unique_classes.intersection(class_weight_int.keys())\n            if not intersection:\n                raise ValueError(\n                    f\"None of the keys in `class_weight` match any class index \"\n                    f\"present in the target labels. \"\n                    f\"class_weight keys: {list(class_weight_int.keys())}. \"\n                    f\"Unique classes in target data: {list(unique_classes)}.\"\n                )

"""
from keras.src.utils.module_utils import tensorflow as tf

class_weight = data_adapter_utils.validate_class_weight(class_weight)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If class_weight is empty (e.g., {}), validate_class_weight will return {}. In that case, calling max(class_weight.keys()) on line 168 will raise a ValueError: max() arg is an empty sequence.\n\nWe should handle empty class_weight gracefully by returning an identity mapping function.

    class_weight = data_adapter_utils.validate_class_weight(class_weight)\n    if not class_weight:\n        return lambda *data: data

Comment on lines +451 to +467
def test_trainable_validation(self):
"""Tests that invalid `trainable` types raise a TypeError."""
layer = keras.layers.Dense(units=4)
config = keras.saving.serialize_keras_object(layer)

# Test valid boolean
config["config"]["trainable"] = True
restored = keras.saving.deserialize_keras_object(config)
self.assertTrue(restored.trainable)

# Test invalid type (string)
config["config"]["trainable"] = "true"
with self.assertRaisesRegex(
TypeError, "Invalid type for `trainable` field"
):
keras.saving.deserialize_keras_object(config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test is for the unrelated trainable validation logic in serialization_lib.py. Since that change is out of scope and should be removed, this test should also be removed.

@codecov-commenter

codecov-commenter commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.04%. Comparing base (2aeecf0) to head (71e1492).

Files with missing lines Patch % Lines
...s/src/trainers/data_adapters/data_adapter_utils.py 89.65% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #23222      +/-   ##
==========================================
- Coverage   84.80%   84.04%   -0.76%     
==========================================
  Files         465      465              
  Lines       69201    69227      +26     
  Branches    11379    11388       +9     
==========================================
- Hits        58687    58184     -503     
- Misses       7577     8116     +539     
+ Partials     2937     2927      -10     
Flag Coverage Δ
keras 83.87% <91.66%> (-0.75%) ⬇️
keras-cpu 83.87% <91.66%> (+<0.01%) ⬆️
keras-gpu ?
keras-jax 57.93% <91.66%> (-0.33%) ⬇️
keras-numpy 53.71% <91.66%> (+0.01%) ⬆️
keras-openvino 59.55% <91.66%> (+0.01%) ⬆️
keras-tensorflow 59.52% <91.66%> (-0.29%) ⬇️
keras-torch 58.67% <91.66%> (-0.39%) ⬇️
keras-tpu ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Hasnaathussain Hasnaathussain force-pushed the fix/23220-class-weight-validation branch from 4870071 to 33a6433 Compare July 6, 2026 17:43
@Hasnaathussain Hasnaathussain force-pushed the fix/23220-class-weight-validation branch from 33a6433 to fd42e66 Compare July 6, 2026 17:51
@Hasnaathussain

Copy link
Copy Markdown
Author

Hi @gbaned - This PR has been ready for review for 1 day and all CI checks are passing.

Summary:

Status: ✅ Ready for review and merge

Could you please review and approve when ready?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid class_weight keys silently ignored in model.fit(), no error raised

3 participants