diff --git a/ivy/data_classes/array/experimental/losses.py b/ivy/data_classes/array/experimental/losses.py index 45f01d9760fd0..5bd672c7b0b43 100644 --- a/ivy/data_classes/array/experimental/losses.py +++ b/ivy/data_classes/array/experimental/losses.py @@ -187,3 +187,57 @@ def soft_margin_loss( ivy.array([0.35667497, 0.22314353, 1.60943791]) """ return ivy.soft_margin_loss(self._data, target, reduction=reduction, out=out) + + def margin_ranking_loss( + self: ivy.Array, + pred: Union[ivy.Array, ivy.NativeArray], + target: Union[ivy.Array, ivy.NativeArray], + /, + *, + margin: Optional[float] = 0.0, + reduction: Optional[str] = "mean", + out: Optional[ivy.Array] = None, + ) -> ivy.Array: + """ + ivy.Array instance method variant of ivy.margin_ranking_loss. This method simply + wraps the function, and so the docstring for ivy.margin_ranking_loss also + applies to this method with minimal changes. + + Parameters + ---------- + true + input array or container containing predictions for the first input. + pred + input array or container containing predictions for the second input. + target + input array or container containing the binary labels (1 or -1). + margin + a float margin for loss. Default: ``0.0``. + reduction + the reduction type to apply to the loss. Default: ``'mean'``. + out + optional output array, for writing the result to. It must have a shape + that the inputs broadcast to. + + Returns + ------- + ret + The margin ranking loss. + + Examples + -------- + >>> true = ivy.array([0.5, 0.8, 0.6]) + >>> pred = ivy.array([0.3, 0.4, 0.2]) + >>> target = ivy.array([1.0, -1.0, -1.0]) + >>> loss = true.margin_ranking_loss(pred, target, margin=0.1) + >>> print(loss) + ivy.array(0.33) + """ + return ivy.margin_ranking_loss( + self._data, + pred, + target, + margin=margin, + reduction=reduction, + out=out, + ) \ No newline at end of file diff --git a/ivy/data_classes/container/experimental/losses.py b/ivy/data_classes/container/experimental/losses.py index b0cf6c1c4225e..ad7ac63550e94 100644 --- a/ivy/data_classes/container/experimental/losses.py +++ b/ivy/data_classes/container/experimental/losses.py @@ -612,3 +612,164 @@ def soft_margin_loss( map_sequences=map_sequences, out=out, ) + + @staticmethod + def _static_margin_ranking_loss( + true: Union[ivy.Container, ivy.Array, ivy.NativeArray], + pred: Union[ivy.Container, ivy.Array, ivy.NativeArray], + target: Union[ivy.Container, ivy.Array, ivy.NativeArray], + /, + *, + margin: Optional[float] = 0.0, + reduction: Optional[Union[str, ivy.Container]] = "mean", + key_chains: Optional[Union[List[str], Dict[str, str], ivy.Container]] = None, + to_apply: Union[bool, ivy.Container] = True, + prune_unapplied: Union[bool, ivy.Container] = False, + map_sequences: Union[bool, ivy.Container] = False, + out: Optional[ivy.Container] = None, + ) -> ivy.Container: + """ + ivy.Container static method variant of ivy.margin_ranking_loss. This method + simply wraps the function, and so the docstring for ivy.margin_ranking_loss also + applies to this method with minimal changes. + + Parameters + ---------- + true + input array or container containing predictions for the first input. + pred + input array or container containing predictions for the second input. + target + input array or container containing the binary labels (1 or -1). + margin + margin for the loss. Default is ``0``. + reduction + Specifies the reduction to apply to the output. Default is ``"mean"``. + key_chains + The key-chains to apply or not apply the method to. Default is ``None``. + to_apply + If True, the method will be applied to key_chains, otherwise key_chains + will be skipped. Default is ``True``. + prune_unapplied + Whether to prune key_chains for which the function was not applied. + Default is ``False``. + map_sequences + Whether to also map method to sequences (lists, tuples). + Default is ``False``. + out + optional output container, for writing the result to. It must have a shape + that the inputs broadcast to. + + Returns + ------- + ret + The margin ranking loss. + + Examples + -------- + With :class:`ivy.Container` inputs: + + >>> true = ivy.Container(a=ivy.array([0.5, 0.2, 0.8]), + >>> b=ivy.array([0.7, 0.1, 0.3])) + >>> pred = ivy.Container(a=ivy.array([0.3, 0.9, 0.6]), + >>> b=ivy.array([0.2, 0.6, 0.4])) + >>> target = ivy.Container(a=ivy.array(1), + >>> b=ivy.array(-1)) + >>> loss = ivy.Container.static_margin_ranking_loss(true, pred, target) + >>> print(loss) + { + a: ivy.array(0.2), + b: ivy.array(0.4) + } + """ + return ContainerBase.cont_multi_map_in_function( + "margin_ranking_loss", + true, + pred, + target, + margin=margin, + reduction=reduction, + key_chains=key_chains, + to_apply=to_apply, + prune_unapplied=prune_unapplied, + map_sequences=map_sequences, + out=out, + ) + + def margin_ranking_loss( + self: ivy.Container, + pred: Union[ivy.Container, ivy.Array, ivy.NativeArray], + target: Union[ivy.Container, ivy.Array, ivy.NativeArray], + /, + *, + margin: Optional[Union[float, ivy.Container]] = 0.0, + reduction: Optional[Union[str, ivy.Container]] = "mean", + key_chains: Optional[Union[List[str], Dict[str, str], ivy.Container]] = None, + to_apply: Union[bool, ivy.Container] = True, + prune_unapplied: Union[bool, ivy.Container] = False, + map_sequences: Union[bool, ivy.Container] = False, + out: Optional[ivy.Container] = None, + ) -> ivy.Container: + """ + ivy.Container instance method variant of ivy.margin_ranking_loss. This method + simply wraps the function, and so the docstring for ivy.margin_ranking_loss also + applies to this method with minimal changes. + + Parameters + ---------- + self + input container containing predictions for the first input. + pred + input array or container containing the second input. + target + input array or container containing the binary labels (1 or -1). + margin + margin for the loss. Default is ``0``. + reduction + Specifies the reduction to apply to the output. Default is ``"mean"``. + key_chains + The key-chains to apply or not apply the method to. Default is ``None``. + to_apply + If True, the method will be applied to key_chains, otherwise key_chains + will be skipped. Default is ``True``. + prune_unapplied + Whether to prune key_chains for which the function was not applied. + Default is ``False``. + map_sequences + Whether to also map method to sequences (lists, tuples). + Default is ``False``. + out + optional output container, for writing the result to. It must have a shape + that the inputs broadcast to. + + Returns + ------- + ret + The margin ranking loss. + + Examples + -------- + >>> true = ivy.Container(a=ivy.array([0.5, 0.2, 0.8]), + >>> b=ivy.array([0.7, 0.1, 0.3])) + >>> pred = ivy.Container(a=ivy.array([0.3, 0.9, 0.6]), + >>> b=ivy.array([0.2, 0.6, 0.4])) + >>> target = ivy.Container(a=ivy.array(1), b=ivy.array(-1)) + >>> loss = true.margin_ranking_loss(true, pred, target) + >>> print(loss) + { + a: ivy.array(0.2), + b: ivy.array(0.4) + } + """ + return self._static_margin_ranking_loss( + self, + pred, + target, + margin=margin, + reduction=reduction, + key_chains=key_chains, + to_apply=to_apply, + prune_unapplied=prune_unapplied, + map_sequences=map_sequences, + out=out, + ) \ No newline at end of file diff --git a/ivy/functional/backends/jax/experimental/losses.py b/ivy/functional/backends/jax/experimental/losses.py index a5470e0afbc39..a5471fdc8dbfa 100644 --- a/ivy/functional/backends/jax/experimental/losses.py +++ b/ivy/functional/backends/jax/experimental/losses.py @@ -56,3 +56,23 @@ def soft_margin_loss( return jnp.sum(loss) else: return loss + + +def margin_ranking_loss( + input1: JaxArray, + input2: JaxArray, + target: JaxArray, + /, + *, + margin: Optional[float] = 1.0, + reduction: Optional[str] = "mean" +) -> JaxArray: + pairwise_margin = margin - target * (input1 - input2) + loss = jnp.where(pairwise_margin > 0, pairwise_margin, 0) + + if reduction == "mean": + return jnp.mean(loss) + elif reduction == "sum": + return jnp.sum(loss) + else: + return loss \ No newline at end of file diff --git a/ivy/functional/backends/numpy/experimental/losses.py b/ivy/functional/backends/numpy/experimental/losses.py index 0a33b6bcf1dad..4c354f255f02b 100644 --- a/ivy/functional/backends/numpy/experimental/losses.py +++ b/ivy/functional/backends/numpy/experimental/losses.py @@ -70,3 +70,26 @@ def soft_margin_loss( return np.sum(loss) else: return loss + + + +@with_unsupported_dtypes({"1.25.2 and below": ("bool",)}, backend_version) +@_scalar_output_to_0d_array +def margin_ranking_loss( + input1: np.ndarray, + input2: np.ndarray, + target: np.ndarray, + /, + *, + margin: Optional[float] = 1.0, + reduction: Optional[str] = "mean", +) -> np.ndarray: + pairwise_margin = margin - target * (input1 - input2) + loss = np.where(pairwise_margin > 0, pairwise_margin, 0) + + if reduction == "mean": + return np.mean(loss) + elif reduction == "sum": + return np.sum(loss) + else: + return loss \ No newline at end of file diff --git a/ivy/functional/backends/paddle/experimental/losses.py b/ivy/functional/backends/paddle/experimental/losses.py index b0be8bda8d502..9c11548668211 100644 --- a/ivy/functional/backends/paddle/experimental/losses.py +++ b/ivy/functional/backends/paddle/experimental/losses.py @@ -120,3 +120,35 @@ def soft_margin_loss( reduction: Optional[str] = "mean", ) -> paddle.Tensor: return paddle.nn.functional.soft_margin_loss(input, label, reduction=reduction) + + +@with_unsupported_device_and_dtypes( + { + "2.5.1 and below": { + "cpu": ( + "float16", + "int8", + "int16", + "int32", + "int64", + "uint8", + "complex64", + "complex128", + "bool", + ) + } + }, + backend_version, +) +def margin_ranking_loss( + input1: paddle.Tensor, + input2: paddle.Tensor, + target: paddle.Tensor, + /, + *, + margin: Optional[float] = 0.0, + reduction: Optional[str] = "mean", +) -> paddle.Tensor: + return paddle.nn.functional.margin_ranking_loss( + input1, input2, target, margin=margin, reduction=reduction + ) \ No newline at end of file diff --git a/ivy/functional/backends/tensorflow/experimental/losses.py b/ivy/functional/backends/tensorflow/experimental/losses.py index fdd493e40b8e8..d62d5be523b26 100644 --- a/ivy/functional/backends/tensorflow/experimental/losses.py +++ b/ivy/functional/backends/tensorflow/experimental/losses.py @@ -62,3 +62,24 @@ def soft_margin_loss( return tf.reduce_mean(loss) else: return loss + + +@with_unsupported_dtypes({"2.13.0 and below": "bool"}, backend_version) +def margin_ranking_loss( + input1: tf.Tensor, + input2: tf.Tensor, + target: tf.Tensor, + /, + *, + margin: Optional[float] = 1.0, + reduction: Optional[str] = "mean" +) -> tf.Tensor: + pairwise_margin = margin - target * (input1 - input2) + loss = tf.where(pairwise_margin > 0, pairwise_margin, 0.0) + + if reduction == "sum": + return tf.reduce_sum(loss) + elif reduction == "mean": + return tf.reduce_mean(loss) + else: + return loss \ No newline at end of file diff --git a/ivy/functional/backends/torch/experimental/losses.py b/ivy/functional/backends/torch/experimental/losses.py index 2c24c6afd01e2..f675ee528e7a0 100644 --- a/ivy/functional/backends/torch/experimental/losses.py +++ b/ivy/functional/backends/torch/experimental/losses.py @@ -97,3 +97,22 @@ def soft_margin_loss( target, reduction=reduction, ) + + +@with_unsupported_dtypes({"2.0.1 and below": ("float16", "bool")}, backend_version) +def margin_ranking_loss( + input1: torch.Tensor, + input2: torch.Tensor, + target: torch.Tensor, + /, + *, + margin: Optional[float] = 0.0, + reduction: Optional[str] = "mean", +) -> torch.Tensor: + return torch.nn.functional.margin_ranking_loss( + input1, + input2, + target, + margin=margin, + reduction=reduction, + ) diff --git a/ivy/functional/ivy/experimental/losses.py b/ivy/functional/ivy/experimental/losses.py index 9f15d91a1e786..883fe264f2a9f 100644 --- a/ivy/functional/ivy/experimental/losses.py +++ b/ivy/functional/ivy/experimental/losses.py @@ -409,3 +409,73 @@ def soft_margin_loss( return ivy.mean(loss, out=out) else: return ivy.inplace_update(out, loss) if out is not None else loss + + +@handle_exceptions +@handle_nestable +@handle_array_like_without_promotion +@inputs_to_ivy_arrays +@handle_array_function +def margin_ranking_loss( + true: Union[ivy.Array, ivy.NativeArray], + pred: Union[ivy.Array, ivy.NativeArray], + target: Union[ivy.Array, ivy.NativeArray], + /, + *, + margin: Optional[float] = 0.0, + reduction: Optional[str] = "mean", + out: Optional[ivy.Array] = None, +) -> ivy.Array: + """ + Compute the margin ranking loss between two input tensors. + + Parameters + ---------- + true : array_like + First input tensor. + pred : array_like + Second input tensor. + target : array_like + Tensor of the same shape as true and pred, + representing the target binary labels (1 for positive pairs, + -1 for negative pairs). + margin : float, optional + Margin value. Default is 0.0. + reduction : {'mean', 'sum'}, optional + Type of reduction to apply to the output. Default is 'mean'. + out : array_like, optional + Optional output array, for writing the result to. + It must have a shape that the inputs broadcast to. + + Returns + ------- + ret : array + The margin ranking loss between the two input tensors. + + Examples + -------- + >>> true = ivy.array([1.0, 2.0, 3.0]) + >>> pred = ivy.array([4.0, 5.0, 6.0]) + >>> target = ivy.array([1.0, -1.0, 1.0]) + >>> ivy.margin_ranking_loss(true, pred, target) + ivy.array(2.) + >>> true = ivy.array([1.0, 2.0, 3.0]) + >>> pred = ivy.array([4.0, 5.0, 6.0]) + >>> target = ivy.array([1.0, -1.0, -1.0]) + >>> ivy.margin_ranking_loss(true, pred, target, margin=1.0) + ivy.array(1.33) + >>> true = ivy.array([1.0, 2.0, 3.0]) + >>> pred = ivy.array([4.0, 5.0, 6.0]) + >>> target = ivy.array([1.0, 1.0, 1.0]) + >>> ivy.margin_ranking_loss(true, pred, target, reduction='sum') + ivy.array(9.) + """ + pairwise_margin = margin - target * (true - pred) + loss = ivy.where(pairwise_margin > 0, pairwise_margin, 0) + + if reduction == "sum": + return ivy.sum(loss, out=out) + elif reduction == "mean": + return ivy.mean(loss, out=out) + else: + return ivy.inplace_update(out, loss) if out is not None else loss diff --git a/ivy_tests/test_ivy/test_functional/test_experimental/test_nn/test_losses.py b/ivy_tests/test_ivy/test_functional/test_experimental/test_nn/test_losses.py index c41b3f0d2d63c..ee73b4b3b8153 100644 --- a/ivy_tests/test_ivy/test_functional/test_experimental/test_nn/test_losses.py +++ b/ivy_tests/test_ivy/test_functional/test_experimental/test_nn/test_losses.py @@ -249,3 +249,64 @@ def test_soft_margin_loss( target=target[0], reduction=reduction, ) + + +@handle_test( + fn_tree="functional.ivy.experimental.margin_ranking_loss", + dtype_true=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1, + max_value=1, + allow_inf=False, + min_num_dims=1, + max_num_dims=3, + min_dim_size=3, + ), + dtype_pred=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1, + max_value=1, + allow_inf=False, + min_num_dims=1, + max_num_dims=3, + min_dim_size=3, + ), + dtype_target=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float"), + min_value=-1, + max_value=1, + allow_inf=False, + min_num_dims=1, + max_num_dims=3, + min_dim_size=3, + ), + margin=helpers.floats(min_value=0.0, max_value=10.0), + reduction=st.sampled_from(["none", "sum", "mean"]), +) +def test_margin_ranking_loss( + dtype_true, + dtype_pred, + dtype_target, + margin, + reduction, + test_flags, + backend_fw, + fn_name, + on_device, +): + true_dtype, true = dtype_true + pred_dtype, pred = dtype_pred + target_dtype, target = dtype_target + + helpers.test_function( + input_dtypes=true_dtype + pred_dtype + target_dtype, + test_flags=test_flags, + backend_to_test=backend_fw, + fn_name=fn_name, + on_device=on_device, + true=true[0], + pred=pred[0], + target=target[0], + margin=margin, + reduction=reduction, + ) \ No newline at end of file