-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add New API nn.HingeEmbeddingLoss #37540
Changes from 29 commits
5e643ca
6edb279
7e9207e
89da508
769a4b8
5ea300d
98d687e
c6bd8d4
341ba5b
a20b2de
b83a1e2
e077423
2988c76
04cf985
a3bfd3e
d7d892c
562aa9c
c658354
b47d411
de677a6
ebbe89e
3231d91
aa9f9c6
22a25dc
c0b31b3
f7c49b7
aca041e
3e94ec4
319d085
b613766
0e323aa
146f809
9eefb38
a6fcc5c
f92f098
0391ff7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
# Copyright (c) 2021 PaddlePaddle 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. | ||
|
||
from __future__ import print_function | ||
|
||
import paddle | ||
import numpy as np | ||
import unittest | ||
|
||
np.random.seed(42) | ||
|
||
|
||
class TestFunctionalHingeEmbeddingLoss(unittest.TestCase): | ||
def setUp(self): | ||
self.delta = 1.0 | ||
self.shape = (10, 10, 5) | ||
self.input_np = np.random.random(size=self.shape).astype(np.float32) | ||
# get label elem in {1., -1.} | ||
self.label_np = 2 * np.random.randint(0, 2, size=self.shape) - 1. | ||
# get wrong label elem not in {1., -1.} | ||
self.wrong_label = paddle.randint(-3, 3, shape=self.shape) | ||
|
||
def run_dynamic_check(self): | ||
input = paddle.to_tensor(self.input_np) | ||
label = paddle.to_tensor(self.label_np, dtype=paddle.float32) | ||
dy_result = paddle.nn.functional.hinge_embedding_loss(input, label) | ||
expected = np.mean( | ||
np.where(label.numpy() == -1., | ||
np.maximum(0., self.delta - input.numpy()), input.numpy())) | ||
self.assertTrue(np.allclose(dy_result.numpy(), expected)) | ||
self.assertTrue(dy_result.shape, [1]) | ||
|
||
dy_result = paddle.nn.functional.hinge_embedding_loss( | ||
input, label, reduction='sum') | ||
expected = np.sum( | ||
np.where(label.numpy() == -1., | ||
np.maximum(0., self.delta - input.numpy()), input.numpy())) | ||
self.assertTrue(np.allclose(dy_result.numpy(), expected)) | ||
self.assertTrue(dy_result.shape, [1]) | ||
|
||
dy_result = paddle.nn.functional.hinge_embedding_loss( | ||
input, label, reduction='none') | ||
expected = np.where(label.numpy() == -1., | ||
np.maximum(0., self.delta - input.numpy()), | ||
input.numpy()) | ||
self.assertTrue(np.allclose(dy_result.numpy(), expected)) | ||
self.assertTrue(dy_result.shape, self.shape) | ||
|
||
def test_cpu(self): | ||
paddle.disable_static(place=paddle.CPUPlace()) | ||
self.run_dynamic_check() | ||
|
||
def test_gpu(self): | ||
if not paddle.is_compiled_with_cuda(): | ||
return | ||
|
||
paddle.disable_static(place=paddle.CUDAPlace(0)) | ||
self.run_dynamic_check() | ||
|
||
# test case the raise message | ||
def test_reduce_errors(self): | ||
def test_value_error(): | ||
loss = paddle.nn.functional.hinge_embedding_loss( | ||
self.input_np, self.label_np, reduction='reduce_mean') | ||
|
||
self.assertRaises(ValueError, test_value_error) | ||
|
||
|
||
class TestClassHingeEmbeddingLoss(unittest.TestCase): | ||
def setUp(self): | ||
self.delta = 1.0 | ||
self.shape = (10, 10, 5) | ||
self.input_np = np.random.random(size=self.shape).astype(np.float32) | ||
# get label elem in {1., -1.} | ||
self.label_np = 2 * np.random.randint(0, 2, size=self.shape) - 1. | ||
# get wrong label elem not in {1., -1.} | ||
self.wrong_label = paddle.randint(-3, 3, shape=self.shape) | ||
|
||
def run_dynamic_check(self): | ||
input = paddle.to_tensor(self.input_np) | ||
label = paddle.to_tensor(self.label_np, dtype=paddle.float32) | ||
hinge_embedding_loss = paddle.nn.loss.HingeEmbeddingLoss() | ||
dy_result = hinge_embedding_loss(input, label) | ||
expected = np.mean( | ||
np.where(label.numpy() == -1., | ||
np.maximum(0., self.delta - input.numpy()), input.numpy())) | ||
self.assertTrue(np.allclose(dy_result.numpy(), expected)) | ||
self.assertTrue(dy_result.shape, [1]) | ||
|
||
hinge_embedding_loss = paddle.nn.loss.HingeEmbeddingLoss( | ||
reduction='sum') | ||
dy_result = hinge_embedding_loss(input, label) | ||
expected = np.sum( | ||
np.where(label.numpy() == -1., | ||
np.maximum(0., self.delta - input.numpy()), input.numpy())) | ||
self.assertTrue(np.allclose(dy_result.numpy(), expected)) | ||
self.assertTrue(dy_result.shape, [1]) | ||
|
||
hinge_embedding_loss = paddle.nn.loss.HingeEmbeddingLoss( | ||
reduction='none') | ||
dy_result = hinge_embedding_loss(input, label) | ||
expected = np.where(label.numpy() == -1., | ||
np.maximum(0., self.delta - input.numpy()), | ||
input.numpy()) | ||
self.assertTrue(np.allclose(dy_result.numpy(), expected)) | ||
self.assertTrue(dy_result.shape, self.shape) | ||
|
||
def test_cpu(self): | ||
paddle.disable_static(place=paddle.CPUPlace()) | ||
self.run_dynamic_check() | ||
|
||
def test_gpu(self): | ||
if not paddle.is_compiled_with_cuda(): | ||
return | ||
|
||
paddle.disable_static(place=paddle.CUDAPlace(0)) | ||
self.run_dynamic_check() | ||
|
||
# test case the raise message | ||
def test_reduce_errors(self): | ||
def test_value_error(): | ||
hinge_embedding_loss = paddle.nn.loss.HingeEmbeddingLoss( | ||
reduction='reduce_mean') | ||
loss = hinge_embedding_loss(self.input_np, self.label_np) | ||
|
||
self.assertRaises(ValueError, test_value_error) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1720,8 +1720,8 @@ def cross_entropy(input, | |
raise ValueError( | ||
"input's class_dimension({}) must equal to " | ||
"weight's class_dimension({}) " | ||
"when weight is provided"\ | ||
.format(input.shape[axis], weight.shape[-1])) | ||
"when weight is provided" \ | ||
.format(input.shape[axis], weight.shape[-1])) | ||
|
||
ignore_weight_mask = paddle.cast((label != ignore_index), | ||
out.dtype) | ||
|
@@ -1732,7 +1732,7 @@ def cross_entropy(input, | |
axis) | ||
if axis != -1 and axis != valid_label.ndim - 1: | ||
temp_perm = list(range(axis % valid_label.ndim)) \ | ||
+ list(range((axis % valid_label.ndim + 1) , valid_label.ndim)) \ | ||
+ list(range((axis % valid_label.ndim + 1), valid_label.ndim)) \ | ||
+ [axis % valid_label.ndim] | ||
weight_gather = _C_ops.gather_nd( | ||
weight, valid_label.transpose(temp_perm)) | ||
|
@@ -1834,8 +1834,8 @@ def cross_entropy(input, | |
else: | ||
if input.shape[axis] != weight.shape[-1]: | ||
raise ValueError("input's class_dimension({}) must equal to " | ||
"weight's class_dimension({}) " | ||
"when weight is provided"\ | ||
"weight's class_dimension({}) " | ||
"when weight is provided" \ | ||
.format(input.shape[axis], weight.shape[-1])) | ||
|
||
valid_label = paddle.where(label == ignore_index, | ||
|
@@ -2051,3 +2051,99 @@ def sigmoid_focal_loss(logit, | |
loss = paddle.sum(loss, name=name) | ||
|
||
return loss | ||
|
||
|
||
def hinge_embedding_loss(input, label, delta=1.0, reduction='mean', name=None): | ||
r""" | ||
This operator calculates hinge_embedding_loss. Measures the loss given an input tensor :math:`x` and a labels tensor :math:`y`(containing 1 or -1). | ||
This is usually used for measuring whether two inputs are similar or dissimilar, e.g. using the L1 pairwise distance as :math:`x`, | ||
and is typically used for learning nonlinear embeddings or semi-supervised learning. | ||
|
||
The loss function for :math:`n`-th sample in the mini-batch is | ||
|
||
.. math:: | ||
l_n = \begin{cases} | ||
x_n, & \text{if}\; y_n = 1,\\ | ||
\max \{0, \Delta - x_n\}, & \text{if}\; y_n = -1, | ||
\end{cases} | ||
|
||
and the total loss functions is | ||
|
||
.. math:: | ||
\ell(x, y) = \begin{cases} | ||
\operatorname{mean}(L), & \text{if reduction} = \text{'mean';}\\ | ||
\operatorname{sum}(L), & \text{if reduction} = \text{'sum'.} | ||
\end{cases} | ||
|
||
where :math:`L = \{l_1,\dots,l_N\}^\top`. | ||
|
||
Parameters: | ||
input (Tensor): Input tensor, the data type is float32 or float64. | ||
the shape is [N, \*], N is batch size and `\*` means any number of additional dimensions, available dtype is float32, float64. | ||
label (Tensor): Label tensor containing 1 or -1, the data type is float32 or float64. | ||
The shape of label is the same as the shape of input. | ||
delta (float, optional): Specifies the hyperparameter delta to be used. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 确认下,这个参数名称用delta而不用margin的原因是什么? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 因为公式里用 Δ 所以我就取名为 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 辛苦改成margin吧,可以跟margin_ranking_loss保持一致 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, thanks. |
||
The value determines how large the input need to be to calculate in | ||
hinge_embedding_loss. When label is -1, Input smaller than delta are minimized with hinge_embedding_loss. | ||
Default = 1.0 | ||
reduction (str, optional): Indicate how to average the loss by batch_size. | ||
the candicates are ``'none'`` | ``'mean'`` | ``'sum'``. | ||
If :attr:`reduction` is ``'none'``, the unreduced loss is returned; | ||
If :attr:`reduction` is ``'mean'``, the reduced mean loss is returned; | ||
If :attr:`reduction` is ``'sum'``, the summed loss is returned. | ||
Default: ``'mean'`` | ||
name (str, optional): Name for the operation (optional, default is None). | ||
For more information, please refer to :ref:`api_guide_Name`. | ||
|
||
Shape: | ||
|
||
input: N-D Tensor, the shape is [N, \*], N is batch size and `\*` means any number of additional dimensions, available dtype is float32, float64. The sum operationoperates over all the elements. | ||
|
||
label: N-D Tensor, same shape as the input. tensor elements should containing 1 or -1, the data type is float32 or float64. | ||
|
||
output: scalar. If :attr:`reduction` is ``'none'``, then same shape as the input. | ||
|
||
Returns: | ||
Tensor. The tensor variable storing the hinge_embedding_loss of input and label. | ||
|
||
Examples: | ||
.. code-block:: python | ||
|
||
import paddle | ||
import paddle.nn.functional as F | ||
|
||
input = paddle.to_tensor([[1, -2, 3], [0, -1, 2], [1, 0, 1]], dtype=paddle.float32) | ||
# label elements in {1., -1.} | ||
label = paddle.to_tensor([[-1, 1, -1], [1, 1, 1], [1, -1, 1]], dtype=paddle.float32) | ||
|
||
loss = F.hinge_embedding_loss(input, label, delta=1.0, reduction='none') | ||
print(loss) | ||
# Tensor([[0., -2., 0.], | ||
# [0., -1., 2.], | ||
# [1., 1., 1.]]) | ||
|
||
loss = F.hinge_embedding_loss(input, label, delta=1.0, reduction='mean') | ||
print(loss) | ||
# Tensor([0.22222222]) | ||
""" | ||
S-HuaBomb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if reduction not in ['sum', 'mean', 'none']: | ||
raise ValueError( | ||
"'reduction' in 'hinge_embedding_loss' should be 'sum', 'mean' or 'none', " | ||
"but received {}.".format(reduction)) | ||
|
||
if not paddle.fluid.framework.in_dygraph_mode(): | ||
check_variable_and_dtype(input, 'input', ['float32', 'float64'], | ||
'hinge_embedding_loss') | ||
check_variable_and_dtype(label, 'label', ['float32', 'float64'], | ||
'hinge_embedding_loss') | ||
|
||
loss = paddle.where(label == 1., input, paddle.to_tensor(0.)) + \ | ||
paddle.where(label == -1., delta - input, paddle.to_tensor(0.)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. max(0, delta-input)还是需要的吧 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 是的,粗心了,已修改。 Done, thanks. |
||
|
||
if reduction == 'mean': | ||
return paddle.mean(loss, name=name) | ||
elif reduction == 'sum': | ||
return paddle.sum(loss, name=name) | ||
elif reduction == 'none': | ||
return loss |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
现在可以支持静态图了,就也加上静态图的测试吧
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
收到,
Done thanks.