{ "cells": [ { "cell_type": "markdown", "id": "3c6823f0", "metadata": {}, "source": [ "***\n", "# Fairness with AutoMLx\n", "
by the Oracle AutoMLx Team
\n", "\n", "***" ] }, { "cell_type": "markdown", "id": "655754ac", "metadata": {}, "source": [ "Fairness Demo notebook.\n", "\n", "Copyright © 2025, Oracle and/or its affiliates.\n", "\n", "Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/" ] }, { "cell_type": "markdown", "id": "cd95dc31", "metadata": {}, "source": [ "## Overview of this Notebook\n", "In this notebook, we explore the fairness features of AutoMLx package. We start by training an AutoML model on the Census Income dataset. Later, we provide examples of how to evaluate the fairness of the model and the dataset. We also explore how the provided explanation techniques may help us gain more insight on the fairness of our model.\n", "\n", "## Prerequisites\n", " - Experience level: Novice (Python and Machine Learning)\n", " - Professional experience: Some industry experience" ] }, { "cell_type": "markdown", "id": "464059f0", "metadata": {}, "source": [ "## Table of Contents\n", "\n", "- Preliminaries\n", "- Quick Start\n", " - Load the Data\n", " - Train and evaluate an AutoML model\n", " - Evaluate the Fairness of the Model by Computing Statistical Parity\n", " - Compute Fairness Feature Importance\n", " - Mitigating a Model's Unfairness\n", "- In Depth: The Census Income Dataset\n", "- Unintended Bias and Fairness\n", " - Overview of the Fairness Metrics \n", " - Unintended Bias Detection\n", " - Measure the Compliance of a Model with a Fairness Metric \n", " - Train a Model Using Scikit-learn \n", " - Train a Model Using AutoML \n", " - Measure the Compliance of the True Labels of a Dataset with a Fairness Metric \n", " - Other Fairness Metrics \n", "- Revealing Bias with Explainability \n", " - Initializing an MLExplainer \n", " - Model Explanations (Global Feature Importance)\n", " - Model Fairness Explanations (Fairness Feature Importance)\n", " - Global vs Fairness Feature Importance\n", "- Model Bias Mitigation\n", "- References\n", " " ] }, { "cell_type": "markdown", "id": "c859da69", "metadata": {}, "source": [ "\n", "## Preliminaries\n", "\n", "Here we import some required libraries." ] }, { "cell_type": "code", "execution_count": 1, "id": "7904fd0b", "metadata": { "execution": { "iopub.execute_input": "2025-05-22T12:06:57.317770Z", "iopub.status.busy": "2025-05-22T12:06:57.317261Z", "iopub.status.idle": "2025-05-22T12:06:57.845478Z", "shell.execute_reply": "2025-05-22T12:06:57.844771Z" } }, "outputs": [], "source": [ "\n", "%matplotlib inline\n", "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "markdown", "id": "b7d3181d", "metadata": {}, "source": [ "Load the required modules." ] }, { "cell_type": "code", "execution_count": 2, "id": "9cff2e32", "metadata": { "execution": { "iopub.execute_input": "2025-05-22T12:06:57.847712Z", "iopub.status.busy": "2025-05-22T12:06:57.847461Z", "iopub.status.idle": "2025-05-22T12:07:00.152896Z", "shell.execute_reply": "2025-05-22T12:07:00.152134Z" } }, "outputs": [], "source": [ "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import plotly.express as px\n", "import sklearn\n", "from sklearn.metrics import roc_auc_score\n", "from sklearn.datasets import fetch_openml\n", "from sklearn.model_selection import train_test_split\n", "\n", "# Settings for plots\n", "plt.rcParams['figure.figsize'] = [10, 7]\n", "plt.rcParams['font.size'] = 15\n", "\n", "import automlx" ] }, { "cell_type": "markdown", "id": "5dfd0c15", "metadata": {}, "source": [ "\n", "## Quick Start\n", "Here, we give an overview of the key features. We first load the Census Income dataset, train a model on it, evaluate its accuracy and fairness, and compute its fairness feature importance. All of these steps will be revisited again, but with more detail through the rest of the notebook.\n", "\n", "\n", "### Load the Data" ] }, { "cell_type": "code", "execution_count": 3, "id": "e11b57b1", "metadata": { "execution": { "iopub.execute_input": "2025-05-22T12:07:00.155809Z", "iopub.status.busy": "2025-05-22T12:07:00.155162Z", "iopub.status.idle": "2025-05-22T12:07:00.965603Z", "shell.execute_reply": "2025-05-22T12:07:00.964887Z" } }, "outputs": [ { "data": { "text/plain": [ "((29304, 14), (9769, 14), (9769, 14))" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dataset = fetch_openml(name='adult', as_frame=True)\n", "df, y = dataset.data, dataset.target\n", "\n", "# Several of the columns are incorrectly labeled as category type in the original dataset\n", "numeric_columns = ['age', 'capitalgain', 'capitalloss', 'hoursperweek']\n", "for col in df.columns:\n", " if col in numeric_columns:\n", " df[col] = df[col].astype(int)\n", " \n", "\n", "X_train, X_test, y_train, y_test = train_test_split(\n", " df, y.map({\">50K\": 1, \"<=50K\": 0}).astype(int), train_size=0.8, random_state=12345\n", ")\n", "\n", "X_train, X_val, y_train, y_val = train_test_split(\n", " X_train, y_train, train_size=0.75, random_state=12345\n", ")\n", "\n", "X_train.shape, X_val.shape, X_test.shape" ] }, { "cell_type": "markdown", "id": "e4c629f8", "metadata": {}, "source": [ "\n", "### Train and evaluate an AutoML model\n", "The AutoML API is quite simple to work with. We create an instance of the pipeline. Next, the training data is passed to `fit`." ] }, { "cell_type": "code", "execution_count": 4, "id": "931b915f", "metadata": { "execution": { "iopub.execute_input": "2025-05-22T12:07:00.968034Z", "iopub.status.busy": "2025-05-22T12:07:00.967406Z", "iopub.status.idle": "2025-05-22T12:10:33.652756Z", "shell.execute_reply": "2025-05-22T12:10:33.652159Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:01,228] [automlx.backend] Overwriting ray session directory to /tmp/5ap89gei/ray, which will be deleted at engine shutdown. If you wish to retain ray logs, provide _temp_dir in ray_setup dict of engine_opts when initializing the AutoMLx engine.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:06,268] [automlx.interface] Dataset shape: (29304,14)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:10,049] [sanerec.autotuning.parameter] Hyperparameter epsilon autotune range is set to its validation range. This could lead to long training times\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:10,533] [sanerec.autotuning.parameter] Hyperparameter repeat_quality_threshold autotune range is set to its validation range. This could lead to long training times\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:10,542] [sanerec.autotuning.parameter] Hyperparameter scope autotune range is set to its validation range. This could lead to long training times\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:10,613] [automlx.data_transform] Running preprocessing. Number of features: 15\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:11,281] [automlx.data_transform] Preprocessing completed. Took 0.668 secs\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:11,356] [automlx.process] Running Model Generation\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:11,414] [automlx.process] KNeighborsClassifier is disabled. The KNeighborsClassifier model is only recommended for datasets with less than 10000 samples and 1000 features.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:11,415] [automlx.process] SVC is disabled. The SVC model is only recommended for datasets with less than 10000 samples and 1000 features.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:11,417] [automlx.process] Model Generation completed.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:07:11,494] [automlx.model_selection] Running Model Selection\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[36m(run pid=2456350)\u001b[0m [LightGBM] [Info] Number of positive: 5613, number of negative: 17830\n", "\u001b[36m(run pid=2456350)\u001b[0m [LightGBM] [Info] Auto-choosing row-wise multi-threading, the overhead of testing was 0.001536 seconds.\n", "\u001b[36m(run pid=2456350)\u001b[0m You can set `force_row_wise=true` to remove the overhead.\n", "\u001b[36m(run pid=2456350)\u001b[0m And if memory is not enough, you can set `force_col_wise=true`.\n", "\u001b[36m(run pid=2456350)\u001b[0m [LightGBM] [Info] Total Bins 402\n", "\u001b[36m(run pid=2456350)\u001b[0m [LightGBM] [Info] Number of data points in the train set: 23443, number of used features: 15\n", "\u001b[36m(run pid=2456350)\u001b[0m [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.760568 -> initscore=1.155797\n", "\u001b[36m(run pid=2456350)\u001b[0m [LightGBM] [Info] Start training from score 1.155797\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:08:00,683] [automlx.model_selection] Model Selection completed - Took 49.188 sec - Selected models: [['XGBClassifier']]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:08:00,709] [automlx.adaptive_sampling] Running Adaptive Sampling. Dataset shape: (29304,16).\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:08:07,397] [automlx.trials] Adaptive Sampling completed - Took 6.6877 sec.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:08:07,753] [automlx.feature_selection] Starting feature ranking for XGBClassifier\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:08:18,930] [automlx.feature_selection] Feature Selection completed. Took 11.212 secs.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:08:18,964] [automlx.trials] Running Model Tuning for ['XGBClassifier']\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:10:22,911] [automlx.trials] Best parameters for XGBClassifier: {'learning_rate': 0.1, 'min_child_weight': 1, 'max_depth': 3, 'reg_alpha': 0, 'booster': 'gbtree', 'reg_lambda': 4.015253743332437, 'n_estimators': 276, 'use_label_encoder': False}\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:10:22,912] [automlx.trials] Model Tuning completed. Took: 123.948 secs\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:10:29,244] [automlx.interface] Re-fitting pipeline\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:10:29,260] [automlx.final_fit] Skipping updating parameter seed, already fixed by FinalFit_bfd43b97-b\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[2025-05-22 05:10:32,277] [automlx.interface] AutoMLx completed.\n" ] }, { "data": { "text/plain": [ "\n", " | equalized_odds | \n", "balanced_accuracy | \n", "multiplier_sex=Female | \n", "multiplier_sex=Male | \n", "
---|---|---|---|---|
0 | \n", "0.009424 | \n", "0.641006 | \n", "0.286225 | \n", "0.274094 | \n", "
1 | \n", "0.045260 | \n", "0.643569 | \n", "0.329184 | \n", "0.274094 | \n", "
2 | \n", "0.051810 | \n", "0.780020 | \n", "2.391568 | \n", "1.075606 | \n", "
3 | \n", "0.089462 | \n", "0.782425 | \n", "1.128523 | \n", "1.155845 | \n", "
4 | \n", "0.092094 | \n", "0.784028 | \n", "3.661628 | \n", "1.155845 | \n", "
5 | \n", "0.097304 | \n", "0.796155 | \n", "1.700147 | \n", "1.364635 | \n", "
6 | \n", "0.098925 | \n", "0.796144 | \n", "1.552707 | \n", "1.365998 | \n", "
7 | \n", "0.109423 | \n", "0.807433 | \n", "3.489373 | \n", "1.927306 | \n", "
8 | \n", "0.117355 | \n", "0.810422 | \n", "3.622867 | \n", "2.003104 | \n", "
9 | \n", "0.125453 | \n", "0.816404 | \n", "8.367842 | \n", "3.129173 | \n", "
10 | \n", "0.140791 | \n", "0.817215 | \n", "8.367842 | \n", "3.447235 | \n", "
11 | \n", "0.157259 | \n", "0.819532 | \n", "7.183583 | \n", "3.448481 | \n", "
12 | \n", "0.171501 | \n", "0.819617 | \n", "7.183583 | \n", "3.791032 | \n", "
13 | \n", "0.178872 | \n", "0.820086 | \n", "2.881528 | \n", "2.715778 | \n", "
14 | \n", "0.182927 | \n", "0.820371 | \n", "5.550752 | \n", "3.545257 | \n", "
15 | \n", "0.186979 | \n", "0.821633 | \n", "2.903717 | \n", "2.822292 | \n", "
16 | \n", "0.188233 | \n", "0.822366 | \n", "6.112919 | \n", "3.882026 | \n", "
17 | \n", "0.221730 | \n", "0.822364 | \n", "2.922310 | \n", "3.448481 | \n", "
18 | \n", "0.226364 | \n", "0.822843 | \n", "2.087503 | \n", "3.129173 | \n", "
19 | \n", "0.228204 | \n", "0.823190 | \n", "2.028335 | \n", "3.146395 | \n", "
20 | \n", "0.241351 | \n", "0.823587 | \n", "2.100868 | \n", "3.448481 | \n", "
21 | \n", "0.263570 | \n", "0.824207 | \n", "2.165387 | \n", "3.973868 | \n", "
22 | \n", "0.276534 | \n", "0.825213 | \n", "1.767152 | \n", "3.973868 | \n", "
\n", " | age | \n", "workclass | \n", "fnlwgt | \n", "education | \n", "education-num | \n", "marital-status | \n", "occupation | \n", "relationship | \n", "race | \n", "sex | \n", "capitalgain | \n", "capitalloss | \n", "hoursperweek | \n", "native-country | \n", "
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", "2 | \n", "State-gov | \n", "77516.0 | \n", "Bachelors | \n", "13.0 | \n", "Never-married | \n", "Adm-clerical | \n", "Not-in-family | \n", "White | \n", "Male | \n", "1 | \n", "0 | \n", "2 | \n", "United-States | \n", "
1 | \n", "3 | \n", "Self-emp-not-inc | \n", "83311.0 | \n", "Bachelors | \n", "13.0 | \n", "Married-civ-spouse | \n", "Exec-managerial | \n", "Husband | \n", "White | \n", "Male | \n", "0 | \n", "0 | \n", "0 | \n", "United-States | \n", "
2 | \n", "2 | \n", "Private | \n", "215646.0 | \n", "HS-grad | \n", "9.0 | \n", "Divorced | \n", "Handlers-cleaners | \n", "Not-in-family | \n", "White | \n", "Male | \n", "0 | \n", "0 | \n", "2 | \n", "United-States | \n", "
3 | \n", "3 | \n", "Private | \n", "234721.0 | \n", "11th | \n", "7.0 | \n", "Married-civ-spouse | \n", "Handlers-cleaners | \n", "Husband | \n", "Black | \n", "Male | \n", "0 | \n", "0 | \n", "2 | \n", "United-States | \n", "
4 | \n", "1 | \n", "Private | \n", "338409.0 | \n", "Bachelors | \n", "13.0 | \n", "Married-civ-spouse | \n", "Prof-specialty | \n", "Wife | \n", "Black | \n", "Female | \n", "0 | \n", "0 | \n", "2 | \n", "Cuba | \n", "
Pipeline(steps=[('preprocessor', OneHotEncoder(handle_unknown='ignore')),\n", " ('classifier', RandomForestClassifier())])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
Pipeline(steps=[('preprocessor', OneHotEncoder(handle_unknown='ignore')),\n", " ('classifier', RandomForestClassifier())])
OneHotEncoder(handle_unknown='ignore')
RandomForestClassifier()
\n", " | equalized_odds | \n", "balanced_accuracy | \n", "multiplier_sex=Female | \n", "multiplier_sex=Male | \n", "
---|---|---|---|---|
0 | \n", "0.015180 | \n", "0.627949 | \n", "0.256520 | \n", "0.233259 | \n", "
1 | \n", "0.032846 | \n", "0.641992 | \n", "0.329184 | \n", "0.274094 | \n", "
2 | \n", "0.045547 | \n", "0.779605 | \n", "2.391568 | \n", "1.075606 | \n", "
3 | \n", "0.100283 | \n", "0.796928 | \n", "1.552707 | \n", "1.365998 | \n", "
4 | \n", "0.126104 | \n", "0.808347 | \n", "2.258122 | \n", "1.794733 | \n", "
5 | \n", "0.129747 | \n", "0.816347 | \n", "8.367842 | \n", "3.129173 | \n", "
6 | \n", "0.159976 | \n", "0.819159 | \n", "7.183583 | \n", "3.448481 | \n", "
7 | \n", "0.209161 | \n", "0.821925 | \n", "3.661628 | \n", "3.447235 | \n", "
8 | \n", "0.230702 | \n", "0.822459 | \n", "2.028335 | \n", "3.146395 | \n", "
9 | \n", "0.288054 | \n", "0.823671 | \n", "1.403050 | \n", "3.973868 | \n", "