整合不使用 MinDiffModel 的 MinDiff

簡介

可以直接將 MinDiff 整合到模型的實作中。雖然這樣做不如使用 MinDiffModel 方便,但此選項提供最高層級的控制,當您的模型是 tf.keras.Model 的子類別時,這會特別有用。

本指南示範如何將 MinDiff 直接整合到自訂模型的實作中,方法是新增至 train_step 方法。

設定

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}

使用 Functional 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}

使用此模型進行訓練看起來與先前的訓練完全相同,但使用的資料集除外。

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 資料集時就是這種情況。

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 的端對端教學課程,請參閱本教學課程