砥砺奋进谱新篇,且看旧貌换新颜。欢迎访问新的 IBM Developer 中文网站! 了解详情

IBM Developer 博客

通过 IBM Developer 关注最新动态并获取信息

AIF360 团队很高兴宣布现已对 Fairness 工具包进行了重大更新:与 scikit-learn 的兼容性。


Scikit-learn 是一个非常受欢迎的数据科学库,它对于训练已制定的机器学习算法、计算基本指标和构建模型管道都非常有用。实际上,我们的许多示例 Notebook 已经在预处理或后处理工作流程中使用了 scikit-learn 分类器。

遗憾的是,在 AIF360 算法和 scikit-learn 算法之间进行切换会中断工作流程,并迫使用户来回转换数据结构。我们也无法利用 scikit-learn 提供的一些强大的元编程工具,例如管道和交叉验证。

AIF360 的最新版本 0.3.0 引入了大量更改,但最引人注目的是新增了新的 aif360.sklearn 模块。其中提供了当前已完成的所有兼容 scikit-learn 的 AIF360 功能。

注意:该版本仍在开发中,尚未迁移完所有功能。有兴趣的开发者还可以查看 “sklearn-compat” 开发分支,以了解最新功能并做出贡献。

此更新的目标是使 AIF360 功能可以与 scikit-learn 功能互换。可以将算法换成去偏算法,并将指标换成公平性指标。例如,您可以使用 AdversarialDebiasing 分类器来代替简单的 LogisticRegression 分类器,这样不仅可以使用 recall_score 分类器,还可以测量 equal_opportunity_difference 或受保护组之间的撤回差异。所有这些操作都跟换一行代码一样容易。

但是,为了将公平性功能整合到算法中,我们在某些情况下无法确保与 scikit-learn 完全兼容。例如,sklearn.decomposition.pca 等 scikit-learn 预处理器会剥离诸如受保护属性之类的样本属性,这随后会在管道中导致 AIF360 算法出现错误。继续了解相应的变通方法以及有关使用 scikit-learn 的其他注意事项。

仍保留旧 API

旧 API 在未来的一段时间内会一直保留,因为我们还会继续在新 API 中复制旧 API 的功能。完成这一目标后,我们可能会选择在几个版本中弃用对旧 API 的支持,但我们会在时机成熟时明确通知这一情况,具体还取决于社区的反馈。

功能概述

同样,API 仍在开发中。API 目前还缺少许多功能,它在一定程度上仍处于试验阶段。用户反馈和贡献对于该项目至关重要。

有关功能的更深入说明,请参阅 API 参考。要获取功能的交互式演示,可以访问我们提供的示例 Notebook

数据集

下面复制了 AIF360 中的四个(共有五个)数据集:Adult Census Income、German Credit、Bank Marketing 和 COMPAS Recidivism。首次调用对应函数时,将自动从 OpenML 下载这些数据集,并将其缓存以供以后重用。

数据结构也进行了简化。数据分为熟悉的 X 特征和 y 目标值以及 sample_weight(如果有)。每个变量都会作为 Pandas DataFrame 对象返回,其中默认情况下包含原始数据值(例如,字符串类别值),并且每个样本的受保护属性值都位于 index 中。

例如,如果输入为以下内容:

from aif360.sklearn.datasets import fetch_compas

X, y = fetch_compas(binary_race=True)
X.head()

则输出应如下所示:

sex age age_cat race juv_fel_count juv_misd_count juv_other_count priors_count c_charge_degree c_charge_desc
id sex race
3 Male African-American Male 34 25 – 45 African-American 0 0 0 0 F Felony Battery w/Prior Convict
4 Male African-American Male 24 Less than 25 African-American 0 0 1 4 F Possession of Cocaine
8 Male Caucasian Male 41 25 – 45 Caucasian 0 0 0 14 F Possession Burglary Tools
10 Female Caucasian Female 39 25 – 45 Caucasian 0 0 0 0 M Battery
14 Male Caucasian Male 27 25 – 45 Caucasian 0 0 0 0 F Poss 3,4 MDMA (Ecstasy)

现在,我们来将受保护的属性编码为 0 或 1。由于类别的默认顺序是将 0 分配给非特权属性,将 1 分配给特权属性,这可以简化计算指标的过程,因为我们可以使用默认的 priv_group=1

import pandas as pd

X.index = pd.MultiIndex.from_arrays(X.index.codes, names=X.index.names)
y.index = pd.MultiIndex.from_arrays(y.index.codes, names=y.index.names)

由于累犯是不利的,因此可以翻转标签。虽然这不是绝对必要的,但可以让我们不必为所有指标提供 pos_label

y = 1 - pd.Series(y.factorize(sort=True)[0], index=y.index)

如前所述,某些 scikit-learn 步骤会从数据中删除包含受保护属性信息的格式设置。这样,将很难使用大多数 sklearn.preprocessing 步骤,例如输入规范化和独热编码。我们将在下面展示另一种变通方法,但为了简便起见,我们希望与 scikit-learn 社区紧密合作,以便顺利完成这项工作。

from sklearn.model_selection import train_test_split
from sklearn.compose import make_column_transformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1234567)
data_preproc = make_column_transformer(
        (OneHotEncoder(sparse=False, handle_unknown='ignore'), X_train.dtypes == 'category'),
        remainder=StandardScaler())

X_train = pd.DataFrame(data_preproc.fit_transform(X_train), index=X_train.index)
X_test = pd.DataFrame(data_preproc.transform(X_test), index=X_test.index)

算法

aif360.sklearn 的初始版本中包含三种算法:一个预处理器(重新加权)、一个中间处理器(对抗性去偏)和一个后处理器(校准均衡概率)。在致力于提供所有 11 种算法(当然还包括任何新算法)的同时,我们也欢迎社区用户做出贡献。

“对抗性去偏”的工作原理与其他 scikit-learn 估算器一样,都是使用 fit() 方法进行训练,并可以返回“硬”(predict()) 和“软”(predict_proba()) 预测。

from aif360.sklearn.inprocessing import AdversarialDebiasing
import tensorflow as tf
tf.logging.set_verbosity(tf.logging.ERROR)

adv_deb = AdversarialDebiasing(prot_attr='race', adversary_loss_weight=1.0, random_state=1234567)
adv_deb.fit(X_train, y_train)
y_pred_AD = adv_deb.predict(X_test)
adv_deb.sess_.close()

“重新加权”有点违反 scikit-learn API 约定,因为它需要从 transform() 返回新的样本权重。因此我们引入了元估算器作为变通方法,用于将重新加权器和任意估算器合并到单个 fit() 步骤中。

from aif360.sklearn.preprocessing import ReweighingMeta, Reweighing
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(solver='liblinear')
rew = ReweighingMeta(estimator=lr, reweigher=Reweighing('race'))
rew.fit(X_train, y_train)
y_pred_REW = rew.predict(X_test)

“校准均衡概率”后处理器也需要一种变通方法。后处理器将根据来自“黑匣”估算器的预测值和实际值进行训练,因此可以产生更公平的预测。这是 scikit-learn 的一项全新功能。此外,为避免数据泄漏,后处理器的训练集应与估算器的训练集不同。PostProcessingMeta 类通过将任意估计量和后处理器的训练和预测结合在一起,同时无缝分割数据集,从而解决了这两个问题。

from aif360.sklearn.postprocessing import CalibratedEqualizedOdds, PostProcessingMeta

pp = CalibratedEqualizedOdds('race', cost_constraint='fnr', random_state=1234567)
ceo = PostProcessingMeta(estimator=lr, postprocessor=pp, random_state=1234567)
ceo.fit(X_train, y_train)
y_pred_CEO = ceo.predict(X_test)
y_proba_CEO = ceo.predict_proba(X_test)

指标

大多数公平性指标已被复制为独立功能。这意味着不再需要为每对预测和 ground-truth 标签创建对象,不过这会使每个函数调用的语法长度有所增加。此外,由于输入对于 sklearn.metrics 中的函数也有效,因此我们应避免重新实现 accuracy_scorerecall_score 等函数。

例如,新移植的指标可以用作网格搜索中的计分器。

from aif360.sklearn.metrics import disparate_impact_ratio

train_di = disparate_impact_ratio(y_test, prot_attr='race')

print(f'Training set disparate impact: {train_di:.3f}')

训练集差异性影响:0.773

from aif360.sklearn.metrics import average_odds_error
from sklearn.metrics import accuracy_score

acc_AD = accuracy_score(y_test, y_pred_AD)
deo_AD = average_odds_error(y_test, y_pred_AD, prot_attr='race')

print(f'[Adversarial Debiasing] Test accuracy: {acc_AD:.2%}')
print(f'[Adversarial Debiasing] Test equal odds measure: {deo_AD:.3f}')
[对抗性去偏] 测试准确性: 65.43%
[对抗性去偏] 测试均衡概率度量 0.091
acc_REW = accuracy_score(y_test, y_pred_REW)
di_REW = disparate_impact_ratio(y_test, y_pred_REW, prot_attr='race')

print(f'[Reweighing] Test accuracy: {acc_REW:.2%}')
print(f'[Reweighing] Test disparate impact: {di_REW:.3f}')
[重新加权] 测试准确性: 66.64%
[重新加权] 测试差异性影响 0.893
from aif360.sklearn.metrics import difference, generalized_fnr

acc_CEO = accuracy_score(y_test, y_pred_CEO)
dfnr_CEO = difference(generalized_fnr, y_test, y_proba_CEO[:, 1], prot_attr='race')

print(f'[Calibrated Equalized Odds] Test accuracy: {acc_CEO:.2%}')
print(f'[Calibrated Equalized Odds] Test FNR difference: {dfnr_CEO:.3f}')
[校准均衡概率] 测试准确性: 63.99%
[校准均衡概率] 测试 FNR 差异 0.053

寻找贡献者

关于这个项目,我们一直在寻找新的开源贡献者!现在,我们已经拥有了关于如何修改每种算法类型的示例,如果您想帮助改进这个项目,可以随时在 GitHub 问题上发贴来选择未实现的算法并进行迁移。我们还专门针对这项工作开设了 Slack 渠道“#sklearn-compat”,以供大家在其中提出问题并提供反馈。

本文翻译自:The AIF360 team adds compatibility with scikit-learn(2020-06-03)