-
-
Notifications
You must be signed in to change notification settings - Fork 25.8k
FIX compute y_std
properly with multi-target in GPR
#20761
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 compute y_std
properly with multi-target in GPR
#20761
Conversation
It could not retrieve y_std for predict(X, return_std=True) when n_targets were bigger than 1. This happened because line 415 in file "sklearn/gaussian_process/_gpr.py" tried to multiply y_var * self._y_train_std ** 2 using simple multiplication ( a1 * a2). However, it fails when self._y_train_std has more than one feature (when n_targets is more than 1), so we need to implement this multiplication using np.outer product, because it will handle the conventional scalar-array multiplication for each output feature (self._y_train_std contains one normalization rate for each output feature).
Tests are failing, you need to:
|
y_std
properly with multi-target in GPR
Thanks for your review! What is a non-regression task? I'm only editing GPR, not GPC (classifier). |
We are still missing a non-regression test and an entry to the what's new. |
You can read the following regarding regression testing Here, the idea is to write a test function that will check that a code that was previously failing is giving the proper result now. Basically, it corresponds to isolate the code that you put in your first comment Please add an entry to the change log at |
ToDo: Fix linting error.
I am getting a |
You can see the failure there. You can as well click on the different "Details" that will land on this page. The issue is that you need to run black -t py37 sklearn/gaussian_process/tests/test_gpr.py and commit the changes. Something handy instead to do this change manually is to install |
Check if GPR can compute y_std in predict() method when normalize_y==True | ||
in multi-target regression. | ||
""" | ||
X_train = np.random.rand((11, 10)) |
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.
Your test is failing as well: cf. there
Here it would be best to define a random state and then you can just use randn
:
rng = np.random.RandomState(42)
n_samples, n_features, n_targets = 12, 10, 6
X_train = rng.randn(n_samples, n_features)
y_train = rng.randn(n_samples, n_targets)
X_test = rng.randn(n_samples, n_features)
# Generic kernel | ||
kernel = kernels.ConstantKernel(1.0, (1e-1, 1e3)) | ||
kernel *= kernels.RBF(10.0, (1e-3, 1e3)) |
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.
# Generic kernel | |
kernel = kernels.ConstantKernel(1.0, (1e-1, 1e3)) | |
kernel *= kernels.RBF(10.0, (1e-3, 1e3)) | |
# Generic kernel | |
kernel = ( | |
kernels.ConstantKernel(1.0, (1e-1, 1e3)) | |
* kernels.RBF(10.0, (1e-3, 1e3)) | |
) |
in multi-target regression. | ||
""" | ||
X_train = np.random.rand((11, 10)) | ||
# 6 target features -> multi-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.
no need for this comment. Variable names and the code should be as much as possible self-explanatory.
kernel = kernels.ConstantKernel(1.0, (1e-1, 1e3)) | ||
kernel *= kernels.RBF(10.0, (1e-3, 1e3)) | ||
|
||
# normalize_y == True |
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.
remove this comment
alpha=0.1, | ||
normalize_y=True) | ||
model.fit(X_train, y_train) | ||
y_pred, std = model.predict(X_test, return_std=True) |
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.
y_pred, std = model.predict(X_test, return_std=True) | |
y_pred, y_std = model.predict(X_test, return_std=True) |
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.
It is true that the previous code was failing already. But we could make a couple of assertions regarding the shape of the returned values.
assert y_pred.shape == (n_samples, n_targets)
and something similar for y_std
""" | ||
Regression test for issues #17394 and #18065. | ||
Check if GPR can compute y_std in predict() method when normalize_y==True | ||
in multi-target regression. | ||
""" |
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.
""" | |
Regression test for issues #17394 and #18065. | |
Check if GPR can compute y_std in predict() method when normalize_y==True | |
in multi-target regression. | |
""" | |
"""Check that `y_std` is properly computed when `normalize_y=True`. | |
Non-regression test for: | |
https://github.com/scikit-learn/scikit-learn/issues/17394 | |
https://github.com/scikit-learn/scikit-learn/issues/18065 | |
""" |
doc/whats_new/v1.0.rst
Outdated
:mod:`sklearn.gaussian_process` | ||
......................... | ||
|
||
- |Fix| Compute 'y_std' properly with multi-target in |
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.
- |Fix| Compute 'y_std' properly with multi-target in | |
- |Fix| Compute `y_std` properly with multi-target in |
doc/whats_new/v1.0.rst
Outdated
:mod:`sklearn.gaussian_process` | ||
......................... |
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.
:mod:`sklearn.gaussian_process` | |
......................... | |
:mod:`sklearn.gaussian_process` | |
............................... |
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 will let this entry here for the moment but we will have to move it in a section 1.0.1 most probably.
Thanks for your support, @glemaitre. Just one more question: When I run black, other functions in test_gpr.py were formatted. Is it ok or should I change it back to its original state? |
I think it is fine because the same command will be run and therefore lead to the same changes on the CI, normally. |
I relaunched the failing build but it certainly only a timeout. So everything should be fine otherwise. |
Do I need to do anything else? |
everything looks fine |
Tagging this PR for 1.0.1 to include it in the next bug fix release |
Ok. It is asking for changes in whats_new section. Should I change 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.
Thank you @patrickctrf!
LGTM with a few modifications for the change log and to follow conventions.
kernel=kernel, | ||
n_restarts_optimizer=n_restarts_optimizer, | ||
random_state=0, | ||
kernel=kernel, n_restarts_optimizer=n_restarts_optimizer, random_state=0 |
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 is unrelated to this PR motives and also comes with no semantic changes.
Has black
changed it? If it is not the case, could you revert this change, please?
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.
Yes, black changed 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.
Can you try to revert those changes? Normally it should be possible to keep the original lines.
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.
Sure! Done
theta_opt, func_min = ( | ||
initial_theta, | ||
obj_func(initial_theta, eval_gradient=False), |
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 comment as above.
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.
Black changed it too.
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.
Identically, can you try to revert those changes? Normally it should be possible to keep the original lines.
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.
Sure! Done
Co-authored-by: Julien Jerphanion <git@jjerphan.xyz>
Co-authored-by: Julien Jerphanion <git@jjerphan.xyz>
Co-authored-by: Julien Jerphanion <git@jjerphan.xyz>
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.
thanks @patrickctrf. The same issue appears in the if return_cov
branch. y_cov has a shape (n_samples, n_samples) but should have a shape (n_samples, n_samples, n_targets). Would you mind fixing that too ? np.einsum("ij,k->ijk", y_var, self._y_train_std ** 2)
followed by a np.squeeze will do 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.
LGTM. Thanks @patrickctrf !
Thanks for your support, guys! |
Reference Issues/PRs
Fixes #17394. Fixes #18065.
What does this implement/fix? Explain your changes.
GaussianProcessRegressor could not retrieve
y_std
for
predict(X, return_std=True)
when n_targets were bigger than 1.This happened because line 415 in file
"sklearn/gaussian_process/_gpr.py" tried to multiply
y_var * self._y_train_std ** 2
using simple multiplication ( a1 * a2).However, it fails when
self._y_train_std
has more than one feature(when
n_targets
is more than 1), so we need to implement thismultiplication using
np.outer
product, because it will handlethe conventional scalar-array multiplication for EACH output feature
(
self._y_train_std
contains one normalization rate for each outputfeature).
After this fix, each output feature will receive its respective
normalized variance when
return_std == True
.A simple example of how to train gpr and reproduce the error:
Line 415 in file "sklearn/gaussian_process/_gpr.py":