公平指标简介

在 TensorFlow.org 上查看 在 Google Colab 中运行 在 GitHub 上查看 下载笔记本 查看 TF Hub 模型

概述

公平指标是一套构建在 TensorFlow 模型分析 (TFMA) 之上的工具,可用于在产品管道中定期评估公平指标。TFMA 是一个用于评估 TensorFlow 和非 TensorFlow 机器学习模型的库。它允许您在大量数据上以分布式方式评估模型,计算图内和其他指标,并以笔记本的形式进行可视化。

公平指标与 TensorFlow 数据验证 (TFDV)What-If 工具 打包在一起。使用公平指标,您可以

  • 评估模型性能,并根据定义的用户组进行切片
  • 通过置信区间和多个阈值处的评估来增强对结果的信心
  • 评估数据集的分布
  • 深入研究各个切片,以探索根本原因和改进机会

在本笔记本中,您将使用公平指标来修复使用 民事评论数据集 训练的模型中的公平性问题。观看此 视频,了解有关此现实世界场景的更多详细信息和背景信息,该场景也是创建公平指标的主要动机之一。

数据集

在本笔记本中,您将使用 民事评论数据集,该数据集包含大约 200 万条由 民事评论平台 在 2017 年公开发布的公开评论,用于持续研究。这项工作由 Jigsaw 赞助,他们在 Kaggle 上举办了比赛,以帮助对有毒评论进行分类,并最大限度地减少模型的意外偏差。

数据集中每个单独的文本评论都有一个毒性标签,如果评论有毒,则标签为 1,如果评论无毒,则标签为 0。在数据中,评论的子集被标记了各种身份属性,包括性别、性取向、宗教和种族或民族类别。

设置

安装 fairness-indicatorswitwidget

pip install -q -U pip==20.2

pip install -q fairness-indicators
pip install -q witwidget

安装后,您必须重新启动 Colab 运行时。从 Colab 菜单中选择**运行时 > 重新启动**运行时。

在重新启动运行时之前,请勿继续本教程的其余部分。

导入所有其他必需的库。

import os
import tempfile
import apache_beam as beam
import numpy as np
import pandas as pd
from datetime import datetime
import pprint

from google.protobuf import text_format

import tensorflow_hub as hub
import tensorflow as tf
import tensorflow_model_analysis as tfma
import tensorflow_data_validation as tfdv

from tfx_bsl.tfxio import tensor_adapter
from tfx_bsl.tfxio import tf_example_record

from tensorflow_model_analysis.addons.fairness.post_export_metrics import fairness_indicators
from tensorflow_model_analysis.addons.fairness.view import widget_view

from fairness_indicators.tutorial_utils import util

from witwidget.notebook.visualization import WitConfigBuilder
from witwidget.notebook.visualization import WitWidget

from tensorflow_metadata.proto.v0 import schema_pb2

下载和分析数据

默认情况下,此笔记本会下载此数据集的预处理版本,但您可以使用原始数据集并重新运行处理步骤(如果需要)。在原始数据集中,每个评论都标记了认为评论对应于特定身份的评分者百分比。例如,评论可能标记为以下内容:{ male: 0.3, female: 1.0, transgender: 0.0, heterosexual: 0.8, homosexual_gay_or_lesbian: 1.0 } 处理步骤按类别(性别、性取向等)对身份进行分组,并删除得分低于 0.5 的身份。因此,上面的示例将转换为以下内容:认为评论对应于特定身份的评分者百分比。例如,评论将标记为以下内容:{ gender: [female], sexual_orientation: [heterosexual, homosexual_gay_or_lesbian] }

download_original_data = False

if download_original_data:
  train_tf_file = tf.keras.utils.get_file('train_tf.tfrecord',
                                          'https://storage.googleapis.com/civil_comments_dataset/train_tf.tfrecord')
  validate_tf_file = tf.keras.utils.get_file('validate_tf.tfrecord',
                                             'https://storage.googleapis.com/civil_comments_dataset/validate_tf.tfrecord')

  # The identity terms list will be grouped together by their categories
  # (see 'IDENTITY_COLUMNS') on threshould 0.5. Only the identity term column,
  # text column and label column will be kept after processing.
  train_tf_file = util.convert_comments_data(train_tf_file)
  validate_tf_file = util.convert_comments_data(validate_tf_file)

else:
  train_tf_file = tf.keras.utils.get_file('train_tf_processed.tfrecord',
                                          'https://storage.googleapis.com/civil_comments_dataset/train_tf_processed.tfrecord')
  validate_tf_file = tf.keras.utils.get_file('validate_tf_processed.tfrecord',
                                             'https://storage.googleapis.com/civil_comments_dataset/validate_tf_processed.tfrecord')

使用 TFDV 分析数据并查找其中可能存在的问题,例如缺失值和数据不平衡,这些问题会导致公平差异。

stats = tfdv.generate_statistics_from_tfrecord(data_location=train_tf_file)
tfdv.visualize_statistics(stats)

TFDV 显示数据中存在一些重大不平衡,这可能会导致模型结果出现偏差。

  • 毒性标签(模型预测的值)不平衡。训练集中只有 8% 的示例是有毒的,这意味着分类器可以通过预测所有评论都是无毒的来获得 92% 的准确率。

  • 在与身份术语相关的字段中,108 万个(0.61%)训练示例中只有 6.6k 个与同性恋有关,而与双性恋有关的示例则更为罕见。这表明由于缺乏训练数据,这些切片的性能可能会下降。

准备数据

定义一个特征图来解析数据。每个示例将包含一个标签、评论文本和与文本相关的身份特征 sexual orientationgenderreligionracedisability

BASE_DIR = tempfile.gettempdir()

TEXT_FEATURE = 'comment_text'
LABEL = 'toxicity'
FEATURE_MAP = {
    # Label:
    LABEL: tf.io.FixedLenFeature([], tf.float32),
    # Text:
    TEXT_FEATURE:  tf.io.FixedLenFeature([], tf.string),

    # Identities:
    'sexual_orientation':tf.io.VarLenFeature(tf.string),
    'gender':tf.io.VarLenFeature(tf.string),
    'religion':tf.io.VarLenFeature(tf.string),
    'race':tf.io.VarLenFeature(tf.string),
    'disability':tf.io.VarLenFeature(tf.string),
}

接下来,设置一个输入函数,将数据馈送到模型。为每个示例添加一个权重列,并对有毒示例进行加权,以解决 TFDV 识别出的类别不平衡问题。在评估阶段仅使用身份特征,因为在训练期间只有评论被馈送到模型。

def train_input_fn():
  def parse_function(serialized):
    parsed_example = tf.io.parse_single_example(
        serialized=serialized, features=FEATURE_MAP)
    # Adds a weight column to deal with unbalanced classes.
    parsed_example['weight'] = tf.add(parsed_example[LABEL], 0.1)
    return (parsed_example,
            parsed_example[LABEL])
  train_dataset = tf.data.TFRecordDataset(
      filenames=[train_tf_file]).map(parse_function).batch(512)
  return train_dataset

训练模型

在数据上创建并训练一个深度学习模型。

model_dir = os.path.join(BASE_DIR, 'train', datetime.now().strftime(
    "%Y%m%d-%H%M%S"))

embedded_text_feature_column = hub.text_embedding_column(
    key=TEXT_FEATURE,
    module_spec='https://tfhub.dev/google/nnlm-en-dim128/1')

classifier = tf.estimator.DNNClassifier(
    hidden_units=[500, 100],
    weight_column='weight',
    feature_columns=[embedded_text_feature_column],
    optimizer=tf.keras.optimizers.legacy.Adagrad(learning_rate=0.003),
    loss_reduction=tf.losses.Reduction.SUM,
    n_classes=2,
    model_dir=model_dir)

classifier.train(input_fn=train_input_fn, steps=1000)

分析模型

获得训练后的模型后,使用 TFMA 和 Fairness Indicators 分析模型以计算公平性指标。首先将模型导出为 SavedModel

导出 SavedModel

def eval_input_receiver_fn():
  serialized_tf_example = tf.compat.v1.placeholder(
      dtype=tf.string, shape=[None], name='input_example_placeholder')

  # This *must* be a dictionary containing a single key 'examples', which
  # points to the input placeholder.
  receiver_tensors = {'examples': serialized_tf_example}

  features = tf.io.parse_example(serialized_tf_example, FEATURE_MAP)
  features['weight'] = tf.ones_like(features[LABEL])

  return tfma.export.EvalInputReceiver(
    features=features,
    receiver_tensors=receiver_tensors,
    labels=features[LABEL])

tfma_export_dir = tfma.export.export_eval_savedmodel(
  estimator=classifier,
  export_dir_base=os.path.join(BASE_DIR, 'tfma_eval_model'),
  eval_input_receiver_fn=eval_input_receiver_fn)

计算公平性指标

在右侧面板的下拉菜单中选择要计算指标的身份以及是否使用置信区间运行。

公平性指标计算选项

使用 What-If 工具可视化数据

在本节中,您将使用 What-If 工具的交互式可视化界面来探索和操作微观层面的数据。

右侧面板上的散点图上的每个点代表加载到工具中的子集中的一条示例。单击其中一个点以查看左侧面板中此特定示例的详细信息。将显示评论文本、真实毒性以及适用的身份。在此左侧面板的底部,您将看到刚刚训练的模型的推理结果。

修改示例的文本,然后单击 **运行推理** 按钮以查看您的更改如何导致感知毒性预测发生变化。

DEFAULT_MAX_EXAMPLES = 1000

# Load 100000 examples in memory. When first rendered, 
# What-If Tool should only display 1000 of these due to browser constraints.
def wit_dataset(file, num_examples=100000):
  dataset = tf.data.TFRecordDataset(
      filenames=[file]).take(num_examples)
  return [tf.train.Example.FromString(d.numpy()) for d in dataset]

wit_data = wit_dataset(train_tf_file)
config_builder = WitConfigBuilder(wit_data[:DEFAULT_MAX_EXAMPLES]).set_estimator_and_feature_spec(
    classifier, FEATURE_MAP).set_label_vocab(['non-toxicity', LABEL]).set_target_feature(LABEL)
wit = WitWidget(config_builder)

呈现公平性指标

使用导出的评估结果呈现 Fairness Indicators 小部件。

您将在下面看到条形图,显示数据每个切片在所选指标上的性能。您可以使用可视化顶部的下拉菜单调整基线比较切片以及显示的阈值。

Fairness Indicator 小部件与上面呈现的 What-If 工具集成在一起。如果您在条形图中选择数据的一个切片,What-If 工具将更新以向您显示来自所选切片的示例。当数据在上面的 What-If 工具中重新加载时,尝试将 **颜色依据** 修改为 **toxicity**。这可以帮助您直观地了解每个切片的示例的毒性平衡。

event_handlers={'slice-selected':
                wit.create_selection_callback(wit_data, DEFAULT_MAX_EXAMPLES)}
widget_view.render_fairness_indicator(eval_result=eval_result,
                                      slicing_column=slice_selection,
                                      event_handlers=event_handlers
                                      )

对于这个特定的数据集和任务,某些身份的系统性更高的假阳性和假阴性率会导致负面后果。例如,在内容审核系统中,某个群体的假阳性率高于总体水平会导致这些声音被压制。因此,在开发和改进模型时,定期评估这些类型的标准非常重要,并利用 Fairness Indicators、TFDV 和 WIT 等工具来帮助阐明潜在的问题。一旦您确定了公平性问题,就可以尝试使用新的数据源、数据平衡或其他技术来提高表现不佳的群体的性能。

有关如何使用 Fairness Indicators 的更多信息和指南,请参阅 此处

使用公平性评估结果

上面在 render_fairness_indicator() 中呈现的 eval_result 对象具有自己的 API,您可以利用它将 TFMA 结果读入您的程序。

获取评估的切片和指标

使用 get_slice_names()get_metric_names() 分别获取评估的切片和指标。

pp = pprint.PrettyPrinter()

print("Slices:")
pp.pprint(eval_result.get_slice_names())
print("\nMetrics:")
pp.pprint(eval_result.get_metric_names())

使用 get_metrics_for_slice() 获取特定切片的指标,作为将指标名称映射到 指标值 的字典。

baseline_slice = ()
heterosexual_slice = (('sexual_orientation', 'heterosexual'),)

print("Baseline metric values:")
pp.pprint(eval_result.get_metrics_for_slice(baseline_slice))
print("\nHeterosexual metric values:")
pp.pprint(eval_result.get_metrics_for_slice(heterosexual_slice))

使用 get_metrics_for_all_slices() 获取所有切片的指标,作为将每个切片映射到从在其上运行 get_metrics_for_slice() 获得的相应指标字典的字典。

pp.pprint(eval_result.get_metrics_for_all_slices())