-
-
Notifications
You must be signed in to change notification settings - Fork 26k
ENH Raises error in hinge_loss when 'pred_decision' is invalid #19643
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
ENH Raises error in hinge_loss when 'pred_decision' is invalid #19643
Conversation
…number of classes with a multiclass target case. New test in order to check this situation in 'test_classification.py'
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.
Thank you for the PR @PierreAttard !
sklearn/metrics/_classification.py
Outdated
if (pred_decision.ndim == 1) or \ | ||
(labels is not None and pred_decision.ndim > 1 and | ||
np.size(y_true_unique) != pred_decision.shape[1]): | ||
raise ValueError("The shape of pred_decision is not " | ||
"consistent with the number of classes. " | ||
"pred_decision shape must be " | ||
"(n_samples, n_classes) with " | ||
"multiclass target") |
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.
The logically flow in this section is a bit strange. I think the following would be clearer:
labels = np.unique(labels if labels is not None else y_true)
if labels.size > 2:
if pred_decision.ndim == 1 or labels.size != pred_decision.shape[1]:
raise ValueError(...)
le = LabelEncoder()
le.fit(labels)
...
This would cover both cases for input validation. What do you think?
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.
Thank you for the PR @PierreAttard !
It's a pleasure !
Indeed, I agreed. I made a modification that looks like what you have proposed at first.
But in this case, we will can not cover the situation labels is None
:
if (labels is None and pred_decision.ndim > 1 and
(np.size(y_true_unique) != pred_decision.shape[1])):
raise ValueError("Please include all labels in y_true "
"or pass labels as third argument")
If it's ok for you, I can do the modification you proposed and adapt the below unitary test in file test_classification.py
to the new situation :
def test_hinge_loss_multiclass_missing_labels_with_labels_none
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.
I am thinking of combing the two if statements into one because they almost do the same check, but looking at this more closely I think we want the two error messages. How about this:
if pred_decision.ndim > 1 and np.size(y_true_unique) != pred_decision.shape[1]):
if labels is not None:
raise ValueError("The shape of pred_decision is not consistent ...")
else:
raise ValueError("Please include ..")
while leaving everything else the same.
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.
Seems a good solution to me.
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.
Ah finally, we miss the situation of the intial issues #19638 :
pred_decision
with only 1 dimension- Whereas it is a multiclass problem
So, we can do add pred_decision.ndim == 1
:
if pred_decision.ndim == 1 or (pred_decision.ndim > 1 and np.size(y_true_unique) != pred_decision.shape[1]):
if labels is not None:
raise ValueError("The shape of pred_decision is not consistent ...")
else:
raise ValueError("Please include ..")
But we would get the wrong error message.
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.
Yea that makes sense. I think we can also remove one of the conditions:
if pred_decision.ndim <= 1 or y_true_unique.size != pred_decision.shape[1]:
if labels is not None:
raise ValueError("The shape of pred_decision is not consistent ...")
else:
raise ValueError("Please include ..")
This would catch the ndim == 0 case as well. This feels like an exercise in boolean logic :)
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.
This feels like an exercise in boolean logic :)
Indeed !!
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.
I just add labels is not None or pred_decision.ndim <= 1
in order to get the right error message when you put a 1d array for pred_decision
whereas it is a multiclass problem.
sklearn/metrics/_classification.py
Outdated
if labels is not None or pred_decision.ndim <= 1: | ||
raise ValueError("The shape of pred_decision is not " | ||
"consistent with the number of classes. " | ||
"pred_decision shape must be " | ||
"(n_samples, n_classes) with " | ||
"multiclass target") | ||
else: | ||
raise ValueError("Please include all labels in y_true " | ||
"or pass labels as third argument") |
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.
A small nit. With another condition, on labels is None
, the logic becomes harder to follow at a glance. At this point, I think creating invalid_decision_shape
would make this condition more explicit:
invalid_decision_shape = (pred_decision.ndim > 1 and
y_true_unique.size != pred_decision.shape[1]))
if labels is None:
if invalid_decision_shape:
raise ValueError("Please include all the labels...")
elif invalid_decision_shape or pred_decision.ndim <= 1:
raise ValueError("The shape of pred_decision...")
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.
Ok, indeed, and it's more elegant.
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.
But we still have the same issue.
If the user do like #19638, it means inge_loss(y_true=[2,1,0,1,0,1,1], pred_decision=[0,1,2,1,0,2,1])
,
the user will get the message "Please include all the labels..." whereas the true error should be "The shape of pred_decision...". If the user, after the error message "Please include all the labels...", pass the labels
parameter, he will still have an error message.
The logical difficulty comes from the fact that there is a "specific case" for the situation multiclass problem AND pred_decision
1d given. It means that the user did not understand the meaning of this parameter (like I didn't).
assert_raise_message(ValueError, error_message, hinge_loss, | ||
y_true=y_true, pred_decision=pred_decision) |
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.
We are moving toward pytest.raises
for testing error messages.
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.
Ok !
pred_decision = [[0, 1], [0, 1], [0, 1], [0, 1], | ||
[2, 0], [0, 1], [1, 0]] | ||
labels = [0, 1, 2] | ||
assert_raise_message(ValueError, error_message, hinge_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.
Same here regarding pytest.raises
.
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.
Ok !
doc/whats_new/v1.0.rst
Outdated
- |Enhancement| A fix to raise an error in :func:`metrics.hinge_loss` when ``pred_decision`` is 1d | ||
whereas it is a multiclass classification or when ``pred_decision`` | ||
parameter is not consistent with ``labels`` parameter. | ||
:pr:`19643` by :user:`Pierre Attard <PierreAttard>`. |
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.
We also try to keep this < 80 characters
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.
Ok !
…raise_message removed
E127 and E501
So, I proposed to put all consistency checks inside a function, I think it is clearer. |
sklearn/metrics/_classification.py
Outdated
@@ -2285,6 +2285,48 @@ def log_loss(y_true, y_pred, *, eps=1e-15, normalize=True, sample_weight=None, | |||
return _weighted_sum(loss, sample_weight, normalize) | |||
|
|||
|
|||
def _check_valid_multiclass_decision_shape(y_true_unique, pred_decision, |
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.
I would be okay with creating another function if we can use this check in other metrics. I can be missing one, but I do not see a metric that does this validation.
sklearn/metrics/_classification.py
Outdated
if labels is None: | ||
if invalid_decision_shape: | ||
raise ValueError("Please include all labels in y_true " | ||
"or pass labels as third argument") | ||
elif invalid_decision_shape: | ||
raise ValueError("The shape of pred_decision is not " |
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.
With the pred_decision.ndim <= 1
handled before hand. I think the following is simple enough to inlined into hinge_loss
.
if pred_decision.ndim <= 1:
raise ValueError("The shape of pred_decision can not be 1d...")
# pred_decision.ndim > 1 is true
if y_true_unique.size != pred_decision.shape[1]:
if labels is None:
raise ValueError("Please include all...")
else:
raise ValueError("The shape of pred_decision...")
Thank you for your patience on this PR @PierreAttard .
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.
No, it's ok !
@@ -135,7 +134,7 @@ def test_classification_report_dictionary_output(): | |||
target_names=iris.target_names, output_dict=True) | |||
|
|||
# assert the 2 dicts are equal. | |||
assert(report.keys() == expected_report.keys()) | |||
assert (report.keys() == expected_report.keys()) |
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 are some unrelated changes in this PR, most likely caused by an editor. May these changes be reverted?
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.
I check it.
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.
Indeed, I didn't notice those changes in a commit.
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.
Some more suggestions but otherwise, LGTM.
Co-authored-by: Olivier Grisel <olivier.grisel@ensta.org>
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.
LGTM with the following details:
Co-authored-by: Olivier Grisel <olivier.grisel@ensta.org>
…nto hinge_loss_y_true_consistency # Conflicts: # sklearn/metrics/tests/test_classification.py
Thank very much @PierreAttard for making the error message much more user-friendly. |
Final review @thomasjpfan? |
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.
LGTM
…t-learn#19643) Co-authored-by: Olivier Grisel <olivier.grisel@ensta.org>
Reference Issues/PRs
Fixes #19638
What does this implement/fix? Explain your changes.
With multiclass target (more than 2 classes), the
pred_decision
argument must have a shape of type :(n_sample, n_classes)
like written in the doc.If it is not the case, an arror is raised with a clear message.
Any other comments?
A new test has been added in test_classification in order to check that situation.