模型修复案例研究

在本笔记本中,我们将训练一个文本分类器来识别可能被视为有毒或有害的书面内容,并应用 MinDiff 来修复一些公平性问题。在我们的工作流程中,我们将

  1. 评估我们的基线模型在包含对敏感群体引用的文本上的性能。
  2. 通过使用 MinDiff 训练来提高任何表现不佳的群体的性能。
  3. 评估新模型在我们选择的指标上的性能。

我们的目的是演示使用 MinDiff 技术的非常小的工作流程,而不是制定机器学习中公平性的原则性方法。因此,我们的评估将只关注一个敏感类别和一个指标。我们也不会解决数据集中的潜在缺陷,也不会调整我们的配置。在生产环境中,您需要认真对待这些问题。有关评估公平性的更多信息,请参阅 本指南

设置

我们首先安装 Fairness Indicators 和 TensorFlow Model Remediation。

安装

导入所有必要的组件,包括 MinDiff 和 Fairness Indicators 用于评估。

导入

2024-04-27 09:16:08.097342: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
2024-04-27 09:16:08.097391: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
2024-04-27 09:16:08.098819: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered

我们使用一个实用函数来下载预处理数据并准备标签以匹配模型的输出形状。该函数还将数据下载为 TFRecords,以便以后更快地进行评估。或者,您可以使用任何可用的实用程序转换函数将 Pandas DataFrame 转换为 TFRecords。

# We use a helper utility to preprocessed data for convenience and speed.
data_train, data_validate, validate_tfrecord_file, labels_train, labels_validate = min_diff_keras_utils.download_and_process_civil_comments_data()
Downloading data from https://storage.googleapis.com/civil_comments_dataset/train_df_processed.csv
345699197/345699197 [==============================] - 4s 0us/step
Downloading data from https://storage.googleapis.com/civil_comments_dataset/validate_df_processed.csv
229970098/229970098 [==============================] - 2s 0us/step
Downloading data from https://storage.googleapis.com/civil_comments_dataset/validate_tf_processed.tfrecord
324941336/324941336 [==============================] - 4s 0us/step

我们定义了一些有用的常量。我们将使用 ’comment_text’ 特征训练模型,我们的目标标签是 ’toxicity’。请注意,此处的批次大小是任意选择的,但在生产环境中,您需要对其进行调整以获得最佳性能。

TEXT_FEATURE = 'comment_text'
LABEL = 'toxicity'
BATCH_SIZE = 512

设置随机种子。(请注意,这并不能完全稳定结果。)

种子

定义和训练基线模型

为了减少运行时间,我们默认使用预训练模型。它是一个简单的 Keras 顺序模型,具有初始嵌入和卷积层,输出毒性预测。如果您愿意,您可以更改此模型并使用我们的实用函数从头开始训练模型。(请注意,由于您的环境可能与我们的环境不同,您需要自定义调整和评估阈值。)

use_pretrained_model = True

if use_pretrained_model:
  URL = 'https://storage.googleapis.com/civil_comments_model/baseline_model.zip'
  BASE_PATH = tempfile.mkdtemp()
  ZIP_PATH = os.path.join(BASE_PATH, 'baseline_model.zip')
  MODEL_PATH = os.path.join(BASE_PATH, 'tmp/baseline_model')

  r = requests.get(URL, allow_redirects=True)
  open(ZIP_PATH, 'wb').write(r.content)

  with zipfile.ZipFile(ZIP_PATH, 'r') as zip_ref:
    zip_ref.extractall(BASE_PATH)
  baseline_model = tf.keras.models.load_model(
      MODEL_PATH, custom_objects={'KerasLayer' : hub.KerasLayer})
else:
  optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
  loss = tf.keras.losses.BinaryCrossentropy()

  baseline_model = min_diff_keras_utils.create_keras_sequential_model()

  baseline_model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])

  baseline_model.fit(x=data_train[TEXT_FEATURE],
                     y=labels_train,
                     batch_size=BATCH_SIZE,
                     epochs=20)

我们保存模型以便使用 Fairness Indicators 进行评估。

base_dir = tempfile.mkdtemp(prefix='saved_models')
baseline_model_location = os.path.join(base_dir, 'model_export_baseline')
baseline_model.save(baseline_model_location, save_format='tf')
INFO:tensorflow:Assets written to: /tmpfs/tmp/saved_models6z9x_9qz/model_export_baseline/assets
INFO:tensorflow:Assets written to: /tmpfs/tmp/saved_models6z9x_9qz/model_export_baseline/assets

接下来,我们运行 Fairness Indicators。提醒一下,我们只针对引用一个类别的评论进行切片评估,即宗教团体。在生产环境中,我们建议采用深思熟虑的方法来确定要评估哪些类别和指标。

为了计算模型性能,实用函数对指标、切片和分类器阈值做了一些方便的选择。

# We use a helper utility to hide the evaluation logic for readability.
base_dir = tempfile.mkdtemp(prefix='eval')
eval_dir = os.path.join(base_dir, 'tfma_eval_result')
eval_result = fi_util.get_eval_results(
    baseline_model_location, eval_dir, validate_tfrecord_file)
WARNING:absl:Tensorflow version (2.15.1) found. Note that TFMA support for TF 2.0 is currently in beta
WARNING:apache_beam.runners.interactive.interactive_environment:Dependencies required for Interactive Beam PCollection visualization are not available, please use: `pip install apache-beam[interactive]` to install necessary dependencies to enable all data visualization features.
WARNING:apache_beam.io.tfrecordio:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow_model_analysis/writers/metrics_plots_and_validations_writer.py:112: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow_model_analysis/writers/metrics_plots_and_validations_writer.py:112: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

呈现评估结果

widget_view.render_fairness_indicator(eval_result)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Overall', 'slice': 'Overall', 'metrics': {'accuracy': …

让我们看一下评估结果。尝试选择指标假阳性率 (FPR),阈值为 0.450。我们可以看到,该模型在某些宗教群体上的表现不如其他群体,显示出更高的 FPR。请注意,某些群体的置信区间很宽,因为它们的示例太少。这使得很难确定这些切片之间的性能是否存在显著差异。我们可能需要收集更多示例来解决此问题。但是,我们可以尝试针对我们确信表现不佳的两个群体应用 MinDiff。

我们选择关注 FPR,因为更高的 FPR 意味着引用这些身份群体的评论比其他评论更有可能被错误地标记为有毒。这可能会导致参与宗教对话的用户遭受不公平的结果,但请注意,其他指标的差异可能会导致其他类型的伤害。

定义和训练 MinDiff 模型

现在,我们将尝试提高表现不佳的宗教群体的 FPR。我们将尝试使用 MinDiff 来实现这一点,这是一种修复技术,它试图通过在训练期间对性能差异进行惩罚来平衡数据切片之间的错误率。当我们应用 MinDiff 时,模型性能可能会在其他切片上略微下降。因此,我们使用 MinDiff 的目标将是

  • 提高表现不佳的群体的性能
  • 限制其他群体和整体性能的下降

准备您的数据

为了使用 MinDiff,我们创建了两个额外的數據拆分

  • 针对非有毒示例的拆分,这些示例引用了少数群体:在本例中,这将包括包含对我们表现不佳的身份术语的引用的评论。我们不包含某些群体,因为示例太少,导致置信区间范围更广,不确定性更高。
  • 针对非有毒示例的拆分,这些示例引用了多数群体。

拥有足够属于表现不佳类别的示例非常重要。根据您的模型架构、数据分布和 MinDiff 配置,所需的数据量可能会有很大差异。在过去的应用中,我们发现 MinDiff 在每个数据拆分中使用 5,000 个示例时效果很好。

在本例中,少数群体拆分中的群体具有 9,688 和 3,906 个示例数量。请注意数据集中的类别不平衡;在实践中,这可能令人担忧,但我们不会在本笔记本中解决它们,因为我们的目的是演示 MinDiff。

我们只为这些群体选择负面示例,以便 MinDiff 可以优化这些示例的正确性。如果您主要关注的是 *误报率* 的差异,那么专门提取一组真实 *负面* 示例似乎违反直觉,但请记住,误报预测是错误分类为正面的真实负面示例,这是我们试图解决的问题。

创建 MinDiff 数据帧

# Create masks for the sensitive and nonsensitive groups
minority_mask = data_train.religion.apply(
    lambda x: any(religion in x for religion in ('jewish', 'muslim')))
majority_mask = data_train.religion.apply(lambda x: x == "['christian']")

# Select nontoxic examples, so MinDiff will be able to reduce sensitive FP rate.
true_negative_mask = data_train['toxicity'] == 0

data_train_main = copy.copy(data_train)
data_train_sensitive = data_train[minority_mask & true_negative_mask]
data_train_nonsensitive = data_train[majority_mask & true_negative_mask]

我们还需要将 Pandas 数据帧转换为 Tensorflow 数据集,以用于 MinDiff 输入。请注意,与 Pandas 数据帧的 Keras 模型 API 不同,使用数据集意味着我们需要在同一个数据集中提供模型的输入特征和标签。在这里,我们提供 'comment_text' 作为输入特征,并将标签重塑以匹配模型的预期输出。

我们也在此阶段对数据集进行批处理,因为 MinDiff 需要批处理数据集。请注意,我们以与调整基线模型相同的方式调整批次大小选择,同时考虑训练速度和硬件因素,并平衡模型性能。在这里,我们为所有三个数据集选择了相同的批次大小,但这并不是必需的,尽管最好让两个 MinDiff 批次大小相同。

创建 MinDiff 数据集

# Convert the pandas DataFrames to Datasets.
dataset_train_main = tf.data.Dataset.from_tensor_slices(
    (data_train_main['comment_text'].values, 
     data_train_main.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)
dataset_train_sensitive = tf.data.Dataset.from_tensor_slices(
    (data_train_sensitive['comment_text'].values, 
     data_train_sensitive.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)
dataset_train_nonsensitive = tf.data.Dataset.from_tensor_slices(
    (data_train_nonsensitive['comment_text'].values, 
     data_train_nonsensitive.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)

训练和评估模型

要使用 MinDiff 进行训练,只需将原始模型包装在 MinDiffModel 中,并使用相应的 lossloss_weight。我们使用 1.5 作为默认的 loss_weight,但这是一个需要根据您的用例进行调整的参数,因为它取决于您的模型和产品要求。您可以尝试更改该值以查看它如何影响模型,请注意,增加它会使少数群体和多数群体的性能更接近,但可能会带来更明显的权衡。

然后,我们按照正常方式编译模型(使用常规的非 MinDiff 损失),并进行拟合以进行训练。

训练 MinDiffModel

use_pretrained_model = True

base_dir = tempfile.mkdtemp(prefix='saved_models')
min_diff_model_location = os.path.join(base_dir, 'model_export_min_diff')

if use_pretrained_model:
  BASE_MIN_DIFF_PATH = tempfile.mkdtemp()
  MIN_DIFF_URL = 'https://storage.googleapis.com/civil_comments_model/min_diff_model.zip'
  ZIP_PATH = os.path.join(BASE_PATH, 'min_diff_model.zip')
  MIN_DIFF_MODEL_PATH = os.path.join(BASE_MIN_DIFF_PATH, 'tmp/min_diff_model')
  DIRPATH = '/tmp/min_diff_model'

  r = requests.get(MIN_DIFF_URL, allow_redirects=True)
  open(ZIP_PATH, 'wb').write(r.content)

  with zipfile.ZipFile(ZIP_PATH, 'r') as zip_ref:
    zip_ref.extractall(BASE_MIN_DIFF_PATH)
  min_diff_model = tf.keras.models.load_model(
      MIN_DIFF_MODEL_PATH, custom_objects={'KerasLayer' : hub.KerasLayer})

  min_diff_model.save(min_diff_model_location, save_format='tf')

else:
  min_diff_weight = 1.5

  # Create the dataset that will be passed to the MinDiffModel during training.
  dataset = md.keras.utils.input_utils.pack_min_diff_data(
      dataset_train_main, dataset_train_sensitive, dataset_train_nonsensitive)

  # Create the original model.
  original_model = min_diff_keras_utils.create_keras_sequential_model()

  # Wrap the original model in a MinDiffModel, passing in one of the MinDiff
  # losses and using the set loss_weight.
  min_diff_loss = md.losses.MMDLoss()
  min_diff_model = md.keras.MinDiffModel(original_model,
                                         min_diff_loss,
                                         min_diff_weight)

  # Compile the model normally after wrapping the original model.  Note that
  # this means we use the baseline's model's loss here.
  optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
  loss = tf.keras.losses.BinaryCrossentropy()
  min_diff_model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])

  min_diff_model.fit(dataset, epochs=20)

  min_diff_model.save_original_model(min_diff_model_location, save_format='tf')
INFO:tensorflow:Assets written to: /tmpfs/tmp/saved_modelsfzu9o9iw/model_export_min_diff/assets
INFO:tensorflow:Assets written to: /tmpfs/tmp/saved_modelsfzu9o9iw/model_export_min_diff/assets

接下来,我们评估结果。

min_diff_eval_subdir = os.path.join(base_dir, 'tfma_eval_result')
min_diff_eval_result = fi_util.get_eval_results(
    min_diff_model_location,
    min_diff_eval_subdir,
    validate_tfrecord_file,
    slice_selection='religion')
WARNING:absl:Tensorflow version (2.15.1) found. Note that TFMA support for TF 2.0 is currently in beta

为了确保我们正确评估新模型,我们需要以与评估基线模型相同的方式选择阈值。在生产环境中,这意味着确保评估指标满足发布标准。在本例中,我们将选择导致与基线模型类似的整体 FPR 的阈值。此阈值可能与您为基线模型选择的阈值不同。尝试选择阈值为 0.400 的误报率。(请注意,示例数量非常少的子组具有非常宽的置信范围区间,并且结果不可预测。)

widget_view.render_fairness_indicator(min_diff_eval_result)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Overall', 'slice': 'Overall', 'metrics': {'accuracy': …

查看这些结果,您可能会注意到我们目标群体的 FPR 已经改善。我们表现最差的群体与多数群体之间的差距已从 .024 改善至 .006。鉴于我们观察到的改进以及多数群体持续的强劲表现,我们已经满足了我们的两个目标。根据产品,可能需要进一步改进,但这种方法使我们的模型更接近于公平地为所有用户提供服务。