在没有 MinDiffModel 的情况下集成 MinDiff

介绍

可以将 MinDiff 直接集成到模型的实现中。虽然这样做没有使用 MinDiffModel 的便利性,但此选项提供了最高级别的控制,这在您的模型是 tf.keras.Model 的子类时特别有用。

本指南演示了如何通过向 train_step 方法添加内容,将 MinDiff 直接集成到自定义模型的实现中。

设置

pip install --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

首先,下载数据。为了简洁起见,输入准备逻辑已分解为辅助函数,如 输入准备指南 中所述。您可以阅读完整指南以了解此过程的详细信息。

# 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))

原始自定义模型自定义

tf.keras.Model 旨在通过子类化轻松自定义。这通常涉及更改对 fit 的调用,如 此处 所述。

本指南使用自定义实现,其中 train_step 非常类似于默认的 tf.keras.Model.train_step。通常,这样做没有好处,但在这里,它将有助于演示如何集成 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}

使用函数式 API 像训练典型的 Model 一样训练模型。

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

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

_ = model.fit(train_ds, epochs=1)

将 MinDiff 直接集成到您的模型中

将 MinDiff 添加到 train_step

要集成 MinDiff,您需要向 CustomModel 添加一些行,这里将其重命名为 CustomModelWithMinDiff

为了清晰起见,本指南使用一个名为 apply_min_diff 的布尔标志。与 MinDiff 相关的代码仅在将其设置为 True 时才会运行。如果将其设置为 False,则模型的行为将与 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}

使用此模型进行训练与之前完全相同,只是使用的 dataset 不同。

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)

重新整形您的输入(可选)

鉴于这种方法提供了完全的控制,您可以借此机会将输入重新整形为更简洁的形式。使用 MinDiffModel 时,min_diff_data 需要打包到每个批次的第一个组件中。对于 train_with_min_diff_ds dataset 来说也是如此。

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)

随着此要求的解除,您可以以更直观的方式重新组织数据,将原始数据和 MinDiff 数据清晰地分开。

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)

此步骤完全可选,但可以帮助更好地组织数据。如果您这样做,在实现 CustomModelWithMinDiff 时,唯一的区别在于您在开头解包 data 的方式。

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)

    ...

通过最后一步,您可以完全控制输入格式以及如何在模型中使用它来应用 MinDiff。

其他资源

  • 有关公平性评估的深入讨论,请参阅 公平性指标指南
  • 有关修复和 MinDiff 的一般信息,请参阅 修复概述
  • 有关 MinDiff 周围要求的详细信息,请参阅 本指南
  • 要查看有关在 Keras 中使用 MinDiff 的端到端教程,请参阅 本教程