机器学习研讨会-二-

42 阅读37分钟

机器学习研讨会(二)

原文:annas-archive.org/md5/434f45a68c082426533e009b496c8b55

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:4. 监督学习算法:预测年收入

概述

在本章中,我们将研究三种不同的用于分类的监督学习算法。我们还将使用这些算法解决一个监督学习分类问题,并通过比较三种不同算法的结果进行误差分析。

到本章末尾,您将能够确定具有最佳性能的算法。

引言

在上一章中,我们讨论了处理监督学习数据问题涉及的关键步骤。这些步骤旨在创建高性能算法,正如前一章所解释的那样。

本章重点介绍将不同算法应用于真实数据集的过程,其底层目标是应用我们之前学到的步骤,选择适用于案例研究的表现最佳算法。因此,您将预处理和分析数据集,然后使用不同的算法创建三个模型。将比较这些模型以衡量它们的性能。

我们将使用的 Census Income 数据集包含个人的人口统计和财务信息,可以用于预测个人收入水平。通过创建能够预测这一结果的模型,可以确定一个人是否可以预先批准接收贷款。

探索数据集

真实应用对于巩固知识至关重要。因此,本章包括一个涉及分类任务的真实案例研究,其中将应用您在前一章学到的关键步骤,以选择表现最佳的模型。

要完成这个任务,将使用 Census Income 数据集,该数据集可以在 UC Irvine 机器学习库中找到。

注意

将在以下部分以及本章的活动中使用的数据集,可以在本书的 GitHub 存储库中找到packt.live/2xUGShx

引用:Dua, D. 和 Graff, C. (2019). UCI Machine Learning Repository [archive.ics.uci.edu/ml]。Irvine, CA:加利福尼亚大学,信息与计算机科学学院。

您可以从本书的 GitHub 存储库下载数据集。或者,要从原始来源下载数据集,请按照以下步骤操作:

  1. 访问以下链接:archive.ics.uci.edu/ml/datasets/Census+Income

  2. 首先,点击Data Folder链接。

  3. 在本章中,将使用adult.data可用的数据。点击此链接后,将触发下载。将其保存为.csv文件。

    注意

    打开文件并在每一列上添加列名,以便于预处理。例如,第一列应该有Age的列名,按照数据集中提供的特征。这些可以在前面的链接中看到,在属性信息下。

理解数据集

为了构建一个准确拟合数据的模型,理解数据集的不同细节是非常重要的,如前几章所述。

首先,评审可用数据以了解数据集的大小和要开发的监督学习任务类型:分类或回归。接下来,应明确界定研究的目的,即使它显而易见。对于监督学习,目的与类标签密切相关。最后,分析每个特征,以便我们了解其类型,便于预处理。

Census Income 数据集是一个关于成人的群体统计数据集,来自美国 1994 年人口普查数据库的提取数据。本章仅使用在adult.data链接下可用的数据。该数据集包含 32,561 个实例,14 个特征和 1 个二进制类标签。考虑到类标签是离散的,我们的任务是实现不同观察值的分类。

注意

以下对数据集的探索不需要任何编码,只需要通过在 Excel 或类似程序中打开数据集进行简单评估。

通过对数据的快速评估,可以观察到某些特征存在缺失值,表现为问号。这在处理在线可用数据集时很常见,应该通过将符号替换为空值(而不是空格)来处理。其他常见的缺失值形式包括NULL值和短横线。

要在 Excel 中编辑缺失值符号,请使用替换功能,如下所示:

  • ?)。

  • 替换为:保持空白(不要输入空格)。

这样,一旦我们将数据集导入代码中,NumPy 将能够找到缺失值并处理它们。

该数据集的预测任务是确定一个人年收入是否超过 50K 美元。根据这一点,两个可能的结果标签是>50K(大于 50K)或<=50K(小于或等于 50K)。

数据集中每个特征的简要解释如下表所示:

图 4.1:数据集特征分析

图 4.1:数据集特征分析

注意

*出版商说明:性别和种族在本研究进行时会影响个人的收入潜力。然而,为了本章的目的,我们决定在练习和活动中排除这些类别。

我们认识到,由于偏见和歧视性做法,无法将性别、种族、教育和职业机会等问题完全分开。在这些练习的预处理阶段从数据集中删除某些特征,并非忽视这些问题,也不是忽视民权领域中组织和个人所做的有价值的工作。

我们强烈建议你考虑数据及其使用方式的社会政治影响,并思考如何通过使用历史数据将过去的偏见传递到新的算法中。

从上表中,可以得出以下结论:

  • 有五个特征与研究无关:fnlwgteducationrelationshipracesex。在进行预处理和模型训练之前,必须从数据集中删除这些特征。

  • 剩余的特征中,有四个是定性值。考虑到许多算法不考虑定性特征,这些值应当以数字形式表示。

利用我们在前几章学到的概念,可以处理上述语句以及异常值和缺失值的预处理过程。以下步骤解释了这个过程的逻辑:

  1. 你需要导入数据集,并删除与研究无关的特征。

  2. 你应该检查是否存在缺失值。考虑到缺失值最多的特征(occupation,有 1,843 个缺失值),由于这些缺失值仅占整个数据集的 5%或更少,因此不需要删除或替换它们。

  3. 你必须将定性值转换为其数值表示。

  4. 你应该检查是否存在异常值。在使用三倍标准差法检测异常值时,具有最多异常值的特征是capital-loss,包含 1,470 个异常值。由于这些异常值占整个数据集的比例不到 5%,因此它们可以不做处理,不会影响模型的结果。

上述过程将原始数据集转换为一个新数据集,包含 32,561 个实例(因为没有删除任何实例),但只有 9 个特征和一个类别标签。所有的值应该是数字形式。按照以下代码片段,使用 pandas 的to_csv函数将预处理后的数据集保存为文件:

preprocessed_data.to_csv("census_income_dataset_preprocessed.csv")

上述代码片段将处理过的数据存储在 Pandas DataFrame 中,并将其保存为 CSV 文件。

注意

确保你执行了前述的预处理步骤,因为这是本章中将用于训练模型的数据集。

要回顾这些步骤,请访问本书的 GitHub 仓库,在名为Chapter04的文件夹下,查看名为Census income dataset preprocessing的文件。

朴素贝叶斯算法

朴素贝叶斯是一种基于贝叶斯定理的分类算法,它天真地假设特征之间是独立的,并且对所有特征赋予相同的权重(重要性程度)。这意味着该算法假设没有任何特征彼此相关或影响对方。例如,尽管在预测一个人的年龄时,体重和身高在某种程度上是相关的,但该算法仍假设每个特征是独立的。此外,算法将所有特征视为同等重要。例如,尽管教育程度可能比一个人孩子的数量更能影响其收入,算法仍然认为这两个特征同样重要。

注意

贝叶斯定理是一种计算条件概率的数学公式。欲了解更多关于该定理的信息,请访问以下网址:plato.stanford.edu/entries/bayes-theorem/

尽管现实生活中的数据集包含了不等重要且相互之间不独立的特征,朴素贝叶斯算法在科学家中依然广受欢迎,因为它在大型数据集上表现得出奇的好。而且,由于算法的简易性,它运行速度快,因此可以应用于需要实时预测的问题。此外,它在文本分类中也得到了广泛使用,因为它常常超越了更复杂的算法。

朴素贝叶斯算法是如何工作的?

算法将输入数据转化为每个类标签与每个特征的发生情况总结,然后利用这些信息计算在给定一组特征组合的情况下,某一事件(类标签)发生的可能性。最后,该可能性会与其他类标签的可能性进行归一化处理。结果是一个实例属于每个类标签的概率。所有概率的总和必须为 1,具有较高概率的类标签是算法作为预测结果选择的标签。

让我们举个例子,来看一下下面表格中的数据:

图 4.2:表 A - 输入数据,表 B - 发生次数

图 4.2:表 A - 输入数据,表 B - 发生次数

表 A 表示输入到算法中的数据,用于构建模型。表 B 则指的是算法隐式使用的事件发生次数,用于计算概率。

为了计算在给定一组特征的情况下,事件发生的可能性,算法会将每个特征下事件发生的概率与该事件的总发生概率相乘,计算公式如下:

Likelihood [A1|E] = P[A1|E1] * P[A1|E2] * … * P[A1|En] * P[A1]

这里,A1 代表一个事件(类标签之一),E表示特征集,其中 E1 是第一个特征,En 是数据集中的最后一个特征。

注意

这些概率的相乘只能通过假设特征之间是独立的来进行。

前面的公式是针对所有可能的结果(所有类别标签)进行计算的,然后标准化每个结果的概率,计算公式如下:

图 4.3:计算标准化概率的公式

图 4.3:计算标准化概率的公式

对于图 4.2中的例子,给定一个新的实例,其中天气为晴朗,温度为凉爽,概率的计算如下:

图 4.4:示例数据集的似然性和概率计算

图 4.4:示例数据集的似然性和概率计算

通过查看前面的公式,可以得出结论,预测结果应该是yes

需要提到的是,对于连续特征,发生情况的汇总是通过创建范围来进行的。例如,对于一个价格特征,算法可能会统计价格低于 100K 的实例数量,以及价格高于 100K 的实例数量。

此外,如果某个特征的某个值从未与某个结果相关联,算法可能会遇到一些问题。这是一个主要问题,因为给定该特征时,结果的概率将为零,这会影响整个计算。在前面的例子中,对于预测一个实例,其中天气为温和,温度为凉爽,给定特征集的no概率将等于零,因为给定温和天气时,no的概率为零,因为没有温和天气对应no结果。

为了避免这种情况,应使用拉普拉斯估计器技术。在这里,表示给定特征下事件发生概率的分数,P[A|E1*]*,通过在分子上加 1,同时在分母上加上该特征的可能值的数量来进行修改。

对于这个例子,使用拉普拉斯估计器来预测天气为温和、温度为凉爽的新实例的预测结果,可以按如下方式进行:

图 4.5:使用拉普拉斯估计器计算示例数据集的似然性和概率

图 4.5:使用拉普拉斯估计器计算示例数据集的似然性和概率

在这里,计算在温和天气下出现yes的分数,从 2/7 变为 3/10,这是由于在分子上加了 1,在分母上加了 3(对应晴朗温和雨天)。其他计算事件发生概率的分数也有相同的变化,前提是给定某一特征。请注意,计算事件独立于任何特征发生的概率的分数没有改变。

然而,正如你到目前为止所学到的,scikit-learn 库允许你训练模型,然后使用它们进行预测,而无需手动编写数学公式。

练习 4.01:应用朴素贝叶斯算法

现在,让我们将朴素贝叶斯算法应用于 Fertility 数据集,该数据集旨在判断个体的生育能力是否受到其人口统计特征、环境条件和过去的医疗状况的影响。按照以下步骤完成此练习:

注意

对于本章中的练习和活动,你需要在系统中安装 Python 3.7、NumPy、Jupyter、Pandas 和 scikit-learn。

  1. Fertility 数据集下载数据。进入链接并点击Data Folder。点击fertility_Diagnosis.txt,这将触发下载。将其保存为.csv文件。

    注意

    该数据集也可以在本书的 GitHub 仓库中找到:packt.live/39SsSSN

    数据集来自 UC Irvine 机器学习库:David Gil, Jose Luis Girela, Joaquin De Juan, M. Jose Gomez-Torres 和 Magnus Johnsson。使用人工智能方法预测精液质量。《专家系统应用》期刊。

  2. 打开一个 Jupyter Notebook 来实现这个练习。导入 pandas,并从 scikit-learn 的naive_bayes模块导入GaussianNB类:

    import pandas as pd
    from sklearn.naive_bayes import GaussianNB
    
  3. 阅读你在第一步下载的.csv文件。确保在read_csv函数中添加header参数,并将其设置为None,因为该数据集不包含标题行:

    data = pd.read_csv("fertility_Diagnosis.csv", header=None)
    
  4. 将数据拆分为XY,因为类标签位于索引为 9 的列下。使用以下代码来完成此操作:

    X = data.iloc[:,:9]
    Y = data.iloc[:,9]
    
  5. 实例化我们之前导入的GaussianNB类。接下来,使用fit方法,使用XY训练模型:

    model = GaussianNB()
    model.fit(X, Y)
    

    运行此脚本的输出如下:

    GaussianNB(priors=None, var_smoothing=1e-09)
    

    这表示类的实例化成功。括号内的信息代表用于参数的值,这些值是类接受的超参数。

    例如,对于GaussianNB类,可以设置每个类别标签的先验概率,并设置一个平滑参数来稳定方差。然而,该模型在初始化时没有设置任何参数,这意味着它将使用每个参数的默认值,对于priorsNone,对于平滑超参数是1e-09

  6. 最后,使用你之前训练的模型对一个新的实例进行预测,给定每个特征的以下值:−0.330.6901100.800.88。使用以下代码来进行预测:

    pred = model.predict([[-0.33,0.69,0,1,1,0,0.8,0,0.88]])
    print(pred)
    

    请注意,我们将值放入双重方括号中,因为predict函数将预测值作为数组的数组输入,其中第一组数组对应于要预测的新实例列表,第二个数组表示每个实例的特征列表。

    上述代码片段的输出如下:

    ['N']
    

    对该对象的预测类别为 N,这意味着该对象的生育能力没有受到影响。

    注意

    若要访问此特定部分的源代码,请参阅 packt.live/2Y2wW0c

    你也可以在 packt.live/3e40LTt 上在线运行这个示例。你必须执行整个 Notebook 才能获得期望的结果。

你已经成功训练了一个 Naïve Bayes 模型,并对新观测值进行了预测。

活动 4.01:为我们的 Census Income 数据集训练 Naïve Bayes 模型

为了在真实数据集上测试不同的分类算法,考虑以下场景:你为一家银行工作,他们决定实现一个能够预测个人年收入的模型,并根据此信息决定是否批准贷款。你得到一个包含 32,561 个合适观测值的数据集,数据集已经过预处理。你的任务是训练三个不同的模型,并确定哪个最适合此案例研究。第一个模型是构建一个高斯 Naïve Bayes 模型。使用以下步骤完成这个活动:

  1. 在 Jupyter Notebook 中,导入所有需要的元素来加载和拆分数据集,以及训练 Naïve Bayes 算法。

  2. 加载预处理后的 Census Income 数据集。接下来,通过创建两个变量 XY,将特征与目标变量分开。

    注意

    预处理后的 Census Income 数据集可以在本书的 GitHub 仓库中找到,地址是 packt.live/2JMhsFB。它包含了本章开始时预处理过的 Census Income 数据集。

  3. 将数据集划分为训练集、验证集和测试集,使用 10% 的拆分比例。

    注意

    当所有三个数据集都是从同一个数据集中创建时,就不需要额外创建训练/验证集来测量数据不匹配的情况。此外,值得注意的是,可以尝试不同的拆分比例,因为前一章解释的百分比并不是固定不变的。尽管这些比例通常有效,但在构建机器学习模型时,重要的是要接受在不同层次上进行实验。

  4. 使用 fit 方法在训练集(X_trainY_train)上训练 Naïve Bayes 模型。

  5. 最后,使用你之前训练的模型对具有以下每个特征值的新实例进行预测:3961340217404038

    对于该个体的预测结果应该为零,意味着该个体的收入可能小于或等于 50K。

    注意

    在本章的所有活动中使用相同的 Jupyter Notebook,这样你就可以在相同的数据集上比较不同模型的表现。

    这个活动的解决方案可以在第 236 页找到。

决策树算法

决策树算法通过一个类似树形结构的序列进行分类。它通过将数据集划分为小的子集来工作,这些子集作为指导来开发决策树节点。这些节点可以是决策节点或叶节点,其中前者代表一个问题或决策,后者代表做出的决策或最终结果。

决策树算法如何工作?

考虑到我们刚才提到的内容,决策树不断根据决策节点中定义的参数来划分数据集。决策节点有分支从其发出,每个决策节点可以有两个或更多的分支。这些分支代表不同的可能答案,定义了数据如何被划分。

例如,考虑以下表格,它展示了一个人是否有未结学生贷款,基于他们的年龄、最高教育水平和当前收入:

图 4.6:学生贷款数据集

图 4.6:学生贷款数据集

基于前述数据构建的决策树的一种可能配置如下图所示,其中浅色框表示决策节点,箭头是代表每个决策节点答案的分支,深色框表示按照序列进行的实例的结果:

图 4.7:决策树中表示的数据

图 4.7:决策树中表示的数据

为了进行预测,在构建决策树之后,模型会逐个实例地跟随与该实例特征匹配的序列,直到到达一个叶节点,即结果。根据这一点,分类过程从根节点(最上面那个)开始,沿着描述该实例的分支进行。该过程一直持续,直到到达叶节点,表示该实例的预测结果。

例如,一个40 岁以上、收入低于$150,000、教育水平为学士的人可能没有学生贷款;因此,分配给该类的标签为

决策树可以处理定量和定性特征,考虑到连续特征会以区间的形式处理。此外,叶节点可以处理分类的或连续的类标签;对于分类类标签,进行分类;而对于连续类标签,要处理的任务是回归。

练习 4.02:应用决策树算法

在本练习中,我们将应用决策树算法到生育数据集,目的是确定个体的生育水平是否受到其人口统计信息、环境条件和以往健康状况的影响。按照以下步骤完成此练习:

  1. 打开一个 Jupyter Notebook 来实现这个练习,并导入pandas,以及从 scikit-learn 的tree模块中导入DecisionTreeClassifier类:

    import pandas as pd
    from sklearn.tree import DecisionTreeClassifier
    
  2. 加载你在练习 4.01中下载的fertility_Diagnosis数据集,应用朴素贝叶斯算法。确保在read_csv函数中添加header参数并设置为None,因为数据集没有包含标题行:

    data = pd.read_csv("fertility_Diagnosis.csv", header=None)
    
  3. 将数据拆分为XY,因为类别标签位于索引为9的列下。使用以下代码:

    X = data.iloc[:,:9]
    Y = data.iloc[:,9]
    
  4. 实例化DecisionTreeClassifier类。接下来,使用fit函数训练模型,使用XY

    model = DecisionTreeClassifier()
    model.fit(X, Y)
    

    再次运行前面的代码片段,输出结果将会显示。这个输出总结了定义模型的条件,通过打印出模型使用的每个超参数的值,如下所示:

    DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None,
                           criterion='gini', max_depth=None,
                           max_features=None, max_leaf_nodes=None,
                           min_impurity_decrease=0.0,
                           min_impurity_split=None,
                           min_samples_leaf=1, min_samples_split=2,
                           min_weight_fraction_leaf=0.0,
                           presort='deprecated',
                           random_state=None, splitter='best')
    

    由于模型在没有设置任何超参数的情况下被实例化,因此总结将显示每个超参数使用的默认值。

  5. 最后,使用你之前训练的模型对相同的实例进行预测,这些实例在练习 4.01中也使用过,应用朴素贝叶斯算法−0.330.6901100.800.88

    使用以下代码来实现:

    pred = model.predict([[-0.33,0.69,0,1,1,0,0.8,0,0.88]])
    print(pred)
    

    预测的输出结果如下:

    ['N']
    

    再次,模型预测显示受试者的生育能力没有受到影响。

    注意

    要访问此特定部分的源代码,请参考packt.live/3hDlvns

    你还可以在线运行这个示例,网址为packt.live/3fsVw07。你必须执行整个 Notebook 才能获得预期的结果。

你已经成功训练了一个决策树模型,并对新数据进行了预测。

活动 4.02:为我们的普查收入数据集训练决策树模型

你继续构建一个能够预测个人年收入的模型。使用预处理过的普查收入数据集,你选择了构建一个决策树模型:

  1. 打开你在之前活动中使用的 Jupyter Notebook,并从 scikit-learn 中导入决策树算法。

  2. 使用来自 scikit-learn 的DecisionTreeClassifier类的fit方法训练模型。使用来自前一个活动的训练集数据(X_trainY_train)来训练模型。

  3. 最后,使用你训练的模型对一个新的实例进行预测,该实例的每个特征值如下:3961340217404038

    对于该个体的预测应该为零,意味着该个体的收入可能小于或等于 50K。

    注意

    这个活动的解决方案可以在第 237 页找到。

支持向量机算法

支持向量机SVM)算法是一种分类器,它找到一个有效地将观察值分隔到各自类别标签的超平面。算法首先将每个实例放入具有n维度的数据空间,其中n表示特征的数量。接着,它会画出一条虚拟的直线,这条线清楚地将属于同一类别标签的实例与属于其他类别标签的实例分开。

支持向量指的是给定实例的坐标。根据这一点,支持向量机是有效地在数据空间中将不同支持向量分开的边界。

对于二维数据空间,超平面是将数据空间分为两个部分的直线,每部分代表一个类别标签。

SVM 算法是如何工作的?

以下图示展示了一个简单的 SVM 模型示例。三角形和圆形的数据点代表输入数据集中的实例,其中形状定义了每个实例所属的类别标签。虚线表示超平面,清晰地分隔了数据点,这个超平面是基于数据点在数据空间中的位置来定义的。此线用于分类未见过的数据,正如图中的方块所示。通过这种方式,位于该线左侧的新实例将被分类为三角形,而位于右侧的实例将被分类为圆形。

特征数量越多,数据空间的维度就越多,这将使得模型的可视化变得不可能:

图 4.8: SVM 模型的图示例

图 4.8: SVM 模型的图示例

尽管该算法看似非常简单,但其复杂性体现在算法绘制适当超平面的方式上。这是因为该模型可以概括成数百个具有多个特征的观察数据。

为了选择正确的超平面,算法遵循以下规则,其中规则 1规则 2更为重要:

  • 规则 1:超平面必须最大化实例的正确分类。这基本上意味着,最佳的直线是那条能够有效地将不同类别标签的数据点分开,同时将属于同一类别的数据点保持在一起的直线。

    例如,在下图中,尽管两条直线都能够将大多数实例分入正确的类别标签,但线 A 会被模型选为比线 B 更好地分隔类别的超平面,后者未能正确分类两个数据点:

    图 4.9: 解释规则 1 的超平面示例

图 4.9: 解释规则 1 的超平面示例

  • 规则 2:超平面必须最大化其到任一类别标签最近数据点的距离,这也被称为间隔。该规则有助于使模型更加健壮,这意味着模型能够对输入数据进行泛化,从而能够高效处理未见过的数据。此规则在防止新实例被错误标记时尤为重要。

    例如,通过查看下图,可以得出结论,两个超平面都符合规则 1。然而,选择了 A 线,因为它最大化了与两个类别最近数据点的距离,相比之下,B 线与其最近数据点的距离较小:

    图 4.10:解释规则 2 的超平面示例

图 4.10:解释规则 2 的超平面示例

默认情况下,SVM 算法使用线性函数来分隔输入数据的点。然而,可以通过更改算法的核类型来修改此配置。例如,考虑以下图示:

注意

对于 scikit-learn 的 SVM 算法,核指的是用于分隔数据点的数学函数,可以是线性的、多项式的或 sigmoid 函数等。要了解更多关于该算法的参数,可以访问以下网址:scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html#sklearn.svm.SVC

图 4.11:示例观测值

图 4.11:示例观测值

为了将这些观测值分隔开,模型必须绘制一个圆或其他类似的形状。该算法通过使用核函数(数学函数)来处理这一问题,核函数可以向数据集引入额外的特征,从而修改数据点的分布,使其能够通过一条直线将其分隔开来。为此有几种不同的核可供选择,选择合适的核函数需要通过试验和错误来进行,以便找到最适合分类现有数据的核函数。

然而,scikit-learn 中 SVM 算法的默认核函数是径向基函数RBF)核。主要原因是,根据多项研究表明,这种核函数在大多数数据问题中表现良好。

练习 4.03:应用 SVM 算法

在本练习中,我们将应用 SVM 算法处理“生育数据集”。这个想法与之前的练习相同,即确定个体的生育水平是否受其人口统计学、环境条件和既往病史的影响。按照以下步骤完成本次练习:

  1. 打开一个 Jupyter Notebook 来实现本次练习。导入 pandas 以及 scikit-learn 的 svm 模块中的 SVC 类:

    import pandas as pd
    from sklearn.svm import SVC
    
  2. 加载你在练习 4.01中下载的fertility_Diagnosis数据集,应用朴素贝叶斯算法。确保在read_csv函数中添加header = None参数,因为该数据集不包含标题行:

    data = pd.read_csv("fertility_Diagnosis.csv", header=None)
    
  3. 将数据分成XY,考虑到类别标签位于索引为9的列下。使用以下代码进行操作:

    X = data.iloc[:,:9]
    Y = data.iloc[:,9]
    
  4. 实例化 scikit-learn 的SVC类,并使用fit函数,利用XY数据来训练模型:

    model = SVC()
    model.fit(X, Y)
    

    再次运行这段代码时,输出结果为模型的总结,以及其默认超参数,如下所示:

    SVC(C=1.0, break_ties=False, cache_size=200,
        class_weight=None, coef0=0.0,
        decision_function_shape='ovr', degree=3,
        gamma='scale', kernel='rbf', max_iter=-1,
        probability=False, random_state=None, shrinking=True,
        tol=0.001, verbose=False)
    
  5. 最后,使用之前训练的模型进行预测,预测与我们在练习 4.01中使用的相同实例:−0.330.6901100.800.88

    使用以下代码进行操作:

    pred = model.predict([[-0.33,0.69,0,1,1,0,0.8,0,0.88]])
    print(pred)
    

    输出结果如下:

    ['N']
    

    再次,模型预测该实例的类别标签为N,意味着该对象的生育能力未受到影响。

    注意

    要访问这一特定部分的源代码,请参考packt.live/2YyEMNX

    你也可以在线运行这个示例,网址为packt.live/2Y3nIR2。你必须执行整个 Notebook 才能得到预期结果。

你已经成功地训练了一个 SVM 模型并进行了预测。

活动 4.03:为我们的普查收入数据集训练 SVM 模型

继续你的任务,构建一个能够预测个人年收入的模型,最后你要训练的算法是支持向量机。按照以下步骤来实现此活动:

  1. 打开你在前一活动中使用的 Jupyter Notebook,并从 scikit-learn 中导入 SVM 算法。

  2. 使用 scikit-learn 中的SVC类的fit方法来训练模型。要训练模型,使用前一活动中的训练集数据(X_trainY_train)。

    注意

    使用fit方法训练 SVC 类可能需要一些时间。

  3. 最后,使用之前训练的模型进行预测,预测一个新实例,假设该实例的每个特征值如下:3961340217404038

    对个体的预测应为零,即该个体的收入最有可能小于或等于 50K。

    注意

    这个活动的解决方案可以在第 238 页找到。

误差分析

在上一章中,我们解释了误差分析的重要性。在本节中,我们将计算前面活动中创建的三个模型的不同评估指标,以便进行比较。

为了学习的目的,我们将使用准确率、精确度和召回率指标来比较模型。这样,就能看到即使某个模型在某个指标上表现较好,在另一个指标上却可能表现较差,这有助于强调选择适当指标的重要性,以便根据你希望实现的目标来衡量模型。

准确率、精确度和召回率

简要提醒一下,为了衡量性能并进行误差分析,你需要使用 predict 方法来处理不同的数据集(训练集、验证集和测试集)。以下代码片段展示了一种简洁的方式,可以同时在我们的三个数据集上衡量所有三个指标:

注意

完成本章活动后,需执行以下步骤。这主要是因为这一部分的步骤是本章活动的延续。

  1. 首先,导入将要使用的三个评估指标:

    from sklearn.metrics import accuracy_score, \
    precision_score, recall_score
    
  2. 接下来,我们创建两个列表,包含将用于 for 循环中的不同数据集,用于对所有模型的所有数据集进行性能计算:

    X_sets = [X_train, X_dev, X_test]
    Y_sets = [Y_train, Y_dev, Y_test]
    
  3. 一个字典将被创建,用来存储每个模型对每组数据的每个评估指标的值:

    metrics = {"NB":{"Acc":[],"Pre":[],"Rec":[]},
               "DT":{"Acc":[],"Pre":[],"Rec":[]},
               "SVM":{"Acc":[],"Pre":[],"Rec":[]}}
    
  4. 使用 for 循环来遍历不同的数据集:

    for i in range(0,len(X_sets)):
        pred_NB = model_NB.predict(X_sets[i])
        metrics["NB"]["Acc"].append(accuracy_score(Y_sets[i], \
                                    pred_NB))
        metrics["NB"]["Pre"].append(precision_score(Y_sets[i], \
                                    pred_NB))
        metrics["NB"]["Rec"].append(recall_score(Y_sets[i], \
                                    pred_NB))
        pred_tree = model_tree.predict(X_sets[i])
        metrics["DT"]["Acc"].append(accuracy_score(Y_sets[i], \
                                    pred_tree))
        metrics["DT"]["Pre"].append(precision_score(Y_sets[i], \
                                    pred_tree))
        metrics["DT"]["Rec"].append(recall_score(Y_sets[i], \
                                    pred_tree))
        pred_svm = model_svm.predict(X_sets[i])
        metrics["SVM"]["Acc"].append(accuracy_score(Y_sets[i], \
                                     pred_svm))
        metrics["SVM"]["Pre"].append(precision_score(Y_sets[i], \
                                     pred_svm))
        metrics["SVM"]["Rec"].append(recall_score(Y_sets[i], \
                                     pred_svm))
    
  5. 打印评估指标,结果如下:

    print(metrics)
    

    输出结果如下:

    图 4.12:打印评估指标

    ](tos-cn-i-73owjymdk6/caf97c7d21524d04b0a29e91ddc21b08)

图 4.12:打印评估指标

for 循环内,有三个代码块,每个代码块对应我们在之前活动中创建的一个模型。每个代码块执行以下操作:

首先,进行预测。预测是通过调用模型的 predict 方法,并输入一组数据来实现的。由于该操作发生在 for 循环中,预测将对所有数据集进行。

接下来,通过将实际数据与我们之前计算的预测结果进行比较,来计算所有三个指标。计算结果会附加到之前创建的字典中。

从前面的代码片段中,得到以下结果:

图 4.13:所有三个模型的性能结果

](tos-cn-i-73owjymdk6/2adb4222b4d84a51bf8d1decc1cc6baf)

图 4.13:所有三个模型的性能结果

注意

请检查代码,以获得这些结果,相关代码可以在本书的 GitHub 仓库中找到,路径为 Chapter04 文件夹,文件名为 Error analysis

初步推断,关于选择最适合的模型,并考虑每个模型面临的条件时,将仅考虑准确率指标的值,假设贝叶斯误差接近 0(这意味着该模型可能达到接近 1 的最大成功率):

  • 在比较了朴素贝叶斯和支持向量机(SVM)模型的三项准确率得分后,可以得出结论:这两种模型在三组数据上的表现几乎相同。这基本上意味着这些模型能够很好地泛化训练集数据,因此在未见过的数据上也能表现良好。然而,这些模型的整体表现约为 0.8,远低于最大成功率。这意味着这些模型可能存在较高的偏差。

  • 此外,决策树模型在训练集准确度方面的表现更接近最大成功率。然而,考虑到模型在验证集上的准确度远低于训练集表现,模型正面临过拟合问题。为了解决过拟合问题,可以通过增加训练集数据或微调模型的超参数来帮助提高验证集和测试集的准确度。对树进行剪枝也有助于缓解过拟合问题。

鉴于此,研究人员现在已经拥有了选择模型并致力于提高结果以实现模型最大可能性能所需的信息。

接下来,为了学习的目的,让我们比较决策树模型的所有衡量标准的结果。尽管三项衡量标准的值都证明了过拟合的存在,但可以观察到精度和召回率标准下的过拟合程度明显更大。此外,可以得出结论,模型在训练集上以召回率衡量时的表现较差,这意味着模型在分类正标签时的能力较弱。这意味着,如果案例研究的目标是最大化正标签的分类数,而不考虑负标签的分类,那么模型还需要提高其在训练集上的表现。

注意

前面的对比是为了说明,同一模型的表现可能因采用不同的衡量标准而有所不同。因此,选择与案例研究相关的衡量标准至关重要。

利用从前几章获得的知识,随时可以继续探索前面表格中显示的结果。

摘要

运用前几章的知识,我们通过对人口普查收入数据集进行分析来开始本章,目的是了解可用数据并对预处理过程做出决策。解释了三种监督学习分类算法——朴素贝叶斯算法、决策树算法和支持向量机(SVM)算法,并将它们应用于之前预处理的数据集,以创建能够泛化到训练数据的模型。最后,我们通过计算不同数据集(训练、验证和测试)上的准确率、精确率和召回率来比较三种模型在人口普查收入数据集上的表现。

在下一章中,我们将研究人工神经网络ANNs)、它们的不同类型以及它们的优缺点。我们还将使用 ANN 解决本章讨论的相同数据问题,并将其性能与其他监督学习算法进行比较。

第五章:5. 监督学习 – 关键步骤

概述

在本章中,我们将深入探讨神经网络的概念,并描述前向传播和反向传播的过程。我们将使用神经网络解决一个监督学习分类问题,并通过执行误差分析来分析神经网络的结果。

到本章结束时,你将能够训练一个网络来解决分类问题,并微调网络的一些超参数以提高其性能。

引言

在前一章节中,我们探讨了三种机器学习算法,用于解决监督学习任务,无论是分类还是回归问题。在本章中,我们将探讨当前最流行的机器学习算法之一——人工神经网络,它属于一种叫做深度学习的机器学习子集。

人工神经网络ANNs),也被称为 多层感知机MLPs),由于它们呈现出一种复杂的算法,可以处理几乎任何具有挑战性的数据问题,因此越来越受欢迎。尽管这一理论在 20 世纪 40 年代就已被提出,但随着技术的进步,尤其是数据收集的能力和计算基础设施的发展,现在这些网络变得更加流行,它们能够在大量数据的基础上训练复杂的算法。

因此,接下来的章节将重点介绍人工神经网络(ANNs)、它们的不同类型以及它们所呈现的优缺点。此外,按照前一章节的内容,将使用一个人工神经网络根据个人的 demographic 和 financial 信息预测其收入,以展示人工神经网络与其他监督学习算法在性能上的差异。

人工神经网络

尽管有多种机器学习算法可用于解决数据问题,但正如我们已经提到的,人工神经网络(ANNs)因其能够在大型复杂数据集中发现模式,而逐渐受到数据科学家的青睐,这些模式是人类无法解释的。

神经这个词部分指的是模型结构与人脑解剖结构的相似性。这一部分旨在复制人类通过将数据从一个神经元传递到另一个神经元,直到得出结果的方式来学习历史数据的能力。

在以下图示中,展示了一个人类神经元,其中 A 表示接收来自其他神经元输入信息的 树突,B 代表处理信息的 细胞核,C 表示负责将处理后的信息传递给下一个神经元的 轴突

图 5.1:人类神经元的可视化表示

图 5.1:人类神经元的可视化表示

此外,人工部分指的是模型的实际学习过程,其主要目标是最小化模型的误差。这是一个人工学习过程,因为没有确凿的证据表明人类神经元如何处理它们接收到的信息,因此模型依赖于将输入映射到期望输出的数学函数。

神经网络是如何工作的?

在深入了解神经网络的过程之前,让我们先看一下其主要组成部分:

  • X,它包含了数据集中所有的数据(每个实例及其特征)。

  • 隐藏层:这一层负责处理输入数据,以便发现有助于做出预测的模式。神经网络可以有任意数量的隐藏层,每层有所需的神经元(单位)数。前几层负责处理简单的模式,而后面几层则负责寻找更复杂的模式。

    隐藏层使用一组表示权重和偏差的变量来帮助训练网络。权重和偏差的值作为在每次迭代中变化的变量,用来将预测结果逼近实际值。稍后将详细解释这一过程。

  • Y_hat,这一层是模型基于从隐藏层接收到的数据所做的预测。该预测以概率的形式呈现,其中具有较高概率的类别标签被选为预测结果。

以下图示展示了前三层的架构,其中 1 下方的圆圈表示输入层的神经元,2 下方的圆圈表示两个隐藏层的神经元(每层由一列圆圈表示),最后,3 下方的圆圈表示输出层的神经元:

图 5.2:神经网络的基本结构

图 5.2:神经网络的基本结构

作为类比,考虑一个制造汽车零部件的过程。在这里,输入层由原材料组成,这些原材料可能是铝。过程的初步步骤包括抛光和清洁材料,可以视为前几层隐藏层。接下来,材料被弯曲以实现汽车部件的形状,这由更深的隐藏层处理。最后,部件交付给客户,这可以视为输出层。

考虑到这些步骤,制造过程的主要目标是实现一个最终部件,该部件高度类似于该过程旨在构建的部件,这意味着输出 Y_hat 应最大化与 Y(实际值)的相似性,才能认为模型与数据相拟合。

训练 ANN 的实际方法是一个迭代过程,包括以下步骤:正向传播、计算成本函数、反向传播、以及权重和偏置的更新。一旦权重和偏置更新完成,过程将重新开始,直到满足迭代次数要求。

让我们详细探讨迭代过程中的每个步骤。

正向传播

输入层将初始信息传递给 ANN。数据的处理是通过在网络的深度(隐藏层的数量)和宽度(每层单元的数量)中传播数据比特完成的。每一层中的每个神经元都使用线性函数处理信息,并结合激活函数来打破线性关系,过程如下:

图 5.3:ANN 使用的线性和激活函数

图 5.3:ANN 使用的线性和激活函数

这里,W1 和 b1 分别是包含权重和偏置的矩阵和向量,作为可以通过迭代更新的变量来训练模型。Z1 是给定神经元的线性函数,A1 是在应用激活函数(用 sigma 符号表示)后得到的单位输出。

前述的两个公式会针对每个层中的每个神经元进行计算,其中隐藏层(除了输入层)的 X 值将被替换为上一层的输出(An),如下所示:

图 5.4:ANN 第二层计算的值

图 5.4:ANN 第二层计算的值

最后,来自最后一个隐藏层的输出被传送到输出层,在那里再次计算线性函数,并结合激活函数进行处理。经过必要的处理后,该层的输出将与真实值进行比较,以评估算法的性能,然后才会进入下一次迭代。

第一次迭代的权重值会在 0 和 1 之间随机初始化,而偏置值可以初始设置为 0。一旦第一次迭代运行,权重和偏置值将被更新,从而使过程重新开始。

激活函数可以有不同的类型。一些常见的激活函数包括修正线性单元ReLU)、双曲正切tanh)、以及SigmoidSoftmax函数,后续部分将对这些函数进行解释。

成本函数

考虑到训练过程的最终目标是基于给定的数据集构建一个模型,以映射预期输出,特别重要的是通过比较预测值(Y_hat)和真实值(Y)之间的差异,来衡量模型估计 XY 之间关系的能力。这是通过计算损失函数(也称为损失函数)来完成的,目的是确定模型预测的准确性。每次迭代都会计算损失函数,以衡量模型在迭代过程中的进展,目标是找到能够最小化损失函数的权重和偏置值。

对于分类任务,最常用的损失函数是交叉熵损失函数,其中损失函数的值越大,预测值与实际值之间的差异越大。

对于二分类任务,也就是只有两个类别输出标签的任务,交叉熵损失函数的计算公式如下:

cost = -(y * log(yhat) + (1-y) *(1-yhat))

这里,y 要么是 1,要么是 0(两个类别标签中的一个),yhat 是模型计算的概率,log 是自然对数。

对于多类别分类任务,公式如下:

图 5.5:多类别分类任务的损失函数

图 5.5:多类别分类任务的损失函数

这里,c 表示类别标签,M 是类别标签的总数。

一旦损失函数计算完成,训练过程将进入反向传播步骤,接下来将解释这一过程。

此外,对于回归任务,损失函数将是均方根误差(RMSE),这一点在第三章监督学习——关键步骤中已解释。

反向传播

反向传播过程作为人工神经网络(ANNs)训练过程的一部分,引入了以加快学习速度。它基本上涉及计算损失函数关于权重和偏置的偏导数,并沿网络传播。其目标是通过调整权重和偏置来最小化损失函数。

考虑到权重和偏置并不直接包含在损失函数中,使用链式法则将误差从损失函数反向传播,直到到达网络的第一层。接下来,计算偏导数的加权平均值,并将其作为更新权重和偏置的值,然后进行新一轮的迭代。

有多种算法可以用于执行反向传播,但最常见的算法是梯度下降。梯度下降是一种优化算法,它试图找到函数的局部或全局最小值,在这里,它的目标是损失函数。它通过确定模型应移动的方向来减少误差,从而实现这一目标。

例如,以下图示展示了 ANN 训练过程中的一个示例,通过不同的迭代,其中反向传播的任务是确定权重和偏差应该更新的方向,从而使误差继续最小化,直到达到最小点:

图 5.6:ANN 训练的迭代过程示例

图 5.6:ANN 训练的迭代过程示例

需要强调的是,反向传播并不总是能找到全局最小值,因为一旦它到达坡道的最低点,它就会停止更新,而不管其他区域如何。例如,考虑以下图示:

图 5.7:最小点的示例

图 5.7:最小点的示例

尽管与左侧和右侧的点相比,所有三个点都可以视为最小点,但其中只有一个是全局最小值。

更新权重和偏差

通过计算反向传播过程中得到的导数平均值,迭代的最后一步是更新权重和偏差的值。这个过程使用以下公式来更新权重和偏差:

New weight = old weight – derivative rate * learning rate
New bias = old bias – derivative rate * learning rate

在这里,旧值是用于执行前向传播步骤的值,导数率是从反向传播步骤中得到的值,对于权重和偏差有所不同,学习率是用于中和导数率影响的常数,使得权重和偏差的变化保持小而平滑。这已被证明有助于更快地达到最低点。

一旦权重和偏差被更新,整个过程将重新开始。

理解超参数

如你所见,超参数是可以通过微调来提高模型准确性的参数。对于神经网络,超参数可以分为两大类:

  • 这些改变网络结构的参数

  • 这些修改训练过程的参数

构建 ANN 的一个重要部分是通过执行误差分析并调整有助于解决影响网络的条件的超参数来进行微调。一般提醒一下,出现高偏差的网络通常可以通过创建更大的网络或训练更长时间(即更多的迭代次数)来改进,而出现高方差的网络则可以通过增加更多的训练数据或引入正则化技术来改善,后者将在后续章节中解释。

鉴于可以更改用于训练 ANN 的大量超参数,接下来将解释最常用的那些超参数。

隐藏层和单元的数量

如前所述,隐藏层的数量以及每层的单元数量可以由研究人员设定。同样地,选择这些数量并没有确切的科学方法,相反,这一选择是微调过程中测试不同近似值的一部分。

然而,在选择隐藏层的数量时,一些数据科学家倾向于采用一种方法,即训练多个网络,每个网络多一个隐藏层。误差最小的模型即为具有正确隐藏层数量的模型。不幸的是,这种方法并不总是奏效,因为对于更复杂的数据问题,单纯通过改变隐藏层数量并不会显著改善性能,无论其他超参数如何。

另一方面,有多种技术可以用于选择隐藏层中的单元数量。数据科学家常常根据网上的类似研究论文来选择这两个超参数的初始值。这意味着一个好的起点是复制在类似领域的项目中成功使用的网络架构,然后通过误差分析,微调超参数以提高性能。

然而,值得注意的是,根据研究活动,深度网络(具有多个隐藏层的网络)往往优于宽度网络(每层具有多个单元的网络)。

激活函数

如前所述,激活函数用于为模型引入非线性。最常用的激活函数包括以下几种:

  • ReLU:该函数的输出为 0 或来自线性函数的数值,以较大的值为准。也就是说,当输入值大于 0 时,输出就是该输入值本身;否则,输出为 0。

  • Tanh:该函数由输入的双曲正弦与双曲余弦的商组成。输出为介于 -1 和 1 之间的数值。

  • Sigmoid:该函数呈 S 形。它将输入值转化为概率。该函数的输出值介于 0 和 1 之间。

  • Softmax:与 sigmoid 函数类似,Softmax 计算输入的概率,不同之处在于 Softmax 函数可以用于多类别分类任务,因为它能够计算一个类别标签相对于其他类别的概率。

激活函数的选择应考虑到,通常情况下,ReLU 和双曲正切(tanh)激活函数被用于所有隐藏层,其中 ReLU 由于其在大多数数据问题中的性能,成为科学家们最常用的选择。

此外,Sigmoid 和 Softmax 激活函数应该用于输出层,因为它们的输出形式是概率。Sigmoid 激活函数用于二分类问题,因为它只输出两个类别标签的概率,而 Softmax 激活函数则可以用于二分类或多分类问题。

正则化

正则化是机器学习中用于改善过拟合模型的一种技术,过拟合意味着当模型过度拟合训练数据时,这个超参数通常在严格要求时才使用,其主要目的是增加模型的泛化能力。

有多种正则化技术,但最常见的有 L1、L2 和 Dropout 技术。尽管 scikit-learn 仅支持 L2 正则化用于其 MLP 分类器,但以下是三种正则化形式的简要说明:

  • L1 和 L2 技术通过在成本函数中添加正则化项来惩罚可能影响模型性能的高权重。这两种方法的主要区别在于,L1 的正则化项是权重的绝对值,而 L2 的正则化项是权重的平方大小。对于常规数据问题,L2 已被证明效果更好,而 L1 主要在特征提取任务中流行,因为它能够创建稀疏模型。

  • Dropout 则指模型通过丢弃一些单元,在迭代步骤中忽略它们的输出,从而简化神经网络。Dropout 值在 0 到 1 之间设置,表示将被忽略的单元的比例。每次迭代步骤中被忽略的单元都是不同的。

批量大小

构建人工神经网络(ANN)时需要调整的另一个超参数是批量大小。它指的是在每次迭代中传入神经网络的实例数量,这些实例将用于执行前向传播和反向传播。对于下一次迭代,将使用一组新的实例。

这种技术还帮助提高模型对训练数据的泛化能力,因为在每次迭代中,模型都会接收到新的实例组合,这在处理过拟合模型时非常有用。

注意

根据多年的研究结果,一个好的实践是将批量大小设置为 2 的倍数。一些常见的值包括 32、64、128 和 256。

学习率

如前所述,学习率用于帮助确定模型在每次迭代中向局部或全局最小值前进的步长。学习率越低,网络的学习过程越慢,但这会导致更好的模型。另一方面,学习率越大,模型的学习过程越快,但这可能导致模型无法收敛。

注意

默认的学习率值通常设置为 0.001。

迭代次数

神经网络是通过迭代过程进行训练的,如前所述。因此,需要设定模型将执行的迭代次数。设置理想迭代次数的最佳方式是从较低的值开始,介于 200 到 500 之间,并在每次迭代的成本函数图显示递减趋势时增加它。不言而喻,迭代次数越大,训练模型的时间也越长。

此外,增加迭代次数是解决欠拟合网络的一种技术。这是因为它给网络更多时间来找到适用于训练数据的正确权重和偏置。

神经网络的应用

除了前述的架构外,随着神经网络的流行,出现了许多新的架构。其中最流行的一些是卷积神经网络,它可以通过使用滤波器作为层来处理图像,以及递归神经网络,它用于处理像文本翻译这样的数据序列。

因此,神经网络的应用几乎涵盖了所有数据问题,从简单到复杂。虽然神经网络能够在非常大的数据集中找到模式(无论是分类任务还是回归任务),但它们也以有效处理一些具有挑战性的问题而闻名,例如自动驾驶汽车的自主能力、聊天机器人构建以及面部识别。

神经网络的局限性

训练神经网络的一些局限性如下:

  • 训练过程需要时间。无论使用什么超参数,通常都需要时间才能收敛。

  • 它们需要非常大的数据集才能更好地工作。神经网络适用于较大的数据集,因为它们的主要优势在于能够在数百万个值中找到模式。

  • 它们被视为黑箱,因为我们无法实际了解网络是如何得出结果的。尽管训练过程背后的数学原理是清晰的,但无法知道模型在训练过程中做出了哪些假设。

  • 硬件要求较高。同样,问题的复杂性越大,硬件要求也越大。

尽管人工神经网络几乎可以应用于任何数据问题,但由于其局限性,在处理简单数据问题时,测试其他算法始终是一个好习惯。这一点非常重要,因为将神经网络应用于那些可以通过更简单模型解决的数据问题,会导致成本大于收益。

应用人工神经网络

现在你已经了解了人工神经网络(ANN)的组成部分,以及它训练模型和做出预测的不同步骤,接下来让我们使用 scikit-learn 库训练一个简单的网络。

在本主题中,将使用 scikit-learn 的神经网络模块,通过上一章的练习和活动中使用的数据集(即生育数据集和处理后的普查收入数据集)来训练一个网络。需要提到的是,scikit-learn 并不是最适合做神经网络的库,因为它目前不支持许多类型的神经网络,并且在处理更深层次的网络时,性能不如其他专注于神经网络的库,比如 TensorFlow 和 PyTorch。

scikit-learn 中的神经网络模块目前支持用于分类的 MLP、用于回归的 MLP 以及受限玻尔兹曼机(Restricted Boltzmann Machine)架构。考虑到本案例研究是一个分类任务,因此将使用用于分类的 MLP。

Scikit-Learn 的多层感知器(MLP)

MLP 是一种监督学习算法,顾名思义,它使用多个层(隐藏层)来学习一个非线性函数,将输入值转换为输出,无论是用于分类还是回归。如前所述,每个层的单元的工作是通过计算一个线性函数并应用激活函数来打破线性关系,从而转化从前一层接收到的数据。

需要提到的是,MLP 具有一个非凸的损失函数,正如前面提到的,这意味着可能存在多个局部最小值。这意味着不同的权重和偏差初始化将导致不同的训练模型,这也意味着不同的准确性水平。

scikit-learn 中的 MLP 分类器有大约 20 个与架构或学习过程相关的超参数,可以调整这些超参数来修改网络的训练过程。幸运的是,所有这些超参数都有预设的默认值,这使得我们可以轻松地运行一个初始模型。然后可以根据需要调整这些超参数,以优化模型。

要训练一个 MLP 分类器,需要输入两个数组:首先是X输入,其维度为(n_samplesn_features),包含训练数据;然后是Y输入,其维度为(n_samples),包含每个样本的标签值。

与我们在前一章中查看的算法类似,模型是通过fit方法进行训练的,然后可以通过在训练好的模型上使用predict方法来获取预测结果。

练习 5.01:应用 MLP 分类器类

在本次练习中,您将使用 scikit-learn 的 MLP 来训练一个模型,解决一个分类任务,该任务包括确定受试者的生育能力是否受其人口统计特征、环境条件和既往病史的影响。

注意

对于本章中的练习和活动,您需要在系统中安装 Python 3.7、NumPy、Jupyter、pandas 和 scikit-learn。

  1. 打开 Jupyter Notebook 实现这个练习。导入所有必要的元素以读取数据集并计算模型的准确性,以及 scikit-learn 的 MLPClassifier 类:

    import pandas as pd
    from sklearn.neural_network import MLPClassifier
    from sklearn.metrics import accuracy_score
    
  2. 使用上一章的生育率数据集,读取 .csv 文件。确保将 header 参数设置为 None,传递给 read_csv 函数,因为该数据集没有包含头行:

    data = pd.read_csv("fertility_Diagnosis.csv", header=None)
    
  3. 将数据集分成 XY 两个集合,以便将特征数据与标签值分开:

    X = data.iloc[:,:9]
    Y = data.iloc[:,9]
    
  4. 从 scikit-learn 的 neural_network 模块实例化 MLPClassifier 类,并使用 fit 方法训练模型。在实例化模型时,保持所有超参数为默认值,但添加 random_state 参数,设置为 101,以确保你得到与本练习中显示的相同结果:

    model = MLPClassifier(random_state=101)
    model = model.fit(X, Y)
    

    处理运行 fit 方法后出现的警告:

    图 5.8:运行 fit 方法后显示的警告信息

    图 5.8:运行 fit 方法后显示的警告信息

    如你所见,警告指出在运行默认迭代次数200次后,模型尚未收敛。

  5. 为了解决这个问题,尝试使用更高的迭代次数,直到警告不再出现。要更改迭代次数,请在实例化模型时,在括号内添加 max_iter 参数:

    model = MLPClassifier(random_state=101, max_iter =1200)
    model = model.fit(X, Y)
    

    此外,警告下方的输出解释了 MLP 所有超参数使用的值。

  6. 最后,使用你之前训练的模型对一个新的实例进行预测,该实例的每个特征值如下:−0.330.6901100.800.88

    使用以下代码:

    pred = model.predict([[-0.33,0.69,0,1,1,0,0.8,0,0.88]])
    print(pred)
    

    模型的预测结果为 N,即模型预测该具有指定特征的人为正常诊断。

  7. 根据模型在 X 变量上的预测,计算你的模型准确性,如下所示:

    pred = model.predict(X)
    score = accuracy_score(Y, pred)
    print(score)
    

    你的模型准确率为 98%

    注意

    要访问此特定部分的源代码,请参见 packt.live/2BaKHRe

    你也可以在线运行这个示例,网址是 packt.live/37tTxpv。你必须执行整个 Notebook 才能得到预期结果。

你已经成功地训练并评估了 MLP 模型的性能。

活动 5.01:为我们的普查收入数据集训练一个 MLP

目的是将上一章节训练的算法性能与神经网络的性能进行比较,针对本活动,我们将继续使用预处理的收入数据集。假设以下情境:你的公司一直在为员工提供提升技能的课程,而你最近学习了神经网络及其强大功能。你决定构建一个网络来建模之前给定的数据集,测试神经网络在根据人口统计数据预测个人收入方面是否优于其他模型。

注意

使用上一章节的预处理数据集开始本活动:census_income_dataset_preprocessed.csv。你也可以在本书的 GitHub 仓库找到该预处理数据集:packt.live/2UQIthA

执行以下步骤来完成此活动:

  1. 导入所有加载和划分数据集、训练 MLP 以及测量准确度所需的元素。

  2. 使用预处理的收入数据集,将特征与目标变量分开,创建XY变量。

  3. 将数据集划分为训练集、验证集和测试集,使用 10%的划分比例。

    注意

    记得在进行数据集划分时继续使用random_state参数等于101,以便设置种子并获得与本书中相同的结果。

  4. 从 scikit-learn 中实例化MLPClassifier类,并使用训练数据训练模型。

    将所有超参数保持为默认值。再次使用random_state等于 101。

    尽管会出现警告,提示在给定的迭代次数下未达到收敛,但无需处理该警告,因为超参数微调将在本章的后续部分进行探讨。

  5. 计算模型在所有三组(训练集、验证集和测试集)上的准确度。

    注意

    本活动的解决方案可以在第 240 页找到。

    三组的准确度应如下所示:

    训练集 = 0.8465

    开发集 = 0.8246

    测试集 = 0.8415

性能分析

在下一部分,我们将首先进行错误分析,使用准确度指标作为工具,确定影响算法性能的主要因素。一旦诊断出模型问题,就可以调整超参数以提升算法的整体性能。最终模型将与上一章节中创建的模型进行比较,以确定神经网络是否优于其他模型。

错误分析

使用在活动 5.01中计算的准确率,即为我们的普查收入数据集训练 MLP,我们可以计算每个数据集的误差率并进行比较,从而诊断影响模型的条件。为此,将假定贝叶斯误差为 1%,因为前一章中的其他模型已能实现超过 97% 的准确率:

图 5.9:网络的准确率和误差率

图 5.9:网络的准确率和误差率

注意

考虑到图 5.9,记住为了检测影响网络的条件,需要取一个误差率,并从中减去上面的误差率。最大的正差异就是我们用来诊断模型的差异。

根据差异列,显而易见训练集中的误差率与贝叶斯误差之间存在最大差异。基于这一点,可以得出结论,模型正遭遇高偏差,正如前几章所解释的那样,可以通过训练更大的网络和/或进行更长时间的训练(增加迭代次数)来解决这一问题。

超参数微调

通过误差分析,我们确定了网络存在高偏差。这非常重要,因为它表明了需要采取的行动,以便更大幅度地提高模型的性能。

考虑到迭代次数和网络大小(层数和单元数)应使用试错法进行调整,以下实验将会进行:

图 5.10:调整超参数的建议实验

图 5.10:调整超参数的建议实验

注意

一些实验可能需要更长时间来运行,因为它们较为复杂。例如,实验 3 比实验 2 花费的时间更长。

这些实验的目的是测试不同超参数的不同值,以便找出是否能取得改进。如果这些实验所获得的改进显著,应考虑进行进一步的实验。

与在 MLP 初始化中添加 random_state 参数类似,迭代次数和网络大小的变化可以通过以下代码实现,该代码显示了实验 3 的值:

from sklearn.neural_network import MLPClassifier
model = MLPClassifier(random_state=101, max_iter = 500, \
                      hidden_layer_sizes=(100,100,100))
model = model.fit(X_train, Y_train)

注意

若要找出调整每个超参数的术语,请访问 scikit-learn 的 MLPClassifier 页面:scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPClassifier.html

正如您在上面的片段中所看到的,max_iter参数用于设置网络训练期间运行的迭代次数。hidden_layer_sizes参数用于设置隐藏层的数量和每个隐藏层的单元数。例如,在上面的示例中,通过将参数设置为(100,100,100),网络的架构为 3 个隐藏层,每个隐藏层有 100 个单元。当然,这种架构还包括所需的输入和输出层。

注意

使用实验 3 的配置来训练网络的示例,鼓励您尝试执行实验 1 和 2 的配置的训练过程。

从运行上述实验中得到的准确度分数如下表所示:

图 5.11:所有实验的准确度分数

图 5.11:所有实验的准确度分数

注意

请记住,调整超参数的主要目的是减少训练集的误差率与贝叶斯误差之间的差异,这就是为什么大部分分析只考虑这个值。

通过分析实验的准确度分数,可以得出结论,最佳的超参数配置是实验 2 中使用的配置。此外,可以得出结论,增加迭代次数对算法性能没有积极影响,因此很可能没有必要尝试其他迭代次数的值。

尽管为了测试隐藏层的宽度,将考虑以下实验,使用实验 2 中选择的迭代次数和隐藏层数量的值,但会改变每层的单元数:

图 5.12:建议的实验以改变网络宽度

图 5.12:建议的实验以改变网络宽度

展示了两个实验的准确度分数,随后解释了它们背后的逻辑:

图 5.13:第二轮实验的准确度分数

图 5.13:第二轮实验的准确度分数

可以看到,与初始模型相比,所有数据集的两个实验的准确度都有所下降。通过观察这些数值,可以得出结论,实验 2 在测试集方面的性能最佳,这使我们得到一个迭代 500 步的网络,具有一个输入和输出层以及两个每个有 100 个单元的隐藏层。

注意

没有理想的方法来测试超参数的不同配置。唯一需要考虑的重要事项是,重点放在那些解决影响网络的条件的超参数上。如果愿意,可以尝试更多实验。

考虑实验 2 的三个数据集的准确率得分来计算误差率,最大的差异仍然是在训练集误差和贝叶斯误差之间。这意味着考虑到训练集误差无法接近最小可能误差边际,该模型可能并不最适合该数据集。

注意

要访问此特定部分的源代码,请参考packt.live/3e2O8bS

本节目前没有在线互动示例,需要在本地运行。

模型比较

当训练了多个模型时,创建模型过程的最后一步是对模型进行比较,以选择最能以一种泛化方式代表训练数据的模型,从而能在未见数据上表现良好。

如前所述,比较必须仅使用选择的度量标准来衡量模型在数据问题上的表现。这一点非常重要,因为一个模型在每个度量标准上的表现可能会大相径庭,因此应选择在理想度量标准下最大化表现的模型。

尽管该度量标准是在所有三个数据集(训练集、验证集和测试集)上计算的,以便能够进行误差分析,但在大多数情况下,比较和选择应优先考虑使用测试集获得的结果。这主要是因为各数据集的目的不同,训练集用于创建模型,验证集用于微调超参数,最终,测试集用于衡量模型在未见数据上的整体表现。

考虑到这一点,在对所有模型进行充分优化后,测试集上表现最优的模型将在未见数据上表现最佳。

活动 5.02:比较不同模型以选择最适合人口收入数据问题的模型

考虑以下场景:在使用可用数据训练四个不同的模型后,你被要求进行分析以选择最适合案例研究的模型。

注意

以下活动主要是分析性的。请使用上一章活动中获得的结果,以及本章中的活动。

执行以下步骤来比较不同的模型:

  1. 打开你用于训练模型的 Jupyter Notebook。

  2. 仅根据准确率得分比较四个模型。请在下表中填写详细信息:图 5.14:所有四个模型在人口收入数据集上的准确率得分

    图 5.14:所有四个模型在人口收入数据集上的准确率得分

  3. 根据准确率得分,识别表现最好的模型。

    注意

    本活动的解决方案可以在第 242 页找到。

总结

本章主要聚焦于人工神经网络(特别是多层感知器,MLP),它们在机器学习领域变得越来越重要,因为它们能够处理高度复杂的数据问题,这些问题通常需要使用极其庞大的数据集,并且这些数据集的模式是肉眼无法识别的。

主要目标是通过使用数学函数来模拟人脑的结构,以处理数据。训练人工神经网络的过程包括前向传播步骤、成本函数的计算、反向传播步骤以及更新不同的权重和偏置,这些权重和偏置帮助将输入值映射到输出。

除了权重和偏置的变量外,人工神经网络还有多个可以调整的超参数,以改善网络的性能。这可以通过修改算法的架构或训练过程来实现。一些最常见的超参数包括网络的大小(隐藏层和单元的数量)、迭代次数、正则化项、批量大小和学习率。

一旦这些概念被讲解完毕,我们就创建了一个简单的网络来解决上一章介绍的“人口普查收入数据集”问题。接下来,通过执行误差分析,我们微调了网络的一些超参数,以提高其性能。

在下一章中,我们将学习如何开发一个端到端的机器学习解决方案,从理解数据和训练模型开始(如前所述),最终到保存训练好的模型,以便将来可以再次使用它。

第六章:6. 构建你自己的程序

概述

在本章中,我们将介绍使用机器学习解决问题所需的所有步骤。我们将查看构建全面程序的关键阶段。我们将保存一个模型,以便每次运行时获得相同的结果,并调用已保存的模型,用于对未见数据进行预测。在本章结束时,你将能够创建一个互动版本的程序,让任何人都能有效地使用它。

介绍

在前几章中,我们介绍了机器学习的主要概念,从两种主要学习方法(监督学习和无监督学习)的区分开始,然后深入探讨了数据科学领域中一些最流行的算法。

本章将讨论构建完整机器学习程序的重要性,而不仅仅是训练模型。这将包括将模型提升到一个新的层次,使其可以轻松访问和使用。

我们将通过学习如何保存训练好的模型来实现这一目标。这样,我们就能加载表现最好的模型,以便对未见数据进行预测。我们还将学习将已保存的模型通过平台公开,使用户能够轻松与之互动的重要性。

当团队合作时,尤其是在公司或研究项目中,这一点尤为重要,因为它使团队的所有成员都能够使用模型,而无需完全理解模型的内部机制。

程序定义

以下部分将涵盖构建一个全面的机器学习程序所需的关键阶段,使得我们能够轻松访问训练好的模型,从而对所有未来数据进行预测。这些阶段将应用于构建一个程序,帮助银行在其营销活动中确定金融产品的推广策略。

构建程序 – 关键阶段

在这一阶段,你应该能够对数据集进行预处理,使用训练数据构建不同的模型,并比较这些模型,从而选择最适合当前数据的模型。这些过程是在构建程序的前两个阶段中处理的,最终使模型得以创建。然而,一个程序还应该考虑保存最终模型的过程,并具备在无需编写代码的情况下快速进行预测的能力。

我们刚刚讨论的过程分为三个主要阶段,并将在以下章节中进行解释。这些阶段代表了任何机器学习项目的最基本要求。

准备工作

准备工作包括我们迄今为止开发的所有程序,目的是根据可用信息和预期结果来概述项目。以下是该阶段三个过程的简要描述(这些内容在前几章中已经详细讨论):

  1. 数据探索:一旦确定了研究目标,就会进行数据探索,以了解可用数据并获取有价值的见解。这些见解稍后将用于决策,例如数据的预处理、数据拆分和模型选择等。数据探索中最常见的信息包括数据集的大小(实例数和特征数)、无关特征,以及是否存在缺失值或明显的异常值。

  2. 数据预处理:正如我们之前讨论的,数据预处理主要是指处理缺失值、异常值和噪声数据;将定性特征转换为数值形式;以及对这些数值进行归一化或标准化。此过程可以通过任何数据编辑器(如 Excel)手动完成,或者使用库编写代码实现。

  3. 数据拆分:最后的过程——数据拆分,涉及将整个数据集拆分为两个或三个子集(根据不同的方法),这些子集将用于训练、验证和测试模型的整体性能。特征和类别标签的分离也在此阶段进行处理。

创建

这一阶段涉及所有创建与可用数据匹配的模型所需的步骤。通过选择不同的算法,进行训练和调优,比较每个算法的表现,最后选择能够最佳泛化到数据的算法(即能够实现更好的整体表现)。这一阶段的过程将简要讨论,具体如下:

  1. 算法选择:无论你决定选择一个还是多个算法,基于可用数据选择算法并考虑每个算法的优缺点是至关重要的。这一点很重要,因为许多数据科学家在面对数据问题时会错误地选择神经网络,而实际上,简单的问题可以通过更简单的模型解决,这些模型运行更快,并且在较小的数据集上表现更好。

  2. X) 和标签类别(Y)以确定关系模式,从而帮助模型泛化到未见数据并在标签不可用时进行预测。

  3. 模型评估:此过程通过衡量算法在所选度量标准下的表现来完成。正如我们之前提到的,选择最佳代表研究目的的度量标准非常重要,因为同一个模型在某一度量标准下可能表现很好,而在另一个度量标准下则表现差。

    在对验证集进行模型评估时,超参数将被微调以实现最佳性能。一旦超参数调优完成,便会在测试集上进行评估,以衡量模型在未见数据上的整体表现。

  4. 模型比较与选择:当基于不同算法创建多个模型时,会进行模型比较,以选择表现最优的模型。此比较应使用相同的度量标准对所有模型进行评估。

交互

构建一个全面的机器学习程序的最后阶段包括使最终用户能够轻松与模型进行交互。这包括将模型保存到文件中、调用保存模型的文件,并开发一个用户可以通过该渠道与模型进行交互的方式:

  1. 存储最终模型:在机器学习程序的开发过程中引入此过程,因为它对于确保模型在未来预测中保持不变并能够被使用至关重要。保存模型的过程非常重要,因为大多数算法在每次运行时都是随机初始化的,这使得每次运行的结果都会有所不同。保存模型的过程将在本章稍后进一步讲解。

  2. predict 方法在未见数据上的应用。此过程将在本章稍后解释。

  3. 交互渠道:最后,开发一个交互性强且易于使用的方式,通过已保存的模型进行预测是至关重要的,特别是因为在许多情况下,模型是由技术团队为其他团队创建的。这意味着理想的程序应该允许非专业人员通过简单地输入数据来使用模型进行预测。本章稍后将进一步展开这一理念。

以下图示展示了前面的各个阶段:

图 6.1:构建机器学习程序的各个阶段

图 6.1:构建机器学习程序的各个阶段

本章余下部分将重点讨论构建模型的最后阶段(交互),因为所有前面的步骤已在之前的章节中讨论。

理解数据集

为了学习如何实现交互部分中的过程,我们将构建一个能够预测一个人是否有兴趣投资定期存款的程序,这将帮助银行更好地定位其促销活动。定期存款是存入银行机构的一种款项,在特定时间内无法提取。

用于构建此程序的数据集可以在 UC Irvine 机器学习库中找到,名称为银行营销数据集

注意

要下载此数据集,请访问以下链接:archive.ics.uci.edu/ml/datasets/Bank+Marketing

数据集也可以在本书的 GitHub 仓库中找到:packt.live/2wnJyny

引用:[Moro et al., 2014] S. Moro, P. Cortez 和 P. Rita*. 基于数据的方法预测银行电话营销的成功。* 决策支持系统,Elsevier,62:22-31,2014 年 6 月。

一旦你访问了 UC Irvine 机器学习库的链接,按照以下步骤下载数据集:

  1. 首先,点击Data Folder链接。

  2. 点击bank超链接以触发下载

  3. 打开.zip文件夹并提取bank-full.csv文件。

    在本节中,我们将在 Jupyter Notebook 中快速浏览数据集。然而,在活动 6.01中,执行银行营销数据集的准备与创建阶段,你将被鼓励进行深入的数据探索和预处理,以获得更好的模型。

  4. 导入所需的库:

    import pandas as pd
    import numpy as np
    
  5. 正如我们迄今所学,数据集可以通过 Pandas 加载到 Jupyter Notebook 中:

    data = pd.read_csv("bank-full.csv")
    data.head()
    

    前面的代码将每个实例的所有特征读取到一个单列中,因为read_csv函数默认使用逗号作为列的分隔符,而数据集使用分号作为分隔符,可以通过显示结果 DataFrame 的前几行来验证这一点。

    data = pd.read_csv("bank-full.csv", delimiter = ";")
    data.head()
    

    在这一步之后,数据应如下所示:

    图 6.3:将数据拆分为列后,.csv 文件的数据截图

    图 6.3:将数据拆分为列后,.csv 文件的数据截图

    如前图所示,文件包含未知值,这些值应当作为缺失值处理。

  6. 为了帮助处理缺失值,所有未知值将通过 Pandas 的replace函数以及 NumPy 替换为NaN,如下所示:

    data = data.replace("unknown", np.NaN)
    data.head()
    

    通过打印data变量的前几行,前面代码段的输出如下:

    图 6.4:替换未知值后,.csv 文件的数据截图

    图 6.4:替换未知值后,.csv 文件的数据截图

    这将使我们在数据集预处理过程中更容易处理缺失值。

  7. 最后,编辑后的数据集将保存在一个新的.csv文件中,以便在本章的活动中使用。你可以通过使用to_csv函数来实现,如下所示:

    data.to_csv("bank-full-dataset.csv")
    

    注意

    要访问本节的源代码,请参阅packt.live/2AAX2ym

    你还可以在线运行此示例,访问packt.live/3ftYXnf。你必须执行整个 Notebook 才能获得预期的结果。

文件应包含共计 45,211 个实例,每个实例有 16 个特征和一个类别标签,可以通过打印存储数据集的变量的形状来验证。类别标签是二元的,yesno类型,表示客户是否订阅了银行的定期存款。

每个实例代表银行的一个客户,而特征捕捉了人口统计信息以及当前(和前期)促销活动中与客户接触的相关数据。

下表简要描述了所有 16 个特征。这将帮助你确定每个特征与研究的相关性,并提供一些预处理数据所需步骤的提示:

图 6.5:描述数据集特征的表格

图 6.5:描述数据集特征的表格

注意

你可以在本书的 GitHub 仓库中找到前述描述以及更多内容,路径为Chapter06文件夹。前述示例的文件名为bank-names.txt,并可以在名为bank.zip.zip文件夹中找到。

利用我们在探索数据集时获得的信息,可以继续进行数据预处理和模型训练,这将是下一活动的目标。

活动 6.01:执行银行营销数据集的准备和创建阶段

本活动的目标是执行准备创建阶段的过程,构建一个全面的机器学习问题。

注意

对于本章中的练习和活动,你需要在系统中安装 Python 3.7、NumPy、Jupyter、Pandas 和 scikit-learn。

假设以下场景:你在你所在城市的主要银行工作,营销团队决定提前了解客户是否可能订阅定期存款,以便他们可以集中精力针对这些客户。

为此,提供了一个数据集,包含了团队当前和之前开展的营销活动的详细信息(即你已下载并探索过的银行营销数据集)。你需要对数据集进行预处理并比较两个模型,以便选择最优模型。

按照以下步骤进行操作:

注意

若要提醒自己如何预处理数据集,可以回顾第一章Scikit-Learn 简介。另一方面,要回顾如何训练监督学习模型、评估性能和进行错误分析,请回顾第三章监督学习——关键步骤,以及第四章监督学习算法:预测年收入

  1. 打开一个 Jupyter Notebook 来实现此活动,并导入所有所需的元素。

  2. 将数据集加载到笔记本中。确保加载的是之前编辑过的文件,名为bank-full-dataset.csv,它也可以在packt.live/2wnJyny找到。

  3. 选择最适合衡量模型性能的指标,考虑到本研究的目的是检测可能订阅定期存款的客户。

  4. 预处理数据集。

    请注意,其中一个定性特征是有序的,因此必须将其转换为遵循相应顺序的数字形式。使用以下代码片段来完成此操作:

    data["education"] = data["education"].fillna["unknown"]
    encoder = ["unknown", "primary", "secondary", "tertiary"]
    for i, word in enumerate(encoder):
        data["education"] = data["education"].\
                            str.replace(word,str(i))
        data["education"] = data["education"].astype("int64")
    
  5. 将特征与类别标签分开,并将数据集分为三个集合(训练集、验证集和测试集)。

  6. 使用决策树算法对数据集进行训练。

  7. 使用多层感知器算法对数据集进行训练。

    注意

    您还可以尝试使用本书中讨论的其他分类算法。尽管如此,这两个算法被选择出来,是为了让您能够比较训练时间的差异。

  8. 使用您之前选择的评估指标来评估这两个模型。

  9. 微调一些超参数,以解决在评估模型时通过错误分析发现的问题。

  10. 比较您模型的最终版本,并选择您认为最适合数据的那个。

预期输出:

图 6.6:预期输出

图 6.6:预期输出

注意

您可以在第 244 页找到此活动的解决方案。

保存和加载训练好的模型

尽管操作数据集并训练正确的模型对于开发机器学习项目至关重要,但工作并不止于此。了解如何保存训练好的模型是关键,因为这将允许您保存超参数,以及最终模型的权重和偏置值,以便模型在重新运行时保持不变。

此外,在模型保存到文件后,了解如何加载保存的模型以便对新数据进行预测也非常重要。通过保存和加载模型,我们可以在任何时候以多种方式重复使用该模型。

保存模型

保存模型的过程也称为序列化,随着神经网络的流行,序列化变得越来越重要。神经网络使用许多参数(权重和偏置),这些参数在每次训练时都会被随机初始化。此外,随着更大、更复杂数据集的引入,训练过程可能会持续几天、几周甚至几个月。

考虑到这一点,保存模型的过程有助于通过将结果标准化为保存版本的模型来优化机器学习解决方案的使用。它还节省了时间,因为它允许您将已保存的模型直接应用于新数据,而无需重新训练。

保存训练好的模型有两种主要方式,其中一种将在本节中解释。pickle模块是 Python 中序列化对象的标准方式,它通过实现一个强大的算法将模型序列化,然后将其保存为.pkl文件。

注意

另一个用于保存训练好模型的模块是joblib,它是 SciPy 生态系统的一部分。

然而,请注意,只有在模型计划在未来的项目中使用或进行未来预测时,才需要保存模型。当机器学习项目是为了理解当前数据时,不需要保存模型,因为分析将在模型训练后进行。

练习 6.01:保存已训练的模型

对于以下练习,我们将使用在第五章人工神经网络:预测年收入中下载的生育率数据集。将在训练数据上训练一个神经网络,然后将其保存。按照以下步骤完成本练习:

注意

数据集也可以在本书的 GitHub 仓库中找到:packt.live/2zBW84e

  1. 打开一个 Jupyter Notebook 以实现这个练习,并导入所有必需的元素以加载数据集、训练多层感知器并保存已训练的模型:

    import pandas as pd
    from sklearn.neural_network import MLPClassifier
    import pickle
    import os
    

    如前所述,pickle模块将用于保存已训练的模型。os模块用于定位 Jupyter Notebook 的当前工作目录,以便将模型保存在相同的路径下。

  2. 加载生育率数据集,并将数据拆分为特征矩阵X和目标矩阵Y。使用header = None参数,因为数据集没有标题行:

    data = pd.read_csv("fertility_Diagnosis.csv", header=None)
    X = data.iloc[:,:9]
    Y = data.iloc[:,9]
    
  3. 在数据上训练一个多层感知器分类器。将迭代次数设置为1200,以避免出现警告消息,表示默认的迭代次数不足以实现收敛:

    model = MLPClassifier(max_iter = 1200)
    model.fit(X,Y)
    

    注意

    提醒一下,调用fit方法后的输出由当前训练的模型组成,并包括它所需要的所有参数。

  4. 序列化模型并将其保存在名为model_exercise.pkl的文件中。使用以下代码来实现:

    path = os.getcwd() + "/model_exercise.pkl"
    file = open(path, "wb")
    pickle.dump(model, file)
    

    在前面的代码片段中,path变量包含将保存序列化模型的文件路径,其中第一个元素定位当前工作目录,第二个元素定义要保存的文件名。file变量用于创建一个将被保存到所需路径的文件,并且文件模式设置为wb,即表示dump方法在pickle模块上应用。它接受之前创建的模型,将其序列化,然后保存。

    注意

    要访问此特定部分的源代码,请参考packt.live/3e18vWw

    你也可以在packt.live/2B7NJpC上在线运行这个示例。你必须执行整个 Notebook 才能得到预期的结果。

你已成功保存了一个已训练的模型。在下一部分,我们将介绍如何加载已保存的模型。

加载模型

加载模型的过程也被称为pickle模块的使用,它也用于加载模型。

值得一提的是,模型不需要在与训练和保存模型相同的代码文件中加载;相反,它应该在任何其他文件中加载。这主要是因为 pickle 库的 load 方法将返回一个模型变量,该变量将用于应用 predict 方法。

在加载模型时,除了像之前一样导入 pickleos 模块外,还需要导入用于训练模型的算法类。例如,要加载一个神经网络模型,必须从 scikit-learn 的 neural_network 模块中导入 MLPClassifier 类。

练习 6.02:加载已保存的模型

在这个练习中,使用不同的 Jupyter Notebook,我们将加载之前训练好的模型(练习 6.01保存已训练的模型)并进行预测。按照以下步骤完成此练习:

  1. 打开一个 Jupyter Notebook 来实现这个练习。

  2. 导入 pickleos 模块。同时,导入 MLPClassifier 类:

    import pickle
    import os
    from sklearn.neural_network import MLPClassifier
    

    如前所述,pickle 模块将用于加载训练好的模型。os 模块用于定位当前 Jupyter Notebook 的工作目录,以便找到包含已保存模型的文件。

  3. 使用pickle加载保存的模型,如下所示:

    path = os.getcwd() + "/model_exercise.pkl"
    file = open(path, "rb")
    model = pickle.load(file)
    

    在这里,path 变量用于存储包含已保存模型的文件路径。接下来,file 变量用于以 rb 文件模式打开文件,load 方法应用于 pickle 模块,用于反序列化并将模型加载到 model 变量中。

  4. 使用加载的模型对一个个体进行预测,特征值如下:-0.33, 0.67, 1, 1, 0, 0, 0.8, -1, 0.5

    将应用 predict 方法后的输出存储在一个名为 pred 的变量中:

    pred = model.predict([[-0.33,0.67,1,1,0,0,0.8,-1,0.5]])
    print(pred)
    

    通过打印 pred 变量,我们得到的预测值为 O,这意味着该个体的诊断发生了改变,如下所示:

    ['O']
    

你已成功加载了一个已保存的模型。

注意

要访问该部分的源代码,请参考 packt.live/2MXyGS7

你也可以在线运行这个示例,网址为packt.live/3dYgVxL。你必须执行整个 Notebook 才能得到预期的结果。

活动 6.02:保存和加载银行营销数据集的最终模型

假设以下场景:你需要保存使用银行营销数据集创建的模型,以便将来可以使用它,而无需重新训练模型,并且避免每次得到不同的结果。为此,你需要保存和加载你在活动 6.01执行银行营销数据集的准备和创建阶段中创建的模型。

注意

以下活动将分为两个部分。

第一部分执行保存模型的过程,将使用与 活动 6.01 中的 Jupyter Notebook 相同的 Notebook 来完成,执行银行营销数据集的准备和创建阶段。第二部分包括加载保存的模型,将使用不同的 Jupyter Notebook 来完成。

按照以下步骤完成此活动:

  1. 打开 活动 6.01 中的 Jupyter Notebook,执行银行营销数据集的准备和创建阶段

  2. 为了学习目的,取你选择的最佳模型,去掉 random_state 参数,然后运行几次。

    确保在每次运行模型时都计算精度指标,以便看到每次运行所达到的性能差异。随时可以停止,当你认为从之前的运行结果中已经得到了一个性能良好的模型时。

    注意

    本书中获得的结果使用了 random_state 值为 2

  3. 将你选择的最佳模型保存为名为 final_model.pkl 的文件。

    注意

    确保使用 os 模块将模型保存在与当前 Jupyter Notebook 相同的路径下。

  4. 打开一个新的 Jupyter Notebook,导入所需的模块和类。

  5. 加载模型。

  6. 使用以下值为个体进行预测:422001210583801-10

    预期输出:

    [0] 
    

    注意

    此活动的解决方案可以在第 253 页找到。

与训练好的模型进行交互

一旦模型创建并保存完成,就到了构建全面的机器学习程序的最后一步:允许与模型的轻松交互。这个步骤不仅让模型可以重复使用,还通过仅使用输入数据进行分类来提高机器学习解决方案的实现效率。

有多种方式与模型进行交互,选择哪种方式取决于用户的性质(即那些会定期使用模型的人)。机器学习项目可以通过不同的方式访问,其中一些方式需要使用 API、在线或离线程序(应用程序)或网站。

此外,一旦基于用户的偏好或专业知识定义了通道,就需要对最终用户与模型之间的连接进行编码,这可以是一个函数或一个类,用于反序列化模型并加载它,接着执行分类,最终返回一个结果,该结果会再次显示给用户。

下图展示了通道与模型之间的关系,其中左侧的图标代表模型,中间的是执行连接的函数或类(中介),右侧的图标是通道。正如我们之前所解释的,通道将输入数据传递给中介,然后中介将信息传入模型以执行分类。分类的输出被返回给中介,中介再通过通道将其传递以便展示:

图 6.7:用户与模型之间交互的示意图

图 6.7:用户与模型之间交互的示意图

练习 6.03:创建一个类和一个通道与训练好的模型交互

在本练习中,我们将在文本编辑器中创建一个类,该类接受输入数据并将其传递给在练习 6.01中训练好的模型,使用的是 Fertility Diagnosis 数据集。此外,我们还将在 Jupyter Notebook 中创建一个表单,用户可以在其中输入数据并获得预测。

在文本编辑器中创建类时,请按照以下步骤操作:

  1. 打开你偏好的文本编辑器,例如 PyCharm。

  2. 导入 pickleos

    import pickle
    import os
    
  3. 创建一个类对象并命名为 NN_Model

    Class NN_Model(object):
    
  4. 在类内部,创建一个初始化方法,将保存的模型文件(model_exercise.pkl)加载到代码中:

    def __init__(self):
        path = os.getcwd() + "/model_exercise.pkl"
        file = open(path, "rb")
        self.model = pickle.load(file)
    

    注意

    记得在类对象内部缩进方法。

    一般来说,类对象中的所有方法必须具有 self 参数。另一方面,在使用 self 语句定义模型变量时,可以在同一个类的任何其他方法中使用该变量。

  5. 在名为 NN_Model 的类中,创建一个 predict 方法。该方法应接收特征值并将其作为参数传递给模型的 predict 方法,以便将其输入到模型中进行预测:

    def predict(self, season, age, childish, trauma, \
                surgical, fevers, alcohol, smoking, sitting):
        X = [[season, age, childish, trauma, surgical, \
              fevers, alcohol, smoking, sitting]]
        return self.model.predict(X)
    

    注意

    记得在类对象内部缩进方法。

  6. 将代码保存为 Python 文件(.py),并命名为 exerciseClass.py。这个文件的名称将在接下来的步骤中用于加载类到 Jupyter Notebook 中。

    现在,让我们编写程序的前端解决方案,包括创建一个表单,用户可以在其中输入数据并获得预测。

    注意

    为了学习目的,表单将在 Jupyter Notebook 中创建。然而,通常情况下,前端是以网站、应用程序或类似的形式存在。

  7. 打开一个 Jupyter Notebook。

  8. 为了导入在步骤 6中保存为 Python 文件的模型类,可以使用以下代码片段:

    from exerciseClass import NN_Model
    
  9. 初始化 NN_Model 类并将其存储在名为 model 的变量中:

    model = NN_Model()
    

    通过调用保存在 Python 文件中的类,初始化方法会自动触发,从而将保存的模型加载到变量中。

  10. 创建一组变量,供用户为每个特征输入值,然后将这些值传递给模型。使用以下值:

    a = 1      # season in which the analysis was performed
    b = 0.56   # age at the time of the analysis
    c = 1      # childish disease
    d = 1      # accident or serious trauma
    e = 1      # surgical intervention
    f = 0      # high fevers in the last year
    g = 1      # frequency of alcohol consumption
    h = -1     # smoking habit
    i = 0.63   # number of hours spent sitting per day
    
  11. 通过在 model 变量上使用 predict 方法进行预测。将特征值作为参数输入,注意你必须按照在文本编辑器中创建 predict 函数时使用的名称来命名它们:

    pred = model.predict(season=a, age=b, childish=c, \
                         trauma=d, surgical=e, fevers=f, \
                         alcohol=g, smoking=h, sitting=i)
    print(pred)
    
  12. 通过打印预测结果,我们得到以下输出:

    ['N']
    

    这意味着该个体的诊断结果正常。

    注意

    要访问该特定部分的源代码,请参见packt.live/2MZPjg0

    你还可以在packt.live/3e4tQOC上在线运行此示例。你必须执行整个 Notebook 才能获得期望的结果。

你已经成功创建了一个函数和一个与模型交互的渠道。

活动 6.03:允许与银行营销数据集模型交互

考虑以下场景:在看到你在上一个活动中展示的结果后,你的老板要求你建立一种非常简单的方式,以便他可以在接下来的一个月内用他收到的数据测试模型。如果所有测试都能顺利通过,他将要求你以更高效的方式启动程序。因此,你决定与老板分享一个 Jupyter Notebook,在其中他只需输入信息就能得到预测结果。

注意

接下来的活动将分为两部分进行开发。第一部分将涉及构建连接渠道和模型的类,这部分将使用文本编辑器进行开发。第二部分将是创建渠道,这将在 Jupyter Notebook 中完成。

按照以下步骤完成此活动:

  1. 在文本编辑器中,创建一个包含两个主要方法的类对象。其中一个应该是初始化器,用于加载已保存的模型,另一个应该是 predict 方法,在此方法中,数据被输入到模型中以获取输出。

  2. 在 Jupyter Notebook 中,导入并初始化你在上一步创建的类。接下来,创建一个变量,用于保存新观察的所有特征值。使用以下值:422001210583801-10

  3. 通过应用 predict 方法进行预测。

预期输出:当你完成此活动时,你将获得 0 作为输出。

注意

此活动的解决方案可以在第 254 页找到。

总结

本章总结了成功训练基于训练数据的机器学习模型所需的所有概念和技术。在本章中,我们引入了构建一个全面机器学习程序的思想,该程序不仅考虑了数据集准备和理想模型创建的各个阶段,还包括了使模型在未来可用的阶段,这一过程通过三个主要步骤来完成:保存模型、加载模型,以及创建一个渠道,让用户能够轻松地与模型互动并获取结果。

为了保存和加载模型,引入了pickle模块。该模块能够将模型序列化以保存到文件中,同时也能反序列化模型,以便将来使用该模型。

此外,为了让用户能够访问模型,需要根据与模型交互的用户类型选择理想的渠道(例如,API、应用程序、网站或表单)。然后,需要编写一个中介程序,将该渠道与模型连接起来。这个中介通常以函数或类的形式存在。

本书的主要目标是介绍 scikit-learn 库,作为一种简单的方式来开发机器学习解决方案。在讨论了数据探索和预处理的必要性及涉及的不同技术后,本书将知识划分为机器学习的两个主要领域,即监督学习和无监督学习。我们讨论了最常见的算法。

最后,我们解释了通过进行错误分析来衡量模型性能的重要性,以便提高模型在未见数据上的整体表现,并最终选择最能代表数据的模型。这个最终的模型应当被保存,以便将来用于可视化或进行预测。

附录

1. Scikit-Learn 简介

活动 1.01:选择目标特征并创建目标矩阵

解决方案:

  1. 使用 seaborn 库加载 titanic 数据集:

    import seaborn as sns
    titanic = sns.load_dataset('titanic')
    titanic.head(10)
    

    前几行应该如下所示:

    图 1.22:显示泰坦尼克数据集前 10 个实例的图像

    图 1.22:显示泰坦尼克数据集前 10 个实例的图像

  2. 选择你偏好的目标特征,作为本活动的目标。

    偏好的目标特征可以是 survivedalive。这主要是因为这两个特征都标记了一个人是否在事故中生还。在接下来的步骤中,选择的变量是 survived。然而,选择 alive 不会影响最终变量的形状。

  3. 创建特征矩阵和目标矩阵。确保将特征矩阵的数据存储在变量 X 中,将目标矩阵的数据存储在另一个变量 Y 中:

    X = titanic.drop('survived',axis = 1)
    Y = titanic['survived']
    
  4. 打印出 X 的形状,如下所示:

    X.shape
    

    输出如下:

    (891, 14)
    

    Y 执行相同操作:

    Y.shape
    

    输出如下:

    (891,)
    

    注意

    若要访问此特定部分的源代码,请参考 packt.live/37BwgSv

    你还可以在 packt.live/2MXFtuP 上在线运行此示例。你必须执行整个 Notebook,才能获得期望的结果。

你已成功将数据集拆分为两个子集,稍后将使用这些子集来训练模型。

活动 1.02:预处理整个数据集

解决方案:

  1. 导入 seaborn 和 scikit-learn 中的 LabelEncoder 类。接着,加载 titanic 数据集,并创建包含以下特征的特征矩阵:sexagefareclassembark_townalone

    import seaborn as sns
    from sklearn.preprocessing import LabelEncoder
    titanic = sns.load_dataset('titanic')
    X = titanic[['sex','age','fare','class',\
                 'embark_town','alone']].copy()
    X.shape
    

    特征矩阵是数据集的副本,以避免每次通过预处理过程更新矩阵时出现警告信息。

    输出如下:

    (891, 6)
    
  2. 检查所有特征中的缺失值。像之前一样,使用 isnull() 来判断值是否缺失,并使用 sum() 来计算每个特征中缺失值的总数:

    print("Sex: " + str(X['sex'].isnull().sum()))
    print("Age: " + str(X['age'].isnull().sum()))
    print("Fare: " + str(X['fare'].isnull().sum()))
    print("Class: " + str(X['class'].isnull().sum()))
    print("Embark town: " + str(X['embark_town'].isnull().sum()))
    print("Alone: " + str(X['alone'].isnull().sum()))
    

    输出将如下所示:

    Sex: 0
    Age: 177
    Fare: 0
    Class: 0
    Embark town: 2
    Alone: 0
    

    从前面的输出可以看出,只有一个特征包含大量缺失值:age。由于缺失值占总数的近 20%,应当替换这些缺失值。将应用均值插补方法,如下所示:

    mean = X['age'].mean()
    mean =round(mean)
    X['age'].fillna(mean,inplace = True)
    

    接下来,发现数值特征中的异常值。我们使用三倍标准差作为计算数值特征最小值和最大值阈值的标准:

    features = ["age", "fare"]
    for feature in features:
        min_ = X[feature].mean() - (3 * X[feature].std())
        max_ = X[feature].mean() + (3 * X[feature].std())
        X = X[X[feature] <= max_]
        X = X[X[feature] >= min_]
        print(feature,    ":", X.shape)
    

    输出如下:

    age: (884, 6)
    fare: (864, 6)
    

    年龄和票价特征的异常值总数分别为 7 和 20,导致初始矩阵的形状减少了 27 个实例。

    接下来,使用 for 循环,查找文本特征中的异常值。value_counts() 函数用于计算每个特征中类别的出现次数:

    features = ["sex", "class", "embark_town", "alone"]
    for feature in features:
        count_ = X[feature].value_counts()
        print(feature)
        print(count_, "\n")
    

    输出如下:

    图 1.23:每个特征中类别的出现次数

    ](tos-cn-i-73owjymdk6/9ce6383dfa2a4d1a82e4fbded7f5d02c)

    图 1.23:每个特征中类别的出现次数

    任何特征的类别都不被认为是异常值,因为它们都代表了整个数据集的 5% 以上。

  3. 将所有文本特征转换为其数值表示。使用 scikit-learn 的 LabelEncoder 类,如下代码所示:

    enc = LabelEncoder()
    X["sex"] = enc.fit_transform(X['sex'].astype('str'))
    X["class"] = enc.fit_transform(X['class'].astype('str'))
    X["embark_town"] = enc.fit_transform(X['embark_town'].\
                                         astype('str'))
    X["alone"] = enc.fit_transform(X['alone'].astype('str'))
    

    打印出特征矩阵的前五个实例,查看转换结果:

    X.head()
    

    输出如下:

    图 1.24:显示特征矩阵前五个实例的截图

    ](tos-cn-i-73owjymdk6/280dfde756964d39b879e57894179487)

    图 1.24:显示特征矩阵前五个实例的截图

  4. 重新调整您的数据,可以通过标准化或规范化它。

    如以下代码所示,所有特征都经过标准化处理,但只有那些不符合标准化变量标准的特征才会被更改:

    X = (X - X.min()) / (X.max() - X.min())
    X.head(10)
    

    最终输出的前 10 行如以下截图所示:

    图 1.25:显示标准化数据集的前 10 个实例

    ](tos-cn-i-73owjymdk6/1a42447d80eb4cb8878e6793783885c7)

图 1.25:显示标准化数据集的前 10 个实例

注意

要访问此特定部分的源代码,请参阅 packt.live/2MY1wld

您也可以在 packt.live/3e2lyqt 在线运行此示例。必须执行整个 Notebook 才能得到预期的结果。

您已成功完成数据预处理,现在可以使用该数据集来训练机器学习算法。

2. 无监督学习——实际应用

活动 2.01:使用数据可视化辅助预处理过程

解决方案:

  1. 导入加载数据集和预处理所需的所有元素:

    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    
  2. 使用 pandas 的 read_csv() 函数加载之前下载的数据集。将数据集存储在名为 data 的 pandas DataFrame 中:

    data = pd.read_csv("wholesale_customers_data.csv")
    
  3. 检查 DataFrame 中的缺失值。使用 isnull() 函数加上 sum() 函数,一次性计算整个数据集的缺失值:

    data.isnull().sum()
    

    输出如下:

    Channel             0
    Region              0
    Fresh               0
    Milk                0
    Grocery             0
    Frozen              0
    Detergents_Paper    0
    Delicassen          0
    dtype: int64
    

    如您从前面的截图中看到的那样,数据集中没有缺失值。

  4. 检查 DataFrame 中的异常值。标记为异常值的所有值是距离均值三个标准差以外的值。

    以下代码片段允许您一次性查找所有特征集中的异常值。然而,另一种有效的方法是一次只检查一个特征中的异常值:

    outliers = {}
    for i in range(data.shape[1]):
        min_t = data[data.columns[i]].mean() \
                - (3 * data[data.columns[i]].std())
        max_t = data[data.columns[i]].mean() \
                + (3 * data[data.columns[i]].std())
        count = 0
        for j in data[data.columns[i]]:
            if j < min_t or j > max_t:
                count += 1
        outliers[data.columns[i]] = [count,data.shape[0]-count]
    print(outliers)
    

    每个特征的异常值计数如下:

    {'Channel': [0, 440], 'Region': [0, 440], 'Fresh': [7, 433], 'Milk': [9, 431], 'Grocery': [7, 433], 'Frozen': [6, 434], 'Detergents_Paper': [10, 430], 'Delicassen': [4, 436]}
    

    如您从前面的截图中看到的那样,某些特征确实存在异常值。考虑到每个特征只有少量异常值,有两种可能的处理方法。

    首先,你可以决定删除离群点。这个决定可以通过为带有离群点的特征显示直方图来支持:

    plt.hist(data["Fresh"])
    plt.show()
    

    输出如下:

    图 2.14:为“Fresh”特征绘制的示例直方图

    plt.figure(figsize=(8,8))
    plt.pie(outliers["Detergents_Paper"],autopct="%.2f")
    plt.show()
    

    输出如下:

    图 2.15:饼图显示数据集中“Detergents_papers”特征中离群点的参与情况

    图 2.15:饼图显示数据集中“Detergents_papers”特征中离群点的参与情况

    上图显示了来自 Detergents_papers 特征的离群点参与情况,这是数据集中离群点最多的特征。只有 2.27% 的值是离群点,这个比例如此之低,几乎不会影响模型的性能。

    对于本书中的解决方案,决定保留离群点,因为它们不太可能影响模型的性能。

  5. 重缩放数据。

    对于此解决方案,使用了标准化公式。请注意,这个公式可以应用于整个数据集,而不是单独应用于每个特征:

    data_standardized = (data - data.mean())/data.std()
    data_standardized.head()
    

    输出如下:

    图 2.16:重缩放后的数据

图 2.16:重缩放后的数据

注意

要访问此特定部分的源代码,请参阅 packt.live/2Y3ooGh

你还可以通过在线运行此示例,访问packt.live/2B8vKPI。你必须执行整个 Notebook 才能获得期望的结果。

你已经成功地预处理了批发客户数据集,这个数据集将在后续的活动中用于构建一个模型,将这些观测值分类到不同的聚类中。

活动 2.02:将 k-means 算法应用于数据集

解决方案:

  1. 打开你在前一个活动中使用的 Jupyter Notebook。在那里,你应该已经导入了所有所需的库,并执行了预处理数据集的必要步骤。

    标准化后的数据应如下所示:

    图 2.17:显示标准化数据集前五个实例的截图

    图 2.17:显示标准化数据集前五个实例的截图

  2. 计算数据点与其质心之间的平均距离,并根据聚类数量来选择合适的聚类数目来训练模型。

    首先,导入算法类:

    from sklearn.cluster import KMeans
    

    接下来,使用以下代码片段,计算数据点与其质心之间的平均距离,基于创建的聚类数量:

    ideal_k = []
    for i in range(1,21):
        est_kmeans = KMeans(n_clusters=i, random_state=0)
        est_kmeans.fit(data_standardized)
        ideal_k.append([i,est_kmeans.inertia_])
    ideal_k = np.array(ideal_k)
    

    最后,绘制关系图以找到线的断点并选择聚类数:

    plt.plot(ideal_k[:,0],ideal_k[:,1])
    plt.show()
    

    输出如下:

    图 2.18:绘图函数的输出结果

    图 2.18:绘图函数的输出结果

    再次,x 轴表示簇的数量,而y 轴则表示数据点与其质心之间的计算平均距离。

  3. 训练模型并为数据集中的每个数据点分配一个簇。绘制结果。

    要训练模型,请使用以下代码:

    est_kmeans = KMeans(n_clusters=6, random_state = 0)
    est_kmeans.fit(data_standardized)
    pred_kmeans = est_kmeans.predict(data_standardized)
    

    选择的簇的数量为6;然而,由于没有精确的分割点,值在 5 到 10 之间也是可以接受的。

    最后,绘制聚类过程的结果。由于数据集包含八个不同的特征,选择两个特征同时绘制,如以下代码所示:

    plt.subplots(1, 2, sharex='col', \
                 sharey='row', figsize=(16,8))
    plt.scatter(data.iloc[:,5], data.iloc[:,3], \
                c=pred_kmeans, s=20)
    plt.xlim([0, 20000])
    plt.ylim([0, 20000])
    plt.xlabel('Frozen')
    plt.subplot(1, 2, 1)
    plt.scatter(data.iloc[:,4], data.iloc[:,3], \
                c=pred_kmeans, s=20)
    plt.xlim([0, 20000])
    plt.ylim([0,20000])
    plt.xlabel('Grocery')
    plt.ylabel('Milk')
    plt.show()
    

    输出结果如下:

    ![图 2.19:聚类过程后获得的两个示例图]

    ](github.com/OpenDocCN/f…)

图 2.19:聚类过程后获得的两个示例图

注意

要访问本次活动的源代码,请参考packt.live/3fhgO0y

您也可以在packt.live/3eeEOB6在线运行此示例。您必须执行整个 Notebook 才能获得期望的结果。

matplotlib中的subplots()函数已用于同时绘制两个散点图。每个图的轴代表一个选定特征与另一个特征值之间的关系。从图中可以看出,由于我们仅能使用数据集中八个特征中的两个,因此没有明显的视觉关系。然而,模型的最终输出创建了六个不同的簇,代表了六种不同的客户画像。

活动 2.03:将均值偏移算法应用于数据集

解决方案:

  1. 打开您在之前活动中使用的 Jupyter Notebook。

  2. 训练模型并为数据集中的每个数据点分配一个簇。绘制结果。

    首先,导入算法类:

    from sklearn.cluster import MeanShift
    

    要训练模型,请使用以下代码:

    est_meanshift = MeanShift(0.4)
    est_meanshift.fit(data_standardized)
    pred_meanshift = est_meanshift.predict(data_standardized)
    

    该模型是使用带宽0.4训练的。不过,您可以尝试其他值,看看结果如何变化。

    最后,绘制聚类过程的结果。由于数据集包含八个不同的特征,选择两个特征同时绘制,如以下代码片段所示。与之前的活动类似,由于只能绘制八个特征中的两个,聚类之间的分隔在视觉上并不可见:

    plt.subplots(1, 2, sharex='col', \
                 sharey='row', figsize=(16,8))
    plt.scatter(data.iloc[:,5], data.iloc[:,3], \
                c=pred_meanshift, s=20)
    plt.xlim([0, 20000])
    plt.ylim([0,20000])
    plt.xlabel('Frozen')
    plt.subplot(1, 2, 1)
    plt.scatter(data.iloc[:,4], data.iloc[:,3], \
                c=pred_meanshift, s=20)
    plt.xlim([0, 20000])
    plt.ylim([0,20000])
    plt.xlabel('Grocery')
    plt.ylabel('Milk')
    plt.show()
    

    输出结果如下:

    ![图 2.20:处理结束时获得的示例图]

    ](github.com/OpenDocCN/f…)

图 2.20:处理结束时获得的示例图

对于每个图,轴代表选定特征的值,相对于另一个特征的值。

注意

要访问本次活动的源代码,请参考packt.live/3fviVy1

您也可以在packt.live/2Y1aqEF在线运行此示例。您必须执行整个 Notebook 才能获得期望的结果。

你已经成功地在 Wholesale Customers 数据集上应用了 mean-shift 算法。稍后,你将能够比较不同算法在相同数据集上的结果,以选择表现最好的算法。

活动 2.04:将 DBSCAN 算法应用于数据集

解决方案:

  1. 打开你在之前活动中使用的 Jupyter Notebook。

  2. 训练模型,并为数据集中的每个数据点分配一个聚类。绘制结果。

    首先,导入算法类:

    from sklearn.cluster import DBSCAN
    

    要训练模型,使用以下代码:

    est_dbscan = DBSCAN(eps=0.8)
    pred_dbscan = est_dbscan.fit_predict(data_standardized)
    

    该模型使用0.8的 epsilon 值进行训练。然而,可以自由测试其他值,看看结果如何变化。

    最后,绘制聚类过程的结果。由于数据集包含八个不同的特征,因此选择两个特征同时绘制,如下面的代码所示:

    plt.subplots(1, 2, sharex='col', \
                 sharey='row', figsize=(16,8))
    plt.scatter(data.iloc[:,5], data.iloc[:,3], \
                c=pred_dbscan, s=20)
    plt.xlim([0, 20000])
    plt.ylim([0,20000])
    plt.xlabel('Frozen')
    plt.subplot(1, 2, 1)
    plt.scatter(data.iloc[:,4], data.iloc[:,3], \
                c=pred_dbscan, s=20)
    plt.xlim([0, 20000])
    plt.ylim([0,20000])
    plt.xlabel('Grocery')
    plt.ylabel('Milk')
    plt.show()
    

    输出结果如下:

    图 2.21:聚类过程结束时获得的示例图

图 2.21:聚类过程结束时获得的示例图

注意

要访问本活动的源代码,请参考packt.live/2YCFvh8

你也可以在packt.live/2MZgnvC上在线运行这个示例。你必须执行整个 Notebook 才能得到预期的结果。

与之前的活动类似,由于只能同时绘制八个特征中的两个,因此无法直观地看到聚类之间的分离。

活动 2.05:衡量和比较算法的性能

解决方案:

  1. 打开你在之前活动中使用的 Jupyter Notebook。

  2. 计算所有你之前训练的模型的轮廓系数分数和 Calinski–Harabasz 指数。

    首先,导入度量:

    from sklearn.metrics import silhouette_score
    from sklearn.metrics import calinski_harabasz_score
    

    计算所有算法的轮廓系数分数,如下面的代码所示:

    kmeans_score = silhouette_score(data_standardized, \
                                    pred_kmeans, \
                                    metric='euclidean')
    meanshift_score = silhouette_score(data_standardized, \
                                       pred_meanshift, \
                                       metric='euclidean')
    dbscan_score = silhouette_score(data_standardized, \
                                    pred_dbscan, \
                                    metric='euclidean')
    print(kmeans_score, meanshift_score, dbscan_score)
    

    对于 k-means、mean-shift 和 DBSCAN 算法,分数分别约为0.35150.09330.1685

    最后,计算所有算法的 Calinski–Harabasz 指数。以下是这部分代码的片段:

    kmeans_score = calinski_harabasz_score(data_standardized, \
                                           pred_kmeans)
    meanshift_score = calinski_harabasz_score(data_standardized, \
                                              pred_meanshift)
    dbscan_score = calinski_harabasz_score(data_standardized, \
                                           pred_dbscan)
    print(kmeans_score, meanshift_score, dbscan_score)
    

    对于前面代码片段中的三个算法,它们的分数分别大约是145.73112.9042.45

    注意

    要访问本活动的源代码,请参考packt.live/2Y2xHWR

    你也可以在packt.live/3hszegy上在线运行这个示例。你必须执行整个 Notebook 才能得到预期的结果。

通过快速查看我们获得的两个度量的结果,可以得出结论:k-means 算法优于其他模型,因此应选择它来解决数据问题。

3. 监督学习——关键步骤

活动 3.01:手写数字数据集上的数据分割

解决方案:

  1. 导入所有所需的元素以拆分数据集,以及从 scikit-learn 导入load_digits函数来加载digits数据集。使用以下代码进行操作:

    from sklearn.datasets import load_digits
    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn.model_selection import KFold
    
  2. 加载digits数据集并创建包含特征和目标矩阵的 Pandas 数据框:

    digits = load_digits()
    X = pd.DataFrame(digits.data)
    Y = pd.DataFrame(digits.target)
    print(X.shape, Y.shape)
    

    特征矩阵和目标矩阵的形状应分别如下所示:

    (1797, 64) (1797, 1)
    
  3. 执行常规的拆分方法,使用 60/20/20%的拆分比例。

    使用train_test_split函数将数据拆分为初步训练集和测试集:

    X_new, X_test, \
    Y_new, Y_test = train_test_split(X, Y, test_size=0.2)
    print(X_new.shape, Y_new.shape, X_test.shape, Y_test.shape)
    

    你创建的集合的形状应如下所示:

    (1437, 64) (1437, 1) (360, 64) (360, 1)
    

    接下来,计算test_size的值,将开发集的大小设置为之前创建的测试集的大小:

    dev_size = X_test.shape[0]/X_new.shape[0]
    print(dev_size)
    

    前面的操作结果是0.2505

    最后,将X_newY_new拆分为最终的训练集和开发集。使用以下代码进行操作:

    X_train, X_dev, \
    Y_train, Y_dev = train_test_split(X_new, Y_new, \
                                      test_size = dev_size)
    print(X_train.shape, Y_train.shape, X_dev.shape, \
          Y_dev.shape, X_test.shape, Y_test.shape)
    

    前面代码段的输出如下所示:

    (1077, 64) (1077, 1) (360, 64) (360, 1) (360, 64) (360, 1)
    
  4. 使用相同的数据框,执行 10 折交叉验证拆分。

    首先,将数据集划分为初步的训练集和测试集:

    X_new_2, X_test_2, \
    Y_new_2, Y_test_2 = train_test_split(X, Y, test_size=0.1)
    

    使用KFold类执行 10 折拆分:

    kf = KFold(n_splits = 10)
    splits = kf.split(X_new_2)
    

    记住,交叉验证会执行不同的拆分配置,每次都会洗牌数据。考虑到这一点,执行一个for循环,遍历所有的拆分配置:

    for train_index, dev_index in splits:
        X_train_2, X_dev_2 = X_new_2.iloc[train_index,:], \
                             X_new_2.iloc[dev_index,:]
        Y_train_2, Y_dev_2 = Y_new_2.iloc[train_index,:], \
                             Y_new_2.iloc[dev_index,:]
    

    负责训练和评估模型的代码应放在for循环体内,以便使用每种拆分配置来训练和评估模型:

    print(X_train_2.shape, Y_train_2.shape, X_dev_2.shape, \
          Y_dev_2.shape, X_test_2.shape, Y_test_2.shape)
    

    按照前面的代码段打印所有子集的形状,输出如下:

    (1456, 64) (1456, 1) (161, 64) (161, 1) (180, 64) (180, 1)
    

    注意

    要访问此特定部分的源代码,请参阅packt.live/37xatv3

    你还可以在packt.live/2Y2nolS在线运行此示例。必须执行整个 Notebook 以获得所需结果。

你已经成功地使用常规拆分方法和交叉验证方法拆分了数据集。这些数据集现在可以用来训练表现优异的模型,这些模型能够在未见过的数据上取得良好的表现。

活动 3.02:评估在手写数据集上训练的模型的性能

解决方案:

  1. 导入所有所需的元素,以加载并拆分数据集,以便训练模型并评估分类任务的性能:

    from sklearn.datasets import load_digits
    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn import tree
    from sklearn.metrics import confusion_matrix
    from sklearn.metrics import accuracy_score
    from sklearn.metrics import precision_score
    from sklearn.metrics import recall_score
    
  2. 从 scikit-learn 加载digits玩具数据集,并创建包含特征和目标矩阵的 Pandas 数据框:

    digits = load_digits()
    X = pd.DataFrame(digits.data)
    Y = pd.DataFrame(digits.target)
    
  3. 将数据拆分为训练集和测试集。使用 20%的数据作为测试集:

    X_train, X_test, \
    Y_train, Y_test = train_test_split(X,Y, test_size = 0.2,\
                                       random_state = 0)
    
  4. 在训练集上训练决策树。然后,使用该模型预测测试集上的类别标签(提示:要训练决策树,请回顾练习 3.04计算分类任务的不同评估指标):

    model = tree.DecisionTreeClassifier(random_state = 0)
    model = model.fit(X_train, Y_train)
    Y_pred = model.predict(X_test)
    
  5. 使用 scikit-learn 构建混淆矩阵:

    confusion_matrix(Y_test, Y_pred)
    

    混淆矩阵的输出如下所示:

    图 3.14:混淆矩阵的输出

    图 3.14:混淆矩阵的输出

  6. 计算模型的准确率:

    accuracy = accuracy_score(Y_test, Y_pred)
    print("accuracy:", accuracy)
    

    准确率为 84.72%。

  7. 计算精确度和召回率。考虑到精确度和召回率只能在二元数据上计算,我们假设我们只对将实例分类为数字 6 或其他任何数字感兴趣:

    Y_test_2 = Y_test[:]
    Y_test_2[Y_test_2 != 6] = 1
    Y_test_2[Y_test_2 == 6] = 0
    Y_pred_2 = Y_pred
    Y_pred_2[Y_pred_2 != 6] = 1
    Y_pred_2[Y_pred_2 == 6] = 0
    precision = precision_score(Y_test_2, Y_pred_2)
    print("precision:", precision)
    recall = recall_score(Y_test_2, Y_pred_2)
    print("recall:", recall)
    

    上述代码片段的输出如下:

    precision: 0.9841269841269841
    recall: 0.9810126582278481
    

    根据此,精确度和召回率分数应分别等于98.41%和98.10%。

    注意

    要访问此特定部分的源代码,请参考packt.live/2UJMFPC

    您还可以在线运行此示例,网址为packt.live/2zwqkgX。必须执行整个笔记本才能获得预期结果。

您已成功衡量了分类任务的性能。

活动 3.03:对训练识别手写数字的模型进行错误分析

解决方案:

  1. 导入所需的元素以加载和拆分数据集。我们将这样做以训练模型并衡量其准确性:

    from sklearn.datasets import load_digits
    import pandas as pd
    from sklearn.model_selection import train_test_split
    import numpy as np
    from sklearn import tree
    from sklearn.metrics import accuracy_score
    
  2. 从 scikit-learn 加载 digits 玩具数据集,并创建包含特征和目标矩阵的 Pandas DataFrame:

    digits = load_digits()
    X = pd.DataFrame(digits.data)
    Y = pd.DataFrame(digits.target)
    
  3. 将数据拆分为训练集、验证集和测试集。使用 0.1 作为测试集的大小,并使用等量数据构建具有相同形状的验证集:

    X_new, X_test, \
    Y_new, Y_test = train_test_split(X, Y, test_size = 0.1,\
                                     random_state = 101)
    test_size = X_test.shape[0] / X_new.shape[0]
    X_train, X_dev, \
    Y_train, Y_dev = train_test_split(X_new, Y_new, \
                                      test_size= test_size, \
                                      random_state = 101)
    print(X_train.shape, Y_train.shape, X_dev.shape, \
          Y_dev.shape, X_test.shape, Y_test.shape)
    

    结果形状如下:

    (1437, 64) (1437, 1) (180, 64) (180, 1) (180, 64) (180, 1)
    
  4. 为特征和目标值创建训练/开发集,其中包含90个训练集实例/标签和90个开发集实例/标签:

    np.random.seed(101)
    indices_train = np.random.randint(0, len(X_train), 90)
    indices_dev = np.random.randint(0, len(X_dev), 90)
    X_train_dev = pd.concat([X_train.iloc[indices_train,:], \
                             X_dev.iloc[indices_dev,:]])
    Y_train_dev = pd.concat([Y_train.iloc[indices_train,:], \
                             Y_dev.iloc[indices_dev,:]])
    print(X_train_dev.shape, Y_train_dev.shape)
    

    结果形状如下:

    (180, 64) (180, 1)
    
  5. 在训练集数据上训练决策树:

    model = tree.DecisionTreeClassifier(random_state = 101)
    model = model.fit(X_train, Y_train)
    
  6. 计算所有数据集的错误率,并确定哪种情况影响模型的表现:

    sets = ["Training", "Train/dev", "Validation", "Testing"]
    X_sets = [X_train, X_train_dev, X_dev, X_test]
    Y_sets = [Y_train, Y_train_dev, Y_dev, Y_test]
    scores = {}
    for i in range(0, len(X_sets)):
        pred = model.predict(X_sets[i])
        score = accuracy_score(Y_sets[i], pred)
        scores[sets[i]] = score
    print(scores)
    

    输出如下:

    {'Training': 1.0, 'Train/dev': 0.9444444444444444, 'Validation': 0.8833333333333333, 'Testing': 0.8833333333333333}
    

    错误率可以在下表中看到:

    图 3.15:手写数字模型的错误率

图 3.15:手写数字模型的错误率

从之前的结果可以得出结论,模型同样受到方差和数据不匹配的影响。

注意

要访问此特定部分的源代码,请参考packt.live/3d0c4uM

您还可以在线运行此示例,网址为packt.live/3eeFlTC。必须执行整个笔记本才能获得预期结果。

您现在已经成功地执行了错误分析,以确定改进模型性能的行动方案。

4. 有监督学习算法:预测年收入

活动 4.01:为我们的普查收入数据集训练朴素贝叶斯模型

解决方案:

  1. 在 Jupyter Notebook 中,导入所有所需的元素以加载和拆分数据集,并训练朴素贝叶斯算法:

    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn.naive_bayes import GaussianNB
    
  2. 加载预处理后的普查收入数据集。接下来,通过创建两个变量 XY,将特征与目标分开:

    data = pd.read_csv("census_income_dataset_preprocessed.csv")
    X = data.drop("target", axis=1)
    Y = data["target"]
    

    请注意,分离XY的方法有多种。使用你最熟悉的方法。不过,请考虑到,X应该包含所有实例的特征,而Y应该包含所有实例的类标签。

  3. 将数据集分为训练集、验证集和测试集,使用 10%的分割比例:

    X_new, X_test, \
    Y_new, Y_test = train_test_split(X, Y, test_size=0.1, \
                                     random_state=101)
    test_size = X_test.shape[0] / X_new.shape[0]
    X_train, X_dev, \
    Y_train, Y_dev = train_test_split(X_new, Y_new, \
                                      test_size=test_size, \
                                      random_state=101)
    print(X_train.shape, Y_train.shape, X_dev.shape, \
          Y_dev.shape, X_test.shape, Y_test.shape)
    

    最终的形状如下:

    (26047, 9) (26047,) (3257, 9) (3257,) (3257, 9) (3257,)
    
  4. 使用fit方法在训练集(X_trainY_train)上训练一个朴素贝叶斯模型:

    model_NB = GaussianNB()
    model_NB.fit(X_train,Y_train)
    
  5. 最后,使用之前训练的模型对一个新实例进行预测,该实例的每个特征值如下:3961340217404038

    pred_1 = model_NB.predict([[39,6,13,4,0,2174,0,40,38]])
    print(pred_1)
    

    预测的输出结果如下:

    [0]
    

    注意

    要访问该特定部分的源代码,请参考packt.live/3ht1TCs

    你也可以在网上运行这个示例,网址是packt.live/2zwqxkf。你必须执行整个 Notebook 才能得到预期的结果。

这意味着该个人的收入低于或等于 50K,因为 0 是收入低于或等于 50K 的标签。

活动 4.02:为我们的普查收入数据集训练一个决策树模型

解决方案:

  1. 打开你在前一个活动中使用的 Jupyter Notebook,并从 scikit-learn 导入决策树算法:

    from sklearn.tree import DecisionTreeClassifier
    
  2. 使用 scikit-learn 的DecisionTreeClassifier类,通过fit方法训练模型。使用前一个活动中的训练集数据(X_trainY_train)进行训练:

    model_tree = DecisionTreeClassifier(random_state=101)
    model_tree.fit(X_train,Y_train)
    
  3. 最后,使用之前训练的模型对一个新实例进行预测,该实例的每个特征值如下:3961340217404038

    pred_2 = model_tree.predict([[39,6,13,4,0,2174,0,40,38]])
    print(pred_2)
    

    前面代码片段的输出如下:

    [0]
    

    注意

    要访问该特定部分的源代码,请参考packt.live/2zxQIqV

    你也可以在网上运行这个示例,网址是packt.live/2AC7iWX。你必须执行整个 Notebook 才能得到预期的结果。

这意味着该对象的收入低于或等于 50K。

活动 4.03:为我们的普查收入数据集训练一个 SVM 模型

解决方案:

  1. 打开你在前一个活动中使用的 Jupyter Notebook,并从 scikit-learn 导入 SVM 算法:

    from sklearn.svm import SVC
    
  2. 使用 scikit-learn 的SVC类,通过fit方法训练模型。使用前一个活动中的训练集数据(X_trainY_train)进行训练:

    model_svm = SVC()
    model_svm.fit(X_train, Y_train)
    
  3. 最后,使用之前训练的模型对一个新实例进行预测,该实例的每个特征值如下:3961340217404038

    pred_3 = model_svm.predict([[39,6,13,4,0,2174,0,40,38]])
    print(pred_3)
    

    输出结果如下:

    [0]
    

    该个人的预测值为零,这意味着该个人的收入低于或等于50K

    注意

    要查看本特定部分的源代码,请参阅 packt.live/2Nb6J9z

    您还可以在 packt.live/3hbpCGm 上在线运行此示例。必须执行整个 Notebook 才能获得所需的结果。

5. 人工神经网络:预测年收入

活动 5.01:为我们的人口普查收入数据集训练 MLP

解决方案:

  1. 导入加载和分割数据集所需的所有元素,以训练 MLP 并测量准确率:

    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn.neural_network import MLPClassifier
    from sklearn.metrics import accuracy_score
    
  2. 使用预处理后的人口普查收入数据集,将特征与目标分开,创建变量 XY

    data = pd.read_csv("census_income_dataset_preprocessed.csv")
    X = data.drop("target", axis=1)
    Y = data["target"]
    

    如前所述,有几种方法可以实现 XY 的分离,主要考虑的是 X 应包含所有实例的特征,而 Y 应包含所有实例的类标签。

  3. 使用分割比例为 10%将数据集划分为训练、验证和测试集:

    X_new, X_test, \
    Y_new, Y_test = train_test_split(X, Y, test_size=0.1, \
                                     random_state=101)
    test_size = X_test.shape[0] / X_new.shape[0]
    X_train, X_dev, \
    Y_train, Y_dev = train_test_split(X_new, Y_new, \
                                      test_size=test_size, \
                                      random_state=101)
    print(X_train.shape, X_dev.shape, X_test.shape, \
          Y_train.shape, Y_dev.shape, Y_test.shape)
    

    创建的集合形状应如下:

    (26047, 9) (3257, 9) (3257, 9) (26047,) (3257,) (3257,)
    
  4. 从 scikit-learn 实例化 MLPClassifier 类,并使用训练数据训练模型。将超参数保留为默认值。同样,使用 random_state 等于 101

    model = MLPClassifier(random_state=101)
    model = model.fit(X_train, Y_train)
    
  5. 计算所有三个集合(训练、验证和测试)的模型准确率:

    sets = ["Training", "Validation", "Testing"]
    X_sets = [X_train, X_dev, X_test]
    Y_sets = [Y_train, Y_dev, Y_test]
    accuracy = {}
    for i in range(0,len(X_sets)):
        pred = model.predict(X_sets[i])
        score = accuracy_score(Y_sets[i], pred)
        accuracy[sets[i]] = score
    print(accuracy)
    

    三个集合的准确率分数应如下:

    {'Training': 0.8465909090909091, 'Validation': 0.8246314496314496, 'Testing': 0.8415719987718759}
    

    注意

    要查看本特定部分的源代码,请参阅 packt.live/3hneWFr

    此部分目前没有在线互动示例,需要在本地运行。

您已成功训练了一个 MLP 模型来解决现实生活中的数据问题。

活动 5.02:比较不同模型以选择最适合人口普查收入数据问题的模型

解决方案:

  1. 打开您用来训练模型的 Jupyter Notebook。

  2. 仅基于它们的准确率分数比较四个模型。

    通过使用上一章节模型的准确率分数以及本章节训练的模型的准确率,可以进行最终比较,以选择最佳解决数据问题的模型。为此,以下表格显示了所有四个模型的准确率分数:

    图 5.15:人口普查收入数据集所有四个模型的准确率分数

    图 5.15:人口普查收入数据集所有四个模型的准确率分数

  3. 根据准确率分数确定最佳解决数据问题的模型。

    要确定最佳解决数据问题的模型,首先比较训练集上的准确率。从中可以得出结论,决策树模型比数据问题更适合。尽管如此,在验证和测试集上的性能低于使用 MLP 实现的性能,这表明决策树模型存在高方差的迹象。

    因此,一个好的方法是通过简化决策树模型来解决其高方差问题。这可以通过添加一个剪枝参数来实现,该参数会“修剪”树的叶子,简化模型并忽略树的一些细节,从而使模型能够更好地泛化到数据。理想情况下,模型应该能够在所有三个数据集上达到相似的准确率,这将使它成为数据问题的最佳模型。

    然而,如果模型无法克服高方差,并且假设所有模型都已经经过精调以实现最大性能,那么应选择 MLP 模型,因为它在测试集上的表现最好。主要原因是模型在测试集上的表现定义了其在未见数据上的总体表现,这意味着测试集表现更好的模型在长期使用中将更加有用。

6. 构建你自己的程序

活动 6.01:为银行营销数据集执行准备和创建阶段

解决方案:

注意

为确保packt.live/2RpIhn9中结果的可重复性,请确保在划分数据集时使用random_state0,在训练模型时使用random_state2

  1. 打开 Jupyter Notebook 并导入所有必需的元素:

    import pandas as pd
    from sklearn.preprocessing import LabelEncoder
    from sklearn.model_selection import train_test_split
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.neural_network import MLPClassifier
    from sklearn.metrics import precision_score
    
  2. 将数据集加载到笔记本中。确保加载先前编辑过的数据集,文件名为bank-full-dataset.csv,该数据集也可以在packt.live/2wnJyny找到:

    data = pd.read_csv("bank-full-dataset.csv")
    data.head(10)
    

    输出结果如下:

    图 6.8:显示数据集前 10 个实例的截图

    图 6.8:显示数据集前 10 个实例的截图

    缺失值显示为NaN,如前所述。

  3. 选择最适合衡量模型性能的指标,考虑到本研究的目的是检测哪些客户会订阅定期存款。

    评估模型性能的指标是精确度指标,因为它比较正确分类的正标签与被预测为正的所有实例的总数。

  4. 预处理数据集。

    处理缺失值的过程按照我们在第一章,《Scikit-Learn 介绍》中学到的概念进行,并已在本书中应用。使用以下代码检查缺失值:

    data.isnull().sum()
    

    根据结果,你会观察到只有四个特征包含缺失值:job(288),education(1,857),contact(13,020),和poutcome(36,959)。

    前两个特征可以不处理,因为缺失值占整个数据的比例低于 5%。另一方面,contact特征的缺失值占 28.8%,并且考虑到该特征表示的是联系方式,这与判断一个人是否会订阅新产品无关,因此可以安全地将该特征从研究中移除。最后,poutcome特征缺失了 81.7%的值,这也是该特征被从研究中移除的原因。

    使用以下代码,将前述两个特征删除:

    data = data.drop(["contact", "poutcome"], axis=1)
    

    正如我们在第一章中解释的那样,Scikit-Learn 简介,并在全书中应用,将分类特征转换为数值形式的过程如下所示。

    对于所有的名义特征,使用以下代码:

    enc = LabelEncoder()
    features_to_convert=["job","marital","default",\
                         "housing","loan","month","y"]
    for i in features_to_convert:
        data[i] = enc.fit_transform(data[i].astype('str'))
    

    如前所述,前面的代码将所有定性特征转换为它们的数值形式。

    接下来,为了处理有序特征,我们必须使用以下代码,如步骤 4中所述:

    data['education'] = data['education'].fillna('unknown')
    encoder = ['unknown','primary','secondary','tertiary']
    for i, word in enumerate(encoder):
        data['education'] = data['education'].astype('str').\
                            str.replace(word, str(i))
    data['education'] = data['education'].astype('int64')
    data.head()
    

    这里,第一行将NaN值转换为unknown,而第二行设置了特征中值的顺序。接下来,使用for循环将每个单词替换为一个按顺序排列的数字。在上述示例中,0将替换unknown,然后1将替换primary,以此类推。最后,整个列被转换为整数类型,因为replace函数将数字写入为字符串。

    如果我们显示结果数据框的头部,输出如下所示:

    图 6.9:截图显示将分类特征转换为数值特征后数据集的前五个实例

    outliers = {}
    for i in range(data.shape[1]):
        min_t = data[data.columns[i]].mean() \
                - (3 * data[data.columns[i]].std())
        max_t = data[data.columns[i]].mean() \
                + (3 * data[data.columns[i]].std())
        count = 0
        for j in data[data.columns[i]]:
            if j < min_t or j > max_t:
                count += 1
        outliers[data.columns[i]] = [count, data.shape[0]]
    print(outliers)
    

    如果我们打印出结果字典,输出如下所示:

    {'age': [381, 45211], 'job': [0, 45211], 'marital': [0, 45211], 'education': [0, 45211], 'default': [815, 45211], 'balance': [745, 45211], 'housing': [0, 45211], 'loan': [0, 45211], 'day': [0, 45211], 'month': [0, 45211], 'duration': [963, 45211], 'campaign': [840, 45211], 'pdays': [1723, 45211], 'previous': [582, 45211], 'y': [0, 45211]}
    

    正如我们所见,离群值在每个特征中的占比都不超过 5%,因此可以不处理这些离群值。

    通过取特征中最具离群值的特征(pdays),并将离群值的数量除以实例的总数(1,723 除以 45,211),可以验证这一点。这个操作的结果是 0.038,相当于 3.8%。这意味着该特征的离群值只占 3.8%。

  5. 将特征与类别标签分开,并将数据集分成三部分(训练集、验证集和测试集)。

    要将特征与目标值分开,使用以下代码:

    X = data.drop("y", axis = 1)
    Y = data["y"]
    

    接下来,为了进行 60/20/20 的拆分,使用以下代码:

    X_new, X_test, \
    Y_new, Y_test = train_test_split(X, Y, test_size=0.2,\
                                     random_state = 0)
    test_size = X_test.shape[0] / X_new.shape[0]
    X_train, X_dev, \
    Y_train, Y_dev = train_test_split(X_new, Y_new, \
                                      test_size=test_size,\
                                      random_state = 0)
    print(X_train.shape, Y_train.shape, X_dev.shape, \
        Y_dev.shape, X_test.shape, Y_test.shape)
    

    如果我们打印出所有子集的形状,输出如下所示:

    (27125, 14) (27125,) (9043, 14) (9043,) (9043, 14) (9043,)
    
  6. 对数据集使用决策树算法并训练模型:

    model_tree = DecisionTreeClassifier(random_state = 2)
    model_tree.fit(X_train, Y_train)
    

    注释

    提醒一下,调用fit方法时的输出是正在训练的模型及其所包含的所有参数。

  7. 对数据集使用多层感知器算法并训练模型。要回顾这一点,请参阅第五章人工神经网络:预测年收入

    model_NN = MLPClassifier(random_state = 2)
    model_NN.fit(X_train, Y_train)
    
  8. 使用之前选择的指标评估两个模型。

    使用以下代码,可以测量决策树模型的精度分数:

    X_sets = [X_train, X_dev, X_test]
    Y_sets = [Y_train, Y_dev, Y_test]
    precision = []
    for i in range(0, len(X_sets)):
        pred = model_tree.predict(X_sets[i])
        score = precision_score(Y_sets[i], pred)
        precision.append(score)
    print(precision)
    

    如果我们打印包含决策树模型每个数据集精度分数的列表,输出如下:

    [1.0, 0.43909348441926344, 0.4208059981255858]
    

    相同的代码可以修改,用于计算多层感知器的分数:

    X_sets = [X_train, X_dev, X_test]
    Y_sets = [Y_train, Y_dev, Y_test]
    precision = []
    for i in range(0, len(X_sets)):
        pred = model_NN.predict(X_sets[i])
        score = precision_score(Y_sets[i], pred)
        precision.append(score)
    print(precision)
    

    如果我们打印包含多层感知器模型每个数据集精度分数的列表,输出如下:

    [0.35577647236029525, 0.35199283475145543, 0.3470483005366726]
    

    以下表格显示了两个模型在所有数据子集上的精度分数:

    图 6.10: 两个模型的精度分数

    图 6.10: 两个模型的精度分数

  9. 通过执行错误分析,微调一些超参数,修正评估模型时检测到的问题。

    尽管决策树在训练集上的精度完美,但与其他两个数据集的结果进行比较后,可以得出模型存在高方差的结论。

    另一方面,多层感知器在所有三个数据集上的表现相似,但整体表现较低,这意味着该模型更可能存在高偏差。

    考虑到这一点,对于决策树模型,为了简化模型,修改了叶节点所需的最小样本数和树的最大深度。另一方面,对于多层感知器,修改了迭代次数、隐藏层数、每层的单元数以及优化的容忍度。

    以下代码显示了决策树算法超参数的最终值,考虑到为了得到这些值需要尝试不同的参数:

    model_tree = DecisionTreeClassifier(random_state = 2, \
                                        min_samples_leaf=100, \
                                        max_depth=100)
    model_tree.fit(X_train, Y_train)
    

    以下代码片段显示了多层感知器算法的超参数最终值:

    model_NN = \
        MLPClassifier(random_state = 2, max_iter=1000,\
                      hidden_layer_sizes = [100,100,50,25,25], \
                      tol=1e-4)
    model_NN.fit(X_train, Y_train)
    

    注意

    提醒一下,调用fit方法的输出包括正在训练的模型,以及它所接受的所有参数。

  10. 比较你模型的最终版本,并选择你认为最适合数据的那个。

    使用与前几步相同的代码,可以计算决策树模型在不同数据集上的精度:

    X_sets = [X_train, X_dev, X_test]
    Y_sets = [Y_train, Y_dev, Y_test]
    precision = []
    for i in range(0, len(X_sets)):
        pred = model_tree.predict(X_sets[i])
        score = precision_score(Y_sets[i], pred)
        precision.append(score)
    print(precision)
    

    输出列表应如下所示:

    [0.6073670992046881, 0.5691158156911582, 0.5448113207547169]
    

    要计算多层感知器的精度,可以使用以下代码片段:

    X_sets = [X_train, X_dev, X_test]
    Y_sets = [Y_train, Y_dev, Y_test]
    precision = []
    for i in range(0, len(X_sets)):
        pred = model_NN.predict(X_sets[i])
        score = precision_score(Y_sets[i], pred)
        precision.append(score)
    print(precision)
    

    结果列表应如下所示:

    [0.759941089837997, 0.5920398009950248, 0.5509259259259259]
    

    通过计算新训练模型在三个数据集上的精度分数,我们获得以下值:

图 6.11: 新训练模型的精度分数

图 6.11: 新训练模型的精度分数

注意

要访问此特定部分的源代码,请参考packt.live/2RpIhn9

本节当前没有在线交互示例,需要在本地运行。

对两个模型的性能进行了提升,并通过比较其值,可以得出结论:多层感知机优于决策树模型。基于此,多层感知机被选择为解决数据问题的更好模型。

注意

我们鼓励你继续微调参数,以达到更高的精度评分。

活动 6.02:保存和加载银行营销数据集的最终模型

解决方案:

  1. 打开活动 6.01中的 Jupyter Notebook,执行银行营销数据集的准备和创建阶段

  2. 为了学习目的,使用你选择的最佳模型,去除random_state参数,并运行几次。

  3. 将你选择的最佳表现模型保存到一个名为final_model.pkl的文件中。

    path = os.getcwd() + "/final_model.pkl"
    file = open(path, "wb")
    pickle.dump(model_NN, file)
    
  4. 打开一个新的 Jupyter Notebook,并导入所需的模块和类:

    from sklearn.neural_network import MLPClassifier
    import pickle
    import os
    
  5. 加载保存的模型:

    path = os.getcwd() + "/final_model.pkl"
    file = open(path, "rb")
    model = pickle.load(file)
    
  6. 使用以下值为个体进行预测:422001210583801-10

    pred = model.predict([[42,2,0,0,1,2,1,0,5,8,380,1,-1,0]])
    print(pred)
    

    注意

    要访问此特定部分的源代码,请参考packt.live/2UIWFss

    本节当前没有在线交互示例,需要在本地运行。

如果我们打印pred变量,输出为0,这是No的数值形式。这意味着该个体更有可能不会订阅新产品。

活动 6.03:允许与银行营销数据集模型进行交互

解决方案:

  1. 在文本编辑器中,创建一个包含两个主要函数的类对象。一个应是加载保存模型的初始化器,另一个应是predict方法,用于将数据传递给模型以获取输出:

    import pickle
    import os
    

    根据前面的代码片段,第一步是导入所有所需的元素以定位并反序列化保存的模型:

    Class NN_Model(object):
        def __init__(self):
            path = os.getcwd() + "/model_exercise.pkl"
            file = open(path, "rb")
            self.model = pickle.load(file)
        def predict(self, age, job, marital, education, \
                    default, balance, housing, loan, day, \
                    month, duration, campaign, pdays, previous):
            X = [[age, job, marital, education, default, \
                  balance, housing, loan, day, month, \
                  duration, campaign, pdays, previous]]
            return self.model.predict(X)
    

    接下来,根据前面的代码片段,编写将保存的模型与交互通道连接的类。该类应具有一个初始化方法,用于反序列化并加载保存的模型,以及一个predict方法,用于将输入数据传递给模型进行预测。

  2. 在 Jupyter Notebook 中,导入并初始化在前一步创建的类。接下来,创建变量来存储新观测值的特征值,并使用以下值:422001210583801-10

    from trainedModel import NN_Model
    model = NN_Model()
    age = 42
    job = 2
    marital = 0
    education = 0
    default = 1
    balance = 2
    housing = 1
    loan = 0
    day = 5
    month = 8
    duration = 380
    campaign = 1
    pdays = -1
    previous = 0
    

    通过应用predict方法进行预测:

    pred = model.predict(age=age, job=job, marital=marital, \
                         education=education, default=default, \
                         balance=balance, housing=housing, \
                         loan=loan, day=day, month=month, \
                         duration=duration, campaign=campaign, \
                         pdays=pdays, previous=previous)
    print(pred)
    

    通过打印变量,预测值为0;也就是说,具有给定特征的个体不太可能订阅该产品,正如这里所看到的:

    [0]
    

    注意

    要访问此特定部分的源代码,请参考packt.live/2Y2yBCJ

    你也可以在packt.live/3d6ku3E在线运行这个示例。你必须执行整个 Notebook 才能得到预期的结果。

在本章的所有活动中,你已经成功地学会了如何开发一个完整的机器学习解决方案,从数据预处理和训练模型,到使用误差分析选择最佳性能的模型,并保存模型以便有效利用。