erasmopurif's picture
First commit
d2a8669
import numpy as np
from aif360.algorithms import Transformer
from aif360.metrics import utils
class Reweighing(Transformer):
"""Reweighing is a preprocessing technique that Weights the examples in each
(group, label) combination differently to ensure fairness before
classification [4]_.
References:
.. [4] F. Kamiran and T. Calders, "Data Preprocessing Techniques for
Classification without Discrimination," Knowledge and Information
Systems, 2012.
"""
def __init__(self, unprivileged_groups, privileged_groups):
"""
Args:
unprivileged_groups (list(dict)): Representation for unprivileged
group.
privileged_groups (list(dict)): Representation for privileged group.
"""
super(Reweighing, self).__init__(
unprivileged_groups=unprivileged_groups,
privileged_groups=privileged_groups)
self.unprivileged_groups = unprivileged_groups
self.privileged_groups = privileged_groups
self.w_p_fav = 1.
self.w_p_unfav = 1.
self.w_up_fav = 1.
self.w_up_unfav = 1.
def fit(self, dataset):
"""Compute the weights for reweighing the dataset.
Args:
dataset (BinaryLabelDataset): Dataset containing true labels.
Returns:
Reweighing: Returns self.
"""
(priv_cond, unpriv_cond, fav_cond, unfav_cond,
cond_p_fav, cond_p_unfav, cond_up_fav, cond_up_unfav) =\
self._obtain_conditionings(dataset)
n = np.sum(dataset.instance_weights, dtype=np.float64)
n_p = np.sum(dataset.instance_weights[priv_cond], dtype=np.float64)
n_up = np.sum(dataset.instance_weights[unpriv_cond], dtype=np.float64)
n_fav = np.sum(dataset.instance_weights[fav_cond], dtype=np.float64)
n_unfav = np.sum(dataset.instance_weights[unfav_cond], dtype=np.float64)
n_p_fav = np.sum(dataset.instance_weights[cond_p_fav], dtype=np.float64)
n_p_unfav = np.sum(dataset.instance_weights[cond_p_unfav],
dtype=np.float64)
n_up_fav = np.sum(dataset.instance_weights[cond_up_fav],
dtype=np.float64)
n_up_unfav = np.sum(dataset.instance_weights[cond_up_unfav],
dtype=np.float64)
# reweighing weights
self.w_p_fav = n_fav*n_p / (n*n_p_fav)
self.w_p_unfav = n_unfav*n_p / (n*n_p_unfav)
self.w_up_fav = n_fav*n_up / (n*n_up_fav)
self.w_up_unfav = n_unfav*n_up / (n*n_up_unfav)
return self
def transform(self, dataset):
"""Transform the dataset to a new dataset based on the estimated
transformation.
Args:
dataset (BinaryLabelDataset): Dataset that needs to be transformed.
Returns:
dataset (BinaryLabelDataset): Dataset with transformed
instance_weights attribute.
"""
dataset_transformed = dataset.copy(deepcopy=True)
(_, _, _, _, cond_p_fav, cond_p_unfav, cond_up_fav, cond_up_unfav) =\
self._obtain_conditionings(dataset)
# apply reweighing
dataset_transformed.instance_weights[cond_p_fav] *= self.w_p_fav
dataset_transformed.instance_weights[cond_p_unfav] *= self.w_p_unfav
dataset_transformed.instance_weights[cond_up_fav] *= self.w_up_fav
dataset_transformed.instance_weights[cond_up_unfav] *= self.w_up_unfav
return dataset_transformed
##############################
#### Supporting functions ####
##############################
def _obtain_conditionings(self, dataset):
"""Obtain the necessary conditioning boolean vectors to compute
instance level weights.
"""
# conditioning
priv_cond = utils.compute_boolean_conditioning_vector(
dataset.protected_attributes,
dataset.protected_attribute_names,
condition=self.privileged_groups)
unpriv_cond = utils.compute_boolean_conditioning_vector(
dataset.protected_attributes,
dataset.protected_attribute_names,
condition=self.unprivileged_groups)
fav_cond = dataset.labels.ravel() == dataset.favorable_label
unfav_cond = dataset.labels.ravel() == dataset.unfavorable_label
# combination of label and privileged/unpriv. groups
cond_p_fav = np.logical_and(fav_cond, priv_cond)
cond_p_unfav = np.logical_and(unfav_cond, priv_cond)
cond_up_fav = np.logical_and(fav_cond, unpriv_cond)
cond_up_unfav = np.logical_and(unfav_cond, unpriv_cond)
return (priv_cond, unpriv_cond, fav_cond, unfav_cond,
cond_p_fav, cond_p_unfav, cond_up_fav, cond_up_unfav)