Search code examples
pythonfunctiontensorflowkeraslstm

How to use multiple loss functions on module.compile and keep mse?


Whats the correct and optimized way to use multiple loss functions with Python and Keras?

I'm playing with this article:

https://medium.com/@polanitzer/predicting-the-israeli-lottery-results-for-the-november-29-2022-game-using-an-artificial-191489eb2c10

The author use the standard 'mse' as loss, and I wanna add some more custom functions, those functions they do not have mathematical accuracy, but are the result of observations only.

Let's say I have 5 situations:

  1. Previous results will not happen again
  2. The sum of results need be between V and W
  3. From an game numbered from 00 to 99 I wanna limit the predicted numbers from 00 to 49 occurency times in X (numbers in this range cannot exceed X times)
  4. From an game numbered from 00 to 99 I wanna limit the predicted numbers from 50 to 99 occurency times in Y (numbers in this range cannot exceed Y)
  5. I wanna keep the default mse function

My approach started from:

Situation 1

def fn_never_repeat(y_true, y_pred, previous_data):
    # Compute the difference score between the predicted outputs and the previous data
    diff = K.mean(K.square(y_pred - previous_data), axis=-1)

    # Return the weighted sum of the difference scores
    return diff

Situation 2

def fn_sum_values(y_true, y_pred):
    # Calculate the sum of the predicted numbers
    predicted_sum = K.sum(y_pred)
    
    # Set the minimum value to 133 and maximum value to 249
    X = 133
    Y = 249
    
    # Calculate the loss based on the deviation from the desired range (X, Y)
    loss = K.maximum(X - predicted_sum, 0) + K.maximum(predicted_sum - Y, 0)
    
    return loss

Situation 3 and 4 (Using 10 as example for both quadrant)

def fn_quadrant(y_true, y_pred):
    count_0_to_49 = K.sum(K.cast(K.less(y_pred, 50), 'float'))
    count_50_to_99 = K.sum(K.cast(K.greater_equal(y_pred, 49), 'float'))

    penalty = 0
    if count_0_to_49 > 10:
        penalty += K.square(count_0_to_49 - 10)
    if count_50_to_99 > 10:
        penalty += K.square(count_50_to_99 - 10)

    return K.mean(K.square(y_true - y_pred)) + penalty

Situation 5

def fn_combined_loss(y_true, y_pred):
    fn_never_repeat = fn_never_repeat(y_true, y_pred, previous_data)
    fn_sum_values = fn_sum_values(y_true, y_pred)
    fn_quadrant = fn_quadrant(y_true, y_pred)
    mse = K.mean(K.square(y_true - y_pred))
    return  0.25 * fn_never_repeat + 0.25 * fn_sum_values + 0.25 * fn_quadrant + 0.25 * mse

Calling it with:

model.compile(optimizer=Adam(learning_rate=0.0001), loss='fn_combined_loss', metrics=['accuracy'], custom_objects={'fn_combined_loss': fn_combined_loss})

And error occours after:

model.fit(x=x_train, y=y_train, batch_size=number_of_batch, epochs=Nepochs, verbose=1, callbacks=[callbacks_list])

I'm stuck on this error:

ValueError: Unknown loss function: combined_loss_fn. Please ensure this object is passed to the custom_objects argument. See https://www.tensorflow.org/guide/keras/save_and_serialize#registering_the_custom_object for details.

The paremeters from lowest times or X times something can happen or cant exceed will come from stored procedure in database. And since the value is provided based on registered results, although it is not an absolute mathematical statistic, it will be dynamically informed.

I'm not sure if functions are correctly, since it is not compiling yet, you guys can point any mistaken if you seen something wrong too.

Thanks in advice!!!


Solution

  • With these few changes I think your training should work

    1. Compile your model with the actual loss function, not as string
    model.compile(optimizer=Adam(learning_rate=0.0001),loss=fn_combined_loss,metrics=['accuracy'])
    
    1. In the combined loss rename variables, do not use the same name as the loss functions have
    def fn_combined_loss(y_true, y_pred):
        fn_never_repeat_ = fn_never_repeat(y_true, y_pred)
        fn_sum_values_ = fn_sum_values(y_true, y_pred)
        fn_quadrant_ = fn_quadrant(y_true, y_pred)
        mse = K.mean(K.square(y_true - y_pred))
        return  0.25 * fn_never_repeat_ + 0.25 * fn_sum_values_ + 0.25 * fn_quadrant_ + 0.25 * mse
    
    1. In fn_quadrant change the constants to float
    def fn_quadrant(y_true, y_pred):
        count_0_to_49 = K.sum(K.cast(K.less(y_pred, 50), 'float'))
        count_50_to_99 = K.sum(K.cast(K.greater_equal(y_pred, 49), 'float'))
        penalty = 0.
        if count_0_to_49 > 10:
            penalty += K.square(count_0_to_49 - 10.)
        if count_50_to_99 > 10:
            penalty += K.square(count_50_to_99 - 10.)
        return K.mean(K.square(y_true - y_pred)) + penalty