Python-数据中心的机器学习-三-

166 阅读1小时+

Python 数据中心的机器学习(三)

原文:annas-archive.org/md5/db8f2beb902c7755f2a881df92b038cc

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章:识别和消除偏差的技术

在以数据为中心的机器学习领域,追求无偏差和公平的模型至关重要。偏差算法的后果可能从性能不佳到道德上有疑问的决定。重要的是要认识到,偏差可以在机器学习管道的两个关键阶段显现:数据和模型。虽然以模型为中心的方法近年来受到了广泛关注,但本章将重点介绍通常被忽视的同样重要的以数据为中心的策略。

在本章中,我们将探讨机器学习中偏差的复杂性,强调数据中心性是偏差缓解的基本方面。我们将探讨来自金融、人力资源和医疗保健等领域的真实世界案例,其中未能解决偏差已经或可能产生深远的影响。

在本章中,我们将涵盖以下主题:

  • 偏差难题

  • 偏差类型

  • 数据中心性必要性

  • 案例研究

偏差难题

机器学习中的偏差并非一个新问题。它深深植根于我们收集的数据和我们设计的算法中。偏差可能源于历史差异、社会偏见,甚至在数据收集和标注过程中人类所做的决策。忽视偏差,或者仅仅通过以模型为中心的技术来处理偏差,可能会导致不良后果。

考虑以下场景,这些场景说明了偏差的多面性:

  • 金融中的偏差:在金融领域,机器学习模型在信用评分、欺诈检测和投资建议中发挥着关键作用。然而,如果历史贷款实践偏向某些人口群体而忽视其他群体,这些偏差可能会渗透到用于训练模型的数据库中。结果,边缘化社区可能会面临不公平的贷款实践,加剧社会经济不平等。

  • 人力资源中的偏差:人工智能在人力资源中的应用在招聘、员工绩效评估甚至薪资谈判方面势头强劲。如果职位发布或历史招聘数据偏向特定性别、种族或背景,AI 系统可能会无意中延续歧视,导致工作场所缺乏多样性和包容性。

  • 医疗保健中的偏差:在医疗保健领域,诊断算法被用于疾病检测和治疗建议。如果训练数据主要代表某些人口群体,来自代表性不足群体的个人可能会接受不理想的护理或面临延迟诊断。其影响可能是改变一生的,强调了公平医疗保健 AI 的必要性。

现在我们已经涵盖了可能产生偏差的领域,在下一节中,我们将探讨机器学习中普遍存在的偏差类型。

偏差类型

在机器学习中,通常有五种需要关注的偏差类别。尽管提供的列表并不全面,但这些类别代表了最普遍的偏差类型,每种类型都可以进一步细分。

容易识别的偏差

一些类型的偏差可以通过主动监控和分析轻松识别。以下是一些例子。

报告偏差

这种偏差发生在数据生产者、数据标注者或数据收集者遗漏了重要元素时,导致数据不能代表现实世界。例如,一家医疗保健企业可能对病人对健康计划的看法感兴趣;然而,数据标注者可能会决定专注于负面和正面情绪,而中性的情绪可能被不足代表。在这样数据上训练的模型擅长识别正面和负面情绪,但可能无法准确预测中性情绪。这种偏差可以通过主动监控来识别,其中对实时数据的预测与对训练数据的预测存在偏差。为了减少报告偏差,在机器学习系统设计初期明确所需的数据点非常重要。同样重要的是确保用于训练的数据代表真实世界数据。

自动化偏差

这种偏差发生在依赖于自动化的数据收集方式,并假设数据捕获不会出错的情况下。随着人工智能的日益完善,对人类的依赖显著减少,因此通常假设如果实施自动化系统,那么它将神奇地解决所有问题。使用主动监控可以帮助识别这种类型的偏差,其中模型在真实数据上的准确性非常低。另一种识别方法是通过让人类标注标签并衡量人类性能与算法性能。如*第六章**,机器学习系统中程序化标注技术的失败可能导致数据丢失或不准确。人工智能的好坏取决于其训练数据。数据中心化的一个关键原则是让人类参与其中;因此,在构建自动化系统时,我们应该确保生成数据代表真实世界场景,并且数据多样化,而不是过度或不足代表。

选择偏差

这种偏差发生在用于训练模型的所选数据不能代表真实世界数据时。这种偏差可以有多种形式:

  • 覆盖偏差:这种偏差可能发生在数据不是以代表性的方式收集时。这可能发生在企业和从业者专注于结果,而忽略了那些对结果没有贡献的数据点。在医疗保健领域,保险公司可能想要预测医院的入院人数;然而,关于那些在保险公司频繁更换并使用竞争性保险产品的人,或者那些没有申请福利而进入医院的人的数据可能并不容易获得,因此,这些人群在训练数据中可能没有得到很好的代表。

  • 参与偏差:这种偏差可能由于参与者选择退出数据收集过程,导致某一群体在另一群体中过度代表。例如,一个模型被训练用来根据调查数据预测流失率,其中 80%已经转移到新竞争对手的人不太可能回应调查,他们的数据在样本中高度代表性不足。

  • 抽样偏差:这种偏差可能发生在数据收集者没有在数据收集过程中使用适当的随机化方法时。例如,一个模型被训练用来根据调查数据预测健康分数;调查者没有随机地针对人口,而是选择了 80%高度关注健康并且更有可能回应的人,与那些不太可能回应的其他受访者相比。在健康产业中,那些更关注健康的人可能比那些不太关注健康的人有更好的健康分数,这可能导致模型偏向于健康人群。

选择偏差难以识别;然而,如果数据中频繁出现漂移,并且频繁重新训练以确保模型质量不下降,那么就是调查和检查所捕获的数据是否代表真实世界数据的好时机。回归模型中的两种分析方法可以帮助识别这种偏差。一种是进行双变量分析,其中敏感变量可以表示在x轴上,目标变量可以放在y轴上。如果两个变量之间存在强烈的关联,那么在训练时间和评分后时间评估关联指标差异是很重要的。如果差异显著,那么用于训练的数据很可能不代表真实生活。第二种技术是通过比较数据未完全代表和完全代表时的可能结果来进行多元分析。这可以通过将子群体分为训练时包含的数据点和排除的数据点来完成。我们可以通过创建一个独立变量组来运行多回归模型,将组 1 标记为包含的数据,组 2 标记为未包含的数据。然后我们将这个新变量作为特征添加到模型训练中,并比较组 1 和组 2 之间是否存在显著的差异。如果存在差异,那么数据收集存在偏差。

在分类示例中,我们可以通过查看敏感子群体中的假阳性率和/或假阴性率来观察这些值是否差异很大。如果差异很大,数据很可能偏向于一个或几个子群体。另一个可以用来检查偏差是否持续存在的指标是人口统计学上的平等性,它是对从一个子群体到另一个子群体选择可能性的概率比较。如果高选择子群体与低选择子群体之间的概率比率低于 0.8,那么数据很可能存在偏差,并且代表性样本不足。建议检查多个指标以了解数据及算法中的偏差。

为了处理这类偏差,建议在收集数据时采用分层抽样等技巧,以确保数据集中不同群体按比例代表。现在我们已经介绍了易于识别的偏差类型,在下一节中,我们将讨论一些难以识别的偏差类型。

难以识别的偏差

一些类型的偏见可能具有挑战性,因为它们是个人可能没有意识到的偏见。这些偏见通常在潜意识层面运作,并可能影响感知、态度和行为。为了捕捉这些偏见,组织和个人需要流程和培训来确保这些偏见不会存在于工作场所。一旦确定数据收集过程或数据标注过程中存在偏见,就可以定义敏感标签来衡量和检查模型是否没有偏见,或者模型中是否存在可接受的偏见水平。以下将描述一些这些偏见。

组别归因偏见

这种类型的偏见发生在对整个数据进行归因时基于某些数据点。这通常发生在数据创建者对数据中存在的属性类型有先入为主的偏见时。这种偏见可以采取两种形式:

  • 群体偏见: 这是一种先入为主的偏见,其中相关数据点与数据创建者产生共鸣,因此这些数据点获得有利的结果——例如,如果一位数据工程经理在设计简历筛选算法时,他们认为完成 Udacity 纳米学位的人符合该职位要求。

  • 群体同质性偏见: 这是一种先入为主的偏见,其中数据点与数据创建者不产生共鸣,因此这些数据点获得不利的结果——例如,如果一位数据工程经理在设计简历筛选算法时,他们认为没有完成 Udacity 纳米学位的人不符合该职位要求。

让我们继续讨论另一种难以识别的偏见。

隐性偏见

这种类型的偏见发生在数据创建者根据自己的心理模型和个人经验对数据进行假设时。例如,在航空食品服务评论数据上训练的情感分析模型可能会将“ okay”这个词与中性情感联系起来。然而,世界上的一些地区使用“ okay”这个词来表示积极情感。

机器学习中的偏见可以采取多种形式;因此,我们将这些偏见分为两大类,易于识别的偏见和难以识别的偏见。从业者通常采用以模型为中心的方法来处理这些偏见,其中修改算法或使用偏见友好型算法已被视为可接受的做法。在下一节中,我们将从以模型为中心的方法转向另一种观点:以数据为中心的方法。

数据中心化命令

解决机器学习中的偏见需要一种全面的方法,其中以数据为中心的策略补充以模型为中心的技术。数据中心化涉及采取主动措施来编辑、清理和增强数据集本身,从而最大限度地减少模型可能继承的偏见。通过采用数据中心化实践,组织可以培养公平性、责任感和道德人工智能。

在本章的剩余部分,我们将探讨一系列以数据为中心的策略,这些策略赋予机器学习从业者减少偏差的能力。这些包括数据重采样、增强、净化、特征选择等。现实世界的例子将说明这些策略在金融、人力资源和医疗保健领域的实际影响。

如果数据被公平且准确地捕获或创建,那么算法很可能大部分都是无偏的。然而,本章中我们将涵盖的技术是在数据创建之后,机器学习从业者必须与提供的数据一起工作。

在以下子节中,我们将讨论一些数据为中心的策略,以在不改变算法的情况下减少机器学习中的偏差。这些可以被称为数据去偏技术。

样本方法

下采样和过采样等样本方法解决类别不平衡问题。下采样减少多数类实例,而过采样增强少数类示例。将两者结合起来可以减轻过拟合和信息损失,有效地平衡类别表示。这些方法可以与异常值处理和 Shapley 值结合使用,以进一步采样数据,其中难以分类或难以估计的数据点可以被移除或引入,以增强公平性指标。这些技术将在下一部分进行介绍。

下采样

在下采样中,我们移除随机或策略性的多数代表数据点的子集,以平衡类别分布——从难以分类或随机删除的多数类中删除数据点是常用的技术。我们还可以使用异常值移除进行回归任务。

过采样

在过采样中,我们添加随机或策略性的少数代表数据点的子集,为算法提供更多示例。我们可以为少数类重复或生成合成数据点,以平衡类别分布。我们可以使用SMOTE(合成少数过采样技术)和随机过采样等技术进行分类任务。或者,我们可以利用异常值或边缘案例的添加/删除进行回归任务。

下采样和过采样的组合

这些包括如SMOTEENNSMOTETomek等技术,其中SMOTE用于过采样少数类。编辑最近邻(ENN)或 Tomek 链接等技术用于移除难以分类或难以使用最近邻达成一致意见的示例,因为这些点接近边界,没有明显的分离。

对数据进行过采样和下采样的异常检测

这包括使用异常检测技术来识别边缘情况的数据点,然后这些点可以被多次重新引入或移除,以便模型能够获得更好的信号或变得更加通用。

使用 Shapley 值进行过采样和下采样数据

这涵盖了使用 Shapley 值进行数据过采样或欠采样。Shapley 值通过评估每个特征对模型预测的贡献来量化特征的重要性。高 Shapley 值突出了有影响力的特征。移除具有高 Shapley 值但预测错误的实例可能会通过减少异常值来提高模型精度。对具有高 Shapley 值和正确预测的实例进行过采样可以加强模型对关键模式的理解,从而可能提高性能。

其他以数据为中心的技术

除了采样方法之外,还有其他以数据为中心的技术可以用来减少偏差,其中一些在之前的章节中已经介绍过,还有一些我们将在案例研究中使用。以下描述了三种主要方法。

数据清洗

这包括移除缺失数据,因为包含缺失数据可能导致不公平的结果。这些技术已在第五章数据清洗技术中介绍,其中缺失数据被归类为“非随机缺失”。

特征选择

这包括选择特定的特征或消除会减少偏差的特征。这可能意味着识别出一个与敏感变量和结果标签高度相关的变量,并移除这样的间接变量或移除敏感变量。

特征工程

特征工程提供了减轻模型偏差的有效工具。例如,重新编码敏感属性、创建交互项或引入代理变量等技术,使模型能够在没有直接访问敏感信息的情况下学习。特征选择和降维方法裁剪了无关或冗余的特征,促进了更公平和更健壮的模型。此外,生成合成特征或利用特定领域的知识有助于提高对数据有更好理解的模型,从而有助于更公平的决策,同时提高整体模型性能并减少偏差。在示例中,我们将创建一个合成变量“兴趣”,以展示模型相对于另一个子群体是如何偏向的。

现在我们已经介绍了以数据为中心的方法,在下一节中,我们将描述问题陈述,并举例说明我们如何在现实生活中识别和减少偏差。

案例研究

当前面临的挑战集中在揭示和解决台湾信用卡违约数据集中可能存在的潜在偏差。该数据集来自加州大学欧文分校机器学习库(archive.ics.uci.edu/dataset/350/default+of+credit+card+clients),包含过去六个月内 30,000 名信用卡客户的详细信息,包括性别、婚姻状况和教育等人口统计因素。关键问题是这些人口统计特征是否将这些特征训练的决策树分类器引入偏差,特别是关注与性别相关的偏差。本例的总体目标是不仅识别偏差,而且通过应用数据为中心的技术来减轻任何偏差的结果。通过使用公平性指标重新评估算法的性能,本例旨在揭示金融决策中偏差的现实影响,特别是这些偏差如何基于性别和其他人口统计因素影响个人,可能导致信用评估和金融机会的不平等对待。解决和纠正此类偏差对于促进金融系统的公平性和平等至关重要。

我们将使用两个关键指标来检查算法的公平性:

  • 均衡机会差异: 该指标比较敏感变量(如性别、种族或年龄)的假阴性率和假阳性率,然后取假阴性率和假阳性率之间的最大差异。例如,在测试集中,男性和女性的假阳性率分别为 0.3 和 0.2(差异为 0.1),而男性和女性的假阴性率分别为 0.15 和 0.12(差异为 0.03)。由于假阳性率的差异更大,均衡机会将为 0.1。

  • 人口统计平等比率: 该指标衡量模型做出的预测是否独立于敏感变量,如种族、性别或年龄。鉴于这是一个比率,它衡量的是低选择率与高选择率之间的比率。比率为 1 表示实现了人口统计平等,而低于 0.8 通常意味着算法对某一群体的高度偏见,超过其他群体。

以下是对数据集中特征的描述:

  • LIMIT_BAL: 给定信用的金额(新台币),包括个人消费者信用及其家庭(补充)信用。

  • Sex: 性别(1 = 男;2 = 女)。

  • Education X3: 教育(1 = 研究生;2 = 大学;3 = 高中;4 = 其他)

  • Marriage X4: 婚姻状况(1 = 已婚;2 = 未婚;3 = 其他)

  • Age X5: 人的年龄(以年为单位)

  • PAY_0- PAY_5; X6 - X11: 过去付款的历史记录,包括从 2005 年 4 月到 9 月的过去每月付款记录,其中 PAY_0X6 = 9 月的还款状态,PAY_2; X7 = 2005 年 8 月的还款状态;... PAY_6; X11 = 2005 年 4 月的还款状态。还款状态的测量尺度为 -1 = 按时支付金额;1 = 延迟 1 个月付款;2 = 延迟 2 个月付款;...;8 = 延迟 8 个月付款;9 = 延迟 9 个月,依此类推。

  • BILL_AMT1 . BILL_AMT6; X12-X17: 账单金额(以新台币计)。BILL_AMT1;X12 表示 2005 年 9 月的信用卡账单金额,而 BILL_AMT6;X17 表示 2005 年 4 月的信用卡账单金额。

  • PAY_AMT1-PAY_AMT6; X18-X23: 根据上个月账单金额支付的金额。PAY_AMT1;X18 表示 2005 年 9 月支付的金额,而 PAY_AMT6;X23 表示 2005 年 4 月支付的金额。

  • default payment next month: 一个人是否在下个月的付款中违约(是 = 1,否 = 0),在 2005 年

要导入数据集,您需要安装 pandas。我们还将使用 os 库来导航路径并存储数据集。此库是 Python 的原生库。我们将调用 loan_dataset.csv 文件,并将其保存在运行此示例的同一目录中:

import pandas as pd
import os
FILENAME = "./loan_dataset.csv"
DATA_URL = "http://archive.ics.uci.edu/ml/machine-learning-databases/00350/default%20of%20credit%20card%20clients.xls"

文件加载所需时间取决于网络速度,从几秒到一分钟不等,因此当我们第一次运行此示例时,文件将存储在本地。然而,在后续运行中,借助 os 库,我们将检查文件是否存在,否则将其下载。我们将重命名两个变量:PAY_0PAY_1,并将“下个月默认付款”重命名为 default。我们不认为 ID 列对机器学习有用,因此我们将删除它:

if not os.path.exists(FILENAME):
    data = (
        pd.read_excel(io=DATA_URL, header=1)
        .drop(columns=["ID"])
        .rename(
            columns={"PAY_0": "PAY_1", "default payment next month": "default"}
        )
    )
    data.to_csv(FILENAME, sep=",", encoding="utf-8", index=False)

现在,我们将从本地目录将文件加载到名为 dataset 的 DataFrame 中。该文件包含 30,000 行和 24 列,包括目标变量:

dataset = pd.read_csv(FILENAME, sep=",", encoding="utf-8")
dataset.shape
(30000, 24)

接下来,我们运行 dataset.info() 方法来检查是否存在任何缺失值或编码错误的列:

图 8.1 – dataset.info() 方法的输出

图 8.1 – dataset.info() 方法的输出

我们没有缺失数据;然而,有三个分类列(SEXEDUCATIONMARRIAGE)的数据类型为整数,我们可能需要将它们转换为字符串。由于 SEX 中的值可能是序数,因此我们将首先将它们重新映射到 10

cat_colums = ['EDUCATION', 'MARRIAGE']
for col in cat_colums:
    dataset[col] = dataset[col].astype("category")
dataset['SEX'] = dataset['SEX'].map({1: 1, 2:0})

如果我们再次运行 dataset.info(),我们会看到三个列的数据类型现在是 category;我们现在可以一维编码它们。我们排除 SEX 进行一维编码,因为在这个数据集中,一个人要么是男性要么是女性,并且该信息可以包含在一列中。我们还将提取 SEX 并将其存储在另一个变量 A 中,并分离目标变量和独立特征。接下来,我们为 SEX 特征中的值创建一个映射,用于分析和可视化,以帮助解释结果,因此 1 将映射到 male 值,而 0 将映射到 female 值。我们将此映射存储在 A_str 变量中:

Y, A = dataset.loc[:, "default"], dataset.loc[:, "SEX"]
X = pd.get_dummies(dataset.drop(columns=["default","SEX"]))
X["SEX"] = A.copy()
A_str = A.map({1: "male", 0: "female"})

接下来,让我们加载所有必需的库。

加载库

要运行示例,你需要以下额外的库:

  • sklearn(scikit-learn)用于数据预处理和拟合模型

  • numpy 用于计算一些指标和一些数据处理

  • imblearn 用于过采样和欠采样

  • fairlearn 用于计算偏差和公平性分数

  • shap 用于可视化模型的解释

我们在开始时加载所有库:

from sklearn.model_selection import train_test_split, cross_validate
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from imblearn.over_sampling import SMOTE, ADASYN
from imblearn.combine import SMOTEENN, SMOTETomek
from sklearn.ensemble import IsolationForest
from imblearn.pipeline import make_pipeline
from imblearn.under_sampling import AllKNN, InstanceHardnessThreshold, RepeatedEditedNearestNeighbours, TomekLinks, EditedNearestNeighbours
from sklearn.metrics import balanced_accuracy_score, roc_auc_score, confusion_matrix, ConfusionMatrixDisplay
from sklearn.pipeline import Pipeline
from fairlearn.metrics import MetricFrame, equalized_odds_difference, demographic_parity_ratio
import numpy as np
import shap

接下来,我们使用 train_test_split 将数据集分为 traintest,并将 20% 的数据分配给测试。我们还把 A_str 分为 A_trainA_test,这样我们就可以在测试数据上计算公平性分数:

X_train, X_test, y_train, y_test, A_train, A_test = train_test_split(X,
                 Y,
                 A_str,
                 test_size=0.2,
                 stratify=Y,
                 random_state=42)

接下来,我们创建决策树分类器管道并用敏感特征训练算法:

d_tree_params = {
    "min_samples_leaf": 10,
    "random_state": 42
}
estimator = Pipeline(steps=[
    ("classifier", DecisionTreeClassifier(**d_tree_params))
])
estimator.fit(X_train, y_train)

接下来,我们计算 ROC 分数并提取预测值。我们还可视化混淆矩阵:

y_pred_proba = estimator.predict_proba(X_test)[:, 1]
y_pred = estimator.predict(X_test)
print(f"Roc score is : {roc_auc_score(y_test, y_pred_proba)}")
cm = ConfusionMatrixDisplay(confusion_matrix(y_test, y_pred), display_labels=estimator.classes_)
cm.plot()
Roc score is : 0.6875636482794665

代码生成了以下混淆矩阵:

图 8.2 – 输出混淆矩阵

图 8.2 – 输出混淆矩阵

为了检查算法是否公平,我们首先计算假阳性率和假阴性率,然后比较测试数据集中男性和女性群体之间的差异,以查看两个群体之间是否存在很大差异。

在下面的代码块中,我们创建了两个函数来计算假阳性率和假阴性率。我们进一步创建了一个公平性度量字典,其中我们使用了假阳性率和假阴性率,以及来自 scikit-learn 的平衡准确度指标。然后我们创建了一个公平性度量列表,并将其存储在一个变量中以方便访问:

def false_positive_rate(y_true, y_pred):
    """Compute the standard error for the false positive rate estimate."""
    tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
    return fp/(fp+tn)
def false_negative_rate(y_true, y_pred):
    """Compute the standard error for the false positive rate estimate."""
    tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
    return fn/(tp+fn)
fairness_metrics = {
    "balanced_accuracy": balanced_accuracy_score,
    "false_positive_rate": false_positive_rate,
    "false_negative_rate": false_negative_rate,
}
metrics_to_report = list(fairness_metrics.keys())

我们还创建了一个函数来报告男性和女性群体在公平性度量上的差异。我们首先使用 fairlearn 的便利函数 MetricFrame 创建一个名为 metricframe 的 DataFrame。它接受真实标签、预测和敏感特征值,以及一个报告的度量字典。然后我们利用 .by_group 属性报告每个群体的公平性度量。在函数内部,我们还报告了来自 fairlearn 库的 equalised_odds_differencedemographic_parity_ratio,以了解模型的总体公平性:

def calculate_fairness_metrics(y_test, y_pred, A_test, metrics=fairness_metrics):
    """Function to calculate fairness metrics"""
    metricframe = MetricFrame(
        metrics=fairness_metrics,
        y_true=y_test,
        y_pred=y_pred,
        sensitive_features=A_test,
    )
    print(metricframe.by_group[metrics_to_report])
    print("\n *diff*")
    print(metricframe.difference()[metrics_to_report])
    print("\n *final_metrics*")
    print(metricframe.overall[metrics_to_report])
    equalized_odds = equalized_odds_difference(
        y_test, y_pred, sensitive_features=A_test
    )
    print("\n *equalized_odds*")
    print(equalized_odds)
    dpr= demographic_parity_ratio(y_test, y_pred, sensitive_features=A_test)
    print("\n *demographic_parity_ratio*")
    print(dpr)

我们现在运行函数并计算公平性分数。很明显,由于在各个群体中假阳性率和假阴性率相似,模型在男性和女性群体中相当相似。由于假阳性率与假阴性率的差异大于假阴性率,均衡机会差异与两组假阳性率之间的差异相同。我们还可以看到,人口比例率高于 0.8,这意味着两个群体都有相当大的可能性获得积极的结果:

calculate_fairness_metrics_unmitigated = calculate_fairness_metrics(y_test, y_pred, A_test)

这将显示以下输出:

图 8.3 – 公平性分数

图 8.3 – 公平性分数

为了说明数据集中的偏差,我们可能需要生成一个与真实世界场景相关的合成变量,在该场景中,根据历史数据,一个群体受到更不公平的对待。首先,我们比较训练数据集中男性和女性的违约率。然后我们添加合成噪声:

for val in dataset.SEX.unique():
    print(f"{('male' if val == 1 else 'female')} default rate is: ")
    print(dataset[dataset.SEX == val]['default'].mean())
    print()
female default rate is:
0.20776280918727916
male default rate is: 0.2416722745625841

由于男性的违约率高于女性,我们可以复制一个偏差场景,其中违约率较低的申请者将获得较低的利率,而违约率较高的申请者将受到银行施加的较高利率。让我们假设银行经理认为男性更有可能违约,并且银行决定不对场景进行泛化,而是对男性收取更高的利率。

为了模拟这个场景,我们将引入一个新的特征,Interest_rate,遵循高斯分布。当某人没有违约时,平均值将是 0,但如果有违约,将是 1 的两倍。我们还为男性设置标准差为 2,为女性设置标准差为 1。

为了生成合成的高斯分布,我们使用numpy.random.normal方法,种子为42以确保可重复性:

np.random.seed(42)
X.loc[:, 'Interest_rate'] = np.random.normal(loc=2*Y, scale=A.map({1:2, 0:1}))
print("Maximum interest rate for men who defaulted vs women who defaulted")
print(X[(X.SEX == 1) & (Y == 1)]["Interest_rate"].max(), X[(X.SEX == 0) & (Y == 1)]["Interest_rate"].max())
print()
print("Maximum interest rate for men who did not default vs women that did not default")
print(X[(X.SEX == 1) & (Y == 0)]["Interest_rate"].max(), X[(X.SEX == 0) & (Y == 0)]["Interest_rate"].max())
Maximum interest rate for men who defaulted vs women who defaulted
9.852475412872653 6.479084251025757
Maximum interest rate for men who did not default vs women that did not default
6.857820956016427 3.852731490654721

现在我们已经添加了噪声,我们使用利率变量重新训练算法并重新计算公平性指标。我们首先分割数据,然后重新训练并重新计算公平性指标。我们像之前一样将数据重新分割成traintest,然后重新训练算法。一旦重新训练,我们计算影响。

在以下代码中,我们可以看到,通过添加合成的利率变量,我们提高了 ROC 指标:

y_pred_proba = estimator.predict_proba(X_test)[:, 1]
y_pred = estimator.predict(X_test)
roc_auc_score(y_test, y_pred_proba)
0.8465698909107798

从以下输出中可以清楚地看出,我们现在有一个基于均衡机会的更具偏差的算法。在男性中,假阴性率相当高,这意味着不太可能偿还银行的男性更有可能获得贷款,如果这个模型被投入生产,可能会导致不公平的结果:

calculate_fairness_metrics(y_test, y_pred, A_test)

这将打印以下信息:

图 8.4 – 公平性分数

图 8.4 – 公平性分数

为了减少偏差,我们将在特征选择中应用第一种以数据为中心的偏差消除技术,通过从算法中移除敏感变量来实现。这可以通过重新训练不带SEX变量的算法来完成。

由于数据集因利率差异较大而偏向于某一性别,在现实世界中,建议数据工程师和数据科学家与领域专家和数据生产者合作,以减少数据集中的这种偏差。例如,与其使用SEX来确定利率,不如使用其他特征,如支付历史、信用历史和收入。在训练步骤中,我们可以删除SEX变量:

estimator.fit(X_train.drop(['SEX'], axis=1), y_train)
Pipeline(steps=[('classifier',
                 DecisionTreeClassifier(min_samples_leaf=10, random_state=42))])

从以下输出中,我们可以看到,通过移除SEX变量,ROC 分数从 0.846 下降到 0.839:

estimator.fit(X_train.drop(['SEX'], axis=1), y_train)
y_pred_proba = estimator.predict_proba(X_test.drop(['SEX'], axis=1))[:, 1]
y_pred = estimator.predict(X_test.drop(['SEX'], axis=1))
roc_auc_score(y_test, y_pred_proba)
0.8392395442658211

观察以下公平性指标,很明显,当结果基于数据集的队列存在偏差时,从训练中移除变量可以消除算法的偏差。在male中的假阴性率有所下降,而在female中有所上升;然而,与使用SEX变量相比,算法更加公平。均衡机会从 0.18 下降到 0.07,但人口比例比有所降低,这意味着一个群体获得贷款的机会比另一个群体更多:

calculate_fairness_metrics_mitigated_v1 = calculate_fairness_metrics(y_test, y_pred, A_test)

输出如下:

图 8.5 – 公平性指标

图 8.5 – 公平性指标

接下来,我们将向您展示如何应用欠采样技术以确保结果变量平衡。

AllKNN 欠采样方法

我们将从imblearn包中的 AllKNN 算法开始,然后尝试即时硬度算法。由于这些算法在底层使用 KNN,这是一种基于距离的度量,我们需要确保使用 scikit-learn 的StandardScaler方法对特征进行缩放。我们将首先缩放变量,然后运行采样算法,然后训练决策树。我们将使用 5 k 交叉验证运行算法,并确保函数返回训练好的模型。交叉验证将在roc_auc和平衡准确度上进行评分。

我们将首先尝试一个来自imblearn的欠采样技术,AllKNN。这个算法并不旨在平衡多数和少数类别;然而,它从多数类别中移除难以分类的实例。它是通过迭代实现的,首先在完整数据集上训练模型。然后在多数类别的预测步骤中,如果邻居之间关于预测结果存在任何不一致,该数据点将从多数类别中移除。在第一次迭代中,训练一个 1-KNN 模型并移除一些样本,然后在下一次迭代中,训练一个 2-KNN 模型,在接下来的迭代中,训练一个 3-KNN 模型。通常,算法(默认情况下)将在 3-KNN 迭代结束时停止;然而,实践者可以选择更多的迭代,算法将不会停止,直到多数和少数类别的样本数量相同或达到最大迭代次数——哪个先到为止。

首先,我们来定义缩放器和采样方法:

scaler = StandardScaler()
sampler_method = AllKNN(n_jobs=-1)

接下来,我们创建一个管道对象,并将缩放器、采样器和估计器传递到管道中:

sampler_pipeline = make_pipeline(
    scaler,
    sampler_method,
    estimator)

然后我们传递训练数据并运行交叉验证。我们将交叉验证方法设置为通过设置 return_estimator=True 返回估计器(管道),这样我们就可以用它来对测试数据进行预测:

cv_results = cross_validate(sampler_pipeline,
                            X_train.drop(['SEX'], axis=1),
                            y_train, scoring=['roc_auc','balanced_accuracy'],
                            return_estimator=True)

接下来,我们打印交叉验证步骤返回的预测结果中每个步骤的 ROC 和平衡准确率的平均值和标准差,在每个步骤中,训练使用了四个折,并在第五个折上进行预测:

print(f"Validation roc auc : {cv_results['test_roc_auc'].mean():.3f} +/- {cv_results['test_roc_auc'].std():.3f}")
print(f"Validation balanced acc : {cv_results['test_balanced_accuracy'].mean():.3f} +/- {cv_results['test_balanced_accuracy'].std():.3f}")
Validation roc auc : 0.853 +/- 0.006
Validation balanced acc : 0.802 +/- 0.005

我们可以看到,通过使用下采样技术移除困难示例,test 数据上的 roc_auc 从上一步的 0.839 上升到 0.85:

model = sampler_pipeline.fit( X_train.drop(['SEX'], axis=1), y_train)
y_pred_proba = model.predict_proba(X_test.drop(['SEX'],axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'],axis=1))
roc_auc_score(y_test, y_pred_proba)
0.8537904984477683

接下来,我们计算公平性指标。尽管男性和女性的假阴性率都有所下降,但假阳性率有所上升,与上一步相比,均衡概率差异也有所增加。这可能是由于难以分类的男性样本已被移除:

calculate_fairness_metrics_mitigated_v2 = calculate_fairness_metrics(y_test, y_pred, A_test)

图 8.6 – 公平性指标

图 8.6 – 公平性指标

我们现在将通过引入困难案例来探索对公平性指标的影响。

实例硬度下采样方法

如其名所示,实例硬度方法专注于难以分类的样本,这些样本通常位于边界或与其他类别重叠。通常,这取决于所使用的算法(因为一些算法在某些困难情况下比其他算法更好)以及类别之间的重叠程度。对于这样的样本,学习算法通常会显示对困难案例的低概率预测,这意味着概率越低,实例硬度越高。在底层,该方法具有根据类别不平衡保留正确数量样本的能力。

在第一步中,我们将定义算法,并将算法传递到实例硬度步骤。然后我们将定义实例硬度下采样方法,采用三折交叉验证。

接下来,我们创建决策树估计器。最后,我们将管道中的步骤与缩放数据集、下采样数据和最终训练模型相结合。当管道定义后,我们运行与之前管道类似的交叉验证:

d_tree_params = {
    "min_samples_leaf": 10,
    "random_state": 42
}
d_tree = DecisionTreeClassifier(**d_tree_params)
sampler_method = InstanceHardnessThreshold(
    estimator=d_tree,
    sampling_strategy='auto',
    random_state=42,
    n_jobs=-1,
    cv=3)
estimator = Pipeline(steps=[
    ("classifier", DecisionTreeClassifier(**d_tree_params))
])
sampler_pipeline = make_pipeline(
    scaler,
    sampler_method,
    estimator)
cv_results = cross_validate(sampler_pipeline, X_train.drop(['SEX'], axis=1), y_train, scoring=['roc_auc','balanced_accuracy'], return_estimator=True)

AllKNNInstanceHardness 返回了类似的交叉验证结果:

print(f"Validation roc auc : {cv_results['test_roc_auc'].mean():.3f} +/- {cv_results['test_roc_auc'].std():.3f}")
print(f"Validation balanced acc : {cv_results['test_balanced_accuracy'].mean():.3f} +/- {cv_results['test_balanced_accuracy'].std():.3f}")
Validation roc auc : 0.853 +/- 0.005
Validation balanced acc : 0.807 +/- 0.007

当使用实例硬度方法时,test 数据上的 ROC 从 0.85 略微上升到 0.854:

model = sampler_pipeline.fit( X_train.drop(['SEX'], axis=1), y_train)
y_pred_proba = model.predict_proba(X_test.drop(['SEX'],axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'],axis=1))
roc_auc_score(y_test, y_pred_proba)
0.8549627959428299

公平性指标与之前的欠采样技术相当相似,可能由于类似的原因,通过去除困难案例,模型无法处理预测困难的男性案例。然而,在两种欠采样方法中,与特征选择步骤相比,均衡机会增加了,而且人口统计学平等比率仍然低于 0.8,这意味着在预测违约时,一个性别子类更有可能被选中而不是另一个:

calculate_fairness_metrics_mitigated_v3 = calculate_fairness_metrics(y_test, y_pred, A_test)

图 8.7 - 公平性指标

图 8.7 - 公平性指标

接下来,让我们看看过采样方法。

过采样方法

提高模型性能和公平性指标的一种方法是引入额外的示例。接下来的两种过采样技术,SMOTEADASYN,在第七章中介绍,在以数据为中心的机器学习中使用合成数据,因此我们不会详细介绍算法背后的细节。我们将使用这些技术,通过添加额外的示例来提高公平性指标,希望模型能够通过额外的数据点更好地学习。

对于每种方法,我们首先将数据集进行缩放,添加额外的少数类示例,然后训练模型。我们将打印交叉验证分数和测试ROC 分数,以及公平性指标。

SMOTE

由于我们在第七章中使用了此算法,在以数据为中心的机器学习中使用合成数据,我们将直接进入代码:

estimator = Pipeline(steps=[
    ("classifier", DecisionTreeClassifier(**d_tree_params))
])
sampler_method = SMOTE(random_state=42)
sampler_pipeline = make_pipeline(
    scaler,
    sampler_method,
    estimator)
cv_results = cross_validate(sampler_pipeline, X_train.drop(['SEX'], axis=1), y_train, scoring=['roc_auc','balanced_accuracy'], return_estimator=True)
print(f"Validation roc auc : {cv_results['test_roc_auc'].mean():.3f} +/- {cv_results['test_roc_auc'].std():.3f}")
print(f"Validation balanced acc : {cv_results['test_balanced_accuracy'].mean():.3f} +/- {cv_results['test_balanced_accuracy'].std():.3f}")
model = sampler_pipeline.fit( X_train.drop(['SEX'], axis=1), y_train)
y_pred_proba = model.predict_proba(X_test.drop(['SEX'],axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'],axis=1))
roc_auc_score(y_test, y_pred_proba)
Validation roc auc : 0.829 +/- 0.009
Validation balanced acc : 0.758 +/- 0.012
0.8393191272926885

与之前介绍的欠采样方法相比,验证指标和测试ROC 分数显示出较差的结果。在下一步中,我们将探索公平性指标:

calculate_fairness_metrics_mitigated_v4 = calculate_fairness_metrics(y_test, y_pred, A_test)

图 8.8 - 公平性指标

图 8.8 - 公平性指标

与欠采样方法相比,公平性指标更好——也就是说,男性和女性之间的假阳性率和假阴性率之间的差异减小了,基于人口统计学平等比率,模型更有可能同时选择两种性别的贷款违约申请人。在下一节中,我们将使用ADASYN算法,并将其与SMOTE和其他欠采样方法进行比较。

ADASYN

SMOTE方法类似,我们在第七章中介绍了ADASYN在以数据为中心的机器学习中使用合成数据,因此我们将直接进入代码,其中我们将对少数类进行过采样:

sampler_method = ADASYN(random_state=42)
sampler_pipeline = make_pipeline(
    scaler,
    sampler_method,
    estimator)
cv_results = cross_validate(sampler_pipeline, X_train.drop(['SEX'], axis=1), y_train, scoring=['roc_auc','balanced_accuracy'], return_estimator=True)
print(f"Validation roc auc : {cv_results['test_roc_auc'].mean():.3f} +/- {cv_results['test_roc_auc'].std():.3f}")
print(f"Validation balanced acc : {cv_results['test_balanced_accuracy'].mean():.3f} +/- {cv_results['test_balanced_accuracy'].std():.3f}")
model = sampler_pipeline.fit( X_train.drop(['SEX'], axis=1), y_train)
y_pred_proba = model.predict_proba(X_test.drop(['SEX'],axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'],axis=1))
roc_auc_score(y_test, y_pred_proba)
Validation roc auc : 0.823 +/- 0.004
Validation balanced acc : 0.757 +/- 0.006
0.816654655300673

验证指标和测试ROC 分数略低于SMOTE结果和欠采样方法。现在,让我们回顾公平性指标:

calculate_fairness_metrics_mitigated_v5 = calculate_fairness_metrics(y_test, y_pred, A_test)

图 8.9 - 公平性指标

图 8.9 - 公平性指标

ADASYN的均衡机会略高,而与SMOTE相比,人口比例略好,两种过采样技术都保证了比欠采样方法更高的公平性,但 ROC 性能略差。

我们已经看到,尽管平衡了类别,但模型公平性受到了损害,模型在大多数男性例子上犯的错误更多。因此,在下一节中,我们将随机引入一些额外的男性例子,其中模型错误地将阳性案例分类为阴性。

随机过采样和错误分类的例子

我们将首先使用ADASYN平衡数据集,并避免欠采样技术,因为我们希望保留难以分类的困难案例。然后我们训练模型,并识别模型认为应该是阳性但错误地将其分类为阴性的男性案例。然后我们随机选择这些案例的 10%,将它们重新添加到训练数据集中,并使用相同的算法重新训练模型。

最后,我们回顾测试数据上的模型指标和公平性指标。

我们使用过采样并在随机位置重新引入被错误分类的数据点。让我们使用ADASYN过采样方法运行管道:

sampler_method = ADASYN(random_state=42)
sampler_pipeline = make_pipeline(
    scaler,
    sampler_method,
    estimator)
cv_results = cross_validate(sampler_pipeline, X_train.drop(['SEX'], axis=1), y_train, scoring=['roc_auc','balanced_accuracy'], return_estimator=True)
model = sampler_pipeline.fit( X_train.drop(['SEX'], axis=1), y_train)

接下来,我们识别训练数据集中模型在男性人口上犯错误的例子——即模型预测错误的阴性例子。我们首先对与男性相关的数据进行子集划分,然后在此数据上运行预测:

X_train_males = X_train[X_train.SEX == 1].copy()
X_train_males["predictions"] = model.predict(X_train_males.drop(['SEX'], axis=1))
X_train_males['y_true'] = y_train.filter(X_train_males.index)

然后,我们选取true标签为1但模型预测为0的数据子集:

X_train_male_false_negatives = X_train_males[(X_train_males.y_true == 1) & (X_train_males.predictions == 0)]

我们随机选择 10%的值并将它们添加到X_train数据集中。我们利用.sample方法,并且这个随机选择是带替换进行的:

X_train_sample = X_train_male_false_negatives[X_train.columns].sample(frac=0.1, replace=True, random_state=42, axis=0)
y_train_sample = X_train_male_false_negatives['y_true'].sample(frac=0.1, replace=True, random_state=42, axis=0)

然后,我们将这 10%添加到X_trainy_train中,并创建一个新的数据集:

X_train_with_male_samples = pd.concat([X_train, X_train_sample], axis=0, ignore_index=True)
y_train_with_male_samples = pd.concat([y_train, y_train_sample], axis=0, ignore_index=True)

然后,我们在新的数据集上训练算法,并打印出验证指标和测试ROC 分数:

cv_results = cross_validate(sampler_pipeline, X_train_with_male_samples.drop(['SEX'], axis=1), y_train_with_male_samples, scoring=['roc_auc','balanced_accuracy'], return_estimator=True)
print(f"Validation roc auc : {cv_results['test_roc_auc'].mean():.3f} +/- {cv_results['test_roc_auc'].std():.3f}")
print(f"Validation balanced acc : {cv_results['test_balanced_accuracy'].mean():.3f} +/- {cv_results['test_balanced_accuracy'].std():.3f}")
model = sampler_pipeline.fit(X_train_with_male_samples.drop(['SEX'], axis=1), y_train_with_male_samples)
y_pred_proba = model.predict_proba(X_test.drop(['SEX'],axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'],axis=1))
roc_auc_score(y_test, y_pred_proba)
Validation roc auc : 0.824 +/- 0.005
Validation balanced acc : 0.754 +/- 0.005
0.8201623558253082

与过采样部分相比,验证指标和测试ROC 分数相当相似。接下来,我们回顾公平性指标,以检查它们是否有所改善:

calculate_fairness_metrics_mitigated_v6 = calculate_fairness_metrics(y_test, y_pred, A_test)

图 8.10 – 公平性指标

图 8.10 – 公平性指标

通过添加一些错误的阴性男性例子,我们可以看到均衡机会略微提高到了 0.098,人口比例也有所改善,增加到 0.85。我们相信,如果我们添加错误的阳性例子和错误的阴性例子,并将这些与欠采样和过采样技术结合起来,我们可以取得更好的结果。

为了演示这一点,我们将遍历四种欠采样技术(AllKNNRepeatedEditedNearestNeighboursInstanceHardnessThresholdTomek),两种过采样技术(SMOTEADASYN),以及两种过采样和欠采样的组合技术(SMOTEENNSMOTETomek)。这些算法的工作原理超出了本例的范围。相反,目标是演示这些数据技术如何导致更好的选择和具有略微较差性能但更高公平性的泛化模型。

我们现在将开发一种机制,首先训练算法,然后添加假阳性和假阴性示例。一旦添加了示例,我们将通过采样数据集并使用先前算法运行管道。我们将记录公平性结果和 ROC 分数,以找到在我们算法中最好地促进公平性和性能平衡的技术。

我们首先创建一个字典,包含上述每种采样技术的配置,这样我们就可以遍历它。我们可以将这个采样技术称为 AutoML:

methods = {
    "all_knn": AllKNN(n_jobs=-1),
    "renn": RepeatedEditedNearestNeighbours(n_jobs=-1),
    "iht": InstanceHardnessThreshold(
        estimator=DecisionTreeClassifier(**d_tree_params),
        random_state=42,
        n_jobs=-1,
        cv=3),
    "tomek": TomekLinks(n_jobs=-1),
    "adasyn" : ADASYN(random_state=42),
    "smote" : SMOTE(random_state=42),
    "smoteenn": SMOTEENN(random_state=42,
                         smote=SMOTE(random_state=42),
                         enn=EditedNearestNeighbours(n_jobs=-1)
                        ),
    "smotetomek": SMOTETomek(random_state=42,
                             smote=SMOTE(random_state=42),
                             tomek=TomekLinks(n_jobs=-1)
                            )
          }

接下来,我们创建了两个函数,这些函数接受训练数据集、模型、列及其子集值,以帮助创建随机样本。以下函数将采样假阳性:

def sample_false_positives(X_train, y_train, estimator, perc=0.1, subset_col="SEX", subset_col_value=1, with_replace=True):
    """Function to sample false positives"""
    X_train = X_train.copy()
    y_train = y_train.copy()
    X_train_subset = X_train[X_train[subset_col] == subset_col_value].copy()
    y_train_subset = y_train.filter(X_train_subset.index).copy()
    X_train_subset["predictions"] = estimator.predict(X_train_subset.drop([subset_col], axis=1))
    X_train_subset['y_true'] = y_train_subset.values
    X_train_subset_false_positives = X_train_subset[(X_train_subset.y_true == 0) & (X_train_subset.predictions == 1)]
    X_train_sample = X_train_subset_false_positives[X_train.columns].sample(frac=perc, replace=with_replace, random_state=42, axis=0)
    y_train_sample = X_train_subset_false_positives['y_true'].sample(frac=perc, replace=with_replace, random_state=42, axis=0)
    X_train_sample = pd.concat([X_train, X_train_sample], axis=0, ignore_index=True)
    y_train_sample = pd.concat([y_train, y_train_sample], axis=0, ignore_index=True)
    return X_train_sample, y_train_sample

此函数将采样假阴性。默认情况下,两种方法都会添加 10% 的随机示例,并替换它们:

def sample_false_negatives(X_train, y_train, estimator, perc=0.1, subset_col="SEX", subset_col_value=1, with_replace=True):
    """Function to sample false positives"""
    X_train = X_train.copy()
    y_train = y_train.copy()
    X_train_subset = X_train[X_train[subset_col] == subset_col_value].copy()
    y_train_subset = y_train.filter(X_train_subset.index).copy()
    X_train_subset["predictions"] = estimator.predict(X_train_subset.drop([subset_col], axis=1))
    X_train_subset['y_true'] = y_train_subset.values
    X_train_subset_false_negatives = X_train_subset[(X_train_subset.y_true == 1) & (X_train_subset.predictions == 0)]
    X_train_sample = X_train_subset_false_negatives[X_train.columns].sample(frac=perc, replace=with_replace, random_state=42, axis=0)
    y_train_sample = X_train_subset_false_negatives['y_true'].sample(frac=perc, replace=with_replace, random_state=42, axis=0)
    X_train_sample = pd.concat([X_train, X_train_sample], axis=0, ignore_index=True)
    y_train_sample = pd.concat([y_train, y_train_sample], axis=0, ignore_index=True)
    return X_train_sample, y_train_sample

接下来,我们创建一个函数,该函数在数据改进后计算测试指标。该函数接受测试数据和估计器,并返回模型指标和公平性指标:

def calculate_metrics(estimator, X_test, y_test, A_test):
    """Function to calculate metrics"""
    y_pred_proba = estimator.predict_proba(X_test)[:, 1]
    y_pred = model.predict(X_test)
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    balanced_accuracy = balanced_accuracy_score(y_test, y_pred)
    equalized_odds = equalized_odds_difference(
        y_test, y_pred, sensitive_features=A_test
    )
    dpr = demographic_parity_ratio(y_test, y_pred, sensitive_features=A_test)
    return roc_auc, balanced_accuracy, equalized_odds, dpr

接下来,我们创建一个管道,该管道将采样数据集,然后创建随机的假阳性 male 和假阴性 male 示例。然后我们将这些示例逐个合并到训练数据中,并重新训练相同的算法。然后我们计算指标并将它们存储在一个名为 results 的列表中,其中包含列。每次迭代都会添加带有模型性能和公平性指标的假阴性和假阳性示例。然后我们使用这个列表来比较算法之间的结果。

注意

创建管道的代码相当长。请参阅 GitHub 以获取完整代码:github.com/PacktPublishing/Data-Centric-Machine-Learning-with-Python/tree/main/Chapter%208%20-%20Techniques%20for%20identifying%20and%20removing%20bias

接下来,我们创建一个名为 df 的 DataFrame,并将所有的 test 指标添加进去,以便我们可以比较哪种方法获得了最佳模型性能和公平性指标:

df = pd.DataFrame(data=results,
                  columns=["method", "sample",
                           "test_roc_auc", "test_balanced_accuracy",
                           "equalized_odds",
                           "demographic_parity_ratio",
                           "validation_roc_auc",
                           "validation_balanced_accuracy"]
                 )

让我们根据均衡机会对 DataFrame 进行排序:

df.sort_values(by="equalized_odds")

我们可以看到,当数据集使用 Tomek Links 进行采样时,从边界移除了困难案例,并与额外的错误阳性男性训练样本结合,这导致了最佳均衡概率为 0.075;然而,没有达到 0.8 的人口比例。当使用 SMOTETomek 技术与错误阴性男性示例结合时,模型实现了 0.088 的均衡概率比,这是所有采样方法中最好的,模型也实现了高的人口比例比。

图 8.11 – 结果输出数据集

图 8.11 – 结果输出数据集

使用异常值进行过采样

在前面的步骤中,我们了解到通过将错误分类的示例添加到训练数据集中,我们能够提高模型公平性。在下一步中,我们不会随机选择样本,而是将利用一个识别异常值的算法,然后我们将这些异常值添加到训练数据集中作为过采样机制。

首先,我们创建一个管道来过采样少数类:

X_train_scaled = pd.DataFrame()
scaler = StandardScaler()
sampler = SMOTETomek(random_state=42,
                     smote=SMOTE(random_state=42),
                     tomek=TomekLinks(n_jobs=-1)
                    )

接下来,我们提取过采样数据。没有理由为什么不能选择欠采样或无采样。一旦提取过采样数据,我们将其缩放回原始特征空间:

columns = X_train.drop(['SEX'], axis=1).columns
X_train_scaled[columns] = scaler.fit_transform(X_train.drop(['SEX'], axis=1))
X_train_resample, y_train_resample = sampler.fit_resample(X_train_scaled, y_train)
X_train_resample[columns] = scaler.inverse_transform(X_train_resample)

接下来,我们训练隔离森林以识别 10%的异常值。为此,我们将污染率设置为0.1。然后我们在重采样数据上拟合模型,并在此数据上运行预测。我们将结果存储在一个名为IF_anomaly的列中,并将其添加到重采样数据集中。然后我们提取这些异常值,作为隔离森林标签,其值为-1

anomaly_model = IsolationForest(contamination=float(.1), random_state=42, n_jobs=-1)
anomaly_model.fit(X_train_resample)
X_train_resample['IF_anomaly'] = anomaly_model.predict(X_train_resample)
X_train_resample['default'] = y_train_resample
X_train_additional_samples = X_train_resample[X_train_resample.IF_anomaly == -1]
X_train_additional_samples.drop(['IF_anomaly'], axis=1, inplace=True)

接下来,我们将这些额外的数据点添加到原始数据集中,并训练决策树模型。一旦模型拟合完成,我们就在测试数据上计算 ROC 分数。我们可以看到这是 0.82:

X_train_clean = X_train_resample[X_train_resample.IF_anomaly != -1]
y_train_clean = X_train_clean.default
estimator.fit(X_train_clean.drop(['IF_anomaly', 'default'], axis=1), y_train_clean)
y_pred_proba = estimator.predict_proba(X_test.drop(['SEX'], axis=1))[:, 1]
y_pred = estimator.predict(X_test.drop(['SEX'], axis=1))
roc_auc_score(y_test, y_pred_proba)
0.8248481592937735

接下来,我们计算公平性指标。根据以下结果,我们可以说,在上一节中训练的模型产生了更好的公平性和人口比例比分数:

图 8.12 – 公平性指标

图 8.12 – 公平性指标

现在我们已经利用了各种欠采样和过采样的数据示例,包括重新引入随机错误分类的示例和异常值,在下一节中,我们将利用一种高级技术,我们将更慎重地选择添加哪些示例以及移除哪些示例,以进一步减少算法中的偏差。

使用 Shapley 值来检测偏差、过采样和欠采样数据

在本节中,我们将利用 Shapley 值来识别模型难以做出正确预测的示例。我们将使用影响分数来添加、消除或使用两者的组合来提高公平性指标。

SHAP(代表Shapley Additive exPlanations)是一种基于博弈论原理的无模型偏见机器学习方法。它通过分配一个分数来帮助研究特征及其交互对最终结果的重要性,类似于在游戏中计算每个玩家在特定时间点的贡献,就像在计算分数输出时那样。

Shapley 值可以帮助提供全局重要性(特征对所有预测的整体影响),也可以提供局部重要性(每个特征对单个结果的影响)。它还可以帮助理解影响的方向——也就是说,一个特征是否有积极影响或消极影响。

因此,在机器学习中,Shapley 值有很多应用场景,例如偏差检测、局部和全局模型调试、模型审计和模型可解释性。

在本节中,我们使用 Shapley 值来理解模型和特征对结果的影响。我们利用这些特征的影响,并确定模型最有可能犯错误的地方。然后我们应用两种技术:一种是从数据中删除这些行,另一种是对包含这些行的数据进行过采样。

首先,我们导入 SHAP 库,然后在过采样数据集上训练决策树模型。在步骤结束时,我们有一个模型和过采样的Xy样本。我们将SEX变量包含在训练数据中,以查看 Shapley 值是否可以帮助我们检测偏差。首先,我们需要将数据重新拆分为traintest集,就像前几节所做的那样:

model = DecisionTreeClassifier(**d_tree_params)
model.fit(X_train, y_train)
DecisionTreeClassifier(min_samples_leaf=10, random_state=42)

接下来,我们定义 SHAP 树解释器,通过提供决策树模型,然后使用.shap_values方法提取train集的 Shapley 值:

explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_train)

让我们提取 Shapley 值的第一个行,针对类别 0。数组包含每个特征值对最终输出的贡献。正值表示相应的特征对预测类别 0 有积极影响,而负值则对预测类别 0 产生负面影响:

shap_values[0][0]

这将打印出以下数组:

图 8.13 – 结果输出数组

图 8.13 – 结果输出数组

接下来,我们为类别标签 0 生成一个摘要图:

shap.summary_plot(shap_values[0], X_train)

这将生成以下图表:

图 8.14 – SHAP 值

图 8.14 – SHAP 值

红点代表特征的高值,而蓝点代表相应特征的低值。x轴表示 Shapley 值,正值表示数据点在预测类别 0 时具有积极影响,而负值表示对应特征的数据点对类别 0 的预测产生负面影响。如果我们看图 8.13,高利率和男性客户对类别 0 的预测产生负面影响是非常明显的。Shapley 值确实表明模型对男性客户存在偏见。

接下来,我们为类别标签 1 生成一个摘要图:

shap.summary_plot(shap_values[1], X_train)

这将生成以下摘要图:

图 8.15 – SHAP 摘要图

图 8.15 – SHAP 摘要图

与类别 0 的摘要图相比,高利率和男性客户对贷款违约有积极影响——也就是说,如果你是男性并且之前有更高的利率,你很可能会违约。

我们之前了解到,通过从模型训练中移除 SEX 特征,模型变得更加公平,并且使用摘要图清楚地指示 Shapley 值。现在,我们通过训练不带 SEX 特征的新模型来提取 Shapley 值。然后我们对训练数据进行评分,首先识别所有对应于假阴性和假阳性的行。然后我们计算模型出错时每行的 Shapley 值总和,并保留影响最低的行。我们运行两个实验:首先,我们对训练数据集进行下采样并计算公平性指标,其次,我们对训练数据集进行上采样以向模型提供更好的信号并重新计算公平性指标。

首先,让我们在不包含 SEX 特征的情况下训练模型:

model = DecisionTreeClassifier(**d_tree_params)
X_train_samples = X_train.drop(['SEX'], axis=1).copy()
y_train_samples = y_train.copy()
model.fit(X_train_samples, y_train_samples)

接下来,我们提取 Shapley 值:

explainer = shap.Explainer(model)
shap_values = explainer.shap_values(X_train_samples)

我们对训练数据进行评分,计算预测值,并将这些值存储在 Y_pred 中:

Y_pred = model.predict(X_train_samples)

然后我们将检查索引 0 处类别 0 和类别 1 的 Shapley 值总和,并打印相应的预测值和 true 值:

print(f"Shapley value for first value in the dataset for class 0 : {sum(shap_values[0][0])}")
print(f"Shapley value for first value in the dataset for class 1 : {sum(shap_values[1][0])}")
print(f"Prediction of first value is {Y_pred[0]}")
print(f"Actual prediction is {y_train_samples[0]}")
Shapley value for first value in the dataset for class 0 : -0.07290931372549389
Shapley value for first value in the dataset for class 1 : 0.07290931372548978
Prediction of first value is 0
Actual prediction is 1

模型预测了 0。接下来,我们提取模型出错时的 Shapley 值。为此,我们使用带有 zip 功能的列表推导。数组的第一个值将是数据点的索引位置,这样我们就可以知道哪个 Shapley 值与哪一行相关。接下来的值按照预测顺序排列,包括 true 值、类别 0 的 Shapley 值行总和以及类别 1 的 Shapley 值总和。一旦我们提取了这些值,我们就创建一个 DataFrame 并将值存储在 df 中,然后通过 DataFrame 进行采样以查看五个值。我们使用随机种子以确保可重复性:

data = [(index, pred, actual, sum(s0), sum(s1)) for
        index, (pred, actual, s0, s1) in
        enumerate(zip(Y_pred, y_train_samples, shap_values[0], shap_values[1]))
        if pred != actual]
df = pd.DataFrame(data=data, columns=["index", "predictions","actuals", "shap_class_0", "shap_class_1"])
df.sample(5, random_state=42)

这将生成以下输出:

图 8.16 – 显示造成错误的 Shapley 值的 DataFrame

图 8.16 – 显示造成错误的 Shapley 值的 DataFrame

对于索引 7915,Shapley 值很接近,这意味着每个类别的特征对模型预测的贡献更接近 0,而对于索引 4255,Shapley 值与 0 相差较远,特征在预测每个类别时具有区分性。

由于我们可以提取每个类别的特征 SHAP 影响值,我们想知道 Shapley 值影响最高的行,以便我们可以从训练中消除这样的数据点;影响低且接近边界的地方,我们可以对数据进行过采样。

观察索引4255的力图,对于预期的类0,由于f(x)相当低,模型可能会预测1,而模型错误地预测了1,而预期类1的力图显示了f(x)值为0.7。这样的数据点可以从数据集中删除:

shap.force_plot(explainer.expected_value[0], shap_values[0][4255,:], X_train_samples.iloc[4255, :], matplotlib=True)

这将生成以下图表:

图 8.17 – 类 0 的力图

图 8.17 – 类 0 的力图

让我们看看类1的力图:

shap.force_plot(explainer.expected_value[1], shap_values[1][4255,:], X_train_samples.iloc[4255, :], matplotlib=True)

这将显示以下输出:

图 8.18 – 类 1 的力图

图 8.18 – 类 1 的力图

现在,我们计算行索引4255的 Shapley 影响,因为它是一个假阳性预测。行索引在 DataFrame 的422位置。我们取 Shapley 影响的绝对值,并在 Shapley 影响最高且预测错误的地方,删除这些值以提高模型性能:

index = 422
shap_impact = abs(df['shap_class_0'][index])
print(shap_impact)
0.4787916666666662

接下来,我们创建一个计算 Shapley 影响的函数。我们感兴趣的是那些单个特征具有至少0.2 Shapley 影响的行。首先,我们获取数组中每个特征的绝对影响,然后提取最大值。如果最大值大于0.2,我们继续处理该行。接下来,我们检查预测值与实际值不匹配的地方,并从这样的行中提取 SHAP 影响:

def get_shapley_impact(shap_value, threshold=0.2):
    """Calculate Shapley impact"""
    shap_value_impacts = np.abs(shap_value)
    if np.max(shap_value_impacts) >= threshold:
        return np.abs(np.sum(shap_value))

我们创建一个保留数据集,其中X_train将被进一步分为训练集和验证集。我们利用 80%进行训练,20%进行验证:

X_train_sample, X_val, y_train_sample, y_val, A_train_sample, A_val = train_test_split(X_train,
                   y_train,
                   A_train,
                   test_size=0.2,
                   stratify=y_train,
                   random_state=42)

然后,我们使用SMOTETomek重新采样数据集,这是公平性和性能方面最好的采样方法,通过将困难示例重新添加到数据集中来实现。一旦数据集被重新采样,我们就按照前面的步骤训练标准决策树,并在保留的验证数据集上计算 ROC 分数:

model = DecisionTreeClassifier(**d_tree_params)
scaler = StandardScaler()
sampler = SMOTETomek(random_state=42,
                     smote=SMOTE(random_state=42),
                     tomek=TomekLinks(n_jobs=-1)
                    )
columns = X_train_sample.columns
X_train_scaled = pd.DataFrame()
X_train_scaled[columns] = scaler.fit_transform(X_train_sample[columns])
X_train_resampled, y_train_resampled = sampler_method.fit_resample(X_train_scaled, y_train_sample)
X_train_resampled[columns] = scaler.inverse_transform(X_train_resampled)
A_train_resampled = X_train_resampled['SEX'].copy()
model.fit(X_train_resampled.drop(['SEX'], axis=1), y_train_resampled)
Y_pred = model.predict(X_train_resampled.drop(['SEX'], axis=1))
y_val_pred = model.predict_proba(X_val.drop(['SEX'], axis=1))[: ,1]
val_roc = roc_auc_score(y_val, y_val_pred)
print(f"Validation roc auc : {val_roc}")
Validation roc auc : 0.8275769341994823

我们在测试数据集上计算公平性和性能指标。均衡机会很高,但人口统计平等等比率在可接受范围内:

y_pred_proba = model.predict_proba(X_test.drop(['SEX'], axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'], axis=1))
print(f"Roc: {roc_auc_score(y_test, y_pred_proba)}")
Roc: 0.8258396009334518

接下来,我们计算公平性指标:

calculate_fairness_metrics(y_test, y_pred, A_test)

这将打印出以下指标:

图 8.19 – 公平性指标

图 8.19 – 公平性指标

我们使用 SHAP 解释器提取 Shapley 值。我们确保已删除SEX特征:

explainer = shap.Explainer(model)
columns = X_train_resampled.drop(['SEX'], axis=1).columns
shap_values = explainer.shap_values(X_train_resampled[columns])

公平性指标表明,男性和女性子类之间的假阴性率差距更大,因此我们专注于使用 Shapley 值减少男性的假阴性案例。

在下一步中,我们提取模型预测为类0但真实值为1的 Shapley 值。因此,我们对类1的 Shapley 值感兴趣,因为在模型出错的地方,类1的 SHAP 影响高可能是一个模型无法正确预测的数据点:

shapley_impact_false_negative = [(i, get_shapley_impact(s), y, p, a)
                                for s, i, y, p, a
                                in zip(shap_values[1], X_train_resampled.index, y_train_resampled, Y_pred, A_train_resampled)
                                 if y == 1 and p == 0 and a == 1]

我们还对那些 Shapley 值和模型预测都与实际值一致的数据点感兴趣。因此,我们关注那些模型正确预测男性数据点的类别为0的数据点。一旦我们提取了这些数据点,我们就关注类别0的高影响力 Shapley 值,以便我们可以对这些数据进行过采样,从而使模型可以对这些数据点获得更好的信号:

shapley_impact_true_negative = [(i, get_shapley_impact(s), y, p, a)
                                for s, i, y, p, a
                                in zip(shap_values[0], X_train_resampled.index, y_train_resampled, Y_pred, A_train_resampled)
                                 if y == 0 and p == 0 and a == 1]

接下来,我们排序假阴性 Shapley 值,以便我们可以提取高影响力的数据点:

shapley_impact_false_negative_sorted = sorted([i for i in shapley_impact_false_negative if i[1] is not None], key=lambda x: x[1], reverse=True)

与前面类似,我们感兴趣的是具有高影响力的真阴性 Shapley 值,因此我们根据高影响力的 Shapley 值对列表进行排序:

shapley_impact_true_negative_sorted =  sorted([i for i in shapley_impact_true_negative if i[1] is not None], key=lambda x: x[1], reverse=True)

现在我们已经提取并排序了假阴性和真阴性男性数据点的 Shapley 值,我们从假阴性列表中挑选前 100 个数据点进行消除,并从真阴性列表中挑选前 100 个数据点重新添加到训练数据中。真阴性列表中的前 100 个数据点将被随机打乱,并且只随机添加其中的 50 个数据点,采用替换策略。我们鼓励从业者尝试另一个打乱比例。一旦确定了用于消除和重新引入到最终训练集中的数据点,我们就更新训练数据。这些数据被命名为X_train_finaly_train_final

data_points_to_eliminate = [i[0] for i in shapley_impact_false_negative_sorted[0:100]]
data_points_to_add = [i[0] for i in shapley_impact_true_negative_sorted[0:100]]
X_train_added = X_train_resampled[columns].iloc[data_points_to_add].sample(frac=0.5, replace=True, random_state=42, axis=0)
y_train_added = y_train_resampled.iloc[data_points_to_add].sample(frac=0.5, replace=True, random_state=42, axis=0)
X_train_reduced = X_train_resampled[columns].drop(data_points_to_eliminate)
y_train_reduced = y_train_resampled.drop(data_points_to_eliminate)
X_train_final = pd.concat([X_train_reduced, X_train_added], axis=0, ignore_index=True)
y_train_final = pd.concat([y_train_reduced, y_train_added], axis=0, ignore_index=True)

接下来,我们训练更新的训练数据,并在测试数据上计算公平性指标和性能指标。很明显,假阴性率之间的差距已经减少,均衡的几率比已提高到 0.082,ROC 分数从先前的 0.825 略微提高到 0.826:

estimator = DecisionTreeClassifier(**d_tree_params)
model = estimator.fit(X_train_final, y_train_final)
y_pred_proba = model.predict_proba(X_test.drop(['SEX'], axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'], axis=1))
print(f"Roc: {roc_auc_score(y_test, y_pred_proba)}")
Roc: 0.8262453372973797

我们重新计算公平性指标:

calculate_fairness_metrics(y_test, y_pred, A_test)

输出结果如下:

图 8.20 – 公平性指标

图 8.20 – 公平性指标

既然我们已经确定通过使用 Shapley 值可以识别难以分类且易于分类的数据点,我们可以构建一个自动机制来迭代数据点,从而可以达到比之前更好的公平性分数。

接下来,我们创建一系列百分比以进行迭代,这样我们就可以利用并排序消除和重新引入的前数据点的百分比。我们将使用 NumPy 的linspace方法创建一个百分比值的列表以进行迭代。我们从0.050.5(5%到 50%)中选择 10 个值。我们称这个列表为perc_points_to_eliminate

perc_points_to_eliminate = np.linspace(0.05,0.5,10)
perc_points_to_eliminate
array([0.05, 0.1 , 0.15, 0.2 , 0.25, 0.3 , 0.35, 0.4 , 0.45, 0.5 ])

我们遍历这些百分比,并重复先前的步骤,其中我们消除了一些值并重新引入了一些值。然而,这次,我们使用百分比来移除顶部百分比的数据点或引入顶部百分比的数据点。

我们还创建了一个空的数据列表,因此,对于每次迭代,我们捕获消除或重新引入的数据点的百分比、测试数据中的假阴性率和假阳性率、均衡的几率比和人口比例比。

一旦我们遍历了所有值 - 10*10 次迭代,我们将它们存储在一个 DataFrame 中,以查看需要移除多少数据点以及需要添加多少数据点才能达到最佳的公平性指标:

fn_examples = len(shapley_impact_false_negative)
tn_examples = len(shapley_impact_true_negative)
model = DecisionTreeClassifier(**d_tree_params)
data = []
for fnp in perc_points_to_eliminate:
    data_points_to_eliminate = [idx[0] for idx in shapley_impact_false_negative_sorted[0:(round(fn_examples*fnp))]]
    for tnp in perc_points_to_eliminate:
        data_points_to_add = [idx[0] for idx in shapley_impact_true_negative_sorted[0:(round(tn_examples*tnp))]]
        X_train_added = X_train_resampled[columns].iloc[data_points_to_add].sample(frac=0.5, replace=True, random_state=42, axis=0)
        y_train_added = y_train_resampled.iloc[data_points_to_add].sample(frac=0.5, replace=True, random_state=42, axis=0)
        X_train_reduced = X_train_resampled[columns].drop(data_points_to_eliminate)
        y_train_reduced = y_train_resampled.drop(data_points_to_eliminate)
        X_train_final = pd.concat([X_train_reduced, X_train_added], axis=0, ignore_index=True)
        y_train_final = pd.concat([y_train_reduced, y_train_added], axis=0, ignore_index=True)
        model.fit(X_train_final, y_train_final)
        y_pred = model.predict(X_test.drop(['SEX'], axis=1))
        fpr = false_positive_rate(y_test, y_pred)
        fnr = false_negative_rate(y_test, y_pred)
        equalized_odds_mitigated = equalized_odds_difference(
            y_test, y_pred, sensitive_features=A_test
        )
        demographic_parity_ratio_mitigated = demographic_parity_ratio(y_test, y_pred, sensitive_features=A_test)
        data.append((fnp,
                     tnp,
                     fpr,
                     fnr,
                     equalized_odds_mitigated,
                     demographic_parity_ratio_mitigated
                    ))

接下来,我们创建一个名为 df_shapley 的 DataFrame,用于存储每个迭代的元数据,并按均衡机会比进行排序:

columns = ["perc_false_negative_removed",
          "perc_true_negative_added",
          "false_positive_rate",
          "false_negative_rate",
          "equalized_odds_mitigated",
          "demographic_parity_ratio_mitigated"]
df_shapley = pd.DataFrame(data=data, columns=columns)
df_shapley.sort_values(by="equalized_odds_mitigated")

这将输出以下 DataFrame:

图 8.21 – 排序后的 df_shapley DataFrame

图 8.21 – 排序后的 df_shapley DataFrame

显然,当移除顶部 25% 的误报数据点并重新引入顶部 30% 的真实负数据点时,模型可以达到均衡机会比 0.074,以及最佳人口统计平等等级比得分 0.85。

最后,我们提取顶部百分比并训练最终模型:

top_values = df_shapley.sort_values(by="equalized_odds_mitigated").values[0]
perc_false_negative_removed = top_values[0]
perc_true_negative_added = top_values[1]
columns = X_train_resampled.drop(['SEX'], axis=1).columns
data_points_to_eliminate = [i[0] for i in shapley_impact_false_negative_sorted[0:(round(fn_examples*perc_false_negative_removed))]]
data_points_to_add = [i[0] for i in shapley_impact_true_negative_sorted[0:(round(tn_examples*perc_true_negative_added))]]
X_train_added = X_train_resampled[columns].iloc[data_points_to_add].sample(frac=0.5, replace=True, random_state=42, axis=0)
y_train_added = y_train_resampled.iloc[data_points_to_add].sample(frac=0.5, replace=True, random_state=42, axis=0)
X_train_reduced = X_train_resampled[columns].drop(data_points_to_eliminate)
y_train_reduced = y_train_resampled.drop(data_points_to_eliminate)
X_train_final = pd.concat([X_train_reduced, X_train_added], axis=0, ignore_index=True)
y_train_final = pd.concat([y_train_reduced, y_train_added], axis=0, ignore_index=True)

我们训练模型并计算公平性和模型指标。我们可以看到,对于 female 的误报率有所增加,但 malefemale 之间的差距已经减小,而 male 的误报率有所降低。ROC 得分达到 0.82,但根据两个公平性指标,模型更加公平:

estimator = DecisionTreeClassifier(**d_tree_params)
model = estimator.fit(X_train_final, y_train_final)
y_pred_proba = model.predict_proba(X_test.drop(['SEX'], axis=1))[:, 1]
y_pred = model.predict(X_test.drop(['SEX'], axis=1))
print(f"Roc: {roc_auc_score(y_test, y_pred_proba)}")
Roc: 0.820911097453972

最后,我们计算公平性指标。

calculate_fairness_metrics(y_test, y_pred, A_test)

这将打印出以下内容:

图 8.22 – 公平性指标

图 8.22 – 公平性指标

现在我们已经探索了通过提高数据质量来减少偏差的不同数据中心技术,我们鼓励您尝试之前的技巧,并尝试这些技巧的组合。一旦您用尽了这些数据中心方法,我们鼓励您使用一些模型中心方法,例如利用公平性感知的算法,尝试集成方法、AutoML 或遍历您自己的算法列表。

摘要

本章广泛探讨了机器学习中普遍存在的偏差挑战。它从解释机器学习模型中固有的各种偏差形式开始,并检查了它们对各个行业的影响。重点是识别、监控和减轻偏差,强调收集具有最小选择和抽样偏差的数据的重要性。

该主题倡导在解决偏差问题时,以数据为中心的强制措施优于以模型为中心的强制措施。探讨了过采样、欠采样、特征选择增强和异常检测等技术,用于偏差校正。Shapley 值在偏差识别中起着至关重要的作用,强调移除具有不匹配高 Shapley 值的示例,并通过替换重新引入数据点以改善比率。根据敏感变量(如 SEX)对误分类示例进行分层,以实现有针对性的偏差校正。

本章通过强调对敏感变量进行数据集精炼和平衡的重要性作为基础步骤。它建议在数据集本身得到改进后,转向以模型为中心的方法,例如集成和公平性算法。这些后续的以模型为中心的策略旨在提高性能和公平性指标,为更通用和公平的人工智能模型奠定基础。

这种全面的方法旨在创建一个平衡的数据集,作为应用以模型为中心的技术的前奏,以促进人工智能系统中的性能和公平性。

第九章:在机器学习中处理边缘案例和罕见事件

机器学习领域,正确识别和处理边缘情况至关重要。边缘情况指的是与数据集大多数数据显著不同的实例,它们可能会对机器学习模型的性能和可靠性产生重大影响。由于类别不平衡问题,罕见事件可能对机器学习模型具有挑战性,因为它们可能没有足够的数据来有效地学习模式。类别不平衡发生在某一类别(罕见事件)相对于其他类别(们)显著代表性不足时。传统的机器学习算法在这种场景下往往表现不佳,因为它们可能偏向于多数类别,导致在识别罕见事件时准确性降低。

在本章中,我们将探讨使用 Python 代码示例检测机器学习和数据中边缘案例的各种技术和方法。我们将探讨统计技术,包括使用可视化和其他措施如 Z 分数来分析数据分布并识别潜在的异常值。我们还将关注隔离森林和半监督方法,如自编码器,以揭示数据集中的异常和不规则模式。我们将学习如何通过过采样、欠采样和生成合成数据等技术来解决类别不平衡并提高模型性能。

在本章的后半部分,我们将了解调整学习过程以考虑不平衡类别的必要性,特别是在罕见事件具有重大后果的场景中。我们将探讨选择适当的评估指标的重要性,强调那些考虑类别不平衡的指标,以确保对模型性能的公平和准确评估。最后,我们将了解集成方法,如 bagging、boosting 和 stacking 如何帮助我们增强模型的鲁棒性,特别是在罕见事件发挥关键作用的场景中。

以下关键主题将涵盖:

  • 在机器学习中检测罕见事件和边缘案例的重要性

  • 统计方法

  • 异常检测

  • 数据增强和重采样技术

  • 成本敏感学习

  • 选择评估指标

  • 集成技术

在机器学习中检测罕见事件和边缘案例的重要性

在机器学习中,检测罕见事件和边缘情况至关重要,原因有以下几点:

  • 关键场景中的决策:罕见事件通常代表需要立即关注或特殊处理的临界场景或异常情况。例如,在医疗诊断中,罕见疾病或极端病例可能需要紧急干预。准确检测这些事件可以导致更好的决策并防止不良后果。

  • 不平衡数据集:许多现实世界的数据集存在类别不平衡的问题,其中一个类别(通常是罕见事件)与其他类别相比显著代表性不足。这可能导致在少数类别上表现不佳的偏见模型。检测罕见事件有助于确定需要特殊处理的需求,例如使用重采样技术或采用适当的评估指标以确保公平评估。

  • 欺诈检测:在欺诈检测应用中,罕见事件通常对应于欺诈交易或活动。检测这些罕见案例对于防止财务损失和确保金融系统的安全至关重要。

  • 质量控制与异常检测:在制造和工业过程中,检测罕见事件可以帮助识别有缺陷或异常的产品或流程。这使及时干预成为可能,从而提高产品质量并保持运营效率。

  • 预测性维护:在预测性维护中,检测边缘案例可以表明潜在的设备故障或异常行为,从而允许主动维护以减少停机时间并提高生产力。

  • 模型泛化:通过准确识别和处理罕见事件,机器学习模型可以更好地泛化到未见数据,并有效地处理现实世界场景。

  • 客户行为分析:在营销和客户分析中,检测罕见事件可以揭示有趣的异常模式或行为,例如识别高价值客户或检测潜在的流失者。

  • 安全和入侵检测:在网络安全领域,罕见事件可能表明安全漏洞或网络攻击。实时检测和应对这些事件对于确保数字系统的安全和完整性至关重要。

  • 环境监测:在环境应用中,罕见事件可能表明不寻常的生态条件或自然灾害。检测此类事件有助于灾害准备和环境监测工作。

让我们讨论不同的统计方法来分析数据分布并识别潜在的异常值。

统计方法

统计方法为我们识别数据中的异常值和异常情况提供了宝贵的工具,有助于数据预处理和决策制定。在本节中,我们将讨论如何使用诸如 Z 分数、四分位数范围IQR)、箱线图和散点图等方法来揭示数据中的异常情况。

Z 分数

Z 分数,也称为标准分数,是一种统计量,表示数据点与数据均值之间的标准差数。Z 分数用于标准化数据,并允许在不同数据集之间进行比较,即使它们有不同的单位或尺度。它们在检测异常值和识别数据集中的极端值方面特别有用。计算数据集中数据点 x 的 Z 分数的公式如下:

Z = (x − μ) / σ

在这里,以下规则适用:

  • Z 是数据点 x 的 Z 分数

  • x 是数据点的值

  • μ 是数据集的均值

  • σ 是数据集的标准差

Z 分数广泛用于检测数据集中的异常值。具有超出一定阈值(例如,Z > 3 或 Z < -3)的 Z 分数的数据点被认为是异常值。这些异常值可能代表数据中的极端值或测量误差。通过将数据转换为 Z 分数,均值变为 0,标准差变为 1,从而得到一个标准化的分布。

Z 分数用于正态性检验,以评估数据集是否遵循正态(高斯)分布。如果数据集遵循正态分布,则大约 68% 的数据点的 Z 分数应在 -1 和 1 之间,约 95% 在 -2 和 2 之间,几乎所有数据点都在 -3 和 3 之间。在假设检验中,Z 分数用于计算 p-值并对总体参数进行推断。

例如,当总体标准差已知时,Z 测试通常用于样本均值比较。Z 分数在异常检测中很有用,当我们想要识别与规范显著偏离的数据点时。高 Z 分数可能表明数据中的异常行为或罕见事件。

让我们使用 Loan Prediction 数据集在 Python 中探索这个概念。让我们首先加载数据集:

import pandas as pd
import numpy as np
df = pd.read_csv('train_loan_prediction.csv')
df.head().T

在这里,我们看到样本数据集:

图 9.1 – df DataFrame

图 9.1 – df DataFrame

现在我们已经加载了数据集,我们可以在一些数值特征上计算 Z 分数:

numerical_features = ['ApplicantIncome', 'CoapplicantIncome', 'LoanAmount']
z_scores = df[numerical_features].apply(lambda x: (x - np.mean(x)) / np.std(x))

我们利用 Z 分数有效地检测数据集中的异常值:

threshold = 3
outliers = (z_scores > threshold) | (z_scores < -threshold)
outliers['is_outlier'] = outliers.any(axis=1)
outlier_rows = df[outliers['is_outlier']]
outlier_rows

通过设置 Z 分数阈值为 3,我们可以通过检查是否有任何行的值超出 Z 分数边界(大于 3 或小于 -3)来识别异常值。这种方法使我们能够确定与均值显著偏离的数据点,并使我们能够采取适当的措施来处理这些异常值,确保数据的完整性和后续分析和模型的准确性。

使用这种方法得到的输出 DataFrame 包含异常值如下:

图 9.2 – 结果的 outlier_rows DataFrame

图 9.2 – 结果的 outlier_rows DataFrame

总结来说,Z 分数使得准确识别异常值变得更加容易。它们作为一个有用的工具,帮助我们更好地理解数据模式。在下一节关于四分位数的讨论中,我们将进一步探讨检测和解决异常值的替代方法。

四分位距(IQR)

IQR 是一种用于描述数据集分布或分散程度的统计量。它在识别和处理异常值以及理解数据的中心趋势方面特别有用。IQR 定义为数据集的第一四分位数(Q1)和第三四分位数(Q3)之间的范围。四分位数是将数据集分为四个相等部分,每个部分包含 25%的数据的点。

可以使用以下公式计算 IQR:

IQR = Q3 − Q1

在这里,以下规则适用:

  • Q1 是第一四分位数(25 百分位数),表示低于此值的 25%的数据。

  • Q3 是第三四分位数(75 百分位数),表示低于此值的 75%的数据。

IQR 通常用于识别数据集中的异常值。低于 Q1 - 1.5 * IQR 或高于 Q3 + 1.5 * IQR 的数据点被认为是异常值,可能需要进一步调查。IQR 提供了关于数据分布的有价值信息。它有助于理解数据集中间 50%的分布,可以用来评估分布的对称性。在比较数据集时,IQR 可以用来评估两个或更多数据集之间数据分散的差异。

下面是一个简单的例子,说明如何使用 Python 和 NumPy 在贷款预测数据集上计算数据集的 IQR。首先进行异常值检测,我们计算数值特征的 IQR。通过定义一个阈值,通常是 Tukey 方法中 IQR 的 1.5 倍,该方法涉及计算 IQR 为第三四分位数(Q3)和第一四分位数(Q1)之间的差值,我们可以识别异常值。Tukey 方法是一种稳健的统计技术,有助于检测与整体分布显著偏离的数据点,为识别数据集中的潜在异常提供了一个可靠的度量。一旦所有数值特征都被标记为异常值,我们就可以显示具有异常值的行,这有助于进一步分析和潜在的数据处理。使用 IQR 和 Tukey 方法一起可以促进有效的异常值检测,并有助于确保数据集的完整性:

Q1 = df[numerical_features].quantile(0.25)
Q3 = df[numerical_features].quantile(0.75)
IQR = Q3 - Q1
threshold = 1.5
outliers = (df[numerical_features] < (Q1 - threshold * IQR)) | (df[numerical_features] > (Q3 + threshold * IQR))
outliers['is_outlier'] = outliers.any(axis=1)
outlier_rows = df[outliers['is_outlier']]
outlier_rows

下面是使用此方法得到的异常值输出 DataFrame:

图 9.3 – 结果的 outlier_rows DataFrame

图 9.3 – 结果的 outlier_rows DataFrame

总之,探索四分位数间距(IQR)为识别异常值提供了一种有价值的技术,尤其是在捕捉数据集的中心趋势方面特别有效。虽然 IQR 在关注中间范围至关重要的情况下具有优势,但认识到它在捕捉整个数据分布方面的局限性是至关重要的。在接下来的关于箱线图的章节中,我们将深入研究一种补充 IQR 的图形表示,提供一种在多种情境下更好地理解数据分散和异常值的视觉工具。

箱线图

箱线图,也称为箱形图,是数据集分布的图形表示。它们提供了一种快速且信息丰富的可视化方式,可以直观地查看数据的分布和偏斜,识别潜在的异常值,并比较多个数据集。箱线图在处理连续数值数据时特别有用,可以用来深入了解数据的中心趋势和变异性。

这里是箱线图的组成部分:

  • 箱线图(IQR):箱线图表示 IQR,即数据的第一四分位数(Q1)和第三四分位数(Q3)之间的范围。它覆盖了数据集的中间 50%,并提供了数据分布的视觉表示。

  • 中位数(Q2):中位数,由箱内的水平线表示,指示数据集的中心值。它将数据分为两个相等的部分,其中 50%的数据点低于中位数,50%的数据点高于中位数。

  • :须从箱的边缘延伸到位于“须长度”内的最远数据点。须的长度通常由一个因子(例如,IQR 的 1.5 倍)确定,并用于识别潜在的异常值。

  • 异常值:位于须外的数据点被认为是异常值,通常单独作为单独的点或圆圈绘制。它们是显著偏离中心分布的数据点,可能需要进一步调查。

以下是一些箱线图的优点:

  • 可视化数据分布:箱线图提供了一种直观的方式来查看数据的分布和偏斜,以及识别任何潜在的数据簇或缺口。

  • 比较数据集:箱线图在并排比较多个数据集时很有用,允许轻松比较中心趋势和变异性。

  • 异常值检测:箱线图通过突出显示位于须外的数据点来促进异常值检测,有助于识别异常或极端值。

  • 处理偏斜数据:箱线图对极端值的影响具有鲁棒性,并且比传统的均值和标准差更有效地处理偏斜数据分布。

让我们使用 Python 中的matplotlib库来实现这种方法。在这里,我们将为ApplicantIncomeLoanAmount生成两个箱线图:

import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=(10, 6))
plt.subplot(2, 1, 1)
df.boxplot(column='ApplicantIncome')
plt.title('Box Plot - ApplicantIncome')
plt.subplot(2, 1, 2)
df.boxplot(column='LoanAmount')
plt.title('Box Plot - LoanAmount')
plt.tight_layout()
plt.show()

这里是绘图输出。我们看到在这些列中存在一些可能的异常值:

图 9.4 – ApplicantIncome(顶部)和 LoanAmount(底部)的箱线图

图 9.4 – ApplicantIncome(顶部)和 LoanAmount(底部)的箱线图

总结箱线图的使用,我们发现它们是一种强大的视觉辅助工具,与四分位数范围(IQR)一起描绘了数据的中心趋势和数据分散。虽然箱线图在提供整体视图方面表现出色,但重要的是要认识到它们可能无法捕捉到复杂数据集的所有细微差别。在下一节中,我们将探讨一种多功能的图形工具,它提供了一个更广阔的视角,有助于识别数据中变量之间的关系和模式。

散点图

散点图是一种流行且多功能的数据可视化技术,用于探索两个连续数值变量之间的关系。它们提供了一个清晰的视觉表示,说明一个变量(自变量)如何影响或影响另一个变量(因变量)。散点图在识别数据中的模式、相关性、簇和异常值方面特别有用,是数据分析中探索性数据分析(EDA)的一个基本工具。

现在我们来探讨散点图的构建及其关键特征。

散点图构建

要创建散点图,需要将两个数值变量的值绘制在笛卡尔坐标系上的点。每个点代表一个数据观测值,其中x坐标对应自变量的值,y坐标对应因变量的值。多个数据点共同形成一个散点图,可以提供关于两个变量之间关系的见解。

散点图的关键特征

以下是一些散点图的关键特征:

  • 相关性:散点图帮助我们评估两个变量之间的相关性或关系。如果图上的点似乎形成了一个清晰的趋势或模式(例如,线性或非线性趋势),则表明变量之间存在显著的相关性。如果点分布随机,可能没有或相关性较弱。

  • 聚类分析:散点图可以揭示数据点的簇,表明数据中可能存在的子组或模式。

  • 异常值检测:散点图通过识别远离主要数据点群的数据点来促进异常值检测。

  • 数据分布:数据点在x轴和y轴上的分布提供了关于变量变异性的见解。

  • 可视化回归线:在某些情况下,可以将回归线拟合到散点图上,以模拟变量之间的关系并做出预测。

让我们在 Python 的ApplicantIncomeLoanAmount列上实现这一功能:

plt.figure(figsize=(8, 6))
plt.scatter(df['ApplicantIncome'], df['LoanAmount'])
plt.xlabel('ApplicantIncome')
plt.ylabel('LoanAmount')
plt.title('Scatter Plot - ApplicantIncome vs. LoanAmount')
plt.show()

下面是展示散点图结果的输出:

图 9.5 – 展示 ApplicantIncome 和 LoanAmount 的散点图

图 9.5 – 展示申请收入和贷款金额的散点图

正如你所见,一些点明显偏离了大多数人群,这表明可能存在潜在的异常值。这些异常数据点与整体模式相区别,需要进一步调查以了解其重要性和对两个变量之间关系的影响。识别和处理异常值对于确保准确的数据分析和模型性能至关重要。通过可视化散点图,我们可以获得关于数据分布和关联的有价值见解,为有效的决策和数据探索铺平道路。

异常检测

异常检测是检测罕见事件的一种特定方法,其重点是识别与规范或正常行为显著偏离的实例。异常可能由罕见事件、错误或数据集中不典型的异常模式引起。当罕见事件的数据有限或没有标记数据时,这种技术特别有用。常见的异常检测算法包括以下:

  • 无监督方法:例如隔离森林和单类 SVM等技术可以在不要求罕见事件的标记示例的情况下用于识别数据中的异常。

  • 半监督方法:这些方法在训练期间结合正常和异常数据,但只有有限数量的标记异常。自编码器和变分自编码器是半监督异常检测算法的例子。

  • 监督方法:如果可用少量标记的异常,可以使用监督学习算法,如随机森林、支持向量机(SVM)和神经网络进行异常检测。

让我们通过 Python 代码示例详细了解这些方法。

使用隔离森林的无监督方法

隔离森林是一种用于无监督学习场景中异常检测的高效且有效的算法。它通过构建隔离树(随机决策树)来隔离数据中的异常或罕见事件,这些树将异常与大多数正常数据点分开。它是由 Fei Tony Liu、Kai Ming Ting 和 Zhi-Hua Zhou 在 2008 年发表的论文《Isolation Forest》中引入的。以下是隔离森林算法的一些关键概念和特性:

  • 随机划分:隔离森林使用随机划分策略来创建隔离树。在构建树的每个步骤中,选择一个随机特征,并在所选特征的值范围内选择一个随机分割值来创建一个节点。这种随机划分导致异常的路径变短,使得它们更容易从正常数据点中隔离出来。

  • 路径长度:Isolation Forest 背后的关键思想是异常被隔离到包含较少数据点的较小分区中,而正常数据点在较大的分区中分布得更均匀。数据点到树中异常的平均路径长度被用作其“隔离”的度量。

  • 异常分数:基于平均路径长度,每个数据点被分配一个异常分数。异常分数表示数据点被隔离或从其余数据中分离的难易程度。较短的平均路径长度对应于较高的异常分数,表明数据点更有可能是异常。

  • auto,它根据数据集的大小估计污染率。

这里列出了 Isolation Forest 的一些优点:

  • Isolation Forest 计算效率高且可扩展,使其适用于大型数据集。它不需要大量的树来实现良好的性能,从而减少了计算开销。

  • 该算法对维度/特征的数量相对不敏感,这在处理高维数据集时尤其有利。Isolation Forest 是一种无监督学习算法,使其适用于标签异常数据稀缺或不可用的情况。

然而,Isolation Forest 也有一些局限性:

  • Isolation Forest 可能在具有多个异常簇的数据集上表现不佳,或者当异常值接近大多数正常数据点时。

  • 与大多数无监督算法一样,Isolation Forest 可能会产生假阳性(将正常数据点错误地分类为异常)和假阴性(将异常错误地分类为正常数据点)。

让我们用以下步骤在 Python 中实现这种方法:

  1. pandas库导入为pd以处理表格格式的数据,并从sklearn.ensemble模块导入IsolationForest以使用 Isolation Forest 算法进行异常检测:

    Import pandas as pd
    from sklearn.ensemble import IsolationForest
    
  2. 定义了numerical_features列表,包含用于异常检测的数值列的名称。这些列是'ApplicantIncome''CoapplicantIncome''LoanAmount'

    numerical_features = ['ApplicantIncome', 'CoapplicantIncome', 'LoanAmount']
    
  3. X_anomaly DataFrame 是通过从原始的df DataFrame 中提取numerical_features中指定的列创建的。这个新的 DataFrame 将被用于异常检测:

    X_anomaly = df[numerical_features]
    
  4. X_anomaly DataFrame,使用每列的平均值来调用fillna()方法。这确保了任何缺失值都被其各自列的平均值所替换:

    X_anomaly.fillna(X_anomaly.mean(), inplace=True)
    
  5. IsolationForest(contamination='auto', random_state=42)。将contamination参数设置为'auto',这意味着它将自动检测数据集中异常值的百分比。将random_state参数设置为42以确保可重复性:

    Isolation_forest = IsolationForest(contamination='auto', random_state=42)
    
  6. 使用 fit_predict() 方法对 X_anomaly 数据进行拟合。此方法同时将模型拟合到数据并预测每个数据点是否为异常。预测存储在 anomaly_predictions 数组中。

  7. anomaly_predictions 数组包含每个数据点的预测标签:-1 表示异常(异常值)和 1 表示内点(非异常值)。这些预测被添加为新的 'IsAnomaly' 列到原始的 df DataFrame 中。

  8. df DataFrame 中 IsAnomaly 等于 -1,表示存在异常值。结果 DataFrame 包含所有具有异常的行,然后可以根据需要进行进一步分析或处理:

    anomaly_predictions = Isolation_forest.fit_predict(X_anomaly)
    df['IsAnomaly'] = anomaly_predictions
    anomalies = df[df['IsAnomaly'] == -1]
    anomalies.head()
    

现在我们可以查看模型预测为异常的行所组成的 DataFrame:

图 9.6 – 结果异常 DataFrame

图 9.6 – 结果异常 DataFrame

总结来说,隔离森林(Isolation Forest)作为一种强大且高效的异常检测工具,在异常数据稀少且与正常实例明显不同的情况下尤为突出。它通过创建随机树来隔离异常数据的能力,使其在从欺诈检测到网络安全等众多应用中成为一种宝贵的资源。然而,承认算法的局限性也是至关重要的。当异常数据没有很好地分离或数据集高度维度化时,隔离森林可能会面临挑战。在下一节中,我们将探讨自动编码器(autoencoders)——一种用于异常检测的半监督方法。

使用自动编码器的半监督方法

使用自动编码器进行异常检测是一种无监督学习方法,它利用神经网络(NNs)来检测数据中的异常。自动编码器是一种神经网络架构,旨在从压缩表示中重建输入数据。在异常检测中,我们利用自动编码器在重建异常实例方面存在困难这一事实,这使得它们在识别异常模式或异常值方面非常有用。

自动编码器由两个主要组件组成:编码器和解码器。编码器将输入数据压缩成称为“潜在空间”的更低维度的表示,而解码器则试图从这个表示中重建原始输入。编码器和解码器通常是对称的,网络被训练以最小化重构误差。

在异常检测中,我们使用没有异常的正常数据来训练自动编码器。由于自动编码器学会重建正常数据,因此它将难以重建异常数据,导致异常实例的重构误差更高。这一特性使我们能够将重构误差用作异常分数。

在训练过程中,我们比较原始输入(例如,数值特征)与重建输出。两者之间的差异是重建误差。低重建误差表明输入接近正常数据分布,而高重建误差则表明输入可能是异常。

在训练自动编码器后,我们需要设置一个阈值来区分正常和异常实例,基于重建误差。有几种方法可以设置阈值,例如基于百分位数或使用验证数据。阈值将取决于所需的假阳性与假阴性之间的权衡,这可以根据应用程序的要求进行调整。

自动编码器灵活且能够捕捉数据中的复杂模式,这使得它们适用于具有非线性关系的多维数据。它们可以处理全局和局部异常,这意味着它们可以检测与大多数数据点不同的异常以及数据特定区域内的异常。自动编码器能够进行无监督学习,这在标记异常数据有限或不可用的情况下具有优势。与其他无监督方法一样,自动编码器可能会产生假阳性(将正常数据错误地分类为异常)和假阴性(将异常数据错误地分类为正常数据)。它们可能难以检测与正常数据非常相似的异常,因为重建误差可能没有显著差异。

让我们通过使用 TensorFlow 库和 Python 中的 Loan Prediction 数据集来查看这种方法的一个示例实现:

  • 使用 pd.read_csv() 读取 train_loan_prediction.csv 文件:

    import pandas as pd
    import numpy as np
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    from tensorflow.keras.layers import Input, Dense
    from tensorflow.keras.models import Model
    df = pd.read_csv('train_loan_prediction.csv')
    
  • 包含用于异常检测的数值列名称的 numerical_features 列表。这些列是 'ApplicantIncome''CoapplicantIncome''LoanAmount'

    numerical_features = ['ApplicantIncome', 'CoapplicantIncome', 'LoanAmount']
    
  • 通过从原始 df DataFrame 中提取 numerical_features 中指定的列创建 X_anomaly DataFrame。这个新的 DataFrame 将用于异常检测。

  • 使用 fillna() 方法将 X_anomaly DataFrame 中的列替换为其各自列的平均值:

    X_anomaly = df[numerical_features]
    X_anomaly.fillna(X_anomaly.mean(), inplace=True)
    
  • 使用 StandardScaler()X_anomaly 进行标准化。标准化将特征缩放到具有零均值和单位方差,这对于训练机器学习模型很重要:

    original_indices = X_anomaly.index
    scaler = StandardScaler()
    X_anomaly_scaled = scaler.fit_transform(X_anomaly)
    
  • 使用 train_test_split() 函数对 (X_train) 和测试 (X_test) 集进行划分。数据原始索引也存储在 original_indices 中:

    X_train, X_test, _, _ = train_test_split(X_anomaly_scaled, original_indices, test_size=0.2, random_state=42)
    X_test_df = pd.DataFrame(X_test, columns=['ApplicantIncome', 'CoapplicantIncome', 'LoanAmount'])
    
  • Dense 层。模型使用 fit() 方法进行训练,均方误差MSE)作为损失函数:

    input_dim = X_anomaly.shape[1]
    encoding_dim = 2
    input_layer = Input(shape=(input_dim,))
    encoder_layer = Dense(encoding_dim, activation='relu')(input_layer)
    decoder_layer = Dense(input_dim, activation='sigmoid')(encoder_layer)
    autoencoder = Model(inputs=input_layer, outputs=decoder_layer)
    autoencoder.compile(optimizer='adam', loss='mean_squared_error')
    autoencoder.fit(X_anomaly_scaled, X_anomaly_scaled, epochs=50, batch_size=16)
    
  • X_test,并计算重建误差为原始数据和重建数据之间的均方差异:

    X_test_reconstructed = autoencoder.predict(X_test)
    reconstruction_error_test = np.mean(np.square(X_test - X_test_reconstructed), axis=1)
    
  • X_test:

    threshold = np.percentile(reconstruction_error_test, 95)
    
  • 如果 anomaly_predictions 中的值为 1,否则将其分配为 0

    anomaly_predictions = (reconstruction_error_test > threshold).astype(int)
    
  • 使用 anomaly_df DataFrame 创建包含异常预测和对应索引的 X_test_df:

    anomaly_df = pd.DataFrame({'IsAnomaly': anomaly_predictions}, index=X_test_df.index)
    
  • 使用 merge() 方法将 'IsAnomaly' 列添加到 df DataFrame 中。

  • df 中的 'IsAnomaly' 列存在。如果存在,它将显示 'IsAnomaly' 等于 1 的行,表示存在异常。如果不存在,它将打印 "No anomalies detected."

    df = df.merge(anomaly_df, how='left', left_index=True, right_index=True)
    if 'IsAnomaly' in df.columns:
        # Display the rows with anomalies
        anomalies = df[df['IsAnomaly'] == 1]
        anomalies
    else:
        print("No anomalies detected.")
    

    结果的 DataFrame 如下所示:

图 9.7 – 结果的 IsAnomaly DataFrame

图 9.7 – 结果的 IsAnomaly DataFrame

总结来说,自动编码器证明是异常检测中的一种多才多艺且强大的工具,能够捕捉到传统方法可能无法捕捉到的细微模式。它们在复杂数据结构中发现细微异常的能力使它们在包括图像分析、网络安全和工业质量控制在内的多个领域变得非常有价值。

然而,自动编码器的有效性取决于各种因素。架构的复杂性和超参数的选择会影响性能,需要仔细调整以获得最佳结果。在下一节中,我们将了解 SVM 如何用于异常检测。

使用 SVM 的监督方法

SVM 是一类常用的监督学习算法,常用于分类任务。当应用于异常检测时,SVM 通过找到一个具有最大边缘的超平面来有效地将正常实例与异常实例分开。以下是 SVM 在底层的工作原理:

  • 超平面定义:在二维空间中,超平面是一个平坦的二维子空间。SVM 的目标是找到一个超平面,将数据集最好地分为两类——正常和异常。这个超平面定位以最大化边缘,即超平面与每个类最近的点的距离。

  • 决策边界:超平面充当决策边界,将一类实例与另一类实例分开。在二元分类场景中,超平面一侧的实例被分类为属于一个类别,而另一侧的实例被分类为属于另一个类别。

  • 核技巧:SVM 通过使用核函数来处理数据中的复杂关系。在许多实际场景中,特征之间的关系可能不是线性的。SVM 通过使用核函数来解决这个问题。这个函数将输入数据转换到更高维的空间,使得找到能够有效分离类别的超平面变得更容易。常用的核函数包括线性核(用于线性可分数据)、多项式核、径向基函数RBF)或高斯核,以及 sigmoid 核。核函数的选择取决于数据的性质。

  • 最优超平面:SVM 旨在找到最大化边界的超平面,这是超平面与每个类最近数据点之间的距离。边界越大,模型可能越稳健和可泛化。支持向量是位于决策边界最近的数据点。它们在定义最优超平面和边界中起着至关重要的作用。SVM 在训练过程中专注于这些支持向量。

让我们通过我们的贷款预测数据集实现一个用于异常检测的 SVM 的 Python 示例:

  1. 使用 pd.read_csv() 读取 train_loan_prediction.csv 文件:

    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    from sklearn.svm import OneClassSVM
    from sklearn.metrics import classification_report, accuracy_score
    df = pd.read_csv('train_loan_prediction.csv')
    
  2. 数据预处理:我们将进行一些基本的数据预处理任务,包括处理缺失值。对于这个异常检测示例,我们通过排除分类变量简化了分析。在更复杂的分析中,如果你认为这些变量与你的特定异常检测任务相关,你可能选择编码并包含这些变量:

    df = df.drop(['Loan_ID', 'Gender', 'Married', 'Dependents', 'Education', 'Self_Employed', 'Property_Area'], axis=1)
    df['Loan_Status'] = df['Loan_Status'].map({'Y': 0, 'N': 1})
    df.fillna(df.mean(), inplace=True)
    
  3. 为 SVM 模型创建训练-测试分割:

    X = df.drop('Loan_Status', axis=1)
    y = df['Loan_Status']
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
  4. 使用 scikit-learn 的 StandardScaler 标准化特征,以确保所有特征具有相同的尺度:

    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)
    
  5. 训练 One-Class SVM 模型进行异常检测。根据你数据集中预期的异常比例调整 nu 参数。nu 参数代表边界错误分数的上限和支撑向量分数的下限。它实际上控制了算法应考虑的异常或异常的比例。选择合适的 nu 值至关重要,并且它取决于你数据集的特征和预期的异常比例。以下是一些帮助你选择 nu 参数的指南:

    • 理解异常的性质:评估你的数据集的领域知识和特征。了解预期的异常比例。如果异常很少,nu 的较小值可能更合适。

    • 尝试一系列的值:首先尝试一系列的 nu 值,例如 0.01、0.05、0.1、0.2 等。你可以根据你对数据的理解调整这个范围。

    • 考虑数据集大小:你的数据集大小也会影响 nu 的选择。对于较大的数据集,较小的值可能更合适,而对于较小的数据集,相对较大的值可能更合适。

    • 平衡误报和漏报:根据应用的不同,你可能需要优先考虑最小化误报或漏报。相应地调整 nu 以达到所需的平衡。

  6. 我们将实施一个针对一系列 nu 值的实验。我们将指定我们想要实验的 nu 值列表。这些值代表 One-Class SVM 模型中边界错误分数的上限和支撑向量的下限。然后我们创建一个空列表来存储每个 nu 值的平均决策函数值:

    nu_values = [0.01, 0.05, 0.1, 0.2, 0.3]
    mean_decision_function_values = []
    
  7. 对于列表中的每个 nu 值,使用该 nu 值训练一个单类 SVM 模型。检索测试集的决策函数值并计算平均决策函数值。将平均决策函数值追加到列表中:

    for nu in nu_values:
        svm_model = OneClassSVM(nu=nu, kernel='rbf', gamma=0.1)
        svm_model.fit(X_train_scaled)
        decision_function_values=
        svm_model.decision_function(X_test_scaled)
        mean_decision_function = np.mean(decision_function_values)
        mean_decision_function_values.append(mean_decision_function)
    
  8. 确定与最高平均决策函数值对应的 nu 值的索引。然后,检索最佳 nu 值:

    best_nu_index = np.argmax(mean_decision_function_values)
    best_nu = nu_values[best_nu_index]
    
  9. 使用最佳 nu 值创建一个最终的 One-Class SVM 模型,并在缩放后的训练数据上对其进行训练:

    final_model = OneClassSVM(nu=best_nu, kernel='rbf', gamma=0.1)
    final_model.fit(X_train_scaled)
    
  10. 我们现在使用此模型在 X_test_scaled 测试数据集上预测异常。这一行通过将-1 映射到 1(表示异常)和任何其他值(通常是 1)映射到 0(表示正常实例)来创建预测的二进制表示(y_pred)。这是由于单类 SVM 模型通常将-1 分配给异常,将 1 分配给正常实例。我们将将其存储在一个新的 DataFrame 中,作为 df_with_anomalies

    y_pred = final_model.predict(X_test_scaled)
    y_pred_binary = [1 if pred == -1 else 0 for pred in y_pred]
    test_set_df = pd.DataFrame(data=X_test_scaled, columns=X.columns, index=X_test.index)
    test_set_df['Anomaly_Label'] = y_pred_binary
    df_with_anomalies = pd.concat([df, test_set_df['Anomaly_Label']], axis=1, join='outer')
    df_with_anomalies['Anomaly_Label'].fillna(0, inplace=True)
    
  11. 打印混淆矩阵和准确率分数:

    print("Classification Report:\n", classification_report(y_test, y_pred_binary))print("Accuracy Score:", accuracy_score(y_test, y_pred_binary))
    

    这将打印以下报告:

图 9.8 – 输出分类报告

图 9.8 – 输出分类报告

  1. 打印被预测为异常的 DataFrame 行:

    df_with_anomalies[df_with_anomalies['Anomaly_Label'] == 1]
    

    这将输出以下 DataFrame:

图 9.9 – df_with_anomalies DataFrame

图 9.9 – df_with_anomalies DataFrame

在本节中,我们介绍了使用 Python 实现 SVM 异常检测的过程。使用 SVM 进行异常检测可以适应具有明显异常的各种数据集,使其成为识别异常值的有价值工具。在下一节中,我们将探讨数据增强和重采样技术,以识别边缘案例和罕见事件。

数据增强和重采样技术

类别不平衡是具有罕见事件的集合中常见的问题。类别不平衡可能会对模型的性能产生不利影响,因为模型倾向于偏向多数类。为了解决这个问题,我们将探讨两种重采样技术:

  • 过采样:通过生成合成样本来增加少数类中的实例数量

  • 欠采样:通过减少多数类中的实例数量来平衡类别分布

让我们更详细地讨论这些重采样技术。

使用 SMOTE 进行过采样

合成少数类过采样技术SMOTE)是一种广泛使用的重采样方法,用于解决机器学习数据集中的类别不平衡问题,尤其是在处理罕见事件或少数类时。SMOTE 通过在现有少数类样本之间进行插值来帮助生成少数类的合成样本。该技术旨在通过创建额外的合成实例来平衡类别分布,从而减轻类别不平衡的影响。在类别不平衡的数据集中,少数类包含的实例数量明显少于多数类。这可能导致模型训练偏差,模型倾向于偏向多数类,在少数类上的表现不佳。

这里是 SMOTE 算法的关键步骤:

  1. 识别少数类实例:SMOTE 的第一步是识别属于少数类的实例。

  2. 选择最近邻:对于每个少数类实例,SMOTE 选择其k个最近邻(通常通过k 近邻算法选择)。这些邻居用于创建合成样本。

  3. 创建合成样本:对于每个少数类实例,SMOTE 在特征空间中实例与其k个最近邻连接的直线上生成合成样本。通过添加实例与其邻居之间的特征差异的随机分数(通常在 0 和 1 之间),创建合成样本。这个过程有效地为合成样本引入了变异性。

  4. 与原始数据结合:合成的样本与原始的少数类实例相结合,从而得到一个具有更平衡类分布的重采样数据集。

SMOTE 有助于解决类别不平衡问题,而不需要丢弃任何数据,因为它生成合成样本而不是从多数类中删除实例。它增加了模型可用的信息量,可能提高模型泛化到少数类的能力。SMOTE 易于实现,并在 Python 中流行的库(如 imbalanced-learn)中可用。

虽然 SMOTE 在许多情况下都有效,但它可能并不总是对高度不平衡的数据集或具有复杂决策边界的数据集表现最佳。生成过多的合成样本可能导致训练数据上的过拟合,因此选择合适的最近邻数量(k)值至关重要。SMOTE 可能会引入一些噪声,并且如果少数类在特征空间中过于稀疏或分散,可能不会那么有效。SMOTE 可以与其他技术结合使用,例如对多数类进行下采样或使用不同的重采样比率,以实现更好的性能。评估模型在适当的指标(例如,精确度、召回率或 F1 分数)上的性能对于评估 SMOTE 和其他技术对模型检测罕见事件能力的影响至关重要。

让我们在 Python 中实现这种方法:

  • 使用pd.read_csv()读取train_loan_prediction.csv文件:

    import pandas as pd
    from imblearn.over_sampling import SMOTE
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import classification_report
    from sklearn.ensemble import RandomForestClassifier
    df = pd.read_csv('train_loan_prediction.csv')
    
  • 数据集中的'Loan_Status'列包含'Y''N'分类值,分别代表贷款批准('Y')和拒绝('N')。为了将这个分类目标变量转换为数值格式,使用map()函数将'Y'映射到1,将'N'映射到0

    df['Loan_Status'] = df['Loan_Status'].map({'Y': 1, 'N': 0})
    
  • fillna()方法。这确保了数据集中任何缺失值都被其相应列的平均值所替换:

    df.fillna(df.mean(), inplace=True)
    
  • X特征集。使用select_dtypes()方法仅包括具有floatint数据类型的列,同时排除非数值列,如'Loan_Status''Loan_ID'y目标变量设置为'Loan_Status'

    numerical_columns = df.select_dtypes(include=[float, int]).columns
    X = df[numerical_columns].drop('Loan_Status', axis=1)
    y = df['Loan_Status']
    
  • 使用 scikit-learn 的train_test_split()函数对训练集(X_train, y_train)和测试集(X_test, y_test)进行分割。训练集包含 80%的数据,而测试集包含 20%的数据。random_state参数设置为42以确保可重复性:

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
  • SMOTE(random_state=42)。然后使用fit_resample()方法对训练数据进行 SMOTE 处理。此方法通过生成合成样本对少数类(贷款拒绝)进行过采样,创建一个平衡的数据集。重采样后的数据存储在X_train_resampledy_train_resampled中:

    smote = SMOTE(random_state=42)
    X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)
    
  • RandomForestClassifier(random_state=42)。分类器使用fit()方法在重采样数据(X_train_resampled, y_train_resampled)上训练。训练好的分类器使用predict()方法对测试数据(X_test)进行预测。预测结果存储在y_pred中:

    clf = RandomForestClassifier(random_state=42)
    clf.fit(X_train_resampled, y_train_resampled)
    y_pred = clf.predict(X_test)
    
  • 使用 scikit-learn 的classification_report()函数生成分类报告。分类报告根据测试集的预测(y_pred)和真实标签(y_test)为每个类别(贷款批准和拒绝)提供精确度、召回率、F1 分数和支持。分类报告最初以字典格式返回。代码使用pd.DataFrame()将此字典转换为clf_report DataFrame,使其更容易处理数据。使用.T属性将clf_report DataFrame 转置,使类别(01)成为行,评估指标(精确度、召回率、F1 分数和支持)成为列。这种转置提供了更方便和可读的格式,以便进行进一步的分析或展示:

    clf_report = pd.DataFrame(classification_report(y_test, y_pred, output_dict=True))
    clf_report = clf_report.T
    clf_report
    

    让我们打印分类报告以了解模型的性能:

图 9.10 – 由前面的代码生成的分类报告

图 9.10 – 由前面的代码生成的分类报告

报告显示,模型在识别类别 0 的实例方面表现中等,精确度为 73.33%。然而,召回率相对较低,为 51.16%,表明模型可能遗漏了一些类别 0 的实际实例。模型在识别类别 1 的实例方面表现出色,精确度高达 77.42%,召回率非常高,为 90.00%。加权平均指标考虑了类别不平衡,为两个类别提供了平衡的评估。模型的总体准确度为 76.42%,表示正确预测实例的百分比。

在下一节中,让我们来探讨欠采样——另一种解决机器学习中类别不平衡问题的方法。

使用 RandomUnderSampler 进行欠采样

使用 RandomUnderSampler 处理类别不平衡是解决不平衡数据集挑战的有效方法,在这种情况下,一个类别显著超过其他类别。在这种情况下,传统的机器学习算法可能难以从数据中学习,并且倾向于偏向多数类别,导致在少数类别或罕见事件上的性能不佳。

RandomUnderSampler 是一种重采样技术,旨在通过随机从多数类别删除实例来平衡类别分布,直到类别比例更加平衡。通过减少多数类别的实例数量,RandomUnderSampler 确保少数类别以更成比例的方式表示,使模型更容易检测和学习与罕见事件相关的模式。

下面是关于使用 RandomUnderSampler 处理类别不平衡的一些关键点:

  • RandomUnderSampler 是一种数据级别的重采样方法。数据级别的重采样技术涉及操纵训练数据以平衡类别分布。在 RandomUnderSampler 中,从多数类别随机选择并删除实例,从而得到一个具有平衡类别分布的较小数据集。

  • RandomUnderSampler 直接从多数类别中删除实例,而不改变少数类别的实例。这种方法有助于保留少数类别的信息,使模型更容易专注于学习与罕见事件相关的模式。

  • RandomUnderSampler 会从多数类别中丢失信息。通过随机删除实例,可能会丢弃一些具有信息量的实例,这可能导致模型在多数类别上的泛化能力降低。

  • RandomUnderSampler 计算效率高,因为它只涉及从多数类别随机删除实例。这使得它比其他一些重采样方法更快。

  • RandomUnderSampler 在某些情况下可能有效,但它并不总是最佳选择,特别是如果多数类别包含重要的模式和信息。在选择适当的重采样技术时,仔细考虑问题和数据集特征至关重要。

  • RandomUnderSampler 可以与其他技术结合使用。例如,可以先应用 RandomUnderSampler,然后使用 RandomOverSampler(过采样)来进一步平衡类别分布。这种方法有助于实现两个类别的更平衡的表示。

  • 评估和模型选择:在处理类别不平衡时,评估模型在相关指标上的性能至关重要,例如精确度、召回率、F1 分数和 ROC 曲线下面积AUC-ROC)。这些指标提供了对模型处理罕见事件和边缘情况的全面评估。

让我们使用 Python 实现这种方法:

使用 pd.read_csv() 读取 train_loan_prediction.csv 文件:

import pandas as pd
from imblearn.under_sampling import RandomUnderSampler
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.ensemble import RandomForestClassifier
df = pd.read_csv('train_loan_prediction.csv')

数据集中的'Loan_Status'列包含'Y''N'分类值,分别代表贷款批准('Y')和拒绝('N')。为了将这个分类目标变量转换为数值格式,使用map()函数将'Y'映射到 1,将'N'映射到 0:

df['Loan_Status'] = df['Loan_Status'].map({'Y': 1, 'N': 0})

以下代码使用fillna()方法对数据集中所有具有缺失值的列应用均值填充。这确保了数据集中的任何缺失值都被其各自列的均值所替换:

df.fillna(df.mean(), inplace=True)

代码仅从数据集中选择数值列来构建X特征集。使用select_dtypes()方法仅包括数据类型为floatint的列,同时排除非数值列,例如'Loan_Status''Loan_ID'y目标变量设置为'Loan_Status'

numerical_columns = df.select_dtypes(include=[float, int]).columns
X = df[numerical_columns].drop('Loan_Status', axis=1)
y = df['Loan_Status']

使用 scikit-learn 中的train_test_split()函数将数据分为训练集(X_train, y_train)和测试集(X_test, y_test)。训练集包含 80%的数据,而测试集包含 20%的数据。random_state参数设置为42以确保可重复性:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

然后我们实例化RandomUnderSampler,并使用fit_resample()方法将其应用于训练数据。此方法对多数类(贷款批准)进行下采样以创建平衡数据集。重采样后的数据存储在X_train_resampledy_train_resampled中:

rus = RandomUnderSampler(random_state=42)
X_train_resampled, y_train_resampled = rus.fit_resample(X_train, y_train)

然后使用fit()方法在重采样数据(X_train_resampled, y_train_resampled)上训练随机森林分类器。训练好的分类器用于使用predict()方法对测试数据(X_test)进行预测。预测结果存储在y_pred中:

clf = RandomForestClassifier(random_state=42)
clf.fit(X_train_resampled, y_train_resampled)
y_pred = clf.predict(X_test)
clf_report = pd.DataFrame(classification_report(y_test, y_pred, output_dict=True))
clf_report = clf_report.T
clf_report

让我们检查分类报告以评估模型性能:

图 9.11 – 模型性能的分类报告

图 9.11 – 模型性能的分类报告

模型对两类都表现出不错的性能,与类别0相比,类别1具有更高的精确度、召回率和 F1 分数。加权平均考虑了类别分布的不平衡,提供了对整体性能的更具有代表性的度量。准确率 0.7805%表明,模型正确预测了测试集中大约 78%的实例的类别。

在下一节中,让我们了解成本敏感学习,并探讨其在罕见事件具有重大后果的场景中的关键作用。

成本敏感学习

成本敏感学习是一种机器学习方法,在模型训练过程中考虑了不同类别误分类的成本。在传统的机器学习中,重点是最大化整体准确率,但在许多实际场景中,某些类别的误分类可能比其他类别的误分类有更严重的后果。

例如,在医疗诊断应用中,将严重疾病误诊为不存在(假阴性)可能比将轻微状况误诊为存在(假阳性)有更严重的后果。在欺诈检测中,错误地将合法交易标记为欺诈(假阳性)可能会给客户带来不便,而未能检测到实际欺诈交易(假阴性)可能导致重大经济损失。

成本敏感学习通过为不同类别分配不同的误分类成本来解决这些成本不平衡问题。通过将这些成本纳入训练过程,模型被鼓励优先考虑最小化整体误分类成本,而不仅仅是优化准确性。

实施成本敏感学习有几种方法:

  • 修改损失函数:在模型训练期间使用的损失函数可以被修改,以包含特定类别的误分类成本。目标是最小化预期成本,这是误分类成本和模型预测的组合。

  • 类别权重:另一种方法是给少数类或误分类成本较高的类分配更高的权重。这种技术可以应用于各种分类器,如决策树、随机森林和 SVMs,以强调从少数类中学习。

  • 采样技术:除了分配权重外,还可以使用重采样技术,如对少数类进行过采样或对多数类进行欠采样,以平衡类别分布并提高模型从罕见事件中学习的能力。

  • 阈值调整:通过调整分类阈值,我们可以控制精确度和召回率之间的权衡,从而做出对少数类更敏感的预测。

  • 集成方法:成本敏感提升等集成方法结合多个模型,专注于难以分类的实例,并给误分类样本分配更高的权重。

成本敏感学习在类别不平衡严重且误分类后果至关重要的场景中尤为重要。通过考虑与不同类别相关的成本,模型可以做出更明智的决策,并在检测罕见事件和处理边缘情况方面提高整体性能。

需要注意的是,成本敏感学习需要对成本矩阵进行仔细考虑,因为错误指定的成本可能导致意外结果。在考虑现实世界成本的相关指标上对模型进行适当的验证和评估,对于确保成本敏感学习算法的有效性和可靠性至关重要。

现在我们将使用 Python 中的 Loan Prediction 数据集演示成本敏感学习:

  1. 使用pandas加载所需的库和数据集:

    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.metrics import classification_report
    df = pd.read_csv('train_loan_prediction.csv')
    
  2. 现在我们需要进行数据预处理来处理缺失值并将目标变量转换为数值数据类型:

    df['Loan_Status'] = df['Loan_Status'].map({'Y': 1, 'N': 0})
    df.fillna(df.mean(), inplace=True)
    
  3. 对于这个例子,我们将仅使用数值列。然后我们将数据集分为traintest

    numerical_columns = df.select_dtypes(include=[float, int]).columns
    X = df[numerical_columns].drop('Loan_Status', axis=1)
    y = df['Loan_Status']
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
  4. 我们首先根据训练数据中类频率的倒数计算类权重。一个类的频率越高,其权重越低,反之亦然。这样,模型会赋予少数类(罕见事件)更高的重要性,并对其正确预测更加敏感:

    class_weights = dict(1 / y_train.value_counts(normalize=True))
    
  5. 接下来,我们将使用计算出的类权重设置class_weight参数来训练随机森林分类器。这种修改允许分类器在训练过程中考虑类权重,从而有效地实现成本敏感学习:

    clf = RandomForestClassifier(random_state=42, class_weight=class_weights)
    clf.fit(X_train, y_train)
    
  6. 在训练好模型后,我们会在测试数据上做出预测,并使用分类报告来评估分类器的性能,该报告为每个类别提供了精确度、召回率、F1 分数和支持:

    y_pred = clf.predict(X_test)
    clf_report = pd.DataFrame(classification_report(y_test, y_pred, output_dict=True))
    clf_report = clf_report.T
    clf_report
    

    让我们查看分类报告并评估随机森林分类器的性能:

图 9.12 – 随机森林分类器性能的分类报告

图 9.12 – 随机森林分类器性能的分类报告

在成本敏感学习中,你通常会定义一个成本矩阵,该矩阵量化了每个类别的误分类成本,并使用它来指导模型的训练。分类报告的结果可以帮助你识别需要调整以使模型与你的应用中特定的成本考虑因素相一致的区域。在假阳性成本和假阴性成本不相等的情况下,成本矩阵特别有用。如果假阳性的成本更高,考虑提高决策阈值。如果假阴性的成本更高,考虑降低阈值。

在下一节中,我们将了解用于检测边缘情况和罕见事件的评估指标。

选择评估指标

在机器学习中处理边缘情况和罕见事件时,选择正确的评估指标对于准确评估模型的性能至关重要。传统的评估指标,如准确率,在感兴趣的类别(罕见事件)远多于多数类的不平衡数据集中可能不足以提供足够的信息。在不平衡数据集中,如果罕见事件是少数类,传统的评估指标如准确率可能会误导。例如,如果一个数据集有 99%的多数类和 1%的罕见事件,一个将所有实例预测为多数类的模型仍然会达到 99%的准确率,这看起来非常高。然而,这样的模型在检测罕见事件方面将无效。为了解决这个问题,我们需要关注模型在正确识别罕见事件方面的性能的评估指标,即使这会导致准确率的下降。

这里有一些更适合检测边缘案例和罕见事件的评估指标:

  • 精确率:精确率衡量模型做出的正预测的准确性。它是真实正例(正确预测的罕见事件)与真实正例和假正例(错误地将多数类作为罕见事件预测)之和的比率。高精确率表明模型在做出正预测时非常谨慎,并且假正例率很低。

  • 召回率(灵敏度):召回率衡量模型预测出的所有实际正例中真实正例的比例。它是真实正例与真实正例和假负例(错误地将多数类作为罕见事件预测)之和的比率。高召回率表明模型能够捕获大量罕见事件实例。

  • F1 分数:F1 分数是精确率和召回率的调和平均数。它提供了两种指标之间的平衡,并且在精确率和召回率之间存在不平衡时特别有用。F1 分数惩罚那些以牺牲另一个指标为代价优先考虑精确率或召回率的模型。

  • 接收器操作特征(ROC-AUC)下的面积:ROC-AUC 是一种用于评估二元分类模型的性能指标。它测量 ROC 曲线下的面积,ROC 曲线绘制了随着分类阈值变化时的真实正例率(召回率)与假正例率。更高的 ROC-AUC 表明模型性能更好,尤其是在检测罕见事件方面。

在下一节中,让我们深入了解集成技术,并了解它们在机器学习模型中的关键作用,尤其是在处理包含边缘案例和罕见事件的 数据时。

集成技术

集成技术是用于提高机器学习模型性能的强大方法,尤其是在不平衡数据集、罕见事件和边缘案例的场景中。这些技术通过结合多个基础模型来创建一个更稳健和准确的最终预测。让我们讨论一些流行的集成技术。

Bagging

自助聚合bagging)是一种集成技术,它从训练数据中创建多个自助样本(带有替换的随机子集),并在每个样本上训练一个单独的基础模型。最终的预测是通过平均或投票所有基础模型的预测得到的。Bagging 在处理高方差和复杂模型时特别有用,因为它减少了过拟合并增强了模型的泛化能力。以下是与 Bagging 相关的关键概念:

  • 自助采样:Bagging 过程从通过称为自助采样的过程创建多个训练数据的随机子集开始。自助采样涉及从原始数据集中随机选择数据点,并进行替换。因此,某些数据点可能在子集中出现多次,而其他数据点可能被排除在外。

  • 基础模型训练:对于每个自助样本,一个基础模型(学习器)独立地在该特定训练数据子集上训练。基础模型可以是任何机器学习算法,例如决策树、随机森林或 SVMs。

  • 聚合预测:一旦所有基础模型都训练完成,它们就被用来对新、未见过的数据进行预测。对于分类任务,最终预测通常由多数投票决定,即选择在基础模型中获得最多投票的类别。在回归任务中,最终预测是通过平均所有基础模型的预测得到的。

这里是 Bagging 的一些好处:

  • 方差减少:Bagging 通过结合在不同数据子集上训练的多个模型的预测来帮助减少模型的方差。这导致了一个更稳定和健壮的模型。

  • 过拟合预防:通过在每个数据子集上训练每个基础模型,Bagging 可以防止个别模型对训练集中的噪声过拟合。

  • 模型泛化:Bagging 通过减少偏差和方差来提高模型的泛化能力,从而在未见过的数据上获得更好的性能。

  • 并行性:由于基础模型是独立训练的,因此 Bagging 适合并行处理,使其在计算上更高效。

随机森林是 Bagging 技术的流行例子。在随机森林中,基础模型是决策树,多个决策树的预测结果被组合起来以做出最终预测。

下面是一个使用 Python 在 Loan Prediction 数据集上实现 Bagging 的随机森林的例子:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
df = pd.read_csv('train_loan_prediction.csv')
df['Loan_Status'] = df['Loan_Status'].map({'Y': 1, 'N': 0})
df.fillna(df.mean(), inplace=True)
numerical_columns = df.select_dtypes(include=[float, int]).columns
X = df[numerical_columns].drop('Loan_Status', axis=1)
y = df['Loan_Status']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf = RandomForestClassifier(random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
clf_report = pd.DataFrame(classification_report(y_test, y_pred, output_dict=True))
clf_report = clf_report.T
clf_report

总之,Bagging 技术为处理边缘情况提供了一种稳健且有效的策略。通过聚合多个基础模型的预测,Bagging 不仅增强了整体模型稳定性,还加强了其准确识别和解决边缘情况的能力,从而有助于构建一个更具弹性和可靠性的预测框架。

在下一节中,我们将探讨提升技术,这是另一种处理边缘情况和罕见事件的方法。

提升技术

提升是一种集成技术,它按顺序构建基础模型,每个后续模型都专注于前一个模型错误分类的实例。它为错误分类的实例分配更高的权重,从而更多地关注罕见事件。流行的提升算法包括自适应提升AdaBoost)、梯度提升和 XGBoost。提升的目标是通过迭代组合弱学习器来创建一个强学习器。

下面是如何提升工作的例子:

  1. 基础模型训练:提升技术首先在全部训练数据集上训练一个基础模型(也称为弱学习器)。弱学习器通常是具有有限预测能力的简单模型,例如决策桩(具有单个分割的决策树)。

  2. 加权训练: 在第一个模型训练完成后,被模型错误分类的数据点被分配更高的权重。这意味着后续的模型将更加关注这些错误分类的数据点,试图纠正其预测。

  3. 迭代训练: 提升算法遵循迭代方法。对于每一次迭代(或提升轮次),都会在更新后的训练数据上训练一个新的弱学习器,并调整其权重。然后,将这些弱学习器组合起来创建一个强学习器,与单个弱学习器相比,强学习器的预测性能得到提升。

  4. 加权投票: 在最终预测过程中,弱学习器的预测通过加权投票相结合,其中准确性更高的模型对最终预测有更大的影响。这允许提升算法专注于难以分类的实例,并提高模型对罕见事件的敏感性。

提升算法的优点如下:

  • 提高准确性: 通过关注数据集中最具挑战性的实例并在多次迭代中细化预测,提升算法提高了模型的准确性。

  • 鲁棒性: 通过迭代调整权重并从之前的错误中学习,提升算法减少了模型对数据中的噪声和异常值的敏感性。

  • 模型自适应: 提升算法很好地适应不同类型的数据,并能处理特征与目标变量之间的复杂关系

  • 集成多样性: 提升算法创建了一个多样化的弱学习器集成,这导致更好的泛化能力和减少过拟合。

让我们通过 AdaBoost 的一个示例来了解提升算法。

AdaBoost 是一种流行的提升算法,在实践中被广泛使用。在 AdaBoost 中,基模型通常是决策树桩,并且在每次迭代后调整模型权重,以强调被错误分类的实例。

下面是一个使用 AdaBoost 实现提升算法的 Python 示例:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import classification_report
df = pd.read_csv('train_loan_prediction.csv')
df['Loan_Status'] = df['Loan_Status'].map({'Y': 1, 'N': 0})
df.fillna(df.mean(), inplace=True)
numerical_columns = df.select_dtypes(include=[float, int]).columns
X = df[numerical_columns].drop('Loan_Status', axis=1)
y = df['Loan_Status']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf = AdaBoostClassifier(random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
clf_report = pd.DataFrame(classification_report(y_test, y_pred, output_dict=True))
clf_report = clf_report.T
clf_report

总结来说,提升技术的应用成为处理边缘情况的一种稳健策略。通过其迭代方法,提升算法使模型能够专注于具有挑战性的实例,从而增强其识别和准确预测罕见事件的能力。

我们现在将探讨如何使用堆叠方法来检测和处理机器学习中的边缘情况和罕见事件。

堆叠

堆叠是一种高级集成学习技术,通过在基模型的输出上训练一个元模型来结合多个基模型的预测。堆叠旨在利用不同基模型的优势,创建一个更准确和鲁棒的最终预测。它是一种“学习如何学习”的形式,其中元模型学习如何最佳地结合基模型的预测。基模型充当“学习器”,它们的预测成为元模型的输入特征,从而进行最终预测。堆叠通常可以通过捕捉来自不同基模型的互补模式来提高性能。

下面是模型方法:

  1. 基础模型训练:堆叠过程首先是在训练数据集上训练多个不同的基础模型。这些基础模型可以是不同类型的机器学习算法,甚至是具有不同超参数的相同算法。

  2. 基础模型预测:一旦基础模型训练完成,它们就被用来在相同的训练数据(样本内预测)或一个独立的验证数据集(样本外预测)上进行预测。

  3. 元模型训练:然后,将基础模型的预测结合起来创建一个新的数据集,该数据集作为元模型的输入。每个基础模型的预测成为该数据集中的一个新特征。元模型在这个新数据集上以及真实的目标标签上进行训练。

  4. 最终预测:在最终预测阶段,基础模型对新未见过的数据进行预测。然后,这些预测被用作元模型的输入特征,从而做出最终预测。

堆叠有以下优点:

  • 提高预测性能:堆叠利用了不同基础模型的互补优势,与使用单个模型相比,可能带来更好的整体预测性能

  • 降低偏差和方差:通过结合多个模型,堆叠可以降低模型的偏差和方差,从而提高泛化能力

  • 灵活性:堆叠允许使用不同的基础模型,使其适用于各种类型的数据和问题

  • 集成多样性:堆叠通过使用各种基础模型创建了一个多样化的集成,这有助于防止过拟合

下面是一个使用 Python 中的 scikit-learn 实现堆叠的示例,使用的是贷款预测数据集:

  1. 我们首先导入所需的库并加载数据集:

    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
    from sklearn.linear_model import LogisticRegression
    from sklearn.metrics import classification_report
    df = pd.read_csv('train_loan_prediction.csv')
    
  2. 我们现在需要执行数据预处理来处理缺失值并将目标变量转换为数值数据类型:

    df['Loan_Status'] = df['Loan_Status'].map({'Y': 1, 'N': 0})
    df.fillna(df.mean(), inplace=True)
    
  3. 为了简单起见,我们将在这个示例中仅使用数值列。然后,我们将数据集分为traintest

    numerical_columns = df.select_dtypes(include=[float, int]).columns
    X = df[numerical_columns].drop('Loan_Status', axis=1)
    y = df['Loan_Status']
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
  4. RandomForestClassifierGradientBoostingClassifier分别使用RandomForestClassifier(random_state=42)GradientBoostingClassifier(random_state=42)实例化:

    base_model_1 = RandomForestClassifier(random_state=42)
    base_model_2 = GradientBoostingClassifier(random_state=42)
    
  5. 这些基础模型使用fit()方法在训练数据(X_trainy_train)上训练,如以下所示。训练好的基础模型被用来使用predict()方法在测试数据(X_test)上进行预测。来自两个基础模型的预测被存储在pred_base_model_1pred_base_model_2中:

    base_model_1.fit(X_train, y_train)
    base_model_2.fit(X_train, y_train)
    pred_base_model_1 = base_model_1.predict(X_test)
    pred_base_model_2 = base_model_2.predict(X_test)
    
  6. stacking_X_train数据集是通过结合基础模型的预测(pred_base_model_1pred_base_model_2)创建的。这个新的数据集将被用作元模型的输入特征:

    stacking_X_train = pd.DataFrame({
        'BaseModel1': pred_base_model_1,
        'BaseModel2': pred_base_model_2
    })
    
  7. LogisticRegression()。元模型使用 fit() 方法在新的数据集 (stacking_X_train) 和测试集的真实标签 (y_test) 上进行训练。元模型学习如何结合基础模型的预测并做出最终预测:

    meta_model = LogisticRegression()
    meta_model.fit(stacking_X_train, y_test)
    
  8. new_unseen_data) 是通过使用 sample() 方法随机选择测试数据 (X_test) 的 20% 创建的。基础模型使用 predict() 方法对新、未见过的数据 (new_unseen_data) 进行预测。对于新数据的预测结果存储在 new_pred_base_model_1new_pred_base_model_2 中:

    new_unseen_data = X_test.sample(frac=0.2, random_state=42)
    new_pred_base_model_1 = base_model_1.predict(new_unseen_data)
    new_pred_base_model_2 = base_model_2.predict(new_unseen_data)
    
  9. stacking_new_unseen_data 数据集是通过结合基础模型 (new_pred_base_model_1new_pred_base_model_2) 对新、未见过的数据的预测创建的。这个新的数据集将被用作元模型的输入特征,以做出最终预测:

    stacking_new_unseen_data = pd.DataFrame({
        'BaseModel1': new_pred_base_model_1,
        'BaseModel2': new_pred_base_model_2
    })
    
  10. stacking_new_unseen_data) 使用 predict() 方法进行预测。final_prediction 变量根据元模型的决策持有预测的类别(0 或 1):

    final_prediction = meta_model.predict(stacking_new_unseen_data)
    final_prediction
    

总结来说,这段代码演示了堆叠的概念,其中基础模型(随机森林和梯度提升)在原始数据上训练,它们的预测被用作元模型(逻辑回归)的输入特征,最终预测是通过在新的、未见过的数据上使用元模型来完成的。堆叠允许模型协同工作,并且与单独使用基础模型相比,有可能提高预测性能。

摘要

在本章中,我们探讨了机器学习中检测罕见事件和边缘案例的关键方面。由于罕见事件的罕见性,它们在各个领域都有重要的意义,并需要特别的关注。我们深入研究了多种技术和方法,使我们能够有效地识别和处理这些不常见的情况。

统计方法,如 Z 分数和 IQR,为我们提供了强大的工具,可以确定数据中的异常值和异常。这些方法有助于建立识别罕见事件的具有意义的阈值,使我们能够区分重要的数据点与噪声。

我们还探讨了基于机器学习的异常检测技术,如隔离森林和自动编码器。这些方法利用无监督学习来识别与大多数数据不同的模式和偏差,使它们非常适合在复杂数据集中检测罕见事件。

此外,我们还讨论了重采样方法(如 SMOTE 和 RandomUnderSampler)在处理类别不平衡中的重要性。这些技术使我们能够创建平衡的数据集,从而提高模型在识别罕见事件时的性能,同时保持数据完整性。

此外,我们发现集成技术,包括堆叠、袋装和提升,在增强我们模型检测边缘案例能力方面的潜力。通过集成多个模型的综合力量,增强了泛化能力和模型鲁棒性。

在存在罕见事件的情况下,选择适当的评估指标至关重要,以确保公平评估和准确评估模型性能。如精确度、召回率、F1 分数和 AUC-ROC 等指标提供了对模型性能的全面洞察,并指导决策制定。

检测罕见事件和边缘案例在医疗诊断、欺诈检测、预测性维护和环境监测等众多领域具有深远的影响。通过采用有效的技术来识别和处理这些不常见的事件,我们提高了机器学习应用的可靠性和效率。

在我们结束本章之前,让我们认识到这项技能在现实场景中的重要性。检测罕见事件使我们能够做出明智的决策,防范潜在风险,并充分利用机器学习在众多领域的积极影响。

在下一章中,我们将探讨数据驱动方法在机器学习中面临的一些挑战。

第四部分:开始使用数据中心化机器学习

到现在为止,你可能已经意识到转向以数据为中心的机器学习方法不仅需要调整你自己的工作方式,还需要影响周围的人——这是一项远非简单的任务。在本部分中,我们探讨了在模型开发和部署过程中可能遇到的技术和非技术障碍,并揭示了采用数据为中心的方法如何帮助克服这些障碍。

本部分包含以下章节:

  • 第十章*,开启你的数据中心化机器学习之旅*

第十章:启动你在数据为中心的机器学习之旅

以数据为中心的机器学习ML)方法是为了应对以模型为中心的范式局限性而创建的。尽管以数据为中心的视角为机器学习在新的和现有领域中的应用开辟了令人难以置信的机会,但这并不意味着它容易实施。

以模型为中心的方法的吸引力在于其相对简单性。它在该领域的统治地位并不一定是因为它更优越,而是因为它更直接。它主要关注改进模型、调整算法和增强计算能力。然而,这种方法往往忽视了机器学习的一个基本方面——为这些模型提供的数据的质量和相关性。

与此相反,以数据为中心的方法优先考虑提高数据质量而不是完善模型。它认识到,即使是最复杂的模型,如果建立在质量低劣或无关的数据的脆弱基础上,也可能失败。

你将面临的挑战是,采用以数据为中心的机器学习方法可能需要大量的努力。

在本章的结尾,我们将集中讨论如何有效地实施你在本书中学到的知识。我们将涵盖以下主题:

  • 如何通过以数据为中心的方法解决六个常见的机器学习挑战

  • 在你的组织中倡导数据质量的重要性

  • 聚集人员以帮助你实施以数据为中心的方法

  • 对人工智能伦理和公平性负责

让我们从探讨以数据为中心的方法如何提供工具来消除机器学习开发中的常见瓶颈开始。

解决六个常见的机器学习挑战

我们编写这本书是为了提供克服通常成为机器学习模型开发瓶颈的六个常见挑战所需的工具和技术。这六个挑战如下:

  • 典型的开发方法是模型为中心的:在传统的机器学习中,重点是模型——选择正确的算法、调整超参数和优化性能指标。这种以模型为中心的方法通常涉及无数次的调整模型,以挤出更多的准确性。然而,虽然模型无疑很重要,但这种方法有时会导致忽视其他同样重要的方面,例如为这些模型提供的数据的质量和相关性。

  • 任何机器学习模型的可能性都受限于数据质量和数量:无论机器学习模型多么复杂,其性能最终都是由其训练所使用的数据的质量和数量决定的。低质量的数据可能导致预测不准确,而数据不足可能导致过拟合,即模型过度学习训练数据,在未见过的数据上表现不佳。

  • 我们并不总能“获取更多数据”:虽然拥有更多数据可以帮助提高模型性能,但获取额外数据并不总是可行的。这可能是因为成本高昂、耗时,或者由于隐私问题或某些事件的罕见性而变得不可能。

  • 大多数数据并非为机器学习目的收集和整理:数据通常出于各种原因被收集,如报告、商业智能BI)或记录保存,但并非专门为机器学习。这可能导致数据对我们特定的机器学习任务来说是不相关的、不完整的或噪声的。

  • 中小企业和标注员不了解数据收集的最终目标:在许多情况下,收集或标注数据的人并不完全清楚机器学习项目的最终目标。这种缺乏理解可能导致数据标注或收集的不一致性,因为不同的人可能会对指令有不同的解释。

  • 中小企业和标注员存在偏见:每个人都有偏见,中小企业和标注员也不例外。这些偏见可能会无意中影响他们收集或标注数据的方式,导致数据集偏差。

为了解决这六个挑战或瓶颈,我们向你介绍了数据为中心的机器学习的四个原则(在第三章**,数据为中心的机器学习原则)和一个由一系列技术和非技术工具和方法组成的数据为中心的机器学习工具包。

图 10.1 展示了这些原则、工具和方法如何为你提供克服这些挑战并取得更多成果的工具:

图 10.1 – 机器学习模型开发中六个常见挑战或瓶颈的概述;本书为你提供了四个原则和一系列以数据为中心的工具和技术来克服这些挑战

图 10.1 – 机器学习模型开发中六个常见挑战或瓶颈的概述;本书为你提供了四个原则和一系列以数据为中心的工具和技术来克服这些挑战

然而,知识只有当它被应用时才有用。随着我们接近对以数据为中心的机器学习的探索,是时候你反思一下,为了应用你在本书中学到的知识,你将不得不做哪些不同的改变。

不可避免地,你将不得不改变一些自己的方法和习惯,并重塑你与同事之间的某些互动。正如我们多次提到的,数据科学是一项团队运动,现在是时候你成为以数据为中心的机器学习团队的队长了。

为了成功地扮演这个角色,有三个关键领域你需要关注:

  • 成为数据质量的倡导者

  • 聚集人们来帮助你

  • 对人工智能伦理和公平性负责

让我们详细地逐一探讨这些领域。

成为数据质量的倡导者

任何成功的数据中心化机器学习倡议的基础是高质量的数据。作为这个领域的专家,你有责任倡导并维护严格的数据质量标准。这包括确保数据收集、清洗和标注过程是稳健和一致的。然而,有时可能会感觉整个宇宙都在与你作对,不断地向你提供糟糕的数据。这可能确实是真的!

热力学第二定律表明,能量倾向于扩散,如果得不到积极的管理,系统自然会向无序或“熵”的增加方向发展。

这种现象几乎在你能想到的任何有序系统中都会发生,包括商业。从本质上讲,一家企业是由一群人、技术和流程系统地组织起来,以实现一系列共同目标。

但随着时间的推移,人们会进出企业,技术堆栈在规模和复杂性上会增长,流程可能会变得冗余或被人们的个人兴趣所取代。

让我们将这个概念应用到这样一个商业场景中,即随着时间的推移,一个组织的科技堆栈在增长。随着技术堆栈的扩展,添加更多的软件、平台和基础设施,就像向系统中添加更多的能量一样。如果没有适当的管理和组织,这可能会导致复杂性和“无序”的增加。系统可能无法高效互动,可能存在冗余,或者关键流程可能会被忽视——这就是我们场景中的“熵”。

我们在与每个组织的互动中都看到了这一场景的发生。尽管任何组织都是从零开始的,但典型的中等到大型企业将在任何给定时间运行数百甚至数千个技术应用程序,生成一个日益复杂的数据网络。

2022 年对北美至少有 1000 名员工的 200 多名 IT 和数据专业人士的调查证实了这一点 1。调查发现,这些组织平均管理着 400 个数据源,20%的受访者从 1000 个或更多的数据源中提取数据来喂养他们的分析系统。

换句话说,数据无序并不是随机事件,而是默认结果。除非投入大量努力来保持秩序,否则混乱的数据将会出现。作为数据专业人士,你的任务是识别并逆转这种数据熵,在组织的其他部分的帮助下。如果你不这样做,随着时间的推移,你的工作将变得更难,而不是更容易。

要管理和逆转数据熵,你首先需要对其进行测量。市场上有一些用于此目的的复杂工具,但如果你的组织无法并愿意投资这些工具,那么就从简单的数据质量措施开始,倡导数据质量。如果你的组织没有有效的数据质量测量协议,你可以成为激发行动的人。

提倡数据质量意味着向他人展示如何做到这一点。在这本书中,我们已经为你提供了采取以数据为中心的方法进行机器学习的工具。现在,轮到你通过展示这种方法的可能性来激励你的同事了。慷慨地分享你在本书中学到的知识。

提倡数据质量还意味着创造一个重视并优先考虑数据准确性的环境。鼓励你的团队永远不要对数据的状态做出假设。通过这样做,你将为可靠、准确和有效的机器学习模型奠定基础。

为了做到这一点,你需要将来自整个业务部门的人聚集在一起。

聚集人群

数据科学在本质上是一门协作学科。它需要来自不同背景和技能集的个人的投入和专业知识。作为任何机器学习项目中心的专家,你的一个主要角色是促进这种协作。

这里是挑战:大多数数据科学家都好奇、技术精湛、自主性强、高度智能的系统思考者。我们喜欢解决复杂问题,并且喜欢独自解决这些问题。我们喜欢独自工作。我们沉迷于细节。我们习惯于人们向我们寻求帮助,而不是反过来。我们是问题解决者,而不是问题制造者。

对于具有这些行为特征的人来说,聚集人群和管理从群体动态中产生的不可避免的人际复杂性可能非常困难。这可能需要大量的精力,并推动我们超越我们的舒适区。在我们这本书中涵盖的所有内容中,学习如何聚集人群可能是最困难的部分。

同时,我们的利益相关者通常对机器学习没有深入的技术理解。他们可能甚至不感兴趣了解这一切是如何运作的——他们只想看到业务成果。他们想看到结果,而不是数据质量问题。这是一个难以激发的群体。

要营造一个开放沟通、思想共享和建设性反馈成为常态的环境并不容易。要打破壁垒并鼓励跨职能协作也不容易。但如果你想要最大化构建有影响力的机器学习解决方案的机会,这是必须做到的。

你通过将其与业务问题联系起来,而不是通过描述技术复杂性,来激发非技术利益相关者对数据质量的兴趣。这要求你了解业务,比要求业务利益相关者了解数据更为重要。

因此,我们鼓励你走出舒适区,尽你所能将数据科学变成一项团队运动,即使这真的很困难,感觉不舒服。当你成功时,这将是非常有回报的。记住——最具创新性的解决方案通常来自汇聚不同视角和方法的多元化团队。

最后,采用以数据为中心的方法不仅限于优化机器学习性能。它还涉及使用数据时必须遵守的伦理和公平的重要责任。

对人工智能的伦理和公平性负责

随着机器学习和人工智能的持续增长,这些技术在伦理和公平性方面的重要性也在增加。作为该领域的专家,你有责任确保你构建模型所依据的数据尽可能公平和无偏见。

机器学习是一种创造行为。它是一个塑造我们周围世界的流程,影响着我们与产品、服务甚至彼此的互动方式。机器学习模型具有塑造行为、塑造感知和建立规范的力量。

这种力量可以用于善行,创造提升生活质量和解决紧迫社会问题的产品。但也可以被滥用,导致有害的结果。作为机器学习解决方案的设计者,我们对自己设计及其对世界的影响负有责任至关重要。仅仅创建功能准确或技术先进的东西是不够的。

这意味着要主动识别潜在的偏见来源并采取措施减轻它们。这也意味着要透明地说明模型的构建和使用方式,并对它们的成果负责。通过承担这一责任,你将有助于建立对机器学习系统的信任,并确保它们以公平和有益于所有人的方式使用。

让数据成为每个人的业务——我们的经验

几年前,我(乔纳斯)接管了一个由数据工程师、BI 开发者、数据分析师和数据科学家组成的高技能团队的领导工作。

尽管这是一个非常胜任的团队,对他们的业务有坚实的理解,但他们很难发挥全部潜力。我迅速确定了团队的首要挑战:来自组织各处的利益相关者不断向团队提出基本的数据请求,这使得每个人都非常忙碌,但影响并不大。

简而言之,利益相关者习惯于请求原始数据或最少加工的数据,以便他们可以找到自己的见解,而团队则被培养成不情愿地接受这种操作模式。

数据和分析的零散使用表明,该组织没有认识到数据质量的重要性。他们还错过了通过更有组织地使用高级分析来大幅提升业务绩效的机会。

我们希望扭转这种局面,以便能够建立一个更结构化、更高效的用数据和分析方法。我们的目标是强调数据质量的重要性及其推动业务绩效提升的潜力。我们旨在改变从自助式原始数据到通过精心策划、高级分析提供有价值的见解的方法,从而将操作模式从被动转变为主动。

我们有三个重点领域:

  • 我们希望让人们意识到数据质量的重要性。我们成功定义是看到关键业务利益相关者转变为自荐的数据质量倡导者。

  • 我们希望提供能够创造持久业务价值的高级数据产品。我们成功定义是看到我们的团队和业务利益相关者共同定义、构建和使用数据驱动的业务工具和产品。

  • 我们希望数据成为公司和其客户的资产。我们成功定义是团队提供的数据驱动解决方案为客户和员工创造了持久的价值。

我们首先举办了全公司教育会议,强调数据质量的重要性以及使用机器学习改善业务绩效的潜力。人们点头表示同意,并称赞团队使复杂信息对普通观众易于理解。但我们没有看到我们希望的行为改变。

随着利益相关者受到启发,开始使用数据来指导他们的决策,基本请求的数量急剧增加。团队比以往任何时候都忙,试图从这些设计不佳的请求中找到最佳方案。

我们迅速决定改变我们的方法。首先,我们引入了一个要求,在承诺任何需要超过半天时间完成的新工作之前,必须先建立业务案例。

其次,我们与高级利益相关者进行了一系列研讨会,以确定和排名公司最大的痛点,这些痛点可以通过提供更好的高级分析来解决。

从这些研讨会中产生了三个主要项目,涉及组织各方的利益相关者:为面向客户的员工提供新的投资组合管理系统、为后台员工提供任务管理系统,以及一个流程自动化项目。所有三个项目都依赖于基于规则的机器学习算法,并需要提高对数据质量的关注。

项目依赖于业务利益相关者负责数据质量,并创建确保数据质量始终符合我们标准的流程。我们的团队与业务利益相关者紧密合作,确保业务问题和数据采集之间始终存在清晰的联系。

让我们非常兴奋的是,在很短的时间内,公司的会议议程中就包括了数据质量问题和发展计划。我们的高级利益相关者已经承担起倡导数据质量的职责,并对其团队负责。

这三个项目都取得了显著的成功,在同时增强跨职能协作的同时产生了稳健的数据产品。这些项目推动了数据驱动决策的转变,并在公司范围内强化了数据作为战略资产的认识。

这种重大转变之所以成为可能,仅仅是因为我的团队中的数据专业人士勇敢地走出他们的舒适区,使数据成为每个人的业务。

摘要

在本书中,你已经获得了宝贵的技能,现在你拥有了以数据为中心的方法达到机器学习下一个前沿的知识。然而,这些知识只有在你应用它们时才有用,而这需要努力。

这需要你使用本书中概述的工具和技术。它还要求你走出舒适区,在数据质量、跨职能协作和数据伦理方面发挥主导作用。你不能独自改变世界,所以把这本书交给同事,并让他们加入数据为中心的行列。

在我们结束之际,请记住,开始这段以数据为中心的机器学习之旅不是一个终点,而是一个持续的过程。数据和人工智能的领域始终在不断发展,我们自己也必须不断进步。让我们继续学习、创新,共同塑造数据科学和机器学习的未来。你的数据为中心的旅程才刚刚开始。

参考文献

  1. www.matillion.com/blog/matillion-and-idg-survey-data-growth-is-real-and-3-other-key-findings,查看日期:2023 年 8 月 30 日