Skip to content

Migration of Supervised contrative Learning tutorial to Keras3 - #2398

Open
maitry63 wants to merge 7 commits into
keras-team:masterfrom
maitry63:sup_contrast_br-followup
Open

Migration of Supervised contrative Learning tutorial to Keras3#2398
maitry63 wants to merge 7 commits into
keras-team:masterfrom
maitry63:sup_contrast_br-followup

Conversation

@maitry63

Copy link
Copy Markdown
Collaborator

This PR is a follow-up to the work done in #2338 that migrated tutorial Supervised contrative Learning into Keras3.

  • Added the required generated files.
  • Fixed the related test cases.
  • Completed the remaining follow-up changes.

@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 migrates the supervised contrastive learning example to Keras 3, replacing TensorFlow and TensorFlow Addons dependencies with unified Keras APIs and keras.ops. The review feedback suggests several improvements to the custom SupervisedContrastiveLoss implementation across the Python script, Jupyter Notebook, and Markdown documentation. Specifically, it recommends implementing the get_config method for proper serialization, using dynamic dtypes (logits.dtype) instead of hardcoded "float32" to ensure mixed-precision compatibility, and replacing the non-idiomatic ops.subtract(0.0, ...) with a simple unary negation operator.

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 on lines +160 to 162
def __init__(self, temperature=0.05, **kwargs):
super().__init__(**kwargs)
self.temperature = temperature

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

To support proper serialization and deserialization of the custom loss (e.g., when saving and loading the model), it is best practice in Keras 3 to implement the get_config method.

    def __init__(self, temperature=0.05, **kwargs):
        super().__init__(**kwargs)
        self.temperature = temperature

    def get_config(self):
        config = super().get_config()
        config.update({"temperature": self.temperature})
        return config

Comment on lines +175 to +178
mask = ops.cast(ops.equal(labels, ops.transpose(labels)), "float32")

batch_size = ops.shape(logits)[0]
logits_mask = 1.0 - ops.eye(batch_size)

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

To ensure full compatibility with mixed precision training (e.g., mixed_float16 or mixed_bfloat16) and to prevent potential runtime dtype mismatch errors in strict backends like PyTorch or JAX, it is highly recommended to use the dynamic dtype of the input logits (i.e., logits.dtype) instead of hardcoding "float32" and default float for ops.eye.

Suggested change
mask = ops.cast(ops.equal(labels, ops.transpose(labels)), "float32")
batch_size = ops.shape(logits)[0]
logits_mask = 1.0 - ops.eye(batch_size)
mask = ops.cast(ops.equal(labels, ops.transpose(labels)), logits.dtype)
batch_size = ops.shape(logits)[0]
logits_mask = 1.0 - ops.eye(batch_size, dtype=logits.dtype)

ops.sum(mask, axis=1) + 1e-8
)

return ops.subtract(0.0, ops.mean(mean_log_prob_pos))

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

Using ops.subtract(0.0, ...) is non-idiomatic and less readable. We can simply use the unary negation operator - on the tensor, which is fully supported across all Keras backends.

Suggested change
return ops.subtract(0.0, ops.mean(mean_log_prob_pos))
return -ops.mean(mean_log_prob_pos)

Comment on lines +260 to 262
" def __init__(self, temperature=0.05, **kwargs):\n",
" super().__init__(**kwargs)\n",
" self.temperature = temperature\n",

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

To support proper serialization and deserialization of the custom loss (e.g., when saving and loading the model), it is best practice in Keras 3 to implement the get_config method.

Suggested change
" def __init__(self, temperature=0.05, **kwargs):\n",
" super().__init__(**kwargs)\n",
" self.temperature = temperature\n",
" def __init__(self, temperature=0.05, **kwargs):\n",
" super().__init__(**kwargs)\n",
" self.temperature = temperature\n",
"\n",
" def get_config(self):\n",
" config = super().get_config()\n",
" config.update({\"temperature\": self.temperature})\n",
" return config\n",

Comment on lines +275 to +278
" mask = ops.cast(ops.equal(labels, ops.transpose(labels)), \"float32\")\n",
"\n",
" batch_size = ops.shape(logits)[0]\n",
" logits_mask = 1.0 - ops.eye(batch_size)\n",

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

To ensure full compatibility with mixed precision training (e.g., mixed_float16 or mixed_bfloat16) and to prevent potential runtime dtype mismatch errors in strict backends like PyTorch or JAX, it is highly recommended to use the dynamic dtype of the input logits (i.e., logits.dtype) instead of hardcoding "float32" and default float for ops.eye.

Suggested change
" mask = ops.cast(ops.equal(labels, ops.transpose(labels)), \"float32\")\n",
"\n",
" batch_size = ops.shape(logits)[0]\n",
" logits_mask = 1.0 - ops.eye(batch_size)\n",
" mask = ops.cast(ops.equal(labels, ops.transpose(labels)), logits.dtype)\n",
"\n",
" batch_size = ops.shape(logits)[0]\n",
" logits_mask = 1.0 - ops.eye(batch_size, dtype=logits.dtype)\n",

" ops.sum(mask, axis=1) + 1e-8\n",
" )\n",
"\n",
" return ops.subtract(0.0, ops.mean(mean_log_prob_pos))\n",

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

Using ops.subtract(0.0, ...) is non-idiomatic and less readable. We can simply use the unary negation operator - on the tensor, which is fully supported across all Keras backends.

Suggested change
" return ops.subtract(0.0, ops.mean(mean_log_prob_pos))\n",
" return -ops.mean(mean_log_prob_pos)\n"

Comment on lines +311 to 313
def __init__(self, temperature=0.05, **kwargs):
super().__init__(**kwargs)
self.temperature = temperature

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

To support proper serialization and deserialization of the custom loss (e.g., when saving and loading the model), it is best practice in Keras 3 to implement the get_config method.

Suggested change
def __init__(self, temperature=0.05, **kwargs):
super().__init__(**kwargs)
self.temperature = temperature
def __init__(self, temperature=0.05, **kwargs):
super().__init__(**kwargs)
self.temperature = temperature
def get_config(self):
config = super().get_config()
config.update({"temperature": self.temperature})
return config

Comment on lines +326 to +329
mask = ops.cast(ops.equal(labels, ops.transpose(labels)), "float32")

batch_size = ops.shape(logits)[0]
logits_mask = 1.0 - ops.eye(batch_size)

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

To ensure full compatibility with mixed precision training (e.g., mixed_float16 or mixed_bfloat16) and to prevent potential runtime dtype mismatch errors in strict backends like PyTorch or JAX, it is highly recommended to use the dynamic dtype of the input logits (i.e., logits.dtype) instead of hardcoding "float32" and default float for ops.eye.

Suggested change
mask = ops.cast(ops.equal(labels, ops.transpose(labels)), "float32")
batch_size = ops.shape(logits)[0]
logits_mask = 1.0 - ops.eye(batch_size)
mask = ops.cast(ops.equal(labels, ops.transpose(labels)), logits.dtype)
batch_size = ops.shape(logits)[0]
logits_mask = 1.0 - ops.eye(batch_size, dtype=logits.dtype)

ops.sum(mask, axis=1) + 1e-8
)

return ops.subtract(0.0, ops.mean(mean_log_prob_pos))

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

Using ops.subtract(0.0, ...) is non-idiomatic and less readable. We can simply use the unary negation operator - on the tensor, which is fully supported across all Keras backends.

Suggested change
return ops.subtract(0.0, ops.mean(mean_log_prob_pos))
return -ops.mean(mean_log_prob_pos)

from tensorflow.keras import layers
import os

os.environ["KERAS_BACKEND"] = "tensorflow" # or "torch" or "jax"

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.

Any reason not to use "jax"?

@hertschuh hertschuh 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.

Can you test with the JAX backend just to make sure?

@maitry63

maitry63 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Can you test with the JAX backend just to make sure?

Sure, tested with Jax backend.

@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open for 14 days with no activity. It will be closed if no further activity occurs. Thank you.

@github-actions github-actions Bot added the stale label Aug 19, 2026
@maitry63 maitry63 removed the stale label Aug 19, 2026

@hertschuh hertschuh 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.

There is a merge conflict:

Comment thread scripts/autogen.py Outdated
Comment on lines +35 to +38
"keras": f"{KERAS_TEAM_GH}/keras/tree/v3.15.1/",
"keras_tuner": f"{KERAS_TEAM_GH}/keras-tuner/tree/v1.4.8/",
"keras_hub": f"{KERAS_TEAM_GH}/keras-hub/tree/v0.30.0/",
"tf_keras": f"{KERAS_TEAM_GH}/tf-keras/tree/v2.20.0/",
"tf_keras": f"{KERAS_TEAM_GH}/tf-keras/tree/v2.20.1/",

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.

Can you rebase? I believe these would go away.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Rebased, to fix the merge conflicts.

@maitry63
maitry63 force-pushed the sup_contrast_br-followup branch from 641cd91 to 05a24a9 Compare August 31, 2026 05:52
@maitry63
maitry63 force-pushed the sup_contrast_br-followup branch from 05a24a9 to 5d79f58 Compare August 31, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants