Skip to content

MAINT parameter validation in Perceptron #23521

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

Merged
merged 45 commits into from
Jun 24, 2022

Conversation

Nwanna-Joseph
Copy link
Contributor

@Nwanna-Joseph Nwanna-Joseph commented Jun 2, 2022

Reference Issues/PRs

See PR #23462

What does this implement/fix? Explain your changes.

Add validators for Perceptron. Towards #23462

  • Defines _parameter_constraints in Perceptron.
  • Following the steps in the reference Perceptron to let Perceptron models call self._validate_params.

Any other comments?

@Nwanna-Joseph Nwanna-Joseph changed the title Validate perceptron [WIP] Validate perceptron Jun 2, 2022
Copy link
Member

@jeremiedbb jeremiedbb left a comment

Choose a reason for hiding this comment

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

Thanks for the PR @Nwanna-Joseph. Looks like you have an auto formatting tool that messes up the indentation of the function definitions. Can you revert theses changes ?

It appears that BaseSGD already defines a method named _validate_params. I think we should rename it for now otherwise calling self._validate_params() will invoke this one instead of the one we're expecting (we need to call both).

@jeremiedbb jeremiedbb added No Changelog Needed Validation related to input validation labels Jun 13, 2022
Copy link
Contributor

@Micky774 Micky774 left a comment

Choose a reason for hiding this comment

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

Hey there @Nwanna-Joseph, thanks for the PR! Note that I'm not a core maintainer so you'll still need two approvals from core maintainers before your PR can be merged, but I'm hoping my review can help you along the way and speed up the process :)

Overall, one thing worth noting here is that this could be significantly simplified/streamlined by making more use of inheritance as I mention in one of my comments. In particular, some constraints ([fit_intercept, max_iter, tol, ...]) can go all the way in BaseSGD but you can also include some in the intermediate BaseSGD{Classifier, Regressor} classes (as you've done w/ loss). These would be params like early_stopping that we don't want in SGDOneClassSVM. Finally there are the params specific to PassiveAggressive{Classifier, Regressor} which should be fairly few.

Let me know if you have any questions/concerns.

Comment on lines 468 to 483
_parameter_constraints = {
"loss": [StrOptions({"epsilon_insensitive", "squared_epsilon_insensitive"})],
"C": [Interval(Real, None, None, closed="neither")],
"fit_intercept": [bool],
"max_iter": [Interval(Integral, 1, None, closed="left")],
"tol": [Interval(Real, None, None, closed="neither"), None],
"shuffle": [bool],
"verbose": [Interval(Integral, 0, None, closed="left")],
"random_state": ["random_state"],
"early_stopping": [bool],
"validation_fraction": [Interval(Real, 0, 1, closed="neither")],
"n_iter_no_change": [Interval(Integral, 1, None, closed="left")],
"warm_start": [bool],
"average": [Interval(Integral, 0, None, closed="left"), bool],
"epsilon": [Interval(Real, 0, None, closed="left")],
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should try to map out what parameters reside in what classes. A lot of this can be greatly simplified via inheritance. For example, PassiveAggressiveRegressor inherits from BaseSGDRegressor, but the parameter constraints here are separate. I think BaseSGDRegressor has the same constraints for all the parameters except [loss, C, epsilon].

This could then be simplified as

Suggested change
_parameter_constraints = {
"loss": [StrOptions({"epsilon_insensitive", "squared_epsilon_insensitive"})],
"C": [Interval(Real, None, None, closed="neither")],
"fit_intercept": [bool],
"max_iter": [Interval(Integral, 1, None, closed="left")],
"tol": [Interval(Real, None, None, closed="neither"), None],
"shuffle": [bool],
"verbose": [Interval(Integral, 0, None, closed="left")],
"random_state": ["random_state"],
"early_stopping": [bool],
"validation_fraction": [Interval(Real, 0, 1, closed="neither")],
"n_iter_no_change": [Interval(Integral, 1, None, closed="left")],
"warm_start": [bool],
"average": [Interval(Integral, 0, None, closed="left"), bool],
"epsilon": [Interval(Real, 0, None, closed="left")],
}
_parameter_constraints = {
**BaseSGDRegressor._parameter_constraints,
"loss": [StrOptions({"epsilon_insensitive", "squared_epsilon_insensitive"})],
"C": [Interval(Real , 0, None, closed="neither")],
"epsilon": [Interval(Real, 0, None, closed="left")],
}

@glemaitre glemaitre changed the title [MRG] Validate perceptron MAINT parameter validation in Perceptron Jun 24, 2022
@jeremiedbb
Copy link
Member

@Nwanna-Joseph I directly pushed some changes to take into account recent improvements in the validation mechanism and to better leverage inheritance.

The inheritance between these classes clearly has some flaws, but let's keep that for a future PR.

@glemaitre glemaitre self-requested a review June 24, 2022 12:17
Comment on lines +26 to +28
from ..utils._param_validation import Interval
from ..utils._param_validation import StrOptions
from ..utils._param_validation import Hidden
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
from ..utils._param_validation import Interval
from ..utils._param_validation import StrOptions
from ..utils._param_validation import Hidden
from ..utils._param_validation import Hidden, Interval, StrOptions

Copy link
Member

@jeremiedbb jeremiedbb Jun 24, 2022

Choose a reason for hiding this comment

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

I'd rather rely on isort #23362 😄

Copy link
Member

Choose a reason for hiding this comment

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

It is to be consistent with the other imports in the other files :)

Copy link
Member

Choose a reason for hiding this comment

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

that you changed unilaterally 🦊

Copy link
Member

Choose a reason for hiding this comment

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

Oups :)

Copy link
Member

@glemaitre glemaitre left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM

@@ -188,19 +186,11 @@ def _get_loss_function(self, loss):
raise ValueError("The loss %s is not supported. " % loss) from e

def _get_learning_rate_type(self, learning_rate):
Copy link
Member

Choose a reason for hiding this comment

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

In a future PR, I think that we can just remove those _get functions and directly get the argument.

@@ -1167,6 +1178,20 @@ class SGDClassifier(BaseSGDClassifier):
[1]
"""

_parameter_constraints = {
**BaseSGDClassifier._parameter_constraints,
"penalty": [StrOptions({"l2", "l1", "elasticnet"}), None],
Copy link
Member

Choose a reason for hiding this comment

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

If seems that None is not documented. I know that in LogisticRegression, we request a string "none" instead of the Python None.

Copy link
Member

Choose a reason for hiding this comment

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

I open #23749 to discuss the issue. We can let it as-is here. Maybe only updating the documentation is required.

Copy link
Member

Choose a reason for hiding this comment

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

None was not documented but it is documented in PassiveAggressive so it was just a mistake. I added it to the docstring

Comment on lines +1185 to +1186
"l1_ratio": [Interval(Real, 0, 1, closed="both")],
"power_t": [Interval(Real, None, None, closed="neither")],
Copy link
Member

Choose a reason for hiding this comment

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

Can we just use Real if we don't have any min and max?

Copy link
Member

Choose a reason for hiding this comment

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

The difference is that -inf and inf are not in the interval while they are instances of Real, so this is more accurate

Copy link
Member

@jeremiedbb jeremiedbb left a comment

Choose a reason for hiding this comment

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

LGTM

Co-authored-by: Meekail Zain <34613774+Micky774@users.noreply.github.com>
Copy link
Member

@glemaitre glemaitre left a comment

Choose a reason for hiding this comment

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

LGTM

@jeremiedbb jeremiedbb merged commit 7f0b57e into scikit-learn:main Jun 24, 2022
ogrisel pushed a commit to ogrisel/scikit-learn that referenced this pull request Jul 11, 2022
…cikit-learn#23521)

Co-authored-by: jeremie du boisberranger <jeremiedbb@yahoo.fr>
Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
Co-authored-by: Meekail Zain <34613774+Micky774@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
No Changelog Needed Validation related to input validation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants