diff --git a/glmdisc/_fitNN.py b/glmdisc/_fitNN.py index e058359..54ed3b7 100644 --- a/glmdisc/_fitNN.py +++ b/glmdisc/_fitNN.py @@ -19,6 +19,19 @@ from tensorflow.keras.layers import concatenate +def _layer_output(layer, x): + """ + Applies a layer to an array of inputs. + + :param layer: a Keras layer + :param numpy.ndarray x: the inputs of the layer + :returns: a list holding the numpy array of outputs (the format returned by + :code:`tf.keras.backend.function`, which does not exist in Keras 3) + :rtype: list + """ + return [np.asarray(layer(tf.convert_to_tensor(x, dtype=tf.float32)))] + + def _plot_cont_soft_quant(self): """ Plots the soft quantization while fitting @@ -128,15 +141,13 @@ def on_epoch_end(self, batch, logs=None): for j in range(self.d_cont): self.best_weights.append(self.current_weights[j]) self.best_outputs.append( - tf.keras.backend.function([self.glmdisc_object.model_nn["liste_layers_quant"][j].input], - [self.glmdisc_object.model_nn["liste_layers_quant"][j].output])( - [self.glmdisc_object.predictors_cont[:, j, np.newaxis]])) + _layer_output(self.glmdisc_object.model_nn["liste_layers_quant"][j], + self.glmdisc_object.predictors_cont[:, j, np.newaxis])) for j in range(self.d_qual): self.best_weights.append(self.current_weights[j + self.d_cont]) self.best_outputs.append( - tf.keras.backend.function([self.glmdisc_object.model_nn["liste_layers_qual"][j].input], - [self.glmdisc_object.model_nn["liste_layers_qual"][j].output])( - [self.glmdisc_object.predictors_qual_dummy[j]])) + _layer_output(self.glmdisc_object.model_nn["liste_layers_qual"][j], + self.glmdisc_object.predictors_qual_dummy[j])) if self.plot_fit: _plot_cont_soft_quant(self) @@ -208,14 +219,12 @@ def _from_layers_to_proba_training(d_cont, d_qual, glmdisc_object): results = [None] * (d_cont + d_qual) for j in range(d_cont): - results[j] = tf.keras.backend.function([glmdisc_object.model_nn["liste_layers_quant"][j].input], - [glmdisc_object.model_nn["liste_layers_quant"][j].output])( - [glmdisc_object.predictors_cont[glmdisc_object.train_rows, j, np.newaxis]]) + results[j] = _layer_output(glmdisc_object.model_nn["liste_layers_quant"][j], + glmdisc_object.predictors_cont[glmdisc_object.train_rows, j, np.newaxis]) for j in range(d_qual): - results[j + d_cont] = tf.keras.backend.function([glmdisc_object.model_nn["liste_layers_qual"][j].input], - [glmdisc_object.model_nn["liste_layers_qual"][j].output])( - [glmdisc_object.predictors_qual_dummy[j][glmdisc_object.train_rows]]) + results[j + d_cont] = _layer_output(glmdisc_object.model_nn["liste_layers_qual"][j], + glmdisc_object.predictors_qual_dummy[j][glmdisc_object.train_rows]) return results @@ -285,7 +294,7 @@ def _evaluate_disc(history, d_cont: int, d_qual: int, glmdisc_object): results = [None] * (d_cont + d_qual) for j in range(d_cont + d_qual): results[j] = np.argmax(proba[j][0], axis=1) - encoders.append(sk.preprocessing.OneHotEncoder(categories='auto', sparse=False, handle_unknown="ignore")) + encoders.append(sk.preprocessing.OneHotEncoder(categories='auto', sparse_output=False, handle_unknown="ignore")) X_transformed = np.concatenate( (X_transformed, encoders[j].fit_transform( @@ -429,7 +438,7 @@ def _parse_kwargs(self, **kwargs): if "optimizer" in kwargs: optim = kwargs['optimizer'] else: - optim = tensorflow.keras.optimizers.RMSprop(lr=0.5, rho=0.9, epsilon=None, decay=0.0) + optim = tensorflow.keras.optimizers.RMSprop(learning_rate=0.5, rho=0.9, epsilon=1e-7) self.model_nn["callbacks"] = [ ReduceLROnPlateau( diff --git a/tests/tests_NN/test_NN_fit.py b/tests/tests_NN/test_NN_fit.py index 113154a..24ae5fd 100644 --- a/tests/tests_NN/test_NN_fit.py +++ b/tests/tests_NN/test_NN_fit.py @@ -6,8 +6,7 @@ import random import glmdisc import sklearn as sk -from glmdisc._fitNN import _initialize_neural_net -import tensorflow +from glmdisc._fitNN import _initialize_neural_net, _layer_output, _parse_kwargs from tensorflow.keras.optimizers import Adagrad from tensorflow.keras.callbacks import EarlyStopping @@ -24,10 +23,8 @@ def test_kwargs(caplog): plot=True, optimizer=Adagrad(), callbacks=EarlyStopping()) - assert isinstance(model.model_nn['tensorflow_model'].optimizer, - tensorflow.python.keras.optimizer_v2.adagrad.Adagrad) - assert isinstance(model.model_nn['callbacks'][-1], - tensorflow.python.keras.callbacks.EarlyStopping) + assert isinstance(model.model_nn['tensorflow_model'].optimizer, Adagrad) + assert isinstance(model.model_nn['callbacks'][-1], EarlyStopping) with pytest.raises(ValueError): model.fit(predictors_cont=x, predictors_qual=None, @@ -228,3 +225,26 @@ def fail_fit(self, y): monkeypatch.setattr(sk.preprocessing.LabelEncoder, "fit", fail_fit) _initialize_neural_net(model) + + +def test_default_optimizer(): + model = glmdisc.Glmdisc(algorithm="NN") + optim = _parse_kwargs(model) + assert float(optim.learning_rate) == pytest.approx(0.5) + + +def test_layer_output(): + from tensorflow.keras.layers import Dense, Input + layer = Dense(3, activation='softmax') + layer(Input((1, ))) + kernel = np.array([[1., -1., 0.]]) + bias = np.array([0., .5, 0.]) + layer.set_weights([kernel, bias]) + x = np.array([[0.], [2.]]) + + output = _layer_output(layer, x) + + logits = x @ kernel + bias + expected = np.exp(logits) / np.exp(logits).sum(axis=1, keepdims=True) + assert isinstance(output, list) and len(output) == 1 + np.testing.assert_allclose(output[0], expected, rtol=1e-5)