Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions ivy/functional/frontends/sklearn/metrics/_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,33 @@ def recall_score(y_true, y_pred, *, sample_weight=None):

ret = ret.astype("float64")
return ret


@to_ivy_arrays_and_back
def log_loss(y_true, y_pred, *, eps=1e-15, 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")

# Clip y_pred to avoid log(0) issues
y_pred = ivy.clip(y_pred, eps, 1 - eps)

# Calculate the log loss component-wise
log_loss = -(y_true * ivy.log(y_pred) + (1 - y_true) * ivy.log(1 - y_pred))

# 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)
log_loss = log_loss * sample_weight

# Compute the mean log loss
loss = ivy.mean(log_loss)

loss = loss.astype("float64")
return loss
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,67 @@ def test_sklearn_recall_score(
y_pred=values[1],
sample_weight=sample_weight,
)

@handle_frontend_test(
fn_tree="sklearn.metrics.log_loss",
arrays_and_dtypes=helpers.dtype_and_values(
available_dtypes=helpers.get_dtypes("float"),
num_arrays=2,
min_value=0,
max_value=1, # log_loss expects probability values between 0 and 1
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_log_loss(
arrays_and_dtypes,
on_device,
fn_tree,
frontend,
test_flags,
backend_fw,
sample_weight,
):
dtypes, values = arrays_and_dtypes
# Ensure y_true is binary (0 or 1) and y_pred is within [0, 1]
values[0] = np.round(values[0]).astype(int)
values[1] = np.clip(values[1], 0, 1)

# 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
]

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],
sample_weight=sample_weight,
)