Pandas DataFrame 到公平性指标案例研究

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

案例研究概述

在本案例研究中,我们将应用 TensorFlow 模型分析公平性指标 来评估存储为 Pandas DataFrame 的数据,其中每行包含真实标签、各种特征和模型预测。我们将展示如何使用此工作流程来发现潜在的公平性问题,而与用于构建和训练模型的框架无关。在本案例研究中,我们可以分析来自任何机器学习框架(例如 TensorFlow、JAX 等)的结果,只要它们被转换为 Pandas DataFrame 即可。

对于此练习,我们将利用在 使用 Tensorflow Lattice 进行道德形状约束 案例研究中开发的深度神经网络 (DNN) 模型,使用来自法学院入学委员会 (LSAC) 的法学院入学数据集。此分类器试图根据学生的法学院入学考试 (LSAT) 成绩和本科 GPA 来预测学生是否会通过律师资格考试。

LSAC 数据集

本案例研究中使用的数据集最初是为一项名为 'LSAC 全国纵向律师资格考试通过率研究。LSAC 研究报告系列' 的研究收集的,由 Linda Wightman 于 1998 年进行。该数据集目前托管 此处

  • dnn_bar_pass_prediction: DNN 模型的 LSAT 预测。
  • gender: 学生的性别。
  • lsat: 学生获得的 LSAT 成绩。
  • pass_bar: 指示学生最终是否通过律师资格考试的真实标签。
  • race: 学生的种族。
  • ugpa: 学生的本科 GPA。
!pip install -q -U pip==20.2

!pip install -q -U \
  tensorflow-model-analysis==0.44.0 \
  tensorflow-data-validation==1.13.0 \
  tfx-bsl==1.13.0

导入所需的软件包

import os
import tempfile
import pandas as pd
import six.moves.urllib as urllib
import pprint

import tensorflow_model_analysis as tfma
from google.protobuf import text_format

import tensorflow as tf
tf.compat.v1.enable_v2_behavior()

下载数据并探索初始数据集。

# Download the LSAT dataset and setup the required filepaths.
_DATA_ROOT = tempfile.mkdtemp(prefix='lsat-data')
_DATA_PATH = 'https://storage.googleapis.com/lawschool_dataset/bar_pass_prediction.csv'
_DATA_FILEPATH = os.path.join(_DATA_ROOT, 'bar_pass_prediction.csv')

data = urllib.request.urlopen(_DATA_PATH)

_LSAT_DF = pd.read_csv(data)

# To simpliy the case study, we will only use the columns that will be used for
# our model.
_COLUMN_NAMES = [
  'dnn_bar_pass_prediction',
  'gender',
  'lsat',
  'pass_bar',
  'race1',
  'ugpa',
]

_LSAT_DF.dropna()
_LSAT_DF['gender'] = _LSAT_DF['gender'].astype(str)
_LSAT_DF['race1'] = _LSAT_DF['race1'].astype(str)
_LSAT_DF = _LSAT_DF[_COLUMN_NAMES]

_LSAT_DF.head()

配置公平性指标。

在使用 DataFrame 的公平性指标时,您需要考虑几个参数

  • 您的输入 DataFrame 必须包含来自模型的预测列和标签列。默认情况下,公平性指标将在您的 DataFrame 中查找名为 prediction 的预测列和名为 label 的标签列。

    • 如果找不到这些值中的任何一个,将引发 KeyError。
  • 除了 DataFrame 之外,您还需要包含一个 eval_config,其中应包括要计算的指标、要计算指标的切片以及示例标签和预测的列名。

    • metrics_specs 将设置要计算的指标。将需要 FairnessIndicators 指标来呈现公平性指标,您可以在 此处 查看其他可选指标的列表。

    • slicing_specs 是一个可选的切片参数,用于指定您感兴趣的调查特征。在本案例研究中,使用了 race1,但您也可以将此值设置为另一个特征(例如,在本 DataFrame 的上下文中为性别)。如果没有提供 slicing_specs,则将包含所有特征。

    • 如果您的 DataFrame 包含与默认 predictionlabel 不同的标签或预测列,您可以将 label_keyprediction_key 配置为新值。

  • 如果没有指定 output_path,将创建一个临时目录。

# Specify Fairness Indicators in eval_config.
eval_config = text_format.Parse("""
  model_specs {
    prediction_key: 'dnn_bar_pass_prediction',
    label_key: 'pass_bar'
  }
  metrics_specs {
    metrics {class_name: "AUC"}
    metrics {
      class_name: "FairnessIndicators"
      config: '{"thresholds": [0.50, 0.90]}'
    }
  }
  slicing_specs {
    feature_keys: 'race1'
  }
  slicing_specs {}
  """, tfma.EvalConfig())

# Run TensorFlow Model Analysis.
eval_result = tfma.analyze_raw_data(
  data=_LSAT_DF,
  eval_config=eval_config,
  output_path=_DATA_ROOT)

使用公平性指标探索模型性能。

运行公平性指标后,我们可以可视化我们选择用于分析模型性能的不同指标。在本案例研究中,我们包含了公平性指标并任意选择了 AUC。

当我们第一次查看每个种族切片的整体 AUC 时,我们可以看到模型性能略有差异,但没有什么值得警觉的。

  • 亚洲人: 0.58
  • 黑人: 0.58
  • 西班牙裔: 0.58
  • 其他: 0.64
  • 白人: 0.6

然而,当我们查看按种族划分的误报率时,我们的模型再次错误地预测了用户通过律师资格考试的可能性,而且这次差异很大。

  • 亚洲人: 0.01
  • 黑人: 0.05
  • 西班牙裔: 0.02
  • 其他: 0.01
  • 白人: 0.01

最值得注意的是黑人和白人学生之间的差异约为 380%,这意味着我们的模型错误地预测黑人学生不会通过律师资格考试的可能性几乎是白人学生的 4 倍。如果我们继续这项工作,从业者可以使用这些结果作为信号,表明他们应该花更多时间确保他们的模型对来自所有背景的人都能很好地工作。

# Render Fairness Indicators.
tfma.addons.fairness.view.widget_view.render_fairness_indicator(eval_result)

tfma.EvalResult

上面在 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()get_metrics_for_all_slices()

如果您想获取特定切片的指标,可以使用 get_metrics_for_slice()。它返回一个字典,将指标名称映射到 指标值

baseline_slice = ()
black_slice = (('race1', 'black'),)

print("Baseline metric values:")
pp.pprint(eval_result.get_metrics_for_slice(baseline_slice))
print("Black metric values:")
pp.pprint(eval_result.get_metrics_for_slice(black_slice))

如果您想获取所有切片的指标,get_metrics_for_all_slices() 返回一个字典,将每个切片映射到相应的 get_metrics_for_slices(slice)

pp.pprint(eval_result.get_metrics_for_all_slices())

结论

在本案例研究中,我们将数据集导入到 Pandas DataFrame 中,然后使用公平性指标对其进行分析。了解模型和基础数据的結果是确保模型不反映有害偏差的重要步骤。在本案例研究中,我们检查了 LSAC 数据集以及来自该数据的预测如何受到学生种族的影響。“什么是公平,什么是公正”的概念在 50 多年前的多个学科中就被引入,包括教育、招聘和机器学习。1 公平性指标是帮助缓解机器学习模型中公平性问题的工具。

有关使用公平性指标的更多信息以及了解公平性问题的资源,请参阅 此处


  1. Hutchinson, B., Mitchell, M. (2018). 50 Years of Test (Un)fairness: Lessons for Machine Learning. https://arxiv.org/abs/1811.10104

附录

以下是一些帮助将 ML 模型转换为 Pandas DataFrame 的函数。

# TensorFlow Estimator to Pandas DataFrame:

# _X_VALUE =  # X value of binary estimator.
# _Y_VALUE =  # Y value of binary estimator.
# _GROUND_TRUTH_LABEL =  # Ground truth value of binary estimator.

def _get_predicted_probabilities(estimator, input_df, get_input_fn):
  predictions = estimator.predict(
      input_fn=get_input_fn(input_df=input_df, num_epochs=1))
  return [prediction['probabilities'][1] for prediction in predictions]

def _get_input_fn_law(input_df, num_epochs, batch_size=None):
  return tf.compat.v1.estimator.inputs.pandas_input_fn(
      x=input_df[[_X_VALUE, _Y_VALUE]],
      y=input_df[_GROUND_TRUTH_LABEL],
      num_epochs=num_epochs,
      batch_size=batch_size or len(input_df),
      shuffle=False)

def estimator_to_dataframe(estimator, input_df, num_keypoints=20):
  x = np.linspace(min(input_df[_X_VALUE]), max(input_df[_X_VALUE]), num_keypoints)
  y = np.linspace(min(input_df[_Y_VALUE]), max(input_df[_Y_VALUE]), num_keypoints)

  x_grid, y_grid = np.meshgrid(x, y)

  positions = np.vstack([x_grid.ravel(), y_grid.ravel()])
  plot_df = pd.DataFrame(positions.T, columns=[_X_VALUE, _Y_VALUE])
  plot_df[_GROUND_TRUTH_LABEL] = np.ones(len(plot_df))
  predictions = _get_predicted_probabilities(
      estimator=estimator, input_df=plot_df, get_input_fn=_get_input_fn_law)
  return pd.DataFrame(
      data=np.array(np.reshape(predictions, x_grid.shape)).flatten())