FaceSSD 公平性指标示例 Colab

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

概述

在本活动中,您将使用 公平性指标 来探索 FaceSSD 对 Labeled Faces in the Wild 数据集的预测。公平性指标是一套构建在 TensorFlow 模型分析 之上的工具,可用于在产品管道中定期评估公平性指标。

关于数据集

在本练习中,您将使用 FaceSSD 预测数据集,该数据集包含大约 200,000 个由 FaceSSD API 生成的不同图像预测和真实值。

关于工具

TensorFlow 模型分析 是一个用于评估 TensorFlow 和非 TensorFlow 机器学习模型的库。它允许用户以分布式方式在大量数据上评估其模型,计算图内和其他指标,跨越数据的不同切片,并在笔记本中进行可视化。

TensorFlow 数据验证 是您可以用来分析数据的工具之一。您可以使用它来查找数据中的潜在问题,例如缺失值和数据不平衡,这些问题会导致公平性差异。

使用 公平性指标,用户将能够

  • 评估模型性能,跨越定义的用户组进行切片
  • 通过置信区间和多个阈值处的评估对结果充满信心

导入

运行以下代码以安装 fairness_indicators 库。此包包含我们将在本练习中使用的工具。可能要求重新启动运行时,但并非必需。

pip install apache_beam
pip install fairness-indicators
pip install witwidget
import os
import tempfile
import apache_beam as beam
import numpy as np
import pandas as pd
from datetime import datetime

import tensorflow_hub as hub
import tensorflow as tf
import tensorflow_model_analysis as tfma
import tensorflow_data_validation as tfdv
from tensorflow_model_analysis.addons.fairness.post_export_metrics import fairness_indicators
from tensorflow_model_analysis.addons.fairness.view import widget_view
from tensorflow_model_analysis.model_agnostic_eval import model_agnostic_predict as agnostic_predict
from tensorflow_model_analysis.model_agnostic_eval import model_agnostic_evaluate_graph
from tensorflow_model_analysis.model_agnostic_eval import model_agnostic_extractor

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

下载和理解数据

Labeled Faces in the Wild 是一个用于人脸验证(也称为配对匹配)的公共基准数据集。LFW 包含从网络收集的超过 13,000 张人脸图像。

我们在该数据集上运行了 FaceSSD 预测,以预测给定图像中是否存在人脸。在本 Colab 中,我们将根据性别对数据进行切片,以观察不同性别组的模型性能是否存在任何显著差异。

如果图像中有多张人脸,则性别标记为“MISSING”。

为了方便起见,我们在 Google Cloud Platform 上托管了该数据集。运行以下代码从 GCP 下载数据,数据下载和分析大约需要一分钟。

data_location = tf.keras.utils.get_file('lfw_dataset.tf', 'https://storage.googleapis.com/facessd_dataset/lfw_dataset.tfrecord')

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

定义常量

BASE_DIR = tempfile.gettempdir()

tfma_eval_result_path = os.path.join(BASE_DIR, 'tfma_eval_result')

compute_confidence_intervals = True

slice_key = 'object/groundtruth/Gender'
label_key = 'object/groundtruth/face'
prediction_key = 'object/prediction/face'

feature_map = {
    slice_key:
        tf.io.FixedLenFeature([], tf.string, default_value=['none']),
    label_key:
        tf.io.FixedLenFeature([], tf.float32, default_value=[0.0]),
    prediction_key:
        tf.io.FixedLenFeature([], tf.float32, default_value=[0.0]),
}

TFMA 的模型无关配置

model_agnostic_config = agnostic_predict.ModelAgnosticConfig(
    label_keys=[label_key],
    prediction_keys=[prediction_key],
    feature_spec=feature_map)

model_agnostic_extractors = [
    model_agnostic_extractor.ModelAgnosticExtractor(
        model_agnostic_config=model_agnostic_config, desired_batch_size=3),
    tfma.extractors.slice_key_extractor.SliceKeyExtractor(
          [tfma.slicer.SingleSliceSpec(),
           tfma.slicer.SingleSliceSpec(columns=[slice_key])])
]

公平性回调和计算公平性指标

# Helper class for counting examples in beam PCollection
class CountExamples(beam.CombineFn):
    def __init__(self, message):
      self.message = message

    def create_accumulator(self):
      return 0

    def add_input(self, current_sum, element):
      return current_sum + 1

    def merge_accumulators(self, accumulators): 
      return sum(accumulators)

    def extract_output(self, final_sum):
      if final_sum:
        print("%s: %d"%(self.message, final_sum))
metrics_callbacks = [
  tfma.post_export_metrics.fairness_indicators(
      thresholds=[0.1, 0.3, 0.5, 0.7, 0.9],
      labels_key=label_key,
      target_prediction_keys=[prediction_key]),
  tfma.post_export_metrics.auc(
      curve='PR',
      labels_key=label_key,
      target_prediction_keys=[prediction_key]),
]

eval_shared_model = tfma.types.EvalSharedModel(
    add_metrics_callbacks=metrics_callbacks,
    construct_fn=model_agnostic_evaluate_graph.make_construct_fn(
        add_metrics_callbacks=metrics_callbacks,
        config=model_agnostic_config))

with beam.Pipeline() as pipeline:
  # Read data.
  data = (
      pipeline
      | 'ReadData' >> beam.io.ReadFromTFRecord(data_location))

  # Count all examples.
  data_count = (
      data | 'Count number of examples' >> beam.CombineGlobally(
          CountExamples('Before filtering "Gender:MISSING"')))

  # If there are more than one face in image, the gender feature is 'MISSING'
  # and we are filtering that image out.
  def filter_missing_gender(element):
    example = tf.train.Example.FromString(element)
    if example.features.feature[slice_key].bytes_list.value[0] != b'MISSING':
      yield element

  filtered_data = (
      data
      | 'Filter Missing Gender' >> beam.ParDo(filter_missing_gender))

  # Count after filtering "Gender:MISSING".
  filtered_data_count = (
      filtered_data | 'Count number of examples after filtering'
      >> beam.CombineGlobally(
          CountExamples('After filtering "Gender:MISSING"')))

  # Because LFW data set has always faces by default, we are adding
  # labels as 1.0 for all images.
  def add_face_groundtruth(element):
    example = tf.train.Example.FromString(element)
    example.features.feature[label_key].float_list.value[:] = [1.0]
    yield example.SerializeToString()

  final_data = (
      filtered_data
      | 'Add Face Groundtruth' >> beam.ParDo(add_face_groundtruth))

  # Run TFMA.
  _ = (
      final_data
      | 'ExtractEvaluateAndWriteResults' >>
       tfma.ExtractEvaluateAndWriteResults(
                 eval_shared_model=eval_shared_model,
                 compute_confidence_intervals=compute_confidence_intervals,
                 output_path=tfma_eval_result_path,
                 extractors=model_agnostic_extractors))

eval_result = tfma.load_eval_result(output_path=tfma_eval_result_path)

呈现公平性指标

使用导出的评估结果呈现公平性指标小部件。

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

此用例的相关指标是真阳性率,也称为召回率。使用左侧的选择器选择真阳性率的图表。这些指标值与 模型卡 上显示的值相匹配。

对于某些照片,如果照片中的人年龄太小,无法准确标注,则性别标记为年轻,而不是男性或女性。

widget_view.render_fairness_indicator(eval_result=eval_result,
                                      slicing_column=slice_key)