-
-
Notifications
You must be signed in to change notification settings - Fork 25.8k
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
MAINT parameter validation in Perceptron #23521
Conversation
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 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).
…nto validate_perceptron
…nto validate_perceptron
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.
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.
_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")], | ||
} |
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 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
_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")], | |
} |
@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. |
from ..utils._param_validation import Interval | ||
from ..utils._param_validation import StrOptions | ||
from ..utils._param_validation import Hidden |
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.
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 |
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'd rather rely on isort #23362 😄
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 to be consistent with the other imports in the other files :)
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.
that you changed unilaterally 🦊
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.
Oups :)
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.
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): |
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.
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], |
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.
If seems that None
is not documented. I know that in LogisticRegression
, we request a string "none"
instead of the Python 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 open #23749 to discuss the issue. We can let it as-is here. Maybe only updating the documentation is required.
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.
None was not documented but it is documented in PassiveAggressive so it was just a mistake. I added it to the docstring
"l1_ratio": [Interval(Real, 0, 1, closed="both")], | ||
"power_t": [Interval(Real, None, None, closed="neither")], |
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 we just use Real
if we don't have any min and max?
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 difference is that -inf and inf are not in the interval while they are instances of Real, so this is more accurate
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
Co-authored-by: Meekail Zain <34613774+Micky774@users.noreply.github.com>
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
…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>
Reference Issues/PRs
See PR #23462
What does this implement/fix? Explain your changes.
Add validators for Perceptron. Towards #23462
Any other comments?