diff --git a/conda_build/pytorch-metric-learning/meta.yaml b/conda_build/pytorch-metric-learning/meta.yaml index ce29274c..8c8eb8e8 100644 --- a/conda_build/pytorch-metric-learning/meta.yaml +++ b/conda_build/pytorch-metric-learning/meta.yaml @@ -1,5 +1,5 @@ {% set name = "pytorch-metric-learning" %} -{% set version = "1.1.0" %} +{% set version = "1.1.1" %} package: name: "{{ name|lower }}" @@ -7,7 +7,7 @@ package: source: url: "https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz" - sha256: d52913eee027746de928bf0e9c031f59a0915cfee4f02cdf81c198a058bd6b21 + sha256: 6e572dc54179c762abc333fc4c6f68fcd909e800f9519ca1463235d14b9f5c44 build: number: 0 diff --git a/src/pytorch_metric_learning/__init__.py b/src/pytorch_metric_learning/__init__.py index 6849410a..a82b376d 100644 --- a/src/pytorch_metric_learning/__init__.py +++ b/src/pytorch_metric_learning/__init__.py @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "1.1.1" diff --git a/src/pytorch_metric_learning/distances/base_distance.py b/src/pytorch_metric_learning/distances/base_distance.py index d4adb241..93df87e9 100644 --- a/src/pytorch_metric_learning/distances/base_distance.py +++ b/src/pytorch_metric_learning/distances/base_distance.py @@ -27,7 +27,7 @@ def forward(self, query_emb, ref_emb=None): ) mat = self.compute_mat(query_emb_normalized, ref_emb_normalized) if self.power != 1: - mat = mat ** self.power + mat = mat**self.power assert mat.size() == torch.Size((query_emb.size(0), ref_emb.size(0))) return mat diff --git a/src/pytorch_metric_learning/losses/fast_ap_loss.py b/src/pytorch_metric_learning/losses/fast_ap_loss.py index f9c10951..c64d12e9 100644 --- a/src/pytorch_metric_learning/losses/fast_ap_loss.py +++ b/src/pytorch_metric_learning/losses/fast_ap_loss.py @@ -34,7 +34,7 @@ def compute_loss(self, embeddings, labels, indices_tuple, ref_emb, ref_labels): return self.zero_losses() dist_mat = self.distance(embeddings) - histogram_max = 2 ** self.distance.power + histogram_max = 2**self.distance.power histogram_delta = histogram_max / self.num_bins mid_points = torch.linspace( 0.0, histogram_max, steps=self.num_edges, device=device, dtype=dtype diff --git a/src/pytorch_metric_learning/losses/large_margin_softmax_loss.py b/src/pytorch_metric_learning/losses/large_margin_softmax_loss.py index e5de6cbd..03539974 100644 --- a/src/pytorch_metric_learning/losses/large_margin_softmax_loss.py +++ b/src/pytorch_metric_learning/losses/large_margin_softmax_loss.py @@ -46,8 +46,8 @@ def get_cos_with_margin(self, cosine): cosine = cosine.unsqueeze(1) for attr in ["n_range", "margin_choose_n", "cos_powers", "alternating"]: setattr(self, attr, c_f.to_device(getattr(self, attr), cosine)) - cos_powered = cosine ** self.cos_powers - sin_powered = (1 - cosine ** 2) ** self.n_range + cos_powered = cosine**self.cos_powers + sin_powered = (1 - cosine**2) ** self.n_range terms = ( self.alternating * self.margin_choose_n * cos_powered * sin_powered ) # Equation 7 in the paper diff --git a/src/pytorch_metric_learning/miners/distance_weighted_miner.py b/src/pytorch_metric_learning/miners/distance_weighted_miner.py index da375909..7f3347a7 100644 --- a/src/pytorch_metric_learning/miners/distance_weighted_miner.py +++ b/src/pytorch_metric_learning/miners/distance_weighted_miner.py @@ -30,7 +30,7 @@ def mine(self, embeddings, labels, ref_emb, ref_labels): # See the first equation from Section 4 of the paper log_weights = (2.0 - d) * torch.log(mat) - ((d - 3) / 2) * torch.log( - 1.0 - 0.25 * (mat ** 2.0) + 1.0 - 0.25 * (mat**2.0) ) inf_or_nan = torch.isinf(log_weights) | torch.isnan(log_weights) diff --git a/src/pytorch_metric_learning/regularizers/center_invariant_regularizer.py b/src/pytorch_metric_learning/regularizers/center_invariant_regularizer.py index dc6f2d1b..091a19ec 100644 --- a/src/pytorch_metric_learning/regularizers/center_invariant_regularizer.py +++ b/src/pytorch_metric_learning/regularizers/center_invariant_regularizer.py @@ -15,7 +15,7 @@ def compute_loss(self, weights): deviations_from_mean = squared_weight_norms - torch.mean(squared_weight_norms) return { "loss": { - "losses": (deviations_from_mean ** 2) / 4, + "losses": (deviations_from_mean**2) / 4, "indices": c_f.torch_arange_from_size(weights), "reduction_type": "element", } diff --git a/src/pytorch_metric_learning/regularizers/lp_regularizer.py b/src/pytorch_metric_learning/regularizers/lp_regularizer.py index d700c59f..574e3185 100644 --- a/src/pytorch_metric_learning/regularizers/lp_regularizer.py +++ b/src/pytorch_metric_learning/regularizers/lp_regularizer.py @@ -14,7 +14,7 @@ def __init__(self, p=2, power=1, **kwargs): def compute_loss(self, embeddings): reg = torch.norm(embeddings, p=self.p, dim=1) if self.power != 1: - reg = reg ** self.power + reg = reg**self.power return { "loss": { "losses": reg, diff --git a/src/pytorch_metric_learning/utils/accuracy_calculator.py b/src/pytorch_metric_learning/utils/accuracy_calculator.py index 81b236bf..d93106ba 100644 --- a/src/pytorch_metric_learning/utils/accuracy_calculator.py +++ b/src/pytorch_metric_learning/utils/accuracy_calculator.py @@ -70,8 +70,7 @@ def r_precision( matches_per_row = torch.sum(same_label * relevance_mask, dim=1) max_possible_matches_per_row = torch.sum(relevance_mask, dim=1) accuracy_per_sample = ( - c_f.to_dtype(matches_per_row, dtype=torch.float64) - / max_possible_matches_per_row + matches_per_row.type(torch.float64) / max_possible_matches_per_row ) return maybe_get_avg_of_avgs( accuracy_per_sample, gt_labels, avg_of_avgs, return_per_class @@ -99,9 +98,7 @@ def mean_average_precision( equality = is_same_label * relevance_mask cumulative_correct = torch.cumsum(equality, dim=1) k_idx = torch.arange(1, num_k + 1, device=device).repeat(num_samples, 1) - precision_at_ks = ( - c_f.to_dtype(cumulative_correct * equality, dtype=torch.float64) / k_idx - ) + precision_at_ks = (cumulative_correct * equality).type(torch.float64) / k_idx summed_precision_per_row = torch.sum(precision_at_ks * relevance_mask, dim=1) if at_r: max_possible_matches_per_row = torch.sum(relevance_mask, dim=1) @@ -172,9 +169,7 @@ def precision_at_k( ): curr_knn_labels = knn_labels[:, :k] same_label = label_comparison_fn(gt_labels, curr_knn_labels) - accuracy_per_sample = ( - c_f.to_dtype(torch.sum(same_label, dim=1), dtype=torch.float64) / k - ) + accuracy_per_sample = torch.sum(same_label, dim=1).type(torch.float64) / k return maybe_get_avg_of_avgs( accuracy_per_sample, gt_labels, avg_of_avgs, return_per_class ) @@ -209,9 +204,7 @@ def get_lone_query_labels( unique_labels, match_counts = label_counts if embeddings_come_from_same_source: label_matches_itself = label_comparison_fn(unique_labels, unique_labels) - lone_condition = ( - match_counts - c_f.to_dtype(label_matches_itself, dtype=torch.long) <= 0 - ) + lone_condition = match_counts - label_matches_itself.type(torch.long) <= 0 else: lone_condition = match_counts == 0 lone_query_labels = unique_labels[lone_condition] diff --git a/src/pytorch_metric_learning/utils/common_functions.py b/src/pytorch_metric_learning/utils/common_functions.py index ecf825c7..66a357e1 100644 --- a/src/pytorch_metric_learning/utils/common_functions.py +++ b/src/pytorch_metric_learning/utils/common_functions.py @@ -86,11 +86,12 @@ def get_hierarchy_label(batch_labels, hierarchy_level): def map_labels(label_map, labels): labels = to_numpy(labels) if labels.ndim == 2: + new_labels = np.zeros(labels.shape, dtype=int) for h in range(labels.shape[1]): - labels[:, h] = label_map(labels[:, h], h) + new_labels[:, h] = label_map(labels[:, h], h) else: - labels = label_map(labels, 0) - return labels + new_labels = label_map(labels, 0) + return new_labels def process_label(labels, hierarchy_level, label_map): @@ -235,7 +236,9 @@ def make_label_to_rank_dict(label_set): Returns: A dictionary mapping each label to its numeric rank in the original set """ - ranked = scipy.stats.rankdata(label_set) - 1 + if len(set(label_set)) != len(label_set): + raise ValueError("label set must not have duplicates") + ranked = scipy.stats.rankdata(label_set).astype(int) - 1 return {k: v for k, v in zip(label_set, ranked)} diff --git a/tests/losses/test_large_margin_softmax_loss.py b/tests/losses/test_large_margin_softmax_loss.py index 5fa9316b..36379f9f 100644 --- a/tests/losses/test_large_margin_softmax_loss.py +++ b/tests/losses/test_large_margin_softmax_loss.py @@ -61,12 +61,12 @@ def test_large_margin_softmax_and_sphereface_loss(self): curr_valA = ( c * (curr_cosineA ** (margin - (2 * z))) - * ((1 - curr_cosineA ** 2) ** z) + * ((1 - curr_cosineA**2) ** z) ) curr_valB = ( c * (curr_cosineB ** (margin - (2 * z))) - * ((1 - curr_cosineB ** 2) ** z) + * ((1 - curr_cosineB**2) ** z) ) if z % 2 == 1: curr_valA *= -1 diff --git a/tests/regularizers/test_center_invariant_regularizer.py b/tests/regularizers/test_center_invariant_regularizer.py index e332b889..053e1224 100644 --- a/tests/regularizers/test_center_invariant_regularizer.py +++ b/tests/regularizers/test_center_invariant_regularizer.py @@ -49,7 +49,7 @@ def test_center_invariant_regularizer(self): torch.norm(loss_func.W[:, i], p=2) ** 2 - average_squared_weight_norms ) - correct_reg_loss += (deviation ** 2) / 4 + correct_reg_loss += (deviation**2) / 4 correct_reg_loss /= num_classes correct_total_loss = correct_class_loss + (correct_reg_loss * reg_weight) diff --git a/tests/utils/test_calculate_accuracies.py b/tests/utils/test_calculate_accuracies.py index a4e4744b..a48685cf 100644 --- a/tests/utils/test_calculate_accuracies.py +++ b/tests/utils/test_calculate_accuracies.py @@ -789,3 +789,14 @@ def test_custom_knn(self): for k, v in acc1.items(): self.assertTrue(np.isclose(v, acc2[k], rtol=1e-3)) + + +class TestWithinAutocast(unittest.TestCase): + def test_within_autocast(self): + if TEST_DEVICE == torch.device("cpu"): + return + AC = accuracy_calculator.AccuracyCalculator() + embeddings = torch.randn(1000, 32) + labels = torch.randint(0, 10, size=(1000,)) + with torch.autocast(device_type="cuda", dtype=torch.float16): + acc = AC.get_accuracy(embeddings, embeddings, labels, labels, True) diff --git a/tests/utils/test_common_functions.py b/tests/utils/test_common_functions.py index 7aa49191..19c0051c 100644 --- a/tests/utils/test_common_functions.py +++ b/tests/utils/test_common_functions.py @@ -1,6 +1,7 @@ import logging import unittest +import numpy as np import torch from sklearn.preprocessing import StandardScaler @@ -10,6 +11,41 @@ from .. import WITH_COLLECT_STATS +def process_label_helper( + cls, + dataset_labels, + batch_labels, + correct_mapped_batch_labels, + correct_unmapped_batch_labels, + hierarchy_level, + input_is_string=False, +): + for set_min_label_to_zero in [False, True]: + if input_is_string and not set_min_label_to_zero: + continue + label_mapper = c_f.LabelMapper(set_min_label_to_zero, dataset_labels) + x = c_f.process_label(batch_labels, hierarchy_level, label_mapper.map) + if set_min_label_to_zero: + cls.assertTrue(np.array_equal(x, correct_mapped_batch_labels)) + else: + cls.assertTrue(np.array_equal(x, correct_unmapped_batch_labels)) + + +def process_label_helper2d( + cls, dataset_labels, batch_labels, mapped_batch_labels, input_is_string=False +): + for h in [0, 1]: + process_label_helper( + cls, + dataset_labels, + batch_labels, + mapped_batch_labels[h], + batch_labels[:, h], + h, + input_is_string=input_is_string, + ) + + class TestCommonFunctions(unittest.TestCase): def test_torch_standard_scaler(self): torch.manual_seed(56987) @@ -58,6 +94,72 @@ def test_logger(self): self.assertTrue(c_f.LOGGER.level == level) self.assertTrue(logging.getLogger().level == logging.WARNING) + def test_process_label(self): + # 1D number labels + dataset_labels = np.array([1, 1, 10, 13, 13, 13, 15]) + batch_labels = np.array([1, 10, 1, 13, 10, 15, 15, 15]) + mapped_batch_labels = np.array([0, 1, 0, 2, 1, 3, 3, 3]) + process_label_helper( + self, dataset_labels, batch_labels, mapped_batch_labels, batch_labels, 0 + ) + + # 1D string labels + # These work only with set_min_label_to_zero=True + # The labels will be sorted alphabetically. So in the following, "bear" becomes 0 + dataset_labels = np.array(["dog", "dog", "cat", "bear", "bear", "bear", "lion"]) + batch_labels = np.array( + ["dog", "cat", "dog", "bear", "cat", "lion", "lion", "lion"] + ) + mapped_batch_labels = np.array([2, 1, 2, 0, 1, 3, 3, 3]) + process_label_helper( + self, + dataset_labels, + batch_labels, + mapped_batch_labels, + batch_labels, + 0, + input_is_string=True, + ) + + # 2D number labels + dataset_labels = np.array( + [[1, 1, 10, 13, 13, 13, 15], [10, 20, 30, 40, 50, 60, 70]] + ).transpose() + batch_labels = np.array( + [[1, 10, 1, 13, 10, 15, 15, 15], [30, 70, 40, 10, 70, 60, 50, 40]] + ).transpose() + mapped_batch_labels0 = np.array([0, 1, 0, 2, 1, 3, 3, 3]) + mapped_batch_labels1 = np.array([2, 6, 3, 0, 6, 5, 4, 3]) + process_label_helper2d( + self, + dataset_labels, + batch_labels, + [mapped_batch_labels0, mapped_batch_labels1], + ) + + # 2D string labels + dataset_labels = np.array( + [ + ["dog", "dog", "cat", "bear", "bear", "bear", "lion"], + ["A.1", "A.2", "A.3", "A.4", "A.5", "A.6", "A.7"], + ] + ).transpose() + batch_labels = np.array( + [ + ["dog", "cat", "dog", "bear", "cat", "lion", "lion", "lion"], + ["A.3", "A.7", "A.4", "A.1", "A.7", "A.6", "A.5", "A.4"], + ] + ).transpose() + mapped_batch_labels0 = np.array([2, 1, 2, 0, 1, 3, 3, 3]) + mapped_batch_labels1 = np.array([2, 6, 3, 0, 6, 5, 4, 3]) + process_label_helper2d( + self, + dataset_labels, + batch_labels, + [mapped_batch_labels0, mapped_batch_labels1], + input_is_string=True, + ) + if __name__ == "__main__": unittest.main()