算法公平性分析工具:AIF360 与 Fairlearn 实践
引言:为何需要关注算法公平性
算法已渗透到招聘、信贷、司法、医疗等决策场景,但数据偏见可能导致模型对特定群体产生系统性歧视。例如,招聘算法可能因历史数据偏向男性而压低女性候选人的评分。算法公平性分析正是识别并缓解这类不公的关键手段。本教程将带你掌握两套主流开源工具——IBM的AIF360与微软的Fairlearn——从检测偏见指标到应用缓解策略的完整实践流程。
准备:理解公平性的核心概念
在动手前,需先认清几种常见的不公平表现形式和度量指标。
- 群体公平性(Group Fairness):要求不同受保护群体(如性别、种族)在模型结果上具备统计均等性。典型指标包括:
- 统计均等差(Statistical Parity Difference):两组获得有利结果的概率差,理想值为0。
- 差别影响(Disparate Impact):弱势组与优势组有利结果比率,美国法律中通常以0.8为阈值。
- 个体公平性(Individual Fairness):相似个体应得到相似预测,实现较复杂。
- 均衡几率(Equalized Odds):要求真阳性率和假阳性率在各组间相等。
- 均等机会(Equal Opportunity):仅要求真阳性率(召回率)相等,是均衡几率的放宽版本。
教程将围绕这些指标展开,使用收入预测数据集(Adult dataset) 作为基准,目标是判断个人年收入是否超过$50K,敏感属性为“性别”。
环境搭建
确保已安装Python 3.8+,然后安装关键库:
pip install aif360 fairlearn pandas numpy scikit-learn
导入必要模块:
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# AIF360模块
from aif360.datasets import AdultDataset
from aif360.metrics import BinaryLabelDatasetMetric, ClassificationMetric
from aif360.algorithms.preprocessing import Reweighing
from aif360.algorithms.inprocessing import AdversarialDebiasing
# Fairlearn模块
from fairlearn.metrics import MetricFrame, selection_rate, true_positive_rate
from fairlearn.reductions import ExponentiatedGradient, DemographicParity, EqualizedOdds
数据准备与预偏见检测
加载并转换数据
AIF360提供标准封装,可直接获取Adult数据集:
dataset_orig = AdultDataset(
protected_attribute_names=['sex'], # 敏感属性
privileged_classes=[['Male']], # 优势群体
features_to_drop=['fnlwgt'] # 移除无用列
)
# 划分训练集和测试集
dataset_orig_train, dataset_orig_test = dataset_orig.split([0.7], shuffle=True, seed=42)
此时数据以BinaryLabelDataset格式存储,包含特征、标签和受保护属性。
基线模型训练
先用逻辑回归建立一个不施加任何公平性干预的基线模型:
# 转换为numpy数组供sklearn使用
train_X = dataset_orig_train.features
train_y = dataset_orig_train.labels.ravel()
test_X = dataset_orig_test.features
test_y = dataset_orig_test.labels.ravel()
# 标准化
scaler = StandardScaler()
train_X = scaler.fit_transform(train_X)
test_X = scaler.transform(test_X)
# 训练
lr = LogisticRegression(max_iter=1000)
lr.fit(train_X, train_y)
y_pred = lr.predict(test_X)
使用AIF360量化偏见
将预测结果构造成AIF360可用的结构:
dataset_orig_test_pred = dataset_orig_test.copy()
dataset_orig_test_pred.labels = y_pred.reshape(-1, 1)
dataset_orig_test.scores = lr.predict_proba(test_X)[:, 1].reshape(-1, 1)
dataset_orig_test_pred.scores = dataset_orig_test.scores
计算公平性指标
# 使用BinaryLabelDatasetMetric查看整体分布差异
metric_train = BinaryLabelDatasetMetric(
dataset_orig_train,
unprivileged_groups=[{'sex': 0}], # 0代表Female
privileged_groups=[{'sex': 1}] # 1代表Male
)
print(f"训练集差别影响: {metric_train.disparate_impact():.3f}")
print(f"训练集统计均等差: {metric_train.statistical_parity_difference():.3f}")
# 使用ClassificationMetric查看预测结果上的偏差
class_metric = ClassificationMetric(
dataset_orig_test, dataset_orig_test_pred,
unprivileged_groups=[{'sex': 0}],
privileged_groups=[{'sex': 1}]
)
print(f"均等机会差: {class_metric.equal_opportunity_difference():.3f}")
print(f"均衡几率差: {class_metric.average_odds_difference():.3f}")
你会观察到统计均等差通常为负值(女性获得正预测的概率低于男性),差别影响可能远低于0.8,这证实了原始模型存在性别偏见。
偏见缓解策略实践
AIF360预处理:重加权法
重加权法通过调整每条样本的权重,使得训练数据中各组在标签分布上趋于平衡,从而间接消除偏见。
RW = Reweighing(
unprivileged_groups=[{'sex': 0}],
privileged_groups=[{'sex': 1}]
)
dataset_transf_train = RW.fit_transform(dataset_orig_train)
# 获取变换后的样本权重
weights = dataset_transf_train.instance_weights.ravel()
# 用加权数据重新训练逻辑回归
lr_rw = LogisticRegression(max_iter=1000)
lr_rw.fit(train_X, train_y, sample_weight=weights)
y_pred_rw = lr_rw.predict(test_X)
重新评估:
dataset_transf_test_pred = dataset_orig_test.copy()
dataset_transf_test_pred.labels = y_pred_rw.reshape(-1, 1)
class_metric_rw = ClassificationMetric(
dataset_orig_test, dataset_transf_test_pred,
unprivileged_groups=[{'sex': 0}],
privileged_groups=[{'sex': 1}]
)
print(f"重加权后差别影响: {class_metric_rw.disparate_impact():.3f}")
通常差别影响会显著向1.0靠近,代价是整体准确率可能轻微下降。
Fairlearn后处理:阈值优化受均等机会约束
Fairlearn的ThresholdOptimizer可对已训练好的分类器调整决策阈值,使其满足指定的公平性约束。
from fairlearn.postprocessing import ThresholdOptimizer
# Fairlearn需要敏感特征作为输入,转换为DataFrame更方便
sensitive_test = pd.Series(dataset_orig_test.protected_attributes.ravel(), name='sex')
original_probas = lr.predict_proba(test_X)[:, 1]
postproc = ThresholdOptimizer(
estimator=lr,
constraints="equalized_odds", # 可改为 "demographic_parity"
prefit=True
)
postproc.fit(train_X, train_y, sensitive_features=dataset_orig_train.protected_attributes.ravel())
y_pred_eo = postproc.predict(test_X, sensitive_features=sensitive_test)
使用Fairlearn的MetricFrame一键评估多项指标:
metrics = {
'accuracy': lambda y_true, y_pred: (y_true == y_pred).mean(),
'selection_rate': selection_rate,
'tpr': true_positive_rate
}
metric_frame = MetricFrame(
metrics=metrics,
y_true=test_y,
y_pred=y_pred_eo,
sensitive_features=sensitive_test
)
print(metric_frame.overall)
print(metric_frame.by_group)
输出将展示各组准确性、选择率和真阳性率,可直观验证约束是否满足。
Fairlearn内处理:指数梯度下降 + 人口均等约束
内处理算法直接在训练过程中嵌入公平性目标,获得兼顾公平与准确性的模型。
exp_grad = ExponentiatedGradient(
estimator=LogisticRegression(max_iter=1000),
constraints=DemographicParity(),
eps=0.01 # 允许的人口均等违反边界
)
exp_grad.fit(train_X, train_y, sensitive_features=dataset_orig_train.protected_attributes.ravel())
y_pred_eg = exp_grad.predict(test_X)
# 评估
metric_frame_eg = MetricFrame(
metrics=metrics,
y_true=test_y,
y_pred=y_pred_eg,
sensitive_features=sensitive_test
)
print("—— ExponentiatedGradient结果 ——")
print(metric_frame_eg.by_group)
内处理通常能比后处理更好地权衡性能,但训练时间较长。
综合分析:权衡与选择
| 方法 | 公平性提升 | 性能影响 | 实现难度 | 适用阶段 |
|---|---|---|---|---|
| 重加权 (AIF360) | 中等 | 轻微准确率下降 | 低 | 预处理 |
| 阈值优化 (Fairlearn) | 高(可强制满足) | 可接受准确率损失 | 低 | 后处理 |
| ExponentiatedGradient (Fairlearn) | 高 | 较平衡 | 中 | 训练中 |
实践中,应首先确定业务可接受的公平性指标(如差别影响 > 0.8 或统计均等差 < 0.1),然后尝试多种方法,通过帕累托前沿选择最佳解。
最佳实践提醒
- 敏感属性编码:AIF360默认将
protected_attribute中0视为非优势组,1视为优势组。务必核对编码方向。 - 样本权重泄露:使用AIF360重加权时,确保样本权重仅用于训练,评估时不要继续加权。
- 多敏感属性:当涉及交叉属性(如性别×种族)时,可定义复合分组,例如
[{'sex':0, 'race':0}]。 - 公平性与准确性的取舍:没有免费午餐,需在特定情境下定义最小可接受准确率与最大可容忍歧视程度。
- 模型不应是唯一焦点:数据收集、标签定义、问题框架同样可能引入偏见,工具仅解决建模环节。
结语
AIF360与Fairlearn为算法公平性提供了从诊断到修复的完整工具链。AIF360擅长标准化数据集封装与全面的度量库,Fairlearn则在灵活的后处理/内处理及交互式仪表板上更胜一筹。结合两者,你可以在项目全生命周期中嵌入公平性审查,构建更负责任的AI系统。