Source code for holisticai.bias.mitigation.postprocessing.mcmf_clustering.transformer
from __future__ import annotations
from typing import TYPE_CHECKING
from holisticai.bias.mitigation.postprocessing.mcmf_clustering.algorithm import Algorithm
from holisticai.utils.transformers.bias import BMPostprocessing as BMPost
if TYPE_CHECKING:
import numpy as np
[docs]
class MCMF(BMPost):
"""
Minimal Cluster Modification for Fairnes (MCMF) [1]_ is focused on the minimal change it so that the clustering is still\
of good quality and fairer.
Parameters
----------
metric : str
Measure function used in the objective function.
The metrics available are:
["constant", "L1", "L2"]
solver : str
Algorithm name used to solve the standard form problem. Solver supported must depend of your scipy package version.
for scipy 1.9.0 the solvers available are:
["highs", "highs-ds", "highs-ipm"]
group_mode : str
Set what groups will be fitted: ['a', 'b', 'ab']
verbose : int
If > 0 , then print logs.
Examples
--------
>>> from holisticai.bias.mitigation import MCMF
>>> mitigator = MCMF(**params)
>>> mitigator.fit_transform(X, y_pred, group_a, group_b)
>>> test_data_transformed = mitigator.transform(X, y_pred, group_a, group_b)
References
----------
.. [1] Davidson, Ian, and S. S. Ravi. "Making existing clusterings fairer: Algorithms, complexity results and insights."\
Proceedings of the AAAI Conference on Artificial Intelligence. Vol. 34. No. 04. 2020.
"""
def __init__(self, metric: str = "L1", solver: str = "highs", group_mode="a", verbose=0):
self.metric = metric
self.group_mode = group_mode
self.solver = solver
self.verbose = verbose
self.algorithm = Algorithm(metric=metric, solver=solver, verbose=verbose)
[docs]
def fit_transform(
self,
X: np.ndarray,
y_pred: np.ndarray,
group_a: np.ndarray,
group_b: np.ndarray,
centroids: np.ndarray,
) -> dict[str, np.ndarray]:
"""
Fit and transform the MCMF algorithm.
Description
----------
Fit the MCMF algorithm and transform the predicted vector.
Parameters
----------
X : matrix-like
Input matrix (nb_examples, nb_features)
y_pred : array-like
Predicted vector (nb_examples,)
group_a : array-like
Group membership vector (binary)
group_b : array-like
Group membership vector (binary)
centroids : ndarray
Centroids array
Returns
-------
dict
A dictionary with new predictions
"""
return self.transform(X, y_pred, group_a, group_b, centroids)
[docs]
def transform(
self,
X: np.ndarray,
y_pred: np.ndarray,
group_a: np.ndarray,
group_b: np.ndarray,
centroids: np.ndarray,
) -> dict[str, np.ndarray]:
"""
Modify y_pred using MCMF algorithm.
Description
----------
Build parameters for the objetive function and call the solver to find the algorithm parameters.
Parameters
----------
X : matrix-like
Input matrix (nb_examples, nb_features)
y_pred : array-like
Predicted vector (nb_examples,)
group_a : array-like
Group membership vector (binary)
group_b : array-like
Group membership vector (binary)
centroids : ndarray
Centroids array
Returns
-------
dict
A dictionary with new predictions
"""
params = self._load_data(X=X, y_pred=y_pred, group_a=group_a, group_b=group_b)
group_a = params["group_a"] == 1
group_b = params["group_b"] == 1
X = params["X"]
y_pred = params["y_pred"]
if isinstance(centroids, str):
centroids = getattr(self.estimator_hdl.estimator, centroids)
if self.group_mode in ["a", "b"]:
group = group_a if self.group_mode == "a" else group_b
new_y_pred = self.algorithm.transform(X=X, y_pred=y_pred, group=group, centroids=centroids)
elif self.group_mode == "ab":
new_y_pred = y_pred.copy()
for group in [group_a, group_b]:
new_y_pred = self.algorithm.transform(X=X, y_pred=new_y_pred, group=group, centroids=centroids)
return {"y_pred": new_y_pred}