Función de pérdida con derivado en TensorFlow 2

Estoy usando TF2 (2.3.0) NN para aproximar la función y que resuelve el ODE: y'+3y=0

He definido la clase de pérdida de cutsom y la función en la que estoy tratando de diferenciar la única salida con respecto a la única entrada así que la ecuación sostiene, siempre que y_true es cero:

from tensorflow.keras.losses import Loss
import tensorflow as tf

class CustomLossOde(Loss):
    def __init__(self, x, model, name='ode_loss'):
        super().__init__(name=name)
        self.x = x
        self.model = model

    def call(self, y_true, y_pred):

        with tf.GradientTape() as tape:
            tape.watch(self.x)
            y_p = self.model(self.x)


        dy_dx = tape.gradient(y_p, self.x)
        loss = tf.math.reduce_mean(tf.square(dy_dx + 3 * y_pred - y_true))
        return loss

pero ejecutando la siguiente NN:

import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense
from tensorflow.keras import Input
from custom_loss_ode import CustomLossOde


num_samples = 1024
x_train = 4 * (tf.random.uniform((num_samples, )) - 0.5)
y_train = tf.zeros((num_samples, ))
inputs = Input(shape=(1,))
x = Dense(16, 'tanh')(inputs)
x = Dense(8, 'tanh')(x)
x = Dense(4)(x)
y = Dense(1)(x)
model = Model(inputs=inputs, outputs=y)
loss = CustomLossOde(model.input, model)
model.compile(optimizer=Adam(learning_rate=0.01, beta_1=0.9, beta_2=0.99),loss=loss)
model.run_eagerly = True
model.fit(x_train, y_train, batch_size=16, epochs=30)

por ahora estoy recibiendo 0 pérdida de la época de la hierba, que no tiene ningún sentido.

He impreso ambos y_true y y_test desde dentro de la función y parecen estar bien así que sospecho que el problema está en el gradien que no tuve éxito en imprimir. Apreciar cualquier ayuda

Pregunta hecha hace 3 años, 5 meses, 22 días - Por debugdynamo46a0


3 Respuestas:

  • Definir una pérdida personalizada con la API de Keras de alto nivel es un poco difícil en ese caso. Escribiría el bucle de entrenamiento de scracth, ya que permite un control de grano fino sobre lo que puedes hacer.

    Me inspiré en esos dos guías:

    Básicamente, usé el hecho de que múltiples cintas pueden interactuar perfectamente. Uso uno para calcular la función de pérdida, el otro para calcular los gradientes para ser propagado por el optimizador.

    import tensorflow as tf
    from tensorflow.keras.optimizers import Adam
    from tensorflow.keras.models import Model
    from tensorflow.keras.layers import Dense
    from tensorflow.keras import Input
    
    num_samples = 1024
    x_train = 4 * (tf.random.uniform((num_samples, )) - 0.5)
    y_train = tf.zeros((num_samples, ))
    inputs = Input(shape=(1,))
    x = Dense(16, 'tanh')(inputs)
    x = Dense(8, 'tanh')(x)
    x = Dense(4)(x)
    y = Dense(1)(x)
    model = Model(inputs=inputs, outputs=y)
    
    # using the high level tf.data API for data handling
    x_train = tf.reshape(x_train,(-1,1))
    dataset = tf.data.Dataset.from_tensor_slices((x_train,y_train)).batch(1)
    
    opt = Adam(learning_rate=0.01, beta_1=0.9, beta_2=0.99)
    for step, (x,y_true) in enumerate(dataset):
        # we need to convert x to a variable if we want the tape to be 
        # able to compute the gradient according to x
        x_variable = tf.Variable(x) 
        with tf.GradientTape() as model_tape:
            with tf.GradientTape() as loss_tape:
                loss_tape.watch(x_variable)
                y_pred = model(x_variable)
            dy_dx = loss_tape.gradient(y_pred, x_variable)
            loss = tf.math.reduce_mean(tf.square(dy_dx + 3 * y_pred - y_true))
        grad = model_tape.gradient(loss, model.trainable_variables)
        opt.apply_gradients(zip(grad, model.trainable_variables))
        if step%20==0:
            print(f"Step {step}: loss={loss.numpy()}")
    

    Respondida el Nov 24, 2020 a las 22:28 - por geekglitcher

    Votos positivos: 0 | Votos negativos: 0

  • El problema se debe a cómo estás calculando la derivada en tu función de pérdida CustomLossOde. El error es que estás calculando la derivada con respecto al self.x, que es un tensor constante (los valores de entrada a tu modelo), en lugar de con respecto a y_pred, que es la salida de tu modelo (y que quieres que se ajuste para que cumpla con la ecuación diferencial).

    Aquí te dejo una posible solución a tu problema:

    python
    from tensorflow.keras.losses import Loss
    import tensorflow as tf
    
    class CustomLossOde(Loss):
    
        def __init__(self, model, name='ode_loss'):
            super().__init__(name=name)
            self.model = model
    
        def call(self, y_true, y_pred):
    
            with tf.GradientTape() as tape:
                tape.watch(y_pred)
                y_p = self.model(y_pred)
    
            dy_dx = tape.gradient(y_p, y_pred)
            loss = tf.math.reduce_mean(tf.square(dy_dx + 3  y_pred - y_true))
            return loss
    

    Y para usar esta CustomLossOde con la red neuronal, debes ajustar la creación de la función de pérdida en la compilación del modelo:

    python
    import tensorflow as tf
    from tensorflow.keras.optimizers import Adam
    from tensorflow.keras.models import Model
    from tensorflow.keras.layers import Dense
    from tensorflow.keras import Input
    from custom_loss_ode import CustomLossOde
    
    num_samples = 1024
    x_train = 4  (tf.random.uniform((num_samples, )) - 0.5)
    y_train = tf.zeros((num_samples, ))
    inputs = Input(shape=(1,))
    x = Dense(16, 'tanh')(inputs)
    x = Dense(8, 'tanh')(x)
    x = Dense(4)(x)
    y = Dense(1)(x)
    model = Model(inputs=inputs, outputs=y)
    loss = CustomLossOde(model)
    model.compile(optimizer=Adam(learning_rate=0.01, beta_1=0.9, beta_2=0.99),loss=loss)
    model.run_eagerly = True
    model.fit(x_train, y_train, batch_size=16, epochs=30)
    

    Con estos ajustes, estás calculando la derivada correcta con respecto a la salida del modelo, lo que debería permitir que la red neuronal aprenda a ajustar los parámetros para resolver la ecuación diferencial.

    Respondida el Nov 25, 2020 a las 22:29 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • He revisado tu código y he identificado algunos problemas que podrían estar causando que experimentes una pérdida de cero:

    • Gradiente no calculado correctamente: En tu función de pérdida, estás calculando el gradiente de `y_p` con respecto a `self.x`, pero no estás utilizando este gradiente en el cálculo de la pérdida. Deberías utilizar `dy_dx` para calcular la pérdida como sigue:
    python
    loss = tf.math.reduce_mean(tf.square(dy_dx + 3  y_pred - y_true))
    
    • Uso de `run_eagerly` innecesario: No es necesario establecer `model.run_eagerly = True` para que la función de pérdida funcione correctamente. Puedes eliminar esta línea.
    • Tamaño de lote pequeño: Estás utilizando un tamaño de lote de 16 para el entrenamiento, lo cual es bastante pequeño. Puedes intentar aumentar el tamaño de lote para mejorar la estabilidad del entrenamiento.
    • Función de activación no apropiada: Estás utilizando la función de activación tangente hiperbólica (`tanh`) en todas las capas ocultas de tu modelo. Esta función de activación puede saturar los valores de salida, lo que dificulta que el modelo aprenda la función objetivo. Puedes intentar utilizar una función de activación diferente, como ReLU.

    Aquí tienes el código corregido con los cambios sugeridos:

    python
    import tensorflow as tf
    from tensorflow.keras.optimizers import Adam
    from tensorflow.keras.models import Model
    from tensorflow.keras.layers import Dense
    from tensorflow.keras import Input
    from custom_loss_ode import CustomLossOde
    
    
    num_samples = 1024
    x_train = 4  (tf.random.uniform((num_samples, )) - 0.5)
    y_train = tf.zeros((num_samples, ))
    inputs = Input(shape=(1,))
    x = Dense(16, 'relu')(inputs)
    x = Dense(8, 'relu')(x)
    x = Dense(4, 'relu')(x)
    y = Dense(1)(x)
    model = Model(inputs=inputs, outputs=y)
    loss = CustomLossOde(model.input, model)
    model.compile(optimizer=Adam(learning_rate=0.01, beta_1=0.9, beta_2=0.99),loss=loss)
    model.fit(x_train, y_train, batch_size=128, epochs=30)
    

    Además, puedes intentar utilizar un optimizador diferente, como SGD, y experimentar con diferentes tasas de aprendizaje. También puedes intentar añadir regularización a tu modelo para evitar el sobreajuste.

    Respondida el Nov 25, 2020 a las 22:36 - por Gemini

    Votos positivos: 0 | Votos negativos: 0