A Python package to assess and improve fairness of machine learning models
—
In-processing mitigation techniques that cast fairness constraints as Lagrange multipliers in constrained optimization problems. These algorithms retrain models to satisfy fairness constraints while maintaining predictive performance by reducing the problem back to standard machine learning training.
Implements the exponentiated gradient algorithm for fair classification and regression. Uses iterative reweighting to satisfy fairness constraints while optimizing for accuracy.
class ExponentiatedGradient:
def __init__(self, estimator, constraints, *, objective=None, eps=0.01,
max_iter=50, nu=None, eta0=2.0, run_linprog_step=True,
sample_weight_name="sample_weight"):
"""
Exponentiated gradient algorithm for fair machine learning.
Parameters:
- estimator: sklearn estimator, base learning algorithm
- constraints: Moment object, fairness constraint to satisfy
- objective: Moment object, optimization objective (default: ErrorRate)
- eps: float, allowed fairness constraint violation
- max_iter: int, maximum number of iterations
- nu: float, convergence threshold (auto-set if None)
- eta0: float, initial learning rate
- run_linprog_step: bool, whether to run linear programming step
- sample_weight_name: str, parameter name for sample weights in estimator
"""
def fit(self, X, y, *, sensitive_features, sample_weight=None):
"""
Fit the model with fairness constraints.
Parameters:
- X: array-like, feature matrix
- y: array-like, target values
- sensitive_features: array-like, sensitive feature values
- sample_weight: array-like, optional sample weights
Returns:
self
"""
def predict(self, X):
"""
Make predictions using the fitted fair model.
Parameters:
- X: array-like, feature matrix
Returns:
array-like: Predicted values
"""
def predict_proba(self, X):
"""Predict class probabilities (classification only)."""
@property
def predictors_(self):
"""List of predictors from the optimization process."""
@property
def weights_(self):
"""Weights for combining predictors."""
@property
def last_iter_(self):
"""Number of iterations run."""
@property
def best_gap_(self):
"""Best constraint violation achieved."""
@property
def best_iter_(self):
"""Iteration with best constraint violation."""from fairlearn.reductions import ExponentiatedGradient, DemographicParity
from sklearn.linear_model import LogisticRegression
# Create constraint and estimator
constraint = DemographicParity()
estimator = LogisticRegression()
# Create and fit fair model
fair_model = ExponentiatedGradient(
estimator=estimator,
constraints=constraint,
eps=0.01,
max_iter=50
)
fair_model.fit(X_train, y_train, sensitive_features=A_train)
y_pred = fair_model.predict(X_test)Implements grid search over Lagrange multipliers for fair machine learning. Systematically explores the trade-off between fairness and accuracy.
class GridSearch:
def __init__(self, estimator, constraints, *, selection_rule="tradeoff",
constraint_weight=0.5, grid_size=10, grid_limit=2.0,
grid_offset=None, sample_weight_name="sample_weight"):
"""
Grid search approach to fairness constraints.
Parameters:
- estimator: sklearn estimator, base learning algorithm
- constraints: Moment object, fairness constraint to satisfy
- selection_rule: str, how to select solution ("tradeoff" or "best_tradeoff")
- constraint_weight: float, weight for constraint vs. loss in tradeoff
- grid_size: int, number of Lagrange multipliers to try
- grid_limit: float, largest Lagrange multiplier to try
- grid_offset: dict, base values for Lagrange multipliers
- sample_weight_name: str, parameter name for sample weights
"""
def fit(self, X, y, *, sensitive_features, sample_weight=None):
"""
Fit the model with fairness constraints using grid search.
Parameters:
- X: array-like, feature matrix
- y: array-like, target values
- sensitive_features: array-like, sensitive feature values
- sample_weight: array-like, optional sample weights
Returns:
self
"""
def predict(self, X):
"""Make predictions using the selected fair model."""
def predict_proba(self, X):
"""Predict class probabilities (classification only)."""
@property
def predictors_(self):
"""All predictors from the grid search."""
@property
def lambda_vecs_(self):
"""Lagrange multiplier vectors from grid search."""
@property
def objectives_(self):
"""Objective values for each grid point."""
@property
def gammas_(self):
"""Constraint violations for each grid point."""
@property
def best_idx_(self):
"""Index of the selected predictor."""Abstract base classes that define the interface for fairness constraints.
class Moment:
"""Base class for moment constraints."""
def load_data(self, X, y, *, sensitive_features, sample_weight=None):
"""Load and validate input data."""
def gamma(self, predictor):
"""Calculate constraint violation for a predictor."""
def project_lambda(self, lambda_vec):
"""Project Lagrange multiplier to feasible region."""
def signed_weights(self, lambda_vec):
"""Calculate signed weights for constraint."""
class ClassificationMoment(Moment):
"""Base class for classification fairness constraints."""
class LossMoment(Moment):
"""Base class for loss-based fairness constraints."""Constraints that require equal positive prediction rates across groups.
class DemographicParity(ClassificationMoment):
"""
Demographic parity constraint requiring equal selection rates.
This constraint is satisfied when P(Y_hat=1 | A=a) is equal for all values of A,
where A represents the sensitive feature and Y_hat represents predictions.
"""
def __init__(self):
"""Initialize demographic parity constraint."""Constraints that require equal true positive and false positive rates across groups.
class EqualizedOdds(ClassificationMoment):
"""
Equalized odds constraint requiring equal TPR and FPR across groups.
This constraint is satisfied when both:
- P(Y_hat=1 | Y=1, A=a) is equal for all values of A (equal TPR)
- P(Y_hat=1 | Y=0, A=a) is equal for all values of A (equal FPR)
"""
def __init__(self):
"""Initialize equalized odds constraint."""Constraints that require equal true positive rates across groups.
class TruePositiveRateParity(ClassificationMoment):
"""
Equal opportunity constraint requiring equal true positive rates.
This constraint is satisfied when P(Y_hat=1 | Y=1, A=a) is equal
for all values of A.
"""
def __init__(self):
"""Initialize true positive rate parity constraint."""
class FalsePositiveRateParity(ClassificationMoment):
"""False positive rate parity constraint."""
class ErrorRateParity(ClassificationMoment):
"""Error rate parity constraint."""General utility parity constraints that can be customized for different fairness criteria.
class UtilityParity(ClassificationMoment):
"""
General utility parity constraint.
Allows specification of different utility functions and events
for flexible fairness definitions.
"""
def __init__(self, *, difference_bound=None, ratio_bound=None):
"""
Initialize utility parity constraint.
Parameters:
- difference_bound: float, maximum allowed difference in utilities
- ratio_bound: tuple, (lower_bound, upper_bound) for utility ratios
"""Constraints based on error rates and loss functions.
class ErrorRate(ClassificationMoment):
"""
Error rate constraint for equal error rates across groups.
"""
def __init__(self):
"""Initialize error rate constraint."""Constraints that bound the loss within each sensitive group.
class BoundedGroupLoss(LossMoment):
"""
Constraint that bounds loss within each group.
Ensures that no group has loss significantly higher than others.
"""
def __init__(self, loss, *, upper_bound):
"""
Initialize bounded group loss constraint.
Parameters:
- loss: loss function object
- upper_bound: float, maximum allowed loss for any group
"""
def load_data(self, X, y, *, sensitive_features, sample_weight=None):
"""Load data and prepare for constraint computation."""
def gamma(self, predictor):
"""Calculate constraint violation."""Loss functions used with bounded group loss constraints.
class SquareLoss:
"""
Square loss function: (y_true - y_pred)^2
"""
def eval(self, y_true, y_pred):
"""Evaluate square loss."""
def eval_derivative(self, y_true, y_pred):
"""Evaluate derivative of square loss."""
class AbsoluteLoss:
"""
Absolute loss function: |y_true - y_pred|
"""
def eval(self, y_true, y_pred):
"""Evaluate absolute loss."""
def eval_derivative(self, y_true, y_pred):
"""Evaluate derivative of absolute loss."""
class ZeroOneLoss(AbsoluteLoss):
"""
Zero-one loss function: I(y_true != y_pred)
Inherits from AbsoluteLoss but evaluates to 0 or 1.
"""You can create custom constraints by inheriting from the base constraint classes:
from fairlearn.reductions import ClassificationMoment
class CustomFairnessConstraint(ClassificationMoment):
def __init__(self):
super().__init__()
def load_data(self, X, y, *, sensitive_features, sample_weight=None):
# Implement data loading logic
pass
def gamma(self, predictor):
# Implement constraint violation calculation
passReduction algorithms are designed to work seamlessly with scikit-learn estimators:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# Use any sklearn estimator
rf = RandomForestClassifier(n_estimators=100)
fair_rf = ExponentiatedGradient(rf, DemographicParity())
# Works with sklearn utilities
scores = cross_val_score(fair_rf, X, y, cv=5)Install with Tessl CLI
npx tessl i tessl/pypi-fairlearn