Python-机器学习模型调试指南-二-

97 阅读1小时+

Python 机器学习模型调试指南(二)

原文:annas-archive.org/md5/842997dabd1ace27744af80826d549f0

译者:飞龙

协议:CC BY-NC-SA 4.0

第六章:机器学习建模中的可解释性和可解释性

我们使用或开发的绝大多数机器学习模型都是复杂的,需要使用可解释性技术来识别改进它们性能、减少偏差和增加可靠性的机会。

在本章中,我们将探讨以下主题:

  • 可解释性机器学习与黑盒机器学习

  • 机器学习中的可解释性方法

  • 在 Python 中练习机器学习可解释性

  • 审视为什么可解释性并不足够

到本章结束时,您将了解机器学习建模中可解释性的重要性,并练习使用 Python 中的某些可解释性技术。

技术要求

在本章中,以下要求应予以考虑,因为它们有助于您更好地理解所提到的概念,在您的项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • matplotlib >= 3.7.1

您可以在 GitHub 上找到本章的代码文件:github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter06

可解释性机器学习与黑盒机器学习

如线性回归这样的可解释和简单模型使得评估改进它们的可能性、发现它们的问题(如需要检测和消除的偏差)以及建立对使用此类模型的信任变得容易。然而,为了实现更高的性能,我们通常不会止步于这些简单模型,而是依赖于复杂或所谓的黑盒模型。在本节中,我们将回顾一些可解释模型,然后介绍您可以用来解释您的黑盒模型的技术。

可解释的机器学习模型

线性模型,如线性回归和逻辑回归、浅层决策树和朴素贝叶斯分类器,是简单且可解释的方法的例子(图 6.1)。我们可以轻松地提取这些模型在预测输出中对特征贡献的部分,并识别改进它们性能的机会,例如通过添加或删除特征或改变特征归一化。我们还可以轻松地识别模型中是否存在偏差——例如,对于特定的种族或性别群体。然而,这些模型非常简单,能够访问成千上万或数百万样本的大型数据集使我们能够训练高性能但复杂的模型:

图 6.1 – 可解释分类方法的示例

图 6.1 – 可解释分类方法的示例

复杂模型,例如具有许多深度决策树或深度神经网络的随机森林模型,虽然它们几乎像黑盒系统一样工作,但有助于我们实现更高的性能。为了能够理解这些模型并解释它们是如何得出预测的,以及建立对其有用性的信任,我们可以使用机器学习可解释性技术。

复杂模型的可解释性

可解释性技术就像复杂机器学习模型和用户之间的桥梁。它们应该提供忠实于模型工作方式的解释。另一方面,它们应该提供对用户有用且易于理解的解释。这些解释可用于识别改进模型性能的机会、减少模型对特征值变化敏感性的影响、提高模型训练中的数据效率、帮助在模型中进行适当的推理、避免虚假相关性,并帮助实现公平性(Weber 等人,2022 年;图 6.2):

图 6.2 – 使用可解释性对机器学习模型的影响

图 6.2 – 使用可解释性对机器学习模型的影响

现在你已经更好地理解了可解释性在机器学习建模中的重要性,我们准备深入了解可解释性技术的细节。

机器学习中的可解释性方法

在使用或开发机器学习建模的可解释性技术时,我们需要牢记以下考虑因素(Ribeiro 等人,2016 年):

  • 可解释性:解释需要用户能够理解。机器学习解释的主要目标之一是使复杂模型对用户可理解,并在可能的情况下提供可操作的信息。

  • 局部忠实度(忠实性):捕捉模型的复杂性,以便它们完全忠实并满足全局忠实度标准,并非所有技术都能实现。然而,解释至少应该对模型局部忠实。换句话说,解释需要恰当地解释模型在研究数据点的邻近区域是如何表现的。

  • 模型无关性:尽管有一些技术是为特定的机器学习方法设计的,例如随机森林,但它们应该对使用不同超参数或针对不同数据集构建的模型保持无关。可解释性技术需要将模型视为黑盒,并为模型提供全局或局部的解释。

可解释性技术可以分为局部可解释性全局可解释性方法。局部可解释性方法旨在满足之前列出的标准,而全局可解释性技术试图超越局部可解释性,并为模型提供全局解释。

局部可解释性技术

局部可解释性帮助我们理解模型在特征空间中接近数据点的行为。尽管这些模型满足局部保真度标准,但被识别为局部重要的特征可能不是全局重要的,反之亦然(Ribeiro 等人,2016)。这意味着我们无法轻易地从全局解释推断出局部解释,反之亦然。在本节中,我们将讨论五种局部解释技术:

  • 特征重要性

  • 反事实

  • 基于样本的可解释性

  • 基于规则的解释性

  • 显著性图

之后,我们还将介绍一些全局可解释性技术。

特征重要性

局部可解释性的主要方法之一是解释每个特征在预测目标数据点在邻域中的结果时的局部贡献。这类方法的广泛使用例子包括 SHapley Additive exPlanations(SHAP)(Lundberg 等人,2017)和 Local Interpretable Model-agnostic Explanations(LIME)(Ribeiro 等人,2016)。让我们简要讨论这两种方法背后的理论,并在 Python 中实践它们。

使用 SHAP 的局部解释

SHAP 是由 Scott Lundberg 和 Su-In Lee(Lundberg 和 Lee,2017)引入的 Python 框架。这个框架的想法是基于使用 Shapely 值,这是一个以美国博弈论家、诺贝尔奖获得者 Lloyd Shapley 命名的已知概念(Winter,2022)。SHAP 可以确定每个特征对模型预测的贡献。由于特征在确定分类模型的决策边界并最终影响模型预测方面是协同工作的,SHAP 尝试首先识别每个特征的边际贡献,然后提供 Shapely 值作为每个特征在整个特征集合作下对模型预测贡献的估计。从理论角度来看,这些边际贡献可以通过单独移除特征以及在不同的组合中移除特征来计算,计算每个特征集移除的效果,然后对贡献进行归一化。由于可能的组合数量可能呈指数增长到数十亿,即使对于具有 40 个特征的模型,这个过程也不能对所有可能的特征组合重复进行。相反,这个过程只使用有限次数来得到 Shapely 值的近似。此外,由于在大多数机器学习模型中无法移除特征,特征值要么由随机分布中的替代值替换,要么由每个特征的背景集合中的有意义且可能的值替换。我们不想深入探讨这个过程的细节,但将在下一节中练习使用这种方法。

使用 LIME 的局部解释

LIME 是 SHAP 的替代品,用于局部可解释性,它以模型无关的方式通过局部近似可解释模型来解释任何分类器或回归器的预测(图 6*.3*;Ribeiro 等人,2016):

图 6.3 – LIME 中局部可解释建模的示意图

图 6.3 – LIME 中局部可解释建模的示意图

该技术的某些优点,如 Ribeiro 等人(2016)的原论文中提到的,包括以下内容:

  • 理论和提供的解释直观且易于理解

  • 提供稀疏解释以增加可解释性

  • 与不同类型的结构化和非结构化数据一起工作,例如文本和图像

反事实

反事实示例或解释有助于我们识别在实例中需要改变什么才能改变分类模型的输出。这些反事实可以帮助在许多应用中识别可操作路径,例如金融、零售、营销、招聘和医疗保健。一个例子是向银行客户建议他们如何改变其贷款申请被拒绝的情况(Guidotti, 2022)。反事实还可以帮助识别模型中的偏差,这有助于我们提高模型性能或消除模型中的公平性问题。在生成和使用反事实解释时,我们需要考虑以下因素(Guidotti, 2022):

  • 有效性:只有当反事实示例的分类结果与原始样本不同时,反事实示例才是有效的。

  • 相似性:反事实示例应尽可能与原始数据点相似。

  • 多样性:尽管反事实示例应与它们所派生的原始样本相似,但它们之间需要具有多样性,以提供不同的选项(即不同的可能特征变化)。

  • 可操作性:并非所有特征值的变化都具有可操作性。由反事实方法建议的反事实的可操作性是实际受益的重要因素。

  • 合理性:反事实示例的特征值应该是合理的。反事实的合理性增加了从它们中推导出解释的信任度。

我们还必须注意,反事实解释器需要高效且足够快地生成反事实,并且在生成与相似数据点相关的反事实时保持稳定(Guidotti, 2022)。

基于样本的可解释性

另一种解释性的方法是依靠真实或合成数据点的特征值和结果来帮助局部模型解释性。在这个解释性技术类别中,我们的目标是找出哪些样本被错误分类,以及哪些特征集导致错误分类的概率增加,以帮助我们解释我们的模型。我们还可以评估哪些训练数据点导致决策边界的改变,以便我们可以预测测试或生产数据点的输出。有一些统计方法,如影响函数(Koh 和 Liang 2017),这是一种评估样本对模型参数影响的经典方法,我们可以用它来识别样本对模型决策过程的贡献。

基于规则的解释性

基于规则的解释方法,如锚点解释,旨在找出导致高概率得到相同输出的特征值条件(Ribeiro 等,2018)。例如,在预测数据集中个人的薪水低于或等于 50k 或高于 50k 的情况下,“教育程度≤高中导致薪水≤50k”可以被视为基于规则的解释中的一个规则。这些解释需要局部忠实。

显著性图

显著性图的目标是解释哪些特征对数据点的预测输出贡献更多或更少。这些方法通常用于在图像数据上训练的机器学习或深度学习模型(Simonyan 等,2013)。例如,我们可以使用显著性图来确定分类模型是否使用背景森林来识别它是一张熊的图片而不是泰迪熊,或者是否使用熊的身体部件来识别它。

全局解释

尽管为机器学习模型实现可靠的全球解释很困难,但它可以增加对它们的信任(Ribeiro 等,2016)。在开发和部署机器学习模型时,性能并不是建立信任的唯一方面。虽然局部解释在调查单个样本和提供可操作信息方面非常有帮助,但可能不足以建立这种信任。在这里,我们将讨论三种超越局部解释的方法,包括收集局部解释、知识蒸馏和反事实的摘要。

收集局部解释

子模可加选择 LIMESP-LIME)是一种全局解释技术,它使用 LIME 的局部解释来提供一个模型行为的全局视角(Riberio 等,2016)。由于可能无法使用所有数据点的局部解释,SP-LIME 选择了一组代表性的多样化样本,这些样本能够代表模型的全局行为。

知识蒸馏

知识蒸馏的思想是使用简单的可解释模型(如决策树)来近似复杂模型的行为,这一概念最初是为神经网络模型提出的(Hinton 等人,2015 年;Frosst 和 Hinton,2017 年)。换句话说,我们的目标是构建简单的模型,如决策树,以近似给定样本集的复杂模型的预测。

反事实的摘要

我们可以使用为多个数据点生成的反事实摘要(包括正确和错误的预测结果)来了解特征在输出预测中的贡献以及预测对特征扰动的敏感性。我们将在本章后面练习使用反事实,你将看到并非所有反事实都是可接受的,并且它们需要根据特征及其值的含义来选择。

在 Python 中实践机器学习可解释性

有几个 Python 库你可以用来提取你的机器学习模型的局部和全局解释(表 6.1)。在这里,我们想练习一些专注于局部模型可解释性的库:

导入和安装库名称URL
SHAPShappypi.org/project/shap/
LIMELimepypi.org/project/lime/
Shapashshapashpypi.org/project/shapash/
ELI5eli5pypi.org/project/eli5/
解释仪表板explainer dashboardpypi.org/project/explainerdashboard/
Dalexdalexpypi.org/project/dalex/
OmniXAIomnixaipypi.org/project/omnixai/
CARLAcarlacarla-counterfactual-and-recourse-library.readthedocs.io/en/latest/
Diverse Counterfactual Explanations (DiCE)dice-mlpypi.org/project/dice-ml/
机器学习库扩展mlxtendpypi.org/project/mlxtend/
锚点anchorgithub.com/marcotcr/anchor

表 6.1 – 具有机器学习模型可解释性功能的 Python 库或存储库

首先,我们将使用 SHAP 进行练习,这是一种广泛用于机器学习可解释性的技术。

SHAP 中的解释

我们将首先探讨使用 SHAP 进行局部解释,随后再讨论全局解释。

局部解释

在本节中,我们将使用 SHAP 从我们的机器学习模型中提取特征重要性。我们将使用加州大学欧文分校UCI)的成年人数据集来预测 90 年代人们是否年收入超过 50k;这也可以作为 SHAP 库的一部分的成年人收入数据集。您可以在archive.ics.uci.edu/ml/datasets/adult上阅读有关特征定义和其他有关此数据集的信息。

首先,在使用任何可解释性方法之前,我们需要使用此数据集构建一个监督机器学习模型。我们将使用XGBoost作为表格数据的高性能机器学习方法来练习 SHAP:

# loading UCI adult income dataset# classification task to predict if people made over $50k in the 90s or not
X,y = shap.datasets.adult()
# split the data to train and test sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size = 0.3, random_state=10)
# initializing a XGboost model
xgb_model = xgboost.XGBClassifier(random_state=42)
# fitting the XGboost model with training data
xgb_model.fit(X_train, y_train)
# generating predictions for the test set
y_pred = xgb_model.predict(X_test)
# identifying misclassified datapoints in the test set
misclassified_index = np.where(y_test != y_pred)[0]
# calculating roc-auc of predictions
print("ROC-AUC of predictions: {}".format(
    roc_auc_score(y_test, xgb_model.predict_proba(
        X_test)[:, 1])))
print("First 5 misclassified test set datapoints:
    {}".format(misclassified_index[0:5]))

SHAP 库中提供了不同的方法来近似特征重要性,例如shap.LinearExplainer()shap.KernelExplainer()shap.TreeExplainer()shap.DeepExplainer()。在基于树的方法(如随机森林和 XGBoost)的情况下,您可以使用shap.TreeExplainer()。让我们使用训练好的模型构建一个解释器对象,然后提取 Shapely 值:

# generate the Tree explainerexplainer = shap.TreeExplainer(xgb_model)
# extract SHAP values from the explainer object
shap_values = explainer.shap_values(X_test)

SHAP 库中有多个绘图函数,可以提供使用 Shapely 值的特征重要性的视觉说明。例如,我们可以使用shap.dependence_plot()来识别教育程度数特征的 Shapely 值:

# If interaction_index of "auto" is chosen then# the strongest interaction is used to color the dots.
shap.dependence_plot("Education-Num", shap_values, X_test)

下面的依赖性图清楚地表明,更高的教育程度数值会导致更高的 Shapely 值或更大的预测积极结果(即,>50k 薪水)的贡献:

图 6.4 – 成年人收入数据集测试集中教育程度数特征的 SHAP 值

图 6.4 – 成年人收入数据集测试集中教育程度数特征的 SHAP 值

我们可以用其他特征重复此过程,例如年龄,这将产生与教育程度数相似的解释。使用shap.dependence_plot()教育程度数年龄的唯一区别在于interaction_index,对于年龄被指定为None

# generate dependence plot for "Age" featureshap.dependence_plot("Age", shap_values, X_test,
    interaction_index=None)

图 6.5 – 成年人收入数据集测试集中年龄特征的 SHAP 值

图 6.5 – 成年人收入数据集测试集中年龄特征的 SHAP 值

如果我们需要从数据集的特定子集中提取模型解释,我们可以使用相同的函数,但使用我们想要调查的数据子集而不是整个数据集。我们还可以使用训练集和测试集来识别用于模型训练的数据中的解释以及我们想要用于评估模型性能的未见数据。为了展示这一点,我们将使用以下代码调查测试集中被错误分类的子集中年龄的重要性:

# generate dependence plot for "Age" featureshap.dependence_plot("Age",
    shap_values[misclassified_index],
    X_test.iloc[misclassified_index,:],
    interaction_index=None)

如您所见,SHAP 值对于错误分类的数据点(图 6.6)和整个数据集(图 6.5)具有相似的趋势:

图 6.6 – 成年人收入数据集测试集中误分类数据点的年龄特征 SHAP 值

图 6.6 – 成年人收入数据集测试集中误分类数据点的年龄特征 SHAP 值

除了从一系列数据点中提取 Shapely 值之外,我们还需要调查特征是如何对一个数据点的正确或错误预测做出贡献的。在这里,我们选择了两个样本:样本 12,实际标签为 False 或 0(即低收入),预测标签为 True 或 1(即高收入),以及 样本 24,实际和预测标签分别为 True 和 False。在这里,我们可以使用 shap.plots._waterfall.waterfall_legacy() 并提取输入特征的预期值,如图 图 6.7 所示。在这种 SHAP 绘图中,对于每个特征 Xf(X) 是给定 X 的预测值,而 E[f(X)] 是目标变量的预期值(即所有预测的平均值,mean(model.predict(X)))。此图显示了单个特征对预测的影响程度:

# extracting expected valuesexpected_value = explainer.expected_value
# generate waterfall plot for observation 12
shap.plots._waterfall.waterfall_legacy(expected_value,
    shap_values[12], features=X_test.iloc[12,:],
    feature_names=X.columns, max_display=15, show=True)
# generate waterfall plot for observation 24
shap.plots._waterfall.waterfall_legacy(expected_value,
    shap_values[24],features=X_test.iloc[24,:],
    feature_names=X.columns,max_display=15, show=True)

图 6.7,针对 样本 12,显示 关系教育程度 是影响最大的特征,而 种族国家 对该样本结果的影响最小:

图 6.7 – 成年人收入数据集中样本 12 的 SHAP 瀑布图

图 6.7 – 成年人收入数据集中样本 12 的 SHAP 瀑布图

关系教育程度 也是对 样本 24 影响最大的特征(图 6.8)。然而,在 样本 12 中,第三大贡献来自 每周小时数,它对 样本 24 的结果影响较小。这种分析类型可以帮助我们比较一些错误的预测,并识别出可能有助于提高模型性能的建议。或者,我们可以提取出改善该数据集中个人未来收入的可操作建议:

图 6.8 – 成年人收入数据集中样本 24 的 SHAP 瀑布图

图 6.8 – 成年人收入数据集中样本 24 的 SHAP 瀑布图

尽管 SHAP 提供了易于理解的见解,但我们仍需确保模型中的特征依赖性在解释 Shapely 值时不会导致混淆。

全局解释

虽然 shap.dependence_plot() 可能看起来提供了全局解释,因为它显示了特征对所有或数据点的子集的影响,但我们仍需要模型特征和数据点的解释来建立对模型的信任。shap.summary_plot() 是此类全局解释的一个例子,它总结了指定数据点集中特征的全局 Shapely 值。这类摘要图和结果对于识别最有效的特征以及了解模型中是否存在诸如种族或性别之类的偏见非常重要。通过以下摘要图(图 6.9),我们可以轻松地看到 性别种族 并不是影响最大的特征之一,尽管它们的影响可能并不一定可以忽略,可能需要进一步调查。我们将在下一章讨论模型偏差和公平性:

图 6.9 – 成人收入数据集的 SHAP 摘要图

图 6.9 – 成人收入数据集的 SHAP 摘要图

以下是生成之前摘要图的代码:

# create a SHAP beeswarm plot (i.e. SHAP summary plot)shap.summary_plot(shap_values, X_test,plot_type="bar")

使用 LIME 的解释

在学习了如何使用 SHAP 进行解释后,我们现在将注意力转向 LIME。我们将首先从局部解释开始。

局部解释

LIME 是获取单个数据点易于理解的局部解释的另一种方法。我们可以使用 lime Python 库来构建解释器对象,然后使用它来识别感兴趣样本的局部解释。在这里,我们再次将使用为 SHAP 训练的 XGBoost 模型,并为 sample 12sample 24 生成解释,以表明它们的预测结果是不正确的。

默认情况下,lime 使用 岭回归 作为生成局部解释的可解释模型。我们可以在 lime.lime_tabular.LimeTabularExplainer() 类中通过将 feature_selection 改为 none 来更改此方法,以进行无特征选择的线性建模,或者使用 lasso_path,它使用 scikit-learnlasso_path(),作为另一种带有正则化的监督线性建模形式。

注意

xgb_model.fit(np.array(X_train), y_train) makes the model usable for the lime library:
# create explainerexplainer = lime.lime_tabular.LimeTabularExplainer(
    np.array(X_train), feature_names=X_train.columns,
    #X_train.to_numpy()
    class_names=['Lower income','Higher income'],
    verbose=True)
# visualizing explanation by LIME
print('actual label of sample 12: {}'.format(y_test[12]))
print('prediction for sample 12: {}'.format(y_pred[12]))
exp = explainer.explain_instance(
    data_row = X_test.iloc[12],
    predict_fn = xgb_model.predict_proba)
exp.show_in_notebook(show_table=True)

您可以将图 6.10 中中间的图解释为 sample 12 预测结果为 高收入低收入 的局部特征贡献。与 SHAP 类似,教育程度关系 特征对样本被错误预测为 高收入 的贡献最大。另一方面,资本收益资本损失 对推动样本输出预测为另一类别的贡献最大。但我们也必须注意特征值,因为对于这个样本,资本收益资本损失 都是零:

图 6.10 – 成人收入数据集中样本 12 的 LIME 局部解释

图 6.10 – 成人收入数据集中样本 12 的 LIME 局部解释

同样,我们可以调查 sample 24 的 LIME 结果,如图 6.11 所示:

图 6.11 – 成年人收入数据集中样本 24 的 LIME 局部解释

图 6.11 – 成年人收入数据集中样本 24 的 LIME 局部解释

资本收益教育年限每周工作时间对预测输出在正负方向上的贡献最大。然而,资本收益不影响这个特定的数据点,因为其值为零。

全局解释

使用 lime.submodular_pick.SubmodularPick() 来选择这些样本。以下是此类的参数,这些参数可以帮助您解释全局回归或分类模型:

  • predict_fn(预测函数):对于 ScikitClassifiers,这是 classifier.predict_proba(),而对于 ScikitRegressors,这是 regressor.predict()

  • sample_size:如果选择 method == 'sample',则要解释的数据点的数量

  • num_exps_desired:返回的解释对象的数量

  • num_features:解释中存在的最大特征数:

sp_obj = submodular_pick.SubmodularPick(explainer,    np.array(X_train), xgb_model.predict_proba,
    method='sample', sample_size=3, num_features=8,
    num_exps_desired=5)
# showing explanation for the picked instances for explanation if you are using Jupyter or Colab notebook
[exp.show_in_notebook() for exp in sp_obj.explanations]

图 6*.12* 展示了 SP-LIME 选出的三个数据点:

图 6.12 – SPI-LIME 为全局可解释性选择的数据点

图 6.12 – SPI-LIME 为全局可解释性选择的数据点

但您可以选择不可视化选定的实例,而是为每个解释对象使用 as_map() 参数而不是 show_in_notebook(),作为 sp_obj.explanations 中的解释对象的一部分,然后为更大的数据点集总结信息,而不是调查少量样本。对于此类分析,您可以使用少量数据点,例如在具有数万个数据点的非常大的数据集中,使用 1%或更低的百分比。

使用多样化的反事实解释(DiCE)生成反事实

您可以使用 dice_ml Python 库(Mothilal 等人,2020 年)来生成反事实,并了解模型如何从一个预测切换到另一个预测,正如本章前面所解释的。首先,我们必须训练一个模型,然后使用 dice_ml.Dice() Python 类创建一个解释对象,在安装并导入 dice_ml 库之后,如下所示:

### This example is taken from https://github.com/interpretml/DiCE ###dataset = helpers.load_adult_income_dataset()
target = dataset["income"] # outcome variable
train_dataset, test_dataset, _, _ = train_test_split(
    dataset,target,test_size=0.2,random_state=0,
    stratify=target)
# Dataset for training an ML model
d = dice_ml.Data(dataframe=train_dataset,
    continuous_features=['age','hours_per_week'],
    outcome_name='income')
# Pre-trained ML model
m = dice_ml.Model(
    model_path=dice_ml.utils.helpers.get_adult_income_modelpath(),
    backend='TF2', func="ohe-min-max")
# DiCE explanation instance
exp = dice_ml.Dice(d,m)

然后,我们可以使用生成的解释对象为单个或多个样本生成反事实。在这里,我们为 sample 1 生成 10 个反事实:

query_instance = test_dataset.drop(columns="income")[0:1]dice_exp = exp.generate_counterfactuals(query_instance,
    total_CFs=10, desired_class="opposite",
    random_seed = 42)
# Visualize counterfactual explanation
dice_exp.visualize_as_dataframe()

图 6*.13* 展示了目标样本的特征值以及 10 个相应的反事实:

图 6.13 – 成年人收入数据集中选定的数据点和生成的反事实

图 6.13 – 成年人收入数据集中选定的数据点和生成的反事实

尽管所有反事实都符合切换目标样本结果的目标(即,样本 1),但并非所有反事实都符合每个特征的定义和意义,是可行的。例如,如果我们想建议一个 29 岁的人将他们的结果从低薪改为高薪,建议他们在 80 岁时会赚高薪并不是一个有效和可行的建议。同样,建议将每周工作小时数从 38 小时增加到>90 小时也是不可行的。你需要使用这样的考虑来拒绝反事实,以便你可以识别模型性能的机会,并为用户提供可操作的建议。此外,你可以切换到不同的技术来为你的模型和应用生成更有意义反事实。

有更多最近的 Python 库,如Dalex(Baniecki 等人,2021 年)和OmniXA(杨等人,2022 年),你可以用于模型可解释性。我们还将讨论如何使用这些方法和 Python 库来减少偏差,并帮助我们朝着在新开发或修改我们已训练的机器学习模型时实现公平性迈进。

回顾为什么仅仅拥有可解释性是不够的

可解释性帮助我们为模型用户建立信任。正如你在本章所学,你可以使用可解释性技术来理解你的模型是如何生成数据集中一个或多个实例的输出的。这些解释有助于从性能和公平性的角度改进我们的模型。然而,我们并不能仅仅通过盲目地使用这些技术并在 Python 中生成一些结果来实现这样的改进。例如,正如我们在使用多样化的反事实解释(DiCE)生成反事实部分所讨论的,一些生成的反事实可能并不合理和有意义,我们不能依赖它们。或者,当使用 SHAP 或 LIME 为单个或多个数据点生成局部解释时,我们需要注意特征的意义、每个特征值的范围及其背后的意义,以及我们调查的每个数据点的特征。使用可解释性进行决策的一个方面是区分模型和我们在训练、测试或生产中调查的特定数据点的问题。一个数据点可能是一个异常值,它使我们的模型对它来说不那么可靠,但并不一定使我们的模型整体上不那么可靠。在下一章,第七章减少偏差和实现公平性中,我们将讨论偏差检测并不仅仅是识别我们的模型依赖于诸如年龄种族肤色等特征。

总的来说,这些考虑告诉我们,仅仅运行几个 Python 类来为我们的模型使用可解释性是不够的,以达到信任并生成有意义的解释。这还远不止于此。

摘要

在本章中,你学习了可解释的机器学习模型以及可解释性技术如何帮助你提高模型的表现力和可靠性。你学习了不同的局部和全局可解释性技术,如 SHAP 和 LIME,并在 Python 中进行了实践。你还有机会使用提供的 Python 代码进行实践,学习如何在项目中使用机器学习可解释性技术。

在下一章中,你将学习如何检测和减少模型中的偏差,以及如何使用 Python 中可用的功能在开发机器学习模型时满足必要的公平性标准。

问题

  1. 可解释性如何帮助你提高模型的表现?

  2. 局部可解释性和全局可解释性之间有什么区别?

  3. 由于其可解释性,使用线性模型是否更好?

  4. 可解释性分析是否会使机器学习模型更可靠?

  5. 你能解释一下 SHAP 和 LIME 在机器学习可解释性方面的区别吗?

  6. 你如何在开发机器学习模型时从反事实中受益?

  7. 假设一个机器学习模型被用于银行的贷款审批。所有建议的反事实对建议一个人如何提高获得批准的机会都很有用吗?

参考文献

  • Weber, Leander,等人。超越解释:基于 XAI 的模型改进的机会和挑战. 信息融合 (2022)。

  • Linardatos, Pantelis,Vasilis Papastefanopoulos,和 Sotiris Kotsiantis. 可解释人工智能:机器学习可解释性方法综述. 混沌 23.1 (2020): 18.

  • Gilpin, Leilani H.,等人。解释解释:机器学习可解释性的概述. 2018 IEEE 第 5 届数据科学和高级分析国际会议(DSAA)。IEEE,2018。

  • Carvalho, Diogo V.,Eduardo M. Pereira,和 Jaime S. Cardoso. 机器学习可解释性:方法与度量综述. 电子学 8.8 (2019): 832.

  • Winter, Eyal. Shapley 值. 游戏理论及其在经济应用中的手册 3 (2002): 2025-2054.

  • *使用 Python 的可解释人工智能指南www.thepythoncode.com/article/explainable-ai-model-python

  • Burkart, Nadia,和 Marco F. Huber. 监督机器学习可解释性综述. 人工智能研究杂志 70 (2021): 245-317.

  • Guidotti, Riccardo. 反事实解释及其发现:文献综述和基准测试. 数据挖掘与知识发现 (2022): 1-55.

  • Ribeiro, Marco Tulio,Sameer Singh,和 Carlos Guestrin. 锚:高精度无模型可解释性. AAAI 人工智能会议论文集。第 32 卷。第 1 期。2018 年。

  • Hinton, Geoffrey,Oriol Vinyals,和 Jeff Dean. 从神经网络中提取知识. arXiv 预印本 arXiv:1503.02531 (2015).

  • Simonyan, Karen, Andrea Vedaldi, and Andrew Zisserman. 卷积网络内部深探:可视化图像分类模型和显著性图. arXiv 预印本 arXiv:1312.6034 (2013)。

  • Frosst, Nicholas, and Geoffrey Hinton. 将神经网络蒸馏成软决策树. arXiv 预印本 arXiv:1711.09784 (2017)。

  • Lundberg, Scott M., and Su-In Lee. 解释模型预测的统一方法. 神经信息处理系统进展第 30 卷 (2017)。

  • Ribeiro, Marco Tulio, Sameer Singh, and Carlos Guestrin. “为什么我应该相信你?”解释任何分类器的预测. 第 22 届 ACM SIGKDD 国际知识发现和数据挖掘会议论文集,2016 年。

  • Baniecki, Hubert, et al. Dalex: 在 Python 中实现具有交互式可解释性和公平性的负责任机器学习. 机器学习研究杂志第 22 卷第 1 期 (2021): 9759-9765。

  • Yang, Wenzhuo, et al. OmniXAI: 一个可解释人工智能库. arXiv 预印本 arXiv:2206.01612 (2022)。

  • Hima Lakkaraju, Julius Adebayo, Sameer Singh, AAAI 2021 教程:解释机器学习预测

  • Mothilal, Ramaravind K., Amit Sharma, and Chenhao Tan. 通过多样化的反事实解释解释机器学习分类器. 2020 年公平、问责和透明度会议论文集。

第七章:降低偏见和实现公平性

当使用机器学习跨越不同行业时,公平性是一个重要的话题,正如我们在第三章,“向负责任的人工智能调试”中讨论的那样。在本章中,我们将向您提供一些在机器学习环境中广泛使用的公平性概念和定义,以及如何使用旨在不仅帮助您评估模型中的公平性,而且在此方面改进它们的公平性和可解释性 Python 库。

本章包含许多图表和代码示例,以帮助您更好地理解这些概念,并在项目中开始从中受益。请注意,一个章节远远不足以使您成为公平性主题的专家,但本章将为您提供开始在实际项目中实践这一主题所需的知识和工具。您可以通过使用更多致力于机器学习公平性的高级资源来了解更多关于这个主题的信息。

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

  • 机器学习建模中的公平性

  • 偏见的来源

  • 使用可解释性技术

  • Python 中的公平性评估和改进

到本章结束时,您将了解一些技术细节和 Python 工具,您可以使用它们来评估模型的公平性并减少偏见。您还将了解如何从您在第六章,“机器学习建模中的可解释性和可解释性”中学习的机器学习可解释性技术中受益。

技术要求

以下要求应在本章中考虑,因为它们将帮助您更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pytest >= 7.2.2

    • shap >= 0.41.0

    • aif360 >= 0.5.0

    • fairlearn >= 0.8.0

  • 对上一章中讨论的机器学习可解释性概念的基本了解

您可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter07

机器学习建模中的公平性

要评估公平性,我们需要在心中考虑特定的因素,然后使用适当的指标来量化模型中的公平性。表 7.1为您提供了评估或实现机器学习建模中公平性的考虑因素、定义和方法的示例。我们将讨论人口统计学平等机会均等均衡机会机会平等的数学定义,作为不同的群体公平性定义。群体公平性定义确保具有共同属性和特征的人群群体的公平性,而不是个人:

机器学习公平主题描述
人口统计学平等确保预测不依赖于给定的敏感属性,例如种族、性别或种族
概率平等确保预测对给定敏感属性的独立性,例如在给定真实输出时对种族、性别或种族的独立性
机会平等确保为个人或人群提供的平等机会
个人公平确保对个人而不是具有共同属性的群体公平
一致性不仅在相似的数据点或用户之间,而且在时间上提供决策的一致性
通过无意识实现公平如果你在决策过程中不知道敏感属性,则可以实现公平
通过透明度实现公平通过透明度和通过可解释性建立信任来提高公平性

表 7.1 – 机器学习和人工智能中关于公平性的重要主题和考虑因素

人口统计学平等是一个群体公平定义,确保模型预测不依赖于给定的敏感属性,例如种族或性别。从数学上讲,我们可以将其定义为预测类别的概率相等,例如 C i,对于给定属性的各个群体,如下所示:

P(C = C i|G = g 1) = P(C = C i|G = g 2)

为了更好地理解人口统计学平等的含义,我们可以考虑以下例子,这些例子符合人口统计学平等的公平性:

  • 在 COMPAS 中每个种族群体中拒绝保释的百分比相同。我们已在第三章中介绍了 COMPAS,向负责任的 AI 调试

  • 男女贷款申请的接受率相同。

  • 贫富社区之间住院的可能性相同。我们已在第三章中详细介绍了这个问题,向负责任的 AI 调试

差异影响比率DIR)是一个衡量基于人口统计学平等差异的指标:

DIR =  P(C = 1|G = g 1)  _____________  P(C = 1|G = g 2)

DIR 值范围是[0, ∞),其中 1 的值满足人口统计学平等,而向更高或更低值的偏差则表示根据此定义的公平性偏差。大于和小于 1 的 DIR 值分别被称为正偏差和负偏差,考虑到我们在分子中使用的群体。

尽管人口统计学上的平等在公平性中很重要,但它有其局限性。例如,在数据本身中的 DIR(即不同群体之间类别普遍性的差异)的情况下,一个完美的模型将不会满足人口统计学上的平等标准。此外,它也不反映每个群体的预测质量。其他定义有助于我们改进公平性评估。机会平等或均衡机会是一个这样的定义。当给定预测与给定敏感属性所属的群体以及真实输出无关时,均衡机会得到满足:

P( ˆ y |y, G = g 1) = P( ˆ y |y, G = g 2) = P( ˆ y |y)

机会平等的定义与均衡机会非常相似,它评估预测与给定真实输出相关的群体的独立性。但机会平等专注于特定的真实值标签。通常,正类被认为是目标类,代表为个人提供机会,例如入学或高薪。以下是机会平等的公式:

P( ˆ y |y = 1, G = g 1) = P( ˆ y |y = 1, G = g 2) = P( ˆ y |y = 1)

根据这些公平性的概念,每个概念都可能给出不同的结果。你需要考虑不同概念之间的差异,以免基于一个或另一个定义泛化公平性。

敏感变量的代理

在评估机器学习模型中的公平性时,一个挑战是存在敏感属性(如性别和种族)的代理。这些代理可能是生成模型输出的主要贡献者,并可能导致我们的模型对特定群体产生偏差。然而,我们不能简单地移除它们,因为这可能会对性能产生重大影响。表 7.2提供了这些代理的示例,针对不同的敏感属性:

敏感变量示例代理
性别教育水平、工资和收入(在某些国家),职业,犯罪指控历史,用户生成内容中的关键词(例如,在简历或社交媒体中),作为大学教职员工
种族犯罪指控历史、用户生成内容中的关键词(例如,在简历或社交媒体中)、ZIP 或邮政编码
残疾行走速度、眼动、身体姿势
婚姻状况教育水平、工资和收入(在某些国家),以及房屋大小和卧室数量
年龄姿势和用户生成内容中的关键词(例如,在简历或社交媒体中)

表 7.2 – 在公平性的背景下,一些重要敏感变量的代理示例(Caton 和 Haas,2020)

现在你已经了解了公平性的重要性以及这个主题下的一些重要定义,让我们回顾一下可能产生偏差的来源,这些偏差可能会阻碍你在模型中实现公平性的目标。

偏差的来源

机器学习生命周期中存在不同的偏差来源。偏差可能存在于收集的数据中,在数据子采样、清理和过滤中引入,或者在模型训练和选择中。在这里,我们将回顾这些来源的例子,以帮助您更好地理解如何在机器学习项目的整个生命周期中避免或检测此类偏差。

数据生成和收集中引入的偏差

我们输入到模型中的数据可能默认就有偏差,甚至在建模开始之前。我们在这里想要回顾的第一个这种偏差的来源是数据集大小的问题。将数据集视为更大人群的一个样本——例如,100 名学生的调查或 200 名银行客户的贷款申请信息。这些数据集的小规模可能会增加偏差的机会。让我们用一个简单的随机数据生成来模拟这一点。我们将编写一个函数,使用np.random.randint()生成两个随机二进制值的向量,然后计算两个 0 和 1 组之间的DIR

np.random.seed(42)def disparate_impact_randomsample(sample_size,
    sampling_num = 100): disparate_impact = []
    for sam_iter in range(0, sampling_num):
        # generating random array of 0 and 1 as two groups with different priviledges (e.g. male versus female)
        group_category = np.random.randint(2,
            size=sample_size)
    # generating random array of 0 and 1 as the output labels (e.g. accepted for loan or not)
    output_labels = np.random.randint(2, size=sample_size)
    group0_label1 = [iter for iter in range(0, len(
        group_category)) if group_category[iter] == 0 
        and output_labels[iter] == 1]
    group1_label1 = [iter for iter in range(0, len(
        group_category)) if group_category[iter] == 1 and 
        output_labels[iter] == 1]
    # calculating disparate impact 
    disparate_impact.append(len
        (group1_label1)/len(group0_label1))
    return disparate_impact

现在,让我们使用这个函数来计算 1,000 个不同规模的不同组别的 DIR,包括501001000100001000000个数据点:

sample_size_list = [50, 100, 1000, 10000, 1000000]disparate_impact_list = []
for sample_size_iter in sample_size_list:
    disparate_impact_list.append(
        disparate_impact_randomsample(
            sample_size = sample_size_iter,
            sampling_num = 1000))

以下箱线图显示了不同样本规模下DIR的分布。你可以看到,较小的样本规模具有更宽的分布,覆盖了非常低或高的DIR值,远离理想的 1 值:

图 7.1 – 不同采样规模下的 DIR 分布

图 7.1 – 不同采样规模下的 DIR 分布

我们还可以计算不同规模样本组的百分比,这些组没有通过特定的阈值,例如>=0.8 和<=1.2。图 7.2显示,较高的数据集规模导致具有正或负偏差的数据集的机会降低:

图 7.2 – 未通过 DIR 阈值的样本集百分比

图 7.2 – 未通过 DIR 阈值的样本集百分比

数据集中现有偏差的来源可能不仅仅是小样本大小的产物。例如,如果你要训练一个模型来预测个人是否会进入 STEM 领域,STEM 是科学、技术、工程和数学领域的缩写,那么你必须考虑这样一个现实:在工程等领域的相应数据中,男性相对于女性的存在是不平衡的,甚至直到最近(图 7.3):

图 7.3 – 1970 年至 2019 年间 STEM 职业中女性的百分比

图 7.3 – 1970 年至 2019 年间 STEM 职业中女性的百分比

多年来,工程师中女性比例不到 20%,这可能是由于她们对这一领域的兴趣较低、招聘过程中的偏见,或是社会中的刻板印象,这导致了该领域工作者数据中的偏见。如果数据处理的建模任务中不公平,可能会导致预测男性进入 STEM 领域的可能性高于女性,尽管她们拥有才能、知识和经验。

数据中还存在另一类内在偏见,尽管在开发机器学习模型时需要考虑它。例如,不到 1%的乳腺癌病例发生在男性身上(www.breastcancer.org)。男性和女性之间这种患病率的差异并非由数据生成或收集中的任何偏见,或社会存在的偏见所导致。这是男性和女性乳腺癌发生率的自然差异。但如果负责开发用于诊断乳腺癌的机器学习模型,男性可能会出现较高的假阴性率(即未诊断出乳腺癌)。如果你的模型没有考虑到女性比男性高得多的患病率,那么它将不是一个对男性公平的乳腺癌诊断模型。这是一个高级示例,用于阐明这类偏见。在构建用于癌症诊断的机器学习工具时,还有许多其他需要考虑的因素。

模型训练和测试中的偏见

如果数据集在男性或女性、不同种族或其他敏感属性方面存在高度不平衡,我们的模型可能会因为相应的机器学习算法在预测数据点结果时使用特征的方式而产生偏见。例如,我们的模型可能会高度依赖敏感属性或它们的代理(表 7.2)。这是模型选择过程中的一个重要考虑因素。在模型选择过程中,我们需要从训练的模型中选择一个,使用不同的方法或同一方法的超参数,以进行进一步的测试或生产。如果我们仅基于性能做出决定,那么我们可能会选择一个不公平的模型。如果我们有敏感属性,并且这些模型将直接或间接影响不同群体的人,那么在模型选择过程中,我们需要同时考虑公平性和性能。

生产中的偏见

由于训练、测试和生产阶段数据分布的不同,生产过程中可能会出现偏见和不公平现象。例如,在生产和测试数据中不存在的性别差异可能会在生产的某个阶段出现。这种情况可能导致在生命周期早期阶段无法检测到的生产偏见。我们将在第十一章中更详细地讨论这类差异,避免和检测数据与概念漂移

本章的下一步是开始练习使用帮助你在检测和消除模型偏差方面的技术和 Python 库。首先,我们将练习使用在第六章中介绍的机器学习建模中的可解释性和可解释性技术

使用可解释性技术

我们可以使用可解释性技术来识别我们模型中的潜在偏差,然后计划改进它们以实现公平性。在这里,我们想通过 SHAP 练习这个概念,并识别我们在上一章练习的成人收入数据集中男性和女性群体之间的公平性问题。使用我们在上一章为成人收入数据训练的 XGBoost 模型构建的相同的 SHAP 解释器对象,在下面的条形图中,我们可以看到,关于整个数据集或仅错误预测的数据点,对性别的依赖性很低,但并非微不足道:

图 7.4 – 整个成人收入数据集和错误预测数据点的 SHAP 摘要图

图 7.4 – 整个成人收入数据集和错误预测数据点的 SHAP 摘要图

现在,我们可以提取每个性别群体中误分类数据点的比例,如下所示:

X_WithPred.groupby(['Sex', 'Correct Prediction']).size().unstack(fill_value=0)

这将产生以下结果:

图 7.5 – 正确和错误预测中男性和女性的数量

图 7.5 – 正确和错误预测中男性和女性的数量

在这里,我们分别有女性和男性群体的 6.83%和 20.08%的误分类百分比。测试集中仅针对男性和女性群体的模型预测的 ROC-AUC 分别为 0.90 和 0.94。

你可以考虑将识别特征之间的相关性作为一种识别代理和潜在去除模型中偏差的方法。以下代码和热图(图 7.6)显示了该数据集特征之间的相关性:

corr_features = X.corr()corr_features.style.background_gradient(cmap='coolwarm')

输出将如下所示:

图 7.6 – 成人收入数据集特征之间的相关性 DataFrame

图 7.6 – 成人收入数据集特征之间的相关性 DataFrame

然而,使用这种相关性分析作为处理代理识别问题或甚至用于过滤特征以提高性能的方法存在缺点。以下是其中两个缺点:

  • 你需要考虑每对特征适当的关联度量。例如,皮尔逊相关不能用于所有特征对,因为每对数据的分布必须满足该方法的使用假设。两个变量都需要遵循正态分布,数据不应有任何异常值,这是皮尔逊相关适当使用时的两个假设之一。这意味着为了正确使用特征相关性分析方法,你需要使用适当的关联度量来比较特征。非参数统计度量,如斯皮尔曼等级相关,可能更适合,因为在使用不同变量对时,其背后的假设较少。

  • 并非所有数值都有相同的意义。一些特征是分类的,并且通过不同的方法被转换成数值特征。性别就是这些特征之一。0 和 1 的值可以用来表示女性和男性群体,但它们在数值特征(如年龄或薪水)中没有任何数值意义。

可解释性技术如 SHAP 会告诉你关于敏感属性及其对数据点结果贡献的依赖关系。然而,默认情况下,它们并不提供改进模型公平性的方法。在这个例子中,我们可以尝试将数据分割成男性和女性群体进行训练和测试。以下代码展示了针对女性群体的这种方法。同样,你可以通过使用“性别”特征的1来分离训练和测试输入输出数据,为男性群体重复此操作。为男性和女性群体分别构建的模型分别得到了 0.90 和 0.93 的 ROC-AUC 值,这几乎与分组分离的性能相同:

X_train = X_train.reset_index(drop=True)X_test = X_test.reset_index(drop=True)
# training a model only for female category (Sex category of 0 in this dataset)
X_train_only0 = X_train[X_train['Sex'] == 0]
X_test_only0 = X_test[X_test['Sex'] == 0]
X_only0 = X[X['Sex'] == 0]
y_train_only0 = [y_train[iter] for iter in X_train.index[
    X_train['Sex'] == 0].tolist()]
y_test_only0 = [y_test[iter] for iter in X_test.index[
    X_test['Sex'] == 0].tolist()]
# initializing an XGboost model
xgb_model = xgboost.XGBClassifier(random_state=42)
# fitting the XGboost model with training data
xgb_model.fit(X_train_only0, y_train_only0)
# calculating roc-auc of predictions
print("ROC-AUC of predictions:
    {}".format(roc_auc_score(y_test_only0,
        xgb_model.predict_proba(X_test_only0)[:, 1])))
# generate the Tree explainer
explainer_xgb = shap.TreeExplainer(xgb_model)
# extract SHAP values from the explainer object
shap_values_xgb = explainer_xgb.shap_values(X_only0)
# create a SHAP beeswarm plot (i.e. SHAP summary plot)
shap.summary_plot(shap_values_xgb, X_only0,
    plot_type="bar")

我们没有从模型中移除“性别”特征。这个特征不能对模型的性能做出贡献,因为每个模型的数据点中这个特征的值没有差异。这也在条形图中通过零 Shapely 值得到了体现:

图 7.7 – 分别在女性和男性群体上训练和测试的模型的 SHAP 摘要图

图 7.7 – 分别在女性和男性群体上训练和测试的模型的 SHAP 摘要图

根据敏感属性分离群体的这种做法,尽管有时被视为一种选择,但并不是处理公平性问题的一个理想方式。它可能不是一个有效的办法,因为模型可能高度依赖于其他敏感特征。此外,我们无法根据数据集中所有敏感属性的组合将数据分割成小块。有一些公平性工具可以帮助你不仅评估公平性和检测偏差,还可以选择一个更好地满足公平性概念的模型。

除了用于可解释性的库之外,还有一些专门为机器学习建模中的公平性检测和改进设计的 Python 库,我们将在下一部分介绍。

Python 中的公平性评估和改进

在评估模型公平性的 Python 库中,广泛使用的并不多(表 7.3)。您可以使用这些库来识别模型是否满足根据数据集中不同的敏感属性所定义的公平性:

导入和安装库名称URL
IBM AI Fairness 360aif360pypi.org/project/aif360/
Fairlearnfairlearnpypi.org/project/fairlearn/
黑盒审计BlackBoxAuditingpypi.org/project/BlackBoxAuditing/
Aequitasaequitaspypi.org/project/aequitas/
负责任 AI 工具箱responsibleaipypi.org/project/responsibleai/
负责任地responsiblypypi.org/project/responsibly/
Amazon Sagemaker Clarifysmclarifypypi.org/project/smclarify/
公平感知机器学习fairnesspypi.org/project/fairness/
偏差校正biascorrectionpypi.org/project/biascorrection/

表 7.3 – 具有机器学习公平性可用功能的 Python 库或存储库

首先,让我们加载成人收入数据集,在导入所需的库之后,并准备训练和测试集,如下所示:

# loading UCI adult income dataset# classification task to predict if people made over $50k in the 90s or not
X,y = shap.datasets.adult()
# split the data to train and test sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size = 0.3, random_state=10)
# making a dataframe out of y values with "Sex" being their indices
y_train = pd.DataFrame({'label': y_train},
    index = X_train['Sex'])
y_test = pd.DataFrame({'label': y_test},
    index = X_test['Sex'])

现在,我们可以训练和测试一个 XGBoost 模型:

xgb_model = xgboost.XGBClassifier(random_state=42)# fitting the XGboost model with training data
xgb_model.fit(X_train, y_train)
# calculating roc-auc of predictions
print("ROC-AUC of predictions:
    {}".format(roc_auc_score(y_test,
        xgb_model.predict_proba(X_test)[:, 1])))
# generating predictions for the test set
y_pred_train = xgb_model.predict(X_train)
y_pred_test = xgb_model.predict(X_test)

在这里,我们希望使用aif360根据Sex属性计算训练和测试数据中真实和预测结果的方向性指标(DIR):

# calculating disparate impact ratiodi_train_orig = disparate_impact_ratio(y_train,
    prot_attr='Sex', priv_group=1, pos_label=True)
di_test_orig = disparate_impact_ratio(y_test,
    prot_attr='Sex', priv_group=1, pos_label=True)
di_train = disparate_impact_ratio(y_train, y_pred_train,
    prot_attr='Sex', priv_group=1, pos_label=True)
di_test = disparate_impact_ratio(y_test, y_pred_test,
    prot_attr='Sex', priv_group=1, pos_label=True)

下面的分组条形图显示,预测在训练和测试集中使 DIR 变得更糟:

图 7.8 – 原始数据和预测输出的 DIR 比较

图 7.8 – 原始数据和预测输出的 DIR 比较

我们可以使用aif360来提高我们的模型向公平性发展。拒绝选项分类是一种后处理技术,在决策边界最高不确定性的置信区间内,对无特权群体给予有利结果,对特权群体给予不利结果(aif360.readthedocs.io/, Kamira 等,2012)。首先,让我们导入在 Python 中执行此操作所需的全部必要库和功能:

# importing Reject option classification, a post processing technique that gives favorable outcomes to unprivileged groups and unfavourable outcomes to# privileged groups in a confidence band around the decision boundary
# with the highest uncertainty
from aif360.sklearn.postprocessing import RejectOptionClassifierCV
# importing PostProcessingMeta,  a meta-estimator which wraps a given
# estimator with a post-processing step.
# fetching adult dataset from aif360 library
X, y, sample_weight = fetch_adult()
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)
y = pd.Series(y.factorize(sort=True)[0], index=y.index)
X = pd.get_dummies(X)

然后,我们可以使用 RejectOptionClassifierCV()aif360 中可用的成人数据集上训练和验证一个随机森林分类器。我们之所以从 XGBoost 切换到随机森林,只是为了练习不同的模型。我们需要将一个初始的随机森林模型和 RejectOptionClassifierCV()PostProcessingMeta() 对象拟合。在过程中,'sex' 被认为是敏感特征:

metric = 'disparate_impact'ppm = PostProcessingMeta(RF(n_estimators = 10,
    random_state = 42),
    RejectOptionClassifierCV('sex', scoring=metric,
        step=0.02, n_jobs=-1))
ppm.fit(X, y)

然后,我们可以绘制平衡准确率和 DIR 在网格搜索中不同尝试的图表,以显示最佳选择的参数,这是 图 7*.9* 中散点图中的星号点。蓝色点向您展示了平衡准确率和 DIR 之间的权衡的帕累托前沿:

图 7.9 – 网格搜索中平衡准确率与 DIR 的比较

图 7.9 – 网格搜索中平衡准确率与 DIR 的比较

如您所见,在这种情况下,性能和公平性之间存在权衡。但在这个例子中,性能降低不到 4% 就能将 DIR 从低于 0.4 提高到 0.8。

正如您在示例中看到的那样,我们可以使用 aif360 来评估公平性,并在性能损失很小的情况下提高我们模型的公平性。您可以使用 Python 中的其他库以类似的方式做到这一点。每个库都有其功能,用于机器学习建模中的公平性评估和改进的两个目标。

我们在本章中提供的内容只是机器学习公平性冰山的一角。但到目前为止,您已经准备好尝试不同的库和技术,并通过我们经历过的实践来了解它们。

摘要

在本章中,您学习了更多关于机器学习时代公平性的概念,以及评估公平性的指标、定义和挑战。我们讨论了诸如 sexrace 这样的敏感属性的示例代理。我们还讨论了可能的偏差来源,例如数据收集或模型训练。您还学习了如何使用 Python 库来评估模型的可解释性和公平性,以评估或改进您的模型,以及避免不仅不道德,而且可能对您的组织产生法律和财务后果的偏差。

在下一章中,您将学习测试驱动开发以及单元测试和差分测试等概念。我们还将讨论机器学习实验跟踪以及它在模型训练、测试和选择过程中如何帮助我们避免模型问题。

问题

  1. 公平性是否只取决于可观察的特征?

  2. 'sex' 的代理特征有哪些例子?

  3. 如果一个模型根据人口统计学平等是公平的,那么根据其他公平性概念,如均衡机会,它是否也是公平的?

  4. 作为两种公平性指标,人口统计学平等和均衡机会有何区别?

  5. 如果您的模型中有一个 'sex' 特征,并且您的模型对它的依赖性较低,这意味着您的模型在不同性别群体中是公平的吗?

  6. 你如何使用可解释性技术来评估你模型中的公平性?

参考文献

  • Barocas, Solon, Moritz Hardt 和 Arvind Narayanan. 机器学习中的公平性. Nips 教程 1 (2017): 2017.

  • Mehrabi, Ninareh, 等人. 机器学习中的偏差与公平性调查. ACM 计算机调查 (CSUR) 54.6 (2021): 1-35.

  • Caton, Simon 和 Christian Haas. 机器学习中的公平性:调查. arXiv 预印本 arXiv:2010.04053 (2020).

  • Pessach, Dana 和 Erez Shmueli. 机器学习中的公平性综述. ACM 计算机调查 (CSUR) 55.3 (2022): 1-44.

  • Lechner, Tosca, 等人. 公平表示的不可能性结果. arXiv 预印本 arXiv:2107.03483 (2021).

  • McCalman, Lachlan, 等人. 评估金融中的 AI 公平性. 计算机 55.1 (2022): 94-97.

  • F. Kamiran, A. Karim 和 X. Zhang,歧视感知分类的决策理论. 国际数据挖掘会议,2012.

第三部分:低错误机器学习开发和部署

在本书的这一部分,我们将提供确保机器学习模型稳健性和可靠性的基本实践,尤其是在生产环境中。我们将从采用测试驱动开发开始,说明它在模型开发过程中降低风险的关键作用。随后,我们将深入研究测试技术和模型监控的重要性,确保我们的模型在部署后仍保持可靠性。然后,我们将解释通过代码、数据和模型版本化实现机器学习可重复性的技术和挑战。最后,我们将讨论数据漂移和概念漂移的挑战,以确保在生产中有可靠的模型。

本部分包含以下章节:

  • 第八章, 使用测试驱动开发控制风险

  • 第九章, 生产环境下的测试和调试

  • 第十章, 版本控制和可重复的机器学习建模

  • 第十一章, 避免和检测数据及概念漂移

第八章:使用测试驱动开发控制风险

与创建基于我们模型的模型和技术相关的风险,例如选择不可靠的模型,是存在的。问题是,我们能否避免这些风险并更好地管理与机器学习建模相关的风险?在本章中,我们将讨论编程策略,如单元测试,这些策略不仅可以帮助我们开发和选择更好的模型,还可以降低建模相关的风险。

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

  • 驱动式开发

  • 机器学习差异测试

  • 跟踪机器学习实验

到本章结束时,您将学会如何通过单元测试和差异测试来降低不可靠建模和软件开发的风险,以及如何使用机器学习实验跟踪可靠地构建模型训练和评估的先前尝试。

技术要求

在本章中,以下要求应予以考虑,因为它们将帮助您更好地理解概念,在您的项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • pytest >= 7.2.2

    • ipytest >= 0.13.0

    • mlflow >= 2.1.1

    • aif360 >= 0.5.0

    • shap >= 0.41.0

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pandas >= 1.4.4

  • 您还需要了解模型偏差和偏差度量定义的基本知识,例如差异影响 比率DIR

您可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter08

驱动式开发用于机器学习建模

减少开发不可靠模型并将其推送到生产的风险的一种方法是通过测试驱动开发。我们的目标是设计单元测试(即设计用于测试软件单个组件的测试),以降低代码修订在同一或不同生命周期中的风险。为了更好地理解这个概念,我们需要了解单元测试是什么,以及我们如何在 Python 中设计和使用它们。

单元测试

单元测试旨在测试我们设计和编写的代码和软件中最小的组件或单元。在机器学习建模中,我们可能有多个模块负责机器学习生命周期的不同步骤,例如数据整理和清洗或模型评估。单元测试帮助我们避免错误和失误,并在编写代码时无需担心是否犯了不会被早期发现的错误。早期发现代码中的问题成本较低,并有助于我们避免错误累积,从而使调试过程更加容易。Pytest 是一个 Python 测试框架,它帮助我们设计不同的测试,包括单元测试,在机器学习编程中使用。

使用 Pytest

Pytest 是一个易于使用的 Python 库,我们可以通过以下步骤来设计单元测试:

  1. 确定我们想要为哪个组件设计单元测试。

  2. 定义一个小操作来用于测试该组件。例如,如果该模块是数据处理的一部分,测试可以设计使用一个非常小的玩具数据集,无论是真实的还是合成的。

  3. 为相应的组件设计一个以 "test_" 开头的函数。

  4. 对于我们想要设计单元测试的所有代码组件,重复 步骤 13。最好尽可能覆盖更多组件。

设计的测试可以用来测试代码中的更改。我们将在这里使用 Pytest 实践单元测试设计,针对一个计算 DIR 并使用 DIR 的输入阈值进行偏差检测以返回“无偏数据”或“有偏数据”的函数:

import pandas as pdfrom aif360.sklearn.metrics import disparate_impact_ratio
def dir_grouping(data_df: pd.DataFrame,
    sensitive_attr: str, priviledge_group,
    dir_threshold = {'high': 1.2, 'low': 0.8}):
        """
        Categorizing data as fair or unfair according to DIR
        :param data_df: Dataframe of dataset
        :param sensitive_attr: Sensitive attribute under investigation
        :priviledge_group: The category in the sensitive attribute that needs to be considered as priviledged
        :param dir_threshold:
        """
    dir = disparate_impact_ratio(data_df,
        prot_attr=sensitive_attr,
        priv_group=priviledge_group, pos_label=True)
    if dir < dir_threshold['high'] and dir > dir_threshold[
        'low']:
        assessment = "unbiased data"
    else:
        assessment = "biased data"
    return assessment

现在我们已经定义了这个函数的示例用法,用于我们的单元测试设计,我们可以选择数据集的前 100 行并计算 DIR:

# calculating DIR for a subset of adult income data in shap libraryimport shap
X,y = shap.datasets.adult()
X = X.set_index('Sex')
X_subset = X.iloc[0:100,]

根据计算出的 DIR,这个数据子集在 'Sex' 属性方面是有偏的。为了设计单元测试,我们需要导入 pytest 库。但如果你使用 Jupyter 或 Colab 笔记本进行原型设计,你可以使用 ipytest 来测试你的代码:

import pytest# you can use ipytest if you are using Jupyter or Colab notebook
import ipytest
ipytest.autoconfig()

如果我们在 Jupyter 或 Colab Notebook 中使用 pytest 并想使用 ipytest 运行测试,我们必须添加 %%ipytest -qq。然后,我们可以定义我们的单元测试函数,test_dir_grouping(),如下所示:

%%ipytest -qqdef test_dir_grouping():
    bias_assessment = dir_grouping(data_df = X_subset,
        sensitive_attr = 'Sex',priviledge_group = 1,
        dir_threshold = {'high':1.2, 'low': 0.8})
    assert bias_assessment == "biased data"

assert 命令检查 dir_grouping() 函数的结果是否为“有偏数据”,正如我们之前的分析所预期的那样,对于数据集的前 100 行。如果结果不同,则测试失败。

当你为软件组件准备好所有单元测试时,你可以在 test_dir_grouping 中运行 pytest,如前面的代码所示,在名为 test_script.py 的 Python 脚本中,你只能测试该脚本,如下所示:

pytest test_script.py

或者,你可以在特定目录中运行 pytest。如果你有一个包含许多不同模块的代码库,你可以根据你的主要函数和类的分组来组织你的测试,然后测试每个目录,如下所示:

# "testdir" could be a directory containing test scriptspytest testdir/

相反,如果你简单地运行 pytest,它将在当前目录及其子目录中执行所有名为 test_*.py\*_test.py 的文件中的所有测试:

pytest

你也可以使用 Python 解释器通过 pytest 执行测试:

python -m pytest

如果你正在使用 Jupyter 或 Colab Notebook 并且使用了 ipytest,你可以按照以下方式运行 Pytest:

ipytest.run()

现在,假设我们以这种方式执行设计的 test_dir_grouping() 函数。当测试通过时,我们将看到如下消息,它告诉我们 100% 的测试通过了。这是因为我们只测试了一个测试,并且测试通过了(图 8*.1*):

图 8.1 – 当设计的测试通过时 Pytest 的输出

图 8.1 – 当设计的测试通过时 Pytest 的输出

如果我们在dir_grouping()函数中错误地将assessment = "biased data"改为assessment = "unbiased data",我们会得到以下结果,这告诉我们 100%的测试失败了。这是因为我们只有一个测试,在这种情况下失败了(图 8*.2*):

图 8.2 – 运行 Pytest 后的失败信息

图 8.2 – 运行 Pytest 后的失败信息

pytest中的失败信息包含一些补充信息,我们可以使用这些信息来调试我们的代码。在这种情况下,它告诉我们,在test_dir_grouping()中,它试图断言test_dir_grouping()的输出,即“unbiased data”,与“biased data”。

Pytest 夹具

当我们为数据分析和学习建模编程时,我们需要使用来自不同变量或数据对象的数据,这些数据可能来自我们本地机器或云中的文件,是从数据库中查询的,或者来自测试中的 URL。夹具通过消除在测试中重复相同代码的需要来帮助我们完成这些过程。将夹具函数附加到测试上将在每个测试运行之前运行它,并将数据返回给测试。在这里,我们使用了 Pytest 文档页面上提供的夹具示例(来源:docs.pytest.org/en/7.1.x/how-to/fixtures.html)。首先,让我们定义两个非常简单的类,称为FruitFruitSalad

# Example of using Pytest fixtures available in https://docs.pytest.org/en/7.1.x/how-to/fixtures.html
class Fruit:
    def __init__(self, name):
        self.name = name
        self.cubed = False
    def cube(self):
        self.cubed = True
class FruitSalad:
    def __init__(self, *fruit_bowl):
        self.fruit = fruit_bowl
        self._cube_fruit()
    def _cube_fruit(self):
        for fruit in self.fruit:
            fruit.cube()

当我们使用pytest时,它会查看测试函数签名中的参数,并寻找与这些参数具有相同名称的夹具。Pytest 然后运行这些夹具,捕获它们返回的内容,并将这些对象作为参数传递给测试函数。我们通过使用@pytest.fixture装饰器来通知 Pytest 一个函数是夹具。在以下示例中,当我们运行测试时,test_fruit_salad请求fruit_bowl,Pytest 执行fruit_bowl并将返回的对象传递给test_fruit_salad

# Arrange@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]
def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)
    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)

这里有一些夹具的特性,可以帮助我们设计测试:

  • 夹具可以请求其他夹具。这有助于我们设计更小的夹具,甚至可以将它们作为其他夹具的一部分来构建更复杂的测试。

  • 夹具可以在不同的测试中重复使用。它们就像函数一样,在不同的测试中使用,并返回它们自己的结果。

  • 一个测试或夹具可以同时请求多个夹具。

  • 夹具可以在每个测试中请求多次。

在测试驱动开发中,我们的目标是编写生产就绪的代码,这些代码能够通过设计的单元测试。通过设计的单元测试对代码中的模块和组件进行更高覆盖,可以帮助你在安心的情况下修订与机器学习生命周期任何组件相关的代码。

在本节中,你学习了单元测试,但其他技术可以帮助我们在可靠的编程和机器学习模型开发中,例如差异测试。我们将在下一节介绍这一内容。

机器学习差异测试

差异测试试图检查同一软件的两个版本,视为基准版本和测试版本,在相同的输入上进行比较,然后比较输出。这个过程帮助我们确定输出是否相同,并识别意外的差异(Gulzar 等人,2019 年;图 8*.3*):

图 8.3 – 差异测试的简化流程图,作为测试同一数据上同一过程两个实现输出的过程

图 8.3 – 差异测试的简化流程图,作为测试同一数据上同一过程两个实现输出的过程

在差异测试中,基准版本已经过验证,并被认为是批准版本,而测试版本需要与基准版本进行比较,以产生正确的输出。在差异测试中,我们还可以旨在评估基准版本和测试版本输出之间的观察到的差异是否是预期的或可以解释的。

在机器学习建模中,我们还可以在比较同一数据上同一算法的两个不同实现时从差异测试中受益。例如,我们可以用它来比较使用scikit-learn和 Spark MLlib构建的模型,作为机器学习建模的两个不同库。如果我们需要使用scikit-learn重新创建一个模型并将其添加到我们的管道中,而原始模型是在 Spark MLlib中构建的,我们可以使用差异测试来评估输出并确保没有差异或差异是预期的(Herbold 和 Tunkel,2023 年)。表 8.1提供了scikit-learn和 Spark MLlib中可用类的一些算法示例。这种方法已被更广泛地用于比较不同深度学习框架之间的模型,例如 TensorFlow 和 PyTorch:

方法scikit-learnSpark MLlib
逻辑回归LogisticRegressionLogisticRegression
朴素贝叶斯GaussianNB, MultinomialNBNaiveBayes
决策树DecisionTree ClassifierDecisionTreeClassifier
随机森林RandomForest ClassifierRandomForestClassifier
支持向量机LinearSVCLinearSVC
多层感知器MLPClassifierMultilayerPerceptron Classifier
梯度提升GradientBoosting ClassifierGBTClassifier

表 8.1 – scikit-learn 和 Spark MLlib 中一些重叠的算法及其类名

除了单元测试和差异测试之外,实验跟踪是我们可以在机器学习项目中受益的另一种技术。

跟踪机器学习实验

跟踪我们的机器学习实验将帮助我们减少得出无效结论和选择不可靠模型的风险。机器学习中的实验跟踪是关于保存实验信息——例如,使用的数据——测试性能和用于性能评估的指标,以及用于建模的算法和超参数。以下是使用机器学习实验跟踪工具的一些重要考虑因素:

  • 你能否将工具集成到你的 持续集成/持续部署CI/CD)管道和机器学习建模框架中?

  • 你能重现你的实验吗?

  • 你能否轻松地搜索实验以找到最佳模型或具有不良或意外行为的模型?

  • 它是否会引起任何安全或隐私问题?

  • 工具是否帮助你更好地在机器学习项目中协作?

  • 工具是否允许你跟踪硬件(例如,内存)消耗?

表 8.2 中提供了常用的机器学习实验跟踪工具及其 URL:

工具URL
MLflow Trackingmlflow.org/docs/latest/tracking.html
DVCdvc.org/doc/use-cases/experiment-tracking
Weights & Biaseswandb.ai/site/experiment-tracking
Comet MLwww.comet.com/site/products/ml-experiment-tracking/
ClearMLclear.ml/clearml-experiment/
Polyaxonpolyaxon.com/product/#tracking
TensorBoardwww.tensorflow.org/tensorboard
Neptune AIneptune.ai/product/experiment-tracking
SageMakeraws.amazon.com/sagemaker/experiments/

表 8.2 – 教学机器学习实验的工具示例

在这里,我们想要在 Python 中练习 MLflow Tracking。首先,我们需要导入所需的库:

import pandas as pdimport numpy as np
from sklearn.metrics import mean_squared_error, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier as RF
from sklearn.datasets import load_breast_cancer
import mlflow
import mlflow.sklearn
np.random.seed(42)

然后,我们必须定义一个函数来评估我们想要测试的模型的结果:

def eval_metrics(actual, pred, pred_proba):    rmse = np.sqrt(mean_squared_error(actual, pred))
    roc_auc = roc_auc_score(actual, pred_proba)
    return rmse, roc_auc

接下来,我们必须从 scikit-learn 加载乳腺癌数据集以进行建模:

X, y = load_breast_cancer(return_X_y=True)# split the data into training and test sets. (0.7, 0.3) split
X_train, X_test, y_train, y_test = train_test_split(X, y,
    test_size = 0.3, random_state=42)

现在,我们准备使用 mlflow 定义一个实验:

experiment_name = "mlflow-randomforest-cancer"existing_exp = mlflow.get_experiment_by_name(
    experiment_name)
if not existing_exp:
    experiment_id = mlflow.create_experiment(
        experiment_name, artifact_location="...")
else:
    experiment_id = dict(existing_exp)['experiment_id']
mlflow.set_experiment(experiment_name)

现在,我们必须检查三个不同的决策树数量,或者三个不同的估计器数量,在加载的乳腺癌数据集上构建、训练和测试三个不同的随机森林模型。这三个运行的所有信息都将存储在指定的实验中,但作为不同的运行。正如你可以在下面的代码中看到的那样,我们使用了 mlflow 的不同功能:

  • mlflow.start_run:作为实验的一部分启动一个运行

  • mlflow.log_param:为了记录模型作为超参数的估计器数量

  • mlflow.log_metric:为了记录在定义的测试集上模型性能的计算指标

  • mlflow.sklearn.log_model:为了记录模型:

for idx, n_estimators in enumerate([5, 10, 20]):    rf = RF(n_estimators = n_estimators, random_state = 42)
    rf.fit(X_train, y_train)
    pred_probs = rf.predict_proba(X_test)
    pred_labels = rf.predict(X_test)
    # calculating rmse and roc-auc for the randorm forest
    # model predictions on the test set
    rmse, roc_auc = eval_metrics(actual = y_test,
        pred = pred_labels,pred_proba = [
            iter[1]for iter in pred_probs])
    # start mlflow
    RUN_NAME = f"run_{idx}"
    with mlflow.start_run(experiment_id=experiment_id,
        run_name=RUN_NAME) as run:
            # retrieve run id
            RUN_ID = run.info.run_id
        # track parameters
        mlflow.log_param("n_estimators", n_estimators)
        # track metrics
        mlflow.log_metric("rmse", rmse)
        # track metrics
        mlflow.log_metric("roc_auc", roc_auc)
        # track model
        mlflow.sklearn.log_model(rf, "model")

我们还可以检索已存储的实验,如下所示:

from mlflow.tracking import MlflowClientesperiment_name = "mlflow-randomforest-cancer"
client = MlflowClient()
# retrieve experiment information
experiment_id = client.get_experiment_by_name(
    esperiment_name).experiment_id

然后,我们可以获取该实验中不同运行的详细信息:

# retrieve runs information (parameter: 'n_estimators',    metric: 'roc_auc')
experiment_info = mlflow.search_runs([experiment_id])
# extracting run ids for the specified experiment
runs_id = experiment_info.run_id.values
# extracting parameters of different runs
runs_param = [client.get_run(run_id).data.params[
    "n_estimators"] for run_id in runs_id]
# extracting roc-auc across different runs
runs_metric = [client.get_run(run_id).data.metrics[
    "roc_auc"] for run_id in runs_id]

我们可以根据用于模型测试的指标来识别最佳运行,例如本例中的 ROC-AUC:

df = mlflow.search_runs([experiment_id],    order_by=["metrics.roc_auc"])
best_run_id = df.loc[0,'run_id']
best_model_path = client.download_artifacts(best_run_id,
    "model")
best_model = mlflow.sklearn.load_model(best_model_path)
print("Best model: {}".format(best_model))

这将产生以下输出:

Best mode: RandomForestClassifier(n_estimators=5,    random_state=42)

如果需要,我们还可以删除运行或实验的运行,如下所示。但你需要确保你确实希望删除此类信息:

# delete runs (make sure you are certain about deleting the runs)for run_id in runs_id:
    client.delete_run(run_id)
# delete experiment (make sure you are certain about deleting the experiment)
client.delete_experiment(experiment_id)

在本节中,你学习了在机器学习环境中进行实验跟踪。你将在下一两章中学习更多关于你在机器学习项目中用于风险控制的技术。

摘要

在本章中,你学习了使用单元测试进行驱动开发以控制你的机器学习开发项目中的风险。你学习了使用 pytest 库在 Python 中的单元测试。我们还简要回顾了差分测试的概念,这有助于你比较你的机器学习模块和软件的不同版本。稍后,你学习了模型实验跟踪作为一项重要工具,它不仅有助于你的模型实验和选择,还有助于你在机器学习项目中进行风险控制。你练习了使用 mlflow 作为广泛使用的机器学习实验跟踪工具之一。现在,你知道如何通过测试驱动开发和实验跟踪来开发可靠的模型和编程模块。

在下一章中,你将学习关于测试模型、评估其质量以及监控其在生产中的性能的策略。你将了解模型监控、集成测试和模型管道及基础设施测试的实用方法。

问题

  1. pytest 如何帮助你开发机器学习项目中的代码模块?

  2. pytest fixtures 如何帮助你使用 pytest

  3. 什么是差分测试,何时需要它?

  4. 什么是 mlflow 以及它如何帮助你在机器学习建模项目中?

参考文献

  • Herbold, Steffen, 和 Steffen Tunkel. 机器学习中的差分测试:超越深度学习的分类算法分析. 实证软件工程 28.2 (2023): 34。

  • Lichman, M. (2013). UCI 机器学习仓库 [archive.ics.uci.edu/ml]。Irvine, CA:加州大学信息与计算机科学学院。

  • Gulzar, Muhammad Ali, Yongkang Zhu, 和 Xiaofeng Han. 差分测试的感知与实践. 2019 IEEE/ACM 第 41 届国际软件工程会议:软件工程实践(ICSE-SEIP)。IEEE,2019。

第九章:生产测试和调试

您可能对训练和测试机器学习模型感到兴奋,而没有考虑到模型在生产中的意外行为以及您的模型如何融入更大的技术。大多数学术课程不会详细介绍测试模型、评估其质量以及在生产前和生产中监控其性能的策略。在本章中,我们将回顾测试和调试生产中模型的重要概念和技术。

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

  • 基础设施测试

  • 机器学习管道的集成测试

  • 监控和验证实时性能

  • 模型断言

到本章结束时,您将了解基础设施和集成测试的重要性,以及模型监控和断言。您还将了解如何使用 Python 库,以便在项目中从中受益。

技术要求

在本章中,以下要求应予以考虑,因为它们将帮助您更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pytest >= 7.2.2

  • 您还必须具备机器学习生命周期的基础知识。

您可以在 GitHub 上找到本章的代码文件,地址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter09

基础设施测试

基础设施测试是指验证和验证部署、管理和扩展机器学习模型所涉及的各个组件和系统的过程。这包括测试软件、硬件和其他构成支持机器学习工作流程的基础设施的资源。机器学习中的基础设施测试有助于您确保模型得到有效训练、部署和维护。它为您在生产环境中提供可靠的模型。定期的基础设施测试可以帮助您及早发现和修复问题,并降低部署和生产阶段失败的风险。

这里是机器学习基础设施测试的一些重要方面:

  • 数据管道测试:这确保了负责数据收集、选择和整理的数据管道正在正确且高效地工作。这有助于保持训练、测试和部署机器学习模型的数据质量和一致性。

  • 模型训练和评估:这验证了模型训练过程的功能,例如超参数调整和模型评估。这个过程消除了训练和评估中的意外问题,以实现可靠和负责任的模式。

  • 模型部署和托管:这项测试用于检查在生产环境中部署训练好的模型的过程,确保服务基础设施,如 API 端点,能够正确工作并能处理预期的请求负载。

  • 监控和可观察性:这项测试用于检查提供对机器学习基础设施性能和行为洞察的监控和日志系统。

  • 集成测试:这项测试验证机器学习基础设施的所有组件,如数据管道、模型训练系统和部署平台,是否能够无缝且无冲突地协同工作。

  • 可伸缩性测试:这项测试评估基础设施根据不断变化的需求(如增加的数据量、更高的用户流量或更复杂的模型)进行扩展或缩减的能力。

  • 安全和合规性测试:这项测试确保机器学习基础设施满足必要的网络安全要求、数据保护法规和隐私标准。

现在你已经了解了基础设施测试的重要性和好处,你准备好学习相关的工具,这些工具可以帮助你在模型部署和基础设施管理中。

基础设施即代码工具

基础设施即代码IaC)和配置管理工具,如ChefPuppetAnsible,可以用于自动化软件和硬件基础设施的部署、配置和管理。这些工具可以帮助我们确保在不同环境中的一致性和可靠性。让我们了解 Chef、Puppet 和 Ansible 是如何工作的,以及它们如何能帮助你在项目中:

  • Chef (www.chef.io/products/chef-infrastructure-management):Chef 是一个开源配置管理工具,它依赖于客户端-服务器模型,其中 Chef 服务器存储所需的配置,Chef 客户端将其应用于节点。

  • Puppet (www.puppet.com/):Puppet 是另一个开源配置管理工具,它以客户端-服务器模式或作为独立应用程序工作。Puppet 通过定期从 Puppet 主服务器拉取配置来在节点上强制执行所需的配置。

  • Ansible (www.ansible.com/):Ansible 是一个开源且易于使用的配置管理、编排和自动化工具,它与节点通信并应用配置。

这些工具主要关注基础设施管理和自动化,但它们也具有模块或插件,可以执行基础设施的基本测试和验证。

基础设施测试工具

Test Kitchen、ServerSpec 和 InSpec 是我们可以使用的基础设施测试工具,以验证和验证我们基础设施所需配置和行为:

  • Test Kitchen (github.com/test-kitchen/test-kitchen):Test Kitchen 是一个主要用于与 Chef 一起使用的集成测试框架,但也可以与其他 IaC 工具(如 Ansible 和 Puppet)一起工作。它允许你在不同的平台和配置上测试你的基础设施代码。Test Kitchen 在各种平台上创建临时实例(使用 Docker 或云提供商等驱动程序),合并你的基础设施代码,并对配置的实例运行测试。你可以使用 Test Kitchen 与不同的测试框架(如 ServerSpec 或 InSpec)一起定义你的测试。

  • ServerSpec (serverspec.org/):ServerSpec 是一个用于基础设施的 行为驱动开发BDD)测试框架。它允许你用人类可读的语言编写测试。ServerSpec 通过在目标系统上执行命令并检查输出与预期结果是否一致来测试你基础设施的期望状态。你可以使用 ServerSpec 与 Test Kitchen 或其他 IaC 工具一起确保你的基础设施配置正确。

  • InSpec (github.com/inspec/inspec):InSpec 是由 Chef 开发的开源基础设施测试框架。它以人类可读的语言定义测试和合规性规则。你可以独立运行 InSpec 测试,或者与 Test Kitchen、Chef 或其他 IaC 平台等工具一起运行。

这些工具确保我们的 IaC 和配置管理设置在部署前按预期工作,以实现跨不同环境的一致性和可靠性。

使用 Pytest 进行基础设施测试

我们还可以使用 Pytest,这是我们上一章中用于单元测试的工具,也可以用于基础设施测试。假设我们编写了应该在名为 test_infrastructure.py 的 Python 文件中以 test_ 前缀开始的测试函数。我们可以使用 Python 库(如 paramikorequestssocket)与我们的基础设施交互(例如,进行 API 调用、连接到服务器等)。例如,我们可以测试 Web 服务器是否以状态码 200 响应:

import requestsdef test_web_server_response():
    url = "http://your-web-server-url.com"
    response = requests.get(url)
    assert response.status_code == 200,
        f"Expected status code 200,
        but got {response.status_code}"

然后,我们可以运行上一章中解释的测试。

除了基础设施测试之外,其他技术可以帮助你为成功部署模型做准备,例如集成测试,我们将在下一章中介绍。

机器学习管道的集成测试

当我们训练一个机器学习模型时,我们需要评估它与其所属的更大系统其他组件的交互效果。集成测试帮助我们验证模型在整体应用程序或基础设施中是否正确工作,并满足预期的性能标准。在我们的机器学习项目中,以下是一些重要的集成测试组件:

  • 测试数据管道:我们需要评估在模型训练之前的数据预处理组件(如数据整理)在训练和部署阶段之间的一致性。

  • 测试 API:如果我们的机器学习模型通过 API 公开,我们可以测试 API 端点以确保它正确处理请求和响应。

  • 测试模型部署:我们可以使用集成测试来评估模型的部署过程,无论它是作为独立服务、容器内还是嵌入在应用程序中部署。这个过程有助于我们确保部署环境提供必要的资源,例如 CPU、内存和存储,并且模型在需要时可以更新。

  • 测试与其他组件的交互:我们需要验证我们的机器学习模型与数据库、用户界面或第三方服务无缝工作。这可能包括测试模型预测在应用程序中的存储、显示或使用方式。

  • 测试端到端功能:我们可以使用模拟真实场景和用户交互的端到端测试来验证模型的预测在整体应用程序的上下文中是准确的、可靠的和有用的。

我们可以从集成测试中受益,以确保在实际应用程序中的平稳部署和可靠运行。我们可以使用几个工具和库来为我们的 Python 机器学习模型创建健壮的集成测试。表 9.1显示了集成测试的一些流行工具:

工具简要描述URL
Pytest一个在 Python 中广泛用于单元和集成测试的框架docs.pytest.org/en/7.2.x/
Postman一个 API 测试工具,用于测试机器学习模型与 RESTful API 之间的交互www.postman.com/
Requests一个 Python 库,通过发送 HTTP 请求来测试 API 和服务requests.readthedocs.io/en/latest/
Locust一个允许你模拟用户行为并测试机器学习模型在各种负载条件下的性能和可扩展性的负载测试工具locust.io/
Selenium一个浏览器自动化工具,你可以用它来测试利用机器学习模型的 Web 应用程序的端到端功能www.selenium.dev/

表 9.1 – 集成测试的流行工具

使用 pytest 进行集成测试

在这里,我们想要练习使用pytest对一个具有两个组件的简单 Python 应用程序进行集成测试:一个数据库和一个服务,它们都从数据库中检索数据。让我们假设我们有database.pyservice.py脚本文件:

database.py:

class Database:    def __init__(self):
        self.data = {"users": [{"id": 1,
            "name": "John Doe"},
            {"id": 2, "name": "Jane Doe"}]}
    def get_user(self, user_id):
        for user in self.data["users"]:
            if user["id"] == user_id:
                return user
            return None

service.py:

from database import Databaseclass UserService:
    def __init__(self, db):
        self.db = db
    def get_user_name(self, user_id):
        user = self.db.get_user(user_id)
        if user:
            return user["name"]
        return None

现在,我们将使用 pytest 编写一个集成测试,以确保 UserService 组件与 Database 组件正确工作。首先,我们需要在名为 test_integration.py 的测试脚本文件中编写我们的测试,如下所示:

import pytestfrom database import Database
from service import UserService
@pytest.fixture
def db():
    return Database()
@pytest.fixture
def user_service(db):
    return UserService(db)
def test_get_user_name(user_service):
    assert user_service.get_user_name(1) == "John Doe"
    assert user_service.get_user_name(2) == "Jane Doe"
    assert user_service.get_user_name(3) is None

定义好的 test_get_user_name 函数通过检查 get_user_name 方法是否为不同的用户 ID 返回正确的用户名来测试 UserServiceDatabase 组件之间的交互。

要运行测试,我们可以在终端中执行以下命令:

pytest test_integration.py

使用 pytest 和 requests 进行集成测试

我们可以将 requestspytest Python 库结合起来,对我们的机器学习 API 进行集成测试。我们可以使用 requests 库发送 HTTP 请求,并使用 pytest 库编写测试用例。假设我们有一个机器学习 API,其端点如下:

POST http://mldebugging.com/api/v1/predict

在这里,API 接受一个包含输入数据的 JSON 有效负载:

{    "rooms": 3,
    "square_footage": 1500,
    "location": "suburban"
}

这将返回一个包含预测价格的 JSON 响应:

{    "predicted_price": 700000
}

现在,我们需要创建一个名为 test_integration.py 的测试脚本文件:

import requestsimport pytest
API_URL = "http://mldebugging.com/api/v1/predict"
def test_predict_house_price():
    payload = {
        "rooms": 3,
        "square_footage": 1500,
        "location": "suburban"
    }
    response = requests.post(API_URL, json=payload)
    assert response.status_code == 200
    assert response.headers["Content-Type"] == "application/json"
    json_data = response.json()
    assert "predicted_price" in json_data
    assert isinstance(json_data["predicted_price"],
        (int, float))

要运行测试,我们可以在终端中执行以下命令:

pytest test_integration.py

在这个例子中,我们定义了一个名为 test_predict_house_price 的测试函数,该函数向 API 发送 POST 请求(即用于将数据提交到服务器以创建或更新资源的 HTTP 方法),并将输入数据作为 JSON 有效负载。然后,测试函数检查 API 响应的状态码、内容类型和预测价格值。如果您想尝试使用您拥有的真实 API,请将示例 URL 替换为实际的 API 端点。

除了本章中提到的测试策略外,您还可以通过模型监控和断言来确保在生产环境中成功部署和可靠的模型。

监控和验证实时性能

在部署期间,我们可以使用监控和日志记录机制来跟踪模型的性能并检测潜在问题。我们可以定期评估已部署的模型,以确保它继续满足性能标准或其他标准,例如无偏见,这是我们为其定义的。我们还可以利用模型监控的信息,根据需要更新或重新训练模型。以下是关于部署前和在生产中建模之间差异的三个重要概念:

  • 数据方差:用于模型训练和测试的数据会经过数据整理和所有必要的清理和重新格式化步骤。然而,提供给已部署模型的(即从用户到模型的数据)可能不会经过相同的数据处理过程,这会导致生产中模型结果出现差异。

  • 数据漂移:如果生产中特征或独立变量的特征和意义与建模阶段的不同,就会发生数据漂移。想象一下,你使用第三方工具为人们的健康或财务状况生成分数。该工具背后的算法可能会随时间改变,当你的模型在生产中使用时,其范围和意义将不会相同。如果你没有相应地更新你的模型,那么你的模型将不会按预期工作,因为特征值的含义在用于训练的数据和部署后的用户数据之间将不同。

  • 概念漂移:概念漂移是指输出变量定义的任何变化。例如,由于概念漂移,训练数据和生产之间的实际决策边界可能不同,这意味着在训练中付出的努力可能导致在生产中远离现实的决策边界。

除了上一章中介绍的 MLflow 之外,还有 Python 和库工具(如 表 9.2 中所示),你可以使用这些工具来监控机器学习模型的表现、I/O 数据和基础设施,帮助你维护生产环境中的模型质量和可靠性:

工具简要描述URL
Alibi Detect一个专注于异常值、对抗性和漂移检测的开源 Python 库github.com/SeldonIO/alibi-detect
Evidently一个开源 Python 库,用于分析和监控机器学习模型,提供各种模型评估技术,如数据漂移检测和模型性能监控github.com/evidentlyai/evidently
ELK StackElasticsearch, Logstash, and Kibana (ELK) 是一个流行的用于收集、处理和可视化来自各种来源(包括机器学习模型)的日志和指标的工具栈www.elastic.co/elk-stack
WhyLabs一个为机器学习模型提供可观察性和监控的平台whylabs.ai/

表 9.2 – 机器学习模型监控和漂移检测的流行工具

我们还可以从一些统计和可视化技术中受益,用于检测和解决数据和概念漂移。以下是一些用于数据漂移评估的方法示例:

  • 统计测试:我们可以使用假设检验,如 Kolmogorov-Smirnov 测试卡方测试Mann-Whitney U 测试,来确定输入数据的分布是否在时间上发生了显著变化。

  • 分布指标:我们可以使用分布指标,如均值、标准差、分位数和其他汇总统计量,来比较训练数据和生产中的新数据。这些指标中的显著差异可能表明数据漂移。

  • 可视化:我们可以使用直方图、箱线图或散点图等可视化技术来展示训练数据和生产中新数据的输入特征,以帮助识别数据分布的变化。

  • 特征重要性:我们可以监控特征重要性值的变化。如果新数据中的特征重要性与训练数据中的特征重要性有显著差异,这可能表明数据漂移。

  • 距离度量:我们可以使用诸如Kullback-Leibler 散度Jensen-Shannon 散度等距离度量来衡量训练数据与新数据分布之间的差异。

模型断言是另一种技术,正如你接下来将要学习的,它可以帮助你构建和部署可靠的机器学习模型。

模型断言

我们可以在机器学习建模中使用传统的编程断言来确保模型按预期行为。模型断言可以帮助我们早期发现问题,例如输入数据漂移或其他可能影响模型性能的意外行为。我们可以将模型断言视为一组在模型训练、验证甚至部署期间进行检查的规则,以确保模型的预测满足预定义的条件。模型断言可以从许多方面帮助我们,例如检测模型或输入数据的问题,使我们能够在它们影响模型性能之前解决它们。它们还可以帮助我们保持模型性能。以下是一些模型断言的示例:

  • 输入数据断言:这些可以检查输入特征是否在预期的范围内或具有正确的数据类型。例如,如果一个模型根据房间数量预测房价,你可能会断言房间数量始终是正整数。

  • 输出数据断言:这些可以检查模型的预测是否满足某些条件或约束。例如,在二元分类问题中,你可能会断言预测的概率在 0 到 1 之间。

让我们通过一个简单的 Python 中模型断言的例子来了解。在这个例子中,我们将使用scikit-learn中的简单线性回归模型,使用玩具数据集根据房间数量预测房价。首先,让我们创建一个玩具数据集并训练线性回归模型:

import numpy as npfrom sklearn.linear_model import LinearRegression
# Toy dataset with number of rooms and corresponding house prices
X = np.array([1, 2, 3, 4, 5]).reshape(-1, 1)
y = np.array([100000, 150000, 200000, 250000, 300000])
# Train the linear regression model
model = LinearRegression()
model.fit(X, y)

现在,让我们定义我们的模型断言,以便它们执行以下操作:

  1. 检查输入(房间数量)是否为正整数。

  2. 检查预测的房价是否在预期范围内。

下面是执行这些操作的代码:

def assert_input(input_data):    assert isinstance(input_data, int),
        "Input data must be an integer"
    assert input_data > 0, "Number of rooms must be positive"
def assert_output(predicted_price, min_price, max_price):
    assert min_price <= predicted_price <= max_price,
        f"Predicted price should be between {min_price} and 
        {max_price}"

现在,我们可以使用定义好的模型断言函数,如下所示:

# Test the assertions with example input and output datainput_data = 3
assert_input(input_data)
predicted_price = model.predict([[input_data]])[0]
assert_output(predicted_price, 50000, 350000)

assert_input函数检查输入数据(即房间数量)是否为整数且为正数。assert_output函数检查预测的房价是否在指定的范围内(例如,在本例中为 50,000 至 350,000)。前面的代码没有给出任何AssertionError断言,因为它符合模型断言函数中定义的标准。假设我们不是使用整数3,而是使用一个字符串,如下所示:

input_data = '3'assert_input(input_data)

这里,我们得到以下AssertionError

AssertionError: Input data must be an integer

假设我们定义了assert_output的输出范围,使其在50000150000之间,并使用具有3个卧室的房屋模型预测,如下所示:

input_data = 3predicted_price = model.predict([[input_data]])[0]
assert_output(predicted_price, 50000, 150000)

我们将得到以下AssertionError

AssertionError: Predicted price should be between 50000 and 150000

模型断言是另一种技术,与模型监控并列,有助于确保我们模型的可靠性。

有了这些,我们就结束了这一章。

摘要

在本章中,你学习了测试驱动开发的重要概念,包括基础设施和集成测试。你学习了实现这两种类型测试的可用工具和库。我们还通过示例学习了如何使用pytest库进行基础设施和集成测试。你还学习了模型监控和模型断言作为评估我们模型在生产和生产环境中的行为之前和之后的两个其他重要主题。这些技术和工具帮助你设计策略,以便你在生产环境中成功部署并拥有可靠的模型。

在下一章中,你将了解可重复性,这是正确机器学习建模中的一个重要概念,以及你如何可以使用数据和模型版本控制来实现可重复性。

问题

  1. 你能解释数据漂移和概念漂移之间的区别吗?

  2. 模型断言如何帮助你开发可靠的机器学习模型?

  3. 集成测试的组件有哪些例子?

  4. 我们如何使用 Chef、Puppet 和 Ansible?

参考文献

  • Kang, Daniel, et al. 模型监控与改进的模型断言. 机器学习与系统会议论文集 2 (2020): 481-496.

第十章:版本控制和可重复性机器学习建模

可重复性是一个重要的主题,有助于机器学习开发者回到机器学习生命周期的不同阶段,并识别模型改进的机会。通过访问通过机器学习生命周期生成的不同版本的数据和模型,可以帮助我们提高项目的可重复性。

在本章中,你将了解机器学习建模中可重复性的意义和重要性。你将学习如何在机器学习管道中集成数据版本控制,以帮助你在项目中实现更有效的协作,并在模型中实现可重复性。你还将了解模型版本化的不同方面以及将其集成到管道中的工具。

我们将涵盖以下主题:

  • 机器学习中的可重复性

  • 数据版本控制

  • 模型版本控制

到本章结束时,你将学会如何在 Python 中利用数据和模型版本控制来为你的建模项目实现可重复性。

技术要求

以下为本章的要求,将帮助你更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • pandas >= 1.4.4

    • sklearn >= 1.2.2

  • DVC >= 1.10.0

  • 你还应该对机器学习生命周期有基本了解

你可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter10

机器学习中的可重复性

机器学习项目中缺乏可重复性可能会导致资源浪费,并降低你在研究项目中模型和发现的可信度。可重复性在这个上下文中并不是唯一的术语;还有两个其他的关键术语:可重复性可复制性。我们不想深入探讨这些差异的细节。相反,我们想要在这个书中使用可重复性的定义。我们将机器学习中的可重复性定义为不同个人或科学家和开发者团队使用与原始报告或研究中报告的相同数据集、方法和开发环境获得相同结果的能力。我们可以通过适当共享代码、数据、模型参数和超参数以及其他相关信息来确保可重复性,这允许其他人验证并基于我们的发现进行构建。让我们通过两个例子来更好地理解可重复性的重要性。

一家生物技术公司的科学家们试图重现 53 项癌症研究的发现(Begley et al., 2012)。但他们只能重现其中 6 项研究的成果。这些研究并不一定是在机器学习可重复性的背景下进行的,但它突出了科学研究中可重复性的重要性以及基于不可重复发现做出决策或进一步研究和开发的潜在后果。

另一个在数据分析和数据驱动发现背景下强调可重复性重要性的例子是所谓的Reinhart-Rogoff Excel 错误(Reinhart, C.,and Rogoff, K.,2010)。2010 年,经济学家 Carmen Reinhart 和 Kenneth Rogoff 发表了一篇论文,提出高公共债务与经济增长之间存在负相关关系。这篇论文影响了全球的经济政策。然而,2013 年,其他研究人员发现他们在 Excel 计算中存在错误,这显著影响了结果。但后来,有人认为错误不是结论背后的驱动因素(Maziarz, 2017)。在这里,我们不想关注他们的发现,而是想强调分析的可重复性可以消除任何进一步的争论,无论原始分析中是否存在错误。

以下三个概念可以帮助您在机器学习建模项目中实现可重复性:

  • 代码版本控制:能够访问机器学习生命周期任何阶段的代码版本对于重复分析或训练和评估过程至关重要

  • 数据版本控制:为了实现可重复性,我们需要访问机器学习生命周期任何阶段使用的版本数据,例如训练和测试

  • 模型版本控制:拥有一个具有冻结参数且在初始化、评估或其他建模过程中的随机性为零的模型版本,有助于您消除不可重复性的风险

我们在第一章“超越代码调试”中简要介绍了代码版本控制。在这里,我们将专注于数据和模型版本控制,以帮助您设计可重复的机器学习模型。

数据版本控制

机器学习生命周期中,我们有不同的阶段,从数据收集和选择到数据整理和转换,数据在这些过程中逐步准备以供模型训练和评估。数据版本控制有助于我们在这些过程中保持数据完整性和可重复性。数据版本控制是跟踪和管理数据集变化的过程。它涉及记录数据的不同版本或迭代,使我们能够在需要时访问和比较先前状态或恢复早期版本。通过确保更改得到适当记录和版本控制,我们可以降低数据丢失或不一致的风险。

有一些数据版本控制工具可以帮助我们管理和跟踪我们想要用于机器学习建模或评估模型可靠性和公平性的数据变化。以下是一些流行的数据版本控制工具:

  • MLflow:我们在前面的章节中介绍了 MLflow 用于实验跟踪和模型监控,但你也可以用它来进行数据版本控制([mlflow.org/](mlflow.org/))

  • 数据版本控制DVC):这是一个开源的数据版本控制系统,用于管理数据、代码和机器学习模型。它旨在处理大型数据集,并与 Git 集成([dvc.org/](dvc.org/))

  • Pachyderm:这是一个提供机器学习工作流程中可重复性、来源和可扩展性的数据版本控制平台([www.pachyderm.com/](www.pachyderm.com/))

  • Delta Lake:这是一个为 Apache Spark 和大数据工作负载提供数据版本控制的开源存储层([delta.io/](delta.io/))

  • Git Large File StorageGit-LFS):这是 Git 的一个扩展,允许对大型文件进行版本控制,如数据文件或模型,同时与代码一起版本控制([git-lfs.github.com/](git-lfs.github.com/))

这些工具中的每一个都为你提供了不同的数据版本控制能力。你可以根据数据的大小、项目的性质以及与其他工具集成的期望水平来选择满足你需求的工具。

这里是一个使用 DVC 进行数据版本控制的 Python 示例。在安装 DVC 后,你可以在终端中写入以下命令来初始化它:

dvc init

这将创建一个.dvc目录并设置必要的配置。现在,让我们创建一个小的 DataFrame 并将其保存为 Python 中的 CSV 文件:

import pandas as pd# create a sample dataset
data_df = pd.DataFrame({'feature 1': [0.5, 1.2, 0.4, 0.8],
    'feature 2': [1.1, 1.3, 0.6, 0.1]})
# save the dataset to a CSV file
data_df.to_csv('dataset.csv', index=False)

现在,我们可以将dataset.csv文件添加到 DVC 中并提交更改,类似于使用 Git 提交代码更改:

dvc add dataset.csvgit add dataset.csv.dvc .gitignore
git commit -m "add initial dataset"

这将创建一个data.csv.dvc文件来跟踪数据集的版本,并将data.csv添加到.gitignore中,以便 Git 不跟踪实际的数据文件。现在,我们可以按如下方式修改数据集并使用相同的名称保存它:

# Add a new column to the datasetdata_df['feature 3'] = [0.05, 0.6, 0.4, 0.9]
# Save the modified dataset to the same CSV file
data_df.to_csv('dataset.csv', index=False)

我们也可以提交更改并将其保存为不同的版本:

dvc add dataset.csvgit add dataset.csv.dvc
git commit -m "update dataset with new feature column"

现在我们有了dataset.csv文件的两个版本,我们可以在需要时使用以下命令在终端切换到之前的版本或最新版本的数据集:

# go back to the previous version of the datasetgit checkout HEAD^
dvc checkout
# return to the latest version of the dataset
git checkout master
dvc checkout

但如果你有很多相同文件或数据的版本,你可以使用 DVC(Data Version Control)作为其一部分的其他简单命令。

除了对数据进行版本控制外,我们还需要在整个开发周期中跟踪和管理模型的不同版本。我们将在下一章中介绍这一点。

模型版本控制

生产的模型是经过一系列实验和模型修改的最终结果,包括不同版本的训练和测试数据,以及不同的机器学习方法和相应的超参数。模型版本化帮助我们确保对模型所做的更改是可追溯的,有助于在机器学习项目中建立可重复性。它确保了在特定时间点可以轻松地重现每个模型的版本,通过提供模型参数、超参数和训练数据的完整快照。它允许我们在新部署的模型出现问题时轻松回滚到先前的版本,或者恢复可能被无意中修改或删除的旧版本。

让我们通过一个非常简单的例子来更好地理解模型版本化的必要性。图 10.1展示了具有五个估计器或决策树的随机森林模型的不同最大深度。如果我们简单地改变用于将数据分割成训练集和测试集的随机状态,使用scikit-learn中的train_test_split(),并对RandomForestClassifier()模型进行模型初始化,我们得到不同的对数损失值和随机森林模型中树的最大深度的依赖性:

图 10.1 – 使用不同的随机状态进行建模和数据分割,从乳腺癌数据集中分离出的训练集和验证集的对数损失

图 10.1 – 使用不同的随机状态进行建模和数据分割,从乳腺癌数据集中分离出的训练集和验证集的对数损失

这是一个小例子,用以展示如果我们的模型没有进行版本控制,这样的简单变化可能会对我们的机器学习建模产生极大的影响。当我们使用实验跟踪工具,如 MLflow 时,我们可以访问所选模型的全部跟踪信息。

为了对模型进行版本控制,我们需要确保以下几点:

  • 我们可以访问相应模型的参数的保存版本

  • 其他必要信息,如模型超参数,已记录或保存以供模型重新训练

  • 需要与模型参数一起用于推理或甚至重新训练和测试的代码已进行版本控制

  • 具有随机性的过程,如模型初始化和训练及测试数据分割,有指定的随机状态或种子

存储模型及其相关文档的方式有很多种。例如,您可以使用序列化库如pickle单独存储模型,或者与 DVC (dvc.org/doc/api-reference/open) 结合使用,如下所示:

with dvc.api.open(model_path, mode='w', remote=remote_url) as f:     pickle.dump(model, f)

为了此目的,您需要指定一个用于保存模型的本地路径,使用pickle.dump,以及使用 DVC 进行模型版本化的远程路径。

摘要

在本章中,你学习了机器学习建模中可重现性的意义和重要性。你还学习了数据版本化和模型版本化,这些有助于我们开发更可靠和可重现的模型和数据分析结果。接下来,你学习了可用于版本化数据和模型的不同的工具和 Python 库。通过本章介绍的概念和实践,你已准备好确保你的机器学习项目中的可重现性。

在下一章中,你将学习如何使用技术来避免和消除数据漂移和概念漂移,这两者构成了模型在部署前后行为差异的两个方面。

问题

  1. 请列举三个可用于数据版本化的工具的例子?

  2. 当你使用 DVC 等工具生成相同数据的不同版本时,你是否需要用不同的名称保存它们?

  3. 你能提供一个例子,说明你使用相同的方法、训练和评估数据,但得到不同的训练和评估性能吗?

参考文献

  • Reinhart, C.,& Rogoff, K. (2010b). 债务与增长的回顾. VOX. CEPRs 政策门户。检索日期:2015 年 9 月 18 日。

  • Reinhart, C.,& Rogoff, K. (2010a). 债务时期的增长.美国经济评论,100,573–578.10.1257/aer.100.2.573。

  • Maziarz, Mariusz. Reinhart-Rogoff 争议作为“新兴相反结果”现象的一个实例.经济方法论杂志,24.3 (2017):213-225。

  • Begley, C. G.,& Ellis, L. M. (2012). 药物开发:提高临床前癌症研究标准.自然,483(7391),531-533。

  • 计算机协会(2016 年).工件审查和徽章.可在www.acm.org/publications/policies/artifact-review-badging在线获取(访问日期:2017 年 11 月 24 日)。

  • Plesser, Hans E. 可重现性 vs. 可复制性:一个混乱术语的简要历史.神经信息学前沿,11 (2018):76。

  • Pineau, J.,Vincent, M.,Larochelle, H.,& Bengio, Y. (2020). 提高机器学习研究可重现性(来自 NeurIPS 2019 可重现性计划的一份报告). arXiv 预印本 arXiv:2003.12206。

  • Raff, E.,Lemire, D.,& Nicholas, C. (2019). 机器学习中算法稳定性的新度量.机器学习研究杂志,20(168),1-32。

  • Gundersen, O. E.,& Kjensmo, S. (2018). 人工智能中的最新技术:可重现性.在第三十二届 AAAI 人工智能会议。

  • Jo, T.,& Bengio, Y. (2017). 测量 CNN 学习表面统计规律的趋势. arXiv 预印本 arXiv:1711.11561。

  • Haibe-Kains, B.,Adam, G. A.,Hosny, A.,Khodakarami, F.,& Waldron, L. (2020). 人工智能中的透明度和可重现性.自然,586(7829),E14-E16。

第十一章:避免和检测数据和相关漂移

我们在第九章 生产测试和调试中讨论了数据和相关漂移在机器学习建模中的影响。在本章中,我们想要更深入地探讨这些概念,并在 Python 中练习检测漂移。

在这里,你将了解我们之前介绍的概念的重要性,例如模型版本控制和模型监控,以避免漂移,并练习使用一些用于漂移检测的 Python 库。

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

  • 避免模型中的漂移

  • 漂移检测

到本章结束时,你将能够在 Python 中检测你的机器学习模型中的漂移,并在生产中拥有可靠的模型。

技术要求

以下要求适用于本章,因为它们有助于你更好地理解这些概念,让你能够在项目中使用它们,并使用提供的代码进行练习:

  • Python 库需求如下:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pandas >= 1.4.4

    • alibi_detect >= 0.11.1

    • lightgbm >= 3.3.5

    • evidently >= 0.2.8

  • 需要理解以下内容:

    • 数据和相关漂移

    • 数据和模型版本控制

你可以在 GitHub 上找到本章的代码文件:github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter11

避免模型中的漂移

数据和相关漂移挑战了机器学习模型在生产中的可靠性。我们机器学习项目中的漂移可能具有不同的特征。以下是一些可以帮助你在项目中检测漂移并计划解决它们的特征:

  • 幅度:我们可能会面对数据分布中的差异幅度,导致我们的机器学习模型发生漂移。数据分布中的小变化可能难以检测,而大变化可能更明显。

  • 频率:漂移可能以不同的频率发生。

  • 渐进与突然:数据漂移可能逐渐发生,数据分布的变化在时间上缓慢发生,或者可能突然发生,变化快速且出乎意料。

  • 可预测性:某些类型的漂移可能是可预测的,例如季节性变化或由于外部事件引起的变化。其他类型的漂移可能是不可预测的,例如消费者行为或市场趋势的突然变化。

  • 意图性:漂移可能是故意的,例如对数据生成过程所做的更改,或者无意的,例如随着时间的推移自然发生的变化。

我们需要使用帮助我们在机器学习建模项目中避免漂移发生和累积的技术和实践。

避免数据漂移

在机器学习生命周期的不同阶段访问我们模型的不同数据版本可以帮助我们通过比较训练和生产的数据、评估预处理数据处理或识别可能导致漂移的数据选择标准来更好地检测漂移。模型监控还有助于我们尽早识别漂移并避免累积。

让我们通过简单地检查用于模型训练的数据版本之间的特征分布的均值,以及生产中的新数据来练习漂移监控。我们首先定义一个用于监控数据漂移的类。在这里,我们假设如果两个数据版本之间的分布均值之差大于 0.1,则认为特征发生了漂移:

class DataDriftMonitor:    def __init__(self, baseline_data: np.array,
        threshold_mean: float = 0.1):
            self.baseline = self.calculate_statistics(
                baseline_data)
            self.threshold_mean = threshold_mean
    def calculate_statistics(self, data: np.array):
        return np.mean(data, axis=0)
    def assess_drift(self, current_data: np.array):
        current_stats = self.calculate_statistics(
            current_data)
        drift_detected = False
        for feature in range(0, len(current_stats)):
            baseline_stat = self.baseline[feature]
            current_stat = current_stats[feature]
            if np.abs(current_stat - baseline_stat) > self.threshold_mean:
                drift_detected = True
                print('Feature id with drift:
                    {}'.format(feature))
                print('Mean of original distribution:
                    {}'.format(baseline_stat))
                print('Mean of new distribution:
                    {}'.format(current_stat))
                break
        return drift_detected

然后,我们使用它来识别两个合成数据集之间的漂移:

np.random.seed(23)# Generating a synthetic dataset, as the original data, with 100 datapoints and 5 features
# from a normal distribution centered around 0 with std of 1
baseline_data = np.random.normal(loc=0, scale=1,
    size=(100, 5))
# Create a DataDriftMonitor instance
monitor = DataDriftMonitor(baseline_data,
    threshold_mean=0.1)
# Generating a synthetic dataset, as the original data, with 100 datapoints and 5 features from a normal distribution #centered around 0.2 with std of 1
current_data = np.random.normal(loc=0.15, scale=1,
    size=(100, 5))
# Assess data drift
drift_detected = monitor.assess_drift(current_data)
if drift_detected:
    print("Data drift detected.")
else:
    print("No data drift detected.")

这会产生以下结果:

Feature id with drift: 1Mean of original distribution: -0.09990597519469419
Mean of new distribution: 0.09662442557421645
Data drift detected.

应对概念漂移

我们可以同样定义具有检测概念漂移标准的类和函数,就像我们练习数据漂移检测那样。但我们还可以检查,无论是通过编程还是作为将我们的机器学习模型投入生产时的质量保证的一部分,外部因素可能导致的漂移,例如环境因素、机构或政府政策的变更等。除了监控数据外,我们还可以从特征工程中受益,选择对概念漂移更鲁棒的特征,或者使用动态适应概念漂移的集成模型。

虽然在我们的模型中避免漂移是理想的,但在实践中我们需要准备好检测和消除它。接下来,你将学习检测模型中漂移的技术。从实际的角度来看,避免和检测模型中的漂移非常相似。但比简单地检查特征分布的均值(如我们在本节中用于避免数据漂移)更好的技术,我们将在下一节中练习。

检测漂移

在所有模型中完全避免漂移是不可能的,但我们可以努力尽早检测并消除它们。在这里,我们将通过在 Python 中使用alibi_detectevidently来练习漂移检测。

使用 alibi_detect 进行漂移检测的练习

我们想要练习的广泛使用的 Python 库之一是alibi_detect。我们首先导入必要的 Python 函数和类,并使用scikit-learn中的make_classification生成一个具有 10 个特征和 10,000 个样本的合成数据集:

import numpy as npimport pandas as pd
import lightgbm as lgb
from alibi_detect.cd import KSDrift
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import balanced_accuracy_score as bacc
# Generate synthetic data
X, y = make_classification(n_samples=10000, n_features=10,
    n_classes=2, random_state=42)

然后,我们将数据分为训练集和测试集:

# Split into train and test setsX_train, X_test, y_train, y_test = train_test_split(X, y,
    test_size=0.2, random_state=42)

然后,我们在训练数据上训练一个LightGBM分类器:

train_data = lgb.Dataset(X_train, label=y_train)params = {
    "objective": "binary",
    "metric": "binary_logloss",
    "boosting_type": "gbdt"
}
clf = lgb.train(train_set = train_data, params = params,
    num_boost_round=100)

现在,我们评估模型在测试集上的性能,并定义一个用于漂移检测的测试标签 DataFrame:

# Predict on the test sety_pred = clf.predict(X_test)
y_pred = [1 if iter > 0.5 else 0 for iter in y_pred]
# Calculate the balanced accuracy of the predictions
balanced_accuracy = bacc(y_test, y_pred)
print('Balanced accuracy on the synthetic test set:
    {}'.format(balanced_accuracy))
# Create a DataFrame from the test data and predictions
df = pd.DataFrame(X_test,
    columns=[f"feature_{i}" for i in range(10)])
df["actual"] = y_test
df["predicted"] = y_pred

现在,我们使用测试数据点的预测和实际标签定义的 DataFrame 来检测漂移。我们从 alibi_detect 包初始化 KSDrift 检测器并将其拟合到训练数据上。我们使用检测器的 predict 方法在测试数据上计算漂移分数和 p 值。漂移分数表示每个特征的漂移水平,而 p 值表示漂移的统计显著性。如果任何漂移分数或 p 值超过某个阈值,我们可能认为模型正在经历漂移,并采取适当的行动,例如使用更新后的数据重新训练模型:

# Initialize the KSDrift detectordrift_detector = KSDrift(X_train)
# Calculate the drift scores and p-values
drift_scores = drift_detector.predict(X_test)
p_values = drift_detector.predict(X_test,
    return_p_val=True)
# Print the drift scores and p-values
print("Drift scores:")
print(drift_scores)
print("P-values:")
print(p_values)

这里是生成的漂移分数和 p 值。由于所有 p 值都大于 0.1,并且考虑到阈值是 0.005,我们可以说在这种情况下没有检测到漂移:

Drift scores:{'data': {'is_drift': 0, 'distance': array([0.02825 , 0.024625, 0.0225  , 0.01275 , 0.014   , 0.017125,0.01775 , 0.015125, 0.021375, 0.014625], dtype=float32), 'p_val': array([0.15258548, 0.28180763, 0.38703775, 0.95421314, 0.907967 ,0.72927415, 0.68762517, 0.8520056 , 0.45154762, 0.87837887],dtype=float32), 'threshold': 0.005}, 'meta': {'name': 'KSDrift', 'online': False, 'data_type': None, 'version': '0.11.1', 'detector_type': 'drift'}}
P-values:
{'data': {'is_drift': 0, 'distance': array([0.02825 , 0.024625, 0.0225  , 0.01275 , 0.014   , 0.017125,0.01775 , 0.015125, 0.021375, 0.014625], dtype=float32), 'p_val': array([0.15258548, 0.28180763, 0.38703775, 0.95421314, 0.907967 ,0.72927415, 0.68762517, 0.8520056 , 0.45154762, 0.87837887],dtype=float32), 'threshold': 0.005}, 'meta': {'name': 'KSDrift', 'online': False, 'data_type': None, 'version': '0.11.1', 'detector_type': 'drift'}}

使用 evidently 进行漂移检测实践

另一个广泛使用的用于漂移检测的 Python 库,我们将在下面进行实践的是 evidently。在导入必要的库之后,我们从 scikit-learn 加载糖尿病数据集:

import pandas as pdimport numpy as np
from sklearn import datasets
from evidently.report import Report
from evidently.metrics import DataDriftTable
from evidently.metrics import DatasetDriftMetric
diabetes_data = datasets.fetch_openml(name='diabetes',
    version=1, as_frame='auto')
diabetes = diabetes_data.frame
diabetes = diabetes.drop(['class', 'pres'], axis = 1)

以下表格显示了我们要从糖尿病数据集中用于漂移检测的特征及其含义:

特征描述
preg怀孕次数
plas口服葡萄糖耐量测试 2 小时后的血浆葡萄糖浓度
skin三角肌皮肤褶皱厚度(毫米)
insu2 小时血清胰岛素(微 U/ml)
mass体重指数(千克/(米)²)
pedi糖尿病家系函数
Age年龄(年)

表 11.1 – 用于漂移检测的糖尿病数据集中特征名称及其描述(Efron 等,2004)

我们将数据点分为两组,称为参考集和当前集,然后使用 evidently.report.Reference 集中的 Report() 生成漂移报告,包括所有年龄小于或等于 40 岁的个体,以及当前集包括数据集中年龄大于 40 岁的其他个体:

diabetes_reference = diabetes[diabetes.age <= 40]diabetes_current = diabetes[diabetes.age > 40]
data_drift_dataset_report = Report(metrics=[
    DatasetDriftMetric(),
    DataDriftTable(),
])
data_drift_dataset_report.run(
    reference_data=diabetes_reference,
    current_data=diabetes_current)
Data_drift_dataset_report

以下插图是我们为糖尿病数据集生成的报告,考虑了所选特征和分离的参考和当前数据集:

图 11.1 – 糖尿病数据集中分离的参考和当前数据的漂移报告

图 11.1 – 糖尿病数据集中分离的参考和当前数据的漂移报告

我们可以看到,agepregplasinsuskin是参考集和当前集中分布差异显著的特性,这些特性在图 11.1所示的报告中指定为检测到漂移的特征。尽管分布之间的差异很重要,但拥有如均值差异这样的补充统计数据可能有助于开发更可靠的漂移检测策略。我们还可以从报告中获取特征的分布,例如图 11.211.3中分别显示的agepreg在参考集和当前集中的分布:

图 11.2 – 当前和参考数据中 age 特征的分布

图 11.2 – 当前和参考数据中 age 特征的分布

图 11.3 – 当前和参考数据中 preg 特征的分布

图 11.3 – 当前和参考数据中 preg 特征的分布

当我们在模型中检测到漂移时,我们可能需要通过摄取新数据或过滤可能引起漂移的部分数据来重新训练它们。如果检测到概念漂移,我们还可能需要更改模型训练。

摘要

在本章中,你了解了避免机器学习模型中漂移的重要性,以及如何通过使用你在前几章中学到的概念(如模型版本化和监控)来从中受益。你还练习了两个用于 Python 中漂移检测的库:alibi_detectevidently。使用这些或类似的库将有助于你在模型中消除漂移,并在生产中拥有可靠的模型。

在下一章中,你将了解不同类型的深度神经网络模型以及如何使用 PyTorch 开发可靠的深度学习模型。

问题

  1. 你能解释一下在机器学习建模中,漂移的两个特征——幅度和频率之间的区别吗?

  2. 我们可以使用哪种统计测试来检测数据漂移?

参考文献

  • Ackerman, Samuel, 等人。“检测影响机器学习模型性能随时间变化的数据漂移和异常值。”arXiv 预印本 arXiv:2012.09258(2020)。

  • Ackerman, Samuel, 等人。“自动检测机器学习分类器中的数据漂移。”arXiv 预印本 arXiv:2111.05672(2021)。

  • Efron, Bradley, Trevor Hastie, Iain Johnstone, 和 Robert Tibshirani(2004)“最小角度回归,”统计年鉴(附带讨论),407-499

  • Gama, João, 等人。“概念漂移适应的调查。”ACM 计算调查(CSUR)46.4(2014):1-37。

  • Lu, Jie, 等人。“在概念漂移下的学习:综述。”IEEE 知识数据工程杂志 31.12(2018):2346-2363。

  • Mallick, Ankur, 等人。“Matchmaker:在机器学习中为大规模系统减轻数据漂移。”机器学习和系统会议论文集 4(2022):77-94。

  • Zenisek, Jan, Florian Holzinger, 和 Michael Affenzeller. “基于机器学习的概念漂移检测用于预测性维护。” 计算机与工业工程 137 (2019): 106031。

第四部分:深度学习建模

在本书的这一部分,我们将通过介绍深度学习的底层理论来奠定基础,然后过渡到对全连接神经网络的动手探索。接着,我们将学习更高级的技术,包括卷积神经网络、转换器和图神经网络。在本部分的结尾,我们将聚焦于机器学习的尖端进展,特别关注生成建模,并介绍强化学习和自监督学习。在这些章节中,我们将通过 Python 和 PyTorch 提供实际示例,确保我们既获得理论知识,也获得实践经验。

本部分包含以下章节:

  • 第十二章, 超越机器学习调试的深度学习

  • 第十三章, 高级深度学习技术

  • 第十四章, 机器学习最新进展简介

第十二章:深度学习超越机器学习调试

机器学习最新的进展是通过深度学习建模实现的。在本章中,我们将介绍深度学习以及 PyTorch 作为深度学习建模的框架。由于本书的重点不是详细介绍不同的机器学习和深度学习算法,我们将关注深度学习为你提供的机会,以开发高性能模型,或使用可用的模型,这些模型可以建立在本章和下一章回顾的技术之上。

本章将涵盖以下主题:

  • 人工神经网络简介

  • 神经网络建模框架

到本章结束时,你将了解深度学习的某些理论方面,重点关注全连接神经网络。你还将使用广泛使用的深度学习框架 PyTorch 进行实践。

技术要求

以下要求应考虑本章,因为它们将帮助你更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • torch >= 2.0.0

    • torchvision >= 0.15.1

  • 你还需要了解不同类型机器学习模型之间的基本区别,例如分类、回归和聚类

你可以在 GitHub 上找到本章的代码文件,链接为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter12

人工神经网络简介

我们的大脑神经网络作为决策系统工作,其中信息处理单元称为神经元,帮助我们识别朋友的面孔等。人工神经网络ANNs)的工作原理类似。与我们的身体中存在一个庞大的神经元网络,负责所有决策(主动或被动)不同,ANNs 被设计为针对特定问题。例如,我们有用于图像分类、信用风险估计、目标检测等任务的 ANNs。为了简化,本书中将使用神经网络而不是 ANNs。

首先,我们想专注于全连接神经网络FCNNs),它们在表格数据上工作(图 12)。在许多资源中,FCNNs 和多层感知器MLPs)被互换使用。为了更好地比较不同类型的神经网络,本书中将使用 FCNNs 而不是 MLPs:

图 12.1 – FCNN 和单个神经元的示意图

图 12.1 – FCNN 和单个神经元的示意图

用于监督学习的 FCNN 有一个输入、一个输出和一个或多个隐藏(中间)层。包含输入和输出层的监督模型中超过三层的神经网络称为深度神经网络,深度学习指的是使用这种网络进行建模(Hinton 和 Salakhutdinov,2006)。

输入层不过是用于建模的数据点的特征。输出层神经元的数量也是根据实际问题确定的。例如,在二元分类的情况下,输出层中的两个神经元代表两个类别。隐藏层的数量和大小是 FCNN 的超参数之一,可以通过优化来提高 FCNN 的性能。

FCNN 中的每个神经元接收来自前一层的神经元的加权输出值的总和,对接收到的值的总和应用线性或非线性变换,然后将结果值输出到下一层的其他神经元。每个神经元输入值计算中使用的权重是训练过程中的学习权重(参数)。非线性变换是通过预定的激活函数实现的(图 12*.2*)。FCNN 以其在输入特征值和输出之间产生复杂非线性关系而闻名,这使得它们在确定(可能)输入和输出之间不同类型的关系时非常灵活。在 FCNN 中,应用于接收到的神经元信息的激活函数负责这种复杂性或灵活性:

图 12.2 – 神经网络建模中广泛使用的激活函数

图 12.2 – 神经网络建模中广泛使用的激活函数

这些激活函数,例如sigmoidsoftmax函数,通常用于输出层,将输出神经元的分数转换为介于零和一之间的值,用于分类模型;这些被称为预测的概率。还有其他激活函数,如高斯误差线性单元GELU)(Hendrycks 和 Gimpel,2016),这些在更近期的模型中如生成预训练转换器GPT)中已被使用,这将在下一章中解释。以下是 GELU 的公式:

GELU(z) = 0.5z(1 + tanh(√ _  2 _ π  (z + 0.044715 z 3)))

监督学习有两个主要过程:预测输出和从预测的不正确性或正确性中学习。在全连接神经网络(FCNNs)中,预测发生在前向传播中。输入和第一个隐藏层之间的 FCNNs 的权重用于计算第一个隐藏层中神经元的输入值,以及其他 FCNN 中的层也是如此(图 12.3)。从输入到输出的过程称为前向传播或前向传递,它为每个数据点生成输出值(预测)。然后,在反向传播(反向传递)中,FCNN 使用预测输出及其与实际输出的差异来调整其权重,从而实现更好的预测:

图 12.3 – 分别用于输出预测和参数更新的前向传播和反向传播的示意图

图 12.3 – 分别用于输出预测和参数更新的前向传播和反向传播的示意图

神经网络的参数在训练过程中通过优化算法来确定。现在,我们将回顾一些在神经网络设置中广泛使用的优化算法。

优化算法

优化算法在幕后工作,试图最小化损失函数以识别训练机器学习模型时的最佳参数。在训练过程的每一步,优化算法决定如何更新神经网络中的每个权重或参数,或其他机器学习模型。大多数优化算法依赖于成本函数的梯度向量来更新权重。主要区别在于如何使用梯度向量以及使用哪些数据点来计算它。

在梯度下降中,所有数据点都用于计算成本函数的梯度;然后,模型的权重在成本最大减少的方向上更新。尽管这种方法对于小数据集是有效的,但它可能变得计算成本高昂,不适用于大数据集,因为对于学习的每一次迭代,都需要同时计算所有数据点的成本。另一种方法是随机梯度下降SGD);在每次迭代中,不是所有数据点,而是选择一个数据点来计算成本和更新权重。但每次只使用一个数据点会导致权重更新时出现高度振荡的行为。相反,我们可以使用迷你批梯度下降,这在教程和工具中通常被称为 SGD,其中不是每个迭代中所有数据点或仅一个,而是使用一批数据点来更新权重。这些三种方法背后的数学原理在图 12.4 中展示:

图 12.4 – 梯度下降、随机梯度下降和迷你批梯度下降优化算法

图 12.4 – 梯度下降、随机梯度下降和小批量梯度下降优化算法

近年来,已经提出了其他优化算法来提高神经网络模型在各种应用中的性能,例如 Adam 优化器(Kingma 和 Ba,2014)。这种方法的背后直觉是避免优化过程中的梯度消失。深入探讨不同优化算法的细节超出了本书的范围。

在神经网络建模中,有两个重要的术语你需要了解其定义:epochbatch size。当使用不同的框架训练神经网络模型时,我们将在下一节中回顾,你需要指定 batch sizeepoch 的数量。在优化的每一次迭代中,数据点的一个子集,或者说是小批量梯度下降中的小批量(如图 12.4*),被用来计算损失;然后,使用反向传播更新模型的参数。这个过程会重复进行,以覆盖训练数据中的所有数据点。Epoch 是我们在优化过程中用来指定所有训练数据被使用多少次的术语。例如,指定 5 个 epoch 意味着模型在优化过程中会使用训练过程中的所有数据点五次。

现在你已经了解了神经网络建模的基础知识,我们将介绍用于神经网络建模的框架。

神经网络建模框架

已有多个框架被用于神经网络建模:

在本书中,我们将专注于 PyTorch 来实践深度学习,但我们介绍的概念与你在项目中使用的框架无关。

用于深度学习建模的 PyTorch

PyTorch 是一个开源的深度学习框架,基于 Meta AI 开发的 Torch 库。你可以在深度学习项目中轻松地将 PyTorch 与 Python 的科学计算库集成。在这里,我们将通过查看使用 MNIST 数字数据集构建 FCNN 模型的一个简单示例来练习使用 PyTorch。这是一个常用的示例,其目标仅仅是理解如果你没有相关经验,如何使用 PyTorch 训练和测试深度学习模型。

首先,我们将导入所需的库并加载用于训练和测试的数据集:

import torchimport torchvision
import torchvision.transforms as transforms
torch.manual_seed(10)
# Device configuration
device = torch.device(
    'cuda' if torch.cuda.is_available() else 'cpu')
# MNIST dataset
batch_size = 100
train_dataset = torchvision.datasets.MNIST(
    root='../../data',train=True,
    transform=transforms.ToTensor(),download=True)
test_dataset = torchvision.datasets.MNIST(
    root='../../data', train=False,
    transform=transforms.ToTensor())
# Data loader
train_loader = torch.utils.data.DataLoader(
    dataset=train_dataset,batch_size=batch_size,
    shuffle=True)
test_loader = torch.utils.data.DataLoader(
    dataset=test_dataset,  batch_size=batch_size,
    shuffle=False)

接下来,我们将确定模型的超参数及其input_size,这是输入层中的神经元数量;这等同于我们数据中的特征数量。在这个例子中,它等于每张图像中的像素数,因为我们把每个像素视为一个特征来构建一个全连接神经网络(FCNN)模型:

input_size = 784# size of hidden layer
hidden_size = 256
# number of classes
num_classes = 10
# number of epochs
num_epochs = 10
# learning rate for the optimization process
learning_rate = 0.001

然后,我们将导入torch.nn,从中我们可以为我们的 FCNN 模型添加线性神经网络层,并编写一个类来确定我们网络的架构,这是一个包含一个大小为 256(256 个神经元)的隐藏层的网络:

import torch.nn as nnclass NeuralNet(nn.Module):
    def __init__(self, input_size, hidden_size,
        num_classes):
        super(NeuralNet, self).__init__()
        Self.fc_layer_1 = nn.Linear(input_size, hidden_size)
        self.fc_layer_2 = nn.Linear(hidden_size, num_classes)
    def forward(self, x):
        out = self.fc_layer_1(x)
        out = nn.ReLU()(out)
        out = self.fc_layer_2(out)
        return out
model = NeuralNet(input_size, hidden_size,
    num_classes).to(device)

torch.nn.Linear()类添加一个线性层,并有两个输入参数:当前层和下一层的神经元数量。对于第一个nn.Linear(),第一个参数必须等于特征数量,而网络初始化类中最后一个nn.Linear()输入参数的第二个参数需要等于数据中的类别数量。

现在,我们必须使用torch.optim()中的 Adam 优化器定义我们的交叉熵损失函数和优化器对象:

criterion = nn.CrossEntropyLoss()optimizer = torch.optim.Adam(model.parameters(),
    lr=learning_rate)

我们现在准备好训练我们的模型了。正如你在下面的代码块中可以看到的,我们有一个关于 epochs 的循环,以及另一个关于每个批次的内部循环。在内部循环中,我们有三个在大多数使用 PyTorch 的监督模型中常见的步骤:

  1. 获取批次内数据点的模型输出。

  2. 使用该批次的真实标签和预测输出计算损失。

  3. 反向传播并更新模型的参数。

接下来,我们必须在 MNIST 训练集上训练模型:

total_step = len(train_loader)for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        # Forward pass to calculate output and loss
        outputs = model(images)
        loss = criterion(outputs, labels)
        # Backpropagation and optimization
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

在第 10 个 epoch 结束时,我们在训练集中有一个损失为 0.0214 的模型。现在,我们可以使用以下代码来计算测试集中模型的准确率:

with torch.no_grad():    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    print('Accuracy of the network on the test images:
         {} %'.format(100 * correct / total))

这使得 MNIST 测试集中的模型达到了 98.4%的准确率。

PyTorch(pytorch.org/docs/stable/optim.html)中提供了超过 10 种不同的优化算法,包括 Adam 优化算法,这些算法可以帮助你在训练深度学习模型时。

接下来,我们将讨论深度学习设置中的超参数调整、模型可解释性和公平性。我们还将介绍 PyTorch Lightning,这将有助于你在深度学习项目中。

深度学习超参数调整

在深度学习建模中,超参数是决定其性能的关键因素。以下是一些你可以用来提高你的深度学习模型性能的 FCNN 超参数:

  • 架构: FCNN 的架构指的是隐藏层的数量和它们的大小,或者神经元的数量。更多的层会导致深度学习模型具有更高的深度,并可能导致更复杂的模型。尽管神经网络模型的深度在许多情况下已被证明可以提高大型数据集上的性能(Krizhevsky 等人,2012 年;Simonyan 和 Zisserman,2014 年;Szegedy 等人,2015 年;He 等人,2016 年),但大多数关于更高深度对性能产生积极影响的成功故事都发生在 FCNN 之外。但架构仍然是一个需要优化的重要超参数,以找到高性能模型。

  • 激活函数: 尽管每个领域和问题中都有常用的激活函数,但你仍然可以为你自己的问题找到最佳的一个。记住,你不需要在所有层中使用相同的函数,尽管我们通常坚持使用一个。

  • 批量大小: 改变批量大小会改变你模型的性能和收敛速度。但通常,它对性能没有显著影响,除了在第一个几个训练轮数学习曲线的陡峭部分。

  • 学习率: 学习率决定了收敛的速度。较高的学习率会导致收敛速度加快,但也可能导致在局部最优点的振荡,甚至发散。例如,Adam 优化器等算法在优化过程中接近局部最优时,会控制收敛率的衰减,但我们仍然可以将学习率作为深度学习建模的超参数进行调整。

  • 训练轮数: 深度学习模型在前几轮的训练中学习曲线陡峭,这取决于学习率和批量大小,然后性能开始趋于平稳。使用足够的训练轮数对于确保你从训练中得到最佳模型至关重要。

  • 正则化: 我们在第五章《提高机器学习模型性能》中讨论了正则化在控制过拟合和提升泛化能力方面的重要性,通过防止模型过度依赖单个神经元,从而可能提高泛化能力。例如,如果设置 dropout 为 0.2,每个神经元在训练过程中有 20%的概率被置零。

  • 权重衰减: 这是一种 L2 正则化形式,它对神经网络中的权重添加惩罚。我们在第五章《提高机器学习模型性能》中介绍了 L2 正则化。

您可以使用不同的超参数优化工具,如 Ray Tune,与 PyTorch 一起训练您的深度学习模型并优化其超参数。您可以在 PyTorch 网站上的这个教程中了解更多信息:pytorch.org/tutorials/beginner/hyperparameter_tuning_tutorial.html

除了超参数调整之外,PyTorch 还具有不同的功能和相关库,用于模型可解释性和公平性等任务。

PyTorch 中的模型可解释性

第六章《机器学习建模中的可解释性和可解释性》中,我们介绍了多种可解释性技术和库,可以帮助您解释复杂的机器学习和深度学习模型。Captum AI (captum.ai/) 是由 Meta AI 开发的一个开源模型可解释性库,用于使用 PyTorch 的深度学习项目。您可以将 Captum 容易地集成到现有的或未来的基于 PyTorch 的机器学习管道中。您可以通过 Captum 利用不同的可解释性和可解释性技术,如集成梯度、GradientSHAP、DeepLIFT 和显著性图。

PyTorch 开发的深度学习模型中的公平性

第七章《减少偏差和实现公平性》中,我们讨论了公平性的重要性,并介绍了不同的概念、统计指标和技术,以帮助您评估和消除模型中的偏差。FairTorch (github.com/wbawakate/fairtorch) 和 inFairness (github.com/IBM/inFairness) 是另外两个库,您可以使用它们来评估使用 PyTorch 的深度学习建模中的公平性和偏差。您可以从 inFairness 中受益,用于审计、训练和后处理模型以实现个体公平性。Fairtorch 还为您提供了减轻分类和回归中偏差的工具,尽管目前这仅限于二元分类。

PyTorch Lightning

PyTorch Lightning 是一个开源的高级框架,简化了使用 PyTorch 开发和训练深度学习模型的过程。以下是 PyTorch Lightning 的一些特性:

  • 结构化代码:PyTorch Lightning 将代码组织成 Lightning Module,这有助于您分离模型架构、数据处理和训练逻辑,使代码更加模块化且易于维护。

  • 训练循环抽象:您可以使用 PyTorch Lightning 避免训练、验证和测试循环中的重复代码

  • 分布式训练:PyTorch Lightning 简化了在多个 GPU 或节点上扩展深度学习模型的过程

  • 实验跟踪和日志记录:PyTorch Lightning 与实验跟踪和日志记录工具(如 MLflow 和 Weights & Biases)集成,这使得您更容易监控深度学习模型训练。

  • 自动优化:PyTorch Lightning 自动处理优化过程,管理优化器和学习率调度器,并使得在不同优化算法之间切换更加容易。

尽管有这些因素,深度学习建模的内容远不止 FCNNs,正如我们在下一章中将要看到的。

摘要

在本章中,你学习了使用 FCNNs 进行深度学习建模。我们通过使用 PyTorch 和一个简单的深度学习模型来练习,帮助你开始使用 PyTorch 进行深度学习建模(如果你还没有这样的经验)。你还学习了 FCNNs 的重要超参数、可用于深度学习环境中的模型可解释性和公平性工具,以及 PyTorch Lightning 作为简化深度学习建模的开源高级框架。你现在可以学习更多关于 PyTorch、PyTorch Lightning 和深度学习的内容,并开始从它们中受益于你的问题。

在下一章中,你将学习到其他更高级的深度学习模型,包括卷积神经网络、转换器和图卷积神经网络模型。

问题

  1. 神经网络模型的参数在反向传播中会更新吗?

  2. 随机梯度下降和批量梯度下降有什么区别?

  3. 你能解释一下批量(batch)和周期(epoch)之间的区别吗?

  4. 你能提供一个例子说明在您的神经网络模型中需要使用 sigmoid 和 softmax 函数的地方吗?

参考文献

  • LeCun, Yann, Yoshua Bengio, 和 Geoffrey Hinton. 深度学习. 自然 521.7553 (2015): 436-444。

  • Hinton, G. E., & Salakhutdinov, R. R. (2006). 使用神经网络降低数据维度. 科学,313(5786),504-507。

  • Abiodun, Oludare Isaac, 等人. 人工神经网络应用中的最新进展:一项调查. Heliyon 4.11 (2018): e00938。

  • Hendrycks, D., & Gimpel, K. (2016). 高斯误差线性单元 (GELUs). arXiv 预印本 arXiv:1606.08415。

  • Kingma, D. P., & Ba, J. (2014). Adam: 一种用于随机优化的方法. arXiv 预印本 arXiv:1412.6980。

  • Kadra, Arlind, 等人. 精心调优的简单网络在表格数据集上表现出色. 神经信息处理系统进展 34 (2021): 23928-23941。

  • Krizhevsky, A., Sutskever, I., & Hinton, G. E. (2012). 使用深度卷积神经网络进行 ImageNet 分类. 在神经信息处理系统进展(第 1097-1105 页)。

  • Simonyan, K., & Zisserman, A. (2014). 非常深的卷积网络在大规模图像识别中的应用. arXiv 预印本 arXiv:1409.1556。

  • He, K., Zhang, X., Ren, S., & Sun, J. (2016). 深度残差学习在图像识别中的应用. 在 IEEE 计算机视觉与模式识别会议论文集(第 770-778 页)。

  • Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., Anguelov, D., ... & Rabinovich, A. (2015). 使用卷积进行更深入的学习。载于 IEEE 计算机视觉与模式识别会议论文集(第 1-9 页)。

第十三章:高级深度学习技术

在上一章中,我们回顾了神经网络建模和深度学习的概念,同时关注全连接神经网络。在本章中,我们将讨论更多高级技术,这些技术让您能够使用深度学习模型跨越不同的数据类型和结构,例如图像、文本和图。这些技术是人工智能在各个行业取得进步的主要推动力,例如在聊天机器人、医疗诊断、药物发现、股票交易和欺诈检测等领域。尽管我们将介绍不同数据类型中最著名的深度学习模型,但本章旨在帮助您理解概念,并使用 PyTorch 和 Python 进行实践,而不是为每个数据类型或主题领域提供最先进的模型。

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

  • 神经网络类型

  • 用于图像形状数据的卷积神经网络

  • 用于语言建模的转换器

  • 使用深度神经网络建模图

到本章结束时,您将了解卷积神经网络CNNs)、转换器和图神经网络作为深度学习建模的三个重要类别,以在您感兴趣的问题中开发高性能模型。您还将了解如何使用 PyTorch 和 Python 开发此类模型。

技术要求

对于本章,以下要求应予以考虑,因为它们将帮助您更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • torch >= 2.0.0

    • torchvision >= 0.15.1

    • transformers >= 4.28.0

    • datasets >= 2.12.0

    • torch_geometric == 2.3.1

  • 您需要具备以下基本知识:

    • 深度学习建模和全连接神经网络

    • 如何使用 PyTorch 进行深度学习建模

您可以在 GitHub 上找到本章的代码文件,链接为 github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter13

神经网络类型

本书迄今为止提供的示例主要集中在表格数据上,无论是机器学习还是深度学习建模,作为机器学习建模的一个类别。然而,机器学习和特别是深度学习在处理非表格数据、非结构化文本、图像和图的问题上已经取得了成功。首先,在本节中,我们将介绍涉及此类数据类型的不同问题;然后,我们将回顾可以帮助您为这些问题构建可靠模型的深度学习技术。

基于数据类型的分类

结构化数据,也称为表格数据,是指可以组织成电子表格和结构化数据库的数据。正如我们在本书中使用这种数据类型一样,我们通常在表格、矩阵或 DataFrame 的列中具有不同的特征,甚至输出。DataFrame 的行代表数据集中的不同数据点。然而,我们还有其他类型的数据,它们不是结构化的,将它们重新格式化为 DataFrame 或矩阵会导致信息丢失。“图 13.1”显示了三种最重要的非结构化数据类型——即文本等序列数据、家庭照片等图像形状数据以及社交网络等图数据:

图 13.1 – 使用深度学习可以建模的不同数据类型

图 13.1 – 使用深度学习可以建模的不同数据类型

表 13.1 提供了一些问题和它们对应的数据如何适合图 13.1 中提到的每个类别的一些示例:

数据类型示例
序列数据文本、股票价格等时间序列数据、声音波序列的音频数据、物体运动序列的地理位置数据、大脑电活动序列的 EEG 数据、心脏电活动序列的 ECG 数据
图像形状数据照片、安全监控图像、X 射线或 CT 扫描等医学图像、视觉艺术和绘画图像、卫星捕获的图像,如天气模式、显微镜捕获的图像,如细胞图像
道路网络、网页之间的连接(网络图)、概念之间的关系(知识图)、个人和群体之间的连接(社交网络)、基因或其他生物实体之间的连接(生物网络)

表 13.1 – 每种数据类型的示例问题

将不同数据类型重新格式化为表格数据时的一些挑战和问题如下:

  • 将序列数据重新格式化为表格形状的数据对象会导致关于数据顺序(如单词)的信息丢失

  • 将图像重新格式化为表格格式会导致局部模式(如二维图像像素之间的关系)的丢失

  • 将图重新格式化为表格数据将消除数据点或特征之间的依赖关系

现在我们已经理解了不将所有数据集和数据类型重新格式化为表格数据的重要性,我们可以开始使用不同的深度学习技术来了解我们如何为非表格数据构建成功的模型。我们将从查看图像形状数据开始。

用于图像形状数据的卷积神经网络

CNNs 允许我们在图像数据上构建深度学习模型,而无需将图像重新格式化为表格格式。这类深度学习技术的名称来源于卷积的概念,在深度学习中指的是将滤波器应用于图像形状数据以产生一个次级图像形状特征图(如图 13**.2所示):

图 13.2 – 将预定义的卷积滤波器应用于 3x3 图像形状数据点的简单示例

图 13.2 – 将预定义的卷积滤波器应用于 3x3 图像形状数据点的简单示例

当训练一个深度学习模型时,例如使用 PyTorch,卷积滤波器或其他我们将在本章后面介绍的滤波器并不是预定义的,而是通过学习过程来学习的。卷积和其他 CNN 建模中的滤波器及过程使我们能够使用这类深度学习技术处理不同图像形状的数据(正如我们在图 13**.1中看到的)。

CNNs 的应用不仅限于图像分类的监督学习,这是它最著名的应用之一。CNNs 已被用于不同的问题,包括图像分割分辨率增强目标检测等(图 13*.3*):

图 13.3 – 卷积神经网络的一些成功应用

图 13.3 – 卷积神经网络的一些成功应用

表 13.2列出了 CNN 在不同应用中的高性能模型,您可以在项目中使用或在构建更好的模型时参考:

问题一些广泛使用的模型和相关技术
图像分类ResNet(He et al., 2016);EfficientNets(Tan and Le, 2019);MobileNets(Howard et al., 2017;Sandler et al., 2018);Xception(Chollet, 2017)
图像分割U-Net(Ronneberger et al., 2015);Mask R-CNN(He et al., 2017);DeepLab(Chen et al., 2017);PSPNet(Chao et al., 2017)
目标检测Mask R-CNN(He et al., 2017);Faster R-CNN(Ren et al., 2015);YOLO(Redmon et al., 2016)
图像超分辨率SRCNN(Dong et el., 2015);FSRCNN(Dong et al., 2016);EDSR(Lim et al., 2017)
图像到图像翻译Pix2Pix(Isola et al., 2017);CycleGAN(Zhu et al., 2017)
风格迁移艺术风格神经算法(Gatys et al., 2016);AdaIN-Style(Huang et al., 2017)
异常检测AnoGAN(Schlegl et al., 2017);RDA(Zhou et al., 2017);Deep SVDD(Ruff et al., 2018)
光学字符识别EAST(Zhou et al., 2017);CRAFT(Bake et al., 2019)

表 13.2 – 不同问题下的高性能 CNN 模型

你可以在二维或三维图像形状数据上训练 CNN 模型。你也可以构建处理此类数据点序列的模型,例如视频,作为图像序列。你可以玩的一些在视频上使用 CNN 的最著名模型或方法包括 C3D(Tran 等,2015 年)、I3D(Carreira 和 Zisserman,2017 年)和 SlowFast(Feichtenhofer 等,2019 年)。

接下来,我们将了解一些评估 CNN 模型性能的方法。

性能评估

你可以使用在第第四章中介绍的机器学习模型性能和效率问题检测的性能指标,例如 ROC-AUC、PR-AUC、精确率和召回率,用于 CNN 分类模型。然而,对于图 13**.3中提出的一些问题,还有其他更具体的指标,如下所示:

  • 像素精度:这个指标定义为正确分类的像素数与总像素数的比率。这个指标与准确度类似,当像素存在类别不平衡时可能会产生误导。

  • Jaccard 指数:Jaccard 指数定义为交集与并集的比率,可以用来计算预测分割与真实分割的交集,并按它们的并集进行归一化。

使用 PyTorch 进行 CNN 建模

PyTorch 中的 CNN 建模过程与我们之前章节中介绍的构建全连接神经网络非常相似。它从指定网络架构开始,然后初始化优化器,最后通过不同的周期和批次从训练数据点中学习。在这里,我们想使用torchvision库在 PyTorch 中练习 CNN 建模。此数据集中图像的示例在图 13**.4中显示:

图 13.4 – torchvision 数据集中德国交通标志识别基准(GTSRB)数据集的图像示例

图 13.4 – torchvision 数据集中德国交通标志识别基准(GTSRB)数据集的图像示例

除了torch.nn.Conv2d卷积滤波器外,torch.nn模块中还有其他滤波器和层可供使用,你可以使用它们来训练高性能的 CNN 模型。除了torch.nn.Conv2d之外,广泛使用的另一个滤波器是torch.nn.MaxPool2d,它可以用作 CNN 建模中的池化层(LeCun 等,1989 年)。你可以在 PyTorch 网站上阅读这两个滤波器所需的参数(pytorch.org/docs/stable/nn.html)。

让我们开始使用 GTSRB 数据集练习 CNN 建模。首先,我们必须加载用于模型训练和测试的数据,然后指定分类模型中的类别数:

transform = transforms.Compose([    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize((0.3337, 0.3064, 0.3171),
        ( 0.2672, 0.2564, 0.2629))
])
batch_size = 6
n_class = 43
# Loading train and test sets of
# German Traffic Sign Recognition Benchmark (GTSRB) Dataset.
trainset = torchvision.datasets.GTSRB(
    root='../../data',split = 'train',
    download=True,transform=transform)
trainloader = torch.utils.data.DataLoader(trainset,
    batch_size=batch_size,shuffle=True, num_workers=2)
testset = torchvision.datasets.GTSRB(
    root='../../data',split = 'test',
    download=True,transform=transform)
testloader = torch.utils.data.DataLoader(testset,
    batch_size=batch_size,shuffle=False,num_workers=2)

然后,我们必须定义一个名为Net的神经网络类,它决定了网络的架构,包括两层卷积加上池化滤波器,然后是 ReLU 激活函数,接着是三层具有 ReLU 激活函数的全连接神经网络:

import torch.nn as nnimport torch.nn.functional as F
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 128)
        self.fc2 = nn.Linear(128, 64)
        self.fc3 = nn.Linear(64, n_class)
    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = torch.flatten(x, 1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

然后,我们必须初始化网络和优化器,如下所示:

import torch.optim as optimnet = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001,
    momentum=0.9)

现在,我们已经准备好使用初始化的架构和优化器来训练网络。在这里,我们将使用三个 epoch 来训练网络。批大小不需要在这里指定,因为它们在从torchvision加载数据时就已经确定,在这个例子中指定为6(这可以在本书的 GitHub 仓库中找到):

n_epoch = 3for epoch in range(n_epoch):
    # running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the input data
        inputs, labels = data
        # zero the parameter gradients
        optimizer.zero_grad()
        # output identification
        outputs = net(inputs)
        # loss calculation and backward propagation for parameter update
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

经过 3 个 epoch 后的最终计算损失为 0.00008。

这只是一个使用 PyTorch 进行 CNN 建模的简单示例。在构建 CNN 模型时,PyTorch 还有其他你可以从中受益的功能,例如数据增强。我们将在下一节讨论这个问题。

CNN 的图像数据转换和增强

作为机器学习生命周期预训练阶段的一部分,你可能需要转换你的图像,例如通过裁剪它们,或者实现数据增强作为一系列用于合成数据生成的技术,以提高你模型的性能,如第五章中所述,提高机器学习模型的性能图 13.5展示了数据增强的一些简单示例,包括旋转和缩放,这些可以帮助你生成合成但高度相关的数据点,以帮助你的模型:

图 13.5 – 基于规则的图像数据增强示例 – (A) 原始图像,(B) 旋转图像,和(C) 缩放图像

图 13.5 – 基于规则的图像数据增强示例 – (A) 原始图像,(B) 旋转图像,和(C) 缩放图像

虽然你可以实现数据增强的简单规则示例,但在 PyTorch 中有许多类可以用于数据转换和增强,如pytorch.org/vision/stable/transforms.html中所述。

使用预训练模型

在深度学习环境中,我们通常依赖于预训练模型来进行推理或进一步微调以解决我们手头的特定问题。卷积神经网络(CNNs)也不例外,你可以在 PyTorch 中找到许多用于图像分类或其他 CNN 应用的预训练模型(pytorch.org/vision/stable/models.html)。你还可以在相同的 URL 上找到如何使用这些模型的代码示例。你可以在pytorch.org/tutorials/beginner/finetuning_torchvision_models_tutorial.html找到必要的代码,教你如何使用新数据微调这些模型。

虽然我们迄今为止一直专注于将 CNN 应用于图像数据,但它们可以用来建模任何图像形状数据。例如,音频数据可以从时域转换到频域,从而产生可以结合序列建模算法使用 CNN 来建模的图像形状数据,正如本章后面所介绍的 (pytorch.org/audio/main/models.html)。

除了图像和图像形状数据之外,深度学习模型和算法已经被开发出来,以在多种应用中正确地建模序列数据,例如在自然语言处理NLP)中,为了简便起见,我们在这里将其称为语言建模。在下一节中,我们将回顾用于语言建模的 Transformer,以帮助您在手中有一个相关想法或项目时开始从这些模型中受益。

用于语言建模的 Transformer

Transformer 在一篇名为 Attention is All You Need 的著名论文(Vaswani et al., 2017)中被引入,作为一种新的序列到序列数据建模任务的方法,例如将一种语言的陈述翻译成另一种语言(即机器翻译)。这些模型建立在自注意力概念之上,该概念有助于模型在训练过程中关注句子或信息序列中的其他重要部分。这种注意力机制有助于模型更好地理解输入序列元素之间的关系——例如,在语言建模中输入序列中的单词之间的关系。使用 Transformer 构建的模型通常比使用如 长短期记忆LSTM)和 循环神经网络RNNs)(Vaswani et al., 2017; Devlin et al., 2018)等前辈技术构建的模型表现更好。

图 13.6 展示了通过 Transformer 模型成功解决的四个传统语言建模问题:

图 13.6 – 深度学习技术在语言建模中成功应用的四个传统问题

图 13.6 – 深度学习技术在语言建模中成功应用的四个传统问题

一些著名的模型已经在这些或其他语言建模任务中直接使用或经过一些修改。以下是一些例子:

变压器模型也被用于其他领域和序列数据,例如电子健康记录(Li et al., 2020)、蛋白质结构预测(Jumpter et al., 2021)和时间序列异常检测(Xu et al., 2021)。

生成模型是机器学习建模中另一个重要的概念,变压器和 CNNs 已经成功地被用于此类模型。此类模型的例子包括 GPT 的不同版本,如 GPT-4 (openai.com/product/gpt-4)。你将在第十四章,“机器学习最新进展导论”中了解生成模型。有一个公开的大型语言模型LLM)排行榜,提供了最新的开源 LLM 模型列表(huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard)。你还可以查看 LLMs 的实用指南资源列表github.com/Mooler0410/LLMsPracticalGuide

我们不想深入探讨变压器背后的理论细节,但你在用 PyTorch 构建一个变压器架构的过程中,将会了解其组成部分。然而,其他广泛使用的性能指标也被用于序列数据和语言模型,例如以下内容:

这些指标可以帮助你评估你的序列模型。

分词

在训练和测试 transformer 模型之前,我们需要通过一个称为分词的过程将数据转换成正确的格式。分词是将数据分成更小的片段,如单词,如在单词分词中,或者字符,如在字符分词中。例如,句子“我喜欢读书”可以被转换成其包含的单词——即[“我”,“喜欢”,“读书”,“书籍”]。在构建分词器时,需要指定允许的最大标记数。例如,对于一个有 1,000 个标记的分词器,最频繁的 1,000 个单词将从提供给构建分词器的文本中用作标记。然后,每个标记将是这 1,000 个最频繁标记中的一个。之后,这些标记每个都会得到一个 ID;这些数字将在之后的神经网络模型训练和测试中使用。分词器标记之外的字词和字符会得到一个共同的值,例如,0 或 1。在文本分词中,另一个挑战是语句和单词序列的不同长度。为了应对这一挑战,在称为填充的过程中,通常会在每个单词序列或句子中的标记 ID 之前或之后使用一个共同的 ID,例如 0。

近期的大型语言模型(LLM)在分词过程中的标记数不同。例如,OpenAI 的 gpt-4-32k 模型提供 32,000 个标记 (help.openai.com/en/articles/7127966-what-is-the-difference-between-the-gpt-4-models),而 Claude 的 LLM 提供 100k 个标记 (www.anthropic.com/index/100k-context-windows)。标记数的差异可能会影响模型在相应文本相关任务中的性能。

常用的分词库包括 Hugging Face 的 transformer (huggingface.co/transformers/v3.5.1/main_classes/tokenizer.html)、SpaCy (spacy.io/) 和 NLTK (www.nltk.org/api/nltk.tokenize.html)。让我们通过练习 Hugging Face 的 transformer 库来更好地理解分词是如何工作的。

首先,让我们导入 transformers.AutoTokenizer() 并加载 bert-base-casedgpt2 预训练的分词器:

from transformers import AutoTokenizertokenizer_bertcased = AutoTokenizer.from_pretrained(
    'bert-base-cased')
tokenizer_gpt2 = AutoTokenizer.from_pretrained('gpt2')

为了练习这两个分词器,我们必须制作一个包含两个语句的列表,用于分词过程:

batch_sentences = ["I know how to use machine learning in my projects","I like reading books."]

然后,我们必须使用每个加载的分词器对这两个语句进行分词和编码,以得到相应的 ID 列表。首先,让我们使用 gpt2,如下所示:

encoded_input_gpt2 = tokenizer_gpt2(batch_sentences)

上述代码将这些两个语句转换成以下二维列表,其中包含每个语句中每个标记的 ID。例如,由于这两个语句都以“I”开头,因此它们的第一 ID 都是 40,这是gpt2分词器中“I”的标记:

[[40, 760, 703, 284, 779, 4572, 4673, 287, 616, 4493], [40, 588, 3555, 3835, 13]]

现在,我们将使用bert-base-cased,但这次,我们将要求分词器也使用填充来生成相同长度的 ID 列表,并以张量格式返回生成的 ID,这对于后续在神经网络建模中使用,例如使用 PyTorch,是合适的:

encoded_input_bertcased = tokenizer_bertcased(    batch_sentences, padding=True, return_tensors="pt")

以下张量显示了生成的两个句子 ID 的长度相同:

tensor([[ 101,  146, 1221, 1293, 1106, 1329, 3395, 3776,    1107, 1139, 3203,  102],
    [ 101,146, 1176, 3455, 2146, 119, 102, 0, 0, 0, 0, 0]])

我们还可以使用这些分词器的解码功能将 ID 转换回原始语句。首先,我们必须使用gpt2解码生成的 ID:

[tokenizer_gpt2.decode(input_id_iter) for input_id_iter in encoded_input_gpt2["input_ids"]]

这生成了以下语句,这些语句与原始输入语句相匹配:

['I know how to use machine learning in my projects', 'I like reading books.']

然而,假设我们使用bert-base-cased分词器进行 ID 解码,如下所示:

[tokenizer_bertcased.decode(input_id_iter) for input_id_iter in encoded_input_bertcased["input_ids"]]

生成的语句不仅包含原始语句,还显示了填充标记的解码方式。这显示为[PAD][CLS],这相当于句子的开始,以及[SEP],它显示了另一个句子开始的位置:

['[CLS] I know how to use machine learning in my projects [SEP]', '[CLS] I like reading books. [SEP] [PAD] [PAD] [PAD] [PAD] [PAD]']

语言嵌入

我们可以将每个单词的识别 ID 转换成更信息丰富的嵌入。这些 ID 本身可以用作 one-hot 编码,如在第第四章中讨论的,检测机器学习模型的性能和效率问题,其中每个单词都得到一个所有元素为零、对应单词的标记为 1 的长向量。但这些 one-hot 编码并没有提供任何关于单词之间关系的信息,这些关系在语言建模的单词级别上类似于数据点。

我们可以将词汇表中的单词转换为嵌入,这些嵌入可以用来捕捉它们之间的语义关系,并帮助我们的机器学习和深度学习模型从不同语言建模任务中的新信息丰富的特征中受益。尽管 BERT 和 GPT-2 等模型并非专为文本嵌入提取而设计,但它们可以用来为文本语料库中的每个单词生成嵌入。但还有其他一些较老的方法,如 Word2Vec(Mikolov 等人,2013 年)、GloVe(Pennington 等人,2014 年)和 fast-text(Bojanowski 等人,2017 年),它们是为嵌入生成而设计的。还有更多最近且更全面的词嵌入模型,如 Cohere(txt.cohere.com/embedding-archives-wikipedia/),您可以使用它来生成文本嵌入,用于嵌入和建模的不同语言。

使用预训练模型进行语言建模

我们可以将预训练模型导入到不同的深度学习框架中,例如 PyTorch,仅用于推理或使用新数据进行进一步的微调。在这里,我们想用 DistilBERT(Sanh et al., 2019)来练习这个过程,它是 BERT(Devlin et al., 2018)的一个更快、更轻量级的版本。具体来说,我们想使用基于 DistilBERT 架构的DistilBertForSequenceClassification()模型,该模型已被调整为用于序列分类任务。在这些过程中,模型会被训练并可用于对给定句子或陈述分配标签的任务的推理。此类标签分配的例子包括垃圾邮件检测或语义标记,如正面、负面和中立。

首先,我们将从torchtransformers库中导入必要的库和类:

import torchfrom torch.utils.data import DataLoader
from transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification, Trainer, TrainingArguments

然后,我们将加载imdb数据集,以便我们可以使用它来训练一个模型,作为一个微调版本的DistilBertForSequenceClassification()

from datasets import load_datasetdataset = load_dataset("imdb")

现在,我们可以在DistilBertTokenizerFast()分词器的基础上定义一个分词器函数,其中使用distilbert-base-uncased作为预训练的分词器:

tokenizer = DistilBertTokenizerFast.from_pretrained(    "distilbert-base-uncased")
def tokenize(batch):
    return tokenizer(batch["text"], padding=True,
        truncation=True, max_length=512)

然后,我们可以将imdb数据集的一小部分(1%)分离出来用于训练和测试,因为我们只想练习这个过程,而使用整个数据集在训练和测试方面需要花费很长时间:

train_dataset = dataset["train"].train_test_split(    test_size=0.01)["test"].map(tokenize, batched=True)
test_dataset = dataset["test"].train_test_split(
    test_size=0.01)["test"].map(tokenize, batched=True)

现在,我们可以在分类过程中指定标签的数量来初始化DistilBertForSequenceClassification()模型。这里,这是2

model = DistilBertForSequenceClassification.from_pretrained(    "distilbert-base-uncased", num_labels=2)

现在,我们可以使用imdb数据集的独立训练数据来训练模型,进行3个 epoch 的训练:

training_args = TrainingArguments(output_dir="./results",    num_train_epochs=3,per_device_train_batch_size=8,
    per_device_eval_batch_size=8, logging_dir="./logs")
trainer = Trainer(model=model, args=training_args,
    train_dataset=train_dataset,eval_dataset=test_dataset)
trainer.train()

这样,模型就已经训练好了,我们可以使用imdb数据集的独立测试集来评估它:

eval_results = trainer.evaluate()

这导致了 0.35 的评估损失。

在你的语言建模或推理任务中,有许多其他可用的模型可以使用(例如,PyTorch Transformers 库:pytorch.org/hub/huggingface_pytorch-transformers/)。还有其他序列模型,除了语言建模之外,适用于以下领域:

你可以在pytorch.org/tutorials/beginner/transformer_tutorial.html了解更多关于 transformer 建模以及如何在 PyTorch 中从头开始构建新架构,而不是使用预训练模型。

在本节中,你学习了将文本建模为一种序列数据类型。接下来,我们将介绍建模图,这是一种更复杂的数据结构。

使用深度神经网络建模图

我们可以将图视为我们用于机器学习和深度学习建模的几乎所有非表格数据的更通用结构。序列可以被认为是一维的(1D),而图像或图像形状数据可以被认为是二维的(2D)(参见图 13.7)。在本章的早期,你学习了如何在 Python 和 PyTorch 中开始从 CNN 和变压器中受益,用于序列和图像形状数据。但更通用的图不适用于这些两个图,它们具有预定义的结构(参见图 13.7),我们不能简单地使用 CNN 或序列模型来建模它们:

图 13.7 – 不同非结构化数据的图表示

图 13.7 – 不同非结构化数据的图表示

图有两个重要的元素,称为节点和边。边连接节点。图中的节点和边可以具有不同的特征,这些特征将它们彼此区分开来(参见图 13.8):

图 13.8 – 根据节点和边特征分类的图类型

图 13.8 – 根据节点和边特征分类的图类型

我们可以有节点具有特征、边具有权重或特征,或者边具有方向的图。无向图(具有无向边的图)在许多应用中很有用,例如社交媒体网络。假设社交媒体图中的每个节点都是一个节点,那么边可以确定哪些人相连。此类图中的节点特征可以是社交媒体网络中人们的不同特征,例如他们的年龄、研究领域或职称、居住城市等。有向图可用于不同的应用,例如因果建模,我们将在第十五章 相关性 与因果关系 中讨论。

如本节开头所述,CNN 和变压器等技术不能直接应用于图。因此,我们将回顾其他神经网络技术,这些技术可以帮助你在项目中建模图。

图神经网络

与 2D 图像和 1D 序列数据相比,图可能具有更复杂的结构。然而,我们可以使用与 CNN 和变压器模型相同的理念,通过依赖数据中的局部模式和关系来建模它们。我们可以在图中依赖局部模式,让神经网络从相邻节点中学习,而不是试图学习关于整个图的信息,这个图可能包含成千上万的节点和数百万的边。这就是图神经网络(GNNs)背后的理念。

我们可以使用 GNNs(图神经网络)来完成不同的任务,例如以下内容:

  • 节点分类:我们可以通过使用图神经网络(GNNs)来预测图中每个节点的类别。例如,如果你考虑一个城市中酒店的图,其中边是它们之间的最短路径,你可以预测在假期期间哪个酒店会被预订。或者如果你有化学背景,你可以使用节点分类来注释蛋白质中的氨基酸,使用蛋白质的 3D 结构(Abdollahi 等人,2023 年)。

  • 节点选择:对于 GNNs 的节点选择与 CNNs 的对象检测任务类似。我们可以设计 GNNs 来识别和选择具有特定特征的节点,例如在产品与消费者图中选择推荐产品的人。

  • 链接预测:我们可以旨在预测已存在节点或图中新节点之间的未知边。例如,在一个代表社交媒体网络的图中,链接预测可能是预测人与人之间的联系。然后,这些个人可以被推荐给对方,以便他们可以将彼此添加到他们的联系网络中。

  • 图分类:我们不是旨在预测或选择节点或边,而是可以设计 GNNs 来预测整个图的特征(chrsmrrs.github.io/datasets/)。在这种情况下,可能会有代表数据点的图,例如用于图分类 GNN 模型的药物分子。

存在着不同 GNNs 的一般分类法,例如 Wu 等人(2020 年)提出的分类法。但在这里,我们想专注于广泛使用的方法的例子,而不是过于技术性地涉及 GNNs 的不同类别。成功用于建模图的方 法的例子包括图卷积网络GCNs)(Kipf 和 Welling 在 2016 年),图样本和聚合GraphSAGE)(Hamilton 等人,2017 年),以及图注意力网络GATs)(Veličković等人,2018 年)。虽然大多数 GNN 技术考虑节点的特征,但并非所有都考虑边特征。消息传递神经网络MPNNs)是一种考虑节点和边特征的技术示例,最初是为生成药物分子的图而设计的(Gilmer 等人,2017 年)。

你可以从手头的数据构建图,或者使用公开可用的数据集,如斯坦福大型网络数据集收集SNAP)来练习不同的 GNN 技术。SNAP 拥有你可以下载并开始练习的最大的图数据集之一(snap.stanford.edu/data/)。

接下来,我们将使用 PyTorch 练习 GNN 建模,以帮助你更好地理解如何在 Python 中构建此类模型。

PyTorch Geometric 中的 GNNs

PyTorch Geometric 是一个基于 PyTorch 的 Python 库,它帮助您训练和测试 GNN。有一系列教程您可以从中受益,了解如何使用 PyTorch Geometric 进行 GNN 建模(pytorch-geometric.readthedocs.io/en/latest/notes/colabs.html)。在这里,我们将通过改编自这些教程之一的代码来练习节点分类问题(colab.research.google.com/drive/14OvFnAXggxB8vM4e8vSURUp1TaKnovzX?usp=sharing#scrollTo=0YgHcLXMLk4o)。

首先,让我们从 PyTorch Geometric 中的 Planetoid 导入 CiteSeer 引用网络数据集(Yang et al., 2016):

from torch_geometric.datasets import Planetoidfrom torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='data/Planetoid', name='CiteSeer',
    transform=NormalizeFeatures())
data = dataset[0]

现在,类似于初始化 FCNN 和 CNN 的神经网络,我们必须为 GNN 建模初始化一个 GCNet 类,但我们将使用 GCNConv 图卷积层而不是线性层和卷积层:

import torchfrom torch_geometric.nn import GCNConv
import torch.nn.functional as F
torch.manual_seed(123)
class GCNet(torch.nn.Module):
    def __init__(self, hidden_channels):
        super().__init__()
        self.gcn_layer1 = GCNConv(dataset.num_features,
            hidden_channels[0])
        self.gcn_layer2 = GCNConv(hidden_channels[0],
            hidden_channels[1])
        self.gcn_layer3 = GCNConv(hidden_channels[1],
            dataset.num_classes)
    def forward(self, x, edge_index):
        x = self.gcn_layer1(x, edge_index)
        x = x.relu()
        x = F.dropout(x, p=0.3, training=self.training)
        x = self.gcn_layer2(x, edge_index)
        x = x.relu()
        x = self.gcn_layer3(x, edge_index)
        return x

在上一节课中,我们使用了三个 GCNConv 层,结合 ReLU 激活函数和 dropout 进行正则化。

现在,我们可以使用定义的 GCNet 类来初始化我们的模型,其隐藏层的大小为 128 和 16,在这段实践代码中都是任意的。我们还需要在指定算法的同时初始化一个优化器,在这个例子中是 Adam,学习率为 0.01,权重衰减为 1e-4 以进行正则化:

model = GCNet(hidden_channels=[128, 16])optimizer = torch.optim.Adam(model.parameters(), lr=0.01,
    weight_decay=1e-4)
criterion = torch.nn.CrossEntropyLoss()

现在,我们可以定义我们的训练函数,它将被用于单 epoch 训练:

def train():        model.train()
        optimizer.zero_grad()
        out = model(data.x, data.edge_index)
        loss = criterion(out[data.train_mask],
            data.y[data.train_mask])
        loss.backward()
        optimizer.step()
        return loss

这样,我们就准备好了一系列的 epoch 并开始训练模型。请注意,以下用于训练模型 400 个 epoch 的循环可能需要很长时间:

import numpy as npepoch_list = []
loss_list = []
for epoch in np.arange(1, 401):
    loss = train()
    if epoch%20 == 0:
        print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')
        epoch_list.append(epoch)
        loss_list.append(loss.detach().numpy())

下面的图显示了训练过程中的学习曲线(损失与 epoch 的关系):

图 13.9 – 在 CiteSeer 数据集上示例 GCN 模型的学习曲线

图 13.9 – 在 CiteSeer 数据集上示例 GCN 模型的学习曲线

我们还可以在数据集的测试部分测试模型,如下所示:

model.eval()pred = model(data.x, data.edge_index).argmax(dim=1)
test_correct = pred[data.test_mask] ==
    data.y[data.test_mask]
test_acc = int(test_correct.sum()) / int(
    data.test_mask.sum())

这导致准确率为 0.655。我们还可以生成测试集上预测的混淆矩阵:

from sklearn.metrics import confusion_matrixcf = confusion_matrix(y_true = data.y, y_pred = model(
    data.x, data.edge_index).argmax(dim=1))
import seaborn as sns
sns.set()
sns.heatmap(cf, annot=True, fmt="d")

这导致以下矩阵,以热图的形式展示。尽管大多数数据点的预测和真实类别匹配,但其中许多被错误分类,并且总结在混淆矩阵的对角线元素之外:

图 13.10 – 在 CiteSeer 数据集上示例 GCN 模型的测试集预测混淆矩阵

图 13.10 – 在 CiteSeer 数据集上示例 GCN 模型的测试集预测混淆矩阵

在本节中,我们讨论了使用深度学习建模不同数据类型和问题的技术。现在,你准备好学习更多关于这些高级技术并在你的项目中使用它们了。

摘要

在本章中,你学习了高级深度学习技术,包括卷积神经网络(CNNs)、转换器(transformers)和图神经网络(GNNs)。你了解了一些使用这些技术开发的广泛使用或著名的模型。你还练习了使用 Python 和 PyTorch 从头开始构建这些高级模型或微调它们。这些知识帮助你更深入地了解这些技术,并在你的项目中开始使用它们,以便你可以对图像和图像形状数据进行建模,对文本和序列数据进行建模,以及对图进行建模。

在下一章中,你将学习到最近在生成建模和提示工程以及自监督学习方面的进展,这些进展将帮助你开发项目或为你提供开发有趣和有用的工具和应用程序的机会。

问题

  1. 你可以使用 CNNs 和 GNNs 解决哪些问题的示例?

  2. 应用卷积是否可以保留图像中的局部模式?

  3. 减少标记数是否会导致语言模型中的错误更多?

  4. 文本标记过程中的填充(padding)是什么?

  5. 我们在 PyTorch 中为 CNNs 和 GNNs 构建的网络架构类是否相似?

  6. 在构建 GNNs 时,何时需要边缘特征?

参考文献

  • He, Kaiming 等人。用于图像识别的深度残差学习。IEEE 计算机视觉和模式识别会议论文集。2016 年。

  • Tan, Mingxing 和 Quoc Le. Efficientnet:重新思考卷积神经网络的模型缩放。机器学习国际会议。PMLR,2019 年。

  • Howard, Andrew G.等人。Mobilenets:用于移动视觉应用的效率卷积神经网络。arXiv 预印本 arXiv:1704.04861(2017 年)。

  • Sandler, Mark 等人。Mobilenetv2:倒置残差和线性瓶颈。IEEE 计算机视觉和模式识别会议论文集。2018 年。

  • Chollet, François。Xception:使用深度可分离卷积的深度学习。IEEE 计算机视觉和模式识别会议论文集。2017 年。

  • Ronneberger, Olaf,Philipp Fischer 和 Thomas Brox. U-net:用于生物医学图像分割的卷积网络。医学图像计算和计算机辅助干预(MICCAI 2015):第 18 届国际会议,德国慕尼黑,2015 年 10 月 5-9 日,第 III 部分 18。Springer 国际出版社,2015 年。

  • He, Kaiming 等人。Mask r-cnn。IEEE 国际计算机视觉会议。2017 年。

  • Chen, Liang-Chieh 等人。Deeplab:使用深度卷积网络、扩张卷积和全连接 crfs 进行语义图像分割。IEEE 模式分析杂志第 40 卷第 4 期(2017 年):834-848。

  • Zhao, Hengshuang 等人。金字塔场景解析网络。IEEE 计算机视觉和模式识别会议论文集。2017 年。

  • Ren, Shaoqing 等人。Faster r-cnn:使用区域提议网络的实时目标检测。神经信息处理系统进展第 28 卷(2015 年)。

  • Redmon, Joseph, et al. 一次检测:统一、实时目标检测. IEEE 计算机视觉与模式识别会议论文集。2016.

  • Dong, Chao, et al. 使用深度卷积网络进行图像超分辨率. IEEE 模式分析杂志 38.2 (2015): 295-307.

  • Dong, Chao, Chen Change Loy, 和 Xiaoou Tang. 加速超分辨率卷积神经网络. 计算机视觉–ECCV 2016:第 14 届欧洲会议,荷兰阿姆斯特丹,2016 年 10 月 11-14 日,会议论文集第 II 部分 14. Springer International Publishing, 2016.

  • Lim, Bee, et al. 用于单图像超分辨率的增强深度残差网络. IEEE 计算机视觉与模式识别会议论文集 workshops。2017.

  • Isola, Phillip, et al. 使用条件对抗网络进行图像到图像翻译. IEEE 计算机视觉与模式识别会议论文集。2017.

  • Zhu, Jun-Yan, et al. 使用循环一致对抗网络的无配对图像到图像翻译. IEEE 国际计算机视觉会议论文集。2017.

  • Gatys, Leon A., Alexander S. Ecker, 和 Matthias Bethge. 使用卷积神经网络进行图像风格迁移. IEEE 计算机视觉与模式识别会议论文集。2016.

  • Huang, Xun, 和 Serge Belongie. 实时自适应实例归一化在任意风格转换中的应用. IEEE 国际计算机视觉会议论文集。2017.

  • Schlegl, Thomas, et al. 无监督异常检测与生成对抗网络引导标记发现. 医学图像处理:第 25 届国际会议,IPMI 2017,美国北卡罗来纳州博恩,2017 年 6 月 25-30 日,会议论文集。Cham: Springer International Publishing, 2017.

  • Ruff, Lukas, et al. 深度单类分类. 国际机器学习会议。PMLR,2018.

  • Zhou, Chong, 和 Randy C. Paffenroth. 使用鲁棒深度自编码器的异常检测. 第 23 届 ACM SIGKDD 国际知识发现和数据挖掘会议论文集。2017.

  • Baek, Youngmin, et al. 文本检测中的字符区域感知. IEEE/CVF 计算机视觉与模式识别会议论文集。2019.

  • Zhou, Xinyu, et al. East:一种高效准确的场景文本检测器. IEEE 计算机视觉与模式识别会议论文集。2017.

  • Tran, Du, et al. 使用 3D 卷积网络学习时空特征. IEEE 国际计算机视觉会议论文集。2015.

  • Carreira, Joao, 和 Andrew Zisserman. 动作识别何去何从?一个新的模型和 Kinetics 数据集. IEEE 计算机视觉与模式识别会议论文集。2017.

  • Feichtenhofer, Christoph, et al. 用于视频识别的 Slowfast 网络. IEEE/CVF 国际计算机视觉会议论文集。2019.

  • LeCun, Yann, 等人. 使用反向传播网络进行手写数字识别. 神经信息处理系统进展 2 (1989).

  • Vaswani, Ashish, 等人. 注意力即一切. 神经信息处理系统进展 30 (2017).

  • Devlin, Jacob, 等人. BERT: 用于语言理解的深度双向变换器预训练. arXiv 预印本 arXiv:1810.04805 (2018).

  • Touvron, Hugo, 等人. Llama: 开放且高效的通用语言模型. arXiv 预印本 arXiv:2302.13971 (2023).

  • Li, Yikuan, 等人. BEHRT: 用于电子健康记录的变换器. 科学报告 10.1 (2020): 1-12.

  • Jumper, John, 等人. 使用 AlphaFold 进行高度精确的蛋白质结构预测. 自然 596.7873 (2021): 583-589.

  • Xu, Jiehui, 等人. 异常变换器:基于关联差异的时间序列异常检测. arXiv 预印本 arXiv:2110.02642 (2021).

  • Yuan, Li, 等人. 从零开始训练图像 Net 上的视觉变换器:tokens-to-token vit. 2021 年 IEEE/CVF 国际计算机视觉会议论文集. 2021.

  • Liu, Yinhan, 等人. Roberta: 一种鲁棒优化的 BERT 预训练方法. arXiv 预印本 arXiv:1907.11692 (2019).

  • Lewis, Mike, 等人. Bart: 用于自然语言生成、翻译和理解的去噪序列到序列预训练. arXiv 预印本 arXiv:1910.13461 (2019).

  • Radford, Alec, 等人. 通过生成预训练改进语言理解. (2018).

  • Raffel, Colin, 等人. 通过统一的文本到文本变换器探索迁移学习的极限. 机器学习研究杂志 21.1 (2020): 5485-5551.

  • Sanh, Victor, 等人. DistilBERT,BERT 的精炼版本:更小、更快、更便宜、更轻. arXiv 预印本 arXiv:1910.01108 (2019).

  • Yang, Zhilin, 等人. Xlnet: 用于语言理解的广义自回归预训练. 神经信息处理系统进展 32 (2019).

  • Mikolov, Tomas, 等人. 在向量空间中高效估计词表示. arXiv 预印本 arXiv:1301.3781 (2013).

  • Pennington, Jeffrey, Richard Socher, 和 Christopher D. Manning. Glove: 用于词表示的全局向量. 2014 年自然语言处理实证方法会议论文集 (EMNLP). 2014.

  • Bojanowski, Piotr, 等人. 通过子词信息丰富词向量. 计算语言学协会会刊 5 (2017): 135-146.

  • Wu, Zonghan, 等人. 图神经网络综述. 电气和电子工程师协会神经网络和机器学习系统交易 32.1 (2020): 4-24.

  • Abdollahi, Nasim, 等人. NodeCoder: 一种基于图的机器学习平台,用于预测建模蛋白质结构的活性位点. arXiv 预印本 arXiv:2302.03590 (2023).

  • Kipf, Thomas N., 和 Max Welling. 使用图卷积网络进行半监督分类. arXiv 预印本 arXiv:1609.02907 (2016).

  • Hamilton, Will, Zhitao Ying, and Jure Leskovec. 在大图上进行归纳表示学习. 神经信息处理系统进展 30 (2017).

  • Velickovic, Petar, et al. 图注意力网络. stat 1050.20 (2017): 10-48550.

  • Gilmer, Justin, et al. 神经消息传递在量子化学中的应用. 机器学习国际会议. PMLR, 2017.

  • Yang, Zhilin, William Cohen, and Ruslan Salakhudinov. 重新审视使用图嵌入的半监督学习. 机器学习国际会议. PMLR, 2016.