The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →A Siamese image model learns to place related images near one another in an embedding space. In Keras, the essential pattern is to build one image encoder, call that same encoder for both inputs, and train the resulting pair model with labels that define what “similar” means. The official Keras contrastive-loss example demonstrates the pattern on MNIST digits; below is a runnable adaptation and guidance for using it beyond the demo.
What the Siamese model learns
A Siamese network has two input branches that share weights. Each branch passes an image through the same encoder and produces an embedding vector. A distance function compares those vectors: same-relation pairs are trained to have a small distance, while different-relation pairs are trained to be farther apart.
As an Amazon Associate I earn from qualifying purchases.
As the Keras example puts it, “Siamese Networks are neural networks which share weights between two or more sister networks, each producing embedding vectors of its respective inputs.” This shared encoder is the defining detail: calling two separately initialized encoders would not implement the same design.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Before writing code, define the positive relation for your application. It might mean the same object, person, product, class, or near-duplicate image. The MNIST example defines a positive pair as two images of the same digit class, not necessarily two pictures of the same physical handwritten digit.
#1 Best Overall
Choose the learning objective and data unit
The Keras examples illustrate three distinct approaches. Choose based on the supervision you have and how you intend to use the embeddings; their code snippets and labels are not interchangeable.
| Approach | Training data | Objective and distinction |
|---|---|---|
| Contrastive loss | Labeled image pairs | Pull positive pairs close and push negative pairs beyond a margin. A direct fit when pair labels are available. See the Keras contrastive example. |
| Triplet loss | Anchor, positive, and negative images | Make the anchor closer to the positive than to the negative by a margin. Requires selecting meaningful positives and negatives. See the Keras triplet example. |
| Batch metric learning | Anchor-positive pairs sampled across classes in a batch | Uses other examples in the batch within the embedding objective. The Keras example normalizes embeddings and uses dot products for neighbor comparisons. See the Keras metric-learning example. |
The contrastive-loss implementation below follows the first approach. It uses the Keras example’s label convention: 0 means a same-class pair, and 1 means a different-class pair. Keep that convention consistent when building labels, defining the loss, and interpreting predictions.
Rank #2
- Use scikit-learn to track an example ML project end to end
- Explore several models, including support vector machines, decision trees, random forests, and ensemble methods
- Exploit unsupervised learning techniques such as dimensionality reduction, clustering, and anomaly detection
- Dive into neural net architectures, including convolutional nets, recurrent nets, generative adversarial networks, autoencoders, diffusion models, and transformers
- Use TensorFlow and Keras to build and train neural nets for computer vision, natural language processing, generative models, and deep reinforcement learning
Build and train a contrastive model
Load MNIST and construct pairs
The reference walkthrough uses 28×28 grayscale MNIST images. Its pair builder creates a matching pair and a different-class pair from each source image, and constructs pairs separately from the training, validation, and test partitions. This example uses the training and validation pairs for fitting; hold the test pairs back for final evaluation.
import numpy as np
import keras
from keras import layers
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# Add the channel dimension and scale pixels to [0, 1].
x_train = x_train.astype("float32")[..., None] / 255.0
x_test = x_test.astype("float32")[..., None] / 255.0
def make_pairs(images, labels, rng=None):
"""Return image pairs and labels: 0=same class, 1=different class."""
rng = np.random.default_rng() if rng is None else rng
by_class = [np.flatnonzero(labels == c) for c in np.unique(labels)]
pairs, pair_labels = [], []
for i, image in enumerate(images):
same_class = by_class[labels[i]]
positive_index = i
while positive_index == i:
positive_index = rng.choice(same_class)
negative_class = rng.choice([c for c in range(len(by_class)) if c != labels[i]])
negative_index = rng.choice(by_class[negative_class])
pairs.append((image, images[positive_index]))
pair_labels.append(0)
pairs.append((image, images[negative_index]))
pair_labels.append(1)
left = np.asarray([pair[0] for pair in pairs], dtype="float32")
right = np.asarray([pair[1] for pair in pairs], dtype="float32")
return (left, right), np.asarray(pair_labels, dtype="float32")
(x_train_left, x_train_right), y_train_pairs = make_pairs(
x_train, y_train, np.random.default_rng(1)
)
(x_test_left, x_test_right), y_test_pairs = make_pairs(
x_test, y_test, np.random.default_rng(2)
)
This code assumes the labels index contiguous classes from zero, as MNIST does. For another dataset, adapt class lookup to its labels. If the intended claim is performance on unseen people, products, or other entities, split by entity before generating pairs; otherwise related images can leak across partitions and make evaluation misleading.
Rank #3
Create one shared image encoder
The encoder below follows the small-network spirit of the Keras tutorial: convolution, average pooling, batch normalization, flattening, and a compact embedding. The tutorial’s own embedding is a 10-unit tanh output. This is a teaching baseline for small grayscale digits, not a fixed architecture recommendation for every image domain.
input_shape = (28, 28, 1)
image = keras.Input(shape=input_shape)
x = layers.Conv2D(32, 3, activation="relu")(image)
x = layers.BatchNormalization()(x)
x = layers.AveragePooling2D()(x)
x = layers.Conv2D(64, 3, activation="relu")(x)
x = layers.BatchNormalization()(x)
x = layers.AveragePooling2D()(x)
x = layers.Flatten()(x)
x = layers.Dense(128, activation="relu")(x)
embedding = layers.Dense(10, activation="tanh")(x)
embedding_network = keras.Model(image, embedding, name="embedding_network")
left_input = keras.Input(shape=input_shape, name="left_image")
right_input = keras.Input(shape=input_shape, name="right_image")
left_embedding = embedding_network(left_input)
right_embedding = embedding_network(right_input)
def euclidean_distance(inputs):
left, right = inputs
squared_distance = keras.ops.sum(keras.ops.square(left - right), axis=1, keepdims=True)
return keras.ops.sqrt(keras.ops.maximum(squared_distance, keras.backend.epsilon()))
distance = layers.Lambda(euclidean_distance, name="distance")(
[left_embedding, right_embedding]
)
pair_model = keras.Model([left_input, right_input], distance, name="siamese_contrastive")
Both branches call embedding_network, so their weights are shared and updated together. The example’s Keras source file shows the corresponding implementation. The tutorial page was created May 6, 2021 and last modified January 28, 2026; it does not pin an installed Keras version or guarantee compatibility with every backend and configuration. Check the current example and your local environment if an API differs.
Rank #4
Apply contrastive loss with the same label convention
With same-class labels set to zero and different-class labels set to one, contrastive loss pulls zero-labeled pairs toward distance zero. It penalizes one-labeled pairs only while their distance remains inside the margin.
Recommended Free Tools
def contrastive_loss(y_true, distance):
margin = 1.0
y_true = keras.ops.cast(y_true, distance.dtype)
similar_cost = (1.0 - y_true) * keras.ops.square(distance)
dissimilar_cost = y_true * keras.ops.square(
keras.ops.maximum(margin - distance, 0.0)
)
return keras.ops.mean(similar_cost + dissimilar_cost)
pair_model.compile(
optimizer=keras.optimizers.RMSprop(),
loss=contrastive_loss,
)
# Reserve validation pairs from the training partition in a real workflow.
pair_model.fit(
[x_train_left, x_train_right],
y_train_pairs,
batch_size=16,
epochs=10,
validation_split=0.1,
)
The Keras walkthrough uses margin 1, RMSprop, batch size 16, and 10 epochs as example settings. They are not universal hyperparameter recommendations. For a careful experiment, construct validation pairs explicitly and ensure the validation split reflects the intended deployment task.
Best Value
Evaluate distances for the actual task
Pair verification needs a validation-selected threshold
The model outputs a distance, not a class probability. For verification—answering whether two images should count as a match—choose a distance threshold using validation data, then apply that fixed rule to held-out test pairs. A distance below the threshold is a match under that rule.
The Keras example’s helper uses 0.5 as an instructional cutoff, with distances above 0.5 treated as dissimilar. That cutoff is not calibrated for other datasets, encoders, or applications. Report test performance with the validation-selected threshold rather than tuning it on the test set.
Retrieval needs ranked-neighbor evaluation
For search or retrieval, compare a query embedding with a gallery of embeddings, rank candidates by distance, and assess whether relevant items appear near the top. Pair accuracy alone does not describe this ranking task. With unit-normalized embeddings, dot products can be used as cosine-similarity scores, as in the Keras metric-learning example.
Keep dataset results in context
The contrastive walkthrough uses MNIST digits; the triplet walkthrough uses the Totally Looks Like dataset, with anchor and visually similar positive image files; the metric-learning example uses CIFAR-10. They are different data setups, not comparable evidence that one method or architecture wins. Evaluate on held-out examples representative of the images, labels, and task you expect in deployment.
For historical context only, FaceNet reported 99.63% on Labeled Faces in the Wild and 95.12% on YouTube Faces DB in its 2015 paper, along with 128-byte face representations. The paper also reported a 30% error-rate reduction against the best published result on both named datasets. Those are results for that paper’s system and protocols, not results from this Keras MNIST tutorial or current records. See the FaceNet paper.
Quick Recap
Adapt the pipeline without changing what its labels mean
- Image shape and preprocessing: MNIST uses one grayscale channel. The triplet example instead decodes three-channel JPEGs, converts values to floating point, resizes to 200×200, and applies ResNet preprocessing. Change the model input shape and preprocessing together when changing datasets; the triplet example illustrates that different path.
- Pair labels: Keep the documented zero-for-similar and one-for-dissimilar convention if retaining this loss. If you invert labels, rewrite the loss and prediction interpretation accordingly.
- Sampling: Random pairs can be easy to distinguish. In an applied problem, inspect the examples the model confuses and ensure your pair or triplet construction represents meaningful cases. Triplet learning needs anchor-positive-negative construction; batch metric learning uses examples in a batch differently.
- Split strategy: Split at the underlying identity or entity level when generalization to unseen entities is the goal, then construct pairs within each partition.
- Output use: Set a validation threshold for verification or evaluate ranked neighbors for retrieval. Do not treat either metric as a substitute for the other.
Common implementation errors
- Separate encoders by accident: Two separately constructed models do not share weights. Define one encoder model and invoke it for both inputs.
- Reversed labels: Under the code above, zero denotes a positive pair and one a negative pair. A common label inversion makes the loss push examples in the wrong direction.
- Preprocessing mismatch: Training, validation, test, and live inference images must have compatible shape, channel order, value scaling, and resizing.
- Calling distance a probability: A Euclidean distance has no inherent probability calibration. Establish an operating threshold from validation examples if making binary decisions.
- Using pair accuracy for search: Retrieval depends on the ordering of a gallery, so evaluate ranked neighbors using held-out data.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




