GridSearchCV and Keras gives different MSE than manual search

Viewed 182

I'm running some experiments with a simple Neural Network using Keras and Sklearn and I'm running into some unexpected results.

In my first experiment, the NN has one hidden layer with 64 neurons, and I'm using the StratifiedKFold class to run a KFold with 5 splits.

from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
from sklearn.model_selection import StratifiedKFold
import numpy as np
import tensorflow as tf
import random

seed = 7
random.seed(seed)
np.random.seed(seed)
tf.random.set_seed(seed)

kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=seed)

for train, test in kfold.split(X, Y):
    model = Sequential()
    model.add(Dense(64, input_dim=12, activation='relu'))
    model.add(Dense(1))
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
    model.summary()
    model.fit(X_train[train], Y[train], epochs=10,verbose=1)
    y_pred=model.predict(X_train[test])
    mse_value, mae_value=model.evaluate(X_train[test], Y[test], verbose=1)
    print(mse_value)

On the first fold, I get the following information printed:

Model: "sequential_169"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_342 (Dense)            (None, 64)                832       
_________________________________________________________________
dense_343 (Dense)            (None, 1)                 65        
=================================================================
Total params: 897
Trainable params: 897
Non-trainable params: 0
_________________________________________________________________
Epoch 1/10
163/163 [==============================] - 0s 520us/step - loss: 23.5748 - mae: 4.6046
Epoch 2/10
163/163 [==============================] - 0s 503us/step - loss: 1.9301 - mae: 1.0770
Epoch 3/10
163/163 [==============================] - 0s 502us/step - loss: 1.0503 - mae: 0.8026
Epoch 4/10
163/163 [==============================] - 0s 492us/step - loss: 0.7895 - mae: 0.6828
Epoch 5/10
163/163 [==============================] - 0s 503us/step - loss: 0.6499 - mae: 0.6171
Epoch 6/10
163/163 [==============================] - 0s 524us/step - loss: 0.5652 - mae: 0.5795
Epoch 7/10
163/163 [==============================] - 0s 506us/step - loss: 0.5806 - mae: 0.5819
Epoch 8/10
163/163 [==============================] - 0s 506us/step - loss: 0.4949 - mae: 0.5497
Epoch 9/10
163/163 [==============================] - 0s 493us/step - loss: 0.4864 - mae: 0.5418
Epoch 10/10
163/163 [==============================] - 0s 492us/step - loss: 0.4942 - mae: 0.5455
41/41 [==============================] - 0s 474us/step - loss: 0.4861 - mae: 0.5457
0.48606643080711365
...

Note that the loss goes from 23.5748 to 0.4942 while training.

In my second experiment, I use the GridSearchCV class to perform a grid search on the number of layers to use. (To demonstrate my problem, I'm just trying one layer). I'm also passing the same kfold strategy as in the previous experiment to the constructor of GridSearchCV.

from tensorflow.keras.wrappers.scikit_learn import KerasRegressor
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from sklearn.model_selection import GridSearchCV
import tensorflow as tf
import random

seed = 7
random.seed(seed)
np.random.seed(seed)
tf.random.set_seed(seed)

def create_model(hidden_layers=1):
    # Initialize the constructor
    model = Sequential()

    # Add hidden layers
    for i in range(hidden_layers):
        if i == 0:
            model.add(Dense(64, input_dim=12, activation='relu'))
        else:
            model.add(Dense(64, activation='relu'))

    # Add an output layer 
    model.add(Dense(1))
        
    model.compile(optimizer='rmsprop', loss='mse', metrics=["mae"])
    model.summary()
        
    return model

model = KerasRegressor(build_fn=create_model, epochs=10, verbose=1)

param_grid = dict(hidden_layers=[1])

kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=seed)

grid = GridSearchCV(estimator=model, param_grid=param_grid,
                    scoring=["neg_mean_absolute_error", "neg_mean_squared_error", "r2"],
                    refit="r2",
                    n_jobs=1, cv=kfold)

grid_result = grid.fit(X, Y)

With this method, on the first fold, I get the following output:

Model: "sequential_180"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_364 (Dense)            (None, 64)                832       
_________________________________________________________________
dense_365 (Dense)            (None, 1)                 65        
=================================================================
Total params: 897
Trainable params: 897
Non-trainable params: 0
_________________________________________________________________
Epoch 1/10
163/163 [==============================] - 0s 527us/step - loss: 9.8205 - mae: 2.3366
Epoch 2/10
163/163 [==============================] - 0s 479us/step - loss: 1.0685 - mae: 0.8089
Epoch 3/10
163/163 [==============================] - 0s 503us/step - loss: 0.9351 - mae: 0.7488
Epoch 4/10
163/163 [==============================] - 0s 503us/step - loss: 0.9602 - mae: 0.7560
Epoch 5/10
163/163 [==============================] - 0s 502us/step - loss: 1.0195 - mae: 0.7830
Epoch 6/10
163/163 [==============================] - 0s 494us/step - loss: 0.9774 - mae: 0.7761
Epoch 7/10
163/163 [==============================] - 0s 489us/step - loss: 0.9569 - mae: 0.7413
Epoch 8/10
163/163 [==============================] - 0s 488us/step - loss: 0.9772 - mae: 0.7794
Epoch 9/10
163/163 [==============================] - 0s 464us/step - loss: 0.8716 - mae: 0.7259
Epoch 10/10
163/163 [==============================] - 0s 494us/step - loss: 0.8687 - mae: 0.7248
41/41 [==============================] - 0s 380us/step
...

Here the loss function behaves really differently from the first experiment; going from 9.8205 to 0.8687.

Since I'm:

  1. Setting all random seeds to the same value
seed = 7
random.seed(seed)
np.random.seed(seed)
tf.random.set_seed(seed)
  1. Using the same KFold method:
kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=seed)
  1. The neural networks have indeed the same architecture:
Model: "sequential_XXX"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_XXX (Dense)            (None, 64)                832       
_________________________________________________________________
dense_XXY (Dense)            (None, 1)                 65        
=================================================================
Total params: 897
Trainable params: 897
Non-trainable params: 0
  1. The epochs and batch sizes are the same.

I would expect both neurons to obtain the same results (at least in the first fold), but I'm getting different results in the loss function.

How is it possible that the NN from the first experiment behaves differently than the one from the second?

EDIT

The problem was that I was training with X_train on the first experiment and with X on the second. X_train was the scaled version of X.

Having said that, Marco's points regarding seeds also apply. Please refer to his answer.

1 Answers

This is simply due to the random weights initialization by keras every time you build a new model in each fold. Simply setting the seed one time at the top makes the below code reproducible but dependent on the order you execute it.

To make the results the same you simply have to initialize the same seed every time you fit a new fold. We do this at the top of create_model function and use it to manually operate CV and with KerasRegressor plus cross_val_score (from sklearn).

def set_seed(seed):  
    random.seed(seed)
    np.random.seed(seed)
    tf.random.set_seed(seed)

def create_model(hidden_layers=1):
    
    set_seed(seed=7) # <==== set the seed at the beginning every time
    
    model = Sequential()
    for i in range(hidden_layers):
        if i == 0:
            model.add(Dense(64, input_dim=12, activation='relu'))
        else:
            model.add(Dense(64, activation='relu'))
    model.add(Dense(1))        
    model.compile(optimizer='rmsprop', loss='mse')
        
    return model

initialize some dummy data

np.random.seed(7)
X = np.random.uniform(0,1, (1000,12))
Y = np.random.randint(0,2, (1000,))

kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=7)

manual CV

for train, test in kfold.split(X, Y):
    model = create_model()
    model.fit(X[train], Y[train], epochs=1, verbose=1)
    mse_value = model.evaluate(X[test], Y[test], verbose=1)

results:

25/25 [==============================] - 1s 1ms/step - loss: 0.2977
7/7 [==============================] - 0s 2ms/step - loss: 0.2508
25/25 [==============================] - 0s 1ms/step - loss: 0.2789
7/7 [==============================] - 0s 2ms/step - loss: 0.2696
25/25 [==============================] - 0s 979us/step - loss: 0.2760
7/7 [==============================] - 0s 2ms/step - loss: 0.2669
25/25 [==============================] - 0s 1ms/step - loss: 0.3076
7/7 [==============================] - 0s 2ms/step - loss: 0.2538
25/25 [==============================] - 0s 1ms/step - loss: 0.2807
7/7 [==============================] - 0s 2ms/step - loss: 0.2642

sklearn CV

model_wrapper = KerasRegressor(build_fn=create_model, epochs=1, verbose=1)
cross_val_score(model_wrapper, X, Y, cv=kfold)

results:

25/25 [==============================] - 0s 1ms/step - loss: 0.2977
7/7 [==============================] - 0s 2ms/step - loss: 0.2508
25/25 [==============================] - 1s 1ms/step - loss: 0.2789
7/7 [==============================] - 0s 2ms/step - loss: 0.2696
25/25 [==============================] - 0s 1ms/step - loss: 0.2760
7/7 [==============================] - 0s 2ms/step - loss: 0.2669
25/25 [==============================] - 0s 1ms/step - loss: 0.3076
7/7 [==============================] - 0s 2ms/step - loss: 0.2538
25/25 [==============================] - 0s 1ms/step - loss: 0.2807
7/7 [==============================] - 0s 2ms/step - loss: 0.2642

here the running notebook

This is valid only with CPU

Related