{ "cells": [ { "cell_type": "markdown", "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "executionInfo": { "elapsed": 66330, "status": "ok", "timestamp": 1707202682814, "user": { "displayName": "Alice H", "userId": "13901604971984976961" }, "user_tz": -60 }, "id": "R1WJwi7C9Ng5", "outputId": "ec9c5093-9e95-40c2-d729-00d2bc4c01ad" }, "source": [ "Today we will need to use two different python env\n", "\n", "```\n", "python3.10 -m venv env_fairness\n", "source env_fairness/bin/activate\n", "pip install --upgrade pip\n", "pip install numpy==1.25 fairlearn==0.9.0 plotly==5.24.1 nbformat==5.10.4 aif360['inFairness']==0.6.1 ipykernel==6.29.5 BlackBoxAuditing==0.1.54 cvxpy==1.6.0\n", "cd env_fairness/lib/python3.9/site-packages/aif360/data/raw/meps\n", "Rscipt generate_data.R\n", "```\n", "\n", "```\n", "python3.10 -m venv env_adv\n", "source env_fairness/bin/activate\n", "pip install --upgrade pip\n", "pip install numpy==1.26 fairlearn==0.9.0 plotly==5.24.1 nbformat==5.10.4 aif360['AdversarialDebiasing']==0.6.1 aif360['inFairness']==0.6.1 ipykernel==6.29.5 BlackBoxAuditing==0.1.54 cvxpy==1.6.0\n", "cd env_adv/lib/python3.9/site-packages/aif360/data/raw/meps\n", "Rscipt generate_data.R\n", "```\n", "\n", "The part of the TD on Adversarial Devbiasing can run only with the second env, the rest of the document can run on the first env.\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "OepcePtF1hIS" }, "source": [ "!!! Attention sur Colab!!!, après avoir executé la cellule ci-dessus, il faudra redémarrer la session (onglet \"Execution\") afin de charger l'environnement installé" ] }, { "cell_type": "markdown", "metadata": { "id": "HRe6S30b9Ng7" }, "source": [ "# TD 4: Mitigation des biais avec une méthode de in-processsing Prejudice Remover\n", "\n", "The aim of this notebook is to use the Prejudice Remover in-processing approach and analyse its impact on the model output.\n", "In terms of Machine Learning we will go a bit further in the train/valid/test paradigm.\n", "\n", "The model has to be learn on the train dataset, then the model parameters has to be optimized on the valid dataset, and finally the model performance is evaluated on the test dataset.\n", "No choice/decision etc can be taken depending on the test dataset. This could result on an overfitting on the test dataset.\n", "\n", "Here you will manipulate:\n", "- Prejudice Remover approach as a black box\n", "- Training of the prejudice remover using the train/valid paradigm. to choice the 'best' threshold\n", "- Combine Prejudice Remover with Reweighing\n", "\n", "As a reminder of pre-processing approach we encourage you to :\n", "- analyse the impact of the Reweighing on different model (Logistic Regression, Decision Tree, Random Forest, etc.)\n" ] }, { "cell_type": "markdown", "metadata": { "id": "9c9ITNS89NhA" }, "source": [ "## 1. Import and load the dataset" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 9, "status": "ok", "timestamp": 1707202697233, "user": { "displayName": "Alice H", "userId": "13901604971984976961" }, "user_tz": -60 }, "id": "88mMZIic9NhB", "outputId": "f0be967d-0531-4dd0-8d13-39accc70509e" }, "outputs": [], "source": [ "# imports\n", "import numpy as np\n", "import pandas as pd\n", "import plotly.express as px\n", "import warnings\n", "\n", "warnings.simplefilter(action=\"ignore\", category=FutureWarning)\n", "warnings.simplefilter(action=\"ignore\", append=True, category=UserWarning)\n", "# Datasets\n", "from aif360.datasets import MEPSDataset19\n", "\n", "# Fairness metrics\n", "from sklearn.metrics import accuracy_score, balanced_accuracy_score\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "MEPSDataset19_data = MEPSDataset19()\n", "(dataset_orig_panel19_train, dataset_orig_panel19_val, dataset_orig_panel19_test) = (\n", " MEPSDataset19().split([0.5, 0.8], shuffle=True)\n", ")" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "executionInfo": { "elapsed": 20083, "status": "ok", "timestamp": 1707202719711, "user": { "displayName": "Alice H", "userId": "13901604971984976961" }, "user_tz": -60 }, "id": "LYbdfIPs9NhE" }, "outputs": [ { "data": { "text/plain": [ "(7915, 4749, 3166)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(dataset_orig_panel19_train.instance_weights), len(\n", " dataset_orig_panel19_val.instance_weights\n", "), len(dataset_orig_panel19_test.instance_weights)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([21854.981705, 18169.604822, 17191.832515, ..., 3896.116219,\n", " 4883.851005, 6630.588948])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "instance_weights = MEPSDataset19_data.instance_weights\n", "instance_weights" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Taille du dataset 15830, poids total du dataset 141367240.546316.'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f\"Taille du dataset {len(instance_weights)}, poids total du dataset {instance_weights.sum()}.\"" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2025-02-21 00:05:58.902977: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n", "2025-02-21 00:05:58.914642: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n", "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", "E0000 00:00:1740092758.925168 1925928 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n", "E0000 00:00:1740092758.928153 1925928 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", "2025-02-21 00:05:58.939068: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n", "To enable the following instructions: AVX2 AVX512F AVX512_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n" ] } ], "source": [ "from aif360.sklearn.metrics import *\n", "from sklearn.metrics import balanced_accuracy_score\n", "\n", " \n", "# This method takes lists\n", "def get_metrics(\n", " y_true, # list or np.array of truth values\n", " y_pred=None, # list or np.array of predictions\n", " prot_attr=None, # list or np.array of protected/sensitive attribute values\n", " priv_group=1, # value taken by the privileged group\n", " pos_label=1, # value taken by the positive truth/prediction\n", " sample_weight=None # list or np.array of weights value,\n", "):\n", " group_metrics = {}\n", " group_metrics[\"base_rate_truth\"] = base_rate(\n", " y_true=y_true, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " group_metrics[\"statistical_parity_difference\"] = statistical_parity_difference(\n", " y_true=y_true, y_pred=y_pred, prot_attr=prot_attr, priv_group=priv_group, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " group_metrics[\"disparate_impact_ratio\"] = disparate_impact_ratio(\n", " y_true=y_true, y_pred=y_pred, prot_attr=prot_attr, priv_group=priv_group, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " if not y_pred is None:\n", " group_metrics[\"base_rate_preds\"] = base_rate(\n", " y_true=y_pred, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " group_metrics[\"equal_opportunity_difference\"] = equal_opportunity_difference(\n", " y_true=y_true, y_pred=y_pred, prot_attr=prot_attr, priv_group=priv_group, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " group_metrics[\"average_odds_difference\"] = average_odds_difference(\n", " y_true=y_true, y_pred=y_pred, prot_attr=prot_attr, priv_group=priv_group, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " if len(set(y_pred))>1:\n", " group_metrics[\"conditional_demographic_disparity\"] = conditional_demographic_disparity(\n", " y_true=y_true, y_pred=y_pred, prot_attr=prot_attr, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " else:\n", " group_metrics[\"conditional_demographic_disparity\"] =None\n", " group_metrics[\"smoothed_edf\"] = smoothed_edf(\n", " y_true=y_true, y_pred=y_pred, prot_attr=prot_attr, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " group_metrics[\"df_bias_amplification\"] = df_bias_amplification(\n", " y_true=y_true, y_pred=y_pred, prot_attr=prot_attr, pos_label=pos_label, sample_weight=sample_weight\n", " )\n", " group_metrics[\"balanced_accuracy_score\"] = balanced_accuracy_score(\n", " y_true=y_true, y_pred=y_pred, sample_weight=sample_weight\n", " )\n", " return group_metrics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Learning a Prejudice Remover model on the training dataset, and choose the best parameters with the validation dataset" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# Bias mitigation techniques\n", "from aif360.algorithms.preprocessing import Reweighing\n", "from aif360.algorithms.inprocessing import PrejudiceRemover" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Question1 : Learn a Standard Scaler on the training dataset features, its output will be used as input of the model learned" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "pr_orig_scaler = StandardScaler()\n", "train_dataset_scaled = dataset_orig_panel19_train.copy()\n", "train_dataset_scaled.features = pr_orig_scaler.fit_transform(train_dataset_scaled.features)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Question2: Create a method to learn a Prejudice Remover on the train dataset and retrieve the model learned\n", "Execute the method with the parameter eta arbitrarily set at 25.0\n", "\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def train_pr_model(eta=25.0, train_dataset=train_dataset_scaled):\n", " model = PrejudiceRemover(sensitive_attr='RACE', eta=eta)\n", " pr_orig_panel19 = model.fit(train_dataset)\n", " return pr_orig_panel19\n", "\n", "pr_orig_panel19 = train_pr_model()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "((7915, 1),\n", " array([[0.24968875],\n", " [0.13644747],\n", " [0.17826004],\n", " [0.23045409],\n", " [0.43822148],\n", " [0.0735638 ],\n", " [0.03395558],\n", " [0.07592396],\n", " [0.15190029],\n", " [0.01911056]]))" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "train_scores = pr_orig_panel19.predict(train_dataset_scaled).scores\n", "train_scores.shape, train_scores[:10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Le score du Prejudice Remover donne un sortie pour chaque instance une seule valeur, c'est un seuil, arbritrairement fixé à 0.5 par défault, qui permet à partir de ce score de décider la prédiction 1 ou 0.\n", "Si le score est supérieur au seuil la prédiction est 1, sinon c'est 0." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Validating: Choose the best parameters\n", "\n", "Here there are two parameters :\n", "- eta: fairness penalty parameter of the PR model\n", "- thershold: the threshold of the binary classification\n", "\n", "The threshold is used to obtains predictions from the model output.\n", "The eta is used during the training\n", "\n", "Question3: Create a method that will loop over 50 threshold ]0:0.5( and 5 values of ETA [1.0: 100.0], and outputs the metrics" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'base_rate_truth': 0.22072904528726825,\n", " 'statistical_parity_difference': -0.14003550662578024,\n", " 'disparate_impact_ratio': 0.4948564702571672}" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_metrics(\n", " y_true = dataset_orig_panel19_val.labels[:,0],\n", " prot_attr= dataset_orig_panel19_val.protected_attributes[:,0],\n", " sample_weight= dataset_orig_panel19_val.instance_weights\n", ")" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "def validation_loop(thereshold_list, eta_list, train_dataset=train_dataset_scaled, val_dataset=dataset_orig_panel19_val):\n", " dataset = val_dataset.copy()\n", " dataset.features = pr_orig_scaler.transform(dataset.features)\n", " y_true = val_dataset.labels[:,0]\n", " prot_attr = val_dataset.protected_attributes[:,0]\n", " sample_weight = val_dataset.instance_weights\n", " metrics_list=[]\n", "\n", " for eta in eta_list:\n", " eta_model = train_pr_model(eta=eta, train_dataset=train_dataset)\n", " y_val_pred_prob = eta_model.predict(dataset).scores\n", " for thr in thereshold_list:\n", " y_val_pred = (y_val_pred_prob[:, -1] > thr).astype(np.float64)\n", " metrics = get_metrics(y_true=y_true, y_pred=y_val_pred, prot_attr=prot_attr, sample_weight=sample_weight)\n", " metrics['threshold'] = thr\n", " metrics['eta']=eta\n", " metrics_list.append(metrics)\n", " return metrics_list\n" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " | base_rate_truth | \n", "statistical_parity_difference | \n", "disparate_impact_ratio | \n", "base_rate_preds | \n", "equal_opportunity_difference | \n", "average_odds_difference | \n", "conditional_demographic_disparity | \n", "smoothed_edf | \n", "df_bias_amplification | \n", "balanced_accuracy_score | \n", "threshold | \n", "eta | \n", "
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", "0.220729 | \n", "-0.034800 | \n", "0.965093 | \n", "0.982916 | \n", "0.000000 | \n", "-0.019825 | \n", "-0.096364 | \n", "2.519941 | \n", "1.816454 | \n", "0.510961 | \n", "0.01 | \n", "0.0 | \n", "
1 | \n", "0.220729 | \n", "-0.034800 | \n", "0.965093 | \n", "0.982916 | \n", "0.000000 | \n", "-0.019825 | \n", "-0.096364 | \n", "2.519941 | \n", "1.816454 | \n", "0.510961 | \n", "0.01 | \n", "0.0 | \n", "
2 | \n", "0.220729 | \n", "-0.124200 | \n", "0.873619 | \n", "0.932640 | \n", "-0.010704 | \n", "-0.074537 | \n", "-0.091926 | \n", "2.103743 | \n", "1.400256 | \n", "0.541498 | \n", "0.02 | \n", "0.0 | \n", "
3 | \n", "0.220729 | \n", "-0.204108 | \n", "0.786869 | \n", "0.875326 | \n", "-0.010715 | \n", "-0.119249 | \n", "-0.086965 | \n", "1.761479 | \n", "1.057992 | \n", "0.571315 | \n", "0.03 | \n", "0.0 | \n", "
4 | \n", "0.220729 | \n", "-0.248157 | \n", "0.726985 | \n", "0.808846 | \n", "-0.031087 | \n", "-0.148392 | \n", "-0.074629 | \n", "1.315223 | \n", "0.611735 | \n", "0.607733 | \n", "0.04 | \n", "0.0 | \n", "
... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
295 | \n", "0.220729 | \n", "-0.126389 | \n", "0.436114 | \n", "0.173155 | \n", "0.013595 | \n", "-0.070038 | \n", "-0.041047 | \n", "0.829851 | \n", "0.126363 | \n", "0.520541 | \n", "0.45 | \n", "100.0 | \n", "
296 | \n", "0.220729 | \n", "-0.125617 | \n", "0.428069 | \n", "0.168963 | \n", "0.014538 | \n", "-0.069146 | \n", "-0.041597 | \n", "0.848471 | \n", "0.144983 | \n", "0.520766 | \n", "0.46 | \n", "100.0 | \n", "
297 | \n", "0.220729 | \n", "-0.123271 | \n", "0.424774 | \n", "0.164573 | \n", "0.014929 | \n", "-0.067455 | \n", "-0.041689 | \n", "0.856199 | \n", "0.152711 | \n", "0.521178 | \n", "0.47 | \n", "100.0 | \n", "
298 | \n", "0.220729 | \n", "-0.122407 | \n", "0.418860 | \n", "0.161254 | \n", "0.012728 | \n", "-0.067504 | \n", "-0.042082 | \n", "0.870218 | \n", "0.166730 | \n", "0.522749 | \n", "0.48 | \n", "100.0 | \n", "
299 | \n", "0.220729 | \n", "-0.118762 | \n", "0.418915 | \n", "0.156472 | \n", "0.004199 | \n", "-0.068461 | \n", "-0.041838 | \n", "0.870086 | \n", "0.166599 | \n", "0.523392 | \n", "0.49 | \n", "100.0 | \n", "
300 rows × 12 columns
\n", "