本文目录导读:

检测Python模型中的偏见(Bias)是一个涉及数据、模型和评估的综合性过程,以下是一套系统化的检测方法,从概念到具体代码实现。
核心概念与检测维度
首先明确需要检测的偏见类型:
- 数据偏见:训练数据本身存在的性别、种族等不均衡
- 算法偏见:模型对不同群体预测表现的差异
- 表征偏见:NLP模型中词向量的性别/种族关联
检测工具与库
# 主要工具库 # 1. AIF360 - IBM的公平性工具包 from aif360.datasets import BinaryLabelDataset from aif360.metrics import ClassificationMetric, BinaryLabelDatasetMetric from aif360.algorithms.preprocessing import Reweighing # 2. Fairlearn - 微软的公平性工具 from fairlearn.metrics import MetricFrame, demographic_parity_difference, equalized_odds_difference from fairlearn.datasets import fetch_adult # 3. 其他辅助库 import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression
数据层面检测
数据分布分析
def check_data_bias(df, protected_attr, target_col):
"""
检查数据中保护属性与目标变量的分布偏差
"""
# 计算各群体在目标变量中的比例
for group in df[protected_attr].unique():
subset = df[df[protected_attr] == group]
positive_rate = subset[target_col].mean()
print(f"{protected_attr}={group}: 正类比例 = {positive_rate:.2%}")
# 计算群体间比例差异
proportions = df.groupby(protected_attr)[target_col].mean()
print(f"\n最大差异: {proportions.max() - proportions.min():.2%}")
# 示例:检查UCI Adult数据集中的性别偏见
from fairlearn.datasets import fetch_adult
adult_data = fetch_adult(as_frame=True)
X = adult_data.data
y = adult_data.target
# 检查性别与收入的关系
check_data_bias(X.assign(income=y), protected_attr='sex', target_col='income')
统计检验
from scipy import stats
def statistical_parity_test(df, protected_attr, target_col):
"""
统计奇偶性检验:不同群体获得正预测的概率是否相等
"""
groups = df.groupby(protected_attr)[target_col]
for (g1, data1), (g2, data2) in itertools.combinations(groups, 2):
# 卡方检验
contingency = pd.crosstab(
df[protected_attr].isin([g1, g2]).replace({True: g1, False: g2}),
df[target_col]
)
chi2, p, _, _ = stats.chi2_contingency(contingency)
print(f"{g1} vs {g2}: p-value = {p:.4f}")
模型预测层面检测
公平性指标计算
def calculate_fairness_metrics(y_true, y_pred, sensitive_features):
"""
计算主要公平性指标
"""
from fairlearn.metrics import (
demographic_parity_difference,
equalized_odds_difference,
equal_opportunity_difference,
false_positive_rate_difference
)
metrics = {
'demographic_parity': demographic_parity_difference(
y_true, y_pred, sensitive_features=sensitive_features),
'equalized_odds': equalized_odds_difference(
y_true, y_pred, sensitive_features=sensitive_features),
'equal_opportunity': equal_opportunity_difference(
y_true, y_pred, sensitive_features=sensitive_features),
'false_positive_rate_diff': false_positive_rate_difference(
y_true, y_pred, sensitive_features=sensitive_features)
}
return metrics
# 使用示例
y_true = [1, 0, 1, 1, 0, 0]
y_pred = [0, 0, 1, 0, 0, 1]
sensitive = [0, 0, 0, 1, 1, 1] # 假设敏感属性
results = calculate_fairness_metrics(y_true, y_pred, sensitive)
print("公平性指标:")
for metric, value in results.items():
print(f"{metric}: {value:.4f}")
群体间性能差异分析
def group_performance_analysis(model, X_test, y_test, sensitive_attr):
"""
分析模型在不同群体间的性能差异
"""
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
# 创建DataFrame包含预测结果
results = X_test.copy()
results['y_true'] = y_test
results['y_pred'] = model.predict(X_test)
# 按敏感属性分组计算指标
metrics_frame = MetricFrame(
metrics={
'accuracy': accuracy_score,
'precision': precision_score,
'recall': recall_score,
'f1_score': f1_score
},
y_true=results['y_true'],
y_pred=results['y_pred'],
sensitive_features=results[sensitive_attr]
)
# 输出各群体指标
print("各群体性能指标:")
print(metrics_frame.by_group)
# 计算差异
print("\n指标差异:")
print(metrics_frame.difference())
return metrics_frame
# 示例:使用AIF360进行完整分析
from aif360.datasets import BinaryLabelDataset
from aif360.metrics import BinaryLabelDatasetMetric, ClassificationMetric
# 准备数据
protected_attr = 'sex'
privileged_groups = [{'sex': 1}] # 假设男性是特权群体
unprivileged_groups = [{'sex': 0}]
# 创建AIF360数据集对象
dataset = BinaryLabelDataset(
df=X.assign(label=y),
label_names=['label'],
protected_attribute_names=[protected_attr],
favorable_class=1,
unfavorable_class=0
)
# 计算数据层面的公平性指标
data_metric = BinaryLabelDatasetMetric(
dataset,
unprivileged_groups=unprivileged_groups,
privileged_groups=privileged_groups
)
print(f"统计奇偶性差异: {data_metric.statistical_parity_difference():.4f}")
print(f"不一致性影响: {data_metric.disparate_impact():.4f}")
NLP模型偏见检测(以BERT为例)
from transformers import pipeline
from bias_bench import benchmark
from bias_bench.datasets import CrowSPairsDataset
def check_nlp_bias(model_name='bert-base-uncased'):
"""
检测NLP模型中的刻板印象偏见
"""
# 使用CrowS-Pairs数据集检测
dataset = CrowSPairsDataset()
# 加载模型
unmasker = pipeline('fill-mask', model=model_name)
# 测试刻板印象句子
test_sentences = [
"The [MASK] was a good nurse.", # 护士性别偏见
"The [MASK] was a successful CEO.", # CEO性别偏见
"The [MASK] worked as a software engineer.", # 工程师性别偏见
]
for sentence in test_sentences:
results = unmasker(sentence)
print(f"\n句子: {sentence}")
for result in results[:5]:
token = result['token_str'].strip()
score = result['score']
print(f" {token}: {score:.4f}")
# 使用专业偏见检测工具
from bias_bench.benchmark import run_benchmark
# benchmark_results = run_benchmark(model_name)
# print(benchmark_results)
# 检查词向量偏见
def word_embedding_bias_check():
"""
使用Word Embedding Association Test (WEAT)检测
"""
from weat import WEAT # 需要安装weat库
# 定义目标和社会属性
target_concepts = {
'male': ['man', 'boy', 'brother', 'father', 'son'],
'female': ['woman', 'girl', 'sister', 'mother', 'daughter']
}
attribute_concepts = {
'career': ['career', 'manager', 'professional', 'executive'],
'family': ['family', 'home', 'parent', 'children']
}
# 计算效应大小
weat = WEAT(model='glove')
effect_size = weat.run_test(
target_concepts['male'], target_concepts['female'],
attribute_concepts['career'], attribute_concepts['family']
)
print(f"WEAT效应大小: {effect_size:.4f}")
完整检测流程示例
class BiasDetectionPipeline:
"""
完整的偏见检测流程
"""
def __init__(self, model, sensitive_attr, privileged_groups=None):
self.model = model
self.sensitive_attr = sensitive_attr
self.privileged_groups = privileged_groups or [{'sex': 1}]
self.unprivileged_groups = [{'sex': 0}]
def run_full_check(self, X, y, X_test, y_test):
results = {}
# 1. 数据偏见检测
print("=== 数据层面偏见检测 ===")
results['data_bias'] = self._check_data_bias(X, y)
# 2. 模型预测偏见
print("\n=== 模型预测偏见检测 ===")
y_pred = self.model.predict(X_test)
results['prediction_bias'] = self._check_prediction_bias(
y_test, y_pred, X_test[self.sensitive_attr]
)
# 3. 特征重要性分析
print("\n=== 特征重要性分析 ===")
results['feature_importance'] = self._analyze_feature_importance(X)
# 4. 生成报告
self._generate_report(results)
return results
def _check_data_bias(self, X, y):
"""
检查数据偏见
"""
from fairlearn.metrics import demographic_parity_difference
data = X.assign(label=y)
# 检查各群体比例
group_sizes = data.groupby(self.sensitive_attr).size()
print("群体样本分布:")
print(group_sizes)
# 检查目标变量分布
group_positive_rate = data.groupby(self.sensitive_attr)['label'].mean()
print("\n正类比例:")
print(group_positive_rate)
return {
'group_sizes': group_sizes,
'positive_rates': group_positive_rate
}
def _check_prediction_bias(self, y_true, y_pred, sensitive):
"""
检查预测偏见
"""
metrics = calculate_fairness_metrics(y_true, y_pred, sensitive)
# 阈值判断
thresholds = {
'demographic_parity': 0.1,
'equalized_odds': 0.1,
'equal_opportunity': 0.1
}
print("公平性指标值:")
for metric, value in metrics.items():
status = "⚠️ 可能存在偏见" if value > thresholds.get(metric, 0.1) else "✓ 正常"
print(f"{metric}: {value:.4f} {status}")
return metrics
def _analyze_feature_importance(self, X):
"""
分析特征重要性是否偏向敏感属性
"""
if hasattr(self.model, 'feature_importances_'):
importances = pd.DataFrame({
'feature': X.columns,
'importance': self.model.feature_importances_
}).sort_values('importance', ascending=False)
print("前5个最重要特征:")
print(importances.head())
# 检查敏感属性是否在重要特征中
if self.sensitive_attr in importances['feature'].head().values:
print(f"⚠️ 注意: 敏感属性'{self.sensitive_attr}'出现在重要特征中")
return importances
return None
def _generate_report(self, results):
"""
生成综合报告
"""
print("\n" + "="*50)
print("偏见检测总结报告")
print("="*50)
# 统计发现的偏见数量
bias_count = 0
if results.get('prediction_bias'):
for metric, value in results['prediction_bias'].items():
if value > 0.1:
bias_count += 1
print(f"发现的潜在偏见数量: {bias_count}")
if bias_count > 0:
print("\n建议措施:")
print("1. 数据层: 使用重采样或重加权技术平衡数据")
print("2. 算法层: 考虑使用公平性约束的优化算法")
print("3. 后处理层: 应用校准或阈值调整")
# 使用示例
if __name__ == "__main__":
# 加载数据
from fairlearn.datasets import fetch_adult
adult = fetch_adult(as_frame=True)
X = adult.data[['age', 'education-num', 'sex', 'hours-per-week']]
y = adult.target
# 划分数据
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# 训练简单模型
model = LogisticRegression(max_iter=1000)
model.fit(X_train[['age', 'education-num', 'hours-per-week']], y_train)
# 运行偏见检测
pipeline = BiasDetectionPipeline(
model=model,
sensitive_attr='sex',
privileged_groups=[{'sex': 1}]
)
results = pipeline.run_full_check(X_train, y_train, X_test, y_test)
偏见缓解方法
# 1. 数据重加权(Pre-processing)
from aif360.algorithms.preprocessing import Reweighing
rw = Reweighing(unprivileged_groups=unprivileged_groups,
privileged_groups=privileged_groups)
dataset_transformed = rw.fit_transform(dataset)
# 2. 公平性约束训练(In-processing)
from fairlearn.reductions import ExponentiatedGradient, DemographicParity
constraint = DemographicParity()
classifier = LogisticRegression(max_iter=1000)
mitigator = ExponentiatedGradient(classifier, constraint)
mitigator.fit(X_train, y_train, sensitive_features=X_train['sex'])
# 3. 后处理校准(Post-processing)
from fairlearn.postprocessing import ThresholdOptimizer
postprocessor = ThresholdOptimizer(
estimator=model,
constraints="demographic_parity",
predict_method='predict_proba'
)
postprocessor.fit(X_train, y_train, sensitive_features=X_train['sex'])
最佳实践建议
- 持续监控:在生产环境中设置偏见检测的CI/CD管道
- 多样性测试:确保测试集包含多种人口统计学群体
- 指标选择:根据应用场景选择合适的公平性定义
- 透明报告:在模型文档中披露检测到的偏见和缓解措施
- 交叉验证:使用不同数据集和随机种子验证发现是否一致
通过以上方法,可以系统性地检测和量化Python模型中的偏见,为后续的公平性改进提供数据支持。