Bias measuring and mitigation with Disparate impact remover#
This tutorial demonstrates how to implement the “Disparate impact remover” preprocessing method to enhance fairness in regression models using the holisticai library.
First, install the holisticai package if you haven’t already:
!pip install holisticai[all]
Then, import the necessary libraries.
[27]:
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from holisticai.datasets import load_dataset
from holisticai.bias.mitigation import DisparateImpactRemover
from holisticai.bias.metrics import regression_bias_metrics
np.random.seed(0)
import warnings
warnings.filterwarnings("ignore")
Load the proprocessed “Communities and Crime” dataset.
[28]:
dataset = load_dataset('us_crime', protected_attribute="race")
dataset = dataset.train_test_split(test_size=0.2, random_state=0)
train_data = dataset['train']
test_data = dataset['test']
dataset
[28]:
[DatasetDict]
train [Dataset]
Instances: 1594
Features: X , y , p_attrs , group_a , group_b
Metadata: race: {'group_a': 'racePctWhite>0.5', 'group_b': 'racePctWhite<=0.5'}
test [Dataset]
Instances: 399
Features: X , y , p_attrs , group_a , group_b
Metadata: race: {'group_a': 'racePctWhite>0.5', 'group_b': 'racePctWhite<=0.5'}
Bias Mitigation#
Traditional Implementation#
We will implement the “Grid search reduction” method, an in-processing technique to mitigate bias in the regression model.
[29]:
LR = LinearRegression()
corr = DisparateImpactRemover()
processed_data = corr.fit_transform(train_data['X'], train_data['group_a'], train_data['group_b'])
LR.fit(processed_data, train_data['y'])
processed_data = corr.transform(test_data['X'], test_data['group_a'], test_data['group_b'])
y_pred = LR.predict(processed_data)
df_dir = regression_bias_metrics(
test_data['group_a'],
test_data['group_b'],
y_pred,
test_data['y'],
metric_type='both'
)
df_dir
[29]:
| Value | Reference | |
|---|---|---|
| Metric | ||
| Disparate Impact Q90 | 0.130820 | 1 |
| Disparate Impact Q80 | 0.227560 | 1 |
| Disparate Impact Q50 | 0.478532 | 1 |
| Statistical Parity Q50 | -0.450590 | 0 |
| No Disparate Impact Level | 0.100759 | - |
| Average Score Difference | -0.198491 | 0 |
| Average Score Ratio | 0.502665 | 1 |
| Z Score Difference | -1.439428 | 0 |
| Max Statistical Parity | 0.494543 | 0 |
| Statistical Parity AUC | 0.316460 | 0 |
| RMSE Ratio | 0.488134 | 1 |
| RMSE Ratio Q80 | 0.693084 | 1 |
| MAE Ratio | 0.440471 | 1 |
| MAE Ratio Q80 | 0.676258 | 1 |
| Correlation Difference | 0.057359 | 0 |
[30]:
dispimp_rmse = mean_squared_error(dataset['test']['y'], y_pred, squared=False)
print("RMS error: {}".format(dispimp_rmse))
RMS error: 0.16073221630461687
Pipeline implementation#
[31]:
from holisticai.pipeline import Pipeline
model = LinearRegression()
pipeline = Pipeline(
steps=[
("bm_preprocessing", DisparateImpactRemover()),
("model", model),
]
)
# X, y, group_a, group_b = train_data
fit_params = {
"bm__group_a": train_data['group_a'],
"bm__group_b": train_data['group_b']
}
pipeline.fit(train_data['X'], train_data['y'], **fit_params)
pipeline
[31]:
[Pipeline]
bm_preprocessing [DisparateImpactRemover]
model [LinearRegression]
[32]:
# X, y, group_a, group_b = test_data
predict_params = {
"bm__group_a": test_data['group_a'],
"bm__group_b": test_data['group_b'],
}
y_pred = pipeline.predict(test_data['X'], **predict_params)
df_disprem = regression_bias_metrics(
test_data['group_a'],
test_data['group_b'],
y_pred,
test_data['y'],
metric_type='both'
)
df_disprem
[32]:
| Value | Reference | |
|---|---|---|
| Metric | ||
| Disparate Impact Q90 | 0.130820 | 1 |
| Disparate Impact Q80 | 0.227560 | 1 |
| Disparate Impact Q50 | 0.478532 | 1 |
| Statistical Parity Q50 | -0.450590 | 0 |
| No Disparate Impact Level | 0.100759 | - |
| Average Score Difference | -0.198491 | 0 |
| Average Score Ratio | 0.502665 | 1 |
| Z Score Difference | -1.439428 | 0 |
| Max Statistical Parity | 0.494543 | 0 |
| Statistical Parity AUC | 0.316460 | 0 |
| RMSE Ratio | 0.488134 | 1 |
| RMSE Ratio Q80 | 0.693084 | 1 |
| MAE Ratio | 0.440471 | 1 |
| MAE Ratio Q80 | 0.676258 | 1 |
| Correlation Difference | 0.057359 | 0 |
[33]:
dispimp_rmse = mean_squared_error(dataset['test']['y'], y_pred, squared=False)
print("RMS error: {}".format(dispimp_rmse))
RMS error: 0.16073221630461687