模型偏见不是"某行代码写错了",而是像一条 ETL 数据管线——采集、抽样、打标、聚合每层都可能注入系统性偏斜,最后在模型里累积放大。跟分布式系统一样:污染往往不在终点,而是上游某个采样节点悄悄丢了数据,下游全盘继承还不自知。"垃圾进垃圾出"是对的,但偏见更隐蔽——合规、完整、没有 bug 的数据,依然可以是有偏的。
要治理偏见,先得知道它从哪进来。学界把机器学习管线里的偏见拆成几个互相独立的注入点——这很重要,因为不同来源要用完全不同的手段修,混为一谈只会白费力气:
import pandas as pd # 一份"看起来干净"的招聘历史数据:无缺失、无错误,但有偏 df = pd.read_csv("hiring.csv") # 列: gender, hired(0/1) # ① 代表性检查:各群体样本量是否悬殊? print(df["gender"].value_counts(normalize=True)) # ② 历史偏见检查:正标签(hired)在群体间的基础比率(base rate)差多少? # 模型会把这个历史差异当"信号"学下来并放大 base_rates = df.groupby("gender")["hired"].mean() print(base_rates) print("基础比率差(disparity):", base_rates.max() - base_rates.min()) # 差距大 → 直接拿去训练 = 把历史不公固化进模型
"公平"不是一个指标,而是像 SLA 定义——同一个系统,你可以承诺"每个租户吞吐量相同",也可以承诺"每个租户错误率相同",这是两份不同的合同,无法同时满足所有人。公平性度量就是把"公平"翻译成可计算的数学条件,而选哪个定义本身就是价值判断,不是技术细节。
"这个模型公平吗"无法回答,除非你先说清按哪种公平。两个最主流的群体公平(group fairness)定义,分歧的根源在于:看输出,还是看错误?
关键区别:人口均等无视真值 Y,只看输出比率;机会均等以真值为条件,要求错误分布一致。所以若两群体真实合格率本就不同,这两个定义会打架——强行拉平录取率,必然在某群体制造更高错误率。这不是 bug,是下一张卡的数学必然。
| 定义 | 数学条件 | 以真值为条件? | 适用直觉 |
|---|---|---|---|
| 人口均等 | 正预测率相等 | 否 | 要求结果分布本身平等 |
| 机会均等 | TPR 且 FPR 相等 | 是 | 要求"犯错方式"平等 |
| 均等机会 | 仅 TPR 相等 | 是 | 只在意别漏掉合格者 |
from fairlearn.metrics import (MetricFrame, demographic_parity_difference, equalized_odds_difference) from sklearn.metrics import selection_rate, true_positive_rate # y_true 真值, y_pred 预测, A 敏感属性(如 gender) mf = MetricFrame( metrics={"选择率": selection_rate, "真阳率TPR": true_positive_rate}, y_true=y_true, y_pred=y_pred, sensitive_features=A) print(mf.by_group) # 逐群体看每个指标 # 一个数概括差距: 0 = 完全公平, 越大越不公 print("人口均等差:", demographic_parity_difference( y_true, y_pred, sensitive_features=A)) # 选择率的组间极差 print("机会均等差:", equalized_odds_difference( y_true, y_pred, sensitive_features=A)) # TPR/FPR 组间极差的较大者 # 两个数往往一个小一个大——印证它们在衡量不同的"公平"
公平性有自己的 CAP 定理。就像分布式系统里一致性(C)、可用性(A)、分区容忍(P) 三者不可兼得,风险评分模型里的三种公平——校准(calibration)、假阳率均衡、真阳率均衡——在群体基础比率不同时,数学上不可能同时满足。这不是工程没做好,是定理证明了的天花板:你必须选择牺牲哪一个。
2016 年,两组研究者(Kleinberg 等;以及 Chouldechova)几乎同时证明了这个结果,起因是著名的 COMPAS 累犯预测争议——ProPublica 指控该算法对黑人被告假阳率更高,厂商 Northpointe 反驳说算法是校准的(同一分数对各群体含义相同)。不可能定理揭示:双方都对,因为他们要的两种公平根本无法共存。
三个条件的白话:
定理内容:只要两群体基础比率不同(真实累犯率不一样)、且模型不是 100% 完美,上面三条最多同时满足两条。为什么?——校准强迫"同一分数=同一真实风险",但当一个群体整体基础比率更高,为维持校准,模型必然在该群体要么放更多假阳、要么漏更多真阳,错误无法在群体间对齐。基础比率的差异被"挤"进了错误率里,躲不掉。
import numpy as np # 用最小例子看"校准"和"假阳率均衡"如何互相绞杀 # 两个群体, 基础比率不同: A组真实正例率 0.6, B组 0.3 def rates(y_true, score, thr=0.5): pred = (score >= thr).astype(int) tpr = pred[y_true == 1].mean() # 真阳率 fpr = pred[y_true == 0].mean() # 假阳率 return tpr, fpr # 假设两组模型都完美校准(分数=真实概率), 但基础比率不同 rng = np.random.default_rng(0) yA = (rng.random(10000) < 0.6).astype(int) # A: 高基础比率 yB = (rng.random(10000) < 0.3).astype(int) # B: 低基础比率 sA = np.where(yA==1, rng.random(10000)*.5+.5, rng.random(10000)*.5) sB = np.where(yB==1, rng.random(10000)*.5+.5, rng.random(10000)*.5) print("A组 TPR,FPR:", rates(yA, sA)) print("B组 TPR,FPR:", rates(yB, sB)) # 两组用同一阈值(都校准)→ 但 FPR 不相等; 想拉平 FPR 就得 # 给两组不同阈值 → 同一分数含义就不同了 → 打破校准。无解。
去偏就像数据质量治理——你可以在入库时清洗(预处理)、写入约束里强制(训练中)、或读取时打补丁(后处理),三个位置各有代价。因果公平更进一步:不满足于"相关性上看起来平等",而是问一个反事实——"如果这个人的敏感属性被改掉,决策会变吗?"——把公平从统计相关升级到因果机制。
知道了偏见来源和度量,怎么修?三类干预按作用于管线的位置划分:
因果公平(causal fairness)是另一条路线。前面所有度量都是相关性的,无法区分"邮编影响决策"是因为邮编本身相关,还是因为它是种族的代理。反事实公平(Kusner 等 2017)的定义:一个决策公平,当且仅当在反事实世界里——把个体敏感属性 A 改成另一个值、并沿因果图更新所有由 A 导致的下游变量——预测分布不变。它要求先画因果图(causal DAG),区分"A 的合法后代"和"应被阻断的歧视路径"。
from fairlearn.postprocessing import ThresholdOptimizer from sklearn.ensemble import GradientBoostingClassifier # 后处理去偏:不重训模型,只按群体调阈值达成"机会均等" base = GradientBoostingClassifier().fit(X_tr, y_tr) fair = ThresholdOptimizer( estimator=base, constraint="equalized_odds", # 目标公平定义(见上一张卡) predict_method="predict_proba", prefit=True) # 拟合时必须传敏感属性——它为每个群体学一组阈值 fair.fit(X_tr, y_tr, sensitive_features=A_tr) y_fair = fair.predict(X_te, sensitive_features=A_te) # 代价: 通常总体精度略降——这就是不可能定理里你付的"公平税" # 注意: 推理时也要敏感属性, 部署上可能涉及合规限制