1111from sklearn .utils .validation import check_array , check_is_fitted , check_X_y
1212
1313
14- class NNOP (BaseEstimator , ClassifierMixin ):
14+ class NNOP (ClassifierMixin , BaseEstimator ):
1515 """Neural Network with Ordered Partitions (NNOP).
1616
1717 This model considers the OrderedPartitions coding scheme for the labels and a rule
1818 for decisions based on the first node whose output is higher than a predefined
19- threshold (T=0.5, in our experiments). The model has one hidden layer with hiddenN
20- neurons and one output layer with as many neurons as the number of classes minus
21- one.
19+ threshold (T=0.5, in our experiments). The model has one hidden layer with
20+ "n_hidden" neurons and one output layer with as many neurons as the number of
21+ classes minus one.
2222
2323 The learning is based on iRProp+ algorithm and the implementation provided by
2424 Roberto Calandra in his toolbox Rprop Toolbox for MATLAB:
@@ -37,21 +37,34 @@ class NNOP(BaseEstimator, ClassifierMixin):
3737 Number of hidden neurons of the model.
3838
3939 max_iter : int, default=500
40- Number of iterations for fmin_l_bfgs_b algorithm.
40+ Maximum number of iterations. The solver iterates until convergence or this
41+ number of iterations.
4142
4243 lambda_value : float, default=0.01
4344 Regularization parameter.
4445
4546 Attributes
4647 ----------
4748 classes_ : ndarray of shape (n_classes,)
48- Array that contains all different class labels found in the original dataset .
49+ Class labels for each output .
4950
50- n_classes_ : int
51- Number of labels in the problem .
51+ loss_ : float
52+ The current loss computed with the loss function .
5253
53- n_samples_ : int
54- Number of samples of X (train patterns array).
54+ n_features_in_ : int
55+ Number of features seen during fit.
56+
57+ n_iter_ : int
58+ The number of iterations the solver has run.
59+
60+ n_layers_ : int
61+ Number of layers.
62+
63+ n_outputs_ : int
64+ Number of outputs.
65+
66+ out_activation_ : str
67+ Name of the output activation function.
5568
5669 theta1_ : ndarray of shape (n_hidden, n_features + 1)
5770 Hidden layer weights (with bias).
@@ -106,16 +119,15 @@ def __init__(self, epsilon_init=0.5, n_hidden=50, max_iter=500, lambda_value=0.0
106119
107120 @_fit_context (prefer_skip_nested_validation = True )
108121 def fit (self , X , y ):
109- """Fit the model with the training data .
122+ """Fit the model to data matrix X and target(s) y .
110123
111124 Parameters
112125 ----------
113- X : {array-like, sparse matrix} of shape (n_samples, n_features)
114- Training patterns array, where n_samples is the number of samples
115- and n_features is the number of features.
126+ X : ndarray or sparse matrix of shape (n_samples, n_features)
127+ The input data.
116128
117- y : array-like of shape (n_samples,)
118- Target vector relative to X .
129+ y : ndarray of shape (n_samples,)
130+ The target values .
119131
120132 Returns
121133 -------
@@ -128,24 +140,16 @@ def fit(self, X, y):
128140 If parameters are invalid or data has wrong format.
129141
130142 """
131- if (
132- self .epsilon_init < 0
133- or self .n_hidden < 1
134- or self .max_iter < 1
135- or self .lambda_value < 0
136- ):
137- return None
138-
139143 # Check that X and y have correct shape
140144 X , y = check_X_y (X , y )
141145 # Store the classes seen during fit
142146 self .classes_ = unique_labels (y )
143147
144148 # Aux variables
145149 y = y [:, np .newaxis ]
146- n_features = X .shape [1 ]
147- n_classes = np .size (np .unique (y ))
150+ n_classes = len (self .classes_ )
148151 n_samples = X .shape [0 ]
152+ self .n_features_in_ = X .shape [1 ]
149153
150154 # Recode y to Y using ordinalPartitions coding
151155 Y = 1 * (
@@ -154,7 +158,9 @@ def fit(self, X, y):
154158 )
155159
156160 # Hidden layer weights (with bias)
157- initial_theta1 = self ._rand_initialize_weights (n_features + 1 , self .n_hidden )
161+ initial_theta1 = self ._rand_initialize_weights (
162+ self .n_features_in_ + 1 , self .n_hidden
163+ )
158164 # Output layer weights
159165 initial_theta2 = self ._rand_initialize_weights (self .n_hidden + 1 , n_classes - 1 )
160166
@@ -167,22 +173,34 @@ def fit(self, X, y):
167173 results_optimization = scipy .optimize .fmin_l_bfgs_b (
168174 func = self ._nnop_cost_function ,
169175 x0 = initial_nn_params .ravel (),
170- args = (n_features , self .n_hidden , n_classes , X , Y , self .lambda_value ),
176+ args = (
177+ self .n_features_in_ ,
178+ self .n_hidden ,
179+ n_classes ,
180+ X ,
181+ Y ,
182+ self .lambda_value ,
183+ ),
171184 fprime = None ,
172185 factr = 1e3 ,
173186 maxiter = self .max_iter ,
174- iprint = - 1 ,
175187 )
176188
177- self .nn_params = results_optimization [0 ]
189+ nn_params = results_optimization [0 ]
190+ self .loss_ = float (results_optimization [1 ])
191+ self .n_iter_ = int (results_optimization [2 ].get ("nit" , 0 ))
192+
178193 # Unpack the parameters
179194 theta1 , theta2 = self ._unpack_parameters (
180- self . nn_params , n_features , self .n_hidden , n_classes
195+ nn_params , self . n_features_in_ , self .n_hidden , n_classes
181196 )
182197 self .theta1_ = theta1
183198 self .theta2_ = theta2
184- self .n_classes_ = n_classes
185- self .n_samples_ = n_samples
199+
200+ # Scikit-learn compatibility
201+ self .n_layers_ = 3
202+ self .n_outputs_ = n_classes - 1
203+ self .out_activation_ = "logistic"
186204
187205 return self
188206
@@ -192,13 +210,12 @@ def predict(self, X):
192210 Parameters
193211 ----------
194212 X : {array-like, sparse matrix} of shape (n_samples, n_features)
195- Test patterns array, where n_samples is the number of samples and n_features
196- is the number of features.
213+ The input data.
197214
198215 Returns
199216 -------
200217 y_pred : ndarray of shape (n_samples,)
201- Class labels for samples in X .
218+ The predicted classes .
202219
203220 Raises
204221 ------
@@ -210,11 +227,12 @@ def predict(self, X):
210227
211228 """
212229 # Check is fit had been called
213- check_is_fitted (self )
230+ check_is_fitted (self , attributes = [ "theta1_" , "theta2_" , "classes_" ] )
214231
215232 # Input validation
216233 X = check_array (X )
217234 n_samples = X .shape [0 ]
235+ n_classes = len (self .classes_ )
218236
219237 a1 = np .append (np .ones ((n_samples , 1 )), X , axis = 1 )
220238 z2 = np .append (np .ones ((n_samples , 1 )), np .matmul (a1 , self .theta1_ .T ), axis = 1 )
@@ -225,189 +243,13 @@ def predict(self, X):
225243
226244 a3 = np .multiply (
227245 np .where (np .append (projected , np .ones ((n_samples , 1 )), axis = 1 ) > 0.5 , 1 , 0 ),
228- np .tile (np .arange (1 , self . n_classes_ + 1 ), (n_samples , 1 )),
246+ np .tile (np .arange (1 , n_classes + 1 ), (n_samples , 1 )),
229247 )
230- a3 [np .where (a3 == 0 )] = self . n_classes_ + 1
248+ a3 [np .where (a3 == 0 )] = n_classes + 1
231249 y_pred = a3 .min (axis = 1 )
232250
233251 return y_pred
234252
235- def get_epsilon_init (self ):
236- """Return the value of the variable self.epsilon_init.
237-
238- Returns
239- -------
240- epsilon_init : float
241- The initialization range of the weights.
242-
243- """
244- return self .epsilon_init
245-
246- def set_epsilon_init (self , epsilon_init ):
247- """Modify the value of the variable self.epsilon_init.
248-
249- Parameters
250- ----------
251- epsilon_init : float
252- The initialization range of the weights.
253-
254- """
255- self .epsilon_init = epsilon_init
256-
257- def get_n_hidden (self ):
258- """Return the value of the variable self.n_hidden.
259-
260- Returns
261- -------
262- n_hidden : int
263- Number of nodes/neurons in the hidden layer.
264-
265- """
266- return self .n_hidden
267-
268- def set_n_hidden (self , n_hidden ):
269- """Modify the value of the variable self.n_hidden.
270-
271- Parameters
272- ----------
273- n_hidden : int
274- Number of nodes/neurons in the hidden layer.
275-
276- """
277- self .n_hidden = n_hidden
278-
279- def get_max_iter (self ):
280- """Return the value of the variable self.max_iter.
281-
282- Returns
283- -------
284- max_iter : int
285- Number of iterations.
286-
287- """
288- return self .max_iter
289-
290- def set_max_iter (self , max_iter ):
291- """Modify the value of the variable self.max_iter.
292-
293- Parameters
294- ----------
295- max_iter : int
296- Number of iterations.
297-
298- """
299- self .max_iter = max_iter
300-
301- def get_lambda_value (self ):
302- """Return the value of the variable self.lambda_value.
303-
304- Returns
305- -------
306- lambda_value : float
307- Lambda parameter used in regularization.
308-
309- """
310- return self .lambda_value
311-
312- def set_lambda_value (self , lambda_value ):
313- """Modify the value of the variable self.lambda_value.
314-
315- Parameters
316- ----------
317- lambda_value : float
318- Lambda parameter used in regularization.
319-
320- """
321- self .lambda_value = lambda_value
322-
323- def get_theta1 (self ):
324- """Return the value of the variable self.theta1_.
325-
326- Returns
327- -------
328- theta1_ : ndarray of shape (n_hidden, n_features + 1)
329- Array with the weights of the hidden layer (with biases included).
330-
331- """
332- return self .theta1_
333-
334- def set_theta1 (self , theta1 ):
335- """Modify the value of the variable self.theta1_.
336-
337- Parameters
338- ----------
339- theta1 : ndarray of shape (n_hidden, n_features + 1)
340- Array with the weights of the hidden layer (with biases included).
341-
342- """
343- self .theta1_ = theta1
344-
345- def get_theta2 (self ):
346- """Return the value of the variable self.theta2_.
347-
348- Returns
349- -------
350- theta2_ : ndarray of shape (n_classes - 1, n_hidden + 1)
351- Array with the weights of the output layer.
352-
353- """
354- return self .theta2_
355-
356- def set_theta2 (self , theta2 ):
357- """Modify the value of the variable self.theta2_.
358-
359- Parameters
360- ----------
361- theta2 : ndarray of shape (n_classes - 1, n_hidden + 1)
362- Array with the weights of the output layer.
363-
364- """
365- self .theta2_ = theta2
366-
367- def get_n_classes (self ):
368- """Return the value of the variable self.n_classes_.
369-
370- Returns
371- -------
372- n_classes_ : int
373- Number of labels in the problem.
374-
375- """
376- return self .n_classes_
377-
378- def set_n_classes (self , n_classes ):
379- """Modify the value of the variable self.n_classes_.
380-
381- Parameters
382- ----------
383- n_classes : int
384- Number of labels in the problem.
385-
386- """
387- self .n_classes_ = n_classes
388-
389- def get_n_samples (self ):
390- """Return the value of the variable self.n_samples_.
391-
392- Returns
393- -------
394- n_samples_ : int
395- Number of samples of X (train patterns array).
396-
397- """
398- return self .n_samples_
399-
400- def set_n_samples (self , n_samples ):
401- """Modify the value of the variable self.n_samples_.
402-
403- Parameters
404- ----------
405- n_samples : int
406- Number of samples of X (train patterns array).
407-
408- """
409- self .n_samples_ = n_samples
410-
411253 def _unpack_parameters (self , nn_params , n_features , n_hidden , n_classes ):
412254 """Get theta1 and theta2 back from nn_params.
413255
@@ -468,10 +310,7 @@ def _rand_initialize_weights(self, L_in, L_out):
468310 Array with the weights of each synaptic relationship between nodes.
469311
470312 """
471- W = (
472- np .random .rand (L_out , L_in ) * 2 * self .get_epsilon_init ()
473- - self .get_epsilon_init ()
474- )
313+ W = np .random .rand (L_out , L_in ) * 2 * self .epsilon_init - self .epsilon_init
475314
476315 return W
477316
0 commit comments