From 71023effca040c06b02e07a6eb2201d1f546f86b Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:34:59 +0530 Subject: [PATCH 01/17] Implement recall_score function with Ivy integration This commit introduces the recall_score function, which calculates recall for binary or multilabel classification using Ivy's framework. The function supports micro, macro, and binary averaging, as well as the optional use of sample weights. Additionally, the code includes appropriate comments for better readability and understanding. --- .../sklearn/metrics/_classification.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index e6679505631b5..2e9e32cad77f9 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -1,6 +1,7 @@ import ivy from ivy.functional.frontends.numpy.func_wrapper import to_ivy_arrays_and_back from sklearn.utils.multiclass import type_of_target +from ivy.utils.exceptions import IvyValueError @to_ivy_arrays_and_back @@ -17,3 +18,27 @@ def accuracy_score(y_true, y_pred, *, normalize=True, sample_weight=None): ret = ret / y_true.shape[0] ret = ret.astype("float64") return ret + + +@to_ivy_arrays_and_back +def recall_score(y_true, y_pred, *, average='binary', sample_weight=None): + # Determine the type of the target variable + y_type = type_of_target(y_true) + # Check if the 'average' parameter has a valid value + if average != 'binary' and average != 'micro' and average != 'macro': + raise IvyValueError("Invalid value for 'average'. Supported values are 'binary', 'micro', or 'macro'.") + # Calculate true positive and actual positive counts based on the target type + if y_type.startswith("multilabel"): + true_positive = ivy.sum(ivy.logical_and(y_true, y_pred) * sample_weight, axis=1) + actual_positive = ivy.sum(y_true * sample_weight, axis=1) + else: + true_positive = ivy.sum(ivy.logical_and(y_true, y_pred) * sample_weight) + actual_positive = ivy.sum(y_true * sample_weight) + # Calculate recall for each class or overall + recall = true_positive / ivy.maximum(actual_positive, ivy.to_scalar(ivy.array([1], 'float64'))) + # Perform additional calculations for micro or macro averaging + if average == 'micro': + recall = ivy.sum(true_positive) / ivy.maximum(ivy.sum(actual_positive), ivy.to_scalar(ivy.array([1], 'float64'))) + elif average == 'macro': + recall = ivy.mean(recall) + return recall From f084e427b9627e49e5a7d211f7e12e66d6d0409d Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:38:01 +0530 Subject: [PATCH 02/17] Add test for recall_score function This commit includes a test for the recall_score function, leveraging the Hypothesis library. The test checks the integration of Ivy's recall_score with the specified backend and frontend configurations, covering various input scenarios. The test logic ensures appropriate handling of data types, values, and optional parameters such as 'average' and 'sample_weight'. --- .../test_metrics/test_classification.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index 7977c77df3d03..0912fef7e1226 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -43,3 +43,46 @@ def test_sklearn_accuracy_score( normalize=normalize, sample_weight=None, ) + + +@handle_frontend_test( + fn_tree="your.module.path.recall_score", # Update with the correct module path + arrays_and_dtypes=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("float_and_integer"), + num_arrays=2, + min_value=-2, + max_value=2, + shared_dtype=True, + shape=(helpers.ints(min_value=2, max_value=5)), + ), + average=st.sampled_from(['binary', 'micro', 'macro']), + sample_weight=st.none() | st.floats(min_value=0, max_value=1), +) +def test_recall_score( + arrays_and_dtypes, + on_device, + fn_tree, + frontend, + test_flags, + backend_fw, + average, + sample_weight, +): + dtypes, values = arrays_and_dtypes + # Ensure discrete values if float dtype + for i in range(2): + if "float" in dtypes[i]: + values[i] = np.floor(values[i]) + + helpers.test_frontend_function( + input_dtypes=dtypes, + backend_to_test=backend_fw, + test_flags=test_flags, + fn_tree=fn_tree, + frontend=frontend, + on_device=on_device, + y_true=values[0], + y_pred=values[1], + average=average, + sample_weight=sample_weight, + ) From e328f8a926aa5e9fad147a4163449c3b7da8fa89 Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Mon, 22 Jan 2024 04:17:32 +0000 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sklearn/metrics/_classification.py | 21 ++++++++---- .../test_metrics/test_classification.py | 34 +++++++++---------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index 2e9e32cad77f9..d098a48e9ed06 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -21,12 +21,15 @@ def accuracy_score(y_true, y_pred, *, normalize=True, sample_weight=None): @to_ivy_arrays_and_back -def recall_score(y_true, y_pred, *, average='binary', sample_weight=None): +def recall_score(y_true, y_pred, *, average="binary", sample_weight=None): # Determine the type of the target variable y_type = type_of_target(y_true) # Check if the 'average' parameter has a valid value - if average != 'binary' and average != 'micro' and average != 'macro': - raise IvyValueError("Invalid value for 'average'. Supported values are 'binary', 'micro', or 'macro'.") + if average != "binary" and average != "micro" and average != "macro": + raise IvyValueError( + "Invalid value for 'average'. Supported values are 'binary', 'micro', or" + " 'macro'." + ) # Calculate true positive and actual positive counts based on the target type if y_type.startswith("multilabel"): true_positive = ivy.sum(ivy.logical_and(y_true, y_pred) * sample_weight, axis=1) @@ -35,10 +38,14 @@ def recall_score(y_true, y_pred, *, average='binary', sample_weight=None): true_positive = ivy.sum(ivy.logical_and(y_true, y_pred) * sample_weight) actual_positive = ivy.sum(y_true * sample_weight) # Calculate recall for each class or overall - recall = true_positive / ivy.maximum(actual_positive, ivy.to_scalar(ivy.array([1], 'float64'))) + recall = true_positive / ivy.maximum( + actual_positive, ivy.to_scalar(ivy.array([1], "float64")) + ) # Perform additional calculations for micro or macro averaging - if average == 'micro': - recall = ivy.sum(true_positive) / ivy.maximum(ivy.sum(actual_positive), ivy.to_scalar(ivy.array([1], 'float64'))) - elif average == 'macro': + if average == "micro": + recall = ivy.sum(true_positive) / ivy.maximum( + ivy.sum(actual_positive), ivy.to_scalar(ivy.array([1], "float64")) + ) + elif average == "macro": recall = ivy.mean(recall) return recall diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index 0912fef7e1226..591e62ba7a656 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -6,7 +6,7 @@ @handle_frontend_test( - fn_tree="sklearn.metrics.accuracy_score", + fn_tree="your.module.path.recall_score", # Update with the correct module path arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("float_and_integer"), num_arrays=2, @@ -15,22 +15,25 @@ shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), - normalize=st.booleans(), + average=st.sampled_from(["binary", "micro", "macro"]), + sample_weight=st.none() | st.floats(min_value=0, max_value=1), ) -def test_sklearn_accuracy_score( +def test_recall_score( arrays_and_dtypes, on_device, fn_tree, frontend, test_flags, backend_fw, - normalize, + average, + sample_weight, ): dtypes, values = arrays_and_dtypes - # sklearn accuracy_score does not support continuous values + # Ensure discrete values if float dtype for i in range(2): if "float" in dtypes[i]: values[i] = np.floor(values[i]) + helpers.test_frontend_function( input_dtypes=dtypes, backend_to_test=backend_fw, @@ -40,13 +43,13 @@ def test_sklearn_accuracy_score( on_device=on_device, y_true=values[0], y_pred=values[1], - normalize=normalize, - sample_weight=None, + average=average, + sample_weight=sample_weight, ) @handle_frontend_test( - fn_tree="your.module.path.recall_score", # Update with the correct module path + fn_tree="sklearn.metrics.accuracy_score", arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("float_and_integer"), num_arrays=2, @@ -55,25 +58,22 @@ def test_sklearn_accuracy_score( shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), - average=st.sampled_from(['binary', 'micro', 'macro']), - sample_weight=st.none() | st.floats(min_value=0, max_value=1), + normalize=st.booleans(), ) -def test_recall_score( +def test_sklearn_accuracy_score( arrays_and_dtypes, on_device, fn_tree, frontend, test_flags, backend_fw, - average, - sample_weight, + normalize, ): dtypes, values = arrays_and_dtypes - # Ensure discrete values if float dtype + # sklearn accuracy_score does not support continuous values for i in range(2): if "float" in dtypes[i]: values[i] = np.floor(values[i]) - helpers.test_frontend_function( input_dtypes=dtypes, backend_to_test=backend_fw, @@ -83,6 +83,6 @@ def test_recall_score( on_device=on_device, y_true=values[0], y_pred=values[1], - average=average, - sample_weight=sample_weight, + normalize=normalize, + sample_weight=None, ) From 41a0d73888e714bba70583519a4df435edaf6071 Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:53:55 +0530 Subject: [PATCH 04/17] Updated the module path of fn_tree --- .../test_sklearn/test_metrics/test_classification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index 591e62ba7a656..ddf37d5541d37 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -6,7 +6,7 @@ @handle_frontend_test( - fn_tree="your.module.path.recall_score", # Update with the correct module path + fn_tree="sklearn.metrics.recall_score", arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("float_and_integer"), num_arrays=2, From 28c51af5891c12c3657cf993908143f193b72d95 Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Thu, 8 Feb 2024 10:25:04 +0000 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_sklearn/test_metrics/test_classification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index ddf37d5541d37..02751ac45516f 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -6,7 +6,7 @@ @handle_frontend_test( - fn_tree="sklearn.metrics.recall_score", + fn_tree="sklearn.metrics.recall_score", arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("float_and_integer"), num_arrays=2, From dc2da173a2be685687866aaf5c21e7a1dced5bef Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:19:31 +0530 Subject: [PATCH 06/17] Updated recall_score in _classification.py, all test passed --- .../sklearn/metrics/_classification.py | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index d098a48e9ed06..7cfa8934bd9b1 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -21,31 +21,16 @@ def accuracy_score(y_true, y_pred, *, normalize=True, sample_weight=None): @to_ivy_arrays_and_back -def recall_score(y_true, y_pred, *, average="binary", sample_weight=None): - # Determine the type of the target variable +def recall_score(y_true, y_pred, *, sample_weight=None): + # TODO: implement sample_weight y_type = type_of_target(y_true) - # Check if the 'average' parameter has a valid value - if average != "binary" and average != "micro" and average != "macro": - raise IvyValueError( - "Invalid value for 'average'. Supported values are 'binary', 'micro', or" - " 'macro'." - ) - # Calculate true positive and actual positive counts based on the target type if y_type.startswith("multilabel"): - true_positive = ivy.sum(ivy.logical_and(y_true, y_pred) * sample_weight, axis=1) - actual_positive = ivy.sum(y_true * sample_weight, axis=1) + raise ValueError("Multilabel not supported for recall score") else: - true_positive = ivy.sum(ivy.logical_and(y_true, y_pred) * sample_weight) - actual_positive = ivy.sum(y_true * sample_weight) - # Calculate recall for each class or overall - recall = true_positive / ivy.maximum( - actual_positive, ivy.to_scalar(ivy.array([1], "float64")) - ) - # Perform additional calculations for micro or macro averaging - if average == "micro": - recall = ivy.sum(true_positive) / ivy.maximum( - ivy.sum(actual_positive), ivy.to_scalar(ivy.array([1], "float64")) - ) - elif average == "macro": - recall = ivy.mean(recall) - return recall + true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype("int64") + actual_positives = ivy.equal(y_true, 1).astype("int64") + ret = ivy.sum(true_positives).astype("int64") + actual_pos_count = ivy.sum(actual_positives).astype("int64") + ret = ret / actual_pos_count + ret = ret.astype("float64") + return ret From dec1eeb10eb166bfb0665ee1ef7771779107355e Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Fri, 23 Feb 2024 11:50:43 +0000 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ivy/functional/frontends/sklearn/metrics/_classification.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index 7cfa8934bd9b1..0868abbba8dbb 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -1,7 +1,6 @@ import ivy from ivy.functional.frontends.numpy.func_wrapper import to_ivy_arrays_and_back from sklearn.utils.multiclass import type_of_target -from ivy.utils.exceptions import IvyValueError @to_ivy_arrays_and_back @@ -27,7 +26,9 @@ def recall_score(y_true, y_pred, *, sample_weight=None): if y_type.startswith("multilabel"): raise ValueError("Multilabel not supported for recall score") else: - true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype("int64") + true_positives = ivy.logical_and( + ivy.equal(y_true, 1), ivy.equal(y_pred, 1) + ).astype("int64") actual_positives = ivy.equal(y_true, 1).astype("int64") ret = ivy.sum(true_positives).astype("int64") actual_pos_count = ivy.sum(actual_positives).astype("int64") From f333e746da15fc7fdf70f1749f6755bccc110ab8 Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:20:48 +0530 Subject: [PATCH 08/17] Updated test_sklearn_recall_score test_classification.py, all test passed --- .../test_metrics/test_classification.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index 02751ac45516f..f3094b891c6fc 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -1,5 +1,5 @@ from hypothesis import strategies as st - +import torch import ivy_tests.test_ivy.helpers as helpers from ivy_tests.test_ivy.helpers import handle_frontend_test import numpy as np @@ -10,29 +10,30 @@ arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("float_and_integer"), num_arrays=2, - min_value=-2, - max_value=2, + min_value=0, # Recall score is for binary classification, so min_value is set to 0 + max_value=1, # Max value is set to 1 for the same reason shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), - average=st.sampled_from(["binary", "micro", "macro"]), - sample_weight=st.none() | st.floats(min_value=0, max_value=1), ) -def test_recall_score( - arrays_and_dtypes, - on_device, - fn_tree, - frontend, - test_flags, - backend_fw, - average, - sample_weight, +def test_sklearn_recall_score( + arrays_and_dtypes, + on_device, + fn_tree, + frontend, + test_flags, + backend_fw, ): dtypes, values = arrays_and_dtypes - # Ensure discrete values if float dtype + # Ensure the values are binary by rounding and converting to int for i in range(2): if "float" in dtypes[i]: - values[i] = np.floor(values[i]) + values[i] = np.round(values[i]).astype(int) + + # Detach tensors if they require grad before converting to NumPy arrays + if backend_fw == 'torch': + values = [value.detach().numpy() if isinstance(value, torch.Tensor) and value.requires_grad else value + for value in values] helpers.test_frontend_function( input_dtypes=dtypes, @@ -43,8 +44,7 @@ def test_recall_score( on_device=on_device, y_true=values[0], y_pred=values[1], - average=average, - sample_weight=sample_weight, + sample_weight=None, ) From 155c0aa61e502dd00893059be8ae759cd3927236 Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Fri, 23 Feb 2024 11:52:11 +0000 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_metrics/test_classification.py | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index f3094b891c6fc..6c720f9158839 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -1,40 +1,36 @@ from hypothesis import strategies as st -import torch +import torch import ivy_tests.test_ivy.helpers as helpers from ivy_tests.test_ivy.helpers import handle_frontend_test import numpy as np @handle_frontend_test( - fn_tree="sklearn.metrics.recall_score", + fn_tree="sklearn.metrics.accuracy_score", arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("float_and_integer"), num_arrays=2, - min_value=0, # Recall score is for binary classification, so min_value is set to 0 - max_value=1, # Max value is set to 1 for the same reason + min_value=-2, + max_value=2, shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), + normalize=st.booleans(), ) -def test_sklearn_recall_score( - arrays_and_dtypes, - on_device, - fn_tree, - frontend, - test_flags, - backend_fw, +def test_sklearn_accuracy_score( + arrays_and_dtypes, + on_device, + fn_tree, + frontend, + test_flags, + backend_fw, + normalize, ): dtypes, values = arrays_and_dtypes - # Ensure the values are binary by rounding and converting to int + # sklearn accuracy_score does not support continuous values for i in range(2): if "float" in dtypes[i]: - values[i] = np.round(values[i]).astype(int) - - # Detach tensors if they require grad before converting to NumPy arrays - if backend_fw == 'torch': - values = [value.detach().numpy() if isinstance(value, torch.Tensor) and value.requires_grad else value - for value in values] - + values[i] = np.floor(values[i]) helpers.test_frontend_function( input_dtypes=dtypes, backend_to_test=backend_fw, @@ -44,36 +40,47 @@ def test_sklearn_recall_score( on_device=on_device, y_true=values[0], y_pred=values[1], + normalize=normalize, sample_weight=None, ) @handle_frontend_test( - fn_tree="sklearn.metrics.accuracy_score", + fn_tree="sklearn.metrics.recall_score", arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("float_and_integer"), num_arrays=2, - min_value=-2, - max_value=2, + min_value=0, # Recall score is for binary classification, so min_value is set to 0 + max_value=1, # Max value is set to 1 for the same reason shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), - normalize=st.booleans(), ) -def test_sklearn_accuracy_score( +def test_sklearn_recall_score( arrays_and_dtypes, on_device, fn_tree, frontend, test_flags, backend_fw, - normalize, ): dtypes, values = arrays_and_dtypes - # sklearn accuracy_score does not support continuous values + # Ensure the values are binary by rounding and converting to int for i in range(2): if "float" in dtypes[i]: - values[i] = np.floor(values[i]) + values[i] = np.round(values[i]).astype(int) + + # Detach tensors if they require grad before converting to NumPy arrays + if backend_fw == "torch": + values = [ + ( + value.detach().numpy() + if isinstance(value, torch.Tensor) and value.requires_grad + else value + ) + for value in values + ] + helpers.test_frontend_function( input_dtypes=dtypes, backend_to_test=backend_fw, @@ -83,6 +90,5 @@ def test_sklearn_accuracy_score( on_device=on_device, y_true=values[0], y_pred=values[1], - normalize=normalize, sample_weight=None, ) From 3df5e1e4c2b99500e7488a46385c7983f44404f6 Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:19:23 +0530 Subject: [PATCH 10/17] feat: Add precision_score function aligned with sklearn metrics --- .../sklearn/metrics/_classification.py | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index 0868abbba8dbb..3379f522979e6 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -20,18 +20,29 @@ def accuracy_score(y_true, y_pred, *, normalize=True, sample_weight=None): @to_ivy_arrays_and_back -def recall_score(y_true, y_pred, *, sample_weight=None): - # TODO: implement sample_weight - y_type = type_of_target(y_true) - if y_type.startswith("multilabel"): - raise ValueError("Multilabel not supported for recall score") +def precision_score(y_true, y_pred, *, sample_weight=None): + # Ensure that y_true and y_pred have the same shape + if y_true.shape != y_pred.shape: + raise IvyValueError("y_true and y_pred must have the same shape") + + # Check if sample_weight is provided and normalize it + if sample_weight is not None: + sample_weight = ivy.array(sample_weight) + if sample_weight.shape[0] != y_true.shape[0]: + raise IvyValueError("sample_weight must have the same length as y_true and y_pred") + sample_weight = sample_weight / ivy.sum(sample_weight) else: - true_positives = ivy.logical_and( - ivy.equal(y_true, 1), ivy.equal(y_pred, 1) - ).astype("int64") - actual_positives = ivy.equal(y_true, 1).astype("int64") - ret = ivy.sum(true_positives).astype("int64") - actual_pos_count = ivy.sum(actual_positives).astype("int64") - ret = ret / actual_pos_count - ret = ret.astype("float64") + sample_weight = ivy.ones_like(y_true) + + # Calculate true positives and predicted positives + true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype("int64") + predicted_positives = ivy.equal(y_pred, 1).astype("int64") + + # Apply sample weights + weighted_true_positives = ivy.multiply(true_positives, sample_weight) + weighted_predicted_positives = ivy.multiply(predicted_positives, sample_weight) + + # Compute precision + ret = ivy.sum(weighted_true_positives) / ivy.sum(weighted_predicted_positives) + ret = ret.astype("float64") return ret From 1dc66d7697ab460fa4c5117ec8bd7ac2cfd1c2c2 Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Fri, 23 Feb 2024 14:50:11 +0000 Subject: [PATCH 11/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontends/sklearn/metrics/_classification.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index 3379f522979e6..5529325c01d69 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -29,13 +29,17 @@ def precision_score(y_true, y_pred, *, sample_weight=None): if sample_weight is not None: sample_weight = ivy.array(sample_weight) if sample_weight.shape[0] != y_true.shape[0]: - raise IvyValueError("sample_weight must have the same length as y_true and y_pred") + raise IvyValueError( + "sample_weight must have the same length as y_true and y_pred" + ) sample_weight = sample_weight / ivy.sum(sample_weight) else: sample_weight = ivy.ones_like(y_true) # Calculate true positives and predicted positives - true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype("int64") + true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype( + "int64" + ) predicted_positives = ivy.equal(y_pred, 1).astype("int64") # Apply sample weights From f6b1546dfea42142d09dd82b2b549c7072263a55 Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:21:49 +0530 Subject: [PATCH 12/17] feat: Implement precision_score function test aligned with sklearn metrics --- .../test_metrics/test_classification.py | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index 6c720f9158839..d3adcb7ad2f4d 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -46,40 +46,44 @@ def test_sklearn_accuracy_score( @handle_frontend_test( - fn_tree="sklearn.metrics.recall_score", + fn_tree="sklearn.metrics.precision_score", arrays_and_dtypes=helpers.dtype_and_values( - available_dtypes=helpers.get_dtypes("float_and_integer"), + available_dtypes=helpers.get_dtypes("integer"), num_arrays=2, - min_value=0, # Recall score is for binary classification, so min_value is set to 0 - max_value=1, # Max value is set to 1 for the same reason + min_value=0, + max_value=1, # Precision score is for binary classification shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), + sample_weight=st.lists(st.floats(min_value=0.1, max_value=1), min_size=2, max_size=5), ) -def test_sklearn_recall_score( - arrays_and_dtypes, - on_device, - fn_tree, - frontend, - test_flags, - backend_fw, +def test_sklearn_precision_score( + arrays_and_dtypes, + on_device, + fn_tree, + frontend, + test_flags, + backend_fw, + sample_weight, ): dtypes, values = arrays_and_dtypes # Ensure the values are binary by rounding and converting to int for i in range(2): - if "float" in dtypes[i]: - values[i] = np.round(values[i]).astype(int) + values[i] = np.round(values[i]).astype(int) + + # Adjust sample_weight to have the correct length + sample_weight = np.array(sample_weight).astype(float) + if len(sample_weight) != len(values[0]): + # If sample_weight is shorter, extend it with ones + sample_weight = np.pad(sample_weight, (0, max(0, len(values[0]) - len(sample_weight))), 'constant', + constant_values=1.0) + # If sample_weight is longer, truncate it + sample_weight = sample_weight[:len(values[0])] # Detach tensors if they require grad before converting to NumPy arrays - if backend_fw == "torch": - values = [ - ( - value.detach().numpy() - if isinstance(value, torch.Tensor) and value.requires_grad - else value - ) - for value in values - ] + if backend_fw == 'torch': + values = [value.detach().numpy() if isinstance(value, torch.Tensor) and value.requires_grad else value + for value in values] helpers.test_frontend_function( input_dtypes=dtypes, @@ -90,5 +94,5 @@ def test_sklearn_recall_score( on_device=on_device, y_true=values[0], y_pred=values[1], - sample_weight=None, + sample_weight=sample_weight, ) From 6bb3b06a529320d7e09f8928e765578738acebe9 Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Fri, 23 Feb 2024 14:52:46 +0000 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_metrics/test_classification.py | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index d3adcb7ad2f4d..e06adfc0bb067 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -55,16 +55,18 @@ def test_sklearn_accuracy_score( shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), - sample_weight=st.lists(st.floats(min_value=0.1, max_value=1), min_size=2, max_size=5), + sample_weight=st.lists( + st.floats(min_value=0.1, max_value=1), min_size=2, max_size=5 + ), ) def test_sklearn_precision_score( - arrays_and_dtypes, - on_device, - fn_tree, - frontend, - test_flags, - backend_fw, - sample_weight, + arrays_and_dtypes, + on_device, + fn_tree, + frontend, + test_flags, + backend_fw, + sample_weight, ): dtypes, values = arrays_and_dtypes # Ensure the values are binary by rounding and converting to int @@ -75,15 +77,25 @@ def test_sklearn_precision_score( sample_weight = np.array(sample_weight).astype(float) if len(sample_weight) != len(values[0]): # If sample_weight is shorter, extend it with ones - sample_weight = np.pad(sample_weight, (0, max(0, len(values[0]) - len(sample_weight))), 'constant', - constant_values=1.0) + sample_weight = np.pad( + sample_weight, + (0, max(0, len(values[0]) - len(sample_weight))), + "constant", + constant_values=1.0, + ) # If sample_weight is longer, truncate it - sample_weight = sample_weight[:len(values[0])] + sample_weight = sample_weight[: len(values[0])] # Detach tensors if they require grad before converting to NumPy arrays - if backend_fw == 'torch': - values = [value.detach().numpy() if isinstance(value, torch.Tensor) and value.requires_grad else value - for value in values] + if backend_fw == "torch": + values = [ + ( + value.detach().numpy() + if isinstance(value, torch.Tensor) and value.requires_grad + else value + ) + for value in values + ] helpers.test_frontend_function( input_dtypes=dtypes, From 4b73b1681f104e39edc8c4d46cded41442a0cd21 Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:27:52 +0530 Subject: [PATCH 14/17] Update test_classification.py --- .../test_metrics/test_classification.py | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index e06adfc0bb067..8a60343d12392 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -46,27 +46,25 @@ def test_sklearn_accuracy_score( @handle_frontend_test( - fn_tree="sklearn.metrics.precision_score", + fn_tree="sklearn.metrics.recall_score", arrays_and_dtypes=helpers.dtype_and_values( available_dtypes=helpers.get_dtypes("integer"), num_arrays=2, min_value=0, - max_value=1, # Precision score is for binary classification + max_value=1, # Recall score is for binary classification shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), - sample_weight=st.lists( - st.floats(min_value=0.1, max_value=1), min_size=2, max_size=5 - ), + sample_weight=st.lists(st.floats(min_value=0.1, max_value=1), min_size=2, max_size=5), ) -def test_sklearn_precision_score( - arrays_and_dtypes, - on_device, - fn_tree, - frontend, - test_flags, - backend_fw, - sample_weight, +def test_sklearn_recall_score( + arrays_and_dtypes, + on_device, + fn_tree, + frontend, + test_flags, + backend_fw, + sample_weight, ): dtypes, values = arrays_and_dtypes # Ensure the values are binary by rounding and converting to int @@ -77,25 +75,15 @@ def test_sklearn_precision_score( sample_weight = np.array(sample_weight).astype(float) if len(sample_weight) != len(values[0]): # If sample_weight is shorter, extend it with ones - sample_weight = np.pad( - sample_weight, - (0, max(0, len(values[0]) - len(sample_weight))), - "constant", - constant_values=1.0, - ) + sample_weight = np.pad(sample_weight, (0, max(0, len(values[0]) - len(sample_weight))), 'constant', + constant_values=1.0) # If sample_weight is longer, truncate it - sample_weight = sample_weight[: len(values[0])] + sample_weight = sample_weight[:len(values[0])] # Detach tensors if they require grad before converting to NumPy arrays - if backend_fw == "torch": - values = [ - ( - value.detach().numpy() - if isinstance(value, torch.Tensor) and value.requires_grad - else value - ) - for value in values - ] + if backend_fw == 'torch': + values = [value.detach().numpy() if isinstance(value, torch.Tensor) and value.requires_grad else value + for value in values] helpers.test_frontend_function( input_dtypes=dtypes, From 154519b448d0bb5db2da048369b36e1baa041d9c Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Fri, 23 Feb 2024 14:58:39 +0000 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_metrics/test_classification.py | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py index 8a60343d12392..1d8342188abee 100644 --- a/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py +++ b/ivy_tests/test_ivy/test_frontends/test_sklearn/test_metrics/test_classification.py @@ -55,16 +55,18 @@ def test_sklearn_accuracy_score( shared_dtype=True, shape=(helpers.ints(min_value=2, max_value=5)), ), - sample_weight=st.lists(st.floats(min_value=0.1, max_value=1), min_size=2, max_size=5), + sample_weight=st.lists( + st.floats(min_value=0.1, max_value=1), min_size=2, max_size=5 + ), ) def test_sklearn_recall_score( - arrays_and_dtypes, - on_device, - fn_tree, - frontend, - test_flags, - backend_fw, - sample_weight, + arrays_and_dtypes, + on_device, + fn_tree, + frontend, + test_flags, + backend_fw, + sample_weight, ): dtypes, values = arrays_and_dtypes # Ensure the values are binary by rounding and converting to int @@ -75,15 +77,25 @@ def test_sklearn_recall_score( sample_weight = np.array(sample_weight).astype(float) if len(sample_weight) != len(values[0]): # If sample_weight is shorter, extend it with ones - sample_weight = np.pad(sample_weight, (0, max(0, len(values[0]) - len(sample_weight))), 'constant', - constant_values=1.0) + sample_weight = np.pad( + sample_weight, + (0, max(0, len(values[0]) - len(sample_weight))), + "constant", + constant_values=1.0, + ) # If sample_weight is longer, truncate it - sample_weight = sample_weight[:len(values[0])] + sample_weight = sample_weight[: len(values[0])] # Detach tensors if they require grad before converting to NumPy arrays - if backend_fw == 'torch': - values = [value.detach().numpy() if isinstance(value, torch.Tensor) and value.requires_grad else value - for value in values] + if backend_fw == "torch": + values = [ + ( + value.detach().numpy() + if isinstance(value, torch.Tensor) and value.requires_grad + else value + ) + for value in values + ] helpers.test_frontend_function( input_dtypes=dtypes, From ccadb936c319957d4c607dd6286149dee6ef5f1c Mon Sep 17 00:00:00 2001 From: muzakkirhussain011 <138936198+muzakkirhussain011@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:29:08 +0530 Subject: [PATCH 16/17] Update _classification.py --- .../sklearn/metrics/_classification.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index 5529325c01d69..5ed2e88482c1b 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -20,7 +20,7 @@ def accuracy_score(y_true, y_pred, *, normalize=True, sample_weight=None): @to_ivy_arrays_and_back -def precision_score(y_true, y_pred, *, sample_weight=None): +def recall_score(y_true, y_pred, *, sample_weight=None): # Ensure that y_true and y_pred have the same shape if y_true.shape != y_pred.shape: raise IvyValueError("y_true and y_pred must have the same shape") @@ -29,24 +29,20 @@ def precision_score(y_true, y_pred, *, sample_weight=None): if sample_weight is not None: sample_weight = ivy.array(sample_weight) if sample_weight.shape[0] != y_true.shape[0]: - raise IvyValueError( - "sample_weight must have the same length as y_true and y_pred" - ) + raise IvyValueError("sample_weight must have the same length as y_true and y_pred") sample_weight = sample_weight / ivy.sum(sample_weight) else: sample_weight = ivy.ones_like(y_true) - # Calculate true positives and predicted positives - true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype( - "int64" - ) - predicted_positives = ivy.equal(y_pred, 1).astype("int64") + # Calculate true positives and actual positives + true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype("int64") + actual_positives = ivy.equal(y_true, 1).astype("int64") # Apply sample weights weighted_true_positives = ivy.multiply(true_positives, sample_weight) - weighted_predicted_positives = ivy.multiply(predicted_positives, sample_weight) + weighted_actual_positives = ivy.multiply(actual_positives, sample_weight) - # Compute precision - ret = ivy.sum(weighted_true_positives) / ivy.sum(weighted_predicted_positives) + # Compute recall + ret = ivy.sum(weighted_true_positives) / ivy.sum(weighted_actual_positives) ret = ret.astype("float64") return ret From b9990d3e0f367f700ca39e562743f2d9f98b854d Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Fri, 23 Feb 2024 14:59:46 +0000 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontends/sklearn/metrics/_classification.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ivy/functional/frontends/sklearn/metrics/_classification.py b/ivy/functional/frontends/sklearn/metrics/_classification.py index 5ed2e88482c1b..4cee0d9e9187b 100644 --- a/ivy/functional/frontends/sklearn/metrics/_classification.py +++ b/ivy/functional/frontends/sklearn/metrics/_classification.py @@ -29,13 +29,17 @@ def recall_score(y_true, y_pred, *, sample_weight=None): if sample_weight is not None: sample_weight = ivy.array(sample_weight) if sample_weight.shape[0] != y_true.shape[0]: - raise IvyValueError("sample_weight must have the same length as y_true and y_pred") + raise IvyValueError( + "sample_weight must have the same length as y_true and y_pred" + ) sample_weight = sample_weight / ivy.sum(sample_weight) else: sample_weight = ivy.ones_like(y_true) # Calculate true positives and actual positives - true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype("int64") + true_positives = ivy.logical_and(ivy.equal(y_true, 1), ivy.equal(y_pred, 1)).astype( + "int64" + ) actual_positives = ivy.equal(y_true, 1).astype("int64") # Apply sample weights