From 673aa044330451321c8e8ab5de2187081c725941 Mon Sep 17 00:00:00 2001 From: Tzu-Wei Sung Date: Thu, 20 Jun 2019 14:54:02 +0800 Subject: [PATCH 1/7] migrate npairs_loss --- tensorflow_addons/losses/BUILD | 14 ++++ tensorflow_addons/losses/README.md | 2 + tensorflow_addons/losses/__init__.py | 1 + tensorflow_addons/losses/npairs.py | 96 +++++++++++++++++++++++++ tensorflow_addons/losses/npairs_test.py | 45 ++++++++++++ 5 files changed, 158 insertions(+) create mode 100644 tensorflow_addons/losses/npairs.py create mode 100644 tensorflow_addons/losses/npairs_test.py diff --git a/tensorflow_addons/losses/BUILD b/tensorflow_addons/losses/BUILD index 0c58d96d59..ba62f8eada 100644 --- a/tensorflow_addons/losses/BUILD +++ b/tensorflow_addons/losses/BUILD @@ -10,6 +10,7 @@ py_library( "focal_loss.py", "lifted.py", "metric_learning.py", + "npairs.py", "sparsemax_loss.py", "triplet.py", ], @@ -46,6 +47,19 @@ py_test( ], ) +py_test( + name = "npairs_test", + size = "small", + srcs = [ + "npairs_test.py", + ], + main = "npairs_test.py", + srcs_version = "PY2AND3", + deps = [ + ":losses", + ], +) + py_test( name = "sparsemax_loss_test", size = "small", diff --git a/tensorflow_addons/losses/README.md b/tensorflow_addons/losses/README.md index 03037e2b4d..c6ba6d32e2 100644 --- a/tensorflow_addons/losses/README.md +++ b/tensorflow_addons/losses/README.md @@ -6,6 +6,7 @@ | contrastive | @WindQAQ | windqaq@gmail.com | | focal_loss | | | | lifted | | | +| npairs | @WindQAQ | windqaq@gmail.com | | sparsemax_loss | @AndreasMadsen | amwwebdk+github@gmail.com | | triplet | | | @@ -15,6 +16,7 @@ | contrastive | ContrastiveLoss | http://yann.lecun.com/exdb/publis/pdf/hadsell-chopra-lecun-06.pdf | | focal_loss | SigmoidFocalCrossEntropy | https://arxiv.org/abs/1708.02002 | | lifted | LiftedStructLoss | https://arxiv.org/abs/1511.06452 | +| npairs | NpairsLoss | http://www.nec-labs.com/uploads/images/Department-Images/MediaAnalytics/papers/nips16_npairmetriclearning.pdf | | sparsemax_loss | SparsemaxLoss | https://arxiv.org/abs/1602.02068 | | triplet | TripletSemiHardLoss | https://arxiv.org/abs/1503.03832 | diff --git a/tensorflow_addons/losses/__init__.py b/tensorflow_addons/losses/__init__.py index a552c69d67..ce94d7b91e 100644 --- a/tensorflow_addons/losses/__init__.py +++ b/tensorflow_addons/losses/__init__.py @@ -21,5 +21,6 @@ from tensorflow_addons.losses.contrastive import contrastive_loss, ContrastiveLoss from tensorflow_addons.losses.focal_loss import sigmoid_focal_crossentropy, SigmoidFocalCrossEntropy from tensorflow_addons.losses.lifted import lifted_struct_loss, LiftedStructLoss +from tensorflow_addons.losses.npairs import npairs_loss, NpairsLoss from tensorflow_addons.losses.sparsemax_loss import sparsemax_loss, SparsemaxLoss from tensorflow_addons.losses.triplet import triplet_semihard_loss, TripletSemiHardLoss diff --git a/tensorflow_addons/losses/npairs.py b/tensorflow_addons/losses/npairs.py new file mode 100644 index 0000000000..ff0d580b39 --- /dev/null +++ b/tensorflow_addons/losses/npairs.py @@ -0,0 +1,96 @@ +# Copyright 2019 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Implements npairs loss.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf +from tensorflow_addons.utils import keras_utils + + +@keras_utils.register_keras_custom_object +@tf.function +def npairs_loss(y_true, y_pred): + """Computes the npairs loss between `y_true` and `y_pred`. + + Npairs loss expects paired data where a pair is composed of samples from + the same labels and each pairs in the minibatch have different labels. + The loss takes each row of the pair-wise similarity matrix, `y_pred`, + as logits and the remapped multi-class labels, `y_true`, as labels. + + The similarity matrix `y_pred` between two embedding matrics `a` and `b` + with shape `[batch_size, hidden_size]` can be computed as follows: + + ```python + # y_pred = a * b^T + y_pred = tf.matmul(a, b, transpose_a=False, transpose_b=True) + ``` + + See: http://www.nec-labs.com/uploads/images/Department-Images/MediaAnalytics/papers/nips16_npairmetriclearning.pdf + + Args: + y_true: 1-D integer `Tensor` with shape `[batch_size]` of + multi-class labels. + y_pred: 2-D float `Tensor` with shape `[batch_size, batch_size]` of + similarity matrix between embedding matrices. + + Returns: + npairs_loss: float scalar. + """ + y_pred = tf.convert_to_tensor(y_pred) + y_true = tf.cast(y_true, y_pred.dtype) + + y_true = tf.reshape(y_true, (tf.shape(y_true)[0], 1)) + y_true = tf.cast(tf.equal(y_true, tf.transpose(y_true)), y_pred.dtype) + y_true /= tf.math.reduce_sum(y_true, 1, keepdims=True) + + loss = tf.nn.softmax_cross_entropy_with_logits( + logits=y_pred, + labels=y_true) + + return tf.math.reduce_mean(loss) + + +@keras_utils.register_keras_custom_object +class NpairsLoss(tf.keras.losses.Loss): + """Computes the npairs loss between `y_true` and `y_pred`. + + Npairs loss expects paired data where a pair is composed of samples from + the same labels and each pairs in the minibatch have different labels. + The loss takes each row of the pair-wise similarity matrix, `y_pred`, + as logits and the remapped multi-class labels, `y_true`, as labels. + + The similarity matrix `y_pred` between two embedding matrics `a` and `b` + with shape `[batch_size, hidden_size]` can be computed as follows: + + ```python + # y_pred = a * b^T + y_pred = tf.matmul(a, b, transpose_a=False, transpose_b=True) + ``` + + See: http://www.nec-labs.com/uploads/images/Department-Images/MediaAnalytics/papers/nips16_npairmetriclearning.pdf + + Args: + name: (Optional) name for the loss. + """ + + def __init__(self, + name="npairs_loss"): + super(NpairsLoss, self).__init__( + reduction=tf.keras.losses.Reduction.NONE, name=name) + + def call(self, y_true, y_pred): + return npairs_loss(y_true, y_pred) diff --git a/tensorflow_addons/losses/npairs_test.py b/tensorflow_addons/losses/npairs_test.py new file mode 100644 index 0000000000..975743f6a8 --- /dev/null +++ b/tensorflow_addons/losses/npairs_test.py @@ -0,0 +1,45 @@ +# Copyright 2019 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for npairs loss.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf +from tensorflow_addons.losses import npairs +from tensorflow_addons.utils import test_utils + + +@test_utils.run_all_in_graph_and_eager_modes +class NpairsLossTest(tf.test.TestCase): + def test_config(self): + nl_obj = npairs.NpairsLoss(name="nl") + self.assertEqual(nl_obj.name, "nl") + self.assertEqual(nl_obj.reduction, tf.keras.losses.Reduction.NONE) + + def test_unweighted(self): + nl_obj = npairs.NpairsLoss() + y_true = tf.constant([0, 0, 1, 1, 2], dtype=tf.int64) + y_pred = tf.constant([[0.0, 0.1, 0.2, 0.3, 0.4], + [0.5, 0.6, 0.7, 0.8, 0.9], + [1.0, 1.1, 1.2, 1.3, 1.4], + [1.5, 1.6, 1.7, 1.8, 1.9], + [2.0, 2.1, 2.2, 2.3, 2.4]], dtype=tf.float32) + loss = nl_obj(y_true, y_pred) + self.assertAllClose(loss, 1.619416) + + +if __name__ == "__main__": + tf.test.main() From cad9fa1c74a3b255630c3436c78cc40eaf8451de Mon Sep 17 00:00:00 2001 From: Tzu-Wei Sung Date: Thu, 20 Jun 2019 15:11:09 +0800 Subject: [PATCH 2/7] format code --- tensorflow_addons/losses/npairs.py | 12 +++++------- tensorflow_addons/losses/npairs_test.py | 10 +++++----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tensorflow_addons/losses/npairs.py b/tensorflow_addons/losses/npairs.py index ff0d580b39..9faee0af06 100644 --- a/tensorflow_addons/losses/npairs.py +++ b/tensorflow_addons/losses/npairs.py @@ -25,7 +25,7 @@ @tf.function def npairs_loss(y_true, y_pred): """Computes the npairs loss between `y_true` and `y_pred`. - + Npairs loss expects paired data where a pair is composed of samples from the same labels and each pairs in the minibatch have different labels. The loss takes each row of the pair-wise similarity matrix, `y_pred`, @@ -46,7 +46,7 @@ def npairs_loss(y_true, y_pred): multi-class labels. y_pred: 2-D float `Tensor` with shape `[batch_size, batch_size]` of similarity matrix between embedding matrices. - + Returns: npairs_loss: float scalar. """ @@ -58,8 +58,7 @@ def npairs_loss(y_true, y_pred): y_true /= tf.math.reduce_sum(y_true, 1, keepdims=True) loss = tf.nn.softmax_cross_entropy_with_logits( - logits=y_pred, - labels=y_true) + logits=y_pred, labels=y_true) return tf.math.reduce_mean(loss) @@ -67,7 +66,7 @@ def npairs_loss(y_true, y_pred): @keras_utils.register_keras_custom_object class NpairsLoss(tf.keras.losses.Loss): """Computes the npairs loss between `y_true` and `y_pred`. - + Npairs loss expects paired data where a pair is composed of samples from the same labels and each pairs in the minibatch have different labels. The loss takes each row of the pair-wise similarity matrix, `y_pred`, @@ -87,8 +86,7 @@ class NpairsLoss(tf.keras.losses.Loss): name: (Optional) name for the loss. """ - def __init__(self, - name="npairs_loss"): + def __init__(self, name="npairs_loss"): super(NpairsLoss, self).__init__( reduction=tf.keras.losses.Reduction.NONE, name=name) diff --git a/tensorflow_addons/losses/npairs_test.py b/tensorflow_addons/losses/npairs_test.py index 975743f6a8..a032e94aae 100644 --- a/tensorflow_addons/losses/npairs_test.py +++ b/tensorflow_addons/losses/npairs_test.py @@ -32,11 +32,11 @@ def test_config(self): def test_unweighted(self): nl_obj = npairs.NpairsLoss() y_true = tf.constant([0, 0, 1, 1, 2], dtype=tf.int64) - y_pred = tf.constant([[0.0, 0.1, 0.2, 0.3, 0.4], - [0.5, 0.6, 0.7, 0.8, 0.9], - [1.0, 1.1, 1.2, 1.3, 1.4], - [1.5, 1.6, 1.7, 1.8, 1.9], - [2.0, 2.1, 2.2, 2.3, 2.4]], dtype=tf.float32) + y_pred = tf.constant( + [[0.0, 0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8, 0.9], + [1.0, 1.1, 1.2, 1.3, 1.4], [1.5, 1.6, 1.7, 1.8, 1.9], + [2.0, 2.1, 2.2, 2.3, 2.4]], + dtype=tf.float32) loss = nl_obj(y_true, y_pred) self.assertAllClose(loss, 1.619416) From 19e8f6960f2c3d67d8714aef1d64e20fd149ad22 Mon Sep 17 00:00:00 2001 From: Tzu-Wei Sung Date: Thu, 20 Jun 2019 16:39:14 +0800 Subject: [PATCH 3/7] elaborate how to compute loss --- tensorflow_addons/losses/npairs_test.py | 27 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tensorflow_addons/losses/npairs_test.py b/tensorflow_addons/losses/npairs_test.py index a032e94aae..eb8c29f543 100644 --- a/tensorflow_addons/losses/npairs_test.py +++ b/tensorflow_addons/losses/npairs_test.py @@ -31,14 +31,27 @@ def test_config(self): def test_unweighted(self): nl_obj = npairs.NpairsLoss() - y_true = tf.constant([0, 0, 1, 1, 2], dtype=tf.int64) - y_pred = tf.constant( - [[0.0, 0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8, 0.9], - [1.0, 1.1, 1.2, 1.3, 1.4], [1.5, 1.6, 1.7, 1.8, 1.9], - [2.0, 2.1, 2.2, 2.3, 2.4]], - dtype=tf.float32) + # batch size = 4, hidden size = 2 + y_true = tf.constant([0, 1, 2, 3], dtype=tf.int64) + # features of anchors + f = tf.constant([[1., 1.], [1., -1.], [-1., 1.], [-1., -1.]], + dtype=tf.float32) + # features of positive samples + fp = tf.constant([[1., 1.], [1., -1.], [-1., 1.], [-1., -1.]], + dtype=tf.float32) + # similarity matrix + y_pred = tf.matmul(f, fp, transpose_a=False, transpose_b=True) loss = nl_obj(y_true, y_pred) - self.assertAllClose(loss, 1.619416) + + # Loss = 1/4 * \sum_i log(1 + \sum_{j != i} exp(f_i*fp_j^T-f_i*fi^T)) + # Compute loss for i = 0, 1, 2, 3 without multiplier 1/4 + # i = 0 => log(1 + sum([exp(-2), exp(-2), exp(-4)])) = 0.253846 + # i = 1 => log(1 + sum([exp(-2), exp(-4), exp(-2)])) = 0.253846 + # i = 2 => log(1 + sum([exp(-2), exp(-4), exp(-2)])) = 0.253846 + # i = 3 => log(1 + sum([exp(-4), exp(-2), exp(-2)])) = 0.253846 + # Loss = (0.253856 + 0.253856 + 0.253856 + 0.253856) / 4 = 0.253856 + + self.assertAllClose(loss, 0.253856) if __name__ == "__main__": From a8a4b8bf1e5056c5b1168e54f71ddc8ce6cf7896 Mon Sep 17 00:00:00 2001 From: Tzu-Wei Sung Date: Thu, 20 Jun 2019 17:28:15 +0800 Subject: [PATCH 4/7] remove trailing space --- tensorflow_addons/losses/npairs_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow_addons/losses/npairs_test.py b/tensorflow_addons/losses/npairs_test.py index eb8c29f543..4258f37795 100644 --- a/tensorflow_addons/losses/npairs_test.py +++ b/tensorflow_addons/losses/npairs_test.py @@ -44,7 +44,7 @@ def test_unweighted(self): loss = nl_obj(y_true, y_pred) # Loss = 1/4 * \sum_i log(1 + \sum_{j != i} exp(f_i*fp_j^T-f_i*fi^T)) - # Compute loss for i = 0, 1, 2, 3 without multiplier 1/4 + # Compute loss for i = 0, 1, 2, 3 without multiplier 1/4 # i = 0 => log(1 + sum([exp(-2), exp(-2), exp(-4)])) = 0.253846 # i = 1 => log(1 + sum([exp(-2), exp(-4), exp(-2)])) = 0.253846 # i = 2 => log(1 + sum([exp(-2), exp(-4), exp(-2)])) = 0.253846 From 70f3588add79f9016eefaefc178b2e21ba74203b Mon Sep 17 00:00:00 2001 From: Tzu-Wei Sung Date: Thu, 20 Jun 2019 19:34:00 +0800 Subject: [PATCH 5/7] fix typo --- tensorflow_addons/losses/npairs_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow_addons/losses/npairs_test.py b/tensorflow_addons/losses/npairs_test.py index 4258f37795..0f0ecc12b3 100644 --- a/tensorflow_addons/losses/npairs_test.py +++ b/tensorflow_addons/losses/npairs_test.py @@ -43,7 +43,7 @@ def test_unweighted(self): y_pred = tf.matmul(f, fp, transpose_a=False, transpose_b=True) loss = nl_obj(y_true, y_pred) - # Loss = 1/4 * \sum_i log(1 + \sum_{j != i} exp(f_i*fp_j^T-f_i*fi^T)) + # Loss = 1/4 * \sum_i log(1 + \sum_{j != i} exp(f_i*fp_j^T-f_i*f_i^T)) # Compute loss for i = 0, 1, 2, 3 without multiplier 1/4 # i = 0 => log(1 + sum([exp(-2), exp(-2), exp(-4)])) = 0.253846 # i = 1 => log(1 + sum([exp(-2), exp(-4), exp(-2)])) = 0.253846 From 54534e08d1bcf3603c90bc825c3194d9e6aa68aa Mon Sep 17 00:00:00 2001 From: Tzu-Wei Sung Date: Thu, 20 Jun 2019 19:57:52 +0800 Subject: [PATCH 6/7] fix typo --- tensorflow_addons/losses/npairs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow_addons/losses/npairs.py b/tensorflow_addons/losses/npairs.py index 9faee0af06..5d96d31d77 100644 --- a/tensorflow_addons/losses/npairs.py +++ b/tensorflow_addons/losses/npairs.py @@ -31,7 +31,7 @@ def npairs_loss(y_true, y_pred): The loss takes each row of the pair-wise similarity matrix, `y_pred`, as logits and the remapped multi-class labels, `y_true`, as labels. - The similarity matrix `y_pred` between two embedding matrics `a` and `b` + The similarity matrix `y_pred` between two embedding matrices `a` and `b` with shape `[batch_size, hidden_size]` can be computed as follows: ```python @@ -72,7 +72,7 @@ class NpairsLoss(tf.keras.losses.Loss): The loss takes each row of the pair-wise similarity matrix, `y_pred`, as logits and the remapped multi-class labels, `y_true`, as labels. - The similarity matrix `y_pred` between two embedding matrics `a` and `b` + The similarity matrix `y_pred` between two embedding matrices `a` and `b` with shape `[batch_size, hidden_size]` can be computed as follows: ```python From 2dc99942c37ecd7d17aaf617afb32f8b4da6c943 Mon Sep 17 00:00:00 2001 From: Tzu-Wei Sung Date: Fri, 21 Jun 2019 17:31:51 +0800 Subject: [PATCH 7/7] use expand_dims for conciseness --- tensorflow_addons/losses/npairs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow_addons/losses/npairs.py b/tensorflow_addons/losses/npairs.py index 5d96d31d77..adba81566e 100644 --- a/tensorflow_addons/losses/npairs.py +++ b/tensorflow_addons/losses/npairs.py @@ -53,7 +53,8 @@ def npairs_loss(y_true, y_pred): y_pred = tf.convert_to_tensor(y_pred) y_true = tf.cast(y_true, y_pred.dtype) - y_true = tf.reshape(y_true, (tf.shape(y_true)[0], 1)) + # Expand to [batch_size, 1] + y_true = tf.expand_dims(y_true, -1) y_true = tf.cast(tf.equal(y_true, tf.transpose(y_true)), y_pred.dtype) y_true /= tf.math.reduce_sum(y_true, 1, keepdims=True)