Pandas DataFrame 轉為公平性指標案例研究

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視 下載筆記本

案例研究總覽

在本案例研究中,我們將應用TensorFlow 模型分析公平性指標,以評估儲存為 Pandas DataFrame 的資料,其中每列包含基本事實標籤、各種特徵和模型預測。我們將展示如何使用此工作流程來發現潛在的公平性問題,而與用於建構和訓練模型的架構無關。如同本案例研究,我們可以分析來自任何機器學習架構 (例如 TensorFlow、JAX 等) 的結果,只要這些結果已轉換為 Pandas DataFrame 即可。

在本練習中,我們將運用在Tensorflow Lattice 倫理形狀限制案例研究中使用法學院入學委員會 (LSAC) 的法學院入學資料集開發的深度神經網路 (DNN) 模型。此分類器嘗試根據學生的法學院入學考試 (LSAT) 分數和大學部 GPA,預測學生是否能通過律師資格考試。

LSAC 資料集

本案例研究中使用的資料集最初是為 Linda Wightman 於 1998 年進行的一項名為「LSAC 全國縱向律師資格考試研究。LSAC 研究報告系列」的研究而收集的。該資料集目前託管於此處

  • 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 的情境中為 gender)。如果未提供 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())