Keras/Tensorflow: Combined Loss function for single output

Question:

I have only one output for my model, but I would like to combine two different loss functions:

def get_model():
    # create the model here
    model = Model(inputs=image, outputs=output)

    alpha = 0.2
    model.compile(loss=[mse, gse],
                      loss_weights=[1-alpha, alpha]
                      , ...)

but it complains that I need to have two outputs because I defined two losses:

ValueError: When passing a list as loss, it should have one entry per model outputs. 
The model has 1 outputs, but you passed loss=[<function mse at 0x0000024D7E1FB378>, <function gse at 0x0000024D7E1FB510>]

Can I possibly write my final loss function without having to create another loss function (because that would restrict me from changing the alpha outside the loss function)?

How do I do something like (1-alpha)*mse + alpha*gse?


Update:

Both my loss functions are equivalent to the function signature of any builtin keras loss function, takes in y_true and y_pred and gives a tensor back for loss (which can be reduced to a scalar using K.mean()), but I believe, how these loss functions are defined shouldn’t affect the answer as long as they return valid losses.

def gse(y_true, y_pred):
    # some tensor operation on y_pred and y_true
    return K.mean(K.square(y_pred - y_true), axis=-1)

Answers:

loss function should be one function.You are giving your model a list of two functions

try:

def mse(y_true, y_pred):
    return K.mean(K.square(y_pred - y_true), axis=-1)

model.compile(loss= (mse(y_true, y_pred)*(1-alpha) + gse(y_true, y_pred)*alpha),
              , ...)
Answered By: Ioannis Nasios

Specify a custom function for the loss:

model = Model(inputs=image, outputs=output)

alpha = 0.2
model.compile(
    loss=lambda y_true, y_pred: (1 - alpha) * mse(y_true, y_pred) + alpha * gse(y_true, y_pred),
    ...)

Or if you don’t want an ugly lambda make it into an actual function:

def my_loss(y_true, y_pred):
    return (1 - alpha) * mse(y_true, y_pred) + alpha * gse(y_true, y_pred)

model = Model(inputs=image, outputs=output)

alpha = 0.2
model.compile(loss=my_loss, ...)

EDIT:

If your alpha is not some global constant, you can have a “loss function factory”:

def make_my_loss(alpha):
    def my_loss(y_true, y_pred):
        return (1 - alpha) * mse(y_true, y_pred) + alpha * gse(y_true, y_pred)
    return my_loss

model = Model(inputs=image, outputs=output)

alpha = 0.2
my_loss = make_my_loss(alpha)
model.compile(loss=my_loss, ...)
Answered By: jdehesa

Yes, define your own custom loss function and pass that to the loss argument upon compiling:

def custom_loss(y_true, y_pred):
    return (1-alpha) * K.mean(K.square(y_true-y_pred)) + alpha * gse

(Not sure what you mean with gse). It can be helpful to have a look at how the vanilla losses are implemented in Keras: https://github.com/keras-team/keras/blob/master/keras/losses.py

Answered By: sdcbr

Not that this answer particularly addresses the original question, I thought of writing it because the same error occurs when trying to load a keras model that has a custom loss using keras.models.load_model, and it’s not been properly answered anywhere. Specifically, following the VAE example code in keras github repository, this error occurs when loading the VAE model after been saved with model.save.

The solution is to save only the weights using vae.save_weights('file.h5') instead of saving the full model. However, you would have to build and compile the model again before loading the weights using vae.load_weights('file.h5').

Following is an example implementation.

class VAE():
    def build_model(self): # latent_dim and intermediate_dim can be passed as arguments
        def sampling(args):
            """Reparameterization trick by sampling from an isotropic unit Gaussian.
            # Arguments
                args (tensor): mean and log of variance of Q(z|X)
            # Returns
                z (tensor): sampled latent vector
            """

            z_mean, z_log_var = args
            batch = K.shape(z_mean)[0]
            dim = K.int_shape(z_mean)[1]
            # by default, random_normal has mean = 0 and std = 1.0
            epsilon = K.random_normal(shape=(batch, dim))
            return z_mean + K.exp(0.5 * z_log_var) * epsilon

        # original_dim = self.no_features
        # intermediate_dim = 256
        latent_dim = 8
        inputs = Input(shape=(self.no_features,))
        x = Dense(256, activation='relu')(inputs)
        x = Dense(128, activation='relu')(x)
        x = Dense(64, activation='relu')(x)
        z_mean = Dense(latent_dim, name='z_mean')(x)
        z_log_var = Dense(latent_dim, name='z_log_var')(x)
        # use reparameterization trick to push the sampling out as input
        # note that "output_shape" isn't necessary with the TensorFlow backend
        z = Lambda(sampling, output_shape=(latent_dim,), name='z')([z_mean, z_log_var])
        # instantiate encoder model
        encoder = Model(inputs, [z_mean, z_log_var, z], name='encoder')


        # build decoder model
        latent_inputs = Input(shape=(latent_dim,), name='z_sampling')
        x = Dense(32, activation='relu')(latent_inputs)
        x = Dense(48, activation='relu')(x)
        x = Dense(64, activation='relu')(x)
        outputs = Dense(self.no_features, activation='linear')(x)

        # instantiate decoder model
        decoder = Model(latent_inputs, outputs, name='decoder')

        # instantiate VAE model
        outputs = decoder(encoder(inputs)[2])
        VAE = Model(inputs, outputs, name='vae_mlp')
        reconstruction_loss = mse(inputs, outputs)
        reconstruction_loss *= self.no_features
        kl_loss = 1 + z_log_var - K.square(z_mean) - K.exp(z_log_var)
        kl_loss = K.sum(kl_loss, axis=-1)
        kl_loss *= -0.5
        vae_loss = K.mean(reconstruction_loss + kl_loss)
        VAE.add_loss(vae_loss)
        VAE.compile(optimizer='adam')
        return VAE

Now,

vae_cls = VAE()
vae = vae_cls.build_model()
# vae.fit()
vae.save_weights('file.h5')

Load model and predict (if in a different script, you need to import the VAE class),

vae_cls = VAE()
vae = vae_cls.build_model()
vae.load_weights('file.h5')
# vae.predict()

Finally, The Difference: [ref]

Keras model.save saves,

  1. Model weights
  2. Model architecture
  3. Model compilation details (loss function(s) and metrics)
  4. Model optimizer and regularizer states

Keras model.save_weights saves only the model weights. Keras model.to_json() saves the model architecture.

Hope this helps someone experimenting with variational autoencoders.

Answered By: Achintha Ihalage

Combine MAE and RMSE together:

import tensorflow as tf
from tensorflow import keras

def loss_fn_mae_rmse(y_true, y_pred, alpha=0.8):
    mae = keras.losses.MeanAbsoluteError()
    mse = keras.losses.MeanSquaredError()
    return alpha * mae(y_true, y_pred) + (1 - alpha) * tf.sqrt(mse(y_true, y_pred))

model = keras.Model(inputs=..., outputs=...)
opt = keras.optimizers.Adam(learning_rate=1e-4)
model.compile(optimizer=opt, loss=loss_fn_mae_rmse, metrics=['mae'])

At the same time, if you want to load this model after training and saved to disk:

model = keras.models.load_model('path/to/model.h5', custom_objects={'loss_fn_mae_rmse': loss_fn_mae_rmse})
Answered By: Belter