Keras: custom data validation callback on training data always returns validation data results

Viewed 798

I am working on an autoencoder in Keras that contains some dropout layers. To evaluate bias and variance, I'd like to compare the losses of training and test data. However, since dropout is used during training, the losses cannot be compared. (See here for an explanation of why the training data results can be worse than test data results.) In order to get training data losses that are not influenced by the dropout, I wrote a callback to validate some additional data set (in this case, it would be the training data again). The strange thing is that I ALWAYS get the same results as for the validation data. Here's a minimal example:

from pprint import pprint

import keras
import numpy as np
import pandas as pd
from numpy.random import seed as np_seed
from tensorflow.random import set_seed as tf_seed

np_seed(1)
tf_seed(2)

# Generation of data sets for training and testing. Random data is only used to showcase the problem. 
df_train = pd.DataFrame(data=np.random.random((1000, 10))) # This will be used for training
df_test_1 = pd.DataFrame(data=np.random.random((1000, 10))) # This will be used as validation data set directly
df_test_2 = pd.DataFrame(data=np.random.random((1000, 10))) # This will be used within the callback

np_seed(1)
tf_seed(2)

model = keras.models.Sequential(
    [
        keras.Input(shape=(10, )),
        keras.layers.Dropout(rate=0.01),
        keras.layers.Dense(5, activation='relu'),
        keras.layers.Dropout(rate=0.01),
        keras.layers.Dense(10, activation='linear'),
    ]
)

model.compile(
    loss='mean_squared_error',
    optimizer=keras.optimizers.Adam(),
)


class CustomDataValidation(keras.callbacks.Callback):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

    def on_epoch_end(self, epoch, logs=None):
        result = self.model.evaluate(x=self.x, y=self.y, return_dict=True)

        for loss_name, loss_value in result.items():
            logs["custom_" + loss_name] = loss_value


cdv = CustomDataValidation(df_test_2, df_test_2)

hist = model.fit(df_train, df_train, validation_data=(df_test_1, df_test_1), epochs=2, validation_split=0.1, callbacks=[cdv])
pprint(hist.history)

The output is

Epoch 1/2
4/4 [==============================] - 0s 1ms/step - loss: 0.7625
29/29 [==============================] - 0s 5ms/step - loss: 0.9666 - val_loss: 0.7625
Epoch 2/2
4/4 [==============================] - 0s 1ms/step - loss: 0.5331
29/29 [==============================] - 0s 2ms/step - loss: 0.6638 - val_loss: 0.5331
{'custom_loss': [0.7624925374984741, 0.5331208109855652],
 'loss': [0.9665887951850891, 0.6637843251228333],
 'val_loss': [0.7624925374984741, 0.5331208109855652]}

'custom_loss' and 'val_loss' are equal although they should be based on totally different data sets. The question is therefore: How can I evaluate the model performance on custom data within a callback?

Edit: Since I did not yet got an answer on stackoverflow, I created an issue at tensorflow's git repo. Also, there's now a notebook available that shows the problem.

1 Answers

It seems that this is a bug in tensorflow versions 2.3.x (tested with 2.3.0 and 2.3.1). In versions 2.4.0-rc0 and 2.2.1, the loss outputs of loss and custom_loss differ, which is the expected behavior:

{'custom_loss': [0.7694963216781616, 0.541864812374115],
'loss': [0.9665887951850891, 0.6637843251228333],
'val_loss': [0.7624925374984741, 0.5331208109855652]}
Related