Skip to content
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

FIX _safe_divide should handle zero-division with numpy scalar #27312

Merged
merged 13 commits into from Sep 10, 2023

Conversation

glemaitre
Copy link
Member

@glemaitre glemaitre commented Sep 7, 2023

Fixing the main branch.

This PR should handle the case of zero-division with two numpy scalar.

ping @lorentzenchr @thomasjpfan @OmarManzoor @lesteve

Edit: The CI failure popped up after merging #26278 in the example examples/ensemble/plot_gradient_boosting_regularization.py

@glemaitre glemaitre marked this pull request as draft September 7, 2023 17:02
@github-actions
Copy link

github-actions bot commented Sep 7, 2023

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: ad04d77. Link to the linter CI: here

@glemaitre
Copy link
Member Author

Given the failure in the example, it seems that the provided fix is not enough. We can hand up returning a very large value because the denominator is extremely small

numerator=-0.2857142857142857
denominator=3.18528276243611e-217
numerator=-8.969824879715574e+215

The next iteration, the numerator and denominator will nan.

@glemaitre
Copy link
Member Author

I am not sure what it the right fix here to handle the numerical instability: either clip in the safe division because we should not assign such a large value in raw_predictions or handle the nan values of the next iteration?

@glemaitre
Copy link
Member Author

Apparently, once we get some nan, it seems that this is too late:

Figure_1

vs. previously

image

@glemaitre
Copy link
Member Author

@glemaitre
Copy link
Member Author

So this is not enough. The negative gradient vector has some nan.

In the previous loss it seems that np.nan_to_num was used. Here I tried but the algorithm is still diverging.

@lorentzenchr
Copy link
Member

I‘ll also have a look, but I need some time. Fortunately, we‘ve plenty of time to fix it.

@lorentzenchr
Copy link
Member

lorentzenchr commented Sep 8, 2023

Which version of numpy do you use?
With numpy 1.24.1 on macos, I can't reproduce this failure. My blue curve is like the one at the bottom plot of #27312 (comment).

I was mistaken, now I see the error.

@OmarManzoor
Copy link
Contributor

OmarManzoor commented Sep 8, 2023

@lorentzenchr I have numpy 1.25.1 on mac M1 and I can reproduce the error when I use the main branch.

@lesteve
Copy link
Member

lesteve commented Sep 8, 2023

As an aside (I don't know how much we care to be perfectly honest): code based on np.seterr + catching an exception is not going to work in Pyodide, i.e. an exception will never be raised. Floating point exceptions are not supported in WebAssembly and this is unlikely to change in the near future, see numpy/numpy#21895 (comment) for more details.

@OmarManzoor
Copy link
Contributor

OmarManzoor commented Sep 8, 2023

@lorentzenchr @glemaitre @lesteve

for leaf in np.nonzero(tree.children_left == TREE_LEAF)[0]:
indices = np.nonzero(terminal_regions == leaf)[0] # of terminal regions
y_ = y.take(indices, axis=0)
sw = None if sample_weight is None else sample_weight[indices]
update = compute_update(y_, indices, neg_gradient, raw_prediction, k)
# TODO: Multiply here by learning rate instead of everywhere else.
tree.value[leaf, 0, 0] = update

I think we should have

indices = np.nonzero(masked_terminal_regions == leaf)[0]  # of terminal regions

if we compare with the original code.

@OmarManzoor
Copy link
Contributor

Plot if I use on main

indices = np.nonzero(masked_terminal_regions == leaf)[0]

Figure_1

@glemaitre
Copy link
Member Author

Yep this look much what we had. It makes sense also if we where computing gradient on data that we should not have :)

@glemaitre
Copy link
Member Author

@lesteve @lorentzenchr Instead of the exception catching, we could use the previous trick that return 0.0 if smaller than smaller than a really small values. This would be OK for Pyodide?

@lesteve
Copy link
Member

lesteve commented Sep 8, 2023

we could use the previous trick that return 0.0 if smaller than smaller than a really small values. This would be OK for Pyodide?

I think this would work with Pyodide.

I am not too sure to which extent we want to support Pyodide quirkiness, as I mentioned above. It feels like not getting some warnings in Pyodide would acceptable but having a gradient boosting algorithm that behave weirdly because the loss becomes NaN or inf is maybe not that great.

@glemaitre
Copy link
Member Author

So I made the fix of @OmarManzoor and change the error catching as it was previously.

@glemaitre glemaitre marked this pull request as ready for review September 8, 2023 12:57
@glemaitre
Copy link
Member Author

I also added a comment to remember why we are not using np.errstate and who knows, we could potentially change it in the future if Pyodide handles it.

@lesteve
Copy link
Member

lesteve commented Sep 8, 2023

I also added a comment to remember why we are not using np.errstate and who knows, we could potentially change it in the future if Pyodide handles it.

Don't hold your breath too much though, the link above says there is no plan to support it right now, so that's at least 3 years away.

sklearn/ensemble/tests/test_gradient_boosting.py Outdated Show resolved Hide resolved
sklearn/ensemble/_gb.py Outdated Show resolved Hide resolved
Copy link
Member

@lorentzenchr lorentzenchr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM just some nits.

@glemaitre @OmarManzoor @lesteve Thanks for fixing my bugs.

sklearn/ensemble/_gb.py Outdated Show resolved Hide resolved
sklearn/ensemble/_gb.py Outdated Show resolved Hide resolved
sklearn/ensemble/_gb.py Outdated Show resolved Hide resolved
sklearn/ensemble/_gb.py Outdated Show resolved Hide resolved
glemaitre and others added 3 commits September 8, 2023 23:27
Co-authored-by: Christian Lorentzen <lorentzen.ch@gmail.com>
Co-authored-by: Christian Lorentzen <lorentzen.ch@gmail.com>
Copy link
Contributor

@OmarManzoor OmarManzoor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks @glemaitre , @lesteve and @lorentzenchr

@OmarManzoor OmarManzoor merged commit bbc73cf into scikit-learn:main Sep 10, 2023
27 checks passed
REDVM pushed a commit to REDVM/scikit-learn that referenced this pull request Nov 16, 2023
…t-learn#27312)

Co-authored-by: Christian Lorentzen <lorentzen.ch@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants