{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "#### This notebook demonstrates the use of the learning fair representations algorithm for bias mitigation\n", "Learning fair representations [1] is a pre-processing technique that finds a latent representation which encodes the data well but obfuscates information about protected attributes. We will see how to use this algorithm for learning representations that encourage individual fairness and apply them on the Adult dataset." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "# Load all necessary packages\n", "import sys\n", "sys.path.append(\"../\")\n", "from aif360.datasets import BinaryLabelDataset\n", "from aif360.datasets import AdultDataset\n", "from aif360.metrics import BinaryLabelDatasetMetric\n", "from aif360.metrics import ClassificationMetric\n", "from aif360.metrics.utils import compute_boolean_conditioning_vector\n", "\n", "from aif360.algorithms.preprocessing.optim_preproc_helpers.data_preproc_functions import load_preproc_data_adult\n", "from aif360.algorithms.preprocessing.lfr import LFR\n", "\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.metrics import accuracy_score\n", "\n", "from IPython.display import Markdown, display\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Load dataset and set options" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Get the dataset and split into train and test\n", "dataset_orig = load_preproc_data_adult()\n", "dataset_orig_train, dataset_orig_test = dataset_orig.split([0.7], shuffle=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Clean up training data" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Training Dataset shape" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "(34189, 18)\n" ] }, { "data": { "text/markdown": [ "#### Favorable and unfavorable labels" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "(1.0, 0.0)\n" ] }, { "data": { "text/markdown": [ "#### Protected attribute names" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "['sex', 'race']\n" ] }, { "data": { "text/markdown": [ "#### Privileged and unprivileged protected attribute values" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "([array([1.]), array([1.])], [array([0.]), array([0.])])\n" ] }, { "data": { "text/markdown": [ "#### Dataset feature names" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "['race', 'sex', 'Age (decade)=10', 'Age (decade)=20', 'Age (decade)=30', 'Age (decade)=40', 'Age (decade)=50', 'Age (decade)=60', 'Age (decade)=>=70', 'Education Years=6', 'Education Years=7', 'Education Years=8', 'Education Years=9', 'Education Years=10', 'Education Years=11', 'Education Years=12', 'Education Years=<6', 'Education Years=>12']\n" ] } ], "source": [ "# print out some labels, names, etc.\n", "display(Markdown(\"#### Training Dataset shape\"))\n", "print(dataset_orig_train.features.shape)\n", "display(Markdown(\"#### Favorable and unfavorable labels\"))\n", "print(dataset_orig_train.favorable_label, dataset_orig_train.unfavorable_label)\n", "display(Markdown(\"#### Protected attribute names\"))\n", "print(dataset_orig_train.protected_attribute_names)\n", "display(Markdown(\"#### Privileged and unprivileged protected attribute values\"))\n", "print(dataset_orig_train.privileged_protected_attributes, \n", " dataset_orig_train.unprivileged_protected_attributes)\n", "display(Markdown(\"#### Dataset feature names\"))\n", "print(dataset_orig_train.feature_names)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Metric for original training data" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Original training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Difference in mean outcomes between unprivileged and privileged groups = -0.196576\n" ] } ], "source": [ "# Metric for the original dataset\n", "privileged_groups = [{'sex': 1.0}]\n", "unprivileged_groups = [{'sex': 0.0}]\n", "metric_orig_train = BinaryLabelDatasetMetric(dataset_orig_train, \n", " unprivileged_groups=unprivileged_groups,\n", " privileged_groups=privileged_groups)\n", "display(Markdown(\"#### Original training dataset\"))\n", "print(\"Difference in mean outcomes between unprivileged and privileged groups = %f\" % metric_orig_train.mean_difference())\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Train with and transform the original training data" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "250 20650.5619275\n", "500 19749.3611659\n", "750 18978.3028853\n", "1000 17995.3008952\n", "1250 16691.6273592\n", "1500 16443.051177\n", "1750 16096.538506\n", "2000 15761.1807924\n", "2250 17279.0619677\n", "2500 15737.4111706\n", "2750 15673.1203091\n", "3000 15576.8576761\n", "3250 15454.6964063\n", "3500 15416.9562705\n", "3750 15316.2346051\n", "4000 15247.5321307\n", "4250 15177.9090497\n", "4500 15138.4650177\n", "4750 15108.6571538\n", "5000 15082.626066\n" ] } ], "source": [ "# Input recontruction quality - Ax\n", "# Fairness constraint - Az\n", "# Output prediction error - Ay\n", "\n", "privileged_groups = [{'sex': 1}]\n", "unprivileged_groups = [{'sex': 0}]\n", " \n", "TR = LFR(unprivileged_groups = unprivileged_groups, privileged_groups = privileged_groups)\n", "TR = TR.fit(dataset_orig_train)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# Transform training data and align features\n", "dataset_transf_train = TR.transform(dataset_orig_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Metric with the transformed training data" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Transformed training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Classification threshold = 0.200000\n", "Difference in mean outcomes between unprivileged and privileged groups = -0.501460\n" ] }, { "data": { "text/markdown": [ "#### Transformed training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Classification threshold = 0.300000\n", "Difference in mean outcomes between unprivileged and privileged groups = -0.358144\n" ] }, { "data": { "text/markdown": [ "#### Transformed training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Classification threshold = 0.350000\n", "Difference in mean outcomes between unprivileged and privileged groups = -0.258428\n" ] }, { "data": { "text/markdown": [ "#### Transformed training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Classification threshold = 0.400000\n", "Difference in mean outcomes between unprivileged and privileged groups = -0.311679\n" ] }, { "data": { "text/markdown": [ "#### Transformed training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Classification threshold = 0.500000\n", "Difference in mean outcomes between unprivileged and privileged groups = -0.214736\n" ] } ], "source": [ "from sklearn.metrics import classification_report\n", "thresholds = [0.2, 0.3, 0.35, 0.4, 0.5]\n", "for threshold in thresholds:\n", " \n", " # Transform training data and align features\n", " dataset_transf_train = TR.transform(dataset_orig_train, threshold=threshold)\n", "\n", " metric_transf_train = BinaryLabelDatasetMetric(dataset_transf_train, \n", " unprivileged_groups=unprivileged_groups,\n", " privileged_groups=privileged_groups)\n", " display(Markdown(\"#### Transformed training dataset\"))\n", " print(\"Classification threshold = %f\" % threshold)\n", " #print(classification_report(dataset_orig_train.labels, dataset_transf_train.labels))\n", " print(\"Difference in mean outcomes between unprivileged and privileged groups = %f\" % metric_transf_train.mean_difference())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Optimized preprocessing has reduced the disparity in favorable outcomes between the privileged and unprivileged\n", "groups (training data)." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Individual fairness metrics" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Consistency of labels in transformed training dataset= 1.000000\n", "Consistency of labels in original training dataset= 0.742326\n" ] } ], "source": [ "display(Markdown(\"#### Individual fairness metrics\"))\n", "print(\"Consistency of labels in transformed training dataset= %f\" %metric_transf_train.consistency())\n", "print(\"Consistency of labels in original training dataset= %f\" %metric_orig_train.consistency())" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "## PCA Analysis of consitency" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "feat_cols = dataset_orig_train.feature_names\n", "\n", "orig_df = pd.DataFrame(dataset_orig_train.features,columns=feat_cols)\n", "orig_df['label'] = dataset_orig_train.labels\n", "orig_df['label'] = orig_df['label'].apply(lambda i: str(i))\n", "\n", "transf_df = pd.DataFrame(dataset_transf_train.features,columns=feat_cols)\n", "transf_df['label'] = dataset_transf_train.labels\n", "transf_df['label'] = transf_df['label'].apply(lambda i: str(i))" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Original training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Explained variation per principal component:\n", "[0.15355025 0.14652464 0.12614707]\n" ] } ], "source": [ "from sklearn.decomposition import PCA\n", "\n", "orig_pca = PCA(n_components=3)\n", "orig_pca_result = orig_pca.fit_transform(orig_df[feat_cols].values)\n", "\n", "orig_df['pca-one'] = orig_pca_result[:,0]\n", "orig_df['pca-two'] = orig_pca_result[:,1] \n", "orig_df['pca-three'] = orig_pca_result[:,2]\n", "\n", "display(Markdown(\"#### Original training dataset\"))\n", "print('Explained variation per principal component:')\n", "print(orig_pca.explained_variance_ratio_)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Transformed training dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Explained variation per principal component:\n", "[0.63337409 0.3302964 0.03632951]\n" ] } ], "source": [ "transf_pca = PCA(n_components=3)\n", "transf_pca_result = transf_pca.fit_transform(transf_df[feat_cols].values)\n", "\n", "transf_df['pca-one'] = transf_pca_result[:,0]\n", "transf_df['pca-two'] = transf_pca_result[:,1] \n", "transf_df['pca-three'] = transf_pca_result[:,2]\n", "\n", "display(Markdown(\"#### Transformed training dataset\"))\n", "print('Explained variation per principal component:')\n", "print(transf_pca.explained_variance_ratio_)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Load, clean up original test data and compute metric" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Testing Dataset shape" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "(14653, 18)\n" ] }, { "data": { "text/markdown": [ "#### Original test dataset" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Difference in mean outcomes between unprivileged and privileged groups = -0.189722\n" ] } ], "source": [ "display(Markdown(\"#### Testing Dataset shape\"))\n", "print(dataset_orig_test.features.shape)\n", "\n", "metric_orig_test = BinaryLabelDatasetMetric(dataset_orig_test, \n", " unprivileged_groups=unprivileged_groups,\n", " privileged_groups=privileged_groups)\n", "display(Markdown(\"#### Original test dataset\"))\n", "print(\"Difference in mean outcomes between unprivileged and privileged groups = %f\" % metric_orig_test.mean_difference())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Transform test data and compute metric" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "dataset_transf_test = TR.transform(dataset_orig_test, threshold=threshold)\n", "metric_transf_test = BinaryLabelDatasetMetric(dataset_transf_test, \n", " unprivileged_groups=unprivileged_groups,\n", " privileged_groups=privileged_groups)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Consistency of labels in tranformed test dataset= 1.000000\n" ] } ], "source": [ "print(\"Consistency of labels in tranformed test dataset= %f\" %metric_transf_test.consistency())" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Consistency of labels in original test dataset= 0.738798\n" ] } ], "source": [ "print(\"Consistency of labels in original test dataset= %f\" %metric_orig_test.consistency())" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "def check_algorithm_success():\n", " \"\"\"Transformed dataset consistency should be greater than original dataset.\"\"\"\n", " assert metric_transf_test.consistency() > metric_orig_test.consistency(), \"Transformed dataset consistency should be greater than original dataset.\"\n", "\n", "check_algorithm_success() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " References:\n", " [1] R. Zemel, Y. Wu, K. Swersky, T. Pitassi, and C. Dwork, \"Learning Fair Representations.\" \n", " International Conference on Machine Learning, 2013." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.15" } }, "nbformat": 4, "nbformat_minor": 2 }