NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #2910 most downloaded on PyPI
The easiest way to use deep metric learning in your application. Modular, flexible, and extensible. Written in PyTorch.
Last release 1 years ago
17 Aug 2025
Release timing varies
gaps range from 3 weeks to 8 months
Most releases are documented
notes for 52 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
7 years old
212 releases · first in 2019
Added SmoothAPLoss . Thanks @ir2718 !
Fixed some module import issues.
Fixed some module import issues.
One column per quarter.
Added the Datasets module for easy downloading of common datasets:
Added ThresholdConsistentMarginLoss .
Improvement + small breaking change to DistributedLossWrapper
DistributedLossWrapperemb argument of DistributedLossWrapper.forward to embeddings to be consistent with the rest of the library.DistributedLossWrapper is being used in a non-distributed setting.Allow scaling up the memory and batch size when using TripletMarginMiner
Thanks @mkmenta !
This is identical to v2.4.0, but includes the LICENSE file which was missing from v2.4.0.
This is identical to v2.4.0, but includes the LICENSE file which was missing from v2.4.0.
Added DynamicSoftMarginLoss . See PR #659 . Thanks @domenicoMuscill0 !
Added HistogramLoss . See pull request #651 . Thanks @domenicoMuscill0 !
Added ManifoldLoss . See pull request #635 . Thanks @domenicoMuscill0 !
symmetric flag to SelfSupervisedLoss. If True, then the embeddings in both embeddings and ref_emb are used as anchors. If False, then only the embeddings in embeddings are used as anchors. The previous behavior was equivalent to symmetric=False. Now the default is symmetric=True, because this is usually what is done in self supervised papers (e.g. SimCLR).Fixed bug where set_stats was not being called in TripletMarginMiner
set_stats was not being called in TripletMarginMiner (#628)HierarchicalSampler extend torch.utils.data.Sampler instead of torch.utils.data.BatchSampler (#613)Fixes bug where BaseDistance.initial_avg_query_norm was not actually being set
BaseDistance.initial_avg_query_norm was not actually being set (#620)New loss function: PNPLoss. Thanks @interestingzhuo!
New loss function: PNPLoss. Thanks @interestingzhuo!
### Bug Fixes - Fixed #591. Thanks @HSinger04!
…in the future without causing more breaking changes.
You don't have to create labels for self-supervised learning anymore:
from pytorch_metric_learning.losses import SelfSupervisedLoss
loss_func = SelfSupervisedLoss(TripletMarginLoss())
embeddings = model(data)
augmented = model(augmented_data)
loss = loss_func(embeddings, augmented)
Thanks @cwkeam!
The order and naming of arguments has changed.
get_accuracy(
query,
reference,
query_labels,
reference_labels,
embeddings_come_from_same_source=False
)
get_accuracy(
query,
query_labels,
reference=None,
reference_labels=None
ref_includes_query=False
)
The benefits of this change are:
query is reference, then you only need to pass in query, query_labelsref_includes_query is shorter and clearer in meaning than embeddings_come_from_same_sourceSome example usage of the new format:
# Accuracy of a query set, where the query set is also the reference set:
get_accuracy(query, query_labels)
# Accuracy of a query set with a separate reference set:
get_accuracy(query, query_labels, ref, ref_labels)
# Accuracy of a query set with a reference set that includes the query set:
get_accuracy(query, query_labels, ref, ref_labels, ref_includes_query=True)
BaseMiner instead of BaseTupleMinerMiners must extend BaseMiner because BaseTupleMiner no longer exists
enqueue_idx is now enqueue_maskBefore, enqueue_idx specified the indices of embeddings that should be added to the memory bank.
Now, enqueue_mask[i] should be True if embeddings[i] should be added to the memory bank.
The benefit of this change is that it fixed an issue in distributed training.
Here's an example of the new usage:
# enqueue the second half of a batch
enqueue_mask = torch.zeros(batch_size).bool()
enqueue_mask[batch_size/2:] = True
Before:
loss_fn = VICRegLoss()
loss_fn(emb, ref_emb)
Now:
loss_fn = VICRegLoss()
loss_fn(emb, ref_emb=ref_emb)
The reason is that VICRegLoss now uses the forward method of BaseMetricLossFunction, to allow for possible generalizations in the future without causing more breaking changes.
mining_funcs and dataset have swapped orderThis is to allow mining_funcs to be optional.
Before if you didn't want to use miners:
MetricLossOnly(
models,
optimizers,
batch_size,
loss_funcs,
mining_funcs = {},
dataset = dataset,
)
Now:
MetricLossOnly(
models,
optimizers,
batch_size,
loss_funcs,
dataset,
)
The following classes/functions were removed
losses.CentroidTripletLoss (it contained a bug that I don't have time to figure out)miners.BaseTupleMiner (use miners.BaseMiner instead)miners.BaseSubsetBatchMiner (rarely used)miners.MaximumLossMiner (rarely used)trainers.UnsupervisedEmbeddingsUsingAugmentations (rarely used)utils.common_functions.Identity (use torch.nn.Identity instead)Nothing published for this version
Nothing published for this version
Fixed #472, in which enqueue_idx for CrossBatchMemory could not be passed into DistributedLossWrapper
enqueue_idx for CrossBatchMemory could not be passed into DistributedLossWrapperembedding_memory and label_memory to buffers, so they can be saved and loaded as state dicts and transferred to devices use .to(device).Resolved https://github.com/KevinMusgrave/pytorch-metric-learning/issues/565
Resolved https://github.com/KevinMusgrave/pytorch-metric-learning/issues/565
Fixed bug where labels were always required for DistributedLossWrapper
Fixes an edge case in ArcFaceLoss. Thanks @ElisonSherton!
Fixes an edge case in ArcFaceLoss. Thanks @ElisonSherton!
Relevant links:
Fixed bug where DistributedMinerWrapper would crash when world_size == 1
DistributedMinerWrapper would crash when world_size == 1 (#542)To be consistent with the common definition of mean average precision, the divisor has been changed again:
To be consistent with the common definition of mean average precision, the divisor has been changed again:
min(k, num_relevant)num_relevantAgain, this has no effect on mean_average_precision_at_r
Fixed a bug in mean_average_precision in AccuracyCalculator. Previously, the divisor for each sample was the number of correctly retrieved samples. In
Fixed a bug in mean_average_precision in AccuracyCalculator. Previously, the divisor for each sample was the number of correctly retrieved samples. In the new version, the divisor for each sample is min(k, num_relevant).
For example, if class "A" has 11 samples, then num_relevant is 11 for every sample with the label "A".
k = 5, meaning that 5 nearest neighbors are retrieved for each sample, then the divisor will be 5.k = 100, meaning that 100 nearest neighbors are retrieved for each sample, then the divisor will be 11.The bug in previous versions did not affect mean_average_precision_at_r.
Added additional shape checks to AccuracyCalculator.get_accuracy.
DistributedLossWrapper and DistributedMinerWrapper now support ref_emb and ref_labels:
DistributedLossWrapper and DistributedMinerWrapper now support ref_emb and ref_labels:
from pytorch_metric_learning import losses
from pytorch_metric_learning.utils import distributed as pml_dist
loss_func = losses.ContrastiveLoss()
loss_func = pml_dist.DistributedLossWrapper(loss_func)
loss = loss_func(embeddings, labels, ref_emb=ref_emb, ref_labels=ref_labels)
Thanks @NoTody for PR #503
In previous versions, when embeddings_come_from_same_source == True, the first nearest-neighbor of each query embedding was discarded, with the assump
In previous versions, when embeddings_come_from_same_source == True, the first nearest-neighbor of each query embedding was discarded, with the assumption that it must be the query embedding itself.
While this is usually the case, it's not always the case. It is possible for two different embeddings to be exactly equal to each other, and discarding the first nearest-neighbor in this case can be incorrect.
This release fixes this bug by excluding each embedding's index from the k-nn results.
In order for the above bug fix to work, AccuracyCalculator now requires that reference[:len(query)] == query when embeddings_come_from_same_source == True. For example, the following will raise an error:
query = torch.randn(100, 10)
ref = torch.randn(100, 10)
ref = torch.cat([ref, query], dim=0)
AC.get_accuracy(query, ref, labels1, labels2, True)
# ValueError
To fix this, move query to the beginning of ref:
query = torch.randn(100, 10)
ref = torch.randn(100, 10)
ref = torch.cat([query, ref], dim=0)
AC.get_accuracy(query, ref, labels1, labels2, True)
Note that this change doesn't affect the case where query is ref.
Bumped the record-keeper version to fix issue #497
Bumped the record-keeper version to fix issue #497
For some loss functions, labels are now optional if indices_tuple is provided: `python loss = loss_func(embeddings, indices_tuple=pairs) `
For some loss functions, labels are now optional if indices_tuple is provided:
loss = loss_func(embeddings, indices_tuple=pairs)
The losses for which you can do this are:
This issue has come up several times:
#412 #490 #482 #473 #179 #263
Added InstanceLoss. See #410 by @layumi
Fixed a bug in BatchEasyHardMiner where get_max_per_row was not always returning correct values, resulting in invalid pairs and triplets. #476
get_max_per_row was not always returning correct values, resulting in invalid pairs and triplets. #476Fixed ThresholdReducer being incompatible with older versions of PyTorch
torch.cat call.Nothing published for this version
Added a `batch_size` parameter to CustomKNN. This computes k-nn per batch of query embeddings (using BatchedDistance), which requires less memory than
batch_size parameter to CustomKNN. This computes k-nn per batch of query embeddings (using BatchedDistance), which requires less memory than computing the entire distance matrix at once.Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
## New Loss Function: SubCenterArcFace - Documentation - Example notebook - Paper - Issue - Pull Request Thanks @chingisooinar!
Thanks @chingisooinar!
## Bug fixes - #427 - #428
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Implementation of On the Unreasonable Effectiveness of Centroids in Image Retrieval
Implementation of On the Unreasonable Effectiveness of Centroids in Image Retrieval
Implementation of VICReg: Variance-Invariance-Covariance Regularization for Self-Supervised Learning
"mean_reciprocal_rank".return_per_class argument for AccuracyCalculator. This is like avg_of_avgs but returns the accuracy per class, instead of averaging them for you.#369 #372 #374 #394
Thanks to @cwkeam and @mlw214!
Nothing published for this version
Nothing published for this version
You can separate the source of anchors and positive/negatives. In the example below, anchors will be selected from `embeddings and positives/negatives
You can separate the source of anchors and positive/negatives. In the example below, anchors will be selected from embeddings and positives/negatives will be selected from ref_emb.
loss_fn = TripletMarginLoss()
loss = loss_fn(embeddings, labels, ref_emb=ref_emb, ref_labels=ref_labels)
efficient=True: each process uses its own embeddings for anchors, and the gathered embeddings for positives/negatives. Gradients will not be equal to those in non-distributed code, but the benefit is reduced memory and faster training.efficient=False: each process uses gathered embeddings for both anchors and positives/negatives. Gradients will be equal to those in non-distributed code, but at the cost of doing unnecessary operations (i.e. doing computations where both anchors and positives/negatives have no gradient).The default is False. You can set it to True like this:
from pytorch_metric_learning import losses
from pytorch_metric_learning.utils import distributed as pml_dist
loss_func = losses.ContrastiveLoss()
loss_func = pml_dist.DistributedLossWrapper(loss_func, efficient=True)
Documentation: https://kevinmusgrave.github.io/pytorch-metric-learning/distributed/
You can use a different type of faiss index:
import faiss
from pytorch_metric_learning.utils.accuracy_calculator import AccuracyCalculator
from pytorch_metric_learning.utils.inference import FaissKNN
knn_func = FaissKNN(index_init_fn=faiss.IndexFlatIP, gpus=[0,1,2])
ac = AccuracyCalculator(knn_func=knn_func)
You can also use a custom distance function:
from pytorch_metric_learning.distances import SNRDistance
from pytorch_metric_learning.utils.inference import CustomKNN
knn_func = CustomKNN(SNRDistance())
ac = AccuracyCalculator(knn_func=knn_func)
Relevant docs:
https://github.com/KevinMusgrave/pytorch-metric-learning/issues/204 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/251 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/256 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/292 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/330 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/337 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/345 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/347 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/349 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/353 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/359 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/361 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/362 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/363 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/368 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/376 https://github.com/KevinMusgrave/pytorch-metric-learning/issues/380
Thanks to @yutanakamura-tky and @KinglittleQ for pull requests, and @mensaochun for providing helpful code in #380
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Accuracy Calculation bug in GlobalTwoStreamEmbeddingSpaceTester
convert_to_weights (#300)train_indexer now accepts a datasetsave_index, load_index, and add_to_indexerpower argument to LpRegularizer (#299)labels has more than 1 dimension (#307)collect_stats (#311)Thanks to @elias-ramzi, @gkouros, @vltanh, and @Hummer12007
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →