6
6
# Jiyuan Qian
7
7
# License: BSD 3 clause
8
8
9
+ from numbers import Integral , Real
9
10
import numpy as np
10
11
11
12
from abc import ABCMeta , abstractmethod
35
36
from ..utils .multiclass import type_of_target
36
37
from ..utils .optimize import _check_optimize_result
37
38
from ..utils .metaestimators import available_if
39
+ from ..utils ._param_validation import StrOptions , Interval
38
40
39
41
40
42
_STOCHASTIC_SOLVERS = ["sgd" , "adam" ]
@@ -54,6 +56,40 @@ class BaseMultilayerPerceptron(BaseEstimator, metaclass=ABCMeta):
54
56
.. versionadded:: 0.18
55
57
"""
56
58
59
+ _parameter_constraints = {
60
+ "hidden_layer_sizes" : [
61
+ "array-like" ,
62
+ Interval (Integral , 1 , None , closed = "left" ),
63
+ ],
64
+ "activation" : [StrOptions ({"identity" , "logistic" , "tanh" , "relu" })],
65
+ "solver" : [StrOptions ({"lbfgs" , "sgd" , "adam" })],
66
+ "alpha" : [Interval (Real , 0 , None , closed = "left" )],
67
+ "batch_size" : [
68
+ StrOptions ({"auto" }),
69
+ Interval (Integral , 1 , None , closed = "left" ),
70
+ ],
71
+ "learning_rate" : [StrOptions ({"constant" , "invscaling" , "adaptive" })],
72
+ "learning_rate_init" : [Interval (Real , 0 , None , closed = "neither" )],
73
+ "power_t" : [Interval (Real , 0 , None , closed = "left" )],
74
+ "max_iter" : [Interval (Integral , 1 , None , closed = "left" )],
75
+ "shuffle" : ["boolean" ],
76
+ "random_state" : ["random_state" ],
77
+ "tol" : [Interval (Real , 0 , None , closed = "left" )],
78
+ "verbose" : ["verbose" ],
79
+ "warm_start" : ["boolean" ],
80
+ "momentum" : [Interval (Real , 0 , 1 , closed = "both" )],
81
+ "nesterovs_momentum" : ["boolean" ],
82
+ "early_stopping" : ["boolean" ],
83
+ "validation_fraction" : [Interval (Real , 0 , 1 , closed = "left" )],
84
+ "beta_1" : [Interval (Real , 0 , 1 , closed = "left" )],
85
+ "beta_2" : [Interval (Real , 0 , 1 , closed = "left" )],
86
+ "epsilon" : [Interval (Real , 0 , None , closed = "neither" )],
87
+ # TODO update when ScalarOptions available
88
+ # [Interval(Integral, 1, None, closed="left"), ScalarOptions({np.inf})]
89
+ "n_iter_no_change" : [Interval (Real , 0 , None , closed = "right" )],
90
+ "max_fun" : [Interval (Integral , 1 , None , closed = "left" )],
91
+ }
92
+
57
93
@abstractmethod
58
94
def __init__ (
59
95
self ,
@@ -381,8 +417,6 @@ def _fit(self, X, y, incremental=False):
381
417
hidden_layer_sizes = [hidden_layer_sizes ]
382
418
hidden_layer_sizes = list (hidden_layer_sizes )
383
419
384
- # Validate input parameters.
385
- self ._validate_hyperparameters ()
386
420
if np .any (np .array (hidden_layer_sizes ) <= 0 ):
387
421
raise ValueError (
388
422
"hidden_layer_sizes must be > 0, got %s." % hidden_layer_sizes
@@ -452,67 +486,6 @@ def _fit(self, X, y, incremental=False):
452
486
453
487
return self
454
488
455
- def _validate_hyperparameters (self ):
456
- if not isinstance (self .shuffle , bool ):
457
- raise ValueError (
458
- "shuffle must be either True or False, got %s." % self .shuffle
459
- )
460
- if self .max_iter <= 0 :
461
- raise ValueError ("max_iter must be > 0, got %s." % self .max_iter )
462
- if self .max_fun <= 0 :
463
- raise ValueError ("max_fun must be > 0, got %s." % self .max_fun )
464
- if self .alpha < 0.0 :
465
- raise ValueError ("alpha must be >= 0, got %s." % self .alpha )
466
- if (
467
- self .learning_rate in ["constant" , "invscaling" , "adaptive" ]
468
- and self .learning_rate_init <= 0.0
469
- ):
470
- raise ValueError (
471
- "learning_rate_init must be > 0, got %s." % self .learning_rate
472
- )
473
- if self .momentum > 1 or self .momentum < 0 :
474
- raise ValueError ("momentum must be >= 0 and <= 1, got %s" % self .momentum )
475
- if not isinstance (self .nesterovs_momentum , bool ):
476
- raise ValueError (
477
- "nesterovs_momentum must be either True or False, got %s."
478
- % self .nesterovs_momentum
479
- )
480
- if not isinstance (self .early_stopping , bool ):
481
- raise ValueError (
482
- "early_stopping must be either True or False, got %s."
483
- % self .early_stopping
484
- )
485
- if self .validation_fraction < 0 or self .validation_fraction >= 1 :
486
- raise ValueError (
487
- "validation_fraction must be >= 0 and < 1, got %s"
488
- % self .validation_fraction
489
- )
490
- if self .beta_1 < 0 or self .beta_1 >= 1 :
491
- raise ValueError ("beta_1 must be >= 0 and < 1, got %s" % self .beta_1 )
492
- if self .beta_2 < 0 or self .beta_2 >= 1 :
493
- raise ValueError ("beta_2 must be >= 0 and < 1, got %s" % self .beta_2 )
494
- if self .epsilon <= 0.0 :
495
- raise ValueError ("epsilon must be > 0, got %s." % self .epsilon )
496
- if self .n_iter_no_change <= 0 :
497
- raise ValueError (
498
- "n_iter_no_change must be > 0, got %s." % self .n_iter_no_change
499
- )
500
-
501
- # raise ValueError if not registered
502
- if self .activation not in ACTIVATIONS :
503
- raise ValueError (
504
- "The activation '%s' is not supported. Supported activations are %s."
505
- % (self .activation , list (sorted (ACTIVATIONS )))
506
- )
507
- if self .learning_rate not in ["constant" , "invscaling" , "adaptive" ]:
508
- raise ValueError ("learning rate %s is not supported. " % self .learning_rate )
509
- supported_solvers = _STOCHASTIC_SOLVERS + ["lbfgs" ]
510
- if self .solver not in supported_solvers :
511
- raise ValueError (
512
- "The solver %s is not supported. Expected one of: %s"
513
- % (self .solver , ", " .join (supported_solvers ))
514
- )
515
-
516
489
def _fit_lbfgs (
517
490
self , X , y , activations , deltas , coef_grads , intercept_grads , layer_units
518
491
):
@@ -617,7 +590,7 @@ def _fit_stochastic(
617
590
if self .batch_size == "auto" :
618
591
batch_size = min (200 , n_samples )
619
592
else :
620
- if self .batch_size < 1 or self . batch_size > n_samples :
593
+ if self .batch_size > n_samples :
621
594
warnings .warn (
622
595
"Got `batch_size` less than 1 or larger than "
623
596
"sample size. It is going to be clipped"
@@ -759,6 +732,8 @@ def fit(self, X, y):
759
732
self : object
760
733
Returns a trained MLP model.
761
734
"""
735
+ self ._validate_params ()
736
+
762
737
return self ._fit (X , y , incremental = False )
763
738
764
739
def _check_solver (self ):
@@ -770,25 +745,6 @@ def _check_solver(self):
770
745
)
771
746
return True
772
747
773
- @available_if (_check_solver )
774
- def partial_fit (self , X , y ):
775
- """Update the model with a single iteration over the given data.
776
-
777
- Parameters
778
- ----------
779
- X : {array-like, sparse matrix} of shape (n_samples, n_features)
780
- The input data.
781
-
782
- y : ndarray of shape (n_samples,)
783
- The target values.
784
-
785
- Returns
786
- -------
787
- self : object
788
- Trained MLP model.
789
- """
790
- return self ._fit (X , y , incremental = True )
791
-
792
748
793
749
class MLPClassifier (ClassifierMixin , BaseMultilayerPerceptron ):
794
750
"""Multi-layer Perceptron classifier.
@@ -800,7 +756,7 @@ class MLPClassifier(ClassifierMixin, BaseMultilayerPerceptron):
800
756
801
757
Parameters
802
758
----------
803
- hidden_layer_sizes : tuple, length = n_layers - 2, default=(100,)
759
+ hidden_layer_sizes : array-like of shape( n_layers - 2,) , default=(100,)
804
760
The ith element represents the number of neurons in the ith
805
761
hidden layer.
806
762
@@ -1205,16 +1161,17 @@ def partial_fit(self, X, y, classes=None):
1205
1161
self : object
1206
1162
Trained MLP model.
1207
1163
"""
1164
+ if not hasattr (self , "coefs_" ):
1165
+ self ._validate_params ()
1166
+
1208
1167
if _check_partial_fit_first_call (self , classes ):
1209
1168
self ._label_binarizer = LabelBinarizer ()
1210
1169
if type_of_target (y ).startswith ("multilabel" ):
1211
1170
self ._label_binarizer .fit (y )
1212
1171
else :
1213
1172
self ._label_binarizer .fit (classes )
1214
1173
1215
- super ().partial_fit (X , y )
1216
-
1217
- return self
1174
+ return self ._fit (X , y , incremental = True )
1218
1175
1219
1176
def predict_log_proba (self , X ):
1220
1177
"""Return the log of probability estimates.
@@ -1273,7 +1230,7 @@ class MLPRegressor(RegressorMixin, BaseMultilayerPerceptron):
1273
1230
1274
1231
Parameters
1275
1232
----------
1276
- hidden_layer_sizes : tuple, length = n_layers - 2, default=(100,)
1233
+ hidden_layer_sizes : array-like of shape( n_layers - 2,) , default=(100,)
1277
1234
The ith element represents the number of neurons in the ith
1278
1235
hidden layer.
1279
1236
@@ -1606,3 +1563,25 @@ def _validate_input(self, X, y, incremental, reset):
1606
1563
if y .ndim == 2 and y .shape [1 ] == 1 :
1607
1564
y = column_or_1d (y , warn = True )
1608
1565
return X , y
1566
+
1567
+ @available_if (lambda est : est ._check_solver )
1568
+ def partial_fit (self , X , y ):
1569
+ """Update the model with a single iteration over the given data.
1570
+
1571
+ Parameters
1572
+ ----------
1573
+ X : {array-like, sparse matrix} of shape (n_samples, n_features)
1574
+ The input data.
1575
+
1576
+ y : ndarray of shape (n_samples,)
1577
+ The target values.
1578
+
1579
+ Returns
1580
+ -------
1581
+ self : object
1582
+ Trained MLP model.
1583
+ """
1584
+ if not hasattr (self , "coefs_" ):
1585
+ self ._validate_params ()
1586
+
1587
+ return self ._fit (X , y , incremental = True )
0 commit comments