Integración de MinDiff sin MinDiffModel

Introducción

Es posible integrar MinDiff directamente en la implementación de su modelo. Al hacerlo no tiene la conveniencia de utilizar MinDiffModel , esta opción ofrece el más alto nivel de control que puede ser particularmente útil cuando el modelo es una subclase de tf.keras.Model .

Esta guía muestra cómo se puede integrar directamente en MinDiff aplicación de un modelo personalizado añadiendo a la train_step método.

Configuración

pip install -q --upgrade tensorflow-model-remediation
import tensorflow as tf
tf.get_logger().setLevel('ERROR')  # Avoid TF warnings.
from tensorflow_model_remediation import min_diff
from tensorflow_model_remediation.tools.tutorials_utils import uci as tutorials_utils

Primero, descargue los datos. Por concisión, la lógica de preparación de entrada ha sido factorizada hacia helper funciones como se describe en la guía de preparación de entrada . Puede leer la guía completa para obtener detalles sobre este proceso.

# Original Dataset for training, sampled at 0.3 for reduced runtimes.
train_df = tutorials_utils.get_uci_data(split='train', sample=0.3)
train_ds = tutorials_utils.df_to_dataset(train_df, batch_size=128)

# Dataset needed to train with MinDiff.
train_with_min_diff_ds = (
    tutorials_utils.get_uci_with_min_diff_dataset(split='train', sample=0.3))

Personalizaciones del modelo personalizado original

tf.keras.Model está diseñado para personalizar fácilmente a través de la subclasificación. Esto generalmente implica cambiar lo que sucede en la llamada a fit como se describe aquí .

Esta guía utiliza una implementación personalizada donde el train_step parece mucho a la forma predeterminada tf.keras.Model.train_step . Normalmente, no habría ningún beneficio al hacerlo, pero aquí, ayudará a demostrar cómo integrar MinDiff.

class CustomModel(tf.keras.Model):

  def train_step(self, data):
    # Unpack the data.
    x, y, sample_weight = tf.keras.utils.unpack_x_y_sample_weight(data)

    with tf.GradientTape() as tape:
      y_pred = self(x, training=True)  # Forward pass.
      loss = self.compiled_loss(
          y, y_pred, sample_weight, regularization_losses=self.losses)
      # Compute the loss value.
      loss = self.compiled_loss(
          y, y_pred, sample_weight, regularization_losses=self.losses)

    # Compute gradients and update weights.
    self.optimizer.minimize(loss, self.trainable_variables, tape=tape)
    # Update and return metrics.
    self.compiled_metrics.update_state(y, y_pred, sample_weight)
    return {m.name: m.result() for m in self.metrics}

Entrenar el modelo como lo haría un típico Model uso de la API funcional.

model = tutorials_utils.get_uci_model(model_class=CustomModel)  # Use CustomModel.

model.compile(optimizer='adam', loss='binary_crossentropy')

_ = model.fit(train_ds, epochs=1)
77/77 [==============================] - 3s 22ms/step - loss: 0.7273

Integrando MinDiff directamente en su modelo

Adición de MinDiff a la train_step

Para integrar MinDiff, tendrá que añadir algunas líneas a la CustomModel que se renombró aquí como CustomModelWithMinDiff .

Para mayor claridad, esta guía utiliza una bandera booleana llamada apply_min_diff . Todo el código correspondiente a MinDiff sólo se ejecutan si se establece en True . Si se establece en False entonces el modelo se comportaría exactamente el mismo que CustomModel .

min_diff_loss_fn = min_diff.losses.MMDLoss()  # Hard coded for convenience.
min_diff_weight = 2  # Arbitrary number for example, hard coded for convenience.
apply_min_diff = True  # Flag to help show where the additional lines are.

class CustomModelWithMinDiff(tf.keras.Model):

  def train_step(self, data):
    # Unpack the data.
    x, y, sample_weight = tf.keras.utils.unpack_x_y_sample_weight(data)

    # Unpack the MinDiff data.
    if apply_min_diff:
      min_diff_data = min_diff.keras.utils.unpack_min_diff_data(x)
      min_diff_x, membership, min_diff_sample_weight = (
          tf.keras.utils.unpack_x_y_sample_weight(min_diff_data))
      x = min_diff.keras.utils.unpack_original_inputs(x)

    with tf.GradientTape() as tape:
      y_pred = self(x, training=True)  # Forward pass.
      loss = self.compiled_loss(
          y, y_pred, sample_weight, regularization_losses=self.losses)
      # Compute the loss value.
      loss = self.compiled_loss(
          y, y_pred, sample_weight, regularization_losses=self.losses)

      # Calculate and add the min_diff_loss. This must be done within the scope
      # of tf.GradientTape().
      if apply_min_diff:
        min_diff_predictions = self(min_diff_x, training=True)
        min_diff_loss = min_diff_weight * min_diff_loss_fn(
            min_diff_predictions, membership, min_diff_sample_weight)
        loss += min_diff_loss

    # Compute gradients and update weights.
    self.optimizer.minimize(loss, self.trainable_variables, tape=tape)
    # Update and return metrics.
    self.compiled_metrics.update_state(y, y_pred, sample_weight)
    return {m.name: m.result() for m in self.metrics}

El entrenamiento con este modelo se ve exactamente igual que con el anterior, con la excepción del conjunto de datos utilizado.

model = tutorials_utils.get_uci_model(model_class=CustomModelWithMinDiff)

model.compile(optimizer='adam', loss='binary_crossentropy')

_ = model.fit(train_with_min_diff_ds, epochs=1)
77/77 [==============================] - 4s 30ms/step - loss: 0.7799

Remodelación de su entrada (opcional)

Dado que este enfoque proporciona un control total, puede aprovechar esta oportunidad para remodelar la entrada en una forma un poco más limpia. Cuando se utiliza MinDiffModel , la min_diff_data se debe estar lleno en el primer componente de cada lote. Este es el caso de la train_with_min_diff_ds conjunto de datos.

for x, y in train_with_min_diff_ds.take(1):
  print('Type of x:', type(x))  # MinDiffPackedInputs
  print('Type of y:', type(y))  # Tensor (original labels)
Type of x: <class 'tensorflow_model_remediation.min_diff.keras.utils.input_utils.MinDiffPackedInputs'>
Type of y: <class 'tensorflow.python.framework.ops.EagerTensor'>

Con este requisito eliminado, puede reorganizar los datos en una estructura un poco más intuitiva con los datos originales y MinDiff claramente separados.

def _reformat_input(inputs, original_labels):
  min_diff_data = min_diff.keras.utils.unpack_min_diff_data(inputs)
  original_inputs = min_diff.keras.utils.unpack_original_inputs(inputs)
  original_data = (original_inputs, original_labels)

  return {
      'min_diff_data': min_diff_data,
      'original_data': original_data}

customized_train_with_min_diff_ds = train_with_min_diff_ds.map(_reformat_input)

Este paso es completamente opcional pero puede ser útil para organizar mejor los datos. Si lo hace, la única diferencia en cómo se implementa CustomModelWithMinDiff será cómo desembalar data al principio.

class CustomModelWithMinDiff(tf.keras.Model):

  def train_step(self, data):
    # Unpack the MinDiff data from the custom structure.
    if apply_min_diff:
      min_diff_data = data['min_diff_data']
      min_diff_x, membership, min_diff_sample_weight = (
          tf.keras.utils.unpack_x_y_sample_weight(min_diff_data))
      data = data['original_data']

    ... # possible preprocessing or validation on data before unpacking.

    x, y, sample_weight = tf.keras.utils.unpack_x_y_sample_weight(data)

    ...

Con este último paso, puede controlar completamente tanto el formato de entrada como cómo se usa dentro del modelo para aplicar MinDiff.

Recursos adicionales