机器学习研讨会-全-

机器学习研讨会(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

关于本书

机器学习算法几乎是所有现代应用程序的核心部分。为了加快学习过程并提高准确性,你需要一个足够灵活和强大的工具,帮助你快速、轻松地构建机器学习算法。通过《机器学习工作坊(第二版)》,你将掌握 scikit-learn 库,并成为开发巧妙机器学习算法的高手。

《机器学习工作坊(第二版)》首先通过分析批发客户的真实数据集,展示了无监督和监督学习算法的工作原理。在掌握基础知识后,你将使用 scikit-learn 开发一个人工神经网络,并通过调整超参数来提高其性能。在工作坊的后期,你将研究一家银行的营销活动数据集,并构建可以列出可能订阅定期存款的客户的机器学习模型。你还将学习如何比较这些模型并选择最优模型。

到了《机器学习工作坊(第二版)》的结尾,你不仅会学到监督学习和无监督学习模型之间的区别及其在现实世界中的应用,还将掌握开始编程自己机器学习算法所需的技能。

读者群体

《机器学习工作坊(第二版)》是机器学习初学者的理想之选。你需要有 Python 编程经验,但无需具备 scikit-learn 和机器学习的先前知识。

关于章节

第一章Scikit-Learn 简介,介绍了本书的两个主要主题:机器学习和 scikit-learn。它解释了输入数据预处理的关键步骤,如何分离特征与目标,如何处理杂乱数据,以及如何重新调整数据值的尺度。

第二章无监督学习——真实生活应用,通过讲解三种最常见的聚类算法,解释了机器学习中聚类的概念。

第三章监督学习——关键步骤,描述了通过监督学习算法可以解决的不同任务:分类和回归。

第四章监督学习算法:预测年收入,教授解决监督学习数据问题的不同概念和步骤。

第五章人工神经网络:预测年收入,展示了如何使用神经网络解决监督学习分类问题,并通过执行误差分析来分析结果。

第六章构建你自己的程序,解释了开发全面机器学习解决方案所需的所有步骤。

约定

文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名如下所示:

使用seaborn库加载titanic数据集。

您在屏幕上看到的单词(例如,在菜单或对话框中)以相同的格式显示。

代码块设置如下:

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

新术语和重要单词以这种方式显示:

“缺失信息或包含异常值或噪声的数据被认为是杂乱数据。”

代码展示

跨多行的代码使用反斜杠(\)分割。当代码执行时,Python 会忽略反斜杠,并将下一行代码视为当前行的直接延续。

例如:

history = model.fit(X, y, epochs=100, batch_size=5, verbose=1, \
                    validation_split=0.2, shuffle=False)

注释被添加到代码中,用于帮助解释特定的逻辑部分。单行注释使用#符号表示,如下所示:

# Print the sizes of the dataset
print("Number of Examples in the Dataset = ", X.shape[0])
print("Number of Features for each example = ", X.shape[1])

多行注释用三引号括起来,如下所示:

"""
Define a seed for the random number generator to ensure the 
result will be reproducible
"""
seed = 1
np.random.seed(seed)
random.set_seed(seed)

设置您的开发环境

在我们详细探讨这本书之前,我们需要安装一些特定的软件和工具。在接下来的章节中,我们将看到如何操作。

在 Windows 和 MacOS 上安装 Python

按照以下步骤安装 Python 3.7 在 Windows 和 macOS 上:

  1. 访问www.python.org/downloads/release/python-376/下载 Python 3.7。

  2. 在页面底部,找到标题为Files的表格:

    对于 Windows,点击Windows x86-64 executable installer以安装 64 位版本,或点击Windows x86 executable installer以安装 32 位版本。

    对于 macOS,点击macOS 64-bit/32-bit installer以安装适用于 macOS 10.6 及更高版本的版本,或点击macOS 64-bit installer以安装适用于 macOS 10.9 及更高版本的版本。

  3. 运行您已下载的安装程序。

在 Linux 上安装 Python

  1. 打开终端并输入以下命令:

    sudo apt-get install python3.7
    

安装 pip

pip默认会随 Python 3.7 的安装一同安装。然而,也有可能它没有被安装。要检查是否已安装,请在终端或命令提示符中执行以下命令:

pip --version

由于您计算机上以前版本的pip已经使用了pip命令,您可能需要使用pip3命令。

如果pip命令(或pip3)未被您的机器识别,请按照以下步骤安装它:

  1. 要安装pip,访问pip.pypa.io/en/stable/installing/并下载get-pip.py文件。

  2. 然后,在终端或命令提示符中,使用以下命令安装它:

    python get-pip.py
    

由于您机器上已安装的旧版本 Python 可能仍在使用python命令,您可能需要使用python3 get-pip.py命令。

安装库

pip随 Anaconda 预安装。一旦在机器上安装了 Anaconda,所有需要的库可以通过pip安装,例如,pip install numpy。另外,您也可以使用pip install –r requirements.txt安装所有需要的库。您可以在packt.live/2Ar1i3v找到requirements.txt文件。

练习和活动将在 Jupyter Notebook 中执行。Jupyter 是一个 Python 库,可以像其他 Python 库一样安装——即使用 pip install jupyter,但幸运的是,它已经随 Anaconda 预装。要打开一个 notebook,只需在终端或命令提示符中运行命令 jupyter notebook

打开 Jupyter Notebook

  1. 打开终端/命令提示符。

  2. 在终端/命令提示符中,进入你克隆的书籍仓库所在的目录。

  3. 通过输入以下命令来打开 Jupyter Notebook:

    jupyter notebook
    
  4. 执行前面的命令后,你将能够通过机器的默认浏览器使用 Jupyter Notebook。

访问代码文件

你可以在packt.live/2wkiC8d找到本书的完整代码文件。你还可以通过使用packt.live/3cYbopv上的交互式实验环境直接在网页浏览器中运行许多活动和练习。

我们已尽力支持所有活动和练习的交互式版本,但我们也建议进行本地安装,以应对在没有此支持的情况下的使用需求。

本书中使用的高质量彩色图片可以在packt.live/3exaFfJ找到。

如果你在安装过程中遇到任何问题或有任何疑问,请通过电子邮件联系我们:workshops@packt.com。

第一章:1. Scikit-Learn 简介

概述

本章介绍了本书的两个主要主题:机器学习和 scikit-learn。通过阅读本书,你将学习到机器学习的概念和应用。你还将了解数据在机器学习中的重要性,以及数据预处理的关键方面,以解决各种数据问题。本章还将涵盖 scikit-learn 的基本语法。通过本章的学习,你将对 scikit-learn 的语法有一个坚实的理解,从而能够解决简单的数据问题,这将成为开发机器学习解决方案的起点。

简介

机器学习ML)无疑是当今最重要的技术之一,因为它旨在将信息(数据)转化为可以用于做出明智决策的知识。在本章中,你将学习到机器学习在当今世界中的不同应用,以及数据在其中所扮演的角色。这将成为本书中介绍不同数据问题的起点,你将能够通过使用 scikit-learn 来解决这些问题。

Scikit-learn 是一个文档齐全且易于使用的库,通过使用简单的方法促进了机器学习算法的应用,最终使初学者能够在无需深入了解算法背后的数学知识的情况下进行数据建模。此外,得益于这个库的易用性,它使得用户能够为数据问题实现不同的近似方法(即创建不同的模型)。更重要的是,通过去除编写算法代码的任务,scikit-learn 使得团队能够将注意力集中在分析模型结果上,以得出关键结论。

Spotify,作为全球领先的音乐流媒体公司,使用 scikit-learn,因为它允许他们为数据问题实施多个模型,并且这些模型可以轻松地与现有开发系统进行连接。这个过程改进了获得有用模型的过程,同时使公司能够以极少的努力将这些模型接入现有应用中。

另一方面, booking.com 使用 scikit-learn,是因为该库提供了种类繁多的算法,使他们能够完成公司依赖的各种数据分析任务,例如构建推荐引擎、检测欺诈活动以及管理客户服务团队。

鉴于上述要点,本章还将解释 scikit-learn 及其主要用途和优点,并简要介绍 scikit-learn 应用程序接口API)的语法和功能。此外,还将展示数据表示、可视化和标准化的过程。上述信息将帮助我们理解开发机器学习模型所需采取的不同步骤。

在本书的接下来的章节中,你将探索可以用于解决现实数据问题的主要机器学习算法。你还将学习不同的技术,用于衡量算法的性能,并了解如何相应地改进它们。最后,你将探索如何通过保存、加载模型并创建 API 来使用训练好的模型。

机器学习简介

机器学习ML)是人工智能AI)的一个子集,包含了多种能够从输入数据中学习的算法,而不需要为特定任务进行编程。这种从数据中学习的能力使得算法能够创建模型,通过在历史数据中寻找模式并随着新数据的输入不断改进模型,从而解决复杂的数据问题。

这些不同的机器学习算法使用不同的近似方法来解决任务(例如概率函数),但关键是它们能够考虑大量变量来解决特定的数据问题,使得最终的模型在解决任务时比人类更有效。通过机器学习算法创建的模型旨在从输入数据中寻找模式,以便在未来做出更有根据的预测。

机器学习的应用

一些常见的可以通过机器学习算法解决的任务包括价格/需求预测、产品/服务推荐以及数据过滤等。以下是这些任务的现实生活中的一些例子:

  • 按需价格预测:服务价格随着需求变化的公司可以使用机器学习算法预测未来的需求,并确定是否有能力满足这一需求。例如,在交通运输行业,如果未来的需求低(淡季),航班价格将下降。相反,如果需求高(旺季),航班价格可能会增加。

  • 娱乐推荐:利用你当前使用的音乐以及与自己相似的人的音乐,机器学习算法能够构建模型,建议你可能喜欢的新专辑。这同样适用于视频流媒体应用程序和在线书店。

  • 电子邮件过滤:机器学习已经被应用于过滤来信,将垃圾邮件与你想要的邮件分开。最近,它还能够将不需要的邮件分类为更多的类别,如社交邮件和促销邮件。

选择合适的机器学习算法

在开发机器学习解决方案时,需要强调的是,往往没有单一的解决方案可以解决所有的数据问题,就像没有适用于所有数据问题的算法一样。基于这一点,考虑到机器学习领域存在大量算法,选择适合特定数据问题的算法往往是区分优秀模型和普通模型的转折点。

以下步骤可以帮助将算法范围缩小到几个:

  1. 了解你的数据:鉴于数据是能够开发任何机器学习解决方案的关键,第一步始终应该是理解数据,以便能够筛选掉无法处理这些数据的算法。

    例如,考虑到数据集中特征和观察值的数量,可以确定是否需要一种能够在小型数据集上产生优秀结果的算法。判断数据集是否较小,取决于数据问题、输出的数量等因素。此外,通过理解数据集中字段的类型,你还可以判断是否需要能够处理分类数据的算法。

  2. A) 算法。另一方面,没有目标特征的数据集被称为无标签数据,并通过无监督学习算法(B)解决。

    此外,输出数据(你期望从模型中获得的输出形式)也在决定使用哪些算法时发挥着关键作用。如果模型的输出需要是一个连续的数值,则任务是回归问题(C)。另一方面,如果输出是一个离散值(例如一组类别),则任务是分类问题(D)。最后,如果输出是一组观察值的子集,那么需要执行的是聚类任务(E):

    图 1.1:展示任务划分

    图 1.1:展示任务划分

    本章的监督学习与无监督学习部分将对任务的划分进行更详细的探讨。

  3. 选择一组算法:一旦完成前述步骤,就可以筛选出那些在输入数据上表现良好并能够达到预期结果的算法。根据你的资源和时间限制,你应该从这些合适的算法中选择一些进行测试,考虑到尝试多种算法始终是一个好习惯。

这些步骤将在下一章中通过一个实际数据问题作为例子进行更详细的解释。

Scikit-Learn

scikit-learn 由 David Cournapeau 于 2007 年作为 Google Summer of Code 项目的一部分创建,是一个开源的 Python 库,旨在简化基于内置机器学习和统计算法构建模型的过程,无需手动编写代码。它广受欢迎的主要原因是其完整的文档、易于使用的 API 以及众多致力于每天改进该库的合作者。

注意

你可以在scikit-learn.org找到 scikit-learn 的文档。

Scikit-learn 主要用于对数据建模,而不是用于处理或总结数据。它为用户提供了一个易于使用、统一的 API,可以用很少的学习成本应用不同的模型,而无需深入了解背后的数学原理。

注意

要理解这些模型,你需要了解一些数学知识,包括线性代数、概率论和多元微积分。有关这些模型的更多信息,请访问towardsdatascience.com/the-mathematics-of-machine-learning-894f046c568

在 scikit-learn 库中可用的模型分为两类,即监督学习和无监督学习,本文稍后将对这两类进行深入解释。这种分类方式有助于确定在特定数据集上使用哪种模型,以获取最大的信息量。

除了在监督学习问题中用于预测未来行为以及在无监督学习问题中进行数据聚类,scikit-learn 还用于以下几个方面:

  • 进行交叉验证和性能指标分析,以了解从模型中获得的结果,并从而提升其性能。

  • 获取样本数据集以在其上测试算法

  • 进行特征提取,从图像或文本数据中提取特征

尽管 scikit-learn 被认为是机器学习领域初学者首选的 Python 库,但全球有许多大公司也在使用它,因为它能通过将模型应用于现有开发来改进产品或服务。同时,它也允许公司快速对新想法进行测试。

一些主要使用 scikit-learn 的公司如下:

  • Spotify:作为最受欢迎的音乐流媒体应用之一,Spotify 主要使用 scikit-learn,原因是该框架提供了丰富的算法选择,同时也很容易将新模型集成到现有开发中。scikit-learn 已被应用于其音乐推荐模型。

  • Booking.com:从开发推荐系统到防止欺诈活动,及许多其他解决方案,这个旅行元搜索引擎能够利用 scikit-learn 探索大量算法,从而创建最先进的模型。

  • Evernote:这款笔记记录和管理应用程序使用 scikit-learn 来处理训练分类模型所需的多个步骤,从数据探索到模型评估。

  • Change.org:由于该框架易于使用且算法种类繁多,这家非营利组织能够创建电子邮件营销活动,触及全球数百万读者。

    注意

    你可以访问 scikit-learn.org/stable/testimonials/testimonials.html 以了解其他使用 scikit-learn 的公司,并查看它们如何使用该库。

总之,scikit-learn 是一个开源的 Python 库,它通过 API 将大多数机器学习任务(包括有监督和无监督)应用于数据问题。它的主要用途是对数据建模,从而可以对未见过的观察数据做出预测;然而,它的功能不限于此,因为该库还允许用户根据正在训练的模型预测结果,并分析模型的性能等。

Scikit-learn 的优势

以下是使用 scikit-learn 进行机器学习的主要优势:

  • 易用性:与其他库(如 TensorFlow 或 Keras)相比,scikit-learn 以简洁的 API 和较小的学习曲线为特点。该 API 以其统一性和直接性而受到欢迎。使用 scikit-learn 的用户不一定需要理解模型背后的数学原理。

  • 统一性:它统一的 API 使得从一个模型切换到另一个模型非常容易,因为不同模型所需的基本语法是相同的。

  • 文档/教程:该库完全由文档支持,文档易于访问且易于理解。此外,它还提供了逐步教程,涵盖了开发任何机器学习项目所需的所有主题。

  • 可靠性和协作:作为一个开源库,scikit-learn 受益于多个合作者的贡献,他们每天都在为其性能改进而努力。来自不同背景的专家的参与,不仅帮助开发了一个更完整的库,也使得库更加可靠。

  • 覆盖范围:当你浏览该库所包含的组件时,你会发现它涵盖了大多数机器学习任务,从有监督的回归任务到无监督的聚类任务。更重要的是,由于拥有众多合作者,新模型通常会在相对较短的时间内加入。

Scikit-learn 的缺点

以下是使用 scikit-learn 进行机器学习的主要缺点:

  • 不灵活性:由于易于使用,这个库往往缺乏灵活性。这意味着用户在参数调优或模型架构方面的自由度较低,例如在梯度提升算法和神经网络中。这对于初学者在处理更复杂项目时会成为一个问题。

  • 不适合深度学习:当处理复杂的机器学习项目时,该库的表现不足。尤其是在深度学习方面,因为 scikit-learn 不支持具有必要架构或计算能力的深度神经网络。

    注意

    深度学习是机器学习的一部分,基于人工神经网络的概念。它通过一系列层次从输入数据中提取有价值的信息(特征)。在本书的后续章节中,您将学习到神经网络,这是能够开发深度学习解决方案的起点。

总的来说,scikit-learn 是一个优秀的入门级库,因为它学习使用起来几乎不需要任何努力,并且有许多辅助材料来帮助其应用。由于多位贡献者的努力,该库保持更新并适用于大多数当前的数据问题。

另一方面,它是一个简单的库,不适合处理更复杂的数据问题,例如深度学习。同样,它也不推荐给那些希望通过调整每个模型中可用的不同参数来提升能力的用户。

其他框架

其他流行的机器学习框架如下:

  • TensorFlow:谷歌的开源机器学习框架,至今仍是数据科学家中最受欢迎的框架。它通常与 Python 集成,并且非常适合开发深度学习解决方案。由于其广泛的流行性,网络上关于该框架的信息使得开发不同的解决方案变得非常容易,更不用说它有 Google 的支持。

  • PyTorch:该框架由 Facebook 的 AI 研究实验室主要开发,是一个开源深度学习框架。尽管它是一个相对较新的框架(发布于 2017 年),但由于其易用性和 Python 特性,它的流行度逐渐上升。得益于动态图计算,它使得代码调试变得非常简单。

  • Keras:这是一个开源深度学习框架,通常适合刚入门的人。由于其简洁性,灵活性较差,但非常适合原型设计简单的概念。与 scikit-learn 类似,它也有自己易于使用的 API。

数据表示

机器学习的主要目标是通过解释数据来构建模型。为了做到这一点,必须以计算机能够读取的方式输入数据。为了将数据输入到 scikit-learn 模型中,数据必须以表格或矩阵的形式表示,并且具有所需的维度,这将在接下来的章节中讨论。

数据表

输入到机器学习问题中的大多数表格是二维的,这意味着它们包含行和列。通常,每一行代表一个观测值(一个实例),而每一列代表每个观测值的一个特征。

以下表格是一个 scikit-learn 示例数据集的片段。该数据集的目的是根据植物的特征区分三种不同类型的鸢尾花。因此,在下表中,每一行代表一棵植物,每一列表示该植物在每个特征上的值:

图 1.2:展示鸢尾花数据集前 10 个实例的表格

图 1.2:展示鸢尾花数据集前 10 个实例的表格

从前面的说明来看,通过查看前表的第一行,可以确定该观测值对应的是一棵花萼长度为5.1、花萼宽度为3.5、花瓣长度为1.4、花瓣宽度为0.2的植物。这棵植物属于setosa种类。

注意

当将图像输入到模型时,表格变为三维,其中行和列表示图像的像素维度,而深度则表示其颜色模式。如果你感兴趣,可以了解更多关于卷积神经网络的内容。

表格中的数据也称为结构化数据。另一方面,非结构化数据指的是无法存储在类似表格的数据库中的所有其他数据(即,不是以行和列的形式)。这包括图像、音频、视频和文本(如电子邮件或评论)。为了能够将非结构化数据输入到机器学习算法中,第一步应该是将其转化为算法可以理解的格式(即表格数据)。例如,图像被转换为像素矩阵,文本被编码为数值。

特征和目标矩阵

对于许多数据问题,数据集中的一个特征将作为setosa种类来使用。因此,学习如何将目标矩阵与特征矩阵分开非常重要。

[n_i, n_f],其中n_i表示实例的数量(例如数据集中的人群),n_f表示特征的数量(例如每个人的基本信息)。通常,特征矩阵存储在一个名为X的变量中。

注意

Pandas 是一个为 Python 构建的开源库。它的创建目的是解决与数据操作和分析相关的各种任务。同样,NumPy 是一个开源的 Python 库,用于操作大型多维数组。它还为这些数组提供了大量的数学函数。

n_i(实例的数量)。然而,在某些情况下,需要多个目标,因此矩阵的维度变为[n_i, n_t],其中n_t是需要考虑的目标数量。

与特征矩阵类似,目标矩阵通常创建为 NumPy 数组或 Pandas 系列。目标数组的值可以是离散的或连续的。通常,目标矩阵存储在名为Y的变量中。

练习 1.01:加载示例数据集并创建特征和目标矩阵

注意

本书中的所有练习和活动将主要在 Jupyter Notebooks 中进行。建议为不同的作业保持单独的 Notebook,除非另有说明。另外,要加载一个示例数据集,将使用seaborn库,它会将数据以表格形式显示。其他加载数据的方法将在后续章节中讲解。

在本练习中,我们将从seaborn库加载tips数据集,并使用它创建特征和目标矩阵。按照以下步骤完成此练习:

注意

本章的练习和活动要求系统中安装 Python 3.7、Seaborn 0.9、Jupyter 6.0、Matplotlib 3.1、NumPy 1.18 和 Pandas 0.25。

  1. 打开一个 Jupyter Notebook 来完成这个练习。在命令提示符或终端中,导航到所需路径并使用以下命令:

    jupyter notebook
    
  2. 使用seaborn库加载tips数据集。为此,你需要导入seaborn库,然后使用load_dataset()函数,如下代码所示:

    import seaborn as sns
    tips = sns.load_dataset('tips')
    

    如我们从前面的代码中看到的,导入库后,会给它起一个别名,以方便在脚本中使用。

    load_dataset()函数从在线库加载数据集。数据集中的数据存储在名为tips的变量中。

  3. 创建一个变量X,用于存储特征。使用drop()函数包含除目标以外的所有特征,在本例中目标列为tip。然后,打印出该变量的前 10 个实例:

    X = tips.drop('tip', axis=1)
    X.head(10)
    axis = 0) or columns (axis = 1).
    

    输出结果应该如下所示:

    图 1.3:显示特征矩阵前 10 个实例的表格

    图 1.3:显示特征矩阵前 10 个实例的表格

  4. 使用X.shape命令打印出新变量的形状:

    X.shape
    

    输出结果如下所示:

    (244, 6)
    

    第一个值表示数据集中的实例数(244),第二个值表示特征数(6)。

  5. 创建一个变量Y,用于存储目标值。无需使用函数来完成此操作。通过索引来提取所需的列。索引允许你访问更大元素的某一部分。在这个例子中,我们想要提取名为tip的列。然后,我们需要打印出该变量的前 10 个值:

    Y = tips['tip']
    Y.head(10)
    

    输出结果应该如下所示:

    图 1.4:显示目标矩阵前 10 个实例的截图

    图 1.4:显示目标矩阵前 10 个实例的截图

  6. 使用Y.shape命令打印出新变量的形状:

    Y.shape
    

    输出结果如下所示:

    (244,) 
    

    形状应该是一维的,长度等于实例的数量(244)。

    注意

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

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

到此,你已经成功创建了数据集的特征矩阵和目标矩阵。

通常,表示数据的首选方式是使用二维表格,其中行代表观察的数量,也称为实例,列代表这些实例的特征,通常称为特征。

对于需要目标标签的数据问题,数据表需要分为特征矩阵和目标矩阵。特征矩阵将包含每个实例的所有特征值,但不包括目标值,因此它是一个二维矩阵。另一方面,目标矩阵将仅包含所有条目的目标特征值,因此它是一个一维矩阵。

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

你想分析泰坦尼克号数据集,查看不同甲板上乘客的生存率,并验证是否可以证明下层甲板的乘客更难幸存。此活动中,我们将尝试加载数据集并通过选择合适的目标特征来创建特征矩阵和目标矩阵,以实现我们的目标。

注意

选择目标特征时,请记住目标应该是我们想要解释数据的结果。例如,如果我们想知道哪些特征在确定植物种类中起作用,那么物种应该是目标值。

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

  1. 使用seaborn库加载titanic数据集。前几行应该是这样的:图 1.5:展示泰坦尼克号数据集前 10 个实例的表格

    图 1.5:展示泰坦尼克号数据集前 10 个实例的表格

  2. 选择你偏好的目标特征以完成本活动的目标。

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

  4. 打印出每个矩阵的形状,应该与以下值匹配:

    Features matrix: (891, 14)
    Target matrix: (891,)
    

    注意

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

数据预处理

数据预处理是开发机器学习解决方案中的关键步骤,因为它有助于确保模型不会在偏差数据上训练。它能够提高模型的性能,而且通常是同一算法在同一数据问题上能为进行良好数据预处理的程序员带来更好效果的原因。

为了让计算机能够有效理解数据,不仅需要以标准化的方式输入数据,还需要确保数据中不包含异常值或噪声数据,甚至没有缺失条目。这一点很重要,因为如果做不到这一点,算法可能会做出与数据不符的假设。这将导致模型训练速度变慢,并且由于对数据的误导性解释,准确性较低。

此外,数据预处理不仅仅止于此。模型的工作方式各不相同,每个模型有不同的假设。这意味着我们需要根据将要使用的模型来预处理数据。例如,有些模型仅接受数值型数据,而其他模型则可以处理名义数据和数值数据。

为了在数据预处理中获得更好的结果,一种良好的做法是以不同的方式转换(预处理)数据,然后在不同的模型中测试这些不同的转换。这样,你就能选择适合特定模型的正确转换方式。值得一提的是,数据预处理有可能帮助解决任何数据问题和任何机器学习算法,考虑到仅仅通过标准化数据集,就能提高训练速度。

杂乱数据

缺少信息或包含异常值或噪音的数据被视为杂乱数据。如果未进行任何预处理来转换数据,可能会导致数据模型构建不佳,因为引入了偏差和信息丢失。这里将解释一些应避免的数据问题。

缺失值

数据集的特征和实例可能都有缺失值。对于某些特征,只有少数实例有值,而对于某些实例,所有特征的值都缺失,这些都被视为缺失数据

图 1.6:缺失值示例

图 1.6:缺失值示例

上图展示了一个实例(实例 8),该实例在所有特征上都没有值,因此它没有任何用处;另外还有一个特征(特征 8),它在 10 个实例中有 7 个缺失值,这意味着该特征无法用于发现实例之间的模式,因为大多数实例没有该特征的值。

通常情况下,缺失超过 5% 到 10% 的值的特征被认为是缺失数据(也称为高缺失率特征),因此需要处理。另一方面,所有特征的值都缺失的实例应被删除,因为它们没有为模型提供任何信息,反而可能引入偏差。

处理具有高缺失率的特征时,建议要么删除该特征,要么用值填充它。替换缺失值的最常见方法如下:

  • 均值填充:用特征的可用值的均值或中位数来替代缺失值

  • 回归插补:用从回归函数中获得的预测值替代缺失值。

    注意

    回归函数是指用于估计因变量与一个或多个自变量之间关系的统计模型。回归函数可以是线性、逻辑回归、 polynomial 等。

尽管均值插补是一种更简单的实现方法,但它可能会引入偏差,因为它会使所有实例趋于一致。另一方面,虽然回归方法将数据与其预测值匹配,但它可能会导致模型过拟合(即创建一个过于适应训练数据、无法处理新数据的模型),因为所有引入的值都遵循一个函数。

最后,当缺失值出现在如性别等文本特征中时,最好的做法是要么删除它们,要么用标记为 未分类 或类似的类别来替代它们。这主要是因为无法对文本应用均值插补或回归插补。

将缺失值标记为新类别(未分类)通常是在删除缺失值会去除数据集中重要部分时采取的做法,因此不适合删除它们。在这种情况下,尽管新标签可能会对模型产生影响,但根据标记缺失值时使用的理由,留空可能是一个更差的选择,因为它会导致模型自行做出假设。

注意

要了解更多关于如何检测和处理缺失值的信息,请访问以下页面:towardsdatascience.com/how-to-handle-missing-data-8646b18db0d4

异常值

异常值是指远离均值的值。这意味着如果一个特征的值符合高斯分布,则异常值位于分布的尾部。

注意

高斯分布(也称为正态分布)具有钟形曲线,因为该分布中大于和小于均值的数值数量相等。

异常值可以是全球性(global)或局部性(local)。前者代表那些与特征值集相距较远的值。例如,当分析某个邻里所有成员的数据时,一个全球异常值可能是一个 180 岁的人(如下面的图示 (A) 所示)。而局部异常值则代表那些与某个特征的子集相距较远的值。以我们之前看到的例子为例,一个局部异常值可能是一个 70 岁的大学生(B),这通常会与该邻里中的其他大学生不同:

图 1.7:展示数据集中全球性和局部异常值的图像

图 1.7:展示数据集中全球性和局部异常值的图像

考虑到上述两个例子,异常值并不会评估值是否可能。虽然一个 180 岁的人不现实,但一个 70 岁的大学生是有可能的,然而这两者都被归类为异常值,因为它们都可能影响模型的表现。

检测异常值的一个直接方法是通过可视化数据来判断它是否符合高斯分布,如果符合,则将落在距离均值三到六个标准差以外的值归类为异常值。然而,并没有一个确定的规则来判断一个异常值,选择标准差数量的决定是主观的,并且会根据不同问题有所变化。

例如,如果通过设置三个标准差为参数来排除值,数据集减少了 40%,那么将标准差的数量调整为四个可能更为合适。

另一方面,在处理文本特征时,检测异常值变得更加棘手,因为没有标准差可以使用。在这种情况下,计算每个类别值的出现次数将有助于判断某个类别是否不可或缺。例如,在服装尺码中,如果 XXS 尺码的比例小于整个数据集的 5%,可能就不需要它。

一旦异常值被检测出来,处理它们的三种常见方法如下:

  • 删除异常值:对于真实的异常值,最好将它们完全删除,以避免扭曲分析。对于那些错误的异常值,如果它们的数量太大,以至于无法进行进一步分析来赋予新值,删除它们也是一个好主意。

  • 定义一个上限:为真实值定义一个上限也可能是有用的。例如,如果你意识到所有超过某个阈值的值都表现得相同,你可以考虑将该值设置为一个上限。

  • 赋予一个新值:如果异常值显然是错误的,你可以使用我们讨论过的处理缺失值的方法之一(均值或回归插补)为其赋予一个新值。

使用上述每种方法的决策取决于异常值的类型和数量。大多数时候,如果异常值的数量占数据集总量的比例较小,那么除了删除它们之外,别无他法。

注意

噪声数据指的是那些不正确或不可能的值。这包括数值型(错误的异常值)和名义型值(例如,一个人的性别被拼写成 "fimale")。像异常值一样,噪声数据可以通过完全删除这些值或赋予它们一个新值来处理。

练习 1.02:处理杂乱数据

在这个练习中,我们将使用 seaborn 的 tips 数据集作为示例,演示如何处理杂乱的数据。按照以下步骤完成这个练习:

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

  2. 导入所有必需的元素。接下来,加载 tips 数据集并将其存储在名为 tips 的变量中。使用以下代码:

    import seaborn as sns
    import numpy as np
    import matplotlib.pyplot as plt
    tips = sns.load_dataset('tips')
    
  3. 接下来,创建一个名为 size 的变量来存储数据集中该特征的值。由于此数据集不包含任何缺失数据,我们将 size 变量的前 16 个值转换为缺失值。打印 age 变量的前 20 个值:

    size = tips["size"]
    size.loc[:15] = np.nan
    size.head(20)
    NaN), which is the representation of a missing value. Finally, it prints the top 20 values of the variable.
    

    输出将如下所示:

    图 1.8:展示了年龄变量前 20 个实例的截图

    图 1.8:展示了年龄变量前 20 个实例的截图

    如您所见,该特征包含我们引入的 NaN 值。

  4. 检查 size 变量的形状:

    size.shape
    

    输出如下所示:

    (244,)
    
  5. 现在,计算 NaN 值的数量以确定如何处理它们。使用 isnull() 函数找到 NaN 值,并使用 sum() 函数将它们全部加起来:

    size.isnull().sum()
    

    输出如下:

    16
    

    NaN 值在变量总大小中的参与率为 6.55%,可以通过将缺失值数量除以特征长度(16/244)来计算。虽然这个比例不高到足以考虑移除整个特征,但确实需要处理缺失值。

  6. 让我们选择均值插补方法来替换缺失值。为此,计算可用值的均值,如下所示:

    mean = size.mean()
    mean = round(mean)
    print(mean)
    

    均值为 3

    注意

    由于 size 特征是一个衡量参加餐厅的人数的指标,因此将均值值 2.55 四舍五入到最接近的整数。

  7. 用均值替换所有缺失值。使用 fillna() 函数,该函数接受每个缺失值并用括号内定义的值替换它。再次打印前 10 个值来检查是否已替换:

    size.fillna(mean, inplace=True)
    size.head(20)
    

    注意

    inplace 设置为 True 时,原始 DataFrame 将被修改。如果未将参数设置为 True,则将保留原始数据集的未修改状态。根据此设置 inplaceTrue,可以替换 NaN 值为均值。

    打印输出如下所示:

    图 1.9:展示了年龄变量前 20 个实例的截图

    图 1.9:展示了年龄变量前 20 个实例的截图

    如前面的截图所示,顶部实例的值已从 NaN 更改为先前计算的均值 3

  8. 使用 Matplotlib 绘制 age 变量的直方图。使用 Matplotlib 的 hist() 函数,如下所示的代码:

    plt.hist(size)
    plt.show()
    

    直方图应如下所示。正如我们所见,其分布类似于高斯分布:

    图 1.10:展示了大小变量的直方图

    图 1.10:展示了大小变量的直方图的截图

  9. 发现数据中的异常值。让我们使用三个标准偏差作为度量标准来计算最小和最大值。

    如前所述,最小值是通过计算所有值的均值并从中减去三倍标准差来确定的。使用以下代码设置最小值,并将其存储在名为min_val的变量中:

    min_val = size.mean() - (3 * size.std())
    print(min_val)
    

    最小值约为-0.1974。根据最小值,左尾的高斯分布没有异常值。这是有道理的,因为分布略微偏向左侧。

    与最小值相反,对于最大值,标准差被加到均值上,以计算更高的阈值。计算最大值,如下所示的代码,并将其存储在名为max_val的变量中:

    max_val = size.mean() + (3 * size.std())
    print(max_val)
    

    最大值约为5.3695,它确定了大小大于 5.36 的实例为异常值。正如你在前面的图中所看到的,这也很有意义,因为这些实例远离高斯分布的钟形曲线。

  10. 统计大于最大值的实例数量,以决定如何处理它们,按照这里给出的指示进行。

    使用索引获取size中高于最大阈值的值,并将其存储在名为outliers的变量中。然后,使用count()计算异常值的数量:

    outliers = size[size > max_val]
    outliers.count()
    

    输出显示有4个异常值。

  11. 打印出异常值并检查是否正确存储了相应的值,如下所示:

    print(outliers)
    

    输出结果如下:

    图 1.11:打印异常值

    图 1.11:打印异常值

    由于异常值的数量较少,并且它们确实是异常值,因此可以将其删除。

    注意

    对于这个练习,我们将删除size变量中的实例,以了解处理异常值的完整过程。然而,稍后在考虑所有特征时,将处理异常值的删除,以便删除整个实例,而不仅仅是删除大小值。

  12. 通过使用索引重新定义size中存储的值,只包含低于最大阈值的值。然后,打印size的形状:

    age = size[size <= max_val]
    age.shape
    

    输出结果如下:

    (240,)
    

    如你所见,size的形状(在步骤 4中计算)已经减少了四个,这正是异常值的数量。

    注意

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

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

你已经成功清理了一个 Pandas 系列。这一过程为稍后的数据集清理提供了指南。

总结一下,我们讨论了数据预处理的重要性,因为如果没有预处理,可能会在模型中引入偏差,进而影响模型的训练时间和性能。数据杂乱的主要表现形式包括缺失值、异常值和噪声。

缺失值,顾名思义,是指那些空缺或为 null 的值。在处理大量缺失值时,重要的是通过删除或分配新值来处理它们。两种分配新值的方法也已讨论过:均值插补和回归插补。

离群值是指与某一特征所有值的均值相差很远的值。检测离群值的一种方法是选择所有超出均值加/减三倍/六倍标准差的值。离群值可能是错误值(不可能的值)或真实值,它们应当采取不同的处理方式。真实的离群值可能会被删除或修正,而错误值则应尽可能用其他值替换。

最后,噪声数据是指那些无论与均值的接近程度如何,都是数据中的错误或拼写错误的值。它们可以是数值型、序数型或名义型的。

注意

请记住,数值数据总是由可以测量的数字表示,名义数据是指不遵循顺序的文本数据,而有序数据是指遵循某种顺序或等级的文本数据。

处理类别特征

类别特征是包含离散值的特征,这些值通常属于有限的类别集合。类别数据可以是名义的或有序的。名义数据指的是不遵循特定顺序的类别,例如音乐类型或城市名称,而有序数据指的是具有顺序感的类别,例如服装尺码或教育水平。

特征工程

尽管许多机器学习算法的改进使得算法能够理解类别数据类型(如文本),但将它们转换为数值值的过程有助于模型的训练,从而提高运行速度和性能。这主要是因为消除了每个类别中可用的语义信息,以及将数据转换为数值值后,可以平等地缩放数据集中的所有特征,正如本章后续部分所解释的那样。

它是如何工作的?特征工程生成一个标签编码,将一个数字值分配给每个类别;然后该值将替换数据集中的类别。例如,一个名为genre的变量,具有poprockcountry类别,可以按如下方式转换:

图 1.12:一张展示特征工程如何工作的图片

图 1.12:一张展示特征工程如何工作的图片

练习 1.03:将特征工程应用于文本数据

在本练习中,我们将把tips数据集的文本特征转换为数值数据。

注意

使用你为之前的练习创建的相同 Jupyter Notebook。

按照以下步骤完成本练习:

  1. 导入 scikit-learn 的LabelEncoder()类,以及pandas库,如下所示:

    from sklearn.preprocessing import LabelEncoder
    import pandas as pd
    
  2. 使用之前导入的类(LabelEncoder)将每个文本特征转换为数值:

    enc = LabelEncoder()
    tips["sex"] = enc.fit_transform(tips['sex'].astype('str'))
    tips["smoker"] = enc.fit_transform(tips['smoker'].astype('str'))
    tips["day"] = enc.fit_transform(tips['day'].astype('str'))
    tips["time"] = enc.fit_transform(tips['time'].astype('str'))
    

    根据前面的代码片段,第一步是通过键入第一行代码实例化LabelEncoder类。第二步,对于每个类别特征,我们使用该类的内置fit_transform()方法,该方法将为每个类别分配一个数值并输出结果。

  3. 输出tips数据集的前几个值:

    tips.head()
    

    输出如下:

    图 1.13:显示 tips 数据集前五个实例的截图

图 1.13:显示 tips 数据集前五个实例的截图

如你所见,类别特征的文本类别已被转换为数值。

注意

若要访问此部分的源代码,请参见packt.live/30GWJgb

你也可以在packt.live/3e2oaVu上在线运行此示例。你必须执行整个 Notebook,才能得到所需的结果。

你已经成功地将文本数据转换为数值。

尽管机器学习的进展使得一些算法处理文本特征变得更加容易,但最好将其转换为数值。这一点尤其重要,因为它消除了处理语义时的复杂性,更不用说它为我们提供了从一个模型切换到另一个模型的灵活性,且没有任何限制。

文本数据的转换是通过特征工程完成的,其中每个文本类别都被分配了一个替代它的数值。此外,尽管可以手动完成此过程,但也有一些强大的内置类和方法可以简化这个过程。一个例子就是使用 scikit-learn 的LabelEncoder类。

数据重缩放

数据重缩放很重要,因为即使数据可以用不同的尺度传递给模型,每个特征的尺度不一致也可能导致算法失去从数据中发现模式的能力,因为它需要做出假设来理解数据,从而使得训练过程变慢并且对模型的性能产生负面影响。

数据重缩放帮助模型更快地运行,且无需为学习数据集中的不变性承担任何负担或责任。此外,在均匀缩放数据上训练的模型会对所有参数分配相同的权重(重要性等级),这使得算法能够泛化到所有特征,而不仅仅是那些值较高的特征,无论它们的含义如何。

一个具有不同尺度的数据集示例是包含不同特征的集合,其中一个特征以千克为单位,另一个测量温度,另一个则统计孩子数量。尽管每个属性的数值都是正确的,但它们的尺度差异很大。例如,千克的数值可以超过 100,而孩子的数量通常不会超过 10。

两种最常用的重缩放数据方法是 数据归一化数据标准化。没有固定的规则来选择数据转换的重缩放方法,因为所有数据集的表现不同。最佳实践是使用两种或三种重缩放方法转换数据,并在每种方法上测试算法,从而选择最适合数据的算法,依据其性能表现。

重缩放方法应该单独使用。在测试不同的重缩放方法时,数据的转换应独立进行。每个转换可以在模型上进行测试,然后选择最适合的一个用于后续步骤。

归一化:机器学习中的数据归一化是指将所有特征的值重缩放,使其落在 0 到 1 之间,并且最大长度为 1。这样做的目的是将不同尺度的属性统一化。

以下公式允许你对特征值进行归一化:

图 1.14:归一化公式

图 1.14:归一化公式

其中,zi 代表第 i 个归一化值,x 代表所有值。

标准化:这是一种重缩放技术,它将数据转换为均值为 0,标准差为 1 的高斯分布。

标准化特征的一种简单方法如下公式所示:

图 1.15:标准化公式

图 1.15:标准化公式

其中,zi 代表第 i 个标准化值,x 代表所有值。

练习 1.04:数据归一化与标准化

本练习涉及数据的归一化和标准化,以 tips 数据集为例。

注意

使用你为前一个练习创建的相同 Jupyter Notebook。

按照以下步骤完成此练习:

  1. 使用包含整个数据集的 tips 变量,采用归一化公式对数据进行归一化,并将结果存储在一个名为 tips_normalized 的新变量中。打印前 10 个值:

    tips_normalized = (tips - tips.min())/(tips.max()-tips.min())
    tips_normalized.head(10)
    

    输出如下:

    图 1.16:显示  变量前 10 个实例的截图

    图 1.16:显示 tips_normalized 变量前 10 个实例的截图

    如前面的截图所示,所有的数值都已转换为介于 0 和 1 之间的等效值。通过对所有特征进行归一化,模型将在相同尺度的特征上进行训练。

  2. 再次使用 tips 变量,使用标准化公式对数据进行标准化,并将其存储在名为 tips_standardized 的变量中。打印出前 10 个值:

    tips_standardized = (tips - tips.mean())/tips.std()
    tips_standardized.head(10)
    

    输出结果如下:

    图 1.17:显示  变量前 10 个实例的截图

图 1.17:显示 tips_standardized 变量前 10 个实例的截图

与归一化相比,标准化中,数值会围绕零正态分布。

注意

要访问此部分的源代码,请参考 packt.live/30FKsbD

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

你已成功地将重缩放方法应用于数据。

总结来说,我们已经涵盖了数据预处理中的最后一步,即数据的重缩放。这个过程是在具有不同尺度特征的数据集上进行的,目的是统一数据的表示方式,以便模型能够更好地理解数据。

如果不对数据进行重缩放,模型将以更慢的速度进行训练,并可能会负面影响模型的性能。

本主题中解释了两种数据重缩放方法:归一化和标准化。一方面,归一化将数据转换为一个长度为 1 的范围(从 0 到 1)。另一方面,标准化将数据转换为具有均值 0 和标准差 1 的高斯分布。

由于没有选择适当重缩放方法的固定规则,推荐的做法是独立地使用两到三种重缩放方法对数据进行转换,然后用每种转换训练模型,评估哪种方法表现最佳。

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

你继续为一家邮轮公司的安全部门工作。由于你在选择理想的目标特征以开展研究方面表现出色,部门决定委托你负责数据集的预处理工作。为此,你需要使用之前学习的所有技术来预处理数据集,并将其准备好用于模型训练。以下步骤将指导你朝着这个方向前进:

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

    注意

    对于这个活动,特征矩阵仅使用了六个特征,因为其他一些特征对于本研究是冗余的。例如,不需要同时保留 sexgender

  2. 检查特征矩阵(X)中所有特征的缺失值和异常值。选择一种方法来处理它们。

  3. 将所有文本特征转换为它们的数值表示。

  4. 对数据进行重新缩放,方法是通过归一化或标准化。

    注意

    该活动的解决方案可以在第 211 页找到。

预期输出:结果可能会有所不同,取决于你的选择。然而,你必须确保最终得到一个没有缺失值、异常值或文本特征的数据集,并且数据已重新缩放。

Scikit-Learn API

scikit-learn API 的目标是提供高效且统一的语法,使机器学习变得更加易于非机器学习专家使用,同时促进和普及其在多个行业中的应用。

它是如何工作的?

尽管有许多合作者,scikit-learn API 是在考虑一组原则的基础上构建并持续更新的,这些原则防止了框架代码的过度繁殖,即不同的代码执行相似的功能。相反,它提倡简单的约定和一致性。因此,scikit-learn API 在所有模型中保持一致,一旦掌握了主要功能,就可以广泛应用。

scikit-learn API 被分为三个互补的接口,它们共享相同的语法和逻辑:估算器、预测器和转换器。估算器接口用于创建模型并将数据拟合到模型中;预测器,顾名思义,用于基于已训练的模型进行预测;最后,转换器用于转换数据。

估算器

这被认为是整个 API 的核心,因为它负责将模型拟合到输入数据中。它通过实例化要使用的模型,然后应用 fit() 方法来触发学习过程,从而根据数据构建模型。

fit() 方法接收两个单独的变量作为训练数据的参数:特征矩阵和目标矩阵(通常分别称为 X_trainY_train)。对于无监督模型,该方法仅接收第一个参数(X_train)。

此方法创建经过训练的模型,用于后续的预测。

一些模型除了训练数据外,还需要其他参数,这些参数也被称为 超参数。这些超参数最初设置为默认值,但可以调整以提高模型的性能,具体内容将在后续章节中讨论。

以下是一个模型训练的示例:

from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(X_train, Y_train)

首先,需要从 scikit-learn 导入要使用的算法类型;例如,用于分类的高斯朴素贝叶斯算法(将在第四章监督学习算法:预测年收入中进一步探讨)。通常最好只导入需要使用的算法,而不是整个库,因为这样可以确保代码运行得更快。

注意

要查找导入不同模型的语法,请参阅 scikit-learn 的文档。访问以下链接,点击你想实现的算法,你将在那里找到相关说明:scikit-learn.org/stable/user_guide.html

第二行代码负责实例化模型并将其存储在一个变量中。最后,将模型拟合到输入数据上。

除此之外,估算器还提供其他辅助任务,如下所示:

  • 特征提取,涉及将输入数据转换为可用于机器学习目的的数值特征。

  • 特征选择,用于选择对模型预测结果有贡献的数据特征。

  • 降维技术,将高维数据转换为低维数据。

预测器

如前所述,预测器使用估算器创建的模型,并利用该模型对未见过的数据进行预测。一般而言,对于监督模型,它将新的数据集(通常称为 X_test)输入模型,以便根据训练模型时学到的参数得到相应的目标或标签。

此外,一些无监督模型也可以从预测器中受益。虽然该方法不会输出特定的目标值,但它可以用于将一个新实例分配到一个聚类中。

根据前面的示例,预测器的实现如下所示:

Y_pred = model.predict(X_test)

我们将 predict() 方法应用于之前训练好的模型,并将新的数据作为参数输入该方法。

除了预测外,预测器还可以实现一些方法,这些方法负责量化预测的置信度(即,代表模型性能水平的数值)。这些性能度量因模型而异,但其主要目的是确定预测与现实之间的差距。通过使用带有相应 Y_testX_test 数据,并将其与使用相同 X_test 进行的预测进行比较,来实现这一目标。

转换器

正如我们之前看到的,数据通常在输入模型之前进行转换。考虑到这一点,API 包含一个 transform() 方法,允许你执行一些预处理技术。

它可以作为转换模型输入数据(X_train)的起点,也可以进一步用于修改将提供给模型进行预测的数据。后者的应用至关重要,因为它确保新数据遵循与训练模型时相同的分布,从而获得准确的结果。

以下是一个转换器的示例,用于对训练数据的值进行归一化处理:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)

StandardScaler 类对其接收到的数据进行标准化。如您所见,在导入并实例化转换器(即 StandardScaler)后,需要对数据进行拟合,以便有效地进行转换:

X_test = scaler.transform(X_test)

转换器的优点在于,一旦它被应用到训练数据集上,它会存储用于转换训练数据的值;这些值可以用于将测试数据集转换为相同的分布,正如前面的代码片段所示。

总之,我们讨论了使用 scikit-learn 的主要优势之一——其 API。这个 API 遵循一致的结构,使得非专家也能轻松应用机器学习算法。

在 scikit-learn 上建模的第一步是实例化模型的类,并通过估算器将其拟合到输入数据,通常是通过调用类的 fit() 方法来完成的。最后,一旦模型训练完成,就可以通过调用类的 predict() 方法,使用预测器来预测新值。

此外,scikit-learn 还提供了一个转换器接口,允许根据需要转换数据。这对于在训练数据上执行预处理方法非常有用,预处理后,测试数据也可以按相同的分布进行转换。

监督学习与无监督学习

机器学习分为两大类:监督学习和无监督学习。

监督学习

监督学习的核心是理解给定特征集与目标值之间的关系,这个目标值也称为标签类别。例如,它可以用于建模一个人的人口统计信息与其偿还贷款能力之间的关系,如下表所示:

图 1.18:一个人的人口统计信息与偿还贷款能力之间的关系

图 1.18:一个人的人口统计信息与偿还贷款能力之间的关系

通过训练来预测这些关系的模型可以应用于预测新数据的标签。正如我们从前面的例子中看到的,建立了这样一个模型的银行可以将贷款申请者的数据输入到模型中,从而确定他们是否可能按时偿还贷款。

这些模型可以进一步分为分类任务和回归任务,具体解释如下。

分类任务用于从具有离散类别标签的数据中构建模型;例如,分类任务可以用于预测一个人是否会偿还贷款。你可以有多个离散类别,比如预测赛马的排名,但这些类别必须是有限的。

大多数分类任务将预测输出为实例属于每个输出标签的概率。分配的标签是具有最高概率的标签,如下图所示:

图 1.19:分类算法的示意图

图 1.19:分类算法的示意图

一些最常见的分类算法如下:

  • 决策树:该算法遵循树状架构,模拟通过一系列决策来做出决策的过程,每次考虑一个变量。

  • 朴素贝叶斯分类器:该算法依赖于一组基于贝叶斯定理的概率方程,假设特征之间是独立的。它能够考虑多个属性。

  • 人工神经网络(ANN):这些模拟生物神经网络的结构和性能,以执行模式识别任务。人工神经网络由相互连接的神经元组成,按照一定的架构布局。它们相互传递信息,直到得到结果。

回归任务则用于具有连续数量标签的数据;例如,回归任务可以用于预测房价。这意味着输出值由一个数量表示,而不是一组可能的输出。输出标签可以是整数或浮动类型:

  • 最常用的回归任务算法是线性回归。它仅包含一个独立特征(x),其与依赖特征(y)之间的关系是线性的。由于其简单性,通常被忽视,尽管它在处理简单数据问题时表现得非常好。

  • 其他更复杂的回归算法包括回归树支持向量回归,以及再次使用的人工神经网络(ANN)

无监督学习

无监督学习是指将模型拟合到数据中,而不与输出标签存在任何关系,这也被称为无标签数据。这意味着该类别的算法试图理解数据并发现其中的模式。例如,无监督学习可以用来了解属于某个社区的人的特征,如下图所示:

图 1.20:无监督算法如何用于了解人群特征的示意图

图 1.20:无监督算法如何用于了解人群特征的示意图

在应用预测器到这些算法时,输出中没有给出目标标签。预测只对某些模型可用,它将新实例放入已创建的数据子组中。无监督学习进一步划分为不同的任务,但最受欢迎的任务是聚类,接下来将讨论这一点。

聚类任务涉及创建数据组(簇),同时遵守这样一个条件:一个组中的实例与其他组中的实例在视觉上有明显区别。任何聚类算法的输出是一个标签,将实例分配到该标签的簇中:

图 1.21:一个表示多种大小簇的图示

图 1.21:一个表示多种大小簇的图示

上面的图示展示了一组簇,每个簇的大小不同,基于属于每个簇的实例数量。考虑到这一点,尽管簇不需要具有相同数量的实例,但可以设置每个簇的最小实例数量,以避免将数据过拟合到非常具体的小簇中。

一些最受欢迎的聚类算法如下:

  • k 均值:该算法通过最小化两个点之间的平方距离之和,将实例分成 n 个方差相等的簇。

  • 均值漂移聚类:通过使用质心来创建簇。每个实例都成为质心的候选者,质心是该簇中所有点的均值。

  • 基于密度的空间聚类(DBSCAN):它通过密度较高的点区域来确定簇,簇之间由密度较低的区域分隔。

总结

机器学习(ML)包括构建能够将数据转化为可以用来做决策的知识的模型,其中一些模型基于复杂的数学概念来理解数据。Scikit-learn 是一个开源的 Python 库,旨在简化将这些模型应用于数据问题的过程,无需过多的复杂数学知识。

本章解释了预处理输入数据的关键步骤,从将特征与目标分开,到处理杂乱数据以及重新缩放数据值。在开始训练模型之前,应该执行所有这些步骤,因为它们有助于提高训练速度以及模型的表现。

接下来,解释了 scikit-learn API 的不同组件:估计器、预测器和转换器。最后,本章讲解了监督学习和无监督学习之间的区别,并介绍了每种学习类型中最受欢迎的算法。

考虑到这一点,在下一章中,我们将重点详细说明如何为实际数据集实现一个无监督算法。

第二章:2. 无监督学习 – 现实生活中的应用

概述

本章介绍机器学习中的聚类概念。它解释了三种最常见的聚类算法,并通过实际数据问题的近似解决方案进行了实际操作。通过本章的学习,您应该能够深入了解如何使用 k-means、均值漂移和 DBSCAN 算法从数据集中创建簇,以及如何衡量这些簇的准确性。

简介

在上一章中,我们学习了如何以表格格式表示数据,创建特征和目标矩阵,预处理数据,并学习了如何选择最适合问题的算法。我们还学习了 scikit-learn API 的工作原理以及为什么易于使用,以及监督学习和无监督学习之间的区别。

本章侧重于无监督学习领域中最重要的任务:聚类。考虑一种情况,您是一家店铺的所有者,希望通过定向社交媒体活动来促销特定产品给某些客户。使用聚类算法,您将能够创建客户的子群体,从而可以根据客户的特点来定位和目标客户。本章的主要目标是解决一个案例研究,您将在其中实施三种不同的无监督学习解决方案。这些不同的应用程序旨在演示 scikit-learn API 的一致性,以及解决机器学习问题所需的步骤。通过本章,您将能够了解使用无监督学习来理解数据以便做出明智决策。

聚类

聚类是一种无监督学习技术,其目标是基于未标记的输入数据中发现的模式得出结论。这种技术主要用于将大数据分成子群体,以便做出明智的决策。

例如,从一个城市的大型餐厅列表中,根据食物类型、客户数量和体验风格,将数据分成子群体(簇)将非常有用,以便为每个簇提供针对其特定需求配置的服务。

聚类算法将数据点划分为n个簇,使得同一簇内的数据点具有相似的特征,而与其他簇的数据点显著不同。

聚类类型

聚类算法可以使用的方法对数据点进行分类。前者将数据点完全指定给某一个簇,而后者则计算每个数据点属于每个簇的概率。例如,对于一个包含顾客过去订单的数据集,这些订单被分成八个子组(簇),硬聚类发生在每个顾客被放入八个簇中的其中一个。而软聚类则为每个顾客分配一个属于八个簇中每个簇的概率。

考虑到簇是基于数据点之间的相似性创建的,聚类算法可以根据用于衡量相似性的规则集进一步划分为几类。以下是四种最常见的规则集的解释:

  • 基于连通性的模型:该模型的相似性方法基于数据空间中的接近度。簇的创建可以通过将所有数据点分配到一个簇中,然后随着数据点之间的距离增加,将数据划分为更小的簇来实现。同样,算法也可以从为每个数据点分配一个独立的簇开始,然后将接近的数据点合并在一起。基于连通性的模型的一个例子是层次聚类。

  • 基于密度的模型:顾名思义,这些模型通过数据空间中的密度来定义簇。这意味着数据点密度较高的区域将形成簇,簇通常会被低密度区域分开。一个例子是 DBSCAN 算法,本章稍后将详细介绍。

  • 基于分布的模型:属于此类别的模型基于所有来自同一簇的数据点遵循相同分布的概率,例如高斯分布。此类模型的一个例子是高斯混合算法,它假设所有数据点来自有限数量的高斯分布的混合体。

  • 基于质心的模型:这些模型基于定义每个簇的质心的算法,该质心通过迭代过程不断更新。数据点会被分配到与其质心的距离最小的簇。此类模型的一个例子是 k 均值算法,本章稍后会讨论。

总之,数据点根据它们之间的相似性以及与其他簇中数据点的差异来分配到不同的簇中。这样的簇划分可以是绝对的,也可以通过确定每个数据点属于每个簇的概率来进行变动。

此外,并没有一套固定的规则来确定数据点之间的相似性,这也是不同聚类算法使用不同规则的原因。一些最常见的规则集包括基于连接性、基于密度、基于分布和基于质心的规则。

聚类的应用

与所有机器学习算法一样,聚类在不同领域有着广泛的应用,以下是其中的一些应用:

  • 搜索引擎结果:聚类可以用于生成包含与用户搜索关键词相近的关键词的搜索引擎结果,并按与搜索结果的相似度进行排序。以谷歌为例,它不仅使用聚类来检索结果,还用聚类来建议新的可能搜索内容。

  • 推荐程序:聚类也可以用于推荐程序,例如将具有相似特征的用户聚集在一起,然后根据每个成员的购买历史进行推荐。以亚马逊为例,它根据你的购买历史以及相似用户的购买记录推荐更多商品。

  • 图像识别:聚类可用于将相似的图像分组。例如,Facebook 使用聚类来帮助识别图片中的人物。

  • 市场细分:聚类还可以用于市场细分,将潜在客户或客户列表分成子群体,以提供定制化的体验或产品。例如,Adobe 利用聚类分析对客户进行细分,从而根据客户的消费意愿进行不同的定位。

上述示例表明,聚类算法可以用来解决不同行业中的不同数据问题,其主要目的是理解大量的历史数据,这些数据在某些情况下可以用来分类新的实例。

探索数据集 – 批发客户数据集

作为学习聚类算法的行为和应用的一部分,本章的以下部分将重点解决一个实际的数据问题,使用的是在 UC Irvine 机器学习仓库中提供的批发客户数据集。

注意

仓库中的数据集可能包含原始数据、部分预处理数据或已处理数据。在使用这些数据集时,请确保阅读可用数据的规格说明,以了解有效建模数据的过程,或者判断该数据集是否适合你的目的。

例如,当前数据集是一个来自更大数据集的提取,引用如下:

该数据集来源于一个更大的数据库,相关文献为:Abreu, N. (2011). 《客户 Recheio 的分析与促销系统的开发》。市场营销硕士,ISCTE-IUL,里斯本。

在接下来的部分,我们将分析数据集的内容,然后将其用于活动 2.01使用数据可视化辅助预处理过程。要从 UC Irvine 机器学习库下载数据集,请执行以下步骤:

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

  2. 在数据集标题下方,找到下载部分并点击Data Folder

  3. 点击Wholesale Customers data.csv文件以触发下载并将文件保存在当前 Jupyter Notebook 的相同路径下。

    注意

    你也可以通过访问这本书的 GitHub 存储库来访问它:packt.live/3c3hfKp

理解数据集

每一步都将以通用方式进行解释,然后会对其在当前案例研究(批发客户数据集)中的应用进行解释:

  1. 首先,理解负责收集和维护数据的人员呈现数据的方式至关重要。

    考虑到案例研究的数据集是从在线资源库获取的,必须理解其呈现的格式。批发客户数据集包含批发分销商客户的历史数据片段。它包含总共 440 个实例(每一行)和八个特征(每一列)。

  2. 接下来,确定研究的目的非常重要,这取决于可用的数据。尽管这可能看起来像是一个冗余的陈述,但许多数据问题变得棘手,因为研究人员对研究目的没有清晰的了解,从而选择了错误的预处理方法、模型和性能指标。

    在批发客户数据集上使用聚类算法的目的是理解每个客户的行为。这将使您能够将具有相似行为的客户分组到一个簇中。客户的行为将由他们在每个产品类别上的支出量、以及购买产品的渠道和地区来定义。

  3. 随后探索所有可用的特征。这主要是出于两个原因:首先,根据研究目的排除被认为具有低相关性的特征或被认为是多余的特征,其次,理解呈现值的方式以确定可能需要的一些预处理技术。

    当前案例研究具有八个特征,每个特征都被认为与研究目的相关。下表对每个特征进行了解释:

    图 2.1:案例研究中特征解释的表格

图 2.1:案例研究中特征解释的表格

在前面的表格中,没有特征需要被忽略,且名义(分类)特征已经由数据集的作者处理。

总结一下,选择数据集或收到数据集时,首先需要了解初步可见的特征,这包括识别可用信息,然后确定项目的目的,最后修改特征,选择那些将用于研究的特征。之后,数据可以进行可视化,以便在预处理之前理解数据。

数据可视化

一旦数据被修改以确保能够用于预期目的,就可以加载数据集并使用数据可视化进一步理解数据。数据可视化并不是开发机器学习项目的必需步骤,尤其是在处理具有数百或数千个特征的数据集时。然而,它已成为机器学习的重要组成部分,主要用于可视化以下内容:

  • 特定的导致问题的特征(例如,包含大量缺失值或异常值的特征)以及如何处理这些问题。

  • 来自模型的结果,例如已创建的聚类或每个标签类别的预测实例数量。

  • 模型的性能,以便查看不同迭代中的行为。

数据可视化在上述任务中的流行,可以通过人脑在图表或图形的呈现下容易处理信息来解释,这使得我们能够对数据有一个总体的理解。它还帮助我们识别需要注意的领域,比如异常值。

使用 pandas 加载数据集

一种便于管理数据集的存储方式是使用 pandas DataFrame。这些工作方式类似于具有标签轴的二维大小可变矩阵。它们便于使用不同的 pandas 函数来修改数据集以进行预处理。

大多数在线存储库中找到的数据集,或公司为数据分析收集的数据集,都存储在逗号分隔值CSV)文件中。CSV 文件是以表格形式显示数据的文本文件。列由逗号(,)分隔,行位于不同的行上:

![图 2.2:CSV 文件的屏幕截图]

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_02_02.jpg)

图 2.2:CSV 文件的屏幕截图

使用 pandas 的read_csv()函数加载存储在 CSV 文件中的数据集并将其放入 DataFrame 中是非常简单的。它接收文件路径作为参数。

注意

当数据集存储在不同格式的文件中时,如 Excel 或 SQL 数据库,分别使用 pandas 的read_xlsx()read_sql()函数。

以下代码展示了如何使用pandas加载数据集:

import pandas as pd
file_path = "datasets/test.csv"
data = pd.read_csv(file_path)
print(type(data))

首先,导入 pandas 库。接下来,定义文件的路径,以便将其输入到read_csv()函数中。最后,打印data变量的类型,以验证已创建一个 Pandas DataFrame。

输出结果如下:

<class 'pandas.core.frame.DataFrame'>

如前面的代码片段所示,名为 data 的变量是 pandas DataFrame 类型。

可视化工具

目前有不同的开源可视化库,其中 seaborn 和 matplotlib 很突出。在上一章中,使用 seaborn 加载并显示数据;然而,从本节开始,我们将选择 matplotlib 作为我们的可视化库。这主要是因为 seaborn 是基于 matplotlib 构建的,目的是引入几种绘图类型并改善显示格式。因此,一旦你学习了 matplotlib,你也可以导入 seaborn 来提高绘图的视觉质量。

注意

有关 seaborn 库的更多信息,请访问以下链接:seaborn.pydata.org/

一般来说,matplotlib 是一个易于使用的 Python 库,用于绘制 2D 高质量图形。对于简单的绘图,库的 pyplot 模块就足够了。

以下表格解释了一些最常用的绘图类型:

图 2.3:列出常用绘图类型的表格(*)

图 2.3:列出常用绘图类型的表格(*)

第三列中的函数在导入 matplotlib 及其 pyplot 模块后可以使用。

注意

访问 matplotlib 的文档,了解您希望使用的绘图类型:matplotlib.org/,以便您可以尝试不同的参数和函数来编辑绘图结果。

练习 2.01:绘制 Circles 数据集中的一个特征的直方图

在本练习中,我们将绘制 circles 数据集中的一个特征的直方图。执行以下步骤以完成此练习:

注意

对本章中的所有练习使用相同的 Jupyter Notebook。circles.csv 文件可以在 packt.live/2xRg3ea 下载。

本章中的所有练习和活动,您需要在系统中安装 Python 3.7、matplotlib、NumPy、Jupyter 和 pandas。

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

  2. 首先,通过输入以下代码来导入你将要使用的所有库:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    

    pandas 库用于将数据集保存为 DataFrame,matplotlib 用于可视化,NumPy 在本章后面的练习中会使用,但由于使用的是相同的 Notebook,这里已经导入了。

  3. 使用 Pandas 的 read_csv 函数加载 circles 数据集。输入以下代码:

    data = pd.read_csv("circles.csv")
    plt.scatter(data.iloc[:,0], data.iloc[:,1])
    plt.show()
    

    创建一个名为 data 的变量来存储 circles 数据集。最后,绘制一个散点图来显示数据空间中的数据点,其中第一个元素是数据集的第一列,第二个元素是数据集的第二列,从而创建一个二维图:

    注意

    Matplotlib 的show()函数用于触发图表的显示,考虑到前面的代码行只是创建了图表。在 Jupyter Notebooks 中,使用show()函数并非必需,但它是一个好习惯,因为在其他编程环境中,必须使用此函数才能显示图表。这也能提高代码的灵活性。另外,在 Jupyter Notebooks 中,使用此函数会使输出更干净。

    图 2.4: 圆形数据集的散点图

    图 2.4: 圆形数据集的散点图

    最终输出是一个包含两个特征和 1,500 个实例的数据集。在这里,点代表一个数据点(一个观察值),其位置由数据集中每个特征的值标记。

  4. 从两个特征中的一个创建直方图。使用切片选择你想要绘制的特征:

    plt.hist(data.iloc[:,0])
    plt.show()
    

    该图将类似于以下图表所示:

    图 2.5: 显示使用第一个特征数据获得的直方图的截图

图 2.5: 显示使用第一个特征数据获得的直方图的截图

注意

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

你也可以在网上运行这个示例,链接:packt.live/2N0L0Rj。你必须执行整个 Notebook,才能获得期望的结果。

你已经成功地使用 matplotlib 创建了散点图和直方图。同样,使用 matplotlib 也可以创建不同类型的图表。

总结来说,数据可视化工具帮助你更好地理解数据集中可用的数据、模型的结果以及模型的性能。这是因为人类大脑更容易接受视觉形式,而不是大量的数据文件。

Matplotlib 已成为最常用的数据可视化库之一。在该库支持的不同类型的图表中,包括直方图、条形图和散点图。

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

你公司市场团队希望了解客户的不同档案,以便能够将营销精力集中在每个档案的个人需求上。为此,团队提供了 440 条先前的销售数据给你们的团队。你的第一个任务是对数据进行预处理。你将使用数据可视化技术展示你的发现,以帮助同事理解你在此过程中做出的决策。你应该使用 pandas 加载 CSV 数据集,并使用数据可视化工具帮助预处理过程。以下步骤将指导你如何完成这一过程:

  1. 导入所有必要的元素以加载数据集并进行预处理。

  2. 使用 Pandas 的read_csv()函数加载先前下载的数据集,假设数据集存储在 CSV 文件中。将数据集存储在一个名为data的 pandas DataFrame 中。

  3. 检查你的 DataFrame 中是否存在缺失值。如果存在,处理缺失值,并通过数据可视化支持你的决策。

    注意

    使用data.isnull().sum()来一次性检查整个数据集中的缺失值,就像我们在上一章中学到的那样。

  4. 检查你的 DataFrame 中是否存在离群值。如果存在,处理离群值,并通过数据可视化支持你的决策。

    注意

    将所有偏离均值三个标准差的值标记为离群值。

  5. 使用归一化或标准化公式重新缩放数据。

    注意

    标准化通常在聚类目的上效果更好。注意,你可以在第 216 页找到这个活动的解决方案。

    预期输出:检查 DataFrame 时,你应该发现数据集中没有缺失值,并且有六个特征包含离群值。

k-means 算法

k-means 算法用于建模没有标签类别的数据。它涉及将数据分成K个子组。数据点分类到每个组是基于相似性,如前所述(参见聚类类型部分),对于该算法,相似性通过数据点到簇的中心(质心)的距离来衡量。算法的最终输出是每个数据点与其所属簇的质心相关联,这可以用于为同一簇中的新数据进行标记。

每个簇的质心代表一组特征,可以用来定义属于该簇的数据点的性质。

理解算法

k-means 算法通过一个迭代过程工作,涉及以下步骤:

  1. 根据用户定义的簇数量,质心可以通过设置初始估计值或从数据点中随机选择来生成。这个步骤称为初始化

  2. 所有数据点通过测量它们到质心的距离分配给数据空间中最近的簇,这被称为分配步骤。目标是最小化平方欧氏距离,公式如下:

    min dist(c,x)2
    

    这里,c表示质心,x表示数据点,dist()是欧氏距离。

  3. 质心通过计算属于同一簇的所有数据点的均值重新计算。这个步骤称为更新步骤

步骤 2步骤 3在迭代过程中重复执行,直到满足某个标准。这个标准可以是:

  • 定义的迭代次数。

  • 数据点不会在簇之间变化。

  • 最小化欧氏距离。

算法设置为始终得出一个结果,尽管这个结果可能会收敛到局部或全局最优解。

k-means 算法接收多个参数作为输入以运行模型。最重要的参数是初始化方法(init)和聚类数目(K)。

注意

若要查看 scikit-learn 库中 k-means 算法的其他参数,请访问以下链接:scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html

初始化方法

算法的一个重要输入是用于生成初始质心的初始化方法。scikit-learn 库允许的初始化方法如下所述:

  • k-means++:这是默认选项。质心是从数据点集中随机选择的,考虑到质心必须彼此远离。为此,该方法为那些距离其他质心较远的数据点分配更高的作为质心的概率。

  • random:此方法从数据点中随机选择 K 个观察值作为初始质心。

选择聚类数目

正如我们之前讨论的,数据被划分成的聚类数目是由用户设置的,因此选择合适的聚类数目非常重要。

用于衡量 k-means 算法性能的一个指标是数据点与其所属聚类质心之间的平均距离。然而,这个指标可能会适得其反,因为聚类数目越多,数据点与其质心之间的距离越小,这可能导致聚类数(K)与数据点的数量相同,从而破坏聚类算法的目的。

为避免这种情况,可以绘制数据点与聚类质心之间的平均距离与聚类数的关系图。适当的聚类数对应于图中的断点,即减少速率发生剧烈变化的地方。在下图中,虚线圆圈表示理想的聚类数:

图 2.6:展示如何估计断点的图表

图 2.6:展示如何估计断点的图表

练习 2.02:导入并在数据集上训练 k-means 算法

接下来的练习将使用与上一练习相同的数据集。考虑到这一点,请使用你在上一练习中使用的同一个 Jupyter Notebook。执行以下步骤以完成此练习:

  1. 打开你用于上一练习的 Jupyter Notebook。在这里,你应该已经导入了所有必需的库,并将数据集存储在名为 data 的变量中。

  2. 如下所示,从 scikit-learn 导入 k-means 算法:

    from sklearn.cluster import KMeans
    
  3. 要选择K的值(即理想的簇数),请计算数据点到其簇质心的平均距离,并与簇的数量进行对比。此练习中使用 20 作为最大簇数。以下是此代码片段:

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

    注意

    random_state参数用于确保结果的可重复性,确保算法的随机初始化保持一致。

    首先,创建一个变量用来存储值,并命名为ideal_k。接下来,执行一个for循环,从 1 个簇开始,直到达到所需的数量(考虑到簇的最大数量不得超过实例的总数)。

    在前面的示例中,有一个限制,最多只能创建 20 个簇。由于这个限制,for循环从 1 到 20 个簇。

    注意

    记住,range()是一个上界不包含的函数,这意味着范围会到达上界以下的一个值。当上界为 21 时,范围会到达 20。

    for循环中,实例化算法并设定要创建的簇的数量,然后将数据拟合到模型中。接下来,将数据对(簇的数量,平均距离到质心)追加到名为ideal_k的列表中。

    到质心的平均距离无需计算,因为模型会在inertia_属性下输出它,可以通过[model_name].inertia_调用。

  4. ideal_k列表转换为 NumPy 数组,以便绘制图形。使用以下代码片段:

    ideal_k = np.array(ideal_k)
    
  5. 绘制你在前面步骤中计算的关系,找到理想的K值,以便输入到最终模型中:

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

    输出如下:

    图 2.7:显示绘图函数输出的截图

    图 2.7:显示绘图函数输出的截图

    在前面的图表中,x 轴表示簇的数量,而y 轴表示每个簇中数据点到其质心的平均距离。

    绘图的拐点大约在5

  6. 使用K=5训练模型。使用以下代码:

    est_kmeans = KMeans(n_clusters=5, random_state=0)
    est_kmeans.fit(data)
    pred_kmeans = est_kmeans.predict(data)
    

    第一行使用5作为簇的数量实例化模型。然后,数据被拟合到模型中。最后,模型用来为每个数据点分配一个簇。

  7. 绘制数据点聚类结果的图表:

    plt.scatter(data.iloc[:,0], data.iloc[:,1], c=pred_kmeans)
    plt.show()
    

    输出如下:

    图 2.8:显示绘图函数输出的截图

图 2.8:显示绘图函数输出的截图

由于数据集仅包含两个特征,因此每个特征作为输入传递给散点图函数,这意味着每个特征由一个坐标轴表示。此外,从聚类过程中获得的标签用作显示数据点的颜色。因此,每个数据点根据两个特征的值定位于数据空间中,颜色代表形成的聚类。

注意

对于具有两个以上特征的数据集,聚类的可视化表示不像前述截图中那样直观。这主要是因为每个数据点(观察值)在数据空间中的位置是基于所有特征的集合,而在视觉上只能显示最多三个特征。

您已经成功导入并训练了 k-means 算法。

注意

要访问本练习的源代码,请参考packt.live/30GXWE1

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

总之,k-means 算法旨在将数据分为K个聚类,K是由用户设置的参数。数据点根据其与聚类中心的接近程度被分组,聚类中心通过迭代过程计算得出。

初始中心点根据已定义的初始化方法设置。然后,所有数据点都会被分配到离它们在数据空间中的位置更近的聚类中心,使用欧氏距离作为度量。一旦数据点被分配到聚类中,每个聚类的中心点会重新计算为所有数据点的均值。这个过程会重复多次,直到满足停止标准。

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

在进行此活动之前,请确保您已完成活动 2.01使用数据可视化辅助预处理过程

在继续分析您公司过往订单的过程中,您现在负责将 k-means 算法应用于数据集。使用之前加载的批发客户数据集,对数据应用 k-means 算法并将数据分类为聚类。执行以下步骤完成此活动:

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

  2. 计算数据点到其聚类中心的平均距离,并根据聚类数量来选择合适的聚类数以训练模型。

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

    注意

    你可以使用 Matplotlib 的subplots()函数同时绘制两个散点图。要了解更多关于此函数的信息,请访问 Matplotlib 的文档,网址如下:matplotlib.org/api/_as_gen/matplotlib.pyplot.subplots.html

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

    聚类的可视化将根据聚类的数量(k)和需要绘制的特征而有所不同。

均值漂移算法

均值漂移算法通过根据数据空间中数据点的密度为每个数据点分配一个簇,密度也称为分布函数中的模式。与 k-means 算法不同,均值漂移算法不需要你指定簇的数量作为参数。

该算法通过将数据点建模为分布函数来工作,其中高密度区域(数据点密集的区域)代表高峰值。然后,基本思路是将每个数据点移动到最近的峰值,从而形成一个簇。

理解算法

均值漂移算法的第一步是将数据点表示为一个密度分布。为此,算法基于核密度估计KDE)的方法进行构建,KDE 是一种用于估算数据集分布的方法:

图 2.9:描述核密度估计(KDE)背后思想的图像

图 2.9:描述核密度估计(KDE)背后思想的图像

在前面的图中,形状底部的点代表用户输入的数据点,而锥形线条代表数据点的估算分布。峰值(高密度区域)将成为簇。为每个簇分配数据点的过程如下:

  1. 在每个数据点周围绘制一个指定大小(带宽)的窗口。

  2. 计算窗口内数据点的均值。

  3. 窗口的中心移动到均值位置。

步骤 2步骤 3 会重复进行,直到数据点达到一个峰值,确定它所属的簇。

带宽值应与数据集中的数据点分布保持一致。例如,对于一个规范化在 0 到 1 之间的数据集,带宽值应该在该范围内,而对于一个所有值都在 1000 到 2000 之间的数据集,带宽值最好设置在 100 到 500 之间。

在下图中,估算的分布通过线条表示,而数据点则是点。在每个框中,数据点都会移动到最近的峰值。所有属于某个峰值的数据点都属于同一个簇:

图 2.10:展示均值漂移算法工作原理的一系列图像

图 2.10:展示均值漂移算法工作原理的一系列图像

数据点到达峰值所需的移动次数取决于其带宽(窗口大小)以及与峰值的距离。

注意

要探索 scikit-learn 中均值漂移算法的所有参数,请访问 scikit-learn.org/stable/modules/generated/sklearn.cluster.MeanShift.html

练习 2.03:在数据集上导入并训练均值漂移算法

以下练习将使用我们在练习 2.01,绘制 Circles 数据集一个特征的直方图中加载的相同数据集。鉴于此,请使用你用来开发前面练习的相同 Jupyter Notebook。执行以下步骤来完成此练习:

  1. 打开你在上一个练习中使用的 Jupyter Notebook。

  2. 按如下方式从 scikit-learn 导入 k-means 算法类:

    from sklearn.cluster import MeanShift
    
  3. 使用带宽为 0.5 训练模型:

    est_meanshift = MeanShift(0.5)
    est_meanshift.fit(data)
    pred_meanshift = est_meanshift.predict(data)
    

    首先,模型使用 0.5 的带宽进行实例化。接下来,将模型拟合到数据。最后,使用该模型为每个数据点分配一个聚类。

    考虑到数据集包含的值范围从 −1 到 1,带宽值不应超过 1。0.5 这个值是在尝试其他值(如 0.1 和 0.9)后选择的。

    注意

    请考虑带宽是算法的一个参数,并且作为参数,它可以进行微调,以实现最佳性能。这个微调过程将在第三章,有监督学习——关键步骤中讲解。

  4. 绘制将数据点聚类的结果:

    plt.scatter(data.iloc[:,0], data.iloc[:,1], c=pred_meanshift)
    plt.show()
    

    输出如下:

    图 2.11:使用前面代码获得的图

图 2.11:使用前面代码获得的图

再次提醒,由于数据集仅包含两个特征,这两个特征都作为输入传递给散点图函数,并成为坐标轴的值。此外,从聚类过程中获得的标签被用作显示数据点的颜色。

创建的聚类总数是四个。

注意

要访问此练习的源代码,请参考 packt.live/37vBOOk

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

你已经成功地导入并训练了均值漂移算法。

总结来说,均值漂移算法首先绘制出表示数据点集的分布函数。这个过程包括在高密度区域创建峰值,而在低密度区域保持平坦。

接下来,算法继续通过缓慢且迭代地移动每个数据点,直到它达到一个峰值,进而将其归为一个聚类。

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

在此活动中,你将应用均值迁移算法来处理数据集,以查看哪种算法更适合数据。因此,使用之前加载的批发消费者数据集,将均值迁移算法应用于数据并将数据分类为聚类。按照以下步骤完成此活动:

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

    注意

    考虑到你使用的是相同的 Jupyter Notebook,请小心不要覆盖任何先前的变量。

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

    聚类的可视化将根据带宽和选择绘制的特征而有所不同。

    注意

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

DBSCAN 算法

基于密度的空间聚类与噪声DBSCAN)算法将彼此接近的点(具有许多邻居的点)分为一组,并将那些距离较远且没有接近邻居的点标记为离群点。

根据这一点,正如其名称所示,算法根据数据空间中所有数据点的密度来对数据点进行分类。

理解算法

DBSCAN 算法需要两个主要参数:epsilon 和最小观察数。

eps 是定义算法在其内搜索邻居的半径的最大距离。min_samples 参数是可选的,因为在 scikit-learn 中它的默认值为 5

图 2.12:DBSCAN 算法如何将数据分类为聚类的示意图

图 2.12:DBSCAN 算法如何将数据分类为聚类的示意图

在上图中,左侧的点被分配到聚类 A,而右上方的点被分配到聚类 B。此外,右下方的点(C)被认为是离群点,以及数据空间中任何其他不符合属于高密度区域所需参数的点(即未满足最小样本数要求,在这个例子中设定为 5)。

注意

类似于带宽参数,epsilon 值应与数据集中数据点的分布一致,因为它表示每个数据点周围的半径。

根据这一点,每个数据点可以按以下方式分类:

练习 2.04:在数据集上导入并训练 DBSCAN 算法

本练习讲解如何在数据集上导入并训练 DBSCAN 算法。我们将使用前面练习中的圆形数据集。请执行以下步骤以完成此练习:

  1. 打开你用于前一个练习的 Jupyter Notebook。

  2. 如下所示,从 scikit-learn 导入 DBSCAN 算法类:

    from sklearn.cluster import DBSCAN
    
  3. 使用0.1的 epsilon 训练模型:

    est_dbscan = DBSCAN(eps=0.1)
    pred_dbscan = est_dbscan.fit_predict(data)
    

    首先,模型通过eps设置为0.1进行实例化。然后,我们使用fit_predict()函数将模型拟合到数据并为每个数据点分配一个聚类。这个包含fitpredict方法的封装函数被使用,因为 scikit-learn 中的 DBSCAN 算法并没有单独的predict()方法。

    同样,0.1这个值是在尝试了所有其他可能的值后选择的。

  4. 绘制聚类过程的结果:

    plt.scatter(data.iloc[:,0], data.iloc[:,1], c=pred_dbscan)
    plt.show()
    

    输出结果如下:

    图 2.13:通过前面的代码获得的图表

图 2.13:通过前面的代码获得的图表

与之前一样,两个特征被作为输入传递给 scatter 函数。此外,聚类过程中获得的标签被用作显示数据点颜色的依据。

创建的聚类总数为两个。

如你所见,每个算法创建的聚类总数不同。这是因为,如前所述,每个算法对相似性的定义不同,因此每个算法对数据的解释也不同。

因此,测试不同的算法来比较结果,并定义哪个算法对数据的泛化能力更强是至关重要的。接下来的内容将探讨一些我们可以用来评估性能的方法,以帮助选择算法。

注意

要访问这个练习的源代码,请参考packt.live/2Bcanxa

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

你已经成功导入并训练了 DBSCAN 算法。

总结来说,DBSCAN 算法通过数据空间中数据点的密度来进行聚类分类。这意味着聚类是由邻居较多的数据点组成的。通过以下方式实现:核心点是指在设定半径范围内包含最小数量邻居的数据点,边界点是指位于核心点半径范围内,但在自己的半径范围内没有最小数量邻居的数据点,噪声点则是指不符合任何规格的数据点。

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

你还将对数据集应用 DBSCAN 算法。这基本上是因为在解决数据问题时,测试不同算法是一种良好的实践,以便选择最适合数据的算法,因为没有一种模型能在所有数据问题上表现良好。使用之前加载的批发消费者数据集,对数据应用 DBSCAN 算法并将数据分类到不同的聚类中。执行以下步骤:

  1. 打开你用于之前活动的 Jupyter Notebook。

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

    注意

    本活动的解答可以在第 225 页找到。

    聚类的可视化将根据选择的 epsilon 值和绘制的特征而有所不同。

评估聚类的表现

在应用聚类算法之后,必须评估算法的表现如何。特别是在难以直观评估聚类时,这一点尤为重要;例如,当数据有多个特征时。

通常,对于有监督的算法,通过将每个实例的预测值与其真实值(类别)进行比较,就很容易评估其性能。另一方面,处理无监督模型(如聚类算法)时,需要采用其他策略。

在聚类算法的具体情况下,可以通过衡量属于同一聚类的数据点的相似度来评估性能。

Scikit-Learn 中的可用度量

Scikit-learn 允许用户使用三种不同的得分来评估无监督聚类算法的表现。这些得分背后的主要思想是衡量聚类边界的清晰度,而不是衡量聚类内的分散度。因此,值得提到的是,这些得分并不考虑每个聚类的大小。

以下是用于衡量无监督聚类任务的两种最常用评分方法的解释:

  • 轮廓系数(Silhouette Coefficient Score)计算每个点与同一聚类中所有其他点的平均距离(a),以及每个点与其最近聚类中所有其他点的平均距离(b)。它根据以下公式将二者联系起来:

    s = (b - a) / max(a,b)
    

    得分结果的值介于-1 和 1 之间。值越低,算法的表现越差。接近 0 的值意味着聚类之间可能存在重叠。还需要澄清的是,这个得分在使用基于密度的算法(如 DBSCAN)时效果不太好。

  • Calinski-Harabasz 指数是为了衡量每个聚类的方差与所有聚类的方差之间的关系而创建的。更具体地说,每个聚类的方差是每个点相对于该聚类中心的均方误差。另一方面,所有聚类的方差是指聚类之间的总体方差。

    Calinski–Harabasz 指数值越高,聚类的定义和分离度越好。没有可接受的截止值,因此,使用此指标的算法性能通过比较来评估,具有最高值的算法表现最好。与轮廓系数一样,该得分在基于密度的算法(如 DBSCAN)上表现不佳。

不幸的是,scikit-learn 库没有其他有效测量基于密度的聚类算法性能的方法,尽管这里提到的方法在某些情况下可以用来衡量这些算法的性能,但当它们不起作用时,除了通过人工评估外,没有其他办法来衡量此类算法的性能。

然而,值得一提的是,scikit-learn 中还有其他性能度量方法,适用于已知真实标签的情况,称为监督聚类;例如,当对已经选择专业或领域的新闻学学生进行聚类时,如果我们使用他们的个人信息以及一些学生记录来将他们分类为表示专业选择的聚类,就可以将预测分类与实际分类进行比较。

其中一些度量标准如下:

  • metrics 模块,它接收真实聚类列表和预测聚类列表作为输入,格式如下:

    from sklearn.metrics import homogeneity_score
    score = homogeneity_score(true_labels, predicted_labels)
    
  • 完整性得分:与同质性得分相对,聚类任务满足完整性条件时,如果所有属于给定类别标签的数据点都属于同一聚类,则认为满足完整性要求。同样,输出的度量值在 0 到 1 之间,1 表示完美的完整性。该得分也是 scikit-learn 库中的度量标准之一,它接收真实标签和预测标签作为输入,格式如下:

    from sklearn.metrics import completeness_score
    score = completeness_score(true_labels, predicted_labels)
    

    注意

    要探索评估监督聚类任务性能的其他度量标准,请访问以下网址,在聚类部分查找:https://scikit-learn.org/stable/modules/classes.html#module-sklearn.metrics。

练习 2.05:评估轮廓系数得分和 Calinski–Harabasz 指数

在本次练习中,我们将学习如何计算前一节中讨论的、在 scikit-learn 中可用的两个得分。请按照以下步骤完成此练习:

  1. 从 scikit-learn 库中导入轮廓系数得分和 Calinski-Harabasz 指数:

    from sklearn.metrics import silhouette_score
    from sklearn.metrics import calinski_harabasz_score
    
  2. 计算我们在所有之前练习中建模的每个算法的轮廓系数得分。使用欧几里得距离作为度量标准,来衡量点与点之间的距离。

    silhouette_score() 函数的输入参数包括数据、模型的预测值(分配给每个数据点的聚类)以及距离度量方法:

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

    前三行代码通过输入数据、预测结果和距离度量,调用silhouette_score()函数计算每个模型(k-means、mean-shift 和 DBSCAN 算法)的评分。最后一行代码打印出每个模型的评分。

    k-means、mean-shift 和 DBSCAN 算法的评分分别为0.3590.37050.1139

    你可以观察到,k-means 和 mean-shift 算法的评分相似,而 DBSCAN 的评分则接近零。这表明前两种算法的性能要好得多,因此,DBSCAN 算法不应被考虑用来解决数据问题。

    然而,重要的是要记住,这种评分在评估 DBSCAN 算法时表现不佳。这主要是因为当一个簇围绕另一个簇时,评分可能会将其解释为重叠,而实际上这些簇是非常清晰定义的,就像当前数据集的情况一样。

  3. 计算我们在本章前面练习中所建模的每种算法的 Calinski-Harabasz 指数。calinski_harabasz_score()函数的输入参数是数据和模型的预测值(分配给每个数据点的簇):

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

    同样,前三行代码使用calinski_harabasz_score()函数对三个模型进行计算,输入数据和预测结果,最后一行打印出结果。

    k-means、mean-shift 和 DBSCAN 算法的值分别约为1379.71305.140.0017。再次强调,这些结果与我们使用轮廓系数评分时得到的结果相似,其中 k-means 和 mean-shift 算法表现良好,而 DBSCAN 算法则不行。

    此外,值得一提的是,每种方法的尺度(轮廓系数评分和 Calinski-Harabasz 指数)差异显著,因此它们不容易进行比较。

    注意

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

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

你已经成功地测量了三种不同聚类算法的性能。

总结来说,本主题中呈现的评分是一种评估聚类算法性能的方式。然而,重要的是要考虑,这些评分的结果并非决定性的,因为它们的性能因算法而异。

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

你可能会遇到一种情况,即无法图形化评估算法的性能,因此你无法确定算法的表现。在这种情况下,你需要使用数值指标来衡量算法的性能,并进行比较。对于之前训练的模型,计算轮廓系数分数和卡林斯基-哈拉巴兹指数来衡量算法的表现。以下步骤提供了有关如何进行此操作的提示:

  1. 打开你用于上一活动的 Jupyter Notebook。

  2. 计算你之前训练的所有模型的轮廓系数分数和卡林斯基-哈拉巴兹指数。

    结果可能会因你在之前活动开发过程中做出的选择以及如何初始化每个算法中的某些参数而有所不同。然而,以下结果可以预期:将数据集分为六个簇的 k-means 算法,带有带宽为 0.4 的均值漂移算法,以及 epsilon 值为 0.8 的 DBSCAN 算法:

    Silhouette Coefficient
    K-means = 0.3515
    mean-shift = 0.0933
    DBSCAN = 0.1685
    Calinski-Harabasz Index
    K-means = 145.73
    mean-shift = 112.90
    DBSCAN = 42.45
    

    注意

    这个活动的解答可以在第 226 页找到。

摘要

输入数据与标签输出无关的数据问题通过使用无监督学习模型来处理。此类数据问题的主要目标是通过寻找模式来理解数据,这些模式在某些情况下可以推广到新的实例。

在此背景下,本章介绍了聚类算法,这些算法通过将相似的数据点聚集成簇,同时将差异显著的数据点分开。

对数据集应用了三种不同的聚类算法,并比较了它们的表现,以便我们可以选择最适合数据的算法。我们还讨论了两种性能评估指标——轮廓系数(Silhouette Coefficient)和卡林斯基-哈拉巴兹指数(Calinski-Harabasz index),考虑到无法在图表中表示所有特征,因此无法通过图形化方式评估算法的表现。然而,重要的是要理解,指标评估的结果并非绝对的,因为某些指标(默认情况下)对某些算法比对其他算法表现得更好。

在下一章中,我们将了解使用监督学习算法解决数据问题的步骤,并学习如何进行误差分析。

第三章:3. 监督学习 – 关键步骤

概述

本章将介绍解决监督学习数据问题的关键概念。从数据集拆分开始,如何有效地创建不偏倚的模型,使其在未见数据上表现良好,你将学会如何衡量模型的性能,以便进行分析并采取必要的措施来改善模型。到本章结束时,你将牢固掌握如何拆分数据集、衡量模型性能和执行错误分析。

介绍

在前一章中,我们学习了如何使用无监督学习算法解决数据问题,并将所学的概念应用于真实数据集。我们还学习了如何比较不同算法的性能,并研究了两种性能评估指标。

本章将探讨解决监督学习问题的主要步骤。首先,本章将解释如何将数据拆分为训练集、验证集和测试集,以便对模型进行训练、验证和测试。接着,将解释最常见的评估指标。需要特别强调的是,在所有可用的评估指标中,应该仅选择一个作为研究的评估指标,并且其选择应基于研究的目的。最后,我们将学习如何进行错误分析,旨在了解采取何种措施来提高模型的结果。

前述概念适用于分类任务和回归任务,前者指的是输出对应有限数量标签的问题,而后者处理连续输出的数值。例如,一个用来判断某人是否会参加会议的模型属于分类任务组。另一方面,一个预测产品价格的模型则是在解决回归任务。

监督学习任务

与无监督学习算法不同,监督学习算法的特点是能够找到一组特征与目标值之间的关系(无论目标值是离散的还是连续的)。监督学习可以解决两种类型的任务:

  • 分类:这些任务的目标是逼近一个函数,将一组特征映射到一个离散的结果集。这些结果通常称为类标签或类别。数据集中的每个观察值都应与一个类标签相关联,才能训练出能够为未来数据预测此类结果的模型。

    一个分类任务的例子是使用人口统计数据来判断某人的婚姻状况。

  • 回归:虽然在回归任务中也会创建一个函数来映射某些输入和某些目标之间的关系,但在回归任务中,结果是连续的。这意味着结果是一个实数值,可以是整数或浮动值。

    回归任务的一个例子是使用产品的不同特征来预测其价格。

尽管许多算法可以被调整以解决这两项任务,但重要的是要强调,还是有些算法不能解决这两项任务,这就是为什么了解我们想要执行的任务是非常重要的,以便根据任务选择相应的算法。

接下来,我们将探讨一些对于执行任何监督学习任务至关重要的主题。

模型验证与测试

随着现在网上有了大量信息,几乎任何人都可以轻松开始一个机器学习项目。然而,当有许多选项可供选择时,为你的数据选择合适的算法是一项挑战。正因如此,选择一个算法而非另一个算法的决策是通过反复试验实现的,其中会测试不同的替代方案。

此外,做出一个优秀模型的决策过程不仅包括选择算法,还包括调整其超参数。为此,传统的方法是将数据划分为三部分(训练集、验证集和测试集),接下来会进一步解释。

数据划分

数据划分是一个过程,涉及将数据集划分为三个子集,以便每个子集可用于不同的目的。通过这种方式,模型的开发不受引入偏差的影响。以下是每个子集的解释:

  • 训练集:顾名思义,这是用于训练模型的数据集部分。它由输入数据(观察值)与结果(标签类别)配对组成。这个集合可以用来训练任意数量的模型,使用不同的算法。然而,性能评估不会在此数据集上进行,因为由于该数据集用于训练模型,因此评估会有偏差。

  • 验证集:也称为开发集,这个集合用于在微调超参数时对每个模型进行公正的评估。性能评估通常在这个数据集上进行,以测试不同的超参数配置。

    尽管模型不会从这些数据中学习(它从训练集数据中学习),但由于它参与了决定超参数变化的过程,因此间接地受到了这个数据集的影响。

    在基于模型在验证集上的表现运行不同的超参数配置后,将为每个算法选择一个最优模型。

  • 测试集:用于在模型训练和验证后对未见数据进行最终评估。这有助于衡量模型在现实数据中的表现,为未来的预测提供参考。

    测试集也用于比较不同的模型。考虑到训练集用于训练不同的模型,而验证集用于微调每个模型的超参数并选择最佳配置,测试集的目的是对最终模型进行无偏比较。

下图展示了选择理想模型并使用前述数据集的过程:

图 3.1:数据集划分目的

图 3.1:数据集划分目的

前面图示中显示的AD部分描述如下:

  • Section A指的是使用训练集中包含的数据训练所需算法的过程。

  • Section B表示每个模型超参数的微调过程。最佳超参数配置的选择基于模型在验证集上的表现。

  • Section C展示了通过比较每个算法在测试集上的表现,选择最终模型的过程。

  • 最后,Section D表示选定的模型,它将应用于实际数据进行预测。

最初,机器学习问题通过将数据划分为两部分来解决:训练集和测试集。这种方法与三部分数据集的方法相似,但测试集既用于微调超参数,也用于确定算法的最终性能。

尽管这种方法也能奏效,但使用这种方法创建的模型并不总是在未见过的实际数据上表现同样优秀。这主要是因为,如前所述,使用这些数据集来微调超参数间接地将偏差引入了模型中。

考虑到这一点,有一种方法可以在将数据集划分为两部分时实现更少偏差的模型,这就是所谓的交叉验证划分。我们将在本章的交叉验证部分深入探讨这一点。

划分比例

既然各种数据集的目的已经明确,接下来需要澄清数据划分的比例。虽然没有一种精确的科学方法来计算划分比例,但在进行划分时需要考虑以下几个因素:

  • 数据集的大小:以前,由于数据不容易获取,数据集通常包含 100 到 100,000 个实例,公认的划分比例通常是 60/20/20%,分别用于训练集、验证集和测试集。

    随着软件和硬件的不断改进,研究人员能够构建包含超过百万个实例的数据集。能够收集如此庞大的数据量使得分割比例可以达到 98/1/1%。这主要是因为数据集越大,能够用于训练模型的数据就越多,而不会影响留给验证集和测试集的数据量。

  • 算法:需要考虑的是,一些算法可能需要更多的数据来训练模型,例如神经网络。在这种情况下,像前述方法一样,应该始终选择更大的训练集。

    另一方面,一些算法不要求验证集和测试集必须平分。例如,一个具有较少超参数的模型可以容易地进行调整,这使得验证集可以小于测试集。然而,如果一个模型有许多超参数,则需要更大的验证集。

然而,尽管前述措施可以作为划分数据集的指南,始终需要考虑数据集的分布以及研究的目的。例如,若一个模型将用于预测与训练模型时使用的数据分布不同的结果,尽管实际数据有限,至少也必须确保实际数据的一部分包含在测试集中,以确保该模型能达到预期目的。

下图展示了数据集按比例划分为三子集。需要特别指出的是,训练集必须大于其他两个子集,因为它是用于训练模型的。此外,可以观察到,训练集和验证集都会对模型产生影响,而测试集主要用于验证模型在未知数据上的实际表现。考虑到这一点,训练集和验证集必须来自相同的分布:

图 3.2:分割比例可视化

图 3.2:分割比例可视化

练习 3.01:对样本数据集进行数据划分

在本次练习中,我们将使用分割比例法对wine数据集进行数据划分。此次练习的划分将采用三分法。按照以下步骤完成本次练习:

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

  1. 打开一个 Jupyter Notebook 以实现本次练习。导入所需的元素,以及 scikit-learn 的datasets包中的load_wine函数:

    from sklearn.datasets import load_wine
    import pandas as pd
    from sklearn.model_selection import train_test_split
    

    第一行导入了将用于从 scikit-learn 加载数据集的函数。接下来,导入了 pandas 库。最后,导入了 train_test_split 函数,它负责划分数据集。该函数将数据划分为两个子集(训练集和测试集)。由于本练习的目标是将数据划分为三个子集,因此该函数将被使用两次,以实现所需的结果。

  2. 加载 wine 玩具数据集并将其存储在一个名为 data 的变量中。使用以下代码片段进行此操作:

    data = load_wine()
    

    load_wine 函数加载的是 scikit-learn 提供的玩具数据集。

    注意

    要查看数据集的特征,请访问以下链接:scikit-learn.org/stable/modules/generated/sklearn.datasets.load_wine.html

    前面函数的输出是一个类似字典的对象,将特征(可以作为数据调用)与目标(可以作为目标调用)分为两个属性。

  3. 将每个属性(数据和目标)转换为 Pandas DataFrame,以便进行数据操作。打印两个 DataFrame 的形状:

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

    print 函数的输出应如下所示:

    (178, 13) (178, 1)
    

    在这里,第一个括号中的值表示数据框 X(即特征矩阵)的形状,而第二个括号中的值表示数据框 Y(即目标矩阵)的形状。

  4. 使用 train_test_split 函数进行数据的首次拆分。使用以下代码片段进行此操作:

    X, X_test, Y, Y_test = train_test_split(X, Y, test_size = 0.2)
    

    train_test_split 函数的输入是两个矩阵 (X,Y) 和测试集的大小,作为一个 0 到 1 之间的值,表示比例。

    print(X.shape, X_test.shape, Y.shape, Y_test.shape)
    

    通过打印所有四个矩阵的形状,按照前面的代码片段,可以确认测试子集的大小(XY)是原始数据集总大小的 20%(150 * 0.2 = 35.6),四舍五入为整数,而训练集的大小是剩余的 80%:

    (142, 13) (36, 13) (142, 1) (36, 1)
    
  5. 为了创建验证集(dev 集),我们将使用 train_test_split 函数来拆分我们在前一步中获得的训练集。然而,为了得到与测试集相同形状的验证集,在创建验证集之前,需要计算测试集大小与训练集大小的比例。此比例将作为下一步的 test_size

    dev_size = 36/142
    print(dev_size)
    

    在这里,36 是我们在前一步中创建的测试集的大小,而 142 是将进一步拆分的训练集的大小。此操作的结果大约是 0.25,可以使用 print 函数进行验证。

  6. 使用 train_test_split 函数将训练集分成两个子集(训练集和验证集)。使用前一步操作的结果作为 test_size

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

    print 函数的输出如下:

    (106, 13) (106, 1) (36, 13) (36, 1) (36, 13) (36, 1)
    

    注意

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

    你还可以在线运行这个示例,网址是packt.live/2YECtsG。你必须执行整个 Notebook 才能获得所需的结果。

你已经成功地将数据集分成三个子集,以开发高效的机器学习项目。你可以自由地测试不同的划分比例。

总之,数据划分的比例并不是固定的,应该根据可用数据的数量、要使用的算法类型以及数据的分布来决定。

交叉验证

交叉验证也是一种通过重新抽样数据来划分数据的过程,这些数据用于训练和验证模型。它包含一个参数K,表示数据集将被划分为的组数。

由于此原因,该过程也称为 K 折交叉验证,其中K通常由你选择的数字代替。例如,使用 10 折交叉验证过程创建的模型意味着数据被分成 10 个子组。交叉验证过程在以下图表中进行了说明:

图 3.3:交叉验证过程

图 3.3:交叉验证过程

前面的图表展示了交叉验证过程中遵循的一般步骤:

  1. 数据会随机洗牌,考虑到交叉验证过程会被重复。

  2. 数据被划分为K个子组。

  3. 验证集/测试集被选为其中一个子组,其余子组构成训练集。

  4. 模型按常规在训练集上训练。模型使用验证集/测试集进行评估。

  5. 来自该迭代的结果已被保存。根据结果调整参数,并通过重新洗牌数据开始新的过程。这个过程重复进行 K 次。

根据前述步骤,数据集被分成K个子集,并且模型被训练K次。每次,选取一个子集作为验证集,剩余的子集用于训练过程。

交叉验证可以通过三分法或二分法来完成。对于前者,数据集首先被分成训练集和测试集,然后使用交叉验证将训练集进一步划分,创建不同的训练集和验证集配置。后者则是在整个数据集上使用交叉验证。

交叉验证之所以受欢迎,是因为它能够构建“无偏”的模型,因为它可以让我们衡量算法在数据集不同部分上的表现,这也让我们能了解算法在未见数据上的表现。它之所以流行,还因为它允许你在小数据集上构建高效的模型。

选择 K 值并没有准确的科学依据,但需要考虑的是,较小的 K 值倾向于降低方差并增加偏差,而较大的 K 值则会产生相反的效果。此外,K 值越小,过程的计算开销越小,从而运行时间更短。

注意

方差和偏差的概念将在偏差、方差与数据不匹配部分进行解释。

练习 3.02:使用交叉验证将训练集划分为训练集和验证集

在本练习中,我们将使用交叉验证方法对 wine 数据集进行数据划分。按照以下步骤完成此练习:

  1. 打开一个 Jupyter Notebook 来实现这个练习并导入所有需要的元素:

    from sklearn.datasets import load_wine
    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn.model_selection import KFold
    

    前述代码的最后一行导入了 KFold 类,它来自 scikit-learn,将用于划分数据集。

  2. 按照之前的练习加载 wine 数据集,并创建包含特征和目标矩阵的 Pandas DataFrame:

    data = load_wine()
    X = pd.DataFrame(data.data)
    Y = pd.DataFrame(data.target)
    
  3. 使用 train_test_split 函数将数据分为训练集和测试集,该函数在之前的练习中已经学习过,test_size 设置为 0.10:

    X, X_test, Y, Y_test = train_test_split(X, Y, \
                                            test_size = 0.10)
    
  4. 使用 10 折交叉验证配置实例化 KFold 类:

    kf = KFold(n_splits = 10)
    

    注意

    随意尝试不同的 K 值,观察该练习输出的形状如何变化。

  5. X 中的数据应用 split 方法。此方法将输出用于训练和验证集的实例索引。该方法会创建 10 种不同的划分配置。将输出保存为名为 splits 的变量:

    splits = kf.split(X)
    

    请注意,不需要对 Y 中的数据运行 split 方法,因为该方法只保存索引号,这在 XY 中是相同的。实际的划分将在后续处理。

  6. 执行一个 for 循环,遍历不同的划分配置。在循环体内,创建用于存储训练集和验证集数据的变量。使用以下代码片段实现:

    for train_index, test_index in splits:
        X_train, X_dev = X.iloc[train_index,:], \
                         X.iloc[test_index,:]
        Y_train, Y_dev = Y.iloc[train_index,:], \
                         Y.iloc[test_index,:]
    

    for 循环会遍历 K 次配置。在循环体内,数据将根据索引号进行划分:

    print(X_train.shape, Y_train.shape, X_dev.shape, \
          Y_dev.shape, X_test.shape, Y_test.shape)
    

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

    (144, 13) (144, 1) (16, 13) (16, 1) (18, 13) (18, 1)
    

    注意

    训练和评估模型的代码应该写在循环体内,因为交叉验证过程的目标是使用不同的划分配置来训练和验证模型。

你已经成功对一个样本数据集进行了交叉验证划分。

注意

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

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

总结来说,交叉验证是一种用于对数据进行洗牌并将其拆分为训练集和验证集的过程,每次训练和验证过程都会在不同的数据集上进行,从而获得一个具有低偏差的模型。

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

你的公司专注于识别手写字符。它希望提高数字的识别能力,因此他们收集了 1797 个从 0 到 9 的手写数字数据集。这些图像已经被转换为其数字表示,因此他们提供了该数据集供你将其拆分为训练/验证/测试集。你可以选择执行传统拆分或交叉验证。按照以下步骤完成此活动:

  1. 导入拆分数据集所需的所有元素,以及从 scikit-learn 中导入load_digits函数以加载digits数据集。

  2. 加载digits数据集并创建包含特征和目标矩阵的 Pandas 数据框。

  3. 采用传统的拆分方法,使用 60/20/20%的拆分比例。

  4. 使用相同的数据框,执行 10 折交叉验证拆分。

    注意

    本活动的解决方案可以在第 228 页找到。可以随意尝试不同的参数以得到不同的结果。

评估指标

模型评估对于创建有效模型是不可或缺的,这些模型不仅在用于训练模型的数据上表现良好,而且在未见过的数据上也能表现出色。评估模型的任务在处理监督学习问题时尤其容易,因为有一个可以与模型预测进行比较的真实值。

确定模型的准确度百分比对于其应用于没有标签类的数据至关重要。例如,具有 98%准确率的模型可能使用户认为预测准确的概率较高,因此应该信任该模型。

如前所述,性能评估应该在验证集(开发集)上进行,以微调模型,并在测试集上进行,以确定所选模型在未见过的数据上的预期表现。

分类任务的评估指标

分类任务指的是类别标签为离散值的模型,正如之前提到的那样。考虑到这一点,评估此类任务性能的最常见衡量标准是计算模型的准确度,即比较实际预测值与真实值。尽管在许多情况下,这可能是一个合适的度量标准,但在选择一个之前,还需要考虑其他几种标准。

现在,我们将查看不同的性能评估指标。

混淆矩阵

混淆矩阵是一个包含模型性能的表格,描述如下:

  • 列表示属于预测类别的实例。

  • 行表示实际属于该类别的实例(真实情况)。

混淆矩阵呈现的配置使得用户能够快速发现模型难度较大的领域。请看以下表格:

图 3.4:一个分类器的混淆矩阵,预测女性是否怀孕

图 3.4:一个分类器的混淆矩阵,预测女性是否怀孕

从上述表格中可以观察到:

  • 通过对第一行的值求和,可以知道有 600 个孕妇观察值。然而,在这 600 个观察值中,模型预测 556 个为孕妇,44 个为非孕妇。因此,模型预测女性怀孕的正确率为 92.6%。

  • 关于第二行,也有 600 个非孕妇观察值。在这 600 个观察值中,模型预测 123 个为孕妇,477 个为非孕妇。模型成功预测非孕妇的准确率为 79.5%。

根据这些陈述,可以得出结论,模型在分类非孕妇观察值时表现最差。

考虑到混淆矩阵中的行表示事件的发生或未发生,列表示模型的预测,混淆矩阵中的值可以解释如下:

  • 真正例TP):指模型正确将事件分类为正例的实例——例如,正确分类为孕妇的实例。

  • 假正例FP):指模型错误地将事件分类为正例的实例——例如,错误分类为孕妇的非孕妇实例。

  • 真反例TN):指模型正确将事件分类为负例的实例——例如,正确分类为非孕妇的实例。

  • 假反例FN):指模型错误地将事件分类为负例的实例——例如,错误预测为非孕妇的孕妇实例。

混淆矩阵中的值可以如下表示:

图 3.5:显示混淆矩阵值的表格

图 3.5:显示混淆矩阵值的表格

准确率

如前所述,准确率衡量模型正确分类所有实例的能力。尽管这被认为是衡量性能的最简单方法之一,但在研究的目标是最小化/最大化某一类事件的发生,而与其他类的性能无关时,它可能并不是一个有用的指标。

图 3.4 中混淆矩阵的准确率计算如下:

图 3.6:显示准确率计算公式的方程式

图 3.6:显示准确率计算公式的方程式

这里,m是实例的总数。

86%的准确率指的是模型在分类两个类标签时的整体表现。

精确度

该指标衡量模型正确分类正类标签(代表事件发生的标签)的能力,通过与预测为正类的实例总数进行比较。它由真正例真正例假正例的和的比例表示,如下公式所示:

图 3.7:展示精确度计算的公式

图 3.7:展示精确度计算的公式

精确度指标仅适用于二分类任务,其中只有两个类标签(例如,真或假)。它也可以应用于多类任务,前提是这些类被转换为两类(例如,预测手写数字是否为 6 或其他数字),其中一个类指的是具有某种条件的实例,另一个类则指不具有该条件的实例。

对于图 3.4中的示例,模型的精确度为 81.8%。

召回率

召回率(recall)衡量的是正确预测为正类标签的数量与所有正类标签的比例。它由真正例假负例的和的比例表示:

图 3.8:展示召回率计算的公式

图 3.8:展示召回率计算的公式

同样,这一度量应应用于两个类标签。对于图 3.4中的示例,召回率为 92.6%,与其他两个指标相比,表示该模型的最高表现。选择某个指标将取决于研究的目的,稍后会更详细地解释。

练习 3.03:在分类任务中计算不同的评估指标

在本次练习中,我们将使用乳腺癌玩具数据集,并使用 scikit-learn 库来计算评估指标。请按照以下步骤完成本次练习:

  1. 打开 Jupyter Notebook 以实现本练习,并导入所有所需的元素:

    from sklearn.datasets import load_breast_cancer
    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
    

    第四行导入了tree模块,这是 scikit-learn 库中的一部分,将用于在本次练习的训练数据上训练决策树模型。下面的代码行将导入在本练习过程中计算的不同评估指标。

  2. 乳腺癌玩具数据集包含了对 569 名女性乳腺肿块分析的最终诊断(恶性或良性)。加载数据集并创建特征和目标 Pandas DataFrame,如下所示:

    data = load_breast_cancer()
    X = pd.DataFrame(data.data)
    Y = pd.DataFrame(data.target)
    
  3. 使用传统的拆分方法来划分数据集:

    X_train, X_test, \
    Y_train, Y_test = train_test_split(X,Y, test_size = 0.1, \
                                       random_state = 0)
    

    请注意,数据集被分成了两个子集(训练集和测试集),因为本次练习的目的是学习如何使用 scikit-learn 包计算评估指标。

    注意

    random_state参数用于设置种子,确保每次运行代码时结果相同。这保证了你将获得与本练习中所示结果相同的结果。可以使用不同的数字作为种子;然而,为了获得与本章练习和活动中所示相同的结果,请使用建议的相同数字。

  4. 首先,从 scikit-learn 的tree模块实例化DecisionTreeClassifier类。接着,在训练集上训练决策树。最后,使用该模型在测试集上预测类别标签。使用以下代码实现:

    model = tree.DecisionTreeClassifier(random_state = 0)
    model = model.fit(X_train, Y_train)
    Y_pred = model.predict(X_test)
    

    首先,使用random_state实例化模型以设置种子。然后,使用fit方法通过训练集的数据(包括XY)训练模型。最后,使用predict方法对测试集中的数据(仅X)进行预测。Y_test中的数据将用于将预测结果与真实值进行比较。

    注意

    训练监督学习模型的步骤将在第四章监督学习算法:预测年收入,和第五章人工神经网络:预测年收入中进一步讲解。

  5. 使用 scikit-learn 构建混淆矩阵,如下所示:

    confusion_matrix(Y_test, Y_pred)
    

    结果如下,其中将真实值与预测值进行比较:

    array([[21, 1],
           [6, 29]])
    
  6. 通过比较Y_testY_pred,计算模型的准确率、精确度和召回率:

    accuracy = accuracy_score(Y_test, Y_pred)
    print("accuracy:", accuracy)
    precision = precision_score(Y_test, Y_pred)
    print("precision:", precision)
    recall = recall_score(Y_test, Y_pred)
    print("recall:", recall)
    

    结果如下所示:

    accuracy: 0.8771
    precision: 0.9666
    recall: 0.8285
    

    由于正标签表示肿瘤为恶性,可以得出结论:模型预测为恶性的实例有 96.6%的高概率为恶性,但对于预测为良性的实例,模型有 17.15%(100%-82.85%)的错误概率。

    注意

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

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

你已经成功计算了分类任务的评估指标。

选择评估指标

有多种指标可用于衡量模型在分类任务中的表现,选择正确的指标是构建能够在研究目的上表现出色的模型的关键。

之前提到过,理解研究目的对于确定需要对数据集执行的预处理技术至关重要。此外,研究目的对于确定评估模型表现的理想指标也非常有帮助。

为什么研究的目的对选择评估度量标准很重要?因为通过了解研究的主要目标,可以决定是应该将注意力集中在模型的整体性能上,还是仅仅关注某一类标签的表现。

例如,一个用于识别图像中是否有鸟类的模型,如果它能够正确地识别出图像中是否有鸟类,而不是错误地将其他动物识别为鸟类,那么这个模型就是一个好模型。因此,模型只需要关注于提高正确分类鸟类的性能。

另一方面,对于一个用于识别手写字符的模型,任何一个字符都不比其他字符更重要,理想的度量标准应该是衡量模型整体准确率的度量。

如果选择了多个度量标准会怎样?就会很难得出模型的最佳性能,因为同时衡量两个度量可能需要采用不同的方法来改进结果。

回归任务的评估度量

考虑到回归任务的最终输出是连续的,没有固定数量的输出标签,因此真实值与预测值之间的比较是基于数值的接近程度,而不是它们是否完全相同。例如,在预测房价时,一个模型预测某房屋的价格为 299,846 美元,而真实价格为 300,000 美元,这个模型可以被认为是一个好的模型。

用于评估连续变量准确性的两种最常用度量标准是平均绝对误差(MAE)均方根误差(RMSE),它们在这里得到了说明:

  • 平均绝对误差(MAE):该度量衡量的是预测值与真实值之间的平均绝对差异,不考虑误差的方向。计算 MAE 的公式如下:

图 3.9:展示 MAE 计算的方程式

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_03_09.jpg)

图 3.9:展示 MAE 计算的方程式

这里,m表示实例的总数,y是实际值,ŷ是预测值。

图 3.10:展示 RMSE 计算的方程式

这两种度量标准都表达了从 0 到无穷大的平均误差,其中数值越低,模型性能越好。它们之间的主要区别在于,MAE 对所有误差赋予相同的权重,而 RMSE 则对误差进行平方处理,从而赋予较大误差更高的权重。

考虑到这一点,RMSE 指标在较大误差应受到惩罚的情况下特别有用,这意味着在性能度量中会考虑异常值。例如,当偏差为 4 的值比偏差为 2 的值差了两倍时,可以使用 RMSE 指标。而 MAE 则在偏差为 4 的值仅是偏差为 2 的值两倍的情况下使用。

练习 3.04:计算回归任务中的评估指标

在本次练习中,我们将计算一个使用线性回归训练的模型的评估指标。我们将使用 boston 玩具数据集来完成此任务。按照以下步骤完成此练习:

  1. 打开一个 Jupyter Notebook 来实现此练习,并导入所有所需的元素,具体如下:

    from sklearn.datasets import load_boston
    import pandas as pd
    from sklearn.model_selection import train_test_split
    from sklearn import linear_model
    from sklearn.metrics import mean_absolute_error
    from sklearn.metrics import mean_squared_error
    import numpy as np
    

    第四行导入了 scikit-learn 中的 linear_model 模块,该模块将用于在训练数据集上训练一个线性回归模型。接下来的几行代码导入了将在本次练习中评估的两个性能指标。

  2. 本次练习将使用 boston 玩具数据集。该数据集包含波士顿 506 个房价的数据。使用以下代码加载并拆分数据集,与我们之前的练习相同:

    data = load_boston()
    X = pd.DataFrame(data.data)
    Y = pd.DataFrame(data.target)
    X_train, X_test, Y_train, Y_test = train_test_split(X,Y, \
                                       test_size = 0.1, random_state = 0)
    
  3. 在训练集上训练一个线性回归模型。然后,使用该模型在测试集上预测类标签,具体如下:

    model = linear_model.LinearRegression()
    model = model.fit(X_train, Y_train)
    Y_pred = model.predict(X_test)
    

    一般来说,首先会实例化 scikit-learn linear_model 模块中的LinearRegression 类。然后,使用 fit 方法通过训练集的数据(包括 XY)来训练模型。最后,使用 predict 方法对测试集中的数据(仅 X)进行预测。Y_test 中的数据将用于将预测结果与真实值进行比较。

  4. 计算 MAE 和 RMSE 两个指标:

    MAE = mean_absolute_error(Y_test, Y_pred)
    print("MAE:", MAE)
    RMSE = np.sqrt(mean_squared_error(Y_test, Y_pred))
    print("RMSE:", RMSE)
    

    结果如下所示:

    MAE: 3.9357
    RMSE: 6.4594
    

    注意

    scikit-learn 库允许你直接计算 MSE。为了计算 RMSE,需要计算从 mean_squared_error() 函数得到值的平方根。通过使用平方根,我们确保 MAE 和 RMSE 的值是可以比较的。

    从结果可以得出结论,模型在测试集上的表现良好,考虑到两个值都接近零。然而,这也意味着性能仍然可以进一步提升。

    注意

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

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

你现在已经成功计算了回归任务中的评估指标,该任务旨在根据房屋的特征计算价格。在接下来的活动中,我们将计算一个分类模型的性能,该模型用于识别手写字符。

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

你继续致力于创建一个识别手写数字的模型。团队已经构建了一个模型,他们希望你评估该模型的表现。在此活动中,你将计算训练模型的不同表现评估指标。按照以下步骤完成该活动:

  1. 导入所有必需的元素以加载并拆分数据集,用于训练模型并评估分类任务的表现。

  2. 从 scikit-learn 中加载 digits 玩具数据集,并创建包含特征和目标矩阵的 Pandas 数据框。

  3. 将数据拆分为训练集和测试集。使用 20% 作为测试集的大小。

  4. 在训练集上训练一个决策树。然后,使用该模型预测测试集上的类别标签。

    注意

    要训练决策树,请回顾 练习 3.04计算分类任务的不同评估指标

  5. 使用 scikit-learn 构建混淆矩阵。

  6. 计算模型的准确率。

  7. 计算精确度和召回率。考虑到精确度和召回率只能在二分类问题上计算,我们假设只关注将实例分类为数字 6其他任何数字

    为了能够计算精确度和召回率,使用以下代码将 Y_testY_pred 转换为一热向量。一个一热向量由仅包含零和一的向量组成。对于本活动,0 代表 数字 6,而 1 代表 任何其他数字。这将类别标签(Y_testY_pred)转换为二进制数据,即只有两个可能的结果,而不是 10 个不同的结果。

    然后,使用新变量计算精确度和召回率:

    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
    

    你应该得到以下值作为输出:

    Accuracy = 84.72%
    Precision = 98.41%
    Recall = 98.10%
    

    注意

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

错误分析

如前所述,通过使用 scikit-learn 库,构建一个平均模型是出乎意料的简单。构建一个优秀模型的关键方面来自研究人员的分析和决策。

如我们迄今所见,一些最重要的任务是选择和预处理数据集、确定研究目的以及选择合适的评估指标。在处理完这些内容并考虑到模型需要进行微调以达到最高标准后,大多数数据科学家建议训练一个简单的模型,无论超参数如何,以便开始研究。

错误分析 随后被引入,作为一种非常有用的方法论,将一个平均模型转化为一个卓越的模型。顾名思义,它包括分析数据集中不同子集的错误,以便定位影响模型的主要条件。

偏差、方差和数据不匹配

要了解可能影响机器学习模型的不同条件,重要的是要理解贝叶斯误差是什么。贝叶斯误差,也称为不可降低误差,是可以达到的最低可能错误率。

在技术和人工智能改进之前,贝叶斯误差被认为是人类可以达到的最低可能误差(人为误差)。例如,对于大多数人类以 0.1 的错误率完成的过程,但顶级专家以 0.05 的错误率完成的过程,贝叶斯误差将为 0.05。

然而,现在贝叶斯误差被重新定义为机器可以达到的最低可能误差,这是未知的,考虑到我们作为人类只能理解到人类误差的程度。因此,当使用贝叶斯误差来分析错误时,一旦模型低于人类误差,就不可能知道最低限度。

以下图表有助于分析不同数据集之间的错误率,并确定对模型影响最大的条件。该图的目的是找出彼此之间错误差异最大的错误,以便相应地诊断和改进模型。重要的是强调,每个集合的错误值是通过从该集合的评估指标(例如准确率)减去 100%来计算的:

图 3.11:错误分析方法论

图 3.11:错误分析方法论

考虑前述图表,执行错误分析的过程如下:

  1. 计算所有数据集的性能评估。此措施用于计算每个集合的错误。

  2. 从底部到顶部开始计算差异如下:

    将开发集误差(12%)减去测试集误差(12%)。得到的数值(0%)保存。

    将训练/开发集误差(9%)减去开发集误差(12%)。得到的数值(3%)保存。

    将训练集误差(8%)减去训练/开发集误差(9%)。得到的数值(1%)保存。

    将贝叶斯误差(2%)减去训练集误差(8%)。得到的数值(6%)保存。

  3. 更大的差异确定最严重影响模型的条件。在本例中,更大的差异出现在贝叶斯误差和训练集误差之间,如前述图表所示,这表明模型正遭受高偏差的影响。

    训练/开发集是训练集和验证集(开发集)中数据的组合。它通常与开发集具有相同的形状,并包含来自两个集合的相同数量的数据。

下面解释每个条件及一些避免/修复它们的技术:

  • 高偏差:也称为欠拟合,这种情况发生在模型没有从训练集中学习时,导致模型在所有三种数据集(训练集、验证集和测试集)上表现较差,且对未见过的数据也表现不佳。

    欠拟合是最容易检测到的情况,通常需要更换不同的算法,以更好地适应可用数据。对于神经网络而言,通常可以通过构建更大的网络或训练更长时间来解决。

  • 高方差:也称为过拟合,这种情况指的是模型在处理与训练集不同的数据时表现不佳。这基本上意味着模型通过学习数据的细节和离群点来过拟合训练数据,而没有进行任何概括。一个遭受过拟合的模型在开发集、测试集或未见过的数据上表现不佳。

    过拟合可以通过调整算法的不同超参数来解决,通常目标是简化算法对数据的近似。例如,对于决策树,可以通过修剪树来删除一些从训练数据中学习到的细节来解决。另一方面,对于神经网络,可以通过添加正则化技术来减少神经元对整体结果的影响。

    此外,向训练集添加更多数据也有助于模型避免高方差,即增加用于训练模型的数据集。

  • 数据不匹配:当训练集和验证集不遵循相同的分布时,就会发生数据不匹配。这会影响模型的表现,因为虽然它基于训练数据进行概括,但这种概括并不描述验证集中出现的数据。例如,若一个模型是用来描述风景照片的,但它是用高清图像训练的,而实际使用的图像是非专业的,这时就会出现数据不匹配问题。

    从逻辑上讲,避免数据不匹配的最佳方法是确保各数据集遵循相同的分布。例如,你可以通过将来自两个来源(专业和非专业图像)的图像一起打乱,然后将它们划分到不同的集合中来实现这一点。

    然而,在数据不足以遵循与未来未见数据(将来用于训练的数据)相同分布的情况下,强烈建议完全从这些数据中创建开发集和测试集,并将剩余的数据添加到大型训练集中。从前面的示例中,应该使用非专业图像来创建开发集和测试集,将剩余的图像与专业图像一起添加到训练集中。这有助于使用包含足够图像以进行泛化的训练集来训练模型,但它使用与未见数据分布相同的数据来微调模型。

    最后,如果所有集合的数据来自相同的分布,那么这个条件实际上指的是一个高方差问题,应按照这种方式进行处理。

  • 对开发集的过拟合:最后,类似于方差问题,当模型没有进行泛化,而是过度拟合开发集时,会发生这种情况。

    应使用与前面解释的高方差问题相同的方法来处理该问题。

在下一个练习中,我们将计算模型在不同数据集上的错误率,这可用于进行错误分析。

练习 3.05:计算不同数据集上的错误率

在本次练习中,我们将计算使用决策树训练的模型的错误率。我们将使用乳腺癌数据集来完成此任务。按照以下步骤完成本次练习:

  1. 打开 Jupyter Notebook 来实现本次练习,并导入所有需要的元素以加载和拆分数据集。这些将用于训练模型并评估其召回率:

    from sklearn.datasets import load_breast_cancer
    import pandas as pd
    from sklearn.model_selection import train_test_split
    import numpy as np
    from sklearn import tree
    from sklearn.metrics import recall_score
    
  2. 在本次练习中,将使用 乳腺癌 数据集。使用以下代码加载数据集,并创建包含特征和目标矩阵的 Pandas DataFrame:

    breast_cancer = load_breast_cancer()
    X = pd.DataFrame(breast_cancer.data)
    Y = pd.DataFrame(breast_cancer.target)
    
  3. 将数据集拆分为训练集、验证集和测试集:

    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)
    

    结果的形状如下:

    (455, 30) (455, 1) (57, 30) (57, 1) (57, 30) (57, 1)
    
  4. 创建一个结合了训练集和验证集数据的训练/开发集:

    np.random.seed(101)
    indices_train = np.random.randint(0, len(X_train), 25)
    indices_dev = np.random.randint(0, len(X_dev), 25)
    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)
    

    首先,设置一个随机种子,以确保结果的可重复性。接下来,使用 NumPy random.randint() 函数从 X_train 集合中选择随机索引。为此,在 0 到 X_train 总长度之间生成 28 个随机整数。相同的过程用于生成开发集的随机索引。最后,创建一个新变量来存储从 X_trainX_dev 中选择的值,并创建一个变量来存储来自 Y_trainY_dev 的相应值。

    已创建的变量包含来自训练集的 25 个实例/标签和来自开发集的 25 个实例/标签。

    结果集的形状如下:

    (50, 30) (50, 1)
    
  5. 在训练集上训练决策树,如下所示:

    model = tree.DecisionTreeClassifier(random_state = 101)
    model = model.fit(X_train, Y_train)
    
  6. 使用 predict 方法生成所有数据集(训练集、训练/开发集、开发集和测试集)的预测。接下来,考虑到本研究的目标是最大化模型预测所有恶性案例的能力,计算所有预测的召回率分数。将所有分数存储在名为 scores 的变量中:

    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 = recall_score(Y_sets[i], pred)
        scores[sets[i]] = score
    print(scores)
    

    所有数据集的误差率如下:

    {'Training': 1.0, 'Train/dev': 0.9705882352941176, 'Validation': 0.9333333333333333, 'Testing': 0.9714285714285714}
    

    从上述值中,可以创建以下包含误差率的表格:

    图 3.12:所有数据集的误差率

图 3.12:所有数据集的误差率

在这里,假设贝叶斯误差为 0,因为恶性肿瘤和良性肿瘤的分类是通过活检进行的。

从上表可以得出结论,考虑到所有的误差率接近 0,即最低可能的误差,该模型在研究目的上表现得非常出色。

最高的误差率差异出现在训练集/开发集和开发集之间,这意味着数据不匹配。然而,考虑到所有数据集来自相同的分布,这种情况被认为是一个高方差问题,增加更多的数据到训练集中应该有助于减少误差率。

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

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

你已经成功计算出了所有数据子集的误差率。在接下来的活动中,我们将进行误差分析,以定义改进已创建的手写数字识别模型性能的步骤。

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

根据你提供给团队的不同指标,他们已选择准确率作为理想的度量标准。考虑到这一点,你的团队要求你进行误差分析,以确定如何改进模型。在此活动中,你将通过比较不同数据集的误差率来进行误差分析,以评估模型的准确度。请按照以下步骤进行:

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

  2. 从 scikit-learn 中加载 digits 玩具数据集,并创建包含特征和目标矩阵的 Pandas DataFrame。

  3. 将数据拆分为训练集、验证集和测试集。使用 0.1 作为测试集的大小,并构建一个大小相同的验证集。

  4. 为特征和目标值创建一个包含 90 个训练集实例/标签和 90 个开发集实例/标签的训练/开发集。

  5. 在训练集数据上训练一个决策树模型。

  6. 计算所有数据集的错误率,评估模型的准确性,并确定哪些条件影响模型的表现。

完成此活动后,您应获得以下错误率:

图 3.13:预期的错误率

图 3.13:预期的错误率

注意

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

摘要

本章解释了可以通过监督学习算法解决的不同任务:分类和回归。虽然这两种任务的目标都是近似一个将一组特征映射到输出的函数,但分类任务有离散的输出数量,而回归任务的输出可以是无限连续的值。

在开发机器学习模型以解决监督学习问题时,主要目标之一是让模型具有良好的泛化能力,从而能够应用于未来未见的数据,而不仅仅是学习一组实例并在新数据上表现不佳。因此,本章解释了一种验证和测试的方法论,涉及将数据划分为三组:训练集、开发集和测试集。这种方法消除了偏差的风险。

之后,我们介绍了如何评估分类和回归问题中模型的表现。最后,我们讨论了如何分析模型的表现并对每个数据集进行错误分析,以检测影响模型表现的条件。

在下一章,我们将重点应用不同的算法到实际的数据集,旨在将我们在本章学到的步骤应用于选择最适合此案例研究的算法。

第四章: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:打印评估指标

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_04_12.jpg)

图 4.12:打印评估指标

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

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

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

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

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

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_04_13.jpg)

图 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:运行  方法后显示的警告信息

    图 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:每个特征中类别的出现次数

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_01_23.jpg)

    图 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:显示特征矩阵前五个实例的截图

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_01_24.jpg)

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

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

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

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

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

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

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_01_25.jpg)

图 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:聚类过程后获得的两个示例图]

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_02_19.jpg)

图 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:处理结束时获得的示例图]

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/ml-ws/img/B15781_02_20.jpg)

图 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 才能得到预期的结果。

在本章的所有活动中,你已经成功地学会了如何开发一个完整的机器学习解决方案,从数据预处理和训练模型,到使用误差分析选择最佳性能的模型,并保存模型以便有效利用。

posted @ 2025-07-14 17:27  绝不原创的飞龙  阅读(25)  评论(0)    收藏  举报