无监督学习与生成式人工智能(MEAP)(全)

无监督学习与生成式人工智能(MEAP)(全)

原文:zh.annas-archive.org/md5/ef4ba0ae2a37f1fc608875bd4cfb7156

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:机器学习简介

“世界上只有模式,模式之上的模式,影响其他模式的模式。被模式隐藏的模式。模式中的模式。” - 查克·帕拉尼克

我们喜欢模式。无论是我们的业务还是我们的生活,我们都会发现模式,并且(通常)倾向于坚持它们。我们有自己喜欢购买的杂货,使用的电信运营商和通话套餐,关注的新闻文章,电影类型和音轨 - 这些都是我们喜好模式的例子。我们热爱模式,而且比模式更喜欢的是发现它们,整理它们,也许习惯于它们!

有一句俗语流传开来 - “数据是新的电力”。数据确实宝贵;这一点无人能否认。但纯粹的数据将毫无用处。我们必须清洗数据,分析和可视化数据,然后我们才能从中获得见解。数据科学、机器学习和人工智能正在帮助我们揭示这些模式 - 从而使我们在活动和业务中能够做出更具洞察力和平衡的决策。

在这本书中,我们将解决一些这样的奥秘。我们将研究一种被称为无监督学习的机器学习分支。无监督学习解决方案是正在改变行业面貌的最有影响力的方法之一。它们被应用于银行和金融、零售、保险、制造业、航空、医学科学、电信等几乎每个行业。

在整本书中,我们讨论了无监督学习的概念 - 算法的基本构建块,它们的细节,背景过程和数学基础。我们审视了这些概念,研究了最佳实践,分析了常见的错误和陷阱,并以案例研究为基础的方法来补充学习。与此同时,我们正在编写解决这些问题的实际 Python 代码。所有的代码都附有逐步解释和注释。

本书分为三个部分。第一部分探讨了无监督学习的基础知识,并涵盖了更简单的概念,如 k 均值聚类、层次聚类、主成分分析等。这一部分为你未来的旅程温和地做了准备。如果你已经精通这些主题,可以直接开始阅读本书的第二部分。建议你快速阅读这些章节,以便刷新概念。

第二部分处于中级水平。我们从关联规则算法如 apriori、ECLAT 和序列规则挖掘开始。然后我们加快步伐,研究更复杂的算法和概念 - 光谱聚类、GMM 聚类、t-SNE、多维缩放(MDS)等。然后我们在下一章节处理文本数据。

第三部分也是最后一部分是高级内容。我们正在讨论像受限玻尔兹曼机、自编码器、生成对抗网络等复杂主题。我们还在书的最后一章中考察了端到端模型开发,包括模型部署、最佳实践和常见陷阱。

当您完成本书时,您将对基于无监督技术的机器学习、各种算法、算法依赖的数学和统计基础、业务用例、Python 实现以及遵循的最佳实践有很好的理解。

本书适合希望深入了解无监督学习算法的学生和研究人员。它适合追求数据科学职业的专业人士,他们希望了解遵循的最佳实践和常见挑战的解决方案。内容非常适合希望在与团队和客户交流时保持信心的经理和领导。最重要的是,适合有好奇心的人,他们打算接受有关无监督学习算法的教育,并开发 Python 经验来解决案例研究。

建议您具有基本的编程理解,比如 C ++、Java、Objective-C 等面向对象的语言。我们将在整本书中使用 Python,所以如果您熟悉 Python,肯定会有所帮助。对数学和几何的基本理解将有助于可视化结果,并且对数据相关用例的一些了解将有助于与业务用例联系起来。最重要的是,全书各章节都需要保持开放的心态来吸收知识。

第一章旨在向您介绍机器学习的概念。在这一开篇章节中,我们将涵盖以下主题:

  1. 数据、数据类型、数据管理和质量

  2. 数据分析、机器学习、人工智能和深度学习

  3. 机器学习的基本知识

  4. 不同类型的机器学习算法

  5. 可用的技术工具包

  6. 总结

让我们首先了解我们拥有的最小颗粒——“数据”作为第一个主题。欢迎来到第一章,祝你一切顺利!

1.1 数据、数据类型、数据管理和质量

我们从一切的主角“数据”开始。数据可以被称为为执行任何类型的分析或研究而收集的事实和统计数据。但是数据有自己的特性、属性、质量措施和管理原则。它被存储、导出、加载、转换和测量。

我们现在将研究所有这些内容——从数据的定义开始。然后我们将继续讨论不同类型的数据、它们各自的示例以及使数据有用且质量良好的属性是什么。

1.1.1 什么是数据

“数据”是无处不在的。使用移动网络打电话 - 你正在产生数据。预订即将到来的假期的机票和酒店 - 正在创建数据。进行银行交易,浏览社交媒体和在线购物网站,购买保险政策,或购买汽车 -任何地方都会产生数据。它从一种形式转变为另一种形式,被储存、清理、管理和分析。

正式地说 - 数据是一组事实、观察、测量、文字、数字、图像和视频。它可能是干净的或不干净的,有序的或无序的,包含混合数据类型的,或者完全纯粹的和历史的或实时的。

图 1.1 我们如何将原始数据转化为信息、知识,最终洞见,可以在业务中用来推动决策和行动

01_01

数据本身是没有用的,直到我们清理它、整理它、分析它并从中得出见解。我们可以在(图 1.1)中可视化这种转变。原始数据转换为信息,当我们可以在其中找到区别时。当我们相关术语并“串联数据”时,同一段信息变成了知识。洞察力是我们能找到主要中心和显著点的阶段。一个洞察力必须是可操作的,简明扼要和直接的。例如,如果一个电信运营商的客户保留团队被告知那些 9 天内没有打电话的客户较之打电话的客户流失的几率要大 30%,这将是一个有用的洞察力,他们可以加以工作并尝试解决。同样,如果一个制造工厂中的线路技术员被告知使用 Mould ABC 比使用 Mould PQR 导致的瑕疵多 60%,他们就会避免使用这种组合。对于企业团队来说, 洞察力非常有用,因此他们可以采取纠正措施。

我们现在知道什么是数据。让我们研究各种类型的数据及其属性,深入了解数据。

1.1.2 不同类型的数据

数据是我们进行的所有交易中生成的,无论是在线还是线下,正如我们在本章的开始讨论的那样。我们可以把数据大致分类如下图 1.2 所示:

图 1.2 数据可以划分为定量和定性类别,再进行进一步的细分

01_02

  1. 定性数据是一种无法测量或称重的数据类型,例如口味、颜色、气味、健身、名称等。它们只能以主观方式观察。正式地说,当我们对某物进行分类或对其进行分类时,生成的数据是定性的。例如,彩虹中的颜色,国家中的城市,产品的质量,性别等。它们也被称为categorical变量。定性数据可以进一步细分为二进制、名义和序数数据集。

  2. 二进制数据, 顾名思义, 只有两个互斥的类。例如, 是 / 否, 干燥 / 湿润, 硬 / 软, 好/坏, 真/假等。

  3. 名义数据可描述为分类的数据类型,但它没有任何顺序或顺序。例如,一个国家使用不同的语言,一道彩虹中的颜色,客户可用的服务类型,一个国家中的城市等。

  4. 顺序数据与名义数据类似,只是我们可以按顺序排列它。例如,快速/中等/慢,积极/中立/消极等。

  5. 定量数据:所有可以测量、称重、量化、记录等的数据点都是定量数据。例如,身高、收入、客户数量、需求量、面积、体积等。它们是最常见的数据形式,也允许对它们进行数学和统计操作。定量数据进一步细分为离散和连续:

  6. 离散数据是精确的、直截了当的,且为整数。例如,飞机上的乘客数量或城市的人口不能是小数。

  7. 连续数据点可以取任何值,通常在一个范围内。例如,身高可以取小数值,或者产品价格不一定是整数。

任何数据点通常都会落入上述讨论的类别中。这些类别基于变量及其类型。还可以使用来源和用途进行更多的逻辑分组,这在解决业务问题时非常有意义。这种分组允许我们设计针对数据类型定制的解决方案。

根据来源和用途的不同,我们还可以将数据分为两大类:非结构化数据和结构化数据。

  1. 结构化数据:可以轻松表示为行列结构的数据集是结构化数据集。例如,零售店中 5 位客户的交易可以存储如下所示的表格(表 1.1):
表 1.1 结构化数据集的示例,具有金额、日期、城市、商品等属性。
客户 ID 交易日期 金额(美元) 物品数量 付款方式 城市
1001 2020 年 6 月 1 日 100 5 现金 新德里
1002 2020 年 6 月 2 日 101 6 纽约
1003 2020 年 6 月 3 日 102 7 伦敦
1004 2020 年 6 月 4 日 103 8 现金 都柏林
1005 2020 年 6 月 5 日 104 9 现金 东京

在上表中,对于每个唯一的客户 ID,我们有交易日期、花费金额、购买物品数量、付款方式和交易所在城市。这种数据类型可以扩展到员工详细信息、通话记录、银行交易等。

注意

分析和模型构建中使用的大多数数据是结构化的。结构化数据更容易以图表和图形的形式存储、分析和可视化。

我们有很多针对结构化数据的算法和技术 - 在日常现实世界的语言中,我们主要指的是结构化数据。

  1. 非结构化数据:非结构化数据可以是文本、音频、图像或视频。非结构化数据及其相应来源的示例如下图 1.3 所示,我们在其中解释了非结构化数据的主要类型:文本、图像、音频和视频以及它们的示例:
图 1.3 非结构化数据及其各种类型和示例。这些数据通常很难分析,并且通常需要基于深度学习的算法。

01_03

值得注意的是,我们的计算机和处理器只能理解二进制数字。因此,这些非结构化数据点仍然需要表示为数字,以便我们可以对其进行数学和统计计算。例如,一幅图像由像素组成。如果是彩色图像,每个像素将具有 RGB(红、绿、蓝)值,而每个 RGB 可以取值范围为(0-255)。因此,我们可以将图像表示为矩阵,进而进行更多的数学计算。类似地,文本、音频和视频也可以被表示。

注意

大多数基于深度学习的解决方案,如 CNN 和 RNN,都用于处理非结构化数据。我们将在本书的后期阶段处理文本和图像数据。

非结构化数据的表示如下例所示(见图 1.4)。我们展示了一张吸尘器的图片。如果将图像的一部分表示为矩阵,将会是这样的。这仅用于说明目的,而不是实际值。

图 1.4 一个示例非结构化数据的图像可以表示为一个矩阵以进行分析

01_04

同样,我们可以有文本、音频或视频数据的表示。由于规模庞大和维度众多,非结构化数据处理和建模复杂,因此大多数基于深度学习的模型能够胜任。这些深度学习模型构成了基于人工智能的解决方案的基础。

这些是数据的广义类型。我们可以有更多的类别,如比率或标度,可以用来定义一个变量与另一个变量的关系。所有这些数据点(无论是结构化还是非结构化)都是根据它们在现实生活中生成的方式来定义的。

所有这些数据点都必须被捕获、存储和管理。有很多工具可用于管理数据,我们将在适当的时候讨论其中的一些。但在此之前,让我们先研究一个最关键但往往不被过多谈论的主题 - 数据质量

1.1.3 数据质量

“垃圾进,垃圾出” - 这个原则总结了良好质量数据的重要性。如果数据不干净、可用、正确和相关,我们将无法解决手头的业务问题。但是,“良好质量”是什么意思呢?我们在下面的图 1.5 中展示了数据质量的主要组成部分,让我们逐一探讨它们。

图 1.5 数据质量至关重要,优质数据的属性如图所示。

01_05

优质数据的主要属性有:

  1. 完整性:我们期望我们的数据集是完整的,不缺少任何值。例如,如果我们正在处理一年的销售数据,良好的数据将具有所有 12 个月的所有值。那么它将是一个完整的数据源。数据集的完整性确保我们没有漏掉重要的变量或数据点。

  2. 有效性:数据的有效性是指其符合我们用例中存在和正在分析的属性、特征和变化的程度。有效性表明我们捕获的观察和测量是否可靠和有效。例如,如果研究的范围是 2015-2019 年,那么使用 2014 年的数据将是无效的。

  3. 准确性:准确性是数据正确性的属性。如果我们有不准确的数据,我们将生成不准确的见解,行动将是错误的。一个好的做法是通过生成 KPI(关键绩效指标)来开始项目,并将它们与业务报告的数字进行比较,以检查我们手头的数据的真实性。

  4. 代表性:这是数据中最重要的属性之一。但通常也是最被低估的。数据的代表性意味着所使用的数据真正捕捉到业务需求,而不是有偏见的。如果数据集存在偏见或代表性不够,生成的模型将无法对新的和未见过的数据进行预测,整个工作将付诸东流。

  5. 可用性:数据的不可用性是我们经常面临的挑战。数据可能无法为业务问题提供,然后我们面临是否继续使用案例的困境。有时我们面临操作挑战,无法访问数据库或面临权限问题,或者数据可能根本不可用于某个特定变量,因为它没有被捕获。在这种情况下,我们必须使用现有的数据,并使用替代变量。例如,想象一下我们正在解决一个需求生成的问题。我们想要预测在即将到来的销售季节期间可以预期在特定商店的顾客人数。但我们没有记录过去几个月访问的客户数量。然后我们可以使用收入作为替代字段,并为我们合成缺失的数据点。

  6. 一致性:在这里,我们检查数据点在系统和界面之间是否一致。不应该出现一个系统报告不同的收入数字,而另一个系统显示完全不同的值的情况。面对这种问题时,我们根据我们手头的数据生成相应的 KPI,并向业务团队寻求指导。

  7. 及时性:这意味着我们在此时拥有所有所需的数据。如果数据集目前不可用,但将来可能会变得可用,那么最好等待到那时。

  8. 完整性:我们拥有的数据表和变量彼此关联和相互关联。例如,员工的详细信息可能分布在多个表中,这些表使用员工 ID 相互关联。数据完整性解决了这一要求,并确保表之间和各个实体之间的所有这些关系是一致的。

数据的良好质量至关重要。在务实的日常业务中,我们经常无法获得高质量的数据。由于多种挑战,很难在系统中找到良好、干净、可访问、一致、代表性和完整的数据。

质量的降低可能是由于数据捕获和收集、导出或加载、所进行的转换等过程中的挑战所导致的。下面列出了一些问题:

  1. 我们可能会在某些列中得到整数作为名称,或者得到特殊字符如“#$!&”,或者得到空值、空白或 NaN(不是一个数字)作为一些值。

  2. 记录中的重复也是我们面临的问题之一。

  3. 异常值是我们经常需要处理的问题之一。例如,假设一个在线零售商的平均日交易量为 1000。有一天,由于服务器问题,没有进行交易。这是一个异常值情况。又比如,有一天,交易数量是 1,000,000。这又是一个异常值的例子。

  4. 然后有关于一天中的时间和一周中的日子的季节性变化和运动 - 所有这些都应该在数据集中足够具有代表性。

  5. 日期格式的不一致会导致在我们尝试合并多个数据源时出现多种挑战。来源 1 可能使用 DD/MM/YYYY,而另一个可能使用 MM/DD/YYYY。这在数据加载步骤中要加以注意。

所有这些异常和质量问题都必须得到彻底解决和清理。我们将在整本书中解决这些数据问题,并分享应遵循的最佳实践。

注意

您的原始数据的质量以及在清理过程中表现出的严谨性 - 定义了您最终分析的影响和解决方案的成熟度。

我们现在已经定义了数据的主要属性。我们现在将研究用于数据工程和管理的广泛过程和技术。

1.1.4 数据工程和管理

强大的数据工程流程和成熟的数据管理实践是成功的机器学习模型解决方案的先决条件。请参考下面的图 1.6,在其中描述了从数据捕获、数据管道和数据加载的过程直到准备好进行分析的点的端到端数据旅程。

图 1.6 数据工程为数据分析铺平了道路。它涉及数据加载、转换、丰富、清理、准备等过程,从而产生了准备好进行分析的数据。

01_06

在数据工程步骤中,数据经过清洗、整理、重塑、转换和摄入。通常情况下,我们有一个服务器,最终的数据存储在那里,准备好进行访问。最常用的过程是创建一个 ETL(导出,转换,加载)过程。然后我们使数据准备好进行分析。我们创建新的变量,处理空值,使用方法丰富数据,最后我们继续到分析/建模阶段。

提示

了解数据工程的基础知识是一个不错的主意,以补充关于数据科学的知识,因为两者是相辅相成的。

因此,我们已经学习了什么是数据,以及好数据的特点。数据被用于分析、建模、可视化、仪表板和洞察力的生成。很多时候,我们发现在业务中数据分析、数据科学、机器学习、数据挖掘、人工智能、商业智能、大数据等术语被相当互换地使用。澄清它们是一个不错的主意 – 这是下一节的主题。我们会讨论到为每个特定功能所提供的大量工具。我们还将了解软件工程在整个过程中的作用。

1.2 数据分析、机器学习、人工智能和商业智能

数据及其重要性在市场上开辟了新的机会,并创造了大量的工作机会。与此同时,机器学习和人工智能是相对较新的领域,因此在工作范围上存在较少的标准化和区分。这导致这些领域的定义和区分不太清晰。我们正在研究这些领域 – 它们在哪些地方有重叠,它们在哪些地方不同,以及一个领域如何赋予另一个领域动力。可以通过下面的图表来可视化(图 1.7):

每个功能相互赋予动力,并相互补充。

图 1.7 各个领域是如何相互关联的,以及它们如何相互依赖的

01_07

数据挖掘和数据工程通过提供分析所需的数据来启动它。它还导出、转换、清理和加载,以便所有相关的功能可以使用。商业智能和可视化使用这些数据生成报告和仪表板。数据分析使用数据生成洞察和趋势。数据科学建立在数据分析、统计学、商业智能、统计学、数据可视化、机器学习和数据挖掘的基础上。机器学习创建统计和数学模型。人工智能进一步推动了能力。

机器学习使用传统编码。编码是用传统语言完成的,因此计算机科学和软件工程的所有逻辑和规则在机器学习中也是有效的。机器学习帮助我们理解我们否则无法理解的数据。在这个方面,它是一个了不起的解决方案。它可以与历史趋势相关联。机器学习最吸引人的优点是其能够处理视频、音频、图像、文本或传感器生成的复杂数据集等非常复杂和高维的数据点。它使我们能够超越表面的看法。现在人工智能可以实现以前被认为完全不可能的壮举。比如自动驾驶汽车、聊天机器人像人类一样对话、语音转文本和翻译成其他选择的语言、自动评分文章、图片标题等等。

现在我们清楚了各个领域之间的差异以及它们之间的相互联系,以及机器学习与传统软件工程的区别。这是我们讨论的主题的基础,进一步探讨了机器学习及其各个组成部分,不同类型的机器学习算法以及它们各自的用例。

1.3 机器学习的要领

想象一下。如果一个孩子要学会如何打开门把手,我们会向她展示几次确切的步骤。孩子试图打开它,但失败了。再试一次,又失败了。但在随后的每次尝试中,孩子都在改进方法。过了一段时间,孩子就能打开门把手了。或者当我们试图学会开车时,我们会犯错误,我们从中学习,我们改进。机器学习的工作方式类似——统计算法查看历史数据并找到模式和见解。该算法揭示了关系和异常、趋势和偏差、相似性和差异,然后将可操作的结果返回给我们。

正式来说,机器学习可以被称为一种计算机算法的分支或研究,它通过历史数据生成见解并帮助做出数据驱动的决策。这些算法基于统计和数学基础,因此具有合理的逻辑解释。机器学习算法需要编码,可以使用任何可用的语言和工具进行,如 Python、R、SPSS、SAS、MATLAB、Weka、Julia、Java 等。它还需要对业务领域有一定的理解。

注意

语言只是达到目的的手段。即使在不同的语言中使用,所有语言对机器学习算法生成的结果都是相似的。

所以每当你在网上购物买裙子时,网站为你推荐配饰,或者你在预订机票时,旅行运营商根据你的需求和计划为你展示一份定制的优惠 – 机器学习都在背后。它学习了你的偏好并将其与你的历史趋势进行比较。它还在寻找你与行为几乎相同的其他客户的相似之处。基于所有这些分析,算法向你做出了智能的推荐。相当迷人,对吧?

许多时候我们会问这个问题,为什么我们需要机器学习,为什么它能超越人类智慧?原因是,我们人类只能同时分析两个或者也许三个维度。但是机器学习算法可以同时处理 50、60 或者也许是 100 个维度。它可以处理任何类型的数据,无论是结构化的还是非结构化的,并且可以帮助自动化任务。因此它生成的模式和见解对于人类大脑来说是相当难以可视化的。

与任何其他项目一样,机器学习需要一个专家团队,他们密切合作,并互补彼此的技能。如下图 1.8 所示,机器学习项目需要以下角色:

  • 业务利益相关者和主题专家(SME): 他们为项目定义业务问题。他们拥有解决方案,对要求有清晰的理解,并且有一个明确的可衡量目标。他们在团队混淆时进行纠正,并作为业务流程和运营的深度了解的专家。他们是营销经理、产品所有者、流程工程师、质量专家、风险分析师、投资组合负责人等。
注意

业务利益相关者从第一天起与团队紧密联系是至关重要的。

  • 运营团队: 这个团队由 Scrum Master、项目经理、业务分析师等组成。该团队的角色可以与典型的项目管理团队相比较,其跟踪进度,维护记录,报告日常活动,并保持整个项目在正确的轨道上。他们创建用户故事,并充当业务团队和数据团队之间的桥梁。
图 1.8 数据科学项目所需的团队及其彼此之间的相互作用

01_08

  • 数据团队: 创建解决方案、进行编码并以模型、仪表板、报告和见解的形式生成输出的核心团队是数据团队。它包括三个主要支柱:数据工程、UI/可视化团队和数据科学团队。他们的职能如下:

  • 数据工程团队负责构建、维护、集成和接入所有数据点。他们定期进行数据刷新,并充当数据的主要保管者。他们使用 ETL、SQL、AWS、Kafka 等。

  • UI/可视化团队构建仪表板、报告、交互式模块和 Web 应用程序。他们使用 SQL 和 Tableau。Qlik、Power BI 等。

  • 数据科学团队负责所有数据分析和模型构建任务。他们发现模式和见解,测试假设并生成最终输出,最终将被所有人消费。最终输出可以是一个机器学习模型,该模型将用于解决业务问题。在不可能使用机器学习模型的情况下,团队可能生成对业务有用的可操作见解。该团队需要 SQL、Python、R、SAS、SPSS 等来完成他们的工作。

我们已经了解了数据科学项目的典型团队结构。我们将不会检查数据科学项目中的广泛步骤。

数据科学项目的运行方式与其他任何项目都一样,有截止日期、阶段、测试、阶段等。原材料是数据,经过各个阶段进行清洗、分析和建模。

我们在下面(图 1.9)看到了一个数据科学项目阶段的示例图。

图 1.9 数据科学项目与其他项目一样,有阶段和截止日期,依赖关系和过程

01_09

项目从项目的业务问题定义开始。业务问题必须简洁、清晰、可衡量和可实现。下表(表 1-2)显示了一个糟糕的业务问题和一个好的业务问题的示例。

表 1.2 如何定义业务问题使其清晰、简洁和可衡量的示例
不明确的业务问题示例 好的业务问题示例
增加产量 优化各种成本项(A、B、C 和 D)并确定最佳组合,以便在接下来的 6 个月内将成本降低 1.2%
降低成本
在一个月内将收入增加 80% 从过程中的各种缺陷因素(X、Y、Z)中,识别出最重要的因素,以便在接下来的 3 个月内将缺陷率降低 1.8%
自动化整个流程

然后我们进入数据发现阶段,在此阶段我们列出所有数据源并将它们托管。识别和访问所有各种数据集,如客户详情、购买历史、社交媒体数据、投资组合等。在此步骤中,确定要使用的数据表,并且大多数情况下,我们会创建一个数据库供我们工作、测试和学习。

我们进行数据预处理。它涉及清理数据,如去除空值、异常值、重复项、垃圾值等。前一步和这一步可能占据项目时间的 60-70%。

在探索性数据分析阶段我们创建一些报告并生成初步见解。这些见解与业务利益相关者讨论,并征求他们的指导进行调整。

数据现在已准备好进行建模。测试了很多版本的解决方案。根据需求选择最佳版本。主要根据准确率和统计指标(如精确率和召回率)来选择模型。我们将在本书的后面章节中探讨选择最佳模型的过程以及精确率和召回率这样的术语。

最终模型被选择,现在我们准备在生产环境中部署模型,它将处理未知数据。

这是一个机器学习项目的大致步骤。和其他项目一样,它有一个代码存储库,最佳实践,编码标准,常见错误,陷阱等等,我们将在整本书中讨论这些内容。

现在我们将转向一个重要的主题,即机器学习算法的类型,我们现在正在讨论它。

1.4 机器学习算法的类型

机器学习模型正在影响决策,并遵循一种统计方法来解决业务问题。它使用历史数据并分析和建模以生成预测算法。可用的历史数据和要解决的业务问题使我们能够将机器学习算法广泛分为四个类别:有监督学习,无监督学习,半监督学习和强化学习,如下图 1.10 所示。现在我们正在详细讨论这四种类型,重点是无监督学习,这也是本书的主题。

图 1.10 机器学习算法可以分为有监督学习算法,无监督学习算法,半监督学习算法和强化学习算法。

01_10

1.4.1 有监督学习

顾名思义,有监督学习算法有一个“指导”或“监督”来指导我们朝着预测未来的业务目标前进。

严格来说,有监督模型是使用输入数据和期望的输出一起来预测未来的统计模型。输出是我们希望预测的值,被称为目标变量,用于进行预测的数据被称为训练数据。目标变量有时被称为标签。数据中存在的各种属性或变量被称为独立变量。历史数据点或训练示例中包含这些独立变量和相应的目标变量。有监督学习算法为未来的未知数据做出预测。解决方案的准确性取决于训练和从标记的历史数据中学到的模式。描述该概念的示例将在下一节中介绍。

监督学习问题被用于需求预测、信用卡欺诈检测、客户流失预测、保费估算等等。它们在零售、电信、银行和金融、航空、保险等行业被广泛使用。

监督学习算法可以进一步分为回归算法和分类算法。我们将首先处理回归问题。

回归算法

回归算法是监督学习算法,即它们需要被预测的目标变量。这些算法用于预测连续变量的值。例如,收入、降雨量、交易数量、生产产量等等。在监督分类问题中,我们预测一个分类变量,比如是否会下雨(是/否)、信用卡交易是否欺诈等等。这是分类和回归问题之间的主要区别。

让我们通过一个例子来理解回归问题。如果我们假设一个人的体重只取决于身高,而不取决于其他参数如性别、种族、饮食等。在这种情况下,我们希望根据身高预测一个人的体重。数据集可能如下所示,对应的数据图会如图 1.11 所示。

回归模型将能够找到数据中的固有模式并拟合描述关系的数学方程。然后它可以将身高作为输入并预测体重。这里身高是自变量,体重是因变量或我们想要预测的标签。

图 1.11 数据和身高与体重之间关系的绘图,用于回归问题

01_11

有许多算法可用于回归问题,主要的算法如下所示:

  1. 线性回归

  2. 决策树

  3. 随机森林

  4. K 近邻

  5. 提升算法

  6. 神经网络

我们可以使用任何算法来解决这个问题。我们将通过使用线性回归来更深入地探索解决这个问题。

线性回归算法通过假设因变量和目标变量之间存在线性关系来建模它们之间的关系。线性回归算法会得出一个问题的数学方程,如方程(1-1)所示。

(方程式 1.1)

权重 = β[0] * 身高 + β[1]

一般来说,线性回归用于拟合描述因变量和自变量之间关系的数学方程,如(1-2)所示。

(方程式 1.2)

Y = β0 + β1 x1 + β2x2 + ….+ε

Y 是我们想要预测的目标变量。

  • x[1] 是第一个自变量

    • x[2] 是第二个自变量

    • ε 是方程中的误差项

  • β[0] 是方程的截距

简单的线性回归问题的可视化如图 1.12 所示。在这里,我们有自变量 x 和因变量 Y,其中 x 是独立变量,Y 是目标变量。线性回归问题的目标是找到最佳拟合线,能够解释数据中存在的随机性。

图 1.12 需要建模的原始数据在左侧。使用回归,识别出最佳拟合线

01_12

这个方程用于对未知数据进行预测。线性回归也有变体,如简单线性回归、多元线性回归、非线性回归等。根据手头的数据,我们选择正确的算法。复杂的数据集可能需要各种变量之间的非线性关系。

我们讨论的下一个回归算法是基于树的解决方案。对于基于树的算法,如决策树、随机森林等,算法将从顶部开始,然后像“if-else”块一样迭代地分裂,以创建节点和子节点,直到达到终端节点。可以通过图 1.13 更好地理解。在决策树图中,我们从顶部开始,根节点,然后分裂直到达到终点,即终端节点。

图 1.13 决策树有一个根节点,在分裂后我们得到一个决策节点,终端节点是最终节点,不能进一步分裂

01_13

决策树非常容易理解和实施,并且训练速度快。它们的可用性在于它们足够直观,适合所有人理解。

还有其他著名的回归算法,如 k 近邻、梯度提升和基于深度学习的解决方案。根据业务问题和相应的准确性,我们更喜欢一个回归算法而不是另一个。

要理解回归使用案例的影响,行业中实施了一些与业务相关的使用案例:

  1. 一个机场运营团队正在评估人员需求,并希望估计预期的乘客流量。这个估计将帮助团队为未来制定计划。它将导致所需资源的优化。回归算法可以帮助预测乘客数量。

  2. 零售商想要了解即将到来的销售季节的预期需求,以便为各种商品规划库存。这将导致成本节约并避免断货。回归算法可以帮助进行这种规划。

  3. 制造厂希望提高现有各种模具和原材料使用的产量。回归解决方案可以建议最佳模具组合,并预测预期产量。

  4. 一家银行向其客户提供信用卡。考虑新客户的信用额度是如何计算的。基于客户的属性,如年龄、职业、收入和以往的交易历史 - 回归算法可以帮助推荐客户级别的信用额度。

  5. 一个保险公司希望利用历史索赔来为其客户制定一个保险费率表。风险可以根据有关驾驶员详细信息、车辆信息等的历史数据来评估。回归肯定可以帮助解决这类问题。

回归问题是监督学习问题的基础,而且在工业界被广泛使用。它们与分类算法一起,是我们正在讨论的大多数预测问题的首选解决方案。

分类算法

简而言之,分类算法用于预测作为因变量的分类变量的值。这个目标变量可以是二元的(是/否,好/坏,欺诈/真实,通过/失败等)或多类的(肯定/否定/中性,是/否/不知道等)。分类算法将通过为目标变量生成概率分数,确定目标事件是否会发生。

在模型已经通过历史数据进行训练之后,分类算法将为未见数据集生成一个概率分数,可用于做出最终决定。根据目标变量中存在的类数,我们的业务决策将有所不同。

让我们看一个分类问题的用例。

想象一下。一个电信运营商正面临着其不断减少的订户基础的问题。现有订户数量正在减少,电信运营商希望制止这种订户流失。为此,设想了一种机器学习模型。

在这种情况下,用于模型构建的历史数据或训练数据可以看起来像下表所示(表 1-3)。这些数据点仅用于说明,不是详尽无遗的。还有其他许多重要变量可用。

表 1.3 电信运营商的结构化数据集示例,显示了多个数据属性
ID 收入(美元) 服务期限(年) 平均费用 月使用量(天) 流失(是/否)
1001 100 1.1 0.10 10 Y
1002 200 4.1 0.09 25 N
1003 300 5.2 0.05 28 N
1004 200 0.9 0.25 11 Y
1005 100 0.5 0.45 12 Y

在上面的示例中,数据集包括订户的过去使用数据。最后一列(流失)显示了该订户是否流失出系统。就像订户#1001 流失了,而 1002 没有。因此,业务问题是,我们希望基于此历史数据构建一个机器学习模型,并预测新的未见客户是否会流失。

这里,“流失”状态(是/否)是目标变量。也被称为因变量。像收入、持续时间、平均成本、月度使用量等其他属性是用来创建机器学习模型的自变量。历史数据称为训练数据。在模型训练后,训练好的监督学习模型将为新客户生成预测概率。

对于分类问题,有很多可用的算法,主要列举如下:

  1. 逻辑回归

  2. 决策树

  3. 随机森林

  4. K 最近邻

  5. 朴素贝叶斯

  6. 支持向量机

  7. 提升算法

  8. 神经网络

我们将讨论一种最流行的分类算法之一,称为逻辑回归。逻辑回归使用 logit 函数来建模分类问题。如果我们正在解决二元分类问题,它将是二元逻辑回归,否则是多元逻辑回归。

与线性回归类似,逻辑回归也拟合一个方程,不同之处在于它使用 S 形函数生成事件发生与否的概率分数。

S 形函数是具有特征“S”形曲线或 S 形曲线的数学函数。S 形函数的数学方程是:

S(x) = 1/(1 + e^x),可以重写为 S(x) = ex/(ex + 1)

逻辑回归使用 S 形函数。逻辑回归问题中使用的方程是:

log (p/1-p) = β[0] + β[1] x[1]

其中 p:事件发生的概率

  • β[0]:截距项

    • β[1]:自变量 x[1] 的系数
  • log(p/1-p)称为 logit,(p/1-p)称为比率

如下图 1.14 所示,如果我们尝试为概率函数拟合线性回归方程,效果不佳。我们想要获得概率分数(即介于 0 和 1 之间的值)。线性回归不仅会返回介于 0 和 1 之间的值,还会返回大于 1 或小于 0 的概率分数。因此,我们有了右侧的 S 形函数,它为我们生成了仅在 0 和 1 之间的概率分数。

图 1.14 线性回归模型无法胜任(左),因此我们有了逻辑回归进行分类。线性回归还可以生成大于 1 或小于 0 的概率分数,这在数学上是不正确的。而 S 形函数仅生成 0 和 1 之间的概率分数。

01_14

逻辑回归算法是最广泛使用的分类问题技术之一。它易于训练和部署,并且通常是我们启动任何监督分类学习项目时的基准算法。

基于树的算法,如决策树和随机森林,也可以用于分类问题。根据需求还可以使用其他算法。

我们简要研究了监督学习算法。我们现在将在下一节讨论无监督学习算法 - 本书的主要主题,然后转移到半监督学习算法。

1.4.2 无监督算法

想象一下,你被给了一些如下图图 1.15 所示的纸标签。任务是使用一些相似性来排列它们。现在有多种方法来解决这个问题。您可以使用颜色,形状或大小。在这里,我们没有任何标签可以指导我们进行这种排列。这是无监督算法的区别。

图 1.15 使用不同参数可以将各种形状聚合在一起的示例

01_15

正式地说,无监督学习仅采用输入数据,然后在其中找到模式,而不引用目标变量。因此,无监督学习算法会根据数据集中的模式的存在或缺失做出反应。

无监督学习因此被用于模式检测,探索数据集中的洞见和了解其结构,分割以及异常检测。

我们可以通过下面的 (图 1.16) 来理解无监督学习算法。左侧的图显示了以向量空间图表示的原始数据点。右边是使用无监督学习算法进行的聚类。

图 1.16 无监督学习算法在左侧的数据中发现模式,并在右侧生成簇。

01_16

无监督算法的用例有:

  1. 一个零售集团希望更好地了解客户。任务是提高客户的粘性,收入,访问次数,购物篮大小等。在这里可以使用无监督学习进行客户细分。根据客户的属性,如收入,访问次数,上次访问日期,加入年龄,人口属性等。细分将导致可以个性化定位的簇。结果将是改善客户体验,增加客户生命周期价值等。

  2. 网络提供商需要创建异常检测系统。历史数据将用作异常数据。无监督学习算法将能够找到模式,并且算法将提供异常值。显著的异常将是需要解决的异常。

  3. 一个医疗产品公司希望查找其患者图像数据中是否存在任何潜在模式。如果有任何模式和因素,这些患者可以得到更好的治疗,也许需要不同的方法。无监督学习可以帮助处理图像数据,从而更好地处理患者。

  4. 数字营销公司希望了解传入客户数据中的“未知因素”,如社交媒体互动、页面点击、评论、星级等。这种理解将有助于改善客户的推荐和整体购买体验。

无监督学习算法在发现模式时提供了灵活性和性能。它们可用于各种数据 - 结构化数据、文本或图像或文本。

无监督学习算法的数量比监督学习少。主要的无监督学习算法有:

  1. 聚类算法

  2. k-means 聚类

  3. 分层聚类

  4. DB Scan 聚类

  5. 光谱聚类

  6. 主成分分析

  7. 奇异值分解

  8. 关联规则

  9. t-SNE(t 分布随机邻域嵌入)

  10. 自动编码器

我们将在接下来的章节中详细介绍所有这些算法。我们将审查数学概念、隐藏过程、Python 实现以及整本书的最佳实践。

让我们通过一个案例研究来理解。

零售商希望深入了解其消费者群体。然后希望通过使用属性(如人物、以前的购买、回应、外部数据等)对整个客户数据集进行分段。

对于用例,在无监督学习项目中遵循的步骤如下图 1.17 所示。

步骤 1:我们通过定义业务问题来启动项目。我们希望更好地了解客户群体。客户分割方法可以是一个好的解决方案。我们想要使用数学 KPI(关键绩效指标)区分的分段。

图 1.17 从数据源到部署就绪的最终解决方案的无监督学习算法步骤

01_17

步骤 2:这是数据发现阶段。识别和访问所有各种数据集,如客户详细信息、购买历史、社交媒体数据、投资组合等。在这一步中确定要使用的数据表。然后所有数据表通常加载到一个共同的数据库中,我们将用来分析、测试和学习。

步骤 3:现在我们可以访问数据了。下一步是清理数据并使其可用。

我们将处理所有空值、NAN、垃圾值、重复项等。

步骤 4:一旦数据清理干净并准备好使用,我们将对其进行探索性数据分析。通常,在探索性分析期间,我们识别模式、循环、异常、最大-最小范围、标准差等。EDA 阶段的输出将是见解和理解。我们还将生成一些图表和图表,如下图 1.18 所示:

图 1.18 数据探索性分析图表示例

01_18

步骤 5:我们现在将开始无监督的方法。我们想要实现聚类方法,因此我们可以尝试几种聚类方法,如 k 均值,层次聚类等。基于他们的各种属性,聚类算法将产生客户的同质化分段。

在案例研究中,我们将使用过去 2-3 年的数据,这是训练数据。由于我们使用的是无监督方法,这里没有目标变量。该算法将根据他们的交易模式、人口统计模式和购买偏好将行为相似的客户段合并起来。在图 1.19 中会呈现如下形式:

图 1.19 聚类算法的输出,在此我们可以使用各种属性对客户进行分段

01_19

步骤 6:我们现在将检查各种算法的表现,换句话说,我们将比较每种算法的准确性。选择的最终聚类算法将产生客户的同质化分段,可以针对并提供定制优惠。

步骤 7:我们将与业务利益相关者讨论结果。有时,利用业务眼光,我们会合并或拆分一些细分市场。

步骤 8:将解决方案部署到生产环境中,我们准备好处理新的未见过的数据集了。

这些是无监督问题中的广泛步骤。算法的创建和选择是一项繁琐的任务。我们将在书中详细研究它。

到目前为止,我们已经讨论了监督和无监督问题。接下来,我们将转向半监督算法,它位于监督和无监督算法的并列位置。

1.4.3 半监督算法

半监督学习是两种方法的中间路径。半监督方法的主要原因是缺乏完整的标记数据集进行训练。

正式地说,半监督方法同时使用监督和无监督方法 - 监督用于对数据点进行分类,无监督用于将它们分组。

在半监督学习中,我们最初使用监督算法对较少数量的标记数据点进行训练。然后,我们使用它来标记或伪标记新的数据点。两个数据集(标记和伪标记)被合并在一起,我们进一步使用这个数据集进行分析。

半监督算法在数据集部分可用的情况下使用,例如医疗行业中的图像。如果我们通过分析患者的图像来创建癌症检测解决方案,可能不会有足够的训练图像样本集。在这种情况下,半监督方法可能会有所帮助。

现在我们将讨论机器学习中的最后一类,称为强化学习。

1.4.4 强化学习

想象一下你正在与计算机下棋。就像这样:

第一轮:你在 5 步后获胜

第二轮:你在 8 步后获胜

第三轮:你在 14 步后获胜

第四轮:你赢了,共进行了 21 步

第五轮:计算机获胜!

这里发生的是,算法根据每次交互进行自我训练,并进行纠正/改进。

形式上,强化学习解决方案是自我维持的解决方案,它们使用一系列试错来进行自我训练。一个序列跟着另一个序列。强化学习的核心是奖励信号。如果动作是积极的,那么奖励就是积极的,表示继续下去。如果动作是消极的,奖励将惩罚该活动。因此,解决方案将始终纠正自己并向前移动,从而通过迭代改进自己。

自动驾驶汽车是强化学习算法的最佳例子。它们会检测何时左转或右转,何时移动以及何时停止。现代视频游戏也使用强化学习算法。强化学习使我们能够突破技术的障碍,并想象以前被认为是不可能的事情。

这样一来,我们就涵盖了不同类型的机器学习算法。它们共同利用数据的真正力量,对我们的生活产生了持久的影响。

但解决方案的核心是技术,我们尚未讨论过。我们现在将转向制作这些解决方案所需的技术堆栈。

1.5 技术工具包

以下工具用于项目的不同方面:

  1. 数据工程:Hadoop,Spark,Scala,Java,C++,SQL,AWS Redshift,Azure

  2. 数据分析:SQL,R,Python,Excel

  3. 机器学习:SQL,R,Python,Excel,Weka,Julia,MATLAB,SPSS,SAS

  4. 可视化:Tableau,Power BI,Qlik,COGNOS

  5. 模型部署:docker,flask,Amazon S3

  6. 云服务:Azure,AWS,GCP

在这本书中,我们将使用 Python 进行工作。建议您在系统上安装最新版本的 Python。建议使用 Python 版本(3.5+)。我们将使用 Jupyter Notebook,因此建议您在系统上安装 Anaconda。

注意

所有代码和数据集将在 GitHub 存储库中签入。您应该复制它们并尝试重现结果。

最常见的问题是:R 和 Python 哪个更好?两者都是出色的语言。两者都被广泛使用。但是在 TensorFlow、Keras 等人工智能库引入后,Python 稍微占优势了一些。

通过这一点,我们结束了对技术的讨论。技术与概念一起使机器学习算法为我们工作。我们将在整本书中探讨所有这些更精细的方面。

祝贺!您已经完成了您迈向学习无监督机器学习技术的第一步。是时候结束并转向摘要了。

1.6 总结

机器学习和人工智能确实是开创性的。它们正在改变我们的出行方式,我们点餐,我们规划,我们购买,我们看医生或订购处方 - 它们无处不在地产生影响。机器学习确实是一种强大的能力,正在为未来铺平道路,并且在模式识别、异常检测、定制和自动化任务方面表现比现有技术栈要好得多。自动驾驶,癌症检测,欺诈识别,人脸识别,图像字幕和聊天机器人是机器学习和人工智能超越传统技术的几个例子。现在是进入该领域的最佳时机。该领域吸引了几乎所有业务功能的投资。该领域为整个光谱创造了大量的就业机会。确实令人难以置信和令人印象深刻!

与此同时,该领域缺乏经过训练的专业人才 - 数据分析师、数据工程师、可视化专家、数据科学家和数据从业者。他们现在都是稀有品种。该领域需要定期培养新人才,他们将成为明天的领袖,并将做出数据驱动的决策。但是我们只是初步了解了数据的力量 - 还有很长的路要走。

在本书的引言章节中,我们向您介绍了机器学习和数据科学的概念。我们比较了各种流程,数据科学项目的步骤以及所需的团队。我们重点考虑了无监督学习算法的类型和各自的用例。

在下一章中,我们将更深入地探讨无监督学习中聚类的概念。我们将讨论所有的数学和统计基础、实用案例研究、Python 实现。第二章涉及更简单的聚类算法 - K-means 聚类、层次聚类和 DBSCAN。在本书的后续章节中,我们将研究更复杂的聚类主题,如 GMM 聚类、时间序列聚类、模糊聚类等。

您现在可以进入问题部分了!

问题

Q1:为什么机器学习如此强大,以至于现在被大量使用?

Q2:机器学习算法有哪些不同类型,它们之间有何不同?

Q3:机器学习项目中的步骤是什么?

Q4:数据工程的作用是什么,为什么它很重要?

Q5:机器学习有哪些可用工具?

第二章:聚类技术

在这第二章中,我们将涵盖以下主题:

  • 行业中的聚类技术及其显著用例

  • 可用的各种聚类算法

  • K 均值、层次聚类和 DBSCAN 聚类

  • 在 Python 中实现算法

  • 聚类分析案例研究

“简单是最终的精妙” – 列奥纳多·达·芬奇

大自然崇尚简单,教导我们走同样的路。大多数时候,我们的决定都是简单的选择。简单的解决方案更容易理解,耗时更少,更容易维护和思考。机器学习世界也不例外。一个优雅的机器学习解决方案不是最复杂的可用算法,而是解决业务问题的一个。一个强大的机器学习解决方案易于理解,并且实施起来实用。聚类解决方案通常更容易被理解。

在上一章中,我们定义了无监督学习,并讨论了可用的各种无监督算法。随着我们在本书中的工作,我们将涵盖每一种算法;在这第二章中,我们将专注于其中的第一个:聚类算法。

我们将首先定义聚类,然后学习不同类型的聚类技术。我们将检查每种算法的数学基础、准确度测量以及优缺点。我们将使用 Python 代码在数据集上实现其中三种算法,以补充理论知识。本章结束时,我们将探讨聚类技术在实际业务场景中的各种用例,为进入实际业务世界做准备。这种技术在整本书中都在使用,我们首先学习概念,然后实现实际代码以提高 Python 技能,并深入研究实际的业务问题。

本章我们将学习基本的聚类算法,包括 K 均值聚类、层次聚类和 DBSCAN 聚类。这些聚类算法通常是我们研究聚类时的起点。在本书的后面章节中,我们将探讨更复杂的算法,如谱聚类、高斯混合模型、时间序列聚类、模糊聚类等。如果你对 K 均值聚类、层次聚类和 DBSCAN 有很好的理解 - 你可以跳到下一章。但建议你读完本章一次 - 你可能会发现一些有用的东西来更新你的概念!

让我们首先了解一下“聚类”是什么意思。祝你在掌握基于无监督学习的聚类技术的旅程中一切顺利!

2.1 技术工具箱

本章我们将使用 Python 的 3.6+版本。我们期望你对 Python 和代码执行有基本的了解。建议你复习面向对象编程和 Python 概念。

本书始终使用 Jupyter 笔记本执行代码。Jupyter 在执行和调试方面提供了灵活性,因此被广泛使用。它非常用户友好,可以在任何平台或操作系统上使用。所以,无论你使用 Windows、Mac OS 还是 Linux,Jupyter 都可以正常工作。

所有数据集和代码文件都已经检入到 Github 代码库中(github.com/vverdhan/UnsupervisedLearningWithPython/tree/main/Chapter2)。你需要安装以下 Python 库来执行代码 – numpy, pandas, matplotlib, scipy, sklearn。CPU 足够执行,但如果遇到一些计算延迟,并且想加快执行速度,切换到 GPU 或 Google 协作平台(colab)。Google 协作平台为机器学习解决方案提供免费计算。建议你了解更多关于谷歌协作平台以及如何用它来训练机器学习算法的内容。

现在,我们将在下一节开始聚类。

2.2 聚类

想象一下这样的情景。一群孩子被要求将房间中的物品分成不同的部分。每个孩子都可以使用自己的逻辑。有人可能根据重量来分类物品,其他孩子可能使用材料,而另一些人可能同时使用重量、材料和颜色。排列组合很多,取决于用于分组的 参数。在这里,一个孩子根据所选择的逻辑对物品进行分组或 clustering

正式来说,clustering 用于将具有相似属性的对象分组到同一段中,具有不同属性的对象分到不同段中。结果的聚类在自身之间具有相似性,而它们在彼此之间更加异质。我们可以通过下图(图 2.1)更好地理解它。

图 2.1 聚类是将具有相似属性的对象分组到逻辑段中。分组是基于不同观测共享的相似性特征,因此它们被分组到一组中。在这里,我们使用形状作为聚类的变量。

02_01

聚类分析不是单个算法或解决方案,而是用于解决实际业务场景中的问题的机制。它们是无监督学习下的一类算法。这是一个迭代过程,遵循逻辑方法和定性业务输入。它导致对数据的深入理解、其中的逻辑模式、模式发现和信息检索。作为一种无监督方法,聚类不需要目标变量。它通过分析数据集中的潜在模式来进行分段,这些模式通常是多维的,因此难以用传统方法分析。

理想情况下,我们希望聚类算法具有以下特点:

  • 输出的聚类应该易于解释和理解,可用且应该具有商业意义。聚类数目不应该太少或太多。例如,如果我们只有 2 个聚类,分割就不够清晰和明确。或者如果我们有 20 个聚类,处理将变得很有挑战性。

    • 算法不应过于敏感于异常值、缺失值或数据集中的噪声。通常来说,一个好的解决方案应该能够处理多种数据类型。

    • 一个好的解决方案对于用于聚类目的的输入参数需要较少的领域理解。这允许具有较少领域理解的分析师训练聚类算法。

    • 算法应该独立于输入参数的顺序。如果顺序很重要,那么聚类就会对顺序产生偏见,因此会给过程增加更多的混乱。

  • 随着我们持续生成新的数据集,聚类必须能够适应新的训练示例,并且不应该是一个耗时的过程。

众所周知,聚类输出将取决于用于分组的属性。在下面所示的(图 2.2)中,对于相同的数据集,可以有两个逻辑分组,而且两者都是同样有效的。因此,明智地选择用于聚类的属性或变量通常取决于手头的业务问题。

图 2.2 使用不同的属性进行聚类会得到相同数据集的不同聚类结果。因此,选择正确的属性集定义我们将实现的最终结果。

02_02

除了用于聚类的属性之外,所使用的实际技术也会产生很大影响。研究人员已经开发了相当多(事实上超过 80 个)的聚类技术。对于感兴趣的观众,我们在附录中提供了所有聚类算法的列表。我们将在下一节开始学习不同的聚类技术。

2.2.1 聚类技术

聚类可以通过各种算法实现。这些算法使用不同的方法来定义对象之间的相似性。例如,基于密度的聚类,基于中心点的聚类,基于分布的方法等。甚至在衡量对象之间距离时,也有多种技术,如欧氏距离,曼哈顿距离等。选择距离测量方法会导致不同的相似度分数。我们将在后面的部分研究这些相似度测量参数。

在高层次上,我们可以确定两种广义的聚类方法:硬聚类软聚类(见图 2.3)。当决定非常明确一个对象属于某个类或簇时,称为硬聚类。在硬聚类中,算法非常确定对象的类。另一方面,软聚类为对象被归属于某个特定簇分配了可能性得分。因此,软聚类方法不会将对象放入一个簇中,而是一个对象可以属于多个簇。有时软聚类也被称为模糊聚类。

图 2.3 硬聚类具有明显的簇,而在软聚类的情况下,数据点可以属于多个簇,我们得到数据点属于簇的可能性分数。左边的第一个图是硬聚类,右边的图是软聚类。

02_03

我们可以如下图所示广义地将聚类技术进行分类:

表 2.1 聚类方法的分类、简要描述和示例
序号 聚类方法 方法的简要描述 示例
1 基于质心的聚类 到指定质心的距离 k-means
2 基于密度的模型 数据点在向量空间的密集区域内连接 DBSCAN, OPTICS
3 基于连接性的聚类 距离连接性是行动方式 分层聚类, BIRCH
4 分布模型 建模基于统计分布 高斯混合模型
5 深度学习模型 无监督的基于神经网络的 自组织映射

表 2.1 中描述的方法并不是唯一可用的方法。我们可以有基于图的模型、重叠的聚类、子空间模型等。

通常,工业中使用的六种流行的聚类算法如下:

  1. K 均值聚类(带有诸如 k-中值、k-中心点之类的变种)

  2. 凝聚式聚类或者分层聚类

  3. DBSCAN (基于密度的空间应用聚类)

  4. 光谱聚类

  5. 高斯混合模型或 GMM

  6. BIRCH(平衡迭代减少 & 使用层次聚类)

还有多种其他可用的算法,如 Chinese whisper,canopy 聚类,SUBCLU,FLAME 等。我们在本章中学习前三种算法以及书中后续章节中的一些高级算法。

  快速测验-回答这些问题以检查你的理解.. 答案在书的末尾

1.   DBSCAN 聚类是基于质心的聚类技术。TRUE or FALSE.

2.   聚类是一种监督学习技术,具有固定的目标变量。TRUE or FALSE.

3.   硬聚类和软聚类有什么区别?

在下一节中,我们将开始学习基于质心的聚类方法,其中我们将学习 k-means 聚类。

2.3 基于质心的聚类

基于重心的算法根据对象到聚类重心的距离来衡量它们的相似性。距离是针对聚类的特定数据点到重心的距离来衡量的。距离越小,相似度越高。我们可以通过接下来的图 2.4 来理解这个概念。右侧的图表示了每个聚类组的相应重心。

注意

要更清楚地了解重心和其他数学概念,请参考末尾的附录。

图 2.4 基于重心的聚类方法为各自的聚类创建一个重心,并根据到重心的距离来衡量相似度。在这种情况下,我们有 5 个重心。因此,这里有五个不同的聚类。

02_03a

在聚类中,距离起着核心作用,因为许多算法将其用作度量相似性的度量。在基于重心的聚类中,距离是在点之间和重心之间进行衡量的。有多种方法来度量距离。最常用的是下面列出的:

  1. 欧几里德距离:它是最常用的距离度量。它表示空间中两点之间的直线距离,是两点之间的最短路径。如果我们想计算点 P[1]和 P[2]之间的距离,其中 P[1]的坐标为(x[1], y[1]),P[2]的坐标为(x[2], y[2]),那么欧几里德距离由下面的(方程式 2.1)给出。几何表示如图 2.5 所示
(方程式 2.1)

距离 = √(y[2] – y[1])² + (x[2] – x[1])²

如果你想复习几何概念(坐标几何),请参考附录。
  1. 切比雪夫距离:以俄罗斯数学家帕夫努蒂·切比雪夫的名字命名,它被定义为两点之间的距离,以便它们的差异在任何坐标维度上的最大值。数学上,我们可以在下面的(方程式 2.2)中表示切比雪夫距离,并在(图 2.5)中显示:
(方程式 2.2)

距离[切比雪夫] = max (|y[2] – y[1]|, |x[2] – x[1]|)

  1. 曼哈顿距离:曼哈顿距离是一个非常简单的概念。它只是计算网格路径上两点之间的距离,因此距离是沿着右角轴测量的。因此,有时它也被称为城市街区距离或出租车度量。数学上,我们可以在(方程式 2.3)中表示曼哈顿距离,并如图 2.5 所示:
(方程式 2.3)

距离[曼哈顿] = (|y[2] – y[1]| + |x[2] – x[1]|)

曼哈顿距离以 L1 范数形式而欧几里德距离以 L2 范数形式。您可以参考附录详细学习 L1 范数和 L2 范数。如果数据集中有大量的维度或变量,曼哈顿距离比欧几里德距离更好。这是由于本书第三章将要学习的维度诅咒

  1. 余弦距离:余弦距离用于测量向量空间图中两点之间的相似性。在三角学中,0 度的余弦是 1,90 度的余弦是 0。因此,如果两个点彼此相似,则它们之间的角度将为零,因此余弦将为 1,这意味着这两个点彼此非常相似,反之亦然。从数学上讲,余弦相似性可以表示为(公式 2.4)。如果我们想要测量向量 A 和 B 之间的余弦值,则余弦是
(公式 2.4)

距离 [余弦] = (A . B) / (||A|| ||B||)

如果您想刷新向量分解的概念,请参考附录。
图 2.5 欧氏距离、曼哈顿距离、切比雪夫距离和余弦相似度是主要使用的距离度量。请注意,使用这些度量标准时,两个点之间的距离是不同的。在欧氏距离中,直接距离被测量为左侧第一张图所示的两点之间的距离。

02_03b

还有其他距离度量标准,如汉明距离、Jaccard 距离等。在我们实际的业务问题中,通常使用欧氏距离,但有时也会使用其他距离度量标准。

注意

上述距离度量标准对其他聚类算法也适用。建议您使用本书中的 Python 代码测试不同的距离度量标准,并比较性能。

现在我们已经了解了各种距离度量标准,我们将继续学习 k-means 聚类,这是最广泛使用的算法。

2.3.1 K-means 聚类

k-means 聚类是一种简单直接的方法。它可以说是最广泛使用的聚类方法,用于分段数据点并创建非重叠聚类。我们必须指定我们希望创建的聚类数“k”作为输入,算法将将每个观察结果关联到 k 个聚类中的一个。

注意

有时人们会将 k-means 聚类与 k 最近邻分类器(knn)混淆。虽然两者之间存在一定的关系,但 knn 用于分类和回归问题。

这是一种相当优雅的方法,它从一些初始的聚类中心开始,然后迭代地将每个观察结果分配给最接近的中心。在这个过程中,中心点被重新计算为聚类中的点的平均值。让我们通过下面的图表(图 2.6)逐步学习所使用的方法。为了简单起见,我们假设数据集中有三个聚类。

步骤 1:让我们假设我们有如下所示的所有数据点。

图 2.6 步骤 1 代表原始数据集。在步骤 2 中,算法初始化三个随机质心,因为我们已经给定了三个聚类的输入数。在步骤 3 中,质心的所有相邻点都被分配到同一个聚类中。

02_04

第二步:初始时随机初始化了三个中心,如三个方块所示-蓝色、红色和绿色。这个三是我们希望最后的簇的数量。

第三步:计算所有数据点到中心的距离,并将点分配给最近的中心。注意,由于它们最接近相应的中心,点的颜色变为蓝色、红色和绿色。

第四步:在这一步中,重新调整了三个中心。中心被重新计算为该簇中点的平均值,如图 2.7 所示。我们可以看到,在第四步中,与第三步相比,三个方块的位置发生了变化。

图 2.7 是在第 4 步重新计算质心。在第 5 步中,数据点再次被重新分配新中心。在第 6 步中,根据新的计算结果,质心再次进行调整。

02_05

第五步:再次重新计算所有数据点到新中心的距离,并将点重新分配给最近的中心。请注意,在这一步中,两个蓝色数据点变成了红色,而一个红点变成了绿色。

第六步:中心再次进行调整,与第四步类似。

图 2.8 是重新计算质心,并且这个过程一直持续到无法进一步改善聚类为止。然后过程停止,如第 8 步所示

02_06

第七步:数据点再次被分配到新的簇中,如前图(图 2.8)所示。

第八步:该过程将继续,直到达到收敛。换句话说,该过程将继续,直到不再重新分配数据点。因此,我们无法进一步改进聚类,已实现最终聚类。

k-means 聚类的目标是确保簇内变化尽可能小,而簇之间的差异尽可能大。换句话说,同一簇的成员彼此最相似,而不同簇的成员则不相似。一旦结果不再改变,我们可以得出结论,已达到局部最优,聚类可以停止。因此,最终的簇在内部是同质的,而在彼此之间是异质的。

有两点需要注意:

  1. 由于 k-means 聚类是随机初始化中心点,因此它只能找到局部最优解,而非全局最优解。因此,建议多次迭代解决方案,并从所有结果中选择最佳输出。迭代意味着多次重复该过程,因为在每次迭代中,随机选择的质心将不同。

  2. 我们必须输入我们希望的最终簇数“k”,它会显著改变输出。与数据规模相比,如果 k 值非常小,结果将是多余的簇,因为没有任何用处。换句话说,如果相对于庞大的数据具有非常小的 k 值,具有不同特征的数据点将被混合在几个群中。具有非常高的 k 值将创建微不同的簇。此外,具有非常高数量的簇将难以在长期内管理和更新。让我们通过一个例子来研究。如果一个电信运营商有 100 万订阅者,那么如果我们将簇数设为 2 或 3,得到的簇的规模将非常大。它还可能导致将不同的客户分类到相同的段中。另一方面,如果我们将簇数设为 50 或 60,由于簇数庞大,输出变得难以管理、分析和维护。

使用不同的“k”值,我们会得到不同的结果,因此我们需要了解如何为数据集选择最佳簇数。现在,让我们研究如何测量聚类解决方案的准确性的过程。

2.3.2 测量聚类的准确性

聚类的一个目标是找到最干净的簇。理论上(虽然不理想),如果我们有与观察数量相同的簇数,结果将完全准确。换句话说,如果有 100 万客户,最纯净的聚类将有 100 万个簇 - 每个客户在一个单独的簇中。但这并不是最佳方法,也不是一种实用的解决方案。聚类旨在在一个簇中创建相似观察结果的组,并且我们使用相同的原理来衡量解决方案的准确性。

  1. 簇内平方和(WCSS)或聚合:这个指标衡量数据点相对于它们距离簇质心的距离的变异性。这个指标是每个数据点距离簇的质心的平均距离,对每个数据点重复。如果值太大,表明数据扩散很大,而较小的值表示数据点非常相似和均匀,因此簇是紧凑的。

有时,这个簇内距离也被称为该簇的惯性。它简单地是所有距离的总和。惯性值越低,簇就越好。

图 2.9 簇内距离与簇间距离 - 两者都用于衡量最终簇的纯度和聚类解决方案的性能

02_07

  1. 聚类间平方和:此度量用于衡量所有聚类质心之间的距离。为了得到它,我们测量所有聚类的质心之间的距离,并将其除以聚类的数量以获得平均值。它越大,聚类越好,表明聚类是异质的,并且彼此可以区分,正如我们在(图 2.9)中所表示的那样。

  2. 轮廓系数是衡量聚类成功的指标之一。它的取值范围从-1 到+1,数值越高越好。它衡量了数据点与其所属聚类中其他数据点相似程度,与其他聚类相比。作为第一步,对于每个观察值——我们计算与同一聚类中所有数据点的平均距离,我们称之为 x[i]。然后我们计算与最近聚类中所有数据点的平均距离,我们称之为 y[i]。然后我们通过下面的方程(方程 2.5)计算系数

(方程 2.5)

轮廓系数 = (y[i] - x[i])/ max(y[i],x[i])

如果系数的值为-1,则意味着观察点位于错误的聚类中。

如果为 0,则该观察点与相邻聚类非常接近。

如果系数的值为+1,则意味着该观察点与相邻聚类之间存在距离。

因此,我们期望获得系数的最高值以获得良好的聚类解决方案。

  1. Dunn 指数也可用于衡量聚类的效果。它使用了点 2 和点 3 中定义的聚类间距离和聚类内距离测量,并由下面的方程(方程 2.6)给出
(方程 2.6)

Dunn 指数 = min(聚类间距离)/max(聚类内距离)

显然,我们会努力最大化 Dunn 指数的值。为了实现这一点,分子应尽可能大,意味着聚类之间相距较远,而分母应尽可能低,表明聚类非常健壮且紧密排列。

现在我们已经检验了衡量算法性能的方法。我们现在将转向寻找 k-means 聚类的最佳“k”值。

2.3.3 寻找最佳的“k”值

选择最优的聚类数量并不容易。正如我们之前所说,最好的聚类是当聚类数量等于观察数量时——但是正如我们在上一节中所学到的,这在实际中是不可能的。但是我们必须将聚类数量“k”作为算法的输入提供。

图 2.10 肘方法寻找最优聚类数量。红色圆圈表示转折点。但最终的聚类数量取决于业务逻辑,通常根据业务知识合并/拆分聚类。维护聚类的便利性在其中也扮演了至关重要的角色

02_08

或许找到“k”的最优值最广泛使用的方法是Elbow Method。在这种方法中,我们计算不同“k”值的簇内平方和或 WCSS。该过程与上一节中讨论的相同。然后,将 WCSS 绘制在图表上,针对不同的“k”值。在我们观察到一个弯曲或肘部的地方,如(图 2.10)所示,它就是数据集的最优簇数。注意(图 2.10)中描绘的锋利边缘。

测验 - 回答这些问题来检查你的理解。书的结尾有答案。

  1. K-means 聚类不需要作为输入的簇数- 真 或 假

  2. Knn 和 k-means 聚类是一回事 - 真 或 假

  3. 描述一种可能的找到最优“k”值的过程

但这并不意味着这是我们建议用于业务问题的最终簇数。根据落入每个簇的观测数量,几个簇可能被合并或分解成子簇。我们还考虑创建簇所需的计算成本。簇的数量越多,计算成本和所需时间就越大。

我们也可以使用前面讨论过的 Silhouette Coefficient 找到最优的簇数。

注意:

探讨合并几个簇或拆分几个簇的业务逻辑是至关重要的。最终,解决方案必须在实际的业务场景中实施。

通过这个,我们已经研究了 k-means 聚类的点点滴滴 - 数学概念和过程,各种距离度量以及确定最佳 k 值。现在我们将研究 k-means 算法为我们提供的优势。

2.3.4 k-means 聚类的优缺点

k-means 算法是一个非常流行和广泛实施的聚类解决方案。该解决方案提供以下优势:

  • 与其他算法相比,它简单易懂且相对容易实现。距离测量计算使得即使是非统计背景的用户也很容易理解。

    • 如果维度的数量很大,k-means 算法比其他聚类算法更快,创建的簇更紧凑。因此,如果维度的数量相当大,则更倾向于使用它。

    • 它很快适应新的观察结果,并且能够非常好地概括各种形状和大小的簇。

  • 解决方案通过一系列重新计算的迭代产生结果。大多数情况下使用欧氏距离度量,这使得计算成本较低。它还确保算法一定会收敛并产生结果。

K-means 在现实生活中的业务问题中被广泛使用。尽管 k-means 聚类有明显的优点,但我们确实面临着算法的某些挑战:

  • 选择最佳聚类数目并不容易。我们必须将其作为输入提供。使用不同的“k”值,结果会完全不同。选择最佳“k”值的过程在上一节中已经探讨过。

    • 解决方案取决于质心的初始值。由于质心是随机初始化的,因此每次迭代的输出都将不同。因此,建议运行多个解决方案的版本并选择最佳的一个。

    • 该算法对异常值非常敏感。它们可能会破坏最终结果,因此在开始聚类之前,我们必须处理异常值。我们还可以实现 k 均值算法的其他变体,如k-modes聚类,以应对异常值的问题。我们将在后续章节讨论处理异常值的方法。

  • 由于 k 均值聚类的基本原理是计算距离,因此该解决方案不直接适用于分类变量。换句话说,我们不能直接使用分类变量,因为我们可以计算数字值之间的距离,但不能对分类变量进行数学计算。为了解决这个问题,我们可以使用独热编码将分类变量转换为数字变量,这是我们在本章末尾讨论的内容之一。

尽管存在这些问题,k 均值聚类是最常用的聚类解决方案之一,因其简单性和易于实现。还有一些不同版本的 k 均值算法,如 k-medoids、k-中位数等,有时用于解决所面临的问题。

  1. 如其名,k-中位数聚类是基于数据集中位数而不是 k 均值中心的。这增加了计算时间,因为只有在数据排序之后才能找到中位数。但与此同时,k 均值对异常值很敏感,而 k 中位数对它们的影响较小。

  2. 接下来,我们有k-medoids 聚类作为 k 均值算法的变体之一。Medoids 与均值类似,只是它们始终来自同一数据集,并且在难以获得均值的情况下实施,比如图像。Medoid 可以被认为是簇中最核心的点,与簇中的所有其他成员最不相似。K-medoids 选择实际观测值作为中心,而不是 k 均值,其中质心甚至可能不是数据的一部分。与 k 均值聚类算法相比,它对异常值的敏感性较低。

还有其他版本,如 kmeans++、小批量 k 均值等。一般来说,在工业界,大多数聚类解决方案都使用 k 均值。如果结果不理想或计算时间太长,您可以探索其他选项,如 kmeans++、小批量 k 均值等。此外,使用不同的距离测量指标可能会为 k 均值算法产生不同的结果。

本节结束了我们对 k-means 聚类算法的讨论。是时候进入实验室,开发真正的 Python 代码了!

2.3.5 使用 Python 实现 k-means 聚类

我们现在将为 k-means 聚类创建一个 Python 解决方案。在这种情况下,我们使用了链接中的数据集

github.com/vverdhan/UnsupervisedLearningWithPython/tree/main/Chapter2

这个数据集包含了四种车型的特征信息。基于车辆的特征,我们将把它们分成不同的群。

第 1 步: 将库和数据集导入到一个数据框中。在这里,vehicles.csv 是输入数据文件。如果数据文件不在与 Jupyter 笔记本相同的文件夹中,您需要提供文件的完整路径。Dropna 用于删除可能存在的缺失值。

import pandas as pd
vehicle_df = pd.read_csv('vehicle.csv').dropna()

第 2 步: 对数据进行一些初始检查,比如形状、信息、前五行、类别分布等。这是为了确保我们已经加载了完整的数据集,并且在加载数据集时没有损坏。Shape命令将给出数据中的行数和列数,info将描述所有变量及其类型,head将显示前 5 行。value_counts显示class变量的分布。换句话说,value_counts返回唯一值的计数。

vehicle_df.shape
vehicle_df.info()
vehicle_df.head()
pd.value_counts(vehicle_df['class'])

第 3 步: 让我们为变量“class”生成两个图表。数据集中的汽车示例更多,而对于公共汽车和货车,数据是平衡的。我们使用 matplotlib 库来绘制这些图表。图表的输出如下所示。

import matplotlib.pyplot as plt
%matplotlib inline
pd.value_counts(vehicle_df["class"]).plot(kind='bar')
pd.value_counts(vehicle_df['class']).hist(bins=300)

02_08a

第 4 步: 现在我们将检查数据集中是否有任何缺失的数据点。我们的数据集中没有缺失的数据点,因为我们已经处理过了。

vehicle_df.isna().sum()
注意

我们将在后面的章节中讨论处理缺失值的方法,因为删除缺失值通常不是最好的方法。

第 5 步: 现在我们将对数据集进行标准化。对于聚类来说,标准化数据集是一个很好的实践。这是很重要的,因为不同的维度可能处于不同的尺度,如果某个维度的值自然上比其他维度的值要大得多,那么一个维度可能会在距离计算中占据主导地位。下面使用zscoreStandardScaler()函数来实现。请参考书的附录,了解zscoreStandardScaler()函数之间的区别。

vehicle_df_1 = vehicle_df.drop('class', axis=1)
from scipy.stats import zscore
vehicle_df_1_z = vehicle_df_1.apply(zscore)
from sklearn.preprocessing import StandardScaler
import numpy as np
sc = StandardScaler()
X_standard = sc.fit_transform(vehicle_df_1)

第 6 步: 我们现在要快速查看数据集,生成一个散点图。该图显示了我们在上一步中创建的X_standard的所有数据点的分布。

plt.scatter(X_standard[:,0], X_standard[:,1])
plt.show()

02_08b

步骤 7: 我们现在将执行 k-means 聚类。首先,我们必须使用肘部法选择最佳聚类数。从 sklearn 库中,我们导入 KMeans。在一个 for 循环中,我们对聚类值从 1 到 10 进行迭代。换句话说,算法将创建 1、2、3、4 到 10 个聚类,然后生成结果供我们选择最优 k 值。

在下面的代码片段中,模型对象包含了在上一步生成的 X_standard 上适配的 KMeans 算法的输出。这里,欧几里得距离被用作距离度量。

from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist
clusters=range(1,10)
meanDistortions=[]
for k in clusters:
    model=KMeans(n_clusters=k)
    model.fit(X_standard)
    prediction=model.predict(X_standard)
    meanDistortions.append(sum(np.min(cdist(X_standard, model.cluster_centers_, 'euclidean'), axis=1)) / X_standard
                           .shape[0])
plt.plot(clusters, meanDistortions, 'bx-')
plt.xlabel('k')
plt.ylabel('Average distortion')
plt.title('Selecting k with the Elbow Method')

02_08c

步骤 8: 正如我们所观察到的,最佳聚类数为 3. 这是一个尖锐转折点,在图表中清晰可见。我们将使用 3 个聚类数进行 k-means 聚类。尽管这里的数字 3 没有特别之处,但它最适合这个数据集,我们也可以使用 4 或 5 个聚类数。random_state 是一个用于确定质心初始化的参数。我们将其设置为一个值以使随机性变得确定性。

kmeans = KMeans(n_clusters=3, n_init = 15, random_state=2345) 
kmeans.fit(X_standard)

步骤 9: 获取聚类的质心

centroids = kmeans.cluster_centers_
centroids

步骤 10: 现在我们将使用质心,以便它们可以按列进行分析。

centroid_df = pd.DataFrame(centroids, columns = list(X_standard) )

步骤 11: 我们现在将创建一个仅用于创建标签的 dataframe,然后将其转换为分类变量。

dataframe_labels = pd.DataFrame(kmeans.labels_ , columns = list(['labels']))
dataframe_labels['labels'] = dataframe_labels['labels'].astype('category')

步骤 12: 在这一步中,我们将两个 dataframes 进行连接

dataframe_labeled = vehicle_df_1.join(dataframe_labels)

步骤 13: 执行 group by 操作以创建用于分析的数据框

dataframe_analysis = (dataframe_labeled.groupby(['labels'] , axis=0)).head(1234)
dataframe_labeled['labels'].value_counts()  

步骤 14: 现在,我们将为我们定义的聚类创建可视化。这是使用 mpl_toolkits 库完成的。逻辑很容易理解。数据点根据相应的标签着色。其余的步骤与调整标签、标题、刻度等有关的细节。由于在绘图中不可能绘制所有的 18 个变量,我们选择了 3 个变量来显示在图中。

from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(8, 6))
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=20, azim=60)
kmeans.fit(vehicle_df_1_z)
labels = kmeans.labels_
ax.scatter(vehicle_df_1_z.iloc[:, 0], vehicle_df_1_z.iloc[:, 1], vehicle_df_1_z.iloc[:, 3],c=labels.astype(np.float), edgecolor='k')
ax.w_xaxis.set_ticklabels([])
ax.w_yaxis.set_ticklabels([])
ax.w_zaxis.set_ticklabels([])
ax.set_xlabel('Length')
ax.set_ylabel('Height')
ax.set_zlabel('Weight')
ax.set_title('3D plot of KMeans Clustering on vehicles dataset')

02_08d

我们也可以用多个其他值对上述代码进行测试。我们已经创建了使用不同值的代码。出于空间考虑,我们将不同值的测试代码放在了 github 位置。

在上面的例子中,我们首先对数据集进行了小型的探索性分析。

注意

探索性数据分析(EDA)是实现稳健机器学习解决方案和成功项目的关键。在随后的章节中,我们将为数据集创建详细的 EDA。

接下来是确定最佳聚类数,本例中为三。然后我们实施了 k-means 聚类。您应该迭代不同的初始化 k-means 解决方案并比较结果,迭代不同的 k 值,并可视化以分析数据点的移动。在本章后面,我们将使用 Python 创建层次聚类的相同数据集。

基于质心的聚类是最推荐的解决方案之一,因为其逻辑较少,易于实现,灵活性高且易于维护。每当我们需要聚类作为解决方案时,大多数情况下我们都会从创建一个作为基准的 k 均值聚类解决方案开始。该算法非常受欢迎,通常是聚类的首选解决方案之一。然后我们会测试并迭代其他算法。

这标志着对基于质心的聚类算法的讨论结束。我们现在将继续前进,讨论连接性解决方案,并在下一节中讨论层次聚类。

2.4 基于连接性的聚类

“物以类聚”是连接性聚类中遵循的原则。核心概念是-彼此连接的对象相似。因此,根据这些对象之间的连接性,它们被组合成簇。图 2.11 中展示了这种表示的一个示例,我们可以迭代地对观察进行分组。例如,我们从所有事物开始,分成生物和非生物等等。这种表示最好使用右侧的图表展示,称为Dendrogram

图 2.11 层次聚类利用迭代地对相似对象进行分组。右侧是聚类的可视化表示,称为树状图。

02_09

由于存在树状结构,连接性聚类有时被称为层次聚类。

层次聚类很好地符合人类直觉,因此我们容易理解它。与 k 均值聚类不同,在层次聚类中,我们不必输入最终聚类的数量,但该方法确实需要一个终止条件,即聚类应何时停止。同时,层次聚类不建议聚类的最佳数量。从生成的层次结构/树状图中,我们必须自己选择最佳的聚类数。在接下来的部分中,当我们为其创建 Python 代码时,我们将更多地探讨它。

层次聚类可以通过图 2.12 来理解,如下所示。这里第一个节点是根节点,然后迭代地分裂成节点和子节点。每当一个节点不能再进一步分裂时,它被称为终端节点或叶子

图 2.12 层次聚类具有一个根节点,分裂为节点和子节点。不能再进一步分裂的节点称为叶子。在自底向上的方法中,将合并叶子节点

02_10

由于将观察结果合并成簇的过程或逻辑不止一个,我们可以生成大量的树状图,如下所示的(Equation 2.7):

(Equation 2.7)

树状图数量 = (2n-3)!/[2^((n-2)) (n-2)!]

其中 n 是观察数或叶子数。所以如果我们只有 2 个观察,我们只能有 1 个树状图。如果有 5 个观察,我们可以有 105 个树状图。因此,根据观察数,我们可以生成大量的树状图。

根据用于创建观察值分组的过程,层次聚类可以进一步分类,我们将在下面探讨这一点。

2.4.1 层次聚类的类型

基于分组策略,层次聚类可以细分为两种类型:聚合聚类和分裂聚类。

序号 聚合方法 分裂方法
1 自底向上方法 自顶向下方法
2 每次观察都会创建自己的群集,然后随着算法的进行而进行合并 我们从一个群集开始,然后观察被迭代地分割以创建类似树状的结构
3 采用贪婪方法进行合并(下面描述了贪婪方法) 采用贪婪方法进行分割
4 观察会找到最佳配对进行合并,当所有观察都与彼此合并时,流程完成 一开始会取全部观察,然后根据分裂条件进行分割,直到所有观察都被用完或达到终止条件
图 2.13 中层次聚类的步骤。从左到右是聚合聚类(节点的分裂),从右到左是分裂聚类(节点的合并)

02_11

让我们先探讨贪婪方法的含义。贪婪方法或贪婪算法是指在每一步都会做出最佳选择,而不考虑对未来状态的影响。换句话说,我们活在当下,并从当下可用的选择中选择最佳选项。当前选择与未来选择无关,算法会在以后解决子问题。贪婪方法可能不会提供最优解,但通常在合理时间内会提供接近最优解的局部最优解。层次聚类在合并或分割节点时遵循这种贪婪方法。

我们现在将审查层次聚类方法中的步骤:

第一步: 如上图(图 2.13)所示,假设我们的数据集中有五个观察值– 1, 2, 3, 4 和 5。

第二步: 在这一步中,观察 1 和 2 被分为一组,4 和 5 被合并成一组。3 没有被合并到任何一组中。

第三步: 现在,在这一步中,我们将前一步中 4,5 的输出和观察 3 分为一个群集。

第四步: 步骤 3 的输出与 1,2 的输出结合成为一个单一的群集。

在这种方法中,从左到右,我们有一种自下而上的方法,而从右到左则是一种自上而下的方法。在自下而上的方法中,我们正在合并观测结果,而在自上而下的方法中,我们正在分割观测结果。我们可以同时使用自下而上或自上而下的方法进行层次聚类。分割性聚类是一种详尽的方法,有时可能比其他方法花费更多时间。

与 k-means 聚类类似,在这里用于测量距离的距离度量标准起着重要作用。我们知道并了解如何测量数据点之间的距离,但是有多种方法来定义该距离,我们现在正在研究这些方法。

2.4.2 距离测量的链接标准

我们知道我们可以使用欧氏距离、曼哈顿距离或切比雪夫距离等距离度量标准来衡量两个观测之间的距离。同时,我们可以采用各种方法来定义该距离。根据这个输入标准,得到的聚类结果将不同。定义距离度量标准的各种方法包括:

  • 最近邻或单链接 使用不同聚类中两个最近点之间的距离。计算不同聚类中最近邻的距离,并用于确定下一个分裂/合并。通过对所有成对进行穷举搜索来完成。

    • 最远邻或完全链接 是最近邻方法的相反。在这里,我们不是考虑最近邻,而是专注于不同聚类中的最远邻。换句话说,我们确定聚类之间的距离是通过两个对象之间的最大距离计算的。

    • 群体平均链接 计算两个不同聚类中所有可能的对象对之间距离的平均值。

  • Ward 链接 方法旨在最小化合并为一体的聚类的方差。

在我们为层次聚类开发实际代码时,我们可以使用这些距离度量的选项,并比较准确度以确定数据集的最佳距离度量。在算法训练期间,算法将合并最小化所选择的链接标准的观测。

注意

算法的这些输入被称为超参数。这些是我们向算法提供的参数,以根据我们的要求生成结果。超参数的一个例子是 k-means 聚类中的“k”。

我们可以在下面的图 2.14 中可视化各种链接。

图 2.14(i)单链接是最近邻(ii)完全链接是最远邻(iii)群体平均是聚类之间距离的平均值

02_12

通过这个,我们已经理解了层次聚类中的工作机制。但我们仍然没有解决使用层次聚类确定最优聚类数量的机制,我们正在研究这个问题。

2.4.3 最佳聚类数量

回想一下,在 k 均值聚类中,我们必须将聚类的数量作为算法的输入。我们使用肘部法确定最佳聚类数量。在层次聚类中,我们不必向算法指定聚类的数量,但仍然必须确定我们希望拥有的最终聚类数量。我们使用树状图来解决这个问题。

假设我们总共有 10 个数据点显示在图 2.15 底部。聚类会迭代地合并,直到我们得到一个最终聚类在顶部。树状图的高度,在两个聚类相互合并时表示向量空间图中的两个聚类之间的相应距离。

图 2.15 树状图用于确定最佳聚类数量。X 和 Y 之间的距离大于 A&B 和 P&Q,因此我们选择该位置作为切割点来创建聚类,并选择聚类数量为 5。x 轴表示聚类,y 轴表示两个聚类之间的距离(不相似度)。

02_13

从树状图中,聚类的数量由水平线切割的垂直线数量决定。最佳聚类数量由树状图中被水平线切割的垂直线数量给出,使得它与最高的垂直线相交。或者如果切割从垂直线的一端移动到另一端,那么所覆盖的长度是最大的。树状图利用聚类的分支显示各个数据点之间的关系密切程度。在树状图中,位于相同高度水平的聚类比位于不同高度水平的聚类之间更密切相关。

在图 2.15 中显示的示例中,我们展示了三个潜在的切割点 - AB,PQ 和 XY。如果我们在 AB 上方切割,将导致两个非常宽泛的聚类,而在 PQ 下方切割,将导致九个聚类,这将变得难以进一步分析。

这里,X 和 Y 之间的距离大于 A&B 和 P&Q。因此,我们可以得出结论,X 和 Y 之间的距离最大,因此我们可以将其确定为最佳切割。该切割在五个不同点相交,因此我们应该有五个聚类。因此,树状图中切割的高度类似于 k 均值聚类中的 k 值。在 k 均值聚类中,“k”确定聚类的数量。在层次聚类中,最佳切割确定我们希望拥有的聚类数量。

类似于 k 均值聚类,最终的聚类数量并不仅取决于算法的选择。商业眼光和实用逻辑在确定最终聚类数量方面起着至关重要的作用。回顾一下,聚类的重要属性之一是它们的可用性,我们在之前的第 2.2 节中已经讨论过。

 小测验——回答这些问题来检验你的理解力。答案在书的结尾。

  1. 层次聚类中使用的贪婪方法是什么?

  2. 完全连接法用于查找最接近邻居的距离——正确还是错误?

  3. 群组联接与瓦德联接有什么区别?

4.   描述找到“k”最优值的过程

我们已经讨论了层次聚类的背景以及如何确定聚类。现在我们将讨论我们在层次聚类中面临的优势和挑战。

2.4.4 层次聚类的优缺点

层次聚类是一种强大的聚类技术,也很受欢迎。类似于 K 均值,它也使用距离作为衡量相似性的度量。同时,该算法也存在一些挑战。我们正在讨论层次聚类的利与弊。层次聚类的优势包括:

  • 也许我们在层次聚类中最大的优势就是结果的可重现性。回顾一下,在 K 均值聚类中,过程始于对中心点的随机初始化,因而会得到不同的结果。而在层次聚类中,我们可以重现结果。

    • 在层次聚类中,我们不需要输入要对数据进行分段的聚类数。

    • 实施是容易实现和理解的。由于它遵循类似树的结构,因此可以向非技术背景的用户解释清楚。

  • 生成的树状图可以通过可视化来解释数据并产生非常好的理解。

与此同时,我们在使用层次聚类算法时面临一些挑战:

  • 层次聚类最大的挑战是收敛所需的时间。K 均值的时间复杂度是线性的,而层次聚类是二次的。举例来说,如果我们有“n”个数据点,那么对于 K 均值聚类,时间复杂度将是 O(n),而对于层次聚类,则是 O(n³)。
如果你想学习 O(n),可以参考本书的附录。
  • 由于时间复杂度为 O(n³),这是一个耗时的任务。此外,计算所需的内存至少是 O(n²),使得层次聚类成为一个非常耗时和内存密集型的过程。即使数据集是中等大小,这也是一个问题。如果我们使用高端处理器,则计算可能不是一个挑战,但对于普通计算机来说可能会成为一个问题。

    • 有时树状图的解释可能会有主观性,因此在解释树状图时需要谨慎。解释树状图的关键是注重任何两个数据点连接的高度。由于不同的分析人员可能会解释出不同的切割点并试图证明他们的方法,因此解释可能存在主观性。因此,建议在数学的光线下解释结果,并将结果与现实世界的业务问题相结合。

    • 层次聚类无法撤销其之前所做的步骤。如果我们觉得某个连接不正确,应该撤销,但目前还没有机制可以移除连接。

  • 该算法对异常值和混乱的数据集非常敏感。异常值、NULL、缺失值、重复项等使数据集变得混乱。因此,结果可能不正确,也不是我们预期的。

尽管存在种种挑战,层次聚类是最常用的聚类算法之一。通常,我们为同一数据集创建 k-means 聚类和层次聚类,以比较两者的结果。如果建议的簇的数量和各自簇的分布看起来相似,我们对所使用的聚类方法更有信心。

我们已经涵盖了对层次聚类的理论理解。现在是行动的时候,跳入 Python 进行编码。

使用 Python 的层次聚类案例研究

现在,我们将为层次聚类创建一个 Python 解决方案,使用与 k-means 聚类相同的数据集。

步骤 1-6: 加载所需的库和数据集。为此,请按照我们在 k-means 算法中所遵循的步骤 1 到 6 进行操作。

步骤 7: 接下来,我们将使用三种链接方法(平均、Ward 和完全)创建层次聚类。然后对簇进行绘图。该方法的输入是 X_Standard 变量、使用的链接方法和距离度量。然后,使用 matplotlib 库,我们绘制树状图。在代码片段中,只需将方法从“average”更改为“ward”和“complete”,就可以得到相应的结果。

from scipy.cluster.hierarchy import dendrogram, linkage
Z_df_average = linkage(X_standard, 'average', metric='euclidean')
Z_df_average.shape
plt.figure(figsize=(30, 12))
dendrogram(Z_df_average)
plt.show()

02_13a

步骤 8: 现在,我们想要选择我们希望拥有的簇的数量。为此目的,让我们通过对最后 10 个合并的簇进行子集划分来重新创建树状图。我们选择了 10,因为这通常是一个最佳选择,建议您也尝试其他值。

dendrogram(
    Z_df_complete,
    truncate_mode='lastp',    p=10,)
plt.show()

02_13b

步骤 9: 我们可以观察到,最优距离是 10。

步骤 10: 将数据聚类到不同的组中。通过使用上一节描述的逻辑,最优簇的数量被确定为四个。

from scipy.cluster.hierarchy import fcluster
hier_clusters = fcluster(Z_df_complete, max_distance, criterion='distance')
hier_clusters
len(set(hier_clusters)) 

步骤 11: 使用 matplotlib 库绘制不同的簇。

plt.scatter(X_standard[:,0], X_standard[:,1], c=hier_clusters)  
plt.show()

02_13c

步骤 12: 对于不同的距离值,簇的数量会改变,因此图表会呈现不同的样子。我们展示了距离为 5、15、20 的不同结果,并且每次迭代生成的簇的数量也不同。在这里,我们可以观察到,当我们从左到右移动时,对于不同距离值,我们得到完全不同的结果。在选择距离值时,我们必须要谨慎,有时候,我们可能需要多次迭代才能得到最佳值。

02_13d

因此,我们可以观察到,使用分层聚类,我们已将数据从图 2 下面的左侧分割到右侧。左侧是原始数据的表示,而右侧是聚类数据集的表示。

02_13e

分层聚类是一种强大的方法,也是一种高度推荐的方法。与 k-means 一起,它为基于聚类的解决方案建立了良好的基础。大多数情况下,至少这两种技术在我们创建聚类解决方案时会被考虑。然后,我们会继续尝试其他方法。

这标志着对基于连通性的聚类算法的讨论的结束。我们现在将前进到基于密度的解决方案,并在下一节讨论 DBSCAN 聚类。

2.5 基于密度的聚类

我们在早期章节中学习了 k-means。回想一下它如何使用基于质心的方法将聚类分配给每个数据点。如果一个观测值是异常值,异常值将质心拉向自己,并像正常观测值一样分配到一个聚类中。这些异常值不一定为聚类带来信息,并且可能会不成比例地影响其他数据点,但仍然被作为聚类的一部分。此外,如图 2.16 所示,使用 k-means 算法获取任意形状的聚类是一项挑战。基于密度的聚类方法为我们解决了这个问题。

图 2.16 DBSCAN 非常适用于不规则形状的聚类。通过 k-means,我们通常会得到球形聚类,而 DBSCAN 可以为我们解决这个问题。

02_14

在基于密度的聚类算法中,我们解决了所有这些问题。在基于密度的方法中,将那些与数据集的其他部分相比具有更高密度的区域标识为聚类。换句话说,在表示数据点的向量空间图中 - 一个聚类由高密度点的相邻区域或邻近区域定义。该聚类将被低密度点的区域与其他聚类分开。在稀疏区域或分隔区域中的观测被视为数据集中的噪音或异常值。密度基础聚类的几个示例显示在(图 2.16)中。

我们提到了两个术语 - “邻域”和“密度”。为了理解基于密度的聚类,我们将在下一节中学习这些术语。

2.5.1 邻域和密度

想象我们在一个向量空间中表示数据观测。我们有一个点 P。现在我们为这个点 P 定义邻域。表示如下所示:图 2.17。

图 2.17 数据点在向量空间图中的表示。在右侧,我们有一个点 P,绘制的圆是半径 ε。因此,对于 ε > 0,点 P 的邻域由距离点 P 小于等于 ε 的点集定义。

02_15

正如我们从上面的图 2.17 中可以看出的那样,对于一个点 P,我们已经为其定义了 ε - 邻域,这些点与 P 等距。在二维空间中,它由一个圆表示,在三维空间中它是一个球体,在 n 维空间中它是以 P 为中心和半径 ε 的 n-球体。这定义了邻域的概念。

现在,让我们来探讨术语密度。回想密度是质量除以体积(质量/体积)。质量越大,密度越高,质量越低,密度越低。反之,体积越小,密度越高,体积越大,密度越低。

在上文的背景下,质量是邻域中的点数。在下面的图 2.18 中,我们可以观察到 ε 对数据点或质量的数量的影响。

图 2.18 对半径 ε 的影响,左侧的点数比右侧多。因此,右侧的质量较低,因为它包含较少的数据点。

02_16

当涉及到体积时,在二维空间的情况下,体积是 πr²,而对于三维的球体,它是 4/3 πr³。对于 n 维度的球体,我们可以根据维度的数量计算相应的体积,这将是 π 乘以一个数值常数的维度数次方。

所以,在图 2.18 中显示的两种情况下,对于一个点“P”,我们可以得到点(质量)和体积的数量,然后我们可以计算相应的密度。但是这些密度的绝对值对我们来说没有任何意义,而是它们与附近区域的相似性(或不同)如何。它用于对具有相似密度的点进行聚类。换句话说,处于同一邻域并具有相似密度的点可以被归为一个簇。

在理想情况下,我们希望拥有具有最大数量点的高密度簇。在下面图 2.19 中显示的两种情况下,我们有一个左侧显示较少密集簇,右侧显示高密集簇。

图 2.19 密集簇优于较少密集簇。理想情况下,我们希望从聚类中获得具有最大数据点数量的密集簇。

02_17

从上面的讨论中,我们可以得出结论:

  1. 如果我们增加 ε 的值,我们将得到更大的体积,但不一定会得到更多的点(质量)。这取决于数据点的分布。

  2. 同样,如果我们减小 ε 的值,我们将得到更小的体积,但不一定会得到更少的点(质量)。

这些是我们遵循的基本要点。因此,在选择簇时,选择具有高密度并覆盖最大数量邻近点的簇是至关重要的。

因此,我们已经总结了密度聚类的概念。这些概念是我们接下来讨论的 DBSCAN 聚类的基石!

2.5.2 DBSCAN 聚类

具有噪声的基于密度的空间聚类应用,或者称为 DBSCAN 聚类,是高度推荐的基于密度的算法之一。它对密集人口区域紧密打包的数据观察进行聚类,但不考虑低密度区域的异常值。与 k 均值不同,我们不需要指定簇的数量,该算法能够识别不规则形状的簇,而 k 均值通常提出球形簇。与层次聚类类似,它通过连接数据点工作,但是只有满足密度标准或阈值的观察。更多内容可以在我们下面描述的步骤中了解。

注意:

DBSCAN 是由 Martin Ester, Hans-Peter Kriegal, Jörg Sander 和 Xiaowei Xu 于 1996 年提出的。该算法于 2014 年在 ACM SIGKDD 获得了时间测试奖。可以在[http://citeseerx.ist. psu.edu/viewdoc/summary?doi=10.1.1.71.1980](viewdoc.html)中查阅论文。

DBSCAN 基于我们在上一节讨论的邻域概念。我们现在将深入研究 DBSCAN 的工作方法和构建模块。

DBSCAN 聚类的实质

我们现在将对 DBSCAN 聚类的核心构建模块进行检查。我们知道这是一个基于密度的聚类算法,因此邻域概念在这里适用。

假设我们有一些数据观察结果需要进行聚类。我们还要找到一个数据点“P”。然后,我们可以轻松地定义两个超参数术语:

  1. P 周围邻域的半径,即我们在上一节中讨论的ε

  2. 我们希望在 P 的邻域内至少有 minPts 个点,或者换句话说,需要至少有一定数量的点来创建一个密集区域。这被称为minPts。这是我们可以通过在 minPts 上应用阈值来输入的参数之一。

基于以上概念,我们可以将观察结果分为三大类 - 核心点、边界点或可达点和异常值:

  1. 核心点:如果至少有 minPts 个点与它的ε距离以内(包括 x 本身),则任何数据点“x”都可以被称为核心点,如下面的(图 2.20)所示。它们是我们聚类的构建模块,因此被称为核心点。我们为每个点使用相同的半径值(ε),因此每个邻域的体积保持不变。但是点的数量会有所变化,因此质量会有所变化。因此密度也会发生变化。由于我们使用 minPoints 设置阈值,我们正在对密度进行限制。因此,我们可以得出结论,核心点满足最低密度阈值要求。需要注意的是,我们可以选择不同的ε和 minPts 值来迭代和微调簇。
图 2.20 核心点显示为方形,边界点显示为填充圆,而噪声显示为未填充圆。这三者共同是 DBSCAN 聚类的构建模块

02_18

  1. 边界点或可达点:在簇中不是核心点的点称为边界点,如图 2.20 中的填充圆所示。

如果点“y”距离核心点 x 的距离在ε范围内,则点“y”直接从 x 可达。一个点只能从核心点到达,这是必须遵循的主要条件或规则。只有核心点才能到达非核心点,反之不成立。换句话说,非核心点只能被其他核心点到达,它无法到达其他任何点。在图 2.20 中,边界点表示为黑色圆圈。

为了更好地理解过程,我们必须理解密度可达连接性这个术语。如下图 2.21 所示,我们有两个核心点 X 和 Y。我们可以直接从 X 到 Y。点 Z 不在 X 的邻域内,但是在 Y 的邻域内。所以,我们不能直接从 X 到达 Z。但是我们可以确实从 X 通过 Y 到达 Z,换句话说,使用 Y 的邻域,我们可以从 X 到达 Z。我们不能从 Z 到达 Z,因为 Z 是边界点,并且如前所述,我们不能从边界点出发。

图 2.21 中的 X 和 Y 是核心点,我们可以从 X 到 Y。虽然 Z 不在 X 的直接邻域内,但我们仍然可以通过 Y 从 X 到达 Z。这是密度连接点的核心概念。

02_19

  1. 离群值:所有其他点都是离群值。换句话说,如果它不是核心点或不是可达点,则它是离群值,如上图 2.20 中的未填充圆所示。它们不被分配任何簇。

现在我们已经定义了 DBSCAN 的构建模块。我们现在将在下一节中继续介绍 DBSCAN 的流程。

DBSCAN 聚类的步骤

现在我们已经定义了 DBSCAN 的构建模块。我们将现在检查 DBSCAN 中遵循的步骤:

  1. 我们首先为ε和创建簇所需的最小点数(minPts)分配值。

  2. 我们首先选择一个随机点,比如说“P”,该点尚未被分析并分配任何簇。

  3. 然后我们分析 P 的邻域。如果它包含足够数量的点,即高于 minPts,则满足开始一个簇的条件。如果是这样,我们将点 P 标记为核心点。如果一个点不能被识别为核心点,我们将为其分配离群值噪声的标签。我们应该注意到这一点后来可以成为不同簇的一部分。然后我们回到步骤 2。

  4. 一旦找到了这个核心点“P”,我们就开始通过添加所有从 P 直接可达的点来创建这个簇,然后通过添加更多从 P 直接可达的点来增加这个簇的大小。然后我们通过迭代所有这些点来将所有点添加到簇中,这些点可以使用邻域包含。如果我们将一个离群值点添加到簇中,则离群值点的标签将更改为边界点。

  5. 这个过程会一直持续,直到密度聚类完成。然后我们找到一个新的未分配点并重复这个过程。

  6. 一旦所有点都被分配到一个簇或称为异常值,我们就停止我们的聚类过程。

在过程中进行迭代。一旦聚类结束,我们就利用业务逻辑来合并或拆分一些簇。

 小测验 - 回答这些问题来检查你的理解。答案在本书末尾

  1. 将 DBSCAN 聚类的重要性与 kmeans 聚类进行比较和对比。

  2. 非核心点可以到达核心点,反之亦然 - 真或假?

  3. 解释邻域和 minPts 的重要性。

  4. 描述找到“k”的最优值的过程

现在我们清楚了 DBSCAN 聚类的过程。在创建 Python 解决方案之前,我们将检查 DBSCAN 算法的优缺点。

DBSCAN 聚类的优缺点

DBSCAN 具有以下优点:

  • 与 k-means 不同,我们不需要为 DBSCAN 指定簇的数量。

    • 该算法对不干净的数据集是一种相当强大的解决方案。与其他算法不同,它可以有效地处理异常值。

    • 我们也可以确定不规则形状的簇。可以说,这是 DBSCAN 聚类的最大优势。

  • 算法只需要半径和 minPts 的输入。

DBSCAN 面临以下挑战:

  • 使用 DBSCAN 时,聚类的差异有时并不明显。根据处理观察的顺序,一个点可以改变其簇。换句话说,如果边界点 P 可以被多个簇访问,P 可以属于任一簇,这取决于处理数据的顺序。

    • 如果数据集不同区域的密度差异很大,则确定 ε 和 minPts 的最佳组合将变得困难,因此,DBSCAN 将无法生成有效结果。

    • 使用的距离度量在包括 DBSCAN 在内的聚类算法中发挥了非常重要的作用。可以说,最常用的度量是欧几里得距离,但如果维度的数量相当大,则计算将变得很困难。

  • 该算法对 ε 和 minPts 的不同取值非常敏感。有时,找到最优值成为一项挑战。

我们现在将为 DBSCAN 聚类创建一个 Python 解决方案。

DBSCAN 聚类的 Python 解决方案

我们将使用与 k-means 和层次聚类相同的数据集。

第 1-6 步: 加载库和数据集,直到 k-means 算法的第 6 步。

第 7 步: 导入额外的库

from sklearn.cluster import DBSCAN 
from sklearn.preprocessing import StandardScaler 
from sklearn.preprocessing import normalize 
from sklearn.neighbors import NearestNeighbors

第 8 步: 我们正在使用 minDist 和半径的值来拟合模型。

db_default = DBSCAN(eps = 0.0375, min_samples = 6).fit(X_standard) 
labels = db_default.labels_

第 9 步: 不同的簇数为 1。

list(set(labels))

第 10 步: 我们在这里得不到任何聚类的结果。换句话说,由于我们没有提供 minPts 和 ε 的最优值,所以聚类没有任何逻辑结果。

步骤 11: 现在,我们将找出ε的最佳值。为此,我们将计算每个点的最近点距离,然后对结果进行排序和绘制。无论何时弯曲程度最大,它就是ε的最佳值。对于 minPts,通常 minPts ≥ d+1,其中 d 是数据集中的维数。

neigh = NearestNeighbors(n_neighbors=2)
nbrs = neigh.fit(X_standard)
distances, indices = nbrs.kneighbors(X_standard)
distances = np.sort(distances, axis=0)
distances = distances[:,1]
plt.plot(distances)

02_19a

建议你阅读链接中的文章,进一步研究如何为 DBSCAN 选择半径的值iopscience.iop.org/article/10.1088/1755-1315/31/1/012012/pdf

步骤 12: 最佳值为 1.5,正如上面观察到的缺陷点所示。我们将使用它,并将 minPts 设置为 5,通常视为标准。

db_default = DBSCAN(eps=1.5, min_samples=5)
db_default.fit(X_standard)
clusters = db_default.labels_

步骤 13: 现在我们可以观察到我们得到了不止一个簇。

list(set(clusters))

步骤 14: 让我们绘制这些簇。

colors = ['blue', 'red', 'orange', 'green', 'purple', 'black', 'brown', 'cyan', 'yellow', 'pink']
vectorizer = np.vectorize(lambda x: colors[x % len(colors)]) 
plt.scatter(X_standard[:,0], X_standard[:,1], c=vectorizer(clusters))

02_19b

我们使用了 DBSCAN 创建了一个解决方案。建议你比较来自所有三种算法的结果。在现实世界的情景中,我们测试使用多种算法的解决方案,用超参数进行迭代,然后选择最佳解决方案。

基于密度的聚类是相当高效且在一定程度上非常有效的解决方案。如果怀疑簇的形状是不规则的,强烈建议使用它。

通过这些内容,我们结束了对 DBSCAN 聚类的讨论。在下一节中,我们将解决一个关于聚类的业务用例。在案例研究中,重点不太在技术概念上,而更多地在商业理解和解决方案生成上。

2.6 使用聚类的案例研究

现在我们将定义一个使用聚类作为解决方案之一的案例研究。案例研究的目标是让你了解实际的商业世界。这种基于案例研究的方法也在与工作相关的面试中使用,在面试阶段会讨论一个案例。因此,强烈建议你了解我们如何在实际的商业场景中实施机器学习解决方案。

一个案例研究通常涉及一个商业问题,可用的数据集,可以使用的各种解决方案,面临的挑战以及最终选择的解决方案。我们还讨论在实际商业中实施解决方案时遇到的问题。

所以,让我们开始使用无监督学习进行聚类的案例研究。在案例研究中,我们关注解决案例研究所采取的步骤,而不是技术算法,因为对于特定问题可能存在多个技术解决方案。

商业背景:我们考虑的行业可能是零售、电信、银行金融保险、航空、医疗保健。基本上,任何涉及客户的业务(几乎所有业务都有客户)。对于任何业务,目标都是为业务产生更多收入,最终增加业务的整体利润。为了增加收入,业务希望拥有越来越多的新客户。业务也希望现有的消费者购买更多,更经常购买。因此,业务始终努力让消费者参与其中,让他们感到满意,并增加他们与自己的交易价值。

为了实现这一目标,业务应该彻底了解消费者群体,了解他们的偏好、口味、价格点、对类别的喜好等。一旦业务详细审查并理解了消费者群体,那么:

  • 产品团队可以根据消费者的需求改进产品特性。

    • 定价团队可以通过将产品价格与客户的首选价格对齐来改进产品价格。价格可以根据客户定制,或者提供忠诚度折扣。

    • 市场营销团队和客户关系团队(CRM)可以通过定制的优惠向消费者推广。

    • 团队可以挽回那些即将流失或停止购买业务的消费者,可以增加他们的消费、增加粘性并增加客户生命周期价值。

  • 总的来说,不同的团队可以根据生成的对消费者的理解来调整其提供的内容。最终消费者会更加幸福,更加投入,更加忠诚于业务,从而使消费者参与更加富有成果。

因此,业务必须深入研究消费者的数据,并生成对基础的理解。客户数据可能看起来像下一节中所示的样子。

用于分析的数据集:我们以服装零售商(H&M、优衣库等)为例。拥有忠诚计划的零售商保存客户的交易明细。各种(不是详尽)数据来源如下所示:

02_19c

我们可以有存储详细信息的商店细节,如商店 ID、商店名称、城市、地区、员工数量等。我们可以有项目层次结构表,其中包含价格、类别等项目的所有详细信息。然后我们可以有客户人口统计详细信息,如年龄、性别、城市和客户与我们过去的销售的交易历史的详细信息。显然,通过联合这些表,我们将能够创建一个将所有详细信息放在一个地方的主表。

注意

建议您培养良好的 SQL 技能。几乎所有与数据相关的领域都需要它——无论是数据科学、数据工程还是数据可视化,SQL 都是无处不在的。

我们在下面展示了一个主表的例子。它不是变量的详尽列表,变量的数量可能比下面的这些要多得多。主表中包括一些原始变量,如收入、发票等,以及衍生变量,如平均交易金额和平均购物篮大小等。

02_19d

我们还可以以电信运营商为例。在这种情况下,我们将分析的属性包括用户使用情况、通话率、收入、在网络上停留的天数、数据使用等。因此,根据手头的业务领域,数据集可能会发生变化。

一旦我们获取到数据集,通常我们会从中创建衍生属性。例如,平均交易金额属性是总收入除以发票数量。除了已有的原始变量之外,我们会创建这样的属性。

建议的解决方案:对于这个问题可能有多种解决方案,我们以下描述其中的一些:

  1. 我们可以创建一个仪表盘来展示主要的关键绩效指标(KPI)。它将允许我们分析历史数据并根据分析结果采取必要的行动。但这个解决方案更多是报告性质的,包括我们已经熟悉的趋势和 KPI。

  2. 我们可以使用在前几节解决方案中使用的一些技术进行数据分析。这将解决问题的一部分,而且同时考虑多个维度是困难的。

  3. 我们可以创建预测模型来预测客户在未来几个月是否会购物或在接下来的 X 天内流失,但这并不能完全解决问题。要明确,这里的流失指的是客户在接下来的 X 天内不再与零售商购物。在这里,持续时间 X 根据业务领域的不同而有所差异。例如,在电信领域,X 的时间会比保险领域短。这是因为人们每天都在使用手机,而在保险领域,大部分客户可能一年只支付一次保费。因此,与保险业务相比,客户的互动较少。

  4. 我们可以创建客户分割解决方案,根据客户的历史趋势和属性将客户分组。这是我们用来解决这个业务问题的解决方案。

问题的解决方案:回想一下第一章中的图 1.9,我们讨论了机器学习算法中的步骤。一切都始于定义业务问题,然后进行数据发现、预处理等。对于以上案例研究,我们将采用类似的策略。我们已经定义了业务问题;数据发现已经完成,我们已经完成了数据的探索性数据分析和预处理。我们希望使用聚类创建一个分割解决方案。

步骤 1: 我们从确定要提供给聚类算法的数据集开始。我们可能已经创建了一些派生变量,处理了一些缺失值或异常值等。在案例研究中,我们想要知道交易、发票、购买商品的最小/最大/平均值等。我们对性别和年龄分布感兴趣。我们也想知道这些变量之间的相互关系,比如女性客户是否比男性客户更多地使用在线模式。所有这些问题都作为这一步的一部分得到回答。

在 Github 存储库中提交了一个 Python Jupyter 笔记本,提供了探索性数据分析(EDA)和数据预处理的详细步骤和代码。快去看看吧!

步骤 2: 我们使用 k 均值聚类和层次聚类创建第一个解决方案。对于每个算法,通过更改超参数进行迭代。在案例研究中,我们将选择访问次数、总收入、购买的不同类别、在线/离线交易比率、性别、年龄等作为聚类参数。

步骤 3: 选择算法的最终版本以及相应的超参数。根据业务理解进一步分析聚类。

步骤 4: 更常见的情况是,根据观察的大小和它们所包含属性的性质,对聚类进行合并或分割。例如,如果总客户群有 100 万人,要对 100 个人的聚类采取行动将非常困难。同时,要管理 70 万人的聚类同样也很困难。

步骤 5: 然后,我们分析最终得到的聚类。检查变量的聚类分布,理解它们的区别因素,并为聚类赋予逻辑名称。我们可以期待在下面的(图 3-)中看到这样的聚类输出。

在下面示例的聚类中,我们描述了消费模式、对之前活动的反应、生命周期阶段和整体参与度等几个维度。还展示了每个维度的相应子细分。聚类将是这些维度的逻辑组合。实际的维度可能会更多。

02_19e

上面显示的细分可以用于多个领域和业务。参数和属性可能会改变,业务背景不同,可用数据的范围可能会有所不同,但总体方法保持相似。

除了上一节中看到的少数应用之外,我们现在正在研究一些用例:

  1. 市场研究利用聚类将消费者分组为市场细分。然后可以更好地分析这些组的偏好。产品摆放可以改进,定价可以更紧密,地理选择将更加科学。

  2. 在生物信息学和医疗行业,聚类可用于将基因分组为不同的类别。基因组可以被划分为不同的组,并且可以通过分析组的属性来进行比较。

  3. 它被用作在创建使用监督学习解决方案的算法之前的有效数据预处理步骤。它还可以通过关注属于一个聚类的数据点来减少数据大小。

  4. 它被用于跨结构化和非结构化数据集的模式检测。我们已经研究了结构化数据集的情况。对于文本数据,它可以用于对类似类型的文档、期刊、新闻等进行分类。我们还可以利用聚类来处理并为图像开发解决方案。我们将在后续章节中研究文本和图像的无监督学习解决方案。

  5. 由于算法基于相似性度量,因此可以用于将传入的数据集分段为欺诈或真实数据,这可以用来减少犯罪活动的数量。

聚类的使用案例相当多。我们只讨论了其中一些突出的案例。它是一种改变工作方法并在数据周围生成大量见解的算法之一。它被广泛应用于电信、零售、银行保险、航空等领域。

与此同时,该算法也存在一些问题。接下来我们将在下一节中讨论我们在聚类中常见的问题。

2.7 聚类中面临的常见挑战

聚类并不是一个完全直截了当、没有任何挑战的解决方案。与世界上任何其他解决方案类似,聚类也面临着自己的一些问题。我们正在讨论我们在聚类中面临的最常见的挑战,它们包括:

  1. 有时,数据的数量非常大且有许多维度可用。在这种情况下,难以管理数据集。计算能力可能是有限的,而且像任何项目一样,时间是有限的。为了解决这个问题,我们可以:

  2. 通过使用监督学习回归方法或决策树算法等方法找到最重要的变量,尝试通过减少维度数量。

  3. 通过使用主成分分析(PCA)或奇异值分解(SVD)等方法来减少维度数量。

  4. 嘈杂的数据集:“垃圾进了,垃圾出”-这个陈词滥调对于聚类也是真实的。如果数据集混乱,会引发很多问题。问题可能包括:

  5. 缺失值,即 NULL、NA、?、空白等。

  6. 数据集中存在异常值。

  7. 数据集中存在类似#€¶§^等垃圾值。

  8. 数据中存在错误的输入。例如,如果将名称输入到收入字段中,那就是一个错误的输入。

我们将在每个章节讨论解决这些问题的步骤和过程。在本章中,我们正在研究 - 如何处理分类变量

  1. 分类变量:回想一下,在讨论中我们讨论过 k-means 无法使用分类变量的问题。我们正在解决这个问题。

要将分类变量转换为数值变量,我们可以使用独热编码。该技术在下图(Figure 2.)中显示的变量 city 有唯一值 LondonNewDelhi。我们可以观察到已创建了两个额外的列,用于填充值为 0 或 1。

02_19f

但是使用独热编码并不能始终保证有效和高效的解决方案。想象一下,如果上述例子中的城市数量是 100,那么数据集中将会有 100 个额外的列,而且其中大部分值都将填充为零。因此,在这种情况下,建议对几个值进行分组。

  1. 距离度量:使用不同的距离度量可能会得到不同的结果。虽然没有“一刀切”,但大多数情况下,欧几里德距离被用于测量距离。

  2. 对聚类的解释是非常主观的。通过使用不同的属性,可以对相同的数据集进行完全不同的聚类。正如前面讨论的那样,重点应该放在解决手头的业务问题上。这是选择超参数和最终算法的关键。

  3. 耗时:由于同时处理了许多维度,有时算法的收敛需要很长时间。

但是尽管面临所有这些挑战,聚类仍然是一种广泛认可和使用的技术。我们在最后一节中讨论了聚类在现实世界中的应用案例。

这标志着本章关于聚类的讨论结束。让我们用一些总结思考来结束本章。

2.8 总结思考

无监督学习不是一项易事。但它肯定是一项非常有吸引力的工作。它不需要任何目标变量,解决方案自身识别模式,这是无监督学习算法最大的优点之一。并且这些实现已经在商业世界产生了巨大的影响。在本章中,我们研究了一类称为聚类的解决方案。

聚类是一种无监督学习解决方案,用于模式识别、探索性分析和数据点的分割。组织机构广泛使用聚类算法,并继续深入了解消费者数据。可以提供更好的价格、提供更相关的优惠、提高消费者参与度,并改善整体客户体验。毕竟,满意的消费者是任何企业的目标。不仅可以对结构化数据使用聚类,还可以对文本数据、图像、视频和音频使用聚类。由于其能够使用大量维度在多个数据集中找到模式,聚类是想要一起分析多个维度时的解决方案。

在本书的第二章中,我们介绍了基于无监督的聚类方法的概念。我们研究了不同类型的聚类算法——k 均值聚类、层次聚类和 DBSCAN 聚类,以及它们的数学概念、各自的用例以及优缺点,重点放在为相同数据集创建实际 Python 代码上。

在接下来的章节中,我们将研究像 PCA 和 SVD 这样的降维技术。将对技术的构建模块、它们的数学基础、优点和缺点、用例以及实际的 Python 实现进行讨论。

现在您可以进入问题部分了!

实用的下一步和建议阅读材料

  1. 从链接获取在线零售数据(www.kaggle.com/hellbuoy/online-retail-customer-clustering)。这个数据集包含了一个总部位于英国的零售商在 2010 年 12 月 1 日至 2011 年 12 月 9 日期间发生的所有在线交易。应用本章描述的三种算法,确定公司应该针对哪些客户以及为什么。

  2. 从链接获取 IRIS 数据集(www.kaggle.com/uciml/iris)。它包括三种鸢尾花品种,每种 50 个样本,具有一些花的特性。使用 kmeans 和 DBSCAN 并比较结果。

  3. 探索 UCI 的聚类数据集(archive.ics.uci.edu/ml/index.php

  4. 研究关于 kmeans 聚类、层次聚类和 DBSCAN 聚类的以下论文

a) Kmeans 算法:

i.   www.ee.columbia.edu/~dpwe/papers/PhamDN05-kmeans.pdf

ii.   www.researchgate.net/publication/271616608_A_Clustering_Method_Based_on_K-Means_Algorithm

iii.   ieeexplore.ieee.org/document/1017616

b) 层次聚类

i.   ieeexplore.ieee.org/document/7100308

ii.   papers.nips.cc/paper/7200-hierarchical-clustering-beyond-the-worst-case.pdf

iii.   papers.nips.cc/paper/8964-foundations-of-comparison-based-hierarchical-clustering.pdf

c) DBSCAN 聚类

i.   arxiv.org/pdf/1810.13105.pdf

ii.   citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.121.9220

2.9 摘要

  • 我们讨论了一种称为聚类的无监督学习技术。通过聚类,我们找出数据集中的潜在模式,并找出数据中的自然分组。

    • 我们了解到,聚类在各行业中都被用于各种目的,包括零售、电信、金融、制药等。聚类解决方案被用于客户分割和营销分割,以更好地理解客户群体,从而进一步提高对客户的定位。

    • 我们学习并理解了基于方法论的多种聚类技术。一些示例包括 k 均值聚类、层次聚类、DBSCAN、模糊聚类等。

    • 我们详细介绍了 K 均值聚类、层次聚类和 DBSCAN 聚类算法。

    • 我们学习了 k 均值(kmeans)是基于聚类的质心,而层次聚类是一种凝聚式聚类技术。DBSCAN 是一种基于密度的聚类算法。

    • 我们还详细讨论了这些聚类算法的优缺点。例如,对于 k 均值,我们必须指定聚类的数量,层次聚类非常耗时,而 DBSCAN 的输出取决于观测数据处理的顺序。

  • 我们介绍了用于测量聚类技术准确性的方法,包括 WCSS(组内平方和)、轮廓系数和 Dunn 指数。

    我们为每种技术实现了基于 Python 的解决方案。主要使用的库是 sklearn。

  • 在本章末尾,我们提供了实际案例研究来补充学习。

第三章:降维

本章内容包括

  • 维数灾难及其缺点

  • 不同的降维方法

  • 主成分分析(PCA)

  • 奇异值分解(SVD)

  • PCA 和 SVD 的 Python 解决方案

  • 降维的案例研究

“知识是事实的累积过程;智慧则在于它们的简化。”– 马丁·H·菲舍尔

我们在生活中面临复杂的情况。生活给我们提供了多种选择,我们从中选择一些可行的选项。这种筛选的决定基于每个选项的重要性、可行性、效用和预期利润。符合条件的选项随后被选中。一个完美的例子就是选择度假目的地。基于天气、旅行时间、安全、食物、预算等多种选项,我们选择了一些地方作为下一个假期的目的地。在本章中,我们正在学习同样的东西–如何在数据科学和机器学习世界中减少选项的数量。

在上一章中,我们涵盖了主要的聚类算法。我们还在那里进行了一个案例研究。在这些真实案例的数据集中,有很多变量。有时,数据中可能有超过 100 个变量或维度。但并非所有变量都重要;并非所有变量都显著。数据集中有很多维度被称为“维数灾难”。为了进行进一步的分析,我们从所有维度或变量中选择了一些。在这一章中,我们将学习降维的必要性,各种降维技术,以及各自的优缺点。我们将深入研究主成分分析(PCA)和 SVD(奇异值分解)的概念,它们的数学基础,并用 Python 实现。延续上一章的结构,我们将在最后探讨电信行业的一个真实案例研究。在后面的章节中,我们还将探索其他高级的降维技术,比如 t-SNE 和 LDA。

聚类和降维是无监督学习的主要类别。上一章我们学习了主要的聚类方法,而本章将涵盖降维。有了这两种解决方案,我们将在无监督学习领域取得很大的进展。但是还有更多的高级话题需要涵盖,这些是本书后面章节的内容。

让我们先了解一下“维数灾难”是什么意思。

3.1 技术工具包

我们将使用与上一章相同的 Python 3.6+版本。Jupyter Notebook 在本章中也将被使用。

所有数据集和代码文件都位于 GitHub 存储库中(github.com/vverdhan/UnsupervisedLearningWithPython/tree/main/Chapter3)。您需要安装以下 Python 库才能执行numpypandasmatplotlibscipysklearn等。由于您在上一章中已经使用了相同的软件包,所以无需再安装它们。CPU 足以执行,但如果遇到一些计算问题,请切换到 GPU 或 Google Colab。如果在安装这些包中遇到任何问题,请参考本书的附录。

现在,让我们开始在以下部分进一步了解“维数灾难”。

3.2 维数灾难

继续使用我们先前介绍的度假目的地示例。目的地的选择取决于许多参数 - 安全性、可用性、餐食、夜生活、天气、预算、健康等等。有太多的参数需要决定,这是一个令人困惑的想法。我们通过一个现实生活的例子来理解。

想象一下:一家零售商想要在市场上推出一款新的鞋类产品。为此,需要选择一个目标客户群体,这个客户群体将通过电子邮件、通讯等方式进行接触。业务目标是引诱这些客户购买新发布的鞋类产品。从整个客户群体中,可以基于诸如客户年龄、性别、消费能力、首选类别、平均开销、购物频率等变量选择目标客户群体。这么多变量或维度使我们在基于有效的数据分析技术进行客户筛选时受到困扰,需要同时分析过多参数,检验每个参数对客户购物概率的影响,因此这个任务变得非常繁琐和困惑。这是真实世界的数据科学项目中我们所面临的维数灾难问题。我们还可以在另一种情况下遇到维度灾难现象,即当观察值的数量小于变量的数量时。考虑一个数据集,其中观察值的数量为 X,而变量的数量超过了 X,这种情况下我们就面临维度灾难。

通过可视化是理解任何数据集的简单方法。让我们在一个向量空间图中可视化一个数据集。如果数据集中只有一个属性或特征,我们可以用一个维度表示它。如图 3.1(i)所示。例如,我们可能希望只用一个维度来捕捉一个物体的高度。如果我们有两个属性,我们需要两个维度,如图 3.1(ii)所示,在这种情况下,为了得到一个物体的面积,我们将需要长度和宽度。如果我们有三个属性,例如,为了计算需要长度、宽度和高度的体积,它需要一个三维空间,如图 3.1(iii)所示。这个需求将根据属性的数量而继续增长。

图 3.1 (i) 只需要一个维度来表示数据点,例如,表示物体的高度 (ii) 我们需要两个维度来表示一个数据点。每个数据点可以对应一个物体的长度和宽度,用于计算面积 (iii) 三个维度用于展示一个点。这里,需要长度、宽度和高度来获得物体的体积。这个过程根据数据中存在的维度数量而继续。

03_01

现在想象一下,如果我们总共有 20 个要分析的数据点。如果我们只有一个属性,我们可以将它表示为 x[1]、x[2]、x[3]、x[4]、x[5] …. x[20],因此,20 维空间足以表示这些点。在第二个例子中,我们需要两个维度,我们将需要(x[1,]y[1])、(x[2,]y[2])、(x[3,]y[3])、(x[4,]y[4])、(x[5,]y[5])….. (x[20,]y[20]),换句话说是 2020 = 400 维空间。对于三维空间,我们将表示一个点为(x[1,]y[1,]z[1])、(x[2,]y[2,]z[2])、(x[3,]y[3,]z[3])、(x[4,]y[4,]z[4])、(x[5,]y[5,]z[5])….. (x[20,]y[20,]z[20]),我们将需要 2020*20 = 800 维空间。这个过程将继续下去。

因此,我们很容易得出结论,随着维度数量的增加,所需的空间量会大幅增加。这被称为维度灾难。这个术语是由理查德·E·贝尔曼引入的,用来指代数据集中有太多变量的问题——其中一些是重要的,而很多可能不太重要。

还有另一个众所周知的理论,即休斯现象如图 3.2 所示。一般来说,在数据科学和机器学习中,我们希望有尽可能多的变量来训练我们的模型。观察到,监督学习分类器算法的性能会增加到一定的极限,随着最适数量的变量达到顶峰。但是,使用相同数量的训练数据,并增加维度数量,监督分类算法的性能会下降。换句话说,如果变量对解决方案的准确性没有贡献,最好不要将其包含在数据集中,并应将这些变量从数据集中删除。

图 3.2 Hughes 现象显示,随着维度数量的增加,机器学习模型的性能会在初期得到提升。但是随着进一步增加,模型的性能会下降。

03_02

维度数量的增加对机器学习模型的影响如下:

  • 由于模型处理了更多的变量,数学复杂度也会增加。例如,在上一章讨论的 k 均值聚类方法中,当变量数量更多时,各个点之间的距离计算将变得更加复杂。因此,整体模型会更加复杂。

    • 在较大维度空间生成的数据集相对于较少变量来说可能更加稀疏。数据集将变得更稀疏,因为有些变量将会有缺失值、NULL 等。因此,空间更为空,数据集更加稀疏,少数变量与之相关联的值更少。

    • 随着模型复杂度的增加,所需的处理时间也会增加。系统在处理如此多维度的情况下将感到压力。

  • 整体解决方案变得更加复杂和难以理解和执行。回顾第一章中我们讨论过的监督学习算法。由于维度数量较高,我们可能会在监督学习模型中面临过拟合问题。

当监督学习模型在训练数据上有很好的准确度,但在未知数据上准确度较低时,这就被称为过拟合。过拟合是一个麻烦,因为机器学习模型的目的是在未知数据上表现良好,而过拟合则违背了这一目的。

让我们将事物与一个现实世界的例子联系起来。考虑一个保险公司提供不同类型的保险政策,如人寿保险、车辆保险、健康保险、家庭保险等。该公司希望利用数据科学并执行聚类用例来增加客户群和销售的总保单数量。他们拥有客户的详细信息,如年龄、性别、职业、保单金额、历史交易、持有的保单数量、年收入、保单类型、历史违约次数等。同时,让我们假设还捕捉到是否客户是左撇子还是右撇子、穿黑鞋还是棕鞋、使用的洗发水品牌、头发颜色和最喜欢的餐厅等变量。如果我们将所有变量包括在数据集中,那么结果数据集中的变量总数将会相当高。对于 k-means 聚类算法,距离计算将变得更加复杂,处理时间将增加,整体解决方案将变得相当复杂。

还有一点必须注意,并非所有的维度或变量都是重要的。因此,从我们拥有的所有变量中筛选出重要的变量至关重要。记住,自然总是倾向于简单的解决方案!在上述讨论的情况下,像头发颜色和最喜欢的餐厅等变量很可能不会影响结果。因此,我们最好减少维度以简化复杂性并减少计算时间。同时,还必须注意,降维并不总是需要的。它取决于数据集的类型和我们希望解决的业务问题。在本章后续部分的案例研究中,我们将进一步探讨这个问题。

 小测验 - 回答这些问题来检查你的理解。答案在书的最后。

1.   维度灾难指的是数据规模很大。TRUE 或 FALSE。

2.   在数据集中拥有大量变量将始终提高解决方案的准确性。TRUE 或 FALSE。

3.   数据集中有大量变量会如何影响模型?

我们已经确定,拥有许多维度对我们来说是一个挑战。我们现在正在研究各种减少维度的方法。

3.3 维度减少方法

在上一节中,我们研究了拥有非常高维数据的缺点。维度较少可能会导致数据结构更简单,这将提高计算效率。同时,我们应该小心减少变量的数量。降维方法的输出应该足够完整以代表原始数据,不应导致任何信息损失。换句话说,如果原本我们有 500 个变量,我们将它降低到 120 个显著变量,那么这 120 个变量应该足够强大,能几乎捕捉到所有信息。让我们通过一个简单的例子来理解。

想象一下:我们希望预测下个月一个城市会接收到多少降雨量。该城市的降雨预测可能取决于一段时间内的温度、风速测量、压力、距离海洋的距离、海拔等。如果我们想要预测降雨,这些变量是有意义的。同时,例如城市中电影院的数量、城市是否是国家的首都或城市中红色汽车的数量可能不会影响降雨的预测。在这种情况下,如果我们不使用城市中电影院的数量来预测降雨量,那么它将不会降低系统的性能。解决方案很有可能仍然能够表现良好。因此,在这种情况下,通过放弃这样的变量不会丢失任何信息,当然,我们可以将其从数据集中删除。另一方面,移除温度或距离海洋的变量很可能会对预测产生负面影响。这是一个非常简单的例子,用于强调减少变量数量的必要性。

维度或变量数量可以通过手动和基于算法的方法的组合来减少。但在详细研究它们之前,我们应该了解一些数学术语和组件,然后再继续,下面我们将对此进行讨论。

3.3.1 数学基础

有很多数学术语是必须掌握的,以便全面了解降维方法。

我们正在尝试减少数据集的维度。数据集只是一个值矩阵——因此很多概念与矩阵操作方法、它们的几何表示以及对这些矩阵进行变换有关。数学概念在本书的附录数学基础中进行了讨论。您还需要理解特征值和特征向量。这些概念将在整本书中被重复使用,因此它们已经放在附录中供快速查阅。在继续之前,建议您先阅读这些内容。现在我们将探讨一些手动的降维方法,然后再转向基于算法的方法。

3.4 手动降维方法

为了解决维度灾难,我们希望减少数据集中的变量数量。可以通过从数据集中移除变量来实现减少。或者,一个非常简单的解决方案是合并那些可以逻辑分组或用共同的数学运算表示的变量。

例如,如下表 3.1 所示,数据可以来自零售商店,不同的客户产生了不同的交易。我们将获得每个客户一段时间内的销售额、发票数和购买的商品数量。在下表中,客户 1 产生了两张发票,总共购买了 5 件商品,并产生了 100 的销售额。

如果我们希望减少变量的数量,我们可以将三个变量合并为两个变量。在这里,我们引入了变量 ATV(平均交易价值)和 ABS(平均篮子大小),其中 ATV = 销售额/发票数,ABS = 商品数量/发票数。

因此,在第二个表中,对于客户 1,我们有 ATV 为 50,ABS 为 2.5。因此,变量的数量已经从三个减少到两个。该过程只是一个示例,展示了如何可以结合各种变量。这并不意味着我们应该用 ATV 替换销售额作为一个变量。

表 3.1 在第一张表中,我们有销售额、发票和商品数量作为变量。在第二个表中,它们被合并为创建新变量。

03_T05

这个过程可以继续减少变量的数量。同样地,对于一个电信用户,我们将拥有一个月内手机通话的分钟数。我们可以将它们相加,创建一个单一的变量 - 一个月内使用的分钟数。上面的例子是非常基础的,用于起步。使用手动过程,我们可以采用另外两种常用的方法 - 手动选择和使用相关系数。

3.4.1 手动特征选择

继续上一节中讨论的降雨预测示例 - 数据科学家可能能够去掉一些变量。这将基于对手头的业务问题和相应数据集的深刻理解。然而,这是一个潜在的假设,即数据科学家能够充分理解数据集,并对业务领域有深刻理解。大部分时间,业务利益相关者将能够指导这样的方法。同样重要的是,变量是独特的,并且不存在太多的依赖性。

如下表 3.2 所示,我们可以删除一些对于预测降雨可能没有用的变量。

表 3.2 在第一个表中,我们有数据集中所有的变量。使用业务逻辑,一些可能没有太多用处的变量在第二个表中被丢弃了。但这需要谨慎处理。最好的方法是从业务利益相关者那里得到指导。

03_T06

有时,特征选择方法也被称为包装器方法。在这里,机器学习模型使用变量的子集进行包装或拟合。在每次迭代中,我们将得到不同的结果集。选择生成最佳结果的集合用于最终模型。

接下来的方法是基于各种属性之间的相关性而存在的。

3.4.2 相关系数

两个变量之间的相关性简单地意味着它们彼此具有相互关系。一个变量值的变化将影响另一个变量的值,这意味着一个变量的数值相似的数据点也在另一个变量中具有相似的数值。高度相关的变量彼此提供类似的信息,因此其中一个可以被舍弃。

相关性在书的附录数学基础中有详细描述。

例如,对于零售店,一天内产生的发票数量与产生的销售额将高度相关,因此可以舍弃其中之一。另一个例子是 - 学习时间更长的学生通常比学习时间较短的学生成绩更好(大多数情况下!)。

但在放弃变量时我们应该小心,不应仅仅依赖相关性。在做出任何决定之前,应该充分了解变量的业务背景。

在从研究中删除任何变量之前,与业务利益相关者讨论是个好主意。

基于相关性的方法有时被称为过滤方法。使用相关系数,我们可以过滤并选择最重要的变量。

 自测题 – 回答这些问题以检查你的理解。答案在书的末尾

1.   如果我们觉得一个变量不需要,我们可以简单地将其舍弃。是或否。

2.   如果两个变量相关,总是舍弃其中一个。是或否。

手动方法是更简单的解决方案,可以相当高效地执行。数据集的大小被减小,我们可以继续进行分析。但是手动方法有时是主观的,并且在很大程度上取决于手头的业务问题。许多时候,不可能使用手动方法进行降维。在这种情况下,我们有基于算法的方法,我们将在下一节中学习。

3.4.3 基于算法的降维方法

在上一节中,我们讨论了手动方法。从那里继续,我们将在本节中研究基于算法的方法。基于算法的技术是基于更数学的基础的,因此证明是更科学的方法。在现实世界的业务问题中,我们使用手动和基于算法的技术的组合。与基于算法的技术相比,手动方法执行起来更直接。此外,我们无法评论两种技术的比较,因为它们基于不同的基础。但与此同时,你必须在实施基于算法的技术时尽职尽责。

降维中使用的主要技术如下所示。我们将在本书中探讨其中大部分。

  1. 主成分分析(PCA)

  2. 奇异值分解(SVD)

  3. 线性判别分析(LDA)

  4. 广义判别分析(GDA)

  5. 非负矩阵分解(NMF)

  6. 多维缩放(MDS)

  7. 局部线性嵌入(LLE)

  8. 等距映射

  9. 自编码器

  10. t-SNE(T 分布随机邻域嵌入)

这些技术被用于一个共同的最终目标 - 将数据从高维空间转换为低维空间。一些数据转换是线性的,而一些是非线性的。

我们将在本章详细讨论主成分分析(PCA)和奇异值分解(SVD)。在本书的后续章节中,将探讨其他主要技术。也许,PCA 是我们在下一节中探讨的最常引用的降维方法。

3.5 主成分分析(PCA)

想象一下:你正在处理一个有 250 个变量的数据集。几乎不可能可视化这样一个高维空间。这 250 个变量中的一些可能彼此相关,一些可能不相关,并且有必要减少变量的数量而不丢失太多信息。主成分分析或 PCA 允许我们在数学上选择最重要的特征并留下其他特征。PCA 确实减少了维数,但也保留了变量之间最重要的关系和数据集中的重要结构。因此,变量的数量减少了,但数据集中的重要信息得到了保留。

主成分分析(PCA)是将高维数据投影到低维空间的过程。简单来说,我们将一个 n 维空间降维到一个 m 维空间(其中 n > m),同时保持原始数据集的本质和基本特征。在这个过程中,旧的变量被降维为新的变量,同时保持了原始数据集的关键信息,新创建的变量称为主成分。主成分是原始变量的线性组合。由于这种转换,第一个主成分捕获了数据集中的最大随机性或最高方差。创建的第二个主成分与第一个主成分正交。

如果两条直线彼此正交,意味着它们相互成90⁰的角度,

这个过程一直持续到第三个成分等等。正交性使我们能够保持后续主成分之间没有相关性。

PCA 利用数据集的线性变换,这样的方法有时被称为特征投影。结果数据集或投影用于进一步的分析。

让我们通过一个例子更好地理解。在下面(表 3.3)所示的例子中,我们用一些变量来表示一个家庭的总感知价值。这些变量包括面积(平方米)、卧室数、阳台数、距离机场的距离、距离火车站的距离等等——我们有 100 多个变量。

表 3.3 房价估算的基于的变量

03_T07

我们可以数学上和逻辑上结合一些变量。PCA 将创建一个新变量,它是一些变量的线性组合,如下面的示例所示。它将得到原始变量的最佳线性组合,以便新变量能够捕获数据集的最大方差。方程式 3.1 仅是为了说明目的而显示的一个示例,在这个示例中我们展示了一个新变量,它是其他变量的组合。

(方程式 3.1)

new_variable = a面积 – b卧室数 + c距离 – d学校数

现在让我们通过视觉概念来理解这个概念。在矢量空间图中,我们可以如下图 3.3 所示地表示数据集。第一张图代表了原始数据,我们可以在 x-y 图表中可视化变量。如上所述,我们希望创建变量的线性组合。换句话说,我们希望创建一个数学方程,能够解释 x 和 y 之间的关系。

这种过程的输出将是一条直线,如图 3.3 中的第二张图所示。这条直线有时被称为最佳拟合线。利用这条最佳拟合线,我们可以预测给定 x 值的 y 值。这些预测实际上就是数据点在直线上的投影。

如下图 3.3 中的第三个图所示,实际值与投影之间的差异是误差。这些误差的总和被称为总投影误差。

图 3.3 (i) 数据集可以在向量空间图中表示 (ii) 直线可以称为最佳拟合线,其具有所有数据点的投影 (iii) 实际值与投影之间的差异是误差项。

03_04

如下图 3.4 所示,这条直线可以有多种选项。这些不同的直线将具有不同的误差和捕捉到的方差值。

图 3.4 数据集可以用多条直线来捕捉,但并非所有直线都能捕捉到最大的方差。给出最小误差的方程将被选定。

03_05

能够捕捉最大方差的直线将被选定。换句话说,它给出了最小的误差。它将是第一个主成分,最大扩展方向将是主轴

第二主成分将以类似的方式导出。由于我们知道第一个主轴,我们可以从总方差中减去沿着该主轴的方差以获得残差方差。换句话说,使用第一个主成分,我们将在数据集中捕捉一些方差。但数据集中仍有部分总方差尚未由第一个主成分解释。未解释的总方差部分是残差方差。使用第二主成分,我们希望尽可能多地捕捉方差。

使用相同的过程来捕捉最大方差的方向,我们将得到第二个主成分。第二主成分可以与第一个主成分呈多个角度,如图 3.5 所示。数学上已经证明,如果第二主成分与第一个主成分正交,那么我们可以使用两个主成分来捕捉最大方差。在图 3.5 中,我们可以观察到两个主成分彼此之间呈 90⁰ 角。

图 3.5 (i) 左侧的第一个图是第一个主成分。 (ii) 第二主成分可以相对于第一个主成分处于不同角度。我们必须找到第二主成分,它允许捕捉最大方差 (iii) 为了捕捉最大方差,第二主成分应与第一个主成分正交,因此组合方差被最大化。

03_06

第三、第四个主成分等依此类推。随着主成分的增多,向量空间中的表示变得难以可视化。你可以将其想象成一个带有多个轴的向量空间图。一旦所有主成分都被导出,数据集就会投影到这些轴上。这个转换后的数据集中的列是主成分。创建的主成分数量会少于原始变量的数量,并且捕获数据集中存在的最大信息。

在我们深入研究 PCA 过程之前,让我们先了解其重要特性:

  • PCA 的目标是减少结果数据集中的维数。

    • PCA 生成的主成分旨在通过最大化特征方差来减少数据集中的噪声。

    • 同时,主成分减少了数据集中的冗余。这是通过最小化特征对之间的协方差实现的。

    • 原始变量不再存在于新创建的数据集中。相反,使用这些变量创建新变量。

    • 主成分不一定会与数据集中的所有变量一一对应。它们是现有变量的新组合。因此,它们可以是一个主成分中多个不同变量的组合(如方程式 3.1 所示)。

    • 从数据集创建的新特征不共享相同的列名。

    • 原始变量可能彼此相关,但新创建的变量彼此不相关。

    • 新创建的变量数量少于原始变量的数量。我们选择主成分数量的过程已在 Python 实现部分进行了描述。毕竟,降维的整个目的就在于此。

    • 如果 PCA 用于减少训练数据集中的变量数量,则必须使用 PCA 减少测试/验证数据集。

  • PCA 不等同于降维。它可以用于许多其他用途。一般来说,仅仅将 PCA 用于降维是错误的。

我们现在将研究在实现 PCA 时采用的方法,然后我们将使用 PCA 开发一个 Python 解决方案。虽然我们在开发代码时不需要应用所有步骤,因为这些重活已经由包和库完成。下面给出的步骤已由这些包处理,但仍然必须理解这些步骤,以正确理解 PCA 的工作原理。

PCA 所遵循的步骤是:

  1. 在 PCA 中,我们首先对数据集进行标准化。 这确保我们所有的变量都具有共同的表示并且可比较。 我们有方法在 Python 中执行标准化,我们将在开发代码时学习。 要更多了解数据集的标准化,请参考附录数学基础。

  2. 在标准化数据集中获取协方差。 这使我们能够研究变量之间的关系。 我们通常创建如下所示的协方差矩阵,如下一节的 Python 示例所示。

  3. 然后我们可以计算协方差矩阵的特征向量和特征值。

  4. 然后,我们按照特征值的降序对特征值进行排序。 选择与最大特征值对应的特征向量。 因此所选的组件将能够捕获数据集中的最大方差。 还有其他方法来列出主要组件,我们将在开发 Python 代码时进行探讨。

 快速测验 - 回答这些问题以检查您的理解。 书的末尾有答案

  1. PCA 将导致数据集中变量的数量相同。 真还是假。

  2. PCA 将能够在数据集中捕获 100%的信息。 真还是假。

  3. 选择 PCA 中主要组件的逻辑是什么?

因此,从本质上讲,主成分是原始变量的线性组合。 这种线性组合中的权重是满足最小二乘法误差标准的特征向量。 我们现在正在研究特征值分解,而奇异值分解将在下一节(3.6)中介绍。

3.5.1 特征值分解

我们在上一节中学习了 PCA,我们说主要组件是原始变量的线性组合。 现在我们将探讨 PCA 的特征值分解。

在 PCA 的上下文中,特征向量将表示矢量的方向,特征值将是沿着该特征向量捕获的方差。 下面的图 3.6 可以说明,我们正在将原始的 nxn 矩阵分解为组件。

图 3.6 使用特征值分解,原始矩阵可以分解为特征向量矩阵,特征值矩阵和特征向量矩阵的逆。 我们使用这种方法实现 PCA。

03_07

从数学上讲,我们可以通过方程式 3.2 来表示关系。

(方程式 3.2)

Av = λv

其中 A 是方阵,v 是特征向量,λ是特征值。 在这里,重要的是要注意特征向量矩阵是正交矩阵,其列是特征向量。 特征值矩阵是对角线矩阵,其特征值是对角线元素。 最后一个组件是特征向量矩阵的逆。 一旦我们有了特征值和特征向量,我们就可以选择显著的特征向量来获取主成分。

我们在本书中将 PCA 和 SVD 作为两种单独的方法进行介绍。这两种方法都用于将高维数据降维到较少的维度,并在此过程中保留数据集中的最大信息量。两者的区别在于 - SVD 存在于任何类型的矩阵(矩形或方形),而特征值分解仅适用于方形矩阵。等我们在本章后面介绍 SVD 时,你会更好地理解它。

我们现在将使用特征值分解创建一个 Python 解决方案。

3.5.2 使用 PCA 的 Python 解决方案

我们已经学习了 PCA 的概念和使用特征值分解的过程。现在是时候进入 Python 并在数据集上开发一个 PCA 解决方案了。我们将向你展示如何在数据集上创建特征向量和特征值。为了实现 PCA 算法,我们将使用sklearn库。库和包提供了一种更快的实现算法的解决方案。

我们将使用鸢尾花数据集来解决这个问题。这是用于机器学习问题的最受欢迎的数据集之一。该数据集包含三种鸢尾花的数据,每种鸢尾花有 50 个样本,并且具有每朵花的特性 - 如花瓣长度,萼片长度等。问题的目标是使用花的特性来预测物种。因此,独立变量是花的特性,而变量“物种”是目标变量。数据集和代码已经提交到 GitHub 仓库。在这里,我们使用内置的 PCA 函数来减少实现 PCA 所需的工作量。

步骤 1: 首先加载所有必要的库。我们将使用 numpy、pandas、seaborn、matplotlib 和 sklearn。请注意,我们从 sklearn 中导入 PCA。

这些是最标准的库。你会发现几乎所有的机器学习解决方案都会在解决方案笔记本中导入这些库。

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

步骤 2: 现在加载数据集。它是一个.csv 文件。

iris_df = pd.read_csv('IRIS.csv')

步骤 3: 现在我们将对数据集进行基本检查 - 查看前五行,数据的形状,变量的分布等。我们在这里不进行详尽的探索性数据分析,因为这些步骤在第二章中已经覆盖了。数据集有 150 行和 6 列。

iris_df.head()

03_07a

iris_df.describe()

03_07b

iris_df.shape

步骤 4: 这里我们需要将数据集分成自变量和因变量。X_variables 代表自变量,它们位于数据集的前 4 列,而 y_variable 是因变量,在这种情况下是物种,是数据集中的最后一列。回想一下,我们希望使用其他属性来预测花的物种。因此,我们将目标变量物种和其他自变量分开。

X_variables = iris_df.iloc[:,1:5]
X_variables
y_variable = iris_df.iloc[:,5]

步骤 5: 现在我们正在对数据集进行标准化。StandardScalar() 内置方法可以很容易地完成这项工作。

StandardScalar 方法为我们对数据集进行了归一化处理。它从变量中减去均值,然后除以标准差。有关归一化的更多细节,请参阅附录数学基础知识。

我们调用该方法,然后在我们的数据集上使用它来获得转换后的数据集。由于我们正在处理自变量,所以这里使用了 X_variables。首先我们调用了 StandardScalar()方法。然后使用 fit_transform 方法。fit_transform 方法首先将转换器拟合到 X 和 Y,然后返回 X 的转换版本。

sc = StandardScaler()
transformed_df = sc.fit_transform(X_variables)

第 6 步: 现在我们将计算协方差矩阵,并将其打印出来,输出如下所示。使用 numpy 很容易得到协方差矩阵。

covariance_matrix = np.cov(transformed_df.T)
covariance_matrix

03_07c

第 7 步: 现在,在这一步中正在计算特征值。在 numpy 库中,我们有内置功能来计算特征值。然后我们按降序对特征值进行排序。为了筛选主成分,我们可以选择大于 1 的特征值。这个标准被称为Kaiser 准则。我们也在探索其他方法。

特征值代表一个成分作为数据摘要的优劣程度。如果特征值为 1,意味着该成分包含与单个变量相同数量的信息。因此我们选择大于 1 的特征值。

在此代码中,我们首先获得eigen_valueseigen_vectors。然后按降序排列它们。

eigen_values, eigen_vectors = np.linalg.eig(covariance_matrix)
eigen_pairs = [(np.abs(eigen_values[i]), eigen_vectors[:,i]) for i in range(len(eigen_values))]
print('Eigenvalues arranged in descending order:')
for i in eigen_pairs:
    print(i[0])

03_07d

第 8 步: 现在,我们将从sklearn库中调用 PCA 方法。该方法用于在这里拟合数据。需要注意的是,我们尚未确定在这个问题中希望使用多少个主成分。

pca = PCA()
pca = pca.fit(transformed_df)

第 9 步: 主成分现在已经被确定。让我们来看看它们解释的方差。我们可以观察到第一个成分解释了 72.77%的变化,第二个解释了 23.03%的变化,依此类推。

explained_variance = pca.explained_variance_ratio_
explained_variance

03_07e

第 10 步: 现在我们正在绘制一个条形图来展现这些成分以获得更好的可视化效果。

dataframe = pd.DataFrame({'var':pca.explained_variance_ratio_,
             'PC':['PC1','PC2','PC3','PC4']})
sns.barplot(x='PC',y="var", 
           data=dataframe, color="b");

03_07f

第 11 步: 我们绘制一个屏风图来可视化主成分解释的累积方差。

plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('number of components')
plt.ylabel('cumulative explained variance')
plt.show()

03_07g

第 12 步: 在这个案例研究中,如果我们选择前两个主成分作为最终解决方案,因为这两个成分捕获了数据集中 95.08%的总方差。

pca_2 = PCA(n_components =2 )
pca_2 = pca_2.fit(transformed_df)
pca_2d = pca_2.transform(X_variables)

第 13 步: 现在,我们将根据两个主成分来绘制数据集。对于此操作,有一个要求,即物种必须被映射回物种变量的实际值,即山鸢尾、变色鸢尾和维吉尼亚鸢尾。在这里,0 对应山鸢尾,1 对应变色鸢尾,2 对应维吉尼亚鸢尾。在下面的代码中,首先使用上述映射替换了物种变量的值。

iris_df['Species'] = iris_df['Species'].replace({'Iris-setosa':0, 'Iris-versicolor':1, 'Iris-virginica':2})

第 14 步: 现在我们将对两个主成分绘制结果。这个图表显示了刚刚创建的数据集被减少到两个主成分。这些主成分能够捕捉 95.08%的数据集方差。图中,第一主成分代表图表的 x 轴,而第二主成分代表图表的 y 轴。颜色代表了不同类别的物种。

plt.figure(figsize=(8,6))
plt.scatter(pca_2d[:,0], pca_2d[:,1],c=iris_df['Species'])
plt.show()

03_07h

以上解决方案将成分数量从四减少到 2,并且仍能保留大部分信息。在这里,我们已经检查了三种选择主成分的方法 - 基于凯撒标准、捕获的方差以及剪切图。

让我们快速分析一下使用 PCA 我们所取得的成果。图 3.7 展示了同一数据集的两种表示形式。左边是 X 变量的原始数据集。它有四个变量和 150 行。右边是 PCA 的输出。它有 150 行,但只有两个变量。回顾一下,我们已经将维度从四减少到了两。因此观测数量仍然是 150,而变量的数量已经从四个减少到两个。

图 3.7 左侧的图展示了原始数据集,它有 150 行和 4 个变量。在实施 PCA 后,变量的数量已被减少到了两个。行数仍然是 150,这由 pca_2d 的长度表明。

03_08

一旦我们减少了成分的数量,我们可以继续实施监督学习或无监督学习的解决方案。我们可以将以上解决方案应用于其他真实世界问题中,这些问题中我们的目标是减少维度。您将在案例研究部分进一步探讨这个问题。

通过这一部分,我们已经学习了 PCA。Github 仓库中包含了一个带有变量和相应图表的非常有趣的 PCA 分解。接下来我们将在下一节探讨奇异值分解(SVD)。

3.6 奇异值分解(SVD)

在上一节中,我们学习了主成分分析(PCA)。PCA 线性地转换数据并生成彼此不相关的主成分。但是特征值分解的过程只能应用于方阵。而奇异值分解(SVD)可以应用于任何 m x n 矩阵。我们现在将更详细地学习这一点。

让我们考虑我们有一个矩阵 A。A 的形状是 m x n,或者包含 m 行和 n 列。A 的转置可以表示为 A^T。

我们可以使用 A 和 A^T 创建另外两个矩阵作为A A^T 和A^TA。这些结果矩阵A A^T 和A^TA具有一些特殊的性质,这些特性如下所列。这些性质的数学证明超出了本书的范围。

A A^T 和 A^TA 的性质是:

  • 它们都是对称的方阵。

    • 它们的特征值要么是正的,要么是零。

    • A A^T 和 A^TA(带上▲)具有相同的特征值。

  • A A^T 和 A^TA 的秩与原矩阵 A 相同。

A A^T 和 A^TA 的特征向量分别被称为 A 的奇异向量。它们的特征值的平方根被称为奇异值

由于这两个矩阵(A A^T 和 A^TA)都是对称的,它们的特征向量互相正交。换句话说,由于是对称的,特征向量是相互垂直的,并且可以具有单位长度。

现在,有了这种数学理解,我们可以定义 SVD。根据奇异值分解方法,可以将任何矩阵 A 分解为

(公式 3.3)

A = U * S * V^T

在这里,A 是原始矩阵,

U 和 V 是正交矩阵,它们的正交特征向量来自于 A A^T 或 A^TA,分别是

S 是对角矩阵,其 r 个元素等于奇异值。

简单地说,SVD 可以被视为使用特征值分解对 PCA 方法进行增强。

奇异值分解比特征值分解更好,且在数值上更健壮。

PCA 被定义为使用主成分对输入变量进行线性转换。所有线性转换、选择最佳成分等概念都保持相同。主要的过程步骤保持相似,除了在 SVD 中,我们使用了稍微不同的方法,其中特征值分解被替换为使用奇异向量和奇异值。通常建议在数据集稀疏时使用 SVD,在数据集较密集时使用 PCA。

 小测验——回答这些问题以检查您的理解力。本书末尾将给出答案。

1.   SVD 基于特征值分解技术。是真还是假?

2.   PCA 比 SVD 更具鲁棒性。是真还是假?

3.   SVD 中奇异值和奇异向量是什么?

接下来,在下一节中,我们将使用 SVD 创建一个 Python 解决方案。

3.6.1 使用 SVD 的 Python 解决方案。

在此案例研究中,我们使用的是“蘑菇”数据集。该数据集包含 23 个烤蘑菇物种的描述。有两个类别——蘑菇是可食用的,“e”,否则蘑菇是有毒的,“p”。

步骤 1: 导入库。我们正在导入

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder, StandardScaler

步骤 2: 导入数据集并检查形状、头等。

mushrooms_df = pd.read_csv('mushrooms.csv')
mushrooms_df.shape
mushrooms_df.head()

03_08a

步骤 3: 我们可以观察到,数据集中的值是分类的。它们必须首先被编码为数字值。这不是处理分类变量的唯一方法。本书的最后一章将探讨其他技术。我们将深入探索这些技术。

首先,我们调用 LabelEncoder,然后将其应用到数据集中的所有列。LabelEncoder 使用一种独热编码方法将分类变量转换为数字变量。

encoder = LabelEncoder()
for col in mushrooms_df.columns:
    mushrooms_df[col] = encoder.fit_transform(mushrooms_df[col])

步骤 4: 重新查看数据集。所有分类值都已转换为数字值。

mushrooms_df.head()

03_08b

第 5 步: 接下来的两步与上一个案例研究相同,在这两步中,我们将数据集分解为 X 变量和 y 标签。然后对数据集进行标准化。

X_variables = mushrooms_df.iloc[:,1:23]
y_label = mushrooms_df.iloc[:, 0]
scaler = StandardScaler()
X_features = scaler.fit_transform(X_variables)

第 6 步: 在这一步中,我们实现了 SVD。在 numpy 中有一个实现 SVD 的方法。输出是 u、s 和 v,其中 u 和 v 是奇异向量,s 是奇异值。如果您愿意,您可以分析它们各自的形状和维度。

u, s, v = np.linalg.svd(X_features, full_matrices=True)

第 7 步: 我们知道奇异值允许我们计算每个奇异向量解释的方差。我们现在将分析每个奇异向量解释的%方差,并绘制出来。结果显示到小数点后三位。然后我们将结果绘制成直方图。在 x 轴上是奇异向量,而在 y 轴上是%解释的方差。

variance_explained = np.round(s**2/np.sum(s**2), decimals=3)
variance_explained
sns.barplot(x=list(range(1,len(variance_explained)+1)),
            y=variance_explained, color="blue")
plt.xlabel('SVs', fontsize=16)
plt.ylabel('Percent of the variance explained', fontsize=15)

03_08c

第 8 步: 现在我们将创建一个数据框。这个新的数据框 svd_df 包含了前两个奇异向量和元数据。然后我们使用 head 命令打印前 5 行。

col_labels= ['SV'+str(i) for i in range(1,3)]
svd_df = pd.DataFrame(u[:,0:2], index=mushrooms_df["class"].tolist(), columns=col_labels)
svd_df=svd_df.reset_index()
svd_df.rename(columns={'index':'Class'}, inplace=True)
svd_df.head()

03_08d

第 9 步: 与上一案例类似,我们将数值替换为实际的类标签。1 代表可食用,而 0 代表有毒。

svd_df['Class'] = svd_df['Class'].replace({1:'Edible', 0:'Poison'})

第 10 步: 我们现在正在绘制两个组件解释的方差。这里我们只选择了前两个组件。建议您使用上一节描述的方法选择最佳的组件数量,并绘制相应的散点图。在这里,x 轴上显示了第一个奇异向量 SV1,而 y 轴上显示了第二个奇异向量 SV2。

color_dict = dict({'Edible':'Black',
                   'Poison': 'Red'})
sns.scatterplot(x="SV1", y="SV2", hue="Class", 
                palette=color_dict, 
                data=svd_df, s=105,
                alpha=0.5)
plt.xlabel('SV 1: {0}%'.format(variance_explained[0]*100), fontsize=15)
plt.ylabel('SV 2: {0}%'.format(variance_explained[1]*100), fontsize=15)

03_08e

我们可以观察两个类别相对于两个组件的分布情况。两个类别 - 可食用和有毒 - 被分别以黑色和红色编码。正如我们上面所指出的,我们只选择了两个组件来展示使用可视化图的影响。建议您使用上一个案例研究中描述的方法选择最佳的组件数量,然后使用不同的奇异向量来可视化结果。这个解决方案可以用来在实际数据集中减少维度。

这就结束了我们对 SVD 的讨论。我们将在下一节中观察 PCA 和 SVD 的优势和挑战。

3.7 维度缩减的优缺点

在本章的开头部分,我们讨论了维度诅咒的缺点。在最后几节中,我们了解了 PCA 和 SVD,并使用 Python 进行了实现。在当前部分,我们将审查这些技术的优势和挑战。

实施 PCA 或 SVD 时我们获得的主要优势如下:

  • 降低维度会减少数据集的复杂性。相关特征被移除并进行了转换。手动处理相关变量是一项相当手动和令人沮丧的工作。PCA 和 SVD 等技术可以很容易地帮助我们完成这项工作。相关特征的数量被最小化,并且整体维度被降低。

    • 如果维度较少,数据集的可视化效果会更好。极高维度的数据集很难进行可视化。

    • 如果移除相关变量,机器学习模型的准确性会得到改善。这些变量不会对模型的性能有所贡献。

    • 训练时间减少因为数据集的复杂性较低。因此,需要较少的计算力和时间。

  • 在监督机器学习模型中,过拟合是一个讨厌的问题。这是一种情况,模型在训练数据集上表现良好,但在测试/验证数据集上却表现不佳。这意味着模型可能无法在真实世界的未知数据集上表现良好。而这违背了构建机器学习模型的整个目的。PCA/SVD 通过减少变量数量来帮助解决过拟合问题。

与此同时,我们在降维技术中面临一些挑战,如下所述:

  • PCA/SVD 创建的新组件不太可解释。它们是数据集中独立变量的组合,实际上与真实世界没有关系,因此很难将它们与真实世界的情况联系起来。

    • PCA/SVD 需要数值变量。因此,所有分类变量必须以数值形式表示。

    • 在实施解决方案之前,需要对数据集进行归一化/标准化

  • 当使用 PCA 或 SVD 时可能会发生信息损失。主成分无法取代原始数据集,因此在实施这些方法时可能会有一些信息损失。

但尽管存在一些挑战,PCA 和 SVD 用于降低数据集的维度。它们是最受欢迎的方法之一,也被广泛使用。与此同时,必须注意的是,这些都是线性方法,我们将在本书的后期部分介绍非线性方法。

通过这样,我们已经涵盖了在降维中使用的两种重要技术。我们将在后面的章节中探讨更高级的技术。是时候转到案例研究了,这是本章的下一部分内容。

3.8 降维案例研究

现在我们将探索一个真实案例,以了解 PCA 和 SVD 在真实商业场景中的使用。

想象一下:你正在为一个电信服务提供商工作。你有一个订户基础,并希望根据许多参数对消费者进行聚类。但挑战在于需要分析的庞大维度。

目标是使用降维算法减少属性的数量。消费者数据集可以如下所示。

  1. 订户的人口统计详情将包括年龄、性别、职业、家庭规模、婚姻状况等。下面显示的列表并不全面。

表格 3.4 订户的人口统计详情,如年龄、性别、婚姻状况、家庭规模、城市等。

03_T08

  1. 消费者的订阅详情可能如下表所示。下面显示的列表并不全面。

表格 3.5 订户的订阅详情,如服务期限、预付费/后付费连接等。

03_T09

  1. 消费者的使用情况将描述分钟数、通话费率、数据使用情况、服务等。下面显示的列表并不全面。

表格 3.6 订户的使用情况指定了使用的分钟数、发送的短信、使用的数据、在网络中度过的天数、国内或国际使用情况等。

03_T10

  1. 订户的付款和交易详情将涉及到所做的各种交易、付款方式、付款频率、自上次付款以来的天数等。

表格 3.7 展示了订户的交易详情,包括金额、模式等的所有详情。

03_T11

数据集可能有更多属性。到目前为止,我们已经确定所涉及的变量数量确实很高。一旦我们汇总了所有这些数据点,最终数据的维数就可能非常庞大。

表格 3.8 最终数据集是所有上述数据集的组合。这将是一个庞大的、真正高维的数据集,需要进行分析。

03_T12

在我们能够进行任何监督或无监督解决方案之前,我们必须减少属性的数量。在本章中,我们专注于降维技术,因此步骤涵盖了该过程的这一方面。在后面的章节中,我们将更详细地研究探索性分析。

作为第一步,我们将对数据集进行健全性检查并进行数据清理。我们将检查数据点的数量,缺失值的数量,重复值,存在的垃圾值等。这将使我们能够删除可能非常稀疏或包含不多信息的任何变量。例如,如果性别仅适用于客户基数的 0.01%,则去除该变量可能是个不错的主意。或者如果所有客户的性别都为男性,该变量对我们没有添加任何新信息,因此可以丢弃。有时,使用业务逻辑,可能会从数据集中删除一个变量。在前面的部分中已经讨论了一个例子。在这一步骤中,我们可能会组合一些变量。例如,我们可能通过将总支出金额除以总交易数来创建一个新变量作为平均交易值。以这种方式,我们将能够减少一些维度。

一个 Python Jupyter 笔记本可在 Github 存储库中找到,我们在那里提供了对数据清理步骤的非常详细的解决方案。

基本数据清洗完成后,我们开始进行探索性数据分析。作为探索性分析的一部分,我们检查变量的分布、其分布情况,数值变量的平均数/中位数/众数。这有时被称为单变量分析。这一步骤允许我们测量变量的分散程度,了解中心趋势,检查分类变量的不同类别的分布,并查找值中的任何异常情况。例如,使用上述提到的数据集,我们将有兴趣分析数据使用的最高/最低/平均值,或者性别或年龄的%分布。我们想知道最受欢迎的交易方式,我们也对交易的最高/最低/平均金额感兴趣。等等,这个列表还可以继续下去。

然后我们探索变量之间的关系,这被称为双变量分析。交叉表,数据分布是双变量分析的一部分。在这一步骤, 创建了一个相关矩阵。需要仔细研究高度相关的变量。根据业务逻辑, 其中一个变量可能会被丢弃。这一步骤有助于可视化和理解一个变量在其他变量存在的情况下的行为。我们可以检查它们的相互关系和关系的强度。在本案例研究中, 我们将回答诸如 - 对比使用更多数据的订阅者与发送更多短信的订阅者, 是否在网络上花费更多时间。或假设 - 使用在线模式进行交易的订阅者是否比使用现金进行交易的订阅者产生更多收入。或者性别/年龄与数据使用之间是否存在关系。在项目的这个阶段, 将回答许多这样的问题。

Github 存储库中提供了 Python Jupyter 笔记本,提供了单变量和双变量阶段的详细步骤和代码。来看一下吧!

在此阶段,我们拥有一个具有大量维度的数据集,并希望减少维数。现在是实施 PCA 或 SVD 的好时机。这些技术将减少维数,并使数据集准备好在过程的下一步中使用,如图 3.8 所示。这张图仅是一个代表性的结构,用于描述维数约简方法的影响。请注意,左图中的大量黑线正在缩减为右图中较少的红线。

图 3.8 非常高维的数据集将通过捕获数据集中的最大方差来减少到低维度

03_09

降维方法的输出将是一个少于原始变量数量的数据集。该数据集可以用于监督学习或无监督学习。我们已经在本章前面的章节中介绍了使用 Python 的例子。

这结束了我们关于电信用户的案例研究。这种情况可以扩展到其他领域,如零售、银行金融、航空、医疗保健、制造等。

现在我们将进入本章的总结。

3.9 总结

数据无处不在,以各种形式、层次、维度呈现,具有不同的复杂程度。人们经常提到“数据越多越好”。在一定程度上这确实是正确的。但是,如果维度非常高,从中获取有用信息将会非常困难。数据分析可能会出现偏差,并且处理起来非常复杂。我们在本章探讨了维数灾难。PCA / SVD 可以帮助减少这种复杂性。他们使数据集准备好下一步。

但是维数约简并不像看起来那么简单。这不是一个容易完成的任务。但它确实是一个非常有回报的任务。并需要商业眼光、逻辑和常识的结合来处理。结果数据集可能仍需要一些额外的工作。但这是建立机器学习模型的一个很好的起点。

这标志着第三章的结束。同时也结束了本书的第一部分。在本部分中,我们介绍了更基础的算法。我们从本书的第一章开始探究机器学习的基础和基本知识。在第二章中,我们探讨了三个聚类算法。在第三章中,我们探索了 PCA 和 SVD。

在书的第二部分,我们正在转向并学习更先进的话题。我们将在下一章开始研究关联规则。然后我们进入高级聚类方法,比如时间序列聚类、模糊聚类、GMM 聚类等等。接着是一章关于高级的降维算法,比如 t-SNE、LDA。然后,在第二部分的结束,我们将研究无监督学习在文本数据集上的应用。书的第三部分更加先进,我们将深入基于神经网络的解决方案并使用图像数据集。所以还有很长的路要走!敬请关注!

现在你可以进入问题部分了!

实际的下一步和建议的阅读

  1. 使用上一章节使用的车辆数据集进行聚类,并在其上实施 PCA 和 SVD。比较在实施 PCA 和 SVD 之前和之后进行聚类的性能。

  2. 从 (data.world/datasets/pca) 获取数据集。在这里,你会发现许多数据集,比如联邦计划网络安全、比萨数据集等等。比较在这些数据集上实施 PCA 和 SVD 的性能。

  3. 阅读下面关于主成分分析(PCA)的论文

  4. www.sciencedirect.com/science/article/pii/009830049390090R

  5. web.stanford.edu/~hastie/Papers/spc_jcgs.pdf

  6. web.cs.ucdavis.edu/~vemuri/papers/pcaVisualization.pdf

  7. cseweb.ucsd.edu/~ravir/papers/pca/pamifinal.pdf

  8. 阅读一下关于奇异值分解的研究论文

  9. people.maths.ox.ac.uk/porterm/papers/s4.pdf

  10. papers.nips.cc/paper/3473-quic-svd-fast-svd-using-cosine-trees.pdf

  11. arxiv.org/pdf/1211.7102.pdf

  12. glaros.dtc.umn.edu/gkhome/fetch/papers/sarwar_SVD.pdf

3.10 总结

  • 我们了解到,在数据集中拥有很多维度会引发一个问题,即维度灾难。由于维度灾难的存在,数据集变得非常复杂,处理起来也变得更加耗时。

    • 我们还提到了可以有多种技术来解决维度灾难的问题,比如 PCA、LDA、SVD、自编码器、t-SNE、等等。

    • 我们详细介绍了主成分分析(PCA),在其中我们学到,一个主成分是各种变量的线性组合。使用这种方法,通过具有捕捉数据集中最大方差的主成分,可以减少总维数。

    • 然后我们转向奇异值分解,在这一部分,大部分过程与 PCA 相同,只是 PCA 中的特征值分解被奇异向量和奇异值在 SVD 中替换了。

    • 我们还学到,当得到主成分后,虽然解决了维度灾难的问题,但原始变量却丢失了。

  • 最后,我们使用 sklearn 库对这些技术进行了 Python 实现。

第四章:关联规则

本章内容包括

  • 关联规则学习

  • 不同类型的关联规则算法

  • 不同关联规则算法的实现

  • 使用 SPADE 进行序列学习

  • 案例研究

“关联的力量比美的力量更强大;因此关联的力量就是美的力量– 约翰·罗斯金”

恭喜你完成了本书的第一部分!你探索了无监督学习的基础知识以及 k-means 聚类、层次聚类、DBSCAN、主成分分析等算法。预计你已经掌握了第一部分的数学概念,并创建了解决每章末尾练习的 Python 代码。

欢迎来到本书的第二部分,在这一部分中,我们将利用第一部分学到的概念,并探索稍微复杂一些的主题。我们将从本书的第四章开始学习关联规则。祝你好运!

下次你去附近的杂货店时,看看店内各种物品的摆放。你会发现架子上摆放着牛奶、鸡蛋、面包、糖、洗衣粉、肥皂、水果、蔬菜、饼干和其他各种物品。你有没有想过这种摆放的逻辑是什么,以及这些物品是如何摆放的?为什么某些产品放在一起,而另一些则相距很远?显然,这种摆放不可能是随意的,背后必须有科学的原理。或者你是否想知道,Netflix 是如何根据你的观影历史为你推荐电影的?我们将在本章中找到这些问题的答案。和往常一样,我们将先学习概念。我们将通过数学逻辑来讨论不同算法的优缺点,并使用 Python 进行实际实现。本章末尾提供了一个商业案例研究来补充知识。

欢迎来到第四章,祝一切顺利!

4.1 技术工具包

我们将继续使用迄今为止使用的相同版本的 Python 和 Jupyter 笔记本。本章使用的代码和数据集已经存储在此位置。

本章需要安装几个 Python 库 – apyori、pyECLAT、fpgrowth_py 和 pyspade。除此之外,我们还需要 numpy 和 pandas。使用库,我们可以很快地实现这些算法。否则,编写这些算法将是一项耗时且痛苦的任务。

让我们开始关联规则的学习。

4.2 关联规则学习

你可能听说过著名的“啤酒和尿布故事”。根据这个轶事,超市的顾客(主要是年轻男子)购买尿布的同时也购买啤酒在同一张发票上。换句话说,购买尿布给他们的宝宝的年轻男子在同一笔交易中购买啤酒的概率相当高。我们不评论这个故事的真实性,但是可以归因于此故事的逻辑是关联规则学习

正式来说 - 关联规则可以用来发现数据集中存在的变量之间的有力关系。我们可以使用关联规则来测量数据集中变量之间的相关性和共同出现。在上述示例中(假设故事是真实的),人们可以分析每日的顾客交易。如果啤酒和尿布之间存在关系,这对超市来说是非常强大的洞察力,可以让他们定制啤酒和尿布的摆放位置,或者调整营销策略,甚至改变价格。

我们可以通过超市中的另一个例子来理解。考虑下面的例子。假设通过分析在超市生成的五张发票,我们得到了如下表 4.1 所示的数据。在这个例子中,发票编号 1001 购买了牛奶,因此它的值为 1,而奶酪没有购买,因此它是 0。

表 4.1 超市生成的发票示例。第一个发票编号是 1001,在该发票中购买了牛奶。因此,在牛奶前面有 1。而奶酪在 1001 中没有购买,因此,在奶酪前面有 0。
发票编号 牛奶 鸡蛋 面包 奶酪
1001 1 1 1 0
1002 0 0 0 1
1003 1 1 1 0
1004 0 1 0 1
1005 1 1 0 1

因此,在发票编号 1001 中,购买了牛奶、鸡蛋和面包,而在发票编号 1002 中只购买了奶酪。在这里,我们可以看到每当牛奶和鸡蛋一起购买时,面包总是在同一张发票中购买。这确实是一个重要的发现。

现在将这种理解扩展到一天内数千笔交易。这将导致非常强大的关系,人眼通常忽视,但关联规则算法可以为我们揭示它们。它可以导致更好的产品摆放、产品更好的价格和更优化的营销支出。这些模式将提升客户体验,并且被证明对于改善整体客户满意度非常有用。

我们可以将关联规则可视化,如图 4.1 所示。在这里,有一些表示为节点 1、2、3、4 等的传入变量。这些节点之间存在关联,如箭头所示。它们之间的这种关系导致了规则 A 和 B 的产生。如果我们回顾一下本节开头提到的啤酒/尿布故事,规则 A 可能是,当年轻男性顾客购买尿布时,他们也经常购买啤酒;而规则 B 可能是,当购买牛奶和鸡蛋时,经常也会购买面包。

图 4.1 关联规则可以被视为数据集中各种变量之间的关系。这些变量之间相互关联,并建立了重要的关系。

04_01

上面讨论的超市示例有时被称为市场篮子分析。但是关联规则不仅适用于杂货零售。它们在生物信息学、医疗行业、入侵检测等其他领域的效用已被证明。Netflix 或 Spotify 可以利用它们分析历史用户行为,然后推荐用户最有可能喜欢的内容。Web 开发人员可以分析客户在其网站上的历史点击和使用情况。通过识别模式,他们可以找出用户倾向于点击的内容和哪些功能将最大化他们的参与度。医生可以使用关联规则更好地诊断患者。医生可以比较症状与其他症状之间的关系的概率,并提供更准确的诊断。用例跨越多个业务领域和业务功能。

我们现在将了解关联规则的构建模块。

4.3 关联规则的构建模块

我们在上一节中介绍了关联规则的定义。

现在让我们了解关联规则背后的数学概念。假设我们在零售店有以下数据集-

  1. 让 X = {x[, x[2], x[3], x[4], x[5] …., x[n]} 代表零售店中可用的n种商品。例如,它们可以是牛奶、鸡蛋、面包、奶酪、苹果等等。

  2. 让 Y = {y[, y[2], y[3], y[4], y[5] …., y[m]} 代表在该零售店生成的m笔交易。每笔交易可能包含来自零售店的全部或部分商品。

显然,交易中的每个商品都只能从零售店购买。换句话说,在集合 Y 中的交易中的每个商品都是集合 X 中商品的子集。同时,每个商品都会附带一个唯一的标识符,每个交易都会附带一个唯一的发票号码。

现在,我们有兴趣分析模式并发现关系。这将用于生成任何规则或见解。因此,让我们首先定义规则的含义。

  1. 假设我们发现一条规则,即每当购买列表 P 中的商品时,列表 Q 中的商品也会被购买。这条规则可以写成如下形式:

  2. 规则是P -> Q。这意味着当购买了 P 中定义的物品时,也会购买 Q。

  3. P 中的物品将是 X 的子集或P Í X。

  4. 同样,Q 中的物品将是 X 的子集或Q Í X。

  5. P 和 Q 不能有任何共同的元素,即P Ç Q = 0

现在,让我们通过一个实际案例来理解这些数学概念。

假设 X = {牛奶,香蕉,鸡蛋,奶酪,苹果,面包,盐,糖,饼干,黄油,冷饮料,水}。这是零售店中所有可用的物品。

Y = {1001, 1002, 1003, 1004, 1005}。这是在该零售店生成的五张发票。每张发票中购买的相应物品如表 4.2 所示。

表 4.2 零售店生成的五张发票示例。请注意,对于每张发票,我们对每个物品都有 0 和 1 相关联。这些发票仅用于说明目的。实际发票中物品的数量可以更多。

04_T02

使用这个数据集,让我们假设我们创建了两个规则:{牛奶,香蕉} -> {鸡蛋}和{牛奶,香蕉} -> {面包}。

第一个规则意味着每当购买牛奶和香蕉,鸡蛋也会在同一次交易中购买。第二个规则意味着每当购买牛奶和香蕉,面包也会在同一次交易中购买。通过分析上述数据集,我们可以清楚地看到规则 1 始终为真,而规则 2 不是。

左边的物品称为前项或 LHS,右边的物品称为后项或 RHS。

在现实世界中,对于任何这样的规则来说,同样的模式必须在数百甚至数千个交易中重复出现。只有在这种情况下,我们才会得出这个规则确实是真实存在的,并且可以推广到整个数据库。

与此同时,可能会有很多这样的规则。在一个每天生成数千份发票的零售店中,可能会有数百个这样的规则。我们如何发现哪些规则是重要的,哪些是不重要的呢?这可以通过我们将在下一节学习的支持度、置信度和提升度的概念来理解。

4.3.1 支持度、置信度、提升度和确信度

在上一节中,我们确定了关联规则中规则的含义。我们也理解,基于事务数据集,可能会有数百条规则。在本节中,我们将探讨如何测量这些规则的有效性,并筛选出最有趣的规则。这可以通过支持度、置信度、提升度和确信度的概念来实现。

回顾上一节我们讨论了规则的泛化。支持度、置信度、提升度和确信度可以帮助我们衡量泛化程度。简而言之,使用这四个参数,我们可以确定规则在我们实际的商业中有多么有用。毕竟,如果一个规则不实用或者不够强大,就没有必要实施。支持度、置信度、提升度和确信度是检查规则效力的参数。我们现在将详细介绍这些概念。

我们将使用表 4.3 中的以下数据集来理解支持度、置信度和提升度的概念。

表 4.3 我们将用来理解支持度、置信度和提升度概念的数据集。第一张发票 1001 包含牛奶、鸡蛋和面包,而奶酪没有购买。同样,为了这个例子,我们总共只考虑了 4 个项目。
发票号码 牛奶 鸡蛋 面包 奶酪
1001 1 1 1 0
1002 0 1 1 1
1003 1 1 1 0
1004 0 1 0 1
1005 0 1 1 0

在这里,对于一个发票,1 表示该发票中是否有购买该商品,而 0 则表示该发票中没有购买该商品。例如,发票编号 1001 包含牛奶、鸡蛋和面包,而 1002 包含鸡蛋、面包和奶酪。

现在让我们研究支持度。

支持

支持度测量数据集中项目的频率百分比。换句话说,它测量了项目在数据集中出现的交易百分比。

支持度可以表示如下

04_01a

参考表 4.3,如果我们对规则 {牛奶, 鸡蛋} -> {面包} 感兴趣。在这种情况下,有两笔交易中同时包含这三种项目(牛奶、鸡蛋和面包)。总交易数为五笔。因此,该规则的支持度为 2 / 5,即 0.4 或 40%。

如果我们对规则 {面包, 鸡蛋} -> {奶酪} 感兴趣。在这种情况下,只有一笔交易中同时包含这三种商品。总交易数为五笔。因此,该规则的支持度为 1 / 5,即 0.2 或 20%。

支持度越高,规则越好。通常,我们设定一个最低阈值来获取支持度。最低阈值通常是与业务利益相关者协商确定的。

现在我们将研究规则的置信度。

置信度

置信度测量规则的真实频率。换句话说,它测量了包含前提的交易中也包含结果的百分比。

因此,如果我们想要测量规则 A->B 的置信度

04_01b

这里,分子在交易中同时存在 A 和 B 时为支持度,而分母则指的是仅 A 的支持度。

表 4.3 我们将用于理解支持度、确信度和提升度概念的数据集。第一张发票 1001 有牛奶、鸡蛋和面包,而奶酪没有购买。同样,为了本例,我们只考虑了总共 4 个物品。
发票号码 牛奶 鸡蛋 面包 奶酪
1001 1 1 1 0
1002 0 1 1 1
1003 1 1 1 0
1004 0 1 0 1
1005 0 1 1 0

参考表 4.3,如果我们对规则 {牛奶,鸡蛋} -> {面包}感兴趣。在这种情况下,有两个交易中同时存在牛奶和鸡蛋。因此,支持度为 2/5 = 0.4。这是分母。有两个交易中同时存在三个物品(牛奶,鸡蛋,面包)。因此,支持度为 2/5 = 0.4,这是分子。放入上面的方程,规则 {牛奶,鸡蛋} -> {面包}的确信度为 0.4/0.4 = 1。

如果我们对规则 {鸡蛋,面包} -> {奶酪}感兴趣。在这种情况下,有三个交易中存在 (鸡蛋,面包)。总交易数为五。所以,这意味着支持度为 3/5,即 0.6。只有一个交易中同时存在三个物品(鸡蛋,面包,奶酪)。所以,支持度为 1/5 = 0.2。因此,规则 {鸡蛋,面包} -> {奶酪}的确信度为 0.2/0.6 = 0.33。

对于规则的信心越高,它就越好。像支持一样,我们对确信度设置了最低阈值。

有时,它也被称为 A 在 B 上的条件概率。它可以理解为在 A 已经发生的情况下 B 发生的概率,并且可以写成 P(A|B)。因此,在上面引用的例子中,提前购买了鸡蛋、面包的情况下购买奶酪的概率为 33%,而购买了牛奶、鸡蛋的情况下购买面包的概率为 100%。

到目前为止,我们已经涵盖了信心和支持。现在我们将研究提升度和确信度,这是评估规则的真正标准。

提升度和确信度

提升度是一种非常重要的规则测量标准。规则 A-> B 的提升度可以定义为

04_01c

在这里,分子在交易中同时存在 A 和 B 时得到支持,而分母则是指 A 的支持乘以 B 的支持。

表 4.3 我们将用于理解支持度、确信度和提升度概念的数据集。第一张发票 1001 有牛奶、鸡蛋和面包,而奶酪没有购买。同样,为了本例,我们只考虑了总共 4 个物品。
发票号码 牛奶 鸡蛋 面包 奶酪
1001 1 1 1 0
1002 0 1 1 1
1003 1 1 1 0
1004 0 1 0 1
1005 0 1 1 0

参考表 4.3,如果我们对规则{牛奶,鸡蛋} -> {面包}感兴趣。在这种情况下,有两个交易中都存在所有三个(牛奶,鸡蛋,面包)。因此,支持度再次为 2/5 = 0.4,这是分子。有两个交易中只有(牛奶,鸡蛋)存在,所以支持度为 2/5 = 0.4。有四个交易中存在面包,因此支持度为 4/5 = 0.8。将其代入上述方程中,规则{牛奶,鸡蛋} -> {面包}的提升度为 0.4/(0.4x0.8) = 1.25。

如果我们对规则{鸡蛋,面包} -> {奶酪}感兴趣。在这种情况下,只有一个交易中存在(鸡蛋,面包,奶酪)。总交易数为五。因此,支持度为 1 / 5,即 0.2。有两个交易中存在(奶酪)。因此,支持度为 2/5 = 0.4。有四个交易中存在(鸡蛋,面包),因此支持度为 4/5 = 0.8。将其代入上述方程中,规则{鸡蛋,面包} -> {奶酪}的提升度为 0.2/(0.4x0.8) = 0.625。

如果提升度的值等于 1,则意味着前项和后项彼此独立,不能从中得出任何规则。

如果提升度的值大于 1,则意味着前项和后项是相互依赖的。此规则可用于预测未来交易中的前项。这是我们想要从数据集中得出的见解。

提升度的值小于 1,意味着前项和后项是彼此替代的。其中一个的存在可能对另一个产生负面影响。这也是业务团队进行战略规划的重要洞察。

在评估任何规则时,使用提升度时,必须将领域知识应用于其中。例如,如果我们评估规则{鸡蛋,面包} -> {奶酪},它表明鸡蛋、面包可以替代奶酪。我们知道这在现实生活中是不正确的。因此,在这种情况下,我们不能对此规则做出任何决定。我们必须借助领域知识来对该规则进行任何结论。

同时,规则{牛奶,鸡蛋} -> {面包}可能是一个可以多次成立的规则。对于许多客户,当他们一起购买牛奶和鸡蛋时,面包很可能也会在同一交易中购买。因此,对于这样的客户来说,这个规则更有意义。目标是有一个强有力的业务逻辑来支持或否定使用算法识别的规则。

信心是一个重要的参数,其公式如下

04_01d

例如,如果我们对规则 {鸡蛋,面包} -> {奶酪} 感兴趣。在这种情况下,只有一个交易中存在 (奶酪)。总交易数为五。所以,支持度是 1/5,即 0.2,并将用于分子。我们已经计算出置信度为 0.625。放回公式中,我们可以计算说服力为 (1-0.2)/(1-0.625) = 2.13

我们可以将说服力解释为 - 规则 {鸡蛋,面包} -> {奶酪} 如果 {鸡蛋,面包,奶酪} 之间的关联纯粹是随机选择的,那么它的错误率将是 2.13 倍。

在大多数业务场景中,提升度是使用的测量标准。还有其他测量参数,如杠杆、集体强度等。但是大多数情况下,置信度、支持度和提升度被用来衡量任何规则的有效性。

 小测验 - 回答这些问题来检查你的理解.. 答案在书的末尾

1.   支持度测量规则在数据集中出现的频率。是或否。

2.   如果提升度大于 1,则表示两个项目彼此独立。是或否。

3.   降低置信度值,规则就越好。是或否。

在分析数据集时,我们评估任何规则时,大多数情况下都会为置信度、支持度和提升度设置阈值。这使我们能够减少规则数量并过滤掉不相关的规则。换句话说,我们只关注那些非常频繁的规则。当我们为数据集创建 Python 解决方案时,我们将更详细地研究它。

现在我们将研究关联规则中使用的各种算法。第一个这样的算法是 Apriori 算法,这是下一个主题。

4.4 Apriori 算法

Apriori 算法是用于关联规则的最流行算法之一。它由 Agrawal 和 Shrikant 在 1994 年提出。本章末尾给出了论文链接。

Apriori 用于理解和分析交易数据库中的频繁项。它利用“自下而上”的方法,首先根据子集的频率生成候选项。让我们通过一个示例来理解整个过程。

我们将使用我们之前讨论过的相同数据集。

表 4.3 我们将用来理解支持度、置信度和提升度概念的数据集。第一个发票 1001 有牛奶、鸡蛋和面包,而奶酪没有购买。
发票编号 牛奶 鸡蛋 面包 奶酪
1001 1 1 1 0
1002 0 1 1 1
1003 1 1 1 0
1004 0 1 0 1
1005 0 1 1 0

Apriori 算法中使用的过程看起来像图 4.2 中下面的过程。

图 4.2 Apriori 算法过程可以如下所示。

04_02

假设我们想要分析面包与数据集中所有其他项目的关系。在这种情况下,级别 1 是面包,我们找到其出现的频率。

然后我们转移到下一层,即第 2 层。现在我们找到面包与其他每个物品 - 牛奶、鸡蛋和奶酪(在第 2 层)的关系。在这里,我们再次找到所有可能组合的出现频率,即 {面包,牛奶},{面包,鸡蛋} 和 {面包,奶酪}。可以在图 4.3 中显示。

图 4.3 我们在 1 级有面包,而其他物品(牛奶、鸡蛋和奶酪)都放在 2 级。面包放在 1 级,因为我们希望分析面包与所有其他物品的关系。

04_03

分析完第 2 层后,我们转移到第三层、第四层等。这个过程会一直持续,直到我们达到最后一层,其中所有的物品都被用完。

通过这个过程,我们可以计算所有可能组合的支持度。例如,我们可以得到

{面包} -> {牛奶},

{面包} -> {鸡蛋} 和

{面包} -> {奶酪}。

对于下一层,我们还会得到支持

{面包,牛奶} -> {鸡蛋},

{面包,鸡蛋} -> {牛奶},

{面包,牛奶} -> {奶酪},

{面包,奶酪} -> {牛奶},

{面包,奶酪} -> {鸡蛋} 和

{面包,鸡蛋} -> {奶酪}。

现在,使用同样的过程,计算下一层的所有可能组合。例如,{面包,鸡蛋,牛奶} -> {奶酪},{面包,鸡蛋,奶酪} -> {牛奶} 等。

当所有的项集都用完时,这个过程将停止。完整的架构可以看起来像图 4.4。

图 4.4 Apriori 算法的完整架构。在这里,我们将计算所有可能组合的支持度。探索所有项目之间的关系,由于整个数据库的扫描,Apriori 的速度受到影响。

04_04

现在,我们可以轻松理解可能的组合数量相当高,这是 Apriori 的挑战之一。Apriori 算法还有一些其他缺点,我们将在后面学习。但现在是用 Python 实现 Apriori 的时候了。

4.4.1 Python 实现

现在我们将继续使用 Python 实现 Apriori 算法。数据集和 Python Jupyter 笔记本已经上传到 GitHub 代码库。

你可能需要安装 apyori。

安装这些库很简单,只需按照以下步骤进行。

import sys
!{sys.executable} -m pip install apyori

步骤 1: 导入用例所需的库。我们导入了 numpy、pandas。为了实现 Apriori,我们还有一个叫做 apyori 的库也被导入了。

import numpy as np
import pandas as pd
from apyori import apriori

步骤 2: 现在,我们导入数据集 store_data.csv 文件。

store_dataset = pd.read_csv('store_data.csv')

你还建议查看打开 .csv 文件的数据集。它会像下面的截图一样。截图显示了前 25 行。每一行代表一个发票。

04_04a

步骤 3: 让我们通过 .info.head 命令对数据进行一些基本检查。

store_dataset.info()

04_04b

store_dataset.head()

04_04c

第 4 步: 在这里我们可以看到,代码已将第一个交易作为标题考虑。因此,我们将重新导入数据,但这次他会指定标题等于 None。

store_dataset = pd.read_csv('store_data.csv', header=None)

第 5 步: 让我们再次看一下头部。这次看起来是正确的。

store_dataset.head()

04_04d

第 6 步: 我们用于代码的库接受数据集作为列表的列表。整个数据集必须是一个大列表,而每个交易是大列表中的内部列表。因此,为了实现它,我们首先将我们的 store_dataset 数据框转换为列表。

all_records = []
for i in range(0, 7501):
    all_records.append([str(store_dataset.values[i,j]) for j in range(0, 20)])

第 7 步: 接下来,我们实施 Apriori 算法。

对于算法,我们正在处理我们在第 6 步创建的 all_records 列表。指定的最小支持度为 0.5 或 50%,最小置信度为 25%,最小提升为 4,规则的最小长度为 2。

此步骤的输出是 apriori_rules 类对象。然后将此对象转换为我们可以理解的列表。最后,我们打印此列表。

apriori_rules = apriori(all_records, min_support=0.5, min_confidence=0.25, min_lift=4, min_length=2)
apriori_rules = list(apriori_rules)
print(len(apriori_rules))

代码的输出将为 0。这意味着不存在满足我们设置的规则条件的规则。

我们再次尝试执行相同的代码,尽管将最小支持度减少到 25%。

apriori_rules = apriori(all_records, min_support=0.25, min_confidence=0.25, min_lift=4, min_length=2)
apriori_rules = list(apriori_rules)
print(len(apriori_rules))

再次,没有生成任何规则,输出为零。即使将最小支持度降低到 10%,也不会产生任何规则。

apriori_rules = apriori(all_records, min_support=0.1, min_confidence=0.25, min_lift=4, min_length=2)
apriori_rules = list(apriori_rules)
print(len(apriori_rules))

现在,我们将最小提升减少到 2。这次我们得到的输出是 200。这意味着有 200 个满足条件的规则。

apriori_rules = apriori(all_records, min_support=0.25, min_confidence=0.25, min_lift=2, min_length=2)
apriori_rules = list(apriori_rules)
print(len(apriori_rules))

第 8 步: 让我们看一下第一条规则。

print(apriori_rules[0])

04_04e

规则解释了杏仁和汉堡之间的关系。支持率为 0.005,置信度为 0.25。提升为 2.92 表明此规则本身相当强大。

第 9 步: 我们现在将详细查看所有规则。为此,循环遍历规则并从每次迭代中提取信息。每个规则都有构成规则的项目以及支持、置信度和提升的相应值。我们在第 8 步中展示了一个示例。现在在第 9 步中,我们只是使用 for 循环从所有规则中提取该信息。

for rule in apriori_rules:
    item_pair = rule[0] 
    items = [x for x in item_pair]
    print("The apriori rule is: " + items[0] + " -> " + items[1])
    print("The support for the rule is: " + str(rule[1]))
    print("The confidence for the rule is: " + str(rule[2][0][2]))
    print("The lift for the rule is: " + str(rule[2][0][3]))
    print("************************")

此步骤的输出如下所示。在这里,我们可以观察到每个规则及其支持、置信度和提升的相应值。

04_04f

我们可以轻松解释这些规则。例如,规则杏仁->汉堡包的提升为 2.92,置信度为 25.49%,支持率为 0.51%。这完成了我们使用 Python 的实现。此示例可以扩展到任何其他真实业务数据集。

并非所有生成的规则都适合使用。在本章的最后一节中,我们将探讨如何从生成的所有规则中获取最佳规则。

Apriori 算法是一种稳健且非常有见地的算法。但像任何其他解决方案一样,它也有一些缺点,我们现在正在讨论。

4.4.2 Apriori 算法的挑战

我们在前面的部分已经讨论过 Apriori 算法生成的子集数量相当大。

图 4.5 数据集的完全扫描被多次进行,因此速度显著下降。

04_05

生成候选项集非常繁琐,因此分析数据集变得相当繁重。Apriori 多次扫描整个数据集,因此需要将数据库加载到内存中。我们可以肯定地得出结论,它需要大量时间才能进行计算。当处理非常大的数据集时,这个问题就会被放大。事实上,在现实世界的问题中,会产生数百万次交易,会产生相当多的候选项集,因此在整个数据集上使用 Apriori 是非常耗时的。

因此,一般情况下,会设定一个最小支持值来减少可能的规则数量。在上面给出的例子中,我们可以根据表 4.4 中所示计算级别 1 组合的支持,如果我们将最小支持值设为 0.5,只有一个规则会被列入候选名单。

表 4.4 对每种商品的组合都进行了支持计算。例如,对于牛奶和面包-交易次数为 2,而总交易次数为 5。所以,支持率为 2/5,即 0.4。
组合 交易次数 总交易次数 支持率
牛奶,鸡蛋 2 5 0.4
牛奶,面包 2 5 0.4
牛奶,奶酪 0 5 0
鸡蛋,面包 4 5 0.8
鸡蛋,奶酪 2 5 0.4
面包,奶酪 1 5 0.2

设定一个最小支持值是一个明智的策略,可以使规则更易管理。这样可以减少时间,生成更有意义的规则。毕竟,从分析生成的规则应该足够通用,以至于可以应用于整个数据库。

 快速测验 - 回答这些问题来检查你的理解。书末有答案。

  1. Apriori 算法只扫描数据库一次。是真还是假?

  2. 如果香蕉在 12 次交易中出现了 5 次,那么香蕉的支持率就是 5/12。正确还是错误?

但 Apriori 算法是一种开创性的解决方案。它仍然非常受欢迎,通常是讨论关联规则时的第一个算法。

数据准备是关键步骤之一,也是一个挑战,我们将在本章最后的案例研究中探讨这一挑战。

我们接下来会学习下一个算法,也就是 ECLAT 算法。

4.5 等价类聚类和自底向上格遍历(ECLAT)

接下来我们会在这一节学习等价类聚类和自底向上格遍历算法,或者称之为 ECLAT,有人认为这个算法在速度和实现的方便性方面比 Apriori 更好。

ECLAT 使用深度优先搜索方法。这意味着 ECLAT 沿着数据集以纵向方式进行搜索。它从根节点开始。然后进入更深的一层,并继续直到达到第一个终端注释。假设终端节点在 X 级。一旦到达终端节点,算法然后返回一步,并到达级别(X-1),并继续直到再次找到终端节点。让我们通过表 4.6 中显示的树状图来理解这个过程。

图 4.6 ECLAT 算法过程的树状图。 它从 1 开始,直到 16 结束。

04_06

ECLAT 将采取以下步骤:

  1. 算法从根节点 1 开始。

  2. 然后它向根节点 2 深入一层。

  3. 然后它会继续向更深的一层,直到达到终端节点 11。

  4. 一旦它到达终端注释 11,然后退回一步,到达节点 5。

  5. 然后算法搜索是否有可用的节点。在节点 5 处,我们可以看到没有可用的节点。

  6. 因此,算法再次退回一步,它达到了节点 2。

  7. 在节点 2 处,算法再次探索。它发现可以到达注释 6。

  8. 因此,算法进入节点 6,并开始探索,直到达到终端节点 12。

  9. 这个过程会一直持续,直到所有组合耗尽。

显然,计算速度取决于数据集中存在的不同项目的总数。这是因为不同项目的数量定义了树的宽度。每笔交易中购买的商品将定义每个节点之间的关系。

在执行 ECLAT 时,将分析每个商品(单独或成对)。让我们使用我们已经用于 Apriori 的相同示例来更好地理解 ECLAT,如表 4.5 所示。

表 4.5 我们将用来理解 ECLAT 的数据集。 第一张发票 1001 有牛奶、鸡蛋和面包,而奶酪没有购买。
发票号 牛奶 鸡蛋 面包 奶酪
1001 1 1 1 0
1002 0 1 1 1
1003 1 1 1 0
1004 0 1 0 1
1005 0 1 1 0

ECLAT 将经历以下步骤来分析数据集:

  1. 在第一次运行中,ECLAT 将找到所有单个商品的发票号。换句话说,它会找到所有商品的个别发票号。可以在下面的表 4.6 中显示,其中牛奶在发票号 1001 和 1003 中出现,而鸡蛋出现在所有发票中。
表 4.6 每个商品所在的相应发票。牛奶出现在 1001 和 1003 号发票中,而鸡蛋出现在五张发票中。
商品 发票号
Milk 1001,1003
Eggs 1001, 1002, 1003, 1004, 1005
Bread 1001, 1002, 1003, 1005
Cheese 1002, 1004
  1. 现在在下一步中,所有两个项目数据集都将如下所示地进行探索,如表 4.7 所示。例如,牛奶和鸡蛋出现在发票号码 1001 和 1003 中,而牛奶和奶酪没有出现在任何发票中。
表 4.7 现在探索了两个项目数据集。牛奶和鸡蛋出现在发票号码 1001 和 1003 中,而没有牛奶和奶酪的发票。
项目 发票号码
牛奶,鸡蛋 1001, 1003
牛奶,面包 1001, 1003
牛奶,奶酪 -
鸡蛋,面包 1001, 1002, 1003, 1005
鸡蛋,奶酪 1002, 1004
面包,奶酪 1002
  1. 在接下来的步骤中,所有三个项目数据集都将如表 4.8 所示进行探索。
表 4.8 在这一步中分析了三个项目数据集。我们只有两个组合。
项目 发票号码
牛奶,鸡蛋,面包 1001, 1003
鸡蛋,面包,奶酪 1002
  1. 在我们的数据集中没有包含四个项目的发票。

  2. 现在根据我们设置的支持计数值的阈值,我们可以选择规则。因此,如果我们希望使规则为真的最小交易次数等于三,那么只有一个规则符合条件,即{鸡蛋,面包}。如果我们将最小交易次数的阈值设定为两,则诸如{牛奶,鸡蛋,面包},{牛奶,鸡蛋},{牛奶,面包},{鸡蛋,面包}和{鸡蛋,奶酪}等规则都符合条件。

现在我们将为 ECLAT 创建一个 Python 解决方案。

4.5.1 Python 实现

现在我们将使用 Python 进行 ECLAT 的执行。我们在这里使用 pyECLAT 库。数据集如下所示:

04_06a

步骤 1: 我们将在这里导入库。

import numpy as np
import pandas as pd
from pyECLAT import ECLAT

步骤 2: 现在导入数据集

data_frame = pd.read_csv('Data_ECLAT.csv', header = None)

步骤 3: 这里我们正在生成一个 ECLAT 实例。

eclat = ECLAT(data=data_frame)

在上一步生成的 ECLAT 实例 eclat 中有一些属性,如 eclat.df_bin 是一个二进制数据框,eclat.uniq_ 是所有唯一项目的列表。

步骤 4: 现在我们将适配模型。我们在这里给出了最小支持度为 0.02。之后我们将打印支持度。

get_ECLAT_indexes, get_ECLAT_supports = eclat.fit(min_support=0.02,
                                                           min_combination=1,
                                                           max_combination=3,
                                                           separator=' & ')
get_ECLAT_supports

输出是

04_06b

我们可以根据支持度提供的结果进行解释。对于每个项目和项目组合,我们都得到了支持度的值。例如,对于炸薯条和鸡蛋,支持度的值为 3.43%。

ECLAT 相对于 Apriori 算法具有一些优势。由于它使用深度搜索方法,因此比 Apriori 更快,计算所需的内存更少。它不会迭代地扫描数据集,因此使其比 Apriori 更快。在我们学习了最后一个算法之后,我们将再次比较这些算法。

现在我们将转向第三个算法:F-P 生长算法。

4.6 频繁模式生长算法(F-P 算法)

F-P 算法或频繁模式增长算法是本章要讨论的第三个算法。它是对 Apriori 算法的改进。回想一下,在 Apriori 中,我们面临计算耗时和昂贵的挑战。FP 通过将数据库表示为一种名为频繁模式树或 FP 树的树来解决这些问题。因为这种频繁模式,所以没有必要像 Apriori 算法那样生成候选项。现在让我们详细讨论一下 FP。

FP 树或频繁模式树是一个树形结构,它挖掘数据集中最频繁出现的项。见图 4.7。

图 4.7 FP 算法可以用树状图结构表示。我们将逐步创建这个树。每个节点代表一个唯一的项。根节点为空。

04_07

每个节点表示数据集中的唯一项。树的根节点通常保持为空。树中的其他节点是数据集中的项。如果它们在同一发票中,则节点彼此连接。我们将逐步学习整个过程。

假设我们使用如表 4.9 所示的数据集。因此,我们有唯一的项:苹果,牛奶,鸡蛋,奶酪和面包。总共有 9 个交易,每个交易中的相应项如表 4.9 所示。

表 4.9 我们将使用的数据集,以了解 FP 算法的概念。这里有九个交易,例如在 T1 中我们有苹果,牛奶和鸡蛋。
交易 项集
T1 苹果,牛奶,鸡蛋
T2 牛奶,奶酪
T3 牛奶,面包
T4 苹果,牛奶,奶酪
T5 苹果,面包
T6 牛奶,面包
T7 苹果,面包
T8 苹果,牛奶,面包,鸡蛋
T9 苹果,牛奶,面包

现在让我们将 FP 算法应用于该数据集。

步骤 1: 就像 Apriori 算法一样,我们首先扫描整个数据集。记录每个项出现的次数并生成频率计数。结果如表 4.10 所示。我们按照整个数据集中各项的频率或对应的支持计数从大到小排列。

表 4.10 各项集的相应频率。例如,苹果已被购买了六次。
频率或支持计数
牛奶 7
苹果 6
面包 6
奶酪 2
鸡蛋 2

如果两个项的频率完全相同,则任何一个都可以排在前面。在上面的例子中,面包和苹果的频率相同。因此,我们可以将面包或苹果作为第一个。

步骤 2: 让我们开始构建 FP 树。我们从根节点开始创建,通常是图 4.8 中的空节点。

图 4.8 FP 树的根节点通常保持为空。

04_08

第 3 步: 现在分析第一个交易 T1。在这里,我们有苹果、牛奶和鸡蛋在第一笔交易中。其中牛奶有最高的支持计数,为 7。因此,从根节点延伸到牛奶的连接,并用 Milk:1 表示。我们在图 4.9 中展示了。

图 4.9 从根节点到牛奶的连接。牛奶有最高的支持,因此我们选择了牛奶。

04_09

第 4 步: 现在我们来看看 T1 中的其他项目。苹果的支持计数为 6,鸡蛋的支持计数为 2。所以,我们将从牛奶到苹果延伸连接,并命名为 Apple:1,然后从苹果到鸡蛋并称之为 Eggs:1。我们在图 4.10 中展示了。

图 4.10 过程的第 4 步,我们已经完成了 T1 中的所有项目。所有的项目牛奶、苹果和鸡蛋现在都彼此连接。

04_10

第 5 步: 现在让我们看看 T2。它有牛奶和奶酪。牛奶已经连接到根节点。所以,牛奶的计数变成 2,变成了 Milk:2。我们接下来会从牛奶到奶酪创建一个分支,并称之为 Cheese:1。增加的部分显示在图 4.11 中。

图 4.11 过程的第 5 步,我们开始分析 T2。牛奶已经连接,所以它的计数增加了 2,同时奶酪被添加到树中。

04_11

第 6 步: 现在轮到 T3。T3 有牛奶和面包。所以,类似于第 5 步,牛奶的计数是 3,变成了 Milk: 3。与第 5 步类似,我们从牛奶到面包添加另一个连接,称为 Bread:1。更新后的树显示在图 4.12 中。

图 4.12 在第 6 步,现在分析 T3。牛奶的计数增加了一个,变成了 3,而面包被添加为一个新的连接。

04_12

第 7 步: 在 T4 中,我们有苹果、牛奶和奶酪。牛奶的计数现在变成了 4,苹果现在是 2。然后我们创建了一个从苹果到奶酪的分支,称之为 Cheese:1。我们在图 4.13 中展示。

图 4.13 在过程的第 7 步中,正在分析 T4。牛奶的计数变成了 4,苹果的计数增加到了 2,并添加了一个新的从苹果到奶酪的分支。

04_13

第 8 步: 我们可以在 T5 中发现,我们有苹果和面包。两者都不直接连接到根节点,频率相等为 6。因此,我们可以任选其一连接到根节点。图更新为图 4.14。

图 4.14 在分析完 T5 之后,图示如下所示发生了变化。我们有苹果和面包被添加到树中。

04_14

第 9 步: 这个过程会继续,直到我们耗尽所有的交易,最终的图形如图 4.15 所示。

图 4.15 一旦我们耗尽了所有可能的组合,最终的树就是这样。但是在此之后还有更多的步骤。到目前为止,我们只创建了树。现在我们需要生成数据集,如表 4.11 所示。

04_15

到目前为止,做得很棒!但是,过程还没有结束。我们刚刚建立了数据集中项目之间的连接。现在我们需要填写一个看起来像表 4.11 的表。

表 4.11 FP 算法要完成的表。这是我们希望生成的输出。
项目 条件模式基 条件 FP 树 频繁模式生成
奶酪
面包
鸡蛋
苹果

也许你会想为什么只列出了 4 个项目。由于牛奶直接源自根节点,没有其他到达它的方式,我们不需要为牛奶单独设置一行。

第 10 步: 在继续之前,我们将最小支持计数固定为 2,以便接受任何规则。由于数据集相当小,我们这样做是为了简单起见。

对于现实生活中的业务问题,建议您尝试多个甚至更高的支持计数值,否则生成的规则数量可能会非常多。

让我们从奶酪作为第一个项目开始。我们可以通过{NULL-牛奶-奶酪}和{NULL-牛奶-苹果-奶酪}到达奶酪。对于这两条路径,奶酪的计数为 1。因此,(如果我们忽略 NULL)我们的条件模式基为{牛奶-奶酪}或{牛奶:1}和{牛奶-苹果-奶酪}或{牛奶-苹果:1}。完整的条件模式基变为{{牛奶:1},{牛奶-苹果:1}}。这些信息添加到表 4.12 的第二列。

表 4.12 过程的第 10 步,我们已经填写了奶酪的第一个单元格。我们填写了奶酪的第一个单元格。
项目 条件模式基 条件 FP 树 频繁模式生成
奶酪 {{牛奶:1},{牛奶-苹果:1}}
面包
鸡蛋
苹果

第 11 步: 现在,如果我们在条件模式基中添加两个值,我们会得到牛奶为 2,苹果为 1。由于我们已经为频率计数设置了 2 的阈值,我们将忽略苹果的计数。条件 FP 树的值,即表中的第三列,变为{牛奶:2}。现在我们只需将原始项目添加到此项中,变为频繁模式生成或第 4 列。现在表是 4-13。

表 4.13 过程的第 11 步,在这一步中我们完成了奶酪项目的详细信息。同样,所有其他项目都将被分析并添加到表中。
项目 条件模式基 条件 FP 树 频繁模式生成
奶酪 {{牛奶:1},{牛奶-苹果:1}}
面包
鸡蛋
苹果

第 12 步: 以类似的方式填写表中的所有其他单元格,最终表为表 4.14。

我们分析了所有项目的组合后,表 4.14 是最终表。
项目 条件模式基 条件 FP 树 频繁模式生成
奶酪 {{牛奶:1},{牛奶-苹果:1}}
面包 {{牛奶-苹果:2}, {牛奶:2}, {苹果:2}} {{牛奶:4, 苹果:2}, {苹果:2}} {牛奶-面包:4}, {苹果-面包:4},
鸡蛋 {{牛奶-苹果:1},{牛奶-苹果-面包:1}} {牛奶-鸡蛋:2},{牛奶-苹果:2},
苹果

这确实是一个复杂的过程。但是一旦步骤清晰,就会变得非常简单。

通过这个练习,我们已经得到了最终的规则集,如最后一列频繁模式生成所示。

请注意,所有的规则都彼此不同。

我们将使用最后一列“频繁模式生成”作为我们数据集的规则。

使用 FP 增长算法的 Python 实现非常简单,并且使用库进行计算很容易。出于空间考虑,我们已将 Jupyter 笔记本上传到了本章的 GitHub 存储库中。

我们现在将探讨另一个有趣的主题,即序列规则挖掘。这是一个非常强大的解决方案,使企业能够根据客户的需求量身定制他们的营销策略和产品推荐。

4.7 序列规则挖掘

想象一下。Netflix 会有一个包含顾客随时间订购的所有电影的交易数据库。如果他们分析并发现 65%的购买了战争电影 X 的客户在接下来的一个月也购买了浪漫喜剧 Y,这是非常有见地和可操作的信息。这将使他们能够向客户推荐他们的产品,并且他们可以定制他们的营销策略。不是吗?

到目前为止,在这一章中,我们已经涵盖了三种关联规则算法。但是所有的数据点都限于相同的数据集,并且没有涉及到序列。序列模式挖掘允许我们分析一组事件发生的数据集。通过分析数据集,我们可以找到统计上相关的模式,这使我们能够解读整个事件序列。显然,事件序列是按照特定顺序排列的,这是序列规则挖掘过程中非常重要的一个属性。

序列规则挖掘不同于时间序列分析。要了解更多关于时间序列分析的信息,请参阅附录。

序列规则挖掘被应用于多个领域和功能。它可以在生物学中用于在 DNA 测序期间提取信息,也可以用于了解用户的在线搜索模式。序列规则挖掘将帮助我们了解用户接下来要搜索什么。在讨论关联规则时,我们使用了购买牛奶、面包、鸡蛋的同一交易中的交易。序列规则挖掘是对此的扩展,其中我们分析连续的交易,并试图解读是否存在序列。

在研究 SPADE 算法时,我们将学习构成该算法基础的数学概念。这些概念有点复杂,可能需要多次阅读才能掌握。

4.7.1 SPADE

我们现在正在使用等价类进行顺序模式挖掘(Sequential Pattern Discovery using Equivalence classes)或 SPADE 探索序列规则挖掘。这是由穆罕默德·J·扎基(Mohammed J. Zaki)提出的,文章链接在本章末尾。

我们了解到,我们希望分析事件的顺序。例如,顾客购买了手机和充电器。一周后买了耳机,两周后买了手机壳和手机屏幕保护壳。因此,在每次交易中都购买了项目。并且每次交易都可以称为一个事件。让我们更详细地了解一下。

假设我们有讨论所需的所有项目的完整列表。I 将包含像 i[1]、i[2]、i[3]、i[4]、i[5] 等项目。因此,我们可以写成

I = {i[1], i[2], i[3], i[4], i[5]………, i[n]},其中我们总共有 n 个不同的项目。

项目可以是任何东西。如果我们考虑杂货店的例子,项目可以是牛奶、鸡蛋、奶酪、面包等等。

一个事件将是在同一交易中的项目集合。一个事件可以包含像(i[1], i[5], i[4], i[8)这样的项目。例如,一个事件可以包含在同一交易中购买的项目(牛奶, 糖, 奶酪, 面包)。我们将用 ⍺ 表示一个事件。

接下来让我们了解一下序列。序列只是按顺序的事件。换句话说,⍺[1] -> ⍺[2] ->⍺[3] ->⍺[4] 可以被称为事件序列。例如,(牛奶, 奶酪)->(面包, 鸡蛋)->(奶酪, 面包, 糖)->(牛奶, 面包)是一系列交易。这意味着在第一次交易中购买了牛奶和奶酪。在接下来的交易中,购买了面包和鸡蛋,依此类推。

包含 k 个项目的序列是一个 k 项目序列。例如,序列(牛奶, 面包)->(鸡蛋)包含 3 个项目。

我们现在将逐步了解 SPADE 算法。

假设我们生成了以下序列。在第一个序列 1001 的交易中,第一个交易中购买了牛奶。第二个交易中购买了牛奶、鸡蛋和面包。紧随其后购买了牛奶和面包。在第四个序列中只购买了糖。在序列 1001 的第五个和最后一个交易中,购买了面包和苹果。并且这适用于所有相应的序列。

表 4.15 序列挖掘的数据集。在序列 ID 1001 中,我们有多个事件。在第一次购买中,购买了牛奶。然后购买了(牛奶, 鸡蛋, 面包)等等。
序列 ID 序列
1001 <(牛奶) (牛奶, 鸡蛋, 面包) (牛奶, 面包) (糖)(面包, 苹果)>
1002 <(牛奶, 糖) (面包) (鸡蛋, 面包) (牛奶, 奶酪)>
1003 <(奶酪, 苹果) (牛奶, 鸡蛋) (糖, 苹果) (面包) (鸡蛋)>
1004 <(奶酪, 香蕉)(牛奶, 苹果)(面包)(鸡蛋)(面包)>

这个(表 4.15 )可以转换成垂直数据格式,如表 4.16 所示。在这一步中,我们计算单个项目的频率,即仅包含一个项目的序列。这仅需要进行一次数据库扫描。

表 4.16 表 4.15 的垂直格式。我们只是得到了每个项目的序列 ID 和项目 ID,并在此处表示它。
序列 ID 元素 ID 项目
1001 1 牛奶
1001 2 牛奶, 鸡蛋, 面包
1001 3 牛奶, 面包
1001 4
1001 5 面包, 苹果
1002 1 牛奶, 糖
1002 2 面包
1002 3 鸡蛋, 面包
1002 4 牛奶, 奶酪
1003 1 奶酪, 苹果
1003 2 牛奶, 鸡蛋
1003 3 糖, 苹果
1003 4 面包
1003 5 鸡蛋
1004 1 奶酪, 香蕉
1004 2 牛奶, 苹果
1004 3 面包
1004 4 鸡蛋
1004 5 面包

表 4.16 只是表 4.15 的垂直制表符号表示。例如,在序列 ID 1001 中,元素 ID 1 是牛奶。对于序列 ID 1001,元素 ID 2 是牛奶,鸡蛋,面包等。

为了解释的目的,我们只考虑两个项目 0 牛奶和鸡蛋以及支持阈值为 2。

然后,在下一步中,我们将对每个项目进行分解。例如,牛奶出现在序列 ID 1001 和元素 ID 1,序列 ID 1001 和元素 ID 2,序列 ID 1001 和元素 ID 3,序列 ID 1002 和元素 ID 1 等中。它会产生类似表 4.17 的表格,我们在其中显示了牛奶和鸡蛋。它需要应用到数据集中的所有项目。

表 4.17 中相应的牛奶和鸡蛋序列 ID。同样的方法可以应用到所有项目和序列中。
牛奶 鸡蛋
序列 ID 元素 ID
1001 1
1001 2
1001 3
1002 1
1002 4
1003 2
1004 3

现在,我们希望计算 2-序列或具有 2 项序列。我们可以有两个序列 - 要么是牛奶 -> 鸡蛋,要么是鸡蛋 -> 牛奶。让我们先来看看牛奶-> 鸡蛋。

对于牛奶 -> 鸡蛋,我们需要在鸡蛋前面放牛奶。对于相同的序列 ID,如果牛奶的元素 ID 小于鸡蛋的元素 ID,则它是一个合格的序列。在上面的示例中,对于序列 ID 1002,牛奶的元素 ID 是 1,而鸡蛋的元素 ID 是 2。因此,我们可以将其添加为第一个合格对,如下表 4.18 的第一行所示。对于序列 ID 1002 也是如此。在表 4.17 中,第 4 行我们有序列 ID 1002。牛奶的元素 ID 是 1,而第 2 行的鸡蛋的元素 ID 是 3。同样,牛奶的元素 ID 小于鸡蛋的元素 ID,因此它变为第二个条目。进程继续。

表 4.18 序列牛奶鸡蛋可以写在这里。关键点是在比较牛奶和鸡蛋的相应元素 ID 时具有相同的序列 ID。
牛奶 鸡蛋
序列 ID
1001
1002
1003
1004

使用相同的逻辑,我们可以创建表格,例如鸡蛋 -> 牛奶,如下所示在下表 4.19 中显示。

表 4.19:序列鸡蛋牛奶可以在此处写下。关键在于比较牛奶和鸡蛋的各自元素 ID 时保持相同的序列 ID。
美纪牛奶
序列 ID
1001
1002

可以对所有可能的组合都进行此操作。现在我们将转向创建 3 项序列,我们将创建 Milk,Eggs -> Milk。为此,我们必须合并这两个表。

表 4.20:结合序列,即牛奶->鸡蛋和鸡蛋->牛奶,以合并表格。

04_T20

结合的逻辑是匹配序列 ID 和元素 ID。我们已经用红色和绿色分别突出显示匹配的部分。对于序列 ID 1001,左表中鸡蛋的元素 ID 和右表中鸡蛋的元素 ID 匹配,这成为表 4.21 的第一条目。同样对于序列 ID 1002,元素 ID 3 匹配。这导致了表 4.21 的生成。

表 4.21:在分析了所有物品的组合后的最终表。
牛奶,鸡蛋 -> 牛奶
序列 ID
1001
1002

这个过程将继续进行。当找不到频繁序列时算法停止。

我们将使用 Python 在数据集上实现 SPADE。我们使用pyspade库,因此我们必须加载数据集并调用函数。它会为我们生成结果。这里支持率设置为 0.6,然后我们打印结果。

from pycspade.helpers import spade, print_result
spade_result = spade(filename='SPADE_dataset.txt', support=0.6, parse=True)
print_result(spade_result)

04_15a

这结束了我们在本章中要讨论的四种算法。现在我们将转向案例研究,为您提供真实的体验。

4.8 关联规则案例研究

关联规则挖掘是一个非常有用和强大的解决方案。我们将使用关联规则解决一个实际案例研究。

回想一下,在本章的开头,我们建议研究杂货店的模式。这种店铺摆设的逻辑是什么呢?

想象一下:你在像沃尔玛、乐购、Spar 或 Marks & Spencer 等杂货零售商工作。他们必须规划新店的视觉布局。显然,零售商要明智地利用店内空间,充分利用最大的容量。与此同时,至关重要的是不妨碍顾客的活动。顾客应该可以接触到所有展示的物品,并能够轻松地导航。你可能会有一些经历过一些店铺让我们感到窒息和陈列品充斥,而另一些则整齐摆放的情况。

我们如何解决这个问题呢?

这个问题可能有多种解决方案。一些零售商可能希望根据商品类别对物品进行分组。他们可能希望将所有烘焙产品都放在一个架子上,或者根据任何其他条件使用。我们在这里学习的是机器学习的例子。

使用购物篮分析,我们可以生成规则,指示各种商品之间的相关关系。我们可以预测哪些商品经常一起购买,并且可以将它们放在店里的一起。例如,如果我们知道牛奶和面包经常一起购买,那么面包可以放在牛奶柜台附近。购买牛奶的顾客可以轻松找到面包并继续购买。

但事情并不像听起来那么简单。让我们逐步解决这个案例。

  1. 业务问题定义:第一步是明确定义我们清楚的业务问题。我们希望发现各种商品之间的关系,以便可以更好地安排商店内的布局。在这里,陈列计划 变得很重要。陈列计划帮助零售商以明智的方式规划商店内的空间利用,使顾客也可以轻松导航和访问产品。它可以被视为商店的视觉布局。示例如图 4.16 所示。
图 4.16 显示了一个陈列计划的示例。陈列计划对于视觉营销非常有用。

04_16

在图中,我们可以看到每个商品类别都有特定的区域。关联规则非常有洞察力,可以帮助生成陈列计划的方向。

  1. 数据发现:下一步是数据发现,其中历史交易被搜索并加载到数据库中。通常,一笔交易看起来像表 4.22。
表 4.22 实际零售店生成的发票示例。将这种数据格式转换为可以被关联规则算法消费的格式是相当具有挑战性的。
发票编号 日期 商品 金额
1001 01-Jun-21 牛奶,鸡蛋,奶酪,面包 $10
1002 01-Jun-21 面包,香蕉,苹果,黄油 $15
1003 01-Jun-21 黄油,胡萝卜,奶酪,鸡蛋,面包,牛奶,香蕉 $19
1004 01-Jun-21 牛奶 $1
1005 01-Jun-21 面包 $0.80
  1. 数据准备:这一步也许是最困难的一步。正如你可能意识到的那样,关联规则模型的创建是一项非常简单的任务。我们有可以为我们处理繁重工作的库。但是它们期望的数据集是以特定格式存在的。这是一项繁琐的任务,非常耗时,并且需要大量的数据预处理技能。

    在准备数据集时,有几个注意事项你必须记住,它们是:

  2. 有时在数据准备阶段我们会得到NULL空值。数据集中的缺失值可能会导致计算时出现问题。在其他机器学习解决方案中,我们建议处理缺失值。在关联规则的情况下,我们建议忽略相应的交易,并且不在最终数据集中考虑它。

  3. 很多时候,我们在数据中会得到垃圾值。数据集中会发现像!@%^&*()_ 这样的特殊字符。这可能是由系统中不正确的输入导致的。因此,需要进行数据清洗。

  4. 我们在书的附录中非常详细地介绍了数据预处理步骤,在这里我们处理 NULL 值和垃圾值。

  5. 将表格转换为关联规则学习算法可以理解和使用的格式是一项必不可少但又艰巨的步骤。深入了解 SQL 数据透视的概念以更好地理解这个概念。否则,你可能需要某人(一位数据工程师)为你创建数据集。

  6. 模型准备: 或许最容易的步骤之一就是建模。我们已经为不同的算法解决了 Python 解决方案。所以,你应该对此相当熟悉。

  7. 模型解释: 创建模型可能很容易,但解释规则却不是。大多数时候,你可以得到如下规则:

  8. NA -> (牛奶,奶酪) - 这样的规则显然是不可用的,也没有任何意义。它表明数据准备不正确,数据集中仍然存在一些垃圾值。

  9. (某些物品)->(包装材料)- 或许是最明显的规则,但同样不可用。这个规则表明,无论何时购物,都会购买包装材料,相当明显对吧?

  10. (土豆,番茄)->(洋葱):这种规则看起来可能是正确的,但这是零售商已经知道的常识。显然,大多数购买蔬菜的顾客都会一起购买土豆、番茄和洋葱。这样的规则可能对业务价值增加不多。

  11. 支持、置信度和提升阈值允许过滤出最重要的规则。我们可以按提升的降序排序规则,然后移除最明显的规则。

  12. 业务主题专家: 业务利益相关者和主题专家参与每一个步骤是至关重要的。在这个案例研究中,运营团队、视觉营销团队、产品团队和营销团队是关键参与者,他们应该在每一个步骤都密切配合。

  13. 一旦规则生成并得到接受,我们就可以使用它们来改善零售空间的陈列方案。零售商可以利用它们来改善营销策略并改进产品促销活动。例如,如果接受了像(A,B)->(C)这样的规则,零售商可能希望将产品捆绑在一起并作为一个单一实体出售。这将增加同一交易中购买的平均物品数量。

  14. 这个案例研究可以扩展到任何其他领域或业务功能。例如,如果我们希望检查用户在网页上的移动,可以使用相同的步骤。网页开发人员可以分析客户在其网站上的历史点击和使用情况。通过识别模式,他们可以找出用户倾向于点击什么以及哪些功能会最大化他们的参与度。医生可以使用关联规则更好地诊断患者。医生可以比较与其他症状的概率相关的症状,并提供更准确的诊断。

现在,我们将研究这些算法的局限性以及关联规则和序列规则的其他可用解决方案。

4.9 总结思考

在我们研究的关联规则和序列规则中,存在一些假设和限制。

  • 在生成规则时,忽略了物品的各自重要性。例如,如果一个顾客在一次交易中购买了 5 罐牛奶和一公斤苹果,那么它被类似地对待于一张发票,其中购买了一罐牛奶和五公斤苹果。因此,我们必须记住物品的各自重要性没有被考虑。

    • 商品的成本反映了产品的感知价值。一些昂贵的产品更为重要,因此,如果顾客购买了它们,就可以产生更多的收入。在分析发票时,我们忽略了物品的成本。

    • 在分析序列时,我们没有考虑两个交易之间的各自时间段。例如,在 T1 和 T2 之间有 10 天,而在 T2 和 T3 之间有 40 天 - 这两个时间段被视为相同。

    • 在所有的分析中,我们将不同类别视为相同。易腐烂商品和不易腐烂商品被以类似方式处理。例如,保质期为 2-3 天的新鲜牛奶被类似对待于保质期无限的洗衣粉。

    • 许多时候,我们在分析后得到了不感兴趣的规则。这些结果来自常识(土豆,西红柿)->(洋葱)。这样的规则并不太有用。我们很多时候都面临这样的问题。

    • 虽然不感兴趣的规则是一个挑战,但发现的大量规则又是问题之一。我们得到了成百上千的规则,要理解和分析每一个都变得困难。在这里,阈值变得很有用。

    • 计算所需的时间和内存需求是巨大的。这些算法需要多次扫描数据集,因此是非常耗时的练习。

  • 生成的规则取决于用于分析的数据集。例如,如果我们仅在夏季生成的数据集上进行分析,我们就不能将规则应用于冬季,因为消费者的偏好会随着不同季节而变化。此外,随着时间的流逝,宏观和微观经济因素也会发生变化,因此算法也应随之更新。

还有一些其他算法也很有趣。对于关联规则,我们可以有多关系关联规则、k-最佳模式发现、近似频繁数据集、广义关联规则、高阶模式发现等。对于序列挖掘,我们有广义序列模式、FreeSpan、PrefixSpan、挖掘相关模式等。这些算法非常有趣,可以用于增强知识。

关联规则和序列挖掘是非常有趣的主题。各个商业领域和功能越来越多地使用关联规则来理解事件的模式。这些见解使团队能够做出明智而科学的决策,以改善客户体验和整体参与度。本章是本书第二部分的第一章。我们在本章中探讨了关联规则和序列挖掘。这些是使用 Apriori、FP 和 ECLAT 算法进行研究的,而序列挖掘则使用了 SPADE。

在下一章中,我们将研究高级聚类算法。敬请关注!

您现在可以继续问题部分。

实际的下一步和建议的阅读

  1. 请阅读以下关联规则算法的研究论文

  2. 关联规则的快速发现(www.cs.bme.hu/~marti/adatbanya/apriori_hashtree.pdf

  3. 用于挖掘关联规则的快速算法(rakesh.agrawal-family.com/papers/vldb94apriori.pdf

  4. 模式和关联规则挖掘方法的高效分析(arxiv.org/pdf/1402.2892.pdf

  5. 关于其保护隐私能力的关联规则挖掘技术综述(www.ripublication.com/ijaer17/ijaerv12n24_216.pdf

  6. 对于序列挖掘,请阅读以下研究论文:

  7. SPADE:用于挖掘频繁序列的高效算法(link.springer.com/content/pdf/10.1023/A:1007652502315.pdf

  8. 顺序挖掘:模式与算法分析(arxiv.org/pdf/1311.0350.pdf

  9. 基于趣味性的顺序模式挖掘算法(ieeexplore.ieee.org/document/8567170

  10. 一种解决顺序模式挖掘问题的新方法(link.springer.com/chapter/10.1007/978-3-642-34630-9_6

4.10 总结

  • 我们研究了关联规则,这些规则可用于发现数据集中存在的变量之间的引人注目的关系

    • 我们介绍了支持度、置信度、提升度和确信度的概念,用于衡量生成的规则的效果。

    • 然后我们转向了 apriori 算法,该算法利用了“自下而上”的方法,首先根据子集的频率生成候选项。Apriori 算法会迭代地扫描整个数据,因此需要很长时间。

    • 我们讨论了 ECLAT,这是一种深度优先搜索方法。它在整个数据集上以垂直方式执行搜索。由于它不会迭代地扫描数据集,因此比 apriori 更快。

    • 我们还介绍了频繁模式增长算法,该算法通过将数据库表示为称为频繁模式树或 FP 树的树来工作。由于这种频繁模式,无需像 Apriori 算法中那样生成候选项,因此它所需的时间较少。

    • 我们接着介绍了一种名为 SPADE 的基于序列的学习技术,其中我们也考虑了各种事件发生的具体顺序。

  • 最后,我们使用 apyori、pyECLAT、fpgrowth_py 和 pyspade 实现了 Python 技术。

第五章:聚类 (高级)

“在复杂中寻找简单- 爱因斯坦”

有时生活很简单,有时我们会遇到相当复杂的情况。我们在两种情况下都能应对,并根据情况调整我们的方法。

在本书的第一部分中,我们涵盖了更简单、更简单的主题。这使您为前进的旅程做好了准备。我们目前处于第二部分,比第一部分稍微复杂一些。第三部分比前两部分更加高级。因此,随着每一章的进行,难度将稍微增加,期望也会增加。

我们在本书的第一部分中学习了聚类算法。我们了解到聚类是一种无监督学习技术,我们希望通过发现数据集中的有趣模式来将数据点分组。我们深入研究了聚类解决方案的含义、聚类算法的不同类别以及最后的案例研究。在那一章中,我们深入探讨了 kmeans 聚类、层次聚类和 DBSCAN 聚类的数学背景、过程、Python 实现以及优缺点。在开始本章之前,建议您复习第二章。

你可能会遇到许多次不符合简单形式和形状的数据集。此外,我们在选择最终要实现的算法之前必须找到最佳匹配项。在这里,我们可能需要更复杂的聚类算法的帮助;这就是本章的主题。在本章中,我们将再次研究三种这样的复杂聚类算法 - 谱聚类、高斯混合模型 (GMM) 聚类和模糊聚类。一如既往,Python 实现将遵循数学和理论概念。本章在数学概念上稍微有些复杂。您不需要成为数学博士,但有时了解算法如何在后台工作是很重要的。与此同时,您会惊讶地发现这些算法的 Python 实现并不乏味。本章没有任何案例研究。

在本书的第五章中,我们将涵盖以下主题:

  1. 谱聚类

  2. 模糊聚类

  3. 高斯混合模型 (GMM) 聚类

  4. 总结

欢迎来到第五章,祝你一切顺利!

5.1 技术工具包

我们将继续使用到目前为止所使用的相同版本的 Python 和 Jupyter 笔记本。本章中使用的代码和数据集已经上传到 GitHub (github.com/vverdhan/UnsupervisedLearningWithPython/tree/main/Chapter%205)。

我们将继续使用到目前为止所使用的常规 Python 库 - numpy、pandas、sklearn、seaborn、matplotlib 等等。在本章中,您需要安装几个 Python 库,它们是 - skfuzzy 和 network 。使用库,我们可以非常快速地实现算法。否则,编写这些算法是相当耗时且繁琐的任务。

让我们开始重新了解一下聚类!

5.2 聚类

回顾第二章,聚类用于将相似的对象或数据点分组。它是一种无监督学习技术,我们的目的是在数据中找到自然分组,如图 5-1 所示。

图 5-1 将对象结果聚类为自然分组。

05_01

在这里,我们可以观察到,在左侧我们有未分组的数据,在右侧数据点已经被分组成逻辑组。我们还可以观察到,可以有两种方法来进行分组或聚类,而且两者都会产生不同的聚类。聚类作为一种技术,在业务解决方案中被广泛应用,如客户细分、市场细分等。

我们在第二章中理解了 kmeans、层次和 DBSCAN 聚类。我们还涵盖了各种距离测量技术和用于衡量聚类算法性能的指标。建议您重新查看这些概念。

在本章中,我们将专注于高级聚类方法。我们将在下一节开始使用谱聚类。

5.3 谱聚类

谱聚类是独特的聚类算法之一。在这个领域进行了一些高质量的研究。像杨安东教授、迈克尔·乔丹教授、雅尔·韦斯教授、施坚博教授、杰特恩德拉·马利克教授等著名的研究人员。我们在本章的最后一节引用了一些论文。

让我们首先定义谱聚类。谱聚类基于数据点的相似性而不是绝对位置进行聚类。因此,在数据处于复杂形状的任何地方,谱聚类都是答案。我们在图 5-2 中展示了一些谱聚类可以提供合理解决方案的示例。

图 5-2 各种复杂数据形状的示例,可以使用谱聚类进行聚类。

05_02

对于图 5-2,我们也可以使用其他算法,如 k 均值聚类。但它们可能无法充分反映数据的复杂形状。正式地说,像 kmeans 聚类这样的算法利用数据点的紧密程度。换句话说,点之间的接近程度和向群集中心的紧密程度驱动 kmeans 中的聚类。另一方面,在谱聚类中,连通性是驱动逻辑。在连通性中,数据点要么彼此紧密相邻,要么以某种方式连接。这种基于连接性的聚类示例已在图 5-2 中描述。

观察图 5-3(i),数据点呈现出环形图案。可能有数据点遵循这种环形图案。这是一个复杂的模式,我们需要对这些数据点进行聚类。想象一下,通过使用聚类方法,红色圆圈被划分为同一簇,如图 5-3(ii)所示。毕竟,它们彼此靠近。但是如果我们仔细观察,这些点是呈圆形排列的,存在一种模式,因此实际的簇应该如图 5-3(iii)所示。

图 5-3 (i) 我们可以有这样一种复杂的数据点表示需要进行聚类。观察环形(ii)一个非常简单的解释可能导致将红点视为同一簇的一部分,但显然,它们并不属于同一簇(iii)我们这里有两个圆。内圆中的点属于一个簇,而外部的点属于另一个簇。

05_03

图 5-3 中显示的示例旨在描述谱聚类的优势。

正如我们之前所说的,谱聚类利用连接方法进行聚类。在谱聚类中,立即相邻的数据点在图中被识别出来。这些数据点有时被称为节点。然后,这些数据点或节点被映射到低维空间。低维空间实际上就是在这个过程中,谱聚类使用从数据集派生的特征值、亲和力矩阵、拉普拉斯矩阵和度矩阵。然后,低维空间可以被分成多个簇。

谱聚类利用连接方法进行聚类,它依赖于图论,在这里我们根据连接它们的边来识别节点的簇。

我们将详细研究这个过程。但在检查这个过程之前,有一些重要的数学概念构成了谱聚类的基础,我们现在将进行介绍。

5.3.1 谱聚类的构建模块

我们知道聚类的目标是将相似的数据点分组到一个簇中,而不相似的数据点分组到另一个簇中。我们应该了解一些数学概念。我们将从相似性图的概念开始,这是数据点的一种相当本质的表示。

相似性图

图是表示数据点的一种最简单和直观的方法之一。在图 5-4(i)中,我们展示了一个图的示例,它只是数据点之间的连接,用边来表示。现在,如果两个数据点之间的相似性是正的,或者它高于某个阈值,那么它们就会连接起来,如图 5-4(ii)所示。我们可以使用相似性的权重而不是绝对值。因此,在图 5-4(ii)中,由于点 1 和 2 相对于点 1 和 3 更相似,因此点 1 和 2 之间的连接权重高于点 1 和 3 之间的连接权重。

图 5-4(i) 图是数据点的简单表示。如果它们非常相似,则点或节点彼此连接(ii)如果数据点之间的相似性高,则权重较高,否则对于不相似的数据点,权重较低。

05_04

所以,我们可以得出结论——使用相似性图,我们希望对数据点进行聚类,使得

  • 数据点的边具有较高的权重值,因此彼此相似,因此它们在同一簇中。

  • 数据点的边具有较低的权重值,因此彼此不相似,因此它们在不同的簇中。

除了相似性图之外,我们还应该了解特征值和特征向量的概念,我们已经在前一章中详细介绍了。建议您进行复习。我们现在将转向邻接矩阵的概念。

邻接矩阵

仔细看图 5-5。我们可以看到从 1 到 5 的各个点彼此连接。然后我们在矩阵中表示连接。该矩阵称为邻接矩阵

正式地说,在邻接矩阵中,行和列分别是相应的节点。矩阵内部的值表示连接——如果值为 0,则表示没有连接,如果值为 1,则表示存在连接。

图 5-5 邻接矩阵表示各个节点之间的连接,如果值为 1,则表示行和列中的相应节点连接。如果值为 0,则表示它们不连接。例如,节点 1 和节点 5 之间存在连接,因此该值为 1,而节点 1 和节点 4 之间没有连接,因此相应的值为 0。

05_05

所以对于邻接矩阵,我们只关心两个数据点之间是否存在连接。如果我们扩展邻接矩阵的概念,我们得到度矩阵,这是我们的下一个概念。

度矩阵

正式地说,度矩阵是一个对角矩阵,其中沿对角线的节点的度数是连接到它的边的数量。如果我们使用上述相同的示例,我们可以将度矩阵表示为图 5-6 所示。节点 3 和 5 各自有三个连接,它们在对角线上得到 3 作为值,而其他节点各自只有两个连接,因此它们在对角线上得到 2 作为值。

图 5-6 虽然邻接矩阵表示各个节点之间的连接,但度矩阵是每个节点的连接数。例如,节点 5 有三个连接,因此其前面有 3,而节点 1 只有两个连接,所以它有 2。

05_06

你可能会想为什么我们要使用矩阵?矩阵提供了数据的优雅表示,并且可以清楚地描述两个点之间的关系。

现在我们已经涵盖了邻接矩阵和度矩阵,我们可以转向拉普拉斯矩阵。

拉普拉斯矩阵

拉普拉斯矩阵有很多变体,但如果我们采用最简单的形式,即拉普拉斯矩阵是度矩阵减去邻接矩阵。换句话说,L = D – A。我们可以在图 5-7 中展示它。

图 5-7 拉普拉斯矩阵相当容易理解。要获得拉普拉斯矩阵,我们只需将邻接矩阵从度矩阵中减去,如上例所示。这里,D 表示度矩阵,A 是邻接矩阵,L 是拉普拉斯矩阵。

05_07

拉普拉斯矩阵是非常重要的,我们使用 L 的特征值来开发谱聚类。一旦我们获得了特征值和特征向量,我们可以定义另外两个值 – 谱间隙和 Fielder 值。第一个非零特征值是 谱间隙,它定义了图的密度。Fielder 值 是第二个特征值,它提供了将图分割成两个组件所需的最小切割的近似值。Fielder 值的相应向量称为 Fielder 矢量

Fielder 矢量具有正负组件,它们的结果总和为零。

我们将在下一节详细学习谱聚类的过程时使用这个概念。在转向谱聚类的过程之前,我们现在将介绍亲和力矩阵的另一个概念。

亲和力矩阵

在邻接矩阵中,如果我们用权重的相似性替换连接的数量,我们将得到亲和力矩阵。如果点完全不相似,则亲和力为 0,否则如果它们完全相似,则亲和力为 1。矩阵中的值表示数据点之间不同水平的相似性。

 快速测验 – 回答这些问题以检查你的理解。答案在书的末尾

1.   度矩阵是通过计算连接数量创建的。真还是假。

2.   拉普拉斯矩阵是否是度和邻接矩阵之间的转置的除法。真还是假。

3.   在纸上写下一个矩阵,然后推导出它的邻接和度矩阵。

我们现在已经掌握了谱聚类的所有构建模块。我们现在可以转向谱聚类的过程。

5.3.2 谱聚类的过程

现在我们已经掌握了谱聚类的所有构建模块。在高层次上,各个步骤可以总结如下:

  1. 我们获得数据集并计算其度矩阵和邻接矩阵。

  2. 使用它们,我们得到拉普拉斯矩阵。

  3. 然后我们计算拉普拉斯矩阵的前 k 个特征向量。k 个特征向量实际上就是对应于 k 个最小特征值的向量。

  4. 这样形成的矩阵用于在 k 维空间中对数据点进行聚类。

现在我们将介绍使用示例来覆盖谱聚类的过程,如图 5-8 所示。这些步骤通常在实际实现中不会遵循,因为我们有包和库来实现它。这里涵盖了这些步骤,以便让您了解如何从零开始开发算法的想法。对于 Python 实现,我们将仅使用库和包。虽然可能从头开始开发实现,但重新发明轮子不够高效。

图 5-8 考虑所示的示例,其中我们有一些数据点,它们彼此连接。我们将对这些数据执行谱聚类。

05_08

现在当我们希望对这些数据进行谱聚类时。

  1. 我们将把创建邻接矩阵和度矩阵的工作留给您。

  2. 下一步是创建拉普拉斯矩阵。我们在图 5-9 中分享了输出的拉普拉斯矩阵。

图 5-9 展示了数据的拉普拉斯矩阵。建议您创建度矩阵和邻接矩阵并检查输出。

05_09

  1. 现在,Fielder 向量如图 5-10 所示,用于上述拉普拉斯矩阵。我们创建 Fielder 向量,如上一节所述。观察矩阵的和为零。
图 5-10 Fielder 向量是拉普拉斯矩阵的输出,在这里观察到矩阵的和为零。

05_10

  1. 我们可以看到有一些正值和一些负值,根据这些值,我们可以创建两个不同的簇。这是一个非常简单的示例,用来说明谱聚类的过程。
图 5-11 识别了两个簇。这是一个非常简单的示例,用来说明谱聚类的过程。

05_11

上述过程是谱聚类的一个非常简单的表示。谱聚类对图像分割、语音分析、文本分析、实体解析等非常有用。这是一种非常简单直观的方法,不对数据的形状做出任何假设。像 kmeans 这样的方法假设点在聚类中心周围呈球形分布,而在谱聚类中没有这样的强假设。

另一个重要的区别是,在谱聚类中,与其他方法相比,数据点不需要具有凸边界,其中紧凑性驱动聚类。谱聚类有时会很慢,因为需要计算特征值、拉普拉斯等。随着数据集的增大,复杂性增加,因此谱聚类可能会变慢,但是当我们有一个稀疏的数据集时,它是一种快速的方法。

我们现在将进行谱聚类算法的 Python 实现。

5.2.1     谱聚类的 Python 实现

到目前为止,我们已经涵盖了谱聚类的理论细节,现在是时候进入代码了。为此,我们将创建一个数据集并运行 k-means 算法,然后使用谱聚类来比较结果。

第一步:首先导入所有必要的库。这些库是标准库,除了我们将要介绍的几个。sklearn 是最著名和最受欢迎的库之一,我们从中导入 SpectralClusteringmake_blobsmake_circles

from sklearn.cluster import SpectralClustering
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
from sklearn.datasets import make_circles
from numpy import random
import numpy as np
from sklearn.cluster import SpectralClustering, KMeans
from sklearn.metrics import pairwise_distances
from matplotlib import pyplot as plt
import networkx as nx
import seaborn as sns

第二步:我们现在将创建一个数据集。我们使用的是 make_circles 方法。在这里,我们取 2000 个样本,并将它们表示成一个圆。结果如下所示。

data, clusters = make_circles(n_samples=2000, noise=.01, factor=.3, random_state=5)
plt.scatter(data[:,0], data[:,1]) 

05_11a

第三步:我们现在将使用 kmeans 聚类测试这个数据集。两种颜色显示两个不同的重叠的簇。

kmeans = KMeans(init='k-means++', n_clusters=2)
km_clustering = kmeans.fit(data)
plt.scatter(data[:,0], data[:,1], c=km_clustering.labels_, cmap='prism', alpha=0.5, edgecolors='g')

05_11b

第四步:我们现在用谱聚类运行相同的数据,发现两个簇在这里被单独处理。

spectral = SpectralClustering(n_clusters=2, affinity='nearest_neighbors', random_state=5)
sc_clustering = spectral.fit(data)
plt.scatter(data[:,0], data[:,1], c=sc_clustering.labels_, cmap='prism', alpha=0.5, edgecolors='g')

05_11c

我们可以观察到同一数据集在两个算法中的处理方式不同。谱聚类在处理分离的圆圈时表现更好,分离的圆圈被独立的表示出来。

第五步:你可以尝试改变数据集中的值并运行算法,模拟不同的情况,进而比较结果。

第一部分的算法已经讲解完成。下一部分我们会讲解模糊聚类。

5.3 模糊聚类

到目前为止,我们已经涵盖了相当多的聚类算法。你是否想过为什么一个数据点只能属于一个聚类?为什么一个数据点不能属于多个聚类?看一下图 5-12。

图 5-12 左侧的图表示所有数据点。红点可以属于多个聚类。实际上,我们可以给每个点分配多个聚类。可以给一个点赋予属于特定聚类的概率分数。

05_12

我们知道聚类是根据对象之间的相似度将其分为同一个组的方法。相似的项在同一簇中,而不同的项在不同的簇中。聚类的思想是确保同一簇中的项应该尽可能相似。当对象只能在一个簇中时,称为硬聚类。K-means 聚类是硬聚类的经典例子。但是,如果回顾图 5-12,我们可以观察到一个对象可以属于多个簇。这也被称为软聚类

创建模糊边界比创建硬聚类更经济。

在模糊聚类中,一个项目可以被分配给多个聚类。靠近聚类中心的项目可能比靠近聚类边缘的项目更多地属于该聚类。这被称为成员关系。它采用最小二乘解决方案找到一个物品的最佳位置。这个最佳位置可能是两个或多个聚类之间的概率空间。我们将在详细介绍模糊聚类过程时详细讨论这个概念,现在我们将转向模糊聚类算法的类型。

5.3.3 模糊聚类的类型

模糊聚类可以进一步分为经典模糊算法和基于形状的模糊算法,我们通过图 5-13 来展示。

图 5-13 模糊算法可以分为经典模糊算法和基于形状的模糊算法。

05_13

我们将在这里详细介绍模糊 c 均值算法。其他算法我们将简要介绍。

  1. 高斯塔夫松-凯塞尔算法,有时称为 GK 算法,通过将一个项目与一个聚类和一个矩阵相关联。通过使用协方差矩阵,GL 会生成椭圆形聚类,并根据数据集中的各种结构进行修改。它允许算法捕捉聚类的椭圆形属性。GK 可以导致更窄的聚类,并且在项目数较多的地方,这些区域可能更加薄。

  2. Gath-Geva 算法不基于目标函数。聚类可以导致任何形状,因为它是统计估计值的模糊化。

  3. 基于形状的聚类算法根据其名称自解释。圆形模糊聚类算法将导致圆形的聚类,依此类推。

  4. 模糊 c 均值算法或 FCM 算法是最流行的模糊聚类算法。它最初由 J.C. Dunn 于 1973 年开发,之后进行了多次改进。它与 k 均值聚类非常相似。这里有一个成员关系的概念,我们将在下面介绍。

参考图 5-14。在第一幅图中,我们有一些项目或数据点。这些数据点可以是聚类数据集的一部分,如客户交易等。在第二幅图中,我们为这些数据点创建了一个聚类。在创建该聚类时,为每个数据点分配成员关系成绩。这些成员关系成绩表明数据点属于聚类的程度或级别。我们将很快介绍计算这些值的数学函数。

我们不应该混淆程度和概率。如果我们对这些程度进行求和,可能得不到 1,因为这些值在 0 和 1 之间对所有项目进行了归一化。

在第三幅图中,我们可以观察和比较点 1 靠近聚类中心,因此比点 2 更多地属于该聚类的程度,而点 2 靠近聚类的边界或边缘。

图 5-14 (i) 我们这里有一些数据点,可以被聚类 (ii) 数据点可以被分成两个簇。对于第一个簇,聚类中心用加号表示。(iii) 我们可以观察到这里点 1 相对于点 2 更接近聚类中心。因此,我们可以得出结论,点 1 属于这个聚类的程度高于聚类 2。

05_14

现在我们将深入探讨算法的技术细节。尽管可能有点数学重,但本节可以作为可选内容。

假设我们有一组 n 个项目

X = {x[1], x[2], x[3], x[4], x[5]…. x[n]}

我们将 FCM 算法应用于这些项。这些 n 项根据某些标准被聚类成 c 个模糊聚类。假设我们从算法中获得了一个 c 个聚类中心的列表,表示为 C = {c[1], c[2], c[3], c[4], c[5]…. c[c]}

算法还返回一个可以定义为下述的划分矩阵。

05_00

这里,每个元素 w[i,j] 表示元素 X 中每个元素属于聚类 c[j] 的程度。这是划分矩阵的目的。

数学上,我们可以得到 w[i,j] 如方程式 5-1 所示。方程的证明超出了本书的范围。

05_14a

该算法还为聚类生成聚类中心。聚类的中心是该聚类中所有点的平均值,平均值由它们各自的属于该聚类的程度加权得到。如果我们用数学表示,我们可以写成方程式 5-2。

05_14b

在方程式 5-1 和 5-2 中,有一个非常重要的术语“m”。m 是用于控制聚类模糊性的超参数。m 的值 ≥ 1,通常可以保持为 2。

m 值越高,我们将获得更模糊的聚类。

现在我们将逐步检查 FCM 算法的过程:

  1. 首先,我们像 k-means 聚类一样开始。我们选择我们希望在输出中拥有的聚类数量。

  2. 然后,将系数随机分配给每个数据点。

  3. 现在我们希望迭代直到算法收敛。回想一下 k-means 算法如何收敛,我们通过随机分配聚类数量来启动该过程。然后,我们迭代地计算每个聚类的中心。这就是 kmeans 如何收敛的。对于 FCM,我们将使用类似的过程,尽管有细微的差异。我们增加了一个成员值 w[i,j] 和 m。

  4. 对于 FCM,为了使算法收敛,我们根据方程式 5-2 计算每个聚类的中心。

05_14c

  1. 对于每个数据点,我们还计算其在特定聚类中的系数。我们将使用方程式 5-1。

  2. 现在我们必须迭代直到 FCM 算法收敛。我们希望最小化的成本函数由 () 给出。

05_14d

一旦这个函数被最小化,我们就可以得出结论,即 FCM 算法已经收敛。换句话说,我们可以停止进程,因为算法已经完成处理。

现在是与 k 均值算法进行比较的好时机了。在 k 均值中,我们有一个严格的目标函数,它只允许一个聚类成员身份,而对于 FCM 聚类,我们可以根据概率分数得到不同的聚类成员身份。

FCM 在边界不清晰且严格的业务案例中非常有用。考虑在生物信息学领域中,一个基因可以属于多个聚类。或者如果我们有重叠的数据集,比如在营销分析、图像分割等领域。与 k 均值相比,FCM 可以给出相对更稳健的结果。

我们现在将在下一节继续进行 FCM 聚类的 Python 实现。

 小测验 - 回答这些问题以检查你的理解。答案在本书末尾。

1.   模糊聚类允许我们创建重叠的聚类。真或假。

2.   一个数据点只能属于一个聚类。真或假。

3.   如果“m”的值较低,则我们会得到更清晰的聚类。真或假。

5.3.4 FCM 的 Python 实现

我们在上一节已经介绍了 FCM 的过程。本节我们将着手讨论 FCM 的 Python 实现。

步骤 1:导入必要的库。

import skfuzzy as fuzz
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

步骤 2:我们现在将声明一个颜色调色板,稍后将用于对聚类进行颜色编码。

color_pallete = ['r','m','y','c', 'brown', 'orange','m','k', 'gray','purple','seagreen']

步骤 3:我们将定义聚类中心。

cluster_centers = [[1, 1],
           [2, 4],
           [5, 8]]

步骤 4:

sigmas = [[0.5, 0.6],
          [0.4, 0.5],
          [0.1, 0.6]]

步骤 5:

np.random.seed(5)  

xpts = np.zeros(1)
ypts = np.zeros(1)
labels = np.zeros(1)
for i, ((xmu, ymu), (xsigma, ysigma)) in enumerate(zip(cluster_centers, sigmas)):
    xpts = np.hstack((xpts, np.random.standard_normal(500) * xsigma + xmu))
    ypts = np.hstack((ypts, np.random.standard_normal(500) * ysigma + ymu))
    labels = np.hstack((labels, np.ones(500) * i))

步骤 6:

fig0, ax0 = plt.subplots()
for label in range(5):
    ax0.plot(xpts[labels == label], ypts[labels == label], '.')
ax0.set_title('Data set having 500 points.')
plt.show()

05_14e

步骤 7:

fig1, axes1 = plt.subplots(3, 3, figsize=(10, 10))
alldata = np.vstack((xpts, ypts))
fpcs = []

for ncenters, ax in enumerate(axes1.reshape(-1), 2):
    cntr, u, u0, d, jm, p, fpc = fuzz.cluster.cmeans(
        alldata, ncenters, 2, error=0.005, maxiter=1000, init=None)

    # Store fpc values for later
    fpcs.append(fpc)

    # Plot assigned clusters, for each data point in training set
    cluster_membership = np.argmax(u, axis=0)
    for j in range(ncenters):
        ax.plot(xpts[cluster_membership == j],
                ypts[cluster_membership == j], '.', color=colors[j])

    # Mark the center of each fuzzy cluster
    for pt in cntr:
        ax.plot(pt[0], pt[1], 'rs')

    ax.set_title('cluster_centers = {0}; FPC = {1:.2f}'.format(ncenters, fpc), size=12)
    ax.axis('off')

fig1.tight_layout()

05_14f

通过这个我们结束了模糊聚类,我们可以在下一节转到高斯混合模型。

5.4 高斯混合模型

我们将继续我们在上一节中关于软聚类的讨论。回想一下,我们在那里介绍了高斯混合模型。现在我们将对其进行详细说明。我们将研究这个概念,并对其进行 Python 实现。

首先让我们重新理解高斯分布,有时也被称为正态分布。你可能听说过钟形曲线,它指的是同一件事情。

在图 5-15 中,观察µ(平均值)为 0,σ²(标准差)为 1 的分布。这是一个完美的正态分布曲线。比较这里不同曲线上的分布。

(图片来源 - 维基百科)

图 5-15 高斯分布是最著名的分布之一。观察均值和标准差值的变化及其对应曲线的影响。

05_15

高斯分布的数学表达式为

05_15a

上面的方程也被称为概率密度函数 (pdf)。在图 5-15 中,观察到当 µ 为 0,σ² 为 1 时的分布。这是一个完美的正态分布曲线。通过改变均值和标准差的值,我们在图 5-15 中比较不同曲线中的分布,从而得到不同的图形。

你可能想知道为什么我们在这里使用高斯分布。这里有一个非常著名的统计定理叫做中心极限定理。我们在这里简要解释该定理。根据该定理,我们收集的数据越多,分布就越趋向于高斯分布。这种正态分布可以在化学、物理、数学、生物学或任何其他学科中观察到。这就是高斯分布的美妙之处。

在图 5-15 中显示的图是一维的。我们也可以有多维高斯分布。在多维高斯分布的情况下,我们将得到图 5-16 中显示的 3D 图。我们的输入是一维的标量。现在,我们的输入不再是标量,而是一个向量,均值也是一个向量,代表数据的中心。因此,均值的维度与输入数据相同。方差现在是协方差矩阵 ∑。该矩阵不仅告诉我们输入的方差,还评论了不同变量之间的关系。换句话说,如果 x 的值发生变化,y 的值会受到影响。请看下面的图 5-16。我们可以理解这里的 x 和 y 变量之间的关系。

(图片来源 – 维基百科)

图 5-16 展示了高斯分布的三维表示。

05_16

协方差在这里起着重要作用。K-means 不考虑数据集的协方差,而在 GMM 模型中使用。

让我们来研究 GMM 聚类的过程。想象我们有一个包含 n 个项目的数据集。当我们使用 GMM 聚类时,我们不是使用质心方法来找到聚类,而是对手头的数据集拟合一组 k 个高斯分布。换句话说,我们有 k 个聚类。我们必须确定每个高斯分布的参数,包括聚类的均值、方差和权重。一旦确定了每个分布的参数,我们就可以找到每个项目属于 k 个聚类的相应概率。

从数学上讲,我们可以根据方程 5-5 计算概率。该方程用于告诉我们一个特定点 x 是 k 个高斯分布的线性组合。Φ[j] 术语用于表示高斯的强度,并且可以看到第二个方程中这种强度的总和等于 1。

05_16a

对于谱聚类,我们必须确定 Φ、∑ 和 µ 的值。正如你所想象的,获取这些参数的值可能是一项棘手的任务。确实有一种稍微复杂的叫做期望最大化技术或 EM 技术,我们现在将介绍它。这一部分涉及的数学概念比较深,是可选的。

5.4.1 期望最大化(EM)技术

EM 是确定模型正确参数的统计和数学解决方案。有很多流行的技术,也许最著名的是最大似然估计。但同时,最大似然估计也可能存在一些挑战。数据集可能有缺失值,或者换句话说,数据集是不完整的。或者数据集中的一个点可能是由两个不同的高斯分布生成的。因此,确定生成数据点的分布将非常困难。在这里,EM 可以提供帮助。

k-means 只使用平均值,而 GMM 利用数据的平均值和方差。

用于生成数据点的过程被称为潜在变量。由于我们不知道这些潜在变量的确切值,EM 首先使用当前数据估计这些潜在变量的最佳值。一旦完成了这一步,然后估计模型参数。使用这些模型参数,再次确定潜在变量。并且使用这些新的潜在变量,推导出新的模型参数。这个过程持续进行,直到获得足够好的潜在值和模型参数,使其很好地适应数据。现在让我们更详细地研究一下。

我们将使用上一节中的相同示例。

想象一下我们有一个包含 n 个项的数据集。当我们使用 GMM 聚类时,我们不是使用质心方法找到簇,而是将一组 k 个高斯分布拟合到手头的数据集。换句话说,我们有 k 个簇。我们必须确定每个高斯分布的参数,即簇的平均值、方差和权重。假设均值为 µ[1]、µ[2]、µ[3]、µ[4]…. µ[k],协方差为 ∑[1]、∑[2]、∑[3]、∑[4]…. ∑[k]。我们还可以有一个参数来表示分布的密度或强度,可以用符号 Φ 表示。

现在我们将从期望步骤或 E 步开始。在这一步中,每个数据点都以概率分配到一个簇。因此,对于每个点,我们计算其属于一个簇的概率,如果这个值很高,则该点在正确的簇中,否则该点在错误的簇中。换句话说,我们正在计算每个数据点由每个 k 个高斯分布生成的概率。

由于我们正在计算概率,因此这些被称为软分配。

概率是使用方程式 5-6 计算的。如果我们仔细观察,分子是概率,然后我们通过分母进行归一化。分子与我们在方程式 5-5 中看到的相同。

05_16b

在上面的期望步骤中,对于数据点 x[i,j],其中 i 是行,j 是列,我们得到一个矩阵,其中行由数据点表示,列由它们对应的高斯值表示。

现在期望步骤已经完成,我们将执行最大化或 M 步骤。在这一步中,我们将使用方程式 5-7 下面的公式更新 µ、∑ 和 Φ 的值。回想一下,在 k-means 聚类中,我们只是取数据点的平均值并继续前进。我们在这里做了类似的事情,尽管使用了我们在上一步中计算的概率或期望。

三个值可以使用下面的方程式计算。方程式 5-7 是协方差 ∑[j] 的计算,在其中我们计算所有点的协方差,然后以该点由高斯 j 生成的概率加权。数学证明超出了本书的范围。

05_16c

均值 µ[j] 由方程式 5-8 确定。在这里,我们确定所有点的均值,以该点由高斯 j 生成的概率加权。

05_16d

类似地,密度或强度是由方程式 5-9 计算的,其中我们将每个点生成为高斯 j 的概率相加,然后除以总点数 N。

05_16e

根据这些值,推导出新的 ∑、µ 和 Φ 的值,并且该过程继续直到模型收敛。当我们能够最大化对数似然函数时,我们停止。

这是一个复杂的数学过程。我们已经涵盖了它,以便让您深入了解统计算法背后发生的事情。Python 实现比我们现在将要介绍的数学概念要简单得多。

 POP QUIZ – 回答这些问题以检查您的理解。答案在书的末尾。

1.   高斯分布的均值等于 1,标准差等于 0。是或否。

2.   GMM 模型不考虑数据的协方差。是或否。

5.4.2 GMM 的 Python 实现

我们首先导入数据,然后将使用 kmeans 和 GMM 进行结果比较。

步骤 1:我们将导入所有的库并导入数据集。

import pandas as pd
data = pd.read_csv('vehicle.csv')
import matplotlib.pyplot as plt

步骤 2:我们现在将从数据集中删除任何 NA。

data = data.dropna()

步骤 3:我们现在将拟合一个 kmeans 算法。我们将聚类数保持为 5。请注意,我们并不是说它们是理想的聚类数。这些聚类数仅用于说明目的。我们声明一个变量 kmeans,然后使用五个聚类,然后是下一个拟合数据集。

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=5)
kmeans.fit(data)

步骤 4:我们现在将绘制聚类。首先,在数据集上进行预测,然后将值添加到数据框作为新列。然后,用不同颜色表示不同的聚类绘制数据。

输出显示在下图中。

pred = kmeans.predict(data)
frame = pd.DataFrame(data)
frame['cluster'] = pred

color=['red','blue','orange', 'brown', 'green']
for k in range(0,5):
    data = frame[frame["cluster"]==k]
    plt.scatter(data["compactness"],data["circularity"],c=color[k])
plt.show()

05_16f

步骤 4:我们现在将拟合一个 GMM 模型。请注意,代码与 kmeans 算法相同,只是算法的名称从 kmeans 更改为 GaussianMixture。

from sklearn.mixture import GaussianMixture
gmm = GaussianMixture(n_components=5)
gmm.fit(data)

#predictions from gmm
labels = gmm.predict(data)
frame = pd.DataFrame(data)
frame['cluster'] = labels

步骤 5:我们现在将绘制结果。输出如下所示。

color=['red','blue','orange', 'brown', 'green']
for k in range(0,5):
    data = frame[frame["cluster"]==k]
    plt.scatter(data["compactness"],data["circularity"],c=color[k])
plt.show()

05_16g

步骤 6:建议您使用不同的聚类值运行代码以观察差异。下面的图中,左边是具有两个聚类的 kmeans,右边是具有两个聚类的 GMM。

05_16h

高斯分布是最广泛使用的数据分布之一。如果我们比较 kmeans 和 GMM 模型,我们会发现 kmeans 并不考虑数据的正态分布。kmeans 也不考虑各个数据点之间的关系。

Kmeans 是基于距离的算法,GMM 是基于分布的算法。

简而言之,使用 GMM 模型创建聚类尤其有利,特别是当我们有重叠的数据集时。这是一个在金融和价格建模、基于 NLP 的解决方案等方面非常有用的技术。

通过这个,我们已经涵盖了本章的所有算法。我们现在可以转到总结。

5.5 总结

在本章中,我们探讨了三种复杂的聚类算法。你可能觉得数学概念有点沉重。它们确实很沉重,但能更深入地理解过程。并不是说这些算法对每个问题都是最好的。在现实世界的业务问题中,理想情况下,我们应该首先使用经典的聚类算法 - kmeans、层次聚类和 DBSCAN。如果我们得不到可接受的结果,那么我们可以尝试复杂的算法。

许多时候,将数据科学问题等同于算法的选择,其实并不是这样。算法当然是整个解决方案的重要组成部分,但不是唯一的部分。在现实世界的数据集中,有很多变量,数据量也相当大。数据有很多噪音。当我们筛选算法时,我们必须考虑所有这些因素。算法的维护和更新也是我们心中的主要问题之一。所有这些细节在书的最后一章中都有详细介绍。

我们将在下一章介绍复杂的降维技术。你现在可以转到问题。

实际下一步和建议阅读

  1. 在第二章中,我们使用了各种技术进行聚类。使用那里的数据集,并执行谱聚类、GMM 和 FCM 聚类以比较结果。

  2. 第二章末尾提供了数据集,可以用于聚类。

  3. 从此 Kaggle 链接获取用于聚类的信用卡数据集(www.kaggle.com/vipulgandhi/spectral-clustering-detailed-explanation),以及我们之前也使用过的著名的 IRIS 数据集。

  4. 有一本由亨利·赫克斯莫尔(Henry Hexmoor)撰写的《计算网络科学》是研究数学概念的好书。

  5. 从下面的链接获取光谱聚类论文并学习它们:

  6. 关于光谱聚类:分析与算法proceedings.neurips.cc/paper/2001/file/801272ee79cfde7fa5960571fee36b9b-Paper.pdf

  7. 具有特定特征值选择的光谱聚类www.eecs.qmul.ac.uk/~sgg/papers/XiangGong-PR08.pdf

  8. 光谱聚类背后的数学及其与主成分分析的等价性arxiv.org/pdf/2103.00733v1.pdf

  9. 从以下链接获取 GMM 论文并探索它们:

  10. 用于聚类的特定高斯混合模型citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.79.7057&rep=rep1&type=pdf

  11. 在数据流中应用复合高斯混合模型ieeexplore.ieee.org/document/5620507

  12. 从以下链接获取 FCM 论文并学习它们:

  13. FCM:模糊 c 均值聚类算法www.sciencedirect.com/science/article/pii/0098300484900207

  14. 对模糊 c 均值聚类技术进行调查www.ijedr.org/papers/IJEDR1704186.pdf

  15. 模糊 c 均值和可能性 c 均值聚类算法的实现,聚类倾向分析和聚类验证arxiv.org/pdf/1809.08417.pdf

第六章:维度降低(高级)

“生活很简单,但我们坚持把它变复杂了 - 孔子”

简单是一种美德,无论是在生活中还是在数据科学中都是如此。到目前为止,我们已经讨论了很多算法 - 其中一些足够简单,而另一些则有些复杂。在本书的第一部分,我们学习了更简单的聚类算法,在最后一章中,我们考察了高级聚类算法。同样,在第三章中,我们学习了几个维度算法,如 PCA。在同样的基础上,我们将在本章中学习两种高级降维技术。

本书本部分和下一部分涵盖的高级主题旨在为您准备复杂问题。虽然您可以应用这些高级解决方案,但始终建议从经典解决方案(如 PCA 进行维度降低)开始。如果获得的解决方案不够优秀,则可以尝试高级方案。

在我们有很多变量时,降维是最受追捧的解决方案之一。请回忆一下第三章中讨论过的“维度诅咒”。在继续之前,建议您回顾第三章。我们将在本章中涵盖 t 分布随机邻居嵌入(t-SNE)和多维缩放(MDS)。本章将涉及一些创建我们将讨论的高级技术基础的数学概念。像往常一样,我们将有概念讨论,接着是 Python 实现。本章末尾将有一个简短的案例研究。此外,在本章中,我们也将使用图像数据集开发解决方案!

您的脑海中可能会有一个困惑。需要什么水平的数学知识,深入的统计知识是先决条件吗?答案既是肯定也是否定。虽然,具备数学理解能让您更深入地理解算法并欣赏过程;同时,对于实际业务实现,有时可能想跳过数学直接转向 Python 实现。我们建议您至少具有超过基本的数学理解以充分把握概念。在本书中,我们提供这种数学支持水平而不过度深入 - 实践世界和数学概念的最佳混合。

在本书的第六章中,我们将介绍以下主题:

  1. t 分布随机邻居嵌入(t-SNE)

  2. 多维缩放(MDS)

  3. 算法的 Python 实现

  4. 案例研究

欢迎来到第六章,并祝你好运!

6.1 技术工具箱

我们将继续使用与迄今为止相同版本的 Python 和 Jupyter 笔记本。本章中使用的代码和数据集已在github.com/vverdhan/UnsupervisedLearningWithPython/tree/main/Chapter%206位置检查过。

在本章中,您需要安装 Keras 作为额外的 Python 库。除此之外,我们还需要常规的库 - numpy、pandas、matplotlib、seaborn、sklearn。使用库,我们可以非常快速地实现算法。否则,编写这些算法是非常耗时和痛苦的任务。

让我们开始学习本书的第六章吧!

6.2 多维缩放(MDS)

我喜欢旅行。不幸的是,由于 COVID 大流行,旅行受到了打击。正如您所知,地图在旅行中非常方便。现在,想象一下你被分配了一个任务。你收到了世界各地一些城市之间的距离。例如,伦敦和纽约之间,伦敦和巴黎之间,巴黎和新德里之间等等。然后我们要求您重新创建生成这些距离的地图。如果我们必须重新创建那个二维地图,那将通过试错,我们将做出一些假设并继续进行该过程。这肯定是一项令人疲惫的练习,容易出错,而且确实非常耗时。MDS 可以轻松地为我们完成这项任务。

在考虑上述例子时,请忽略地球不是平的这一事实。并假设距离测量度量是恒定的。例如,英里或公里之间没有混淆。

作为例证,考虑图 6-1。

图 6-1 表示城市之间的距离以及它们在地图上的表示。该图仅用于帮助理解,并不代表实际结果。

06_01

正式地说,如果我们有 x 个数据点,多维缩放(MDS)可以帮助我们将这些 x 点之间的成对距离的信息转换为笛卡尔空间中点的配置。或者简单地说,MDS 将一个大的维度数据集转换为一个较低维度的数据集,并在这个过程中保持点之间的距离或相似度不变。

为了简化,考虑下面的图片。这里有三个点 - A、B 和 C。我们将这些点表示在一个三维空间中。然后我们在二维空间中表示这三个点,最后它们在一维空间中表示。在下图 6-2 中,图中的点之间的距离不是按比例的。图 6-2 中的示例表示降低维度数量的含义。

图 6-2 表示三个点 - 首先我们展示三个点在三维空间中。然后它们被表示在二维空间中,最后在一维空间中。

06_02

因此,在 MDS 中,多维数据被降低到较低维度。

我们可以有三种类型的 MDS 算法

  1. 经典 MDS,

  2. 度量多维缩放和

  3. 非度量多维缩放。

我们将在书中详细讨论度量 MDS 过程,同时我们将简要介绍经典的和非度量的方法。

想象一下我们有两个点 – i 和 j。假设两点之间的原始距离是 d[ij],而低维空间中的相应距离是 d[ij]。

在经典 MDS 中,点之间的距离被视为欧几里得距离,原始距离和拟合距离在相同的度量中表示。这意味着,如果在高维空间中使用欧几里得方法计算原始距离,那么在低维空间中计算的拟合距离也是使用欧几里得距离计算的。我们已经知道如何计算欧几里得距离了。例如,我们要找到点 i 和 j 之间的距离,假设距离为 d[ij]。距离可以用方程式 6-1 给出的欧几里得距离公式给出。

06_02a

在早些章节中,我们已经讨论过其他距离函数,如曼哈顿距离、汉明距离等。建议你进行复习。

现在我们将来到非度量 MDS。刚才我们注意到欧几里得距离可以用来计算两点之间的距离。有时候无法采用距离的实际值,比如当 d[ij] 是一个实验的结果,主观评价被做出时。换句话说,各个数据参数被分配了一个排名。例如,如果点 2 和 5 之间的距离在原始数据中排名第 4,那么在这种情况下,使用 d[ij] 的绝对值就不明智了,因此必须使用相对值或排名值。这就是非度量 MDS 的过程。例如,想象一下我们有四个点 – A、B、C 和 D。我们希望排列这四个点之间的相应距离。点的相应组合可以是 – A 和 B、A 和 C、A 和 D、B 和 C、B 和 D,最后是 C 和 D。它们的距离可以按照表 6-1 中所示的排名进行排列。

表 6-1 代表着四个点之间的相应距离以及距离的排名
点对 距离 相应距离的排名
A 和 B 100 3
A 和 C 105 4
A 和 D 95 2
B 和 C 205 6
B 和 D 150 5
C 和 D 55 1

因此,在非度量 MDS 方法中,我们不是使用实际距离,而是使用距离的相应排名。现在我们将转向度量 MDS 方法。

我们知道在经典的 MDS 中,原始距离和拟合距离在相同的度量中表示。在度量 MDS中,假设通过在数据集上应用一些参数变换,d[ij]的值可以转换为欧几里得距离。在一些文章中,你可能会发现经典 MDS 和度量 MDS 被互换使用。

在 MDS 中,作为第一步,计算点之间的相应距离。一旦计算出相应的距离,MDS 将尝试将更高维的数据点表示为更低维的空间。为了执行此操作,必须进行优化过程,以便选择最合适的结果维数。因此,必须优化一个损失函数或成本函数。

如果你不知道什么是成本函数,请看下面这一节。

成本函数

我们使用算法来预测变量的值。例如,我们可能会使用某种算法来预测下一年产品的预期需求。我们希望算法尽可能准确地预测。成本函数是检查算法性能的一种简单方法。

成本函数是衡量我们算法效果的一种简单技术。它是衡量预测模型性能的最常见方法。它比较算法预测的原始值和预测值,并计算模型在预测中的错误程度。

正如你所想象的,在理想解决方案中,我们希望预测值与实际值相同,这是非常难以实现的。如果预测值与实际值相差很大,成本函数的输出就会更高。如果预测值接近实际值,则成本函数的值较低。一个健壮的解决方案是具有较低成本函数值的解决方案。因此,优化任何算法的目标将是最小化成本函数的值。成本函数也称为损失函数,这两个术语可以互换使用。

在度量 MDS 中,我们也可以将成本函数称为应力。应力的公式由下面给出的方程式 6-2 给出:

06_02b

现在让我们来了解方程式:

  1. 术语 Stress[D] 是 MDS 函数必须最小化的值。

  2. 在较低维空间中具有新坐标集的数据点由 x[, x[2], x[3]…. x[N].] 表示。

  3. 术语 ||x[i] – x[j]|| 是它们在较低维空间中的两点之间的距离。

  4. 术语 d[ij] 是原始多维空间中两点之间的原始距离。

通过观察方程式,我们可以清楚地理解,如果||x[i] – x[j]||和 d[ij]的值彼此接近,结果应力的值将较小。

最小化应力值是损失函数的目标。

为了优化这个损失函数,可以使用多种方法。其中最著名的方法之一是使用梯度下降法,最初由 Kruskal 和 Wish 于 1978 年提出。梯度下降法非常简单易懂,并可以用一个简单的类比来解释。

想象一下你站在山顶上,想要下来。在这样做的同时,你想选择最快的路径,因为你想尽快下来(不,你不能跳!)。所以,为了迈出第一步,你会四处看看,无论哪条路最陡峭,你都可以朝那个方向迈出一步,然后你会到达一个新的点。然后再次,你会朝最陡峭的方向迈出一步。我们在图 6-3(i) 中展示了这个过程。

图 6-3 (i) 第一幅图是一个站在山顶上并试图下来的人。梯度下降过程遵循这种方法 (ii) 梯度下降过程中成本函数的实际优化过程。注意,在收敛点,成本函数的值是最小的。

06_03

现在,如果一个算法要达到类似的效果,过程可以表示为图 6-3 (ii),其中损失函数从一个点开始,最终到达收敛点。在这个收敛点,成本函数是最小的。

MDS 与其他降维技术不同。与诸如 PCA 等技术相比,MDS 不对数据集做任何假设,因此可以用于更大类型的数据集。此外,MDS 允许使用任何距离测量度量。与 PCA 不同,MDS 不是一种特征值-特征向量技术。回想一下,在 PCA 中,第一轴捕获最大的方差,第二轴具有下一个最佳方差,依此类推。在 MDS 中,没有这样的条件。MDS 中的轴可以根据需要翻转或旋转。接下来,在大多数其他使用的降维方法中,算法确实计算了许多轴,但无法查看。在 MDS 中,开始时明确选择较小数量的维度。因此,解决方案中的歧义较少。此外,在其他解决方案中通常只有一个唯一的解决方案,而 MDS 尝试迭代地找到可接受的解决方案。这意味着在 MDS 中,对于相同的数据集可能会有多个解决方案。

但与此同时,MDS 所需的计算时间对于更大的数据集要求更高。在用于优化的梯度下降方法中存在一个陷阱。参考图 6-4。让我们参考上一节中提到的山的例子。想象一下,当你从山顶下来时。起点是 A,山的底部在 C 点。当你下来时,你到达了 B 点。正如图 6-4(i) 中所示,B 点周围有一点高度。在 B 点,你可能会错误地得出结论,认为自己已经到达了山底。换句话说,你会认为自己完成了任务。这就是局部最小值的确切问题。

有可能损失函数会陷入局部极小值而不是全局极小值。算法可能会认为已经达到收敛点,而实际上并没有完全收敛,我们仍然处于局部极小值。

图 6-4 虽然第一幅图是收敛点并且代表梯度下降法,但请注意第二幅图中全局极小值在其他地方,而算法可能会陷入局部极小值。该算法可能认为它已经优化了成本函数并达到了全局极小值,而实际上它只达到了局部极小值。

06_04

关于 MDS 解决方案的有效性仍有个问题有待解答。我们如何衡量解决方案的有效性?在原始论文中,Kruskal 推荐使用应力值来衡量解决方案的拟合度,这些值显示在表 6-1 中。这些建议大多基于 Kruskal 的经验经验。这些应力值基于 Kruskal 的经验。

应力值 拟合度
0.200 较差
0.100 中等
0.050 良好
0.025 优秀
0.000 完美

下一个逻辑问题是 - 我们应该选择多少个最终维度?Scree 图提供了答案,如第 6-5 图所示。回想一下,在第二章,我们使用了类似的肘部法选择了 kmeans 聚类中的最优聚类数。对于 MDS,我们也可以使用肘部法来确定代表数据的最优组件数。

图 6-5  Scree 图来找出最优组件数。与我们在前几章讨论过的 kmeans 解决方案类似;我们需要在图表中找到肘部。

06_05

这就结束了我们对 MDS 的讨论。现在我们将转向算法的 Python 实现。

 答题 - 回答这些问题来检查你的理解。答案在书的末尾处

1.  度量和非度量 MDS 算法有什么区别?

2.  梯度下降法用于最大化成本。是 True 还是 False。

3.  使用一个简单的例子解释梯度下降法。

6.2.1  MDS 的 Python 实现

现在我们将采用 Python 实现 MDS 方法。我们将使用我们之前使用过的著名的鸢尾花数据集。由于 scikit learn 包中提供的库,算法的实现相当简单。

由于库提供了重要的工作,因此实现通常很简单。

步骤 1:我们首先加载库。通常会用到sklearnmatplotlibnumpy,并且还从sklearn加载MDS

import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.manifold import MDS
from sklearn.preprocessing import MinMaxScaler
import pandas as pd
import warnings
warnings.filterwarnings("ignore")

步骤 2:现在加载数据集。鸢尾花数据集在sklearn库中可用,因此我们无需在此处导入 excel 或.csv 文件。

raw_data = load_iris()
dataset = raw_data.data

步骤 3:MDS 的要求是在实际可视化之前对数据集进行缩放。我们使用MixMaxScalar()函数来实现相同的效果。 MinMax 缩放只是使用以下公式对数据进行缩放:

06_F01

d_scaler = MinMaxScaler()
dataset_scaled = d_scaler.fit_transform(dataset)

作为此步骤的输出,数据已经被缩放,准备进行下一步的建模。

步骤 4:我们现在从 sklearn 库中调用 MDS 方法。 random_state 值允许我们重现结果。我们已经决定示例中的组件数量为 3。

mds_output = MDS(3,random_state=5)

步骤 5:我们现在将使用 MDS 模型拟合之前创建的缩放数据。

data_3d = mds_output.fit_transform(dataset_scaled)

步骤 6:我们现在声明我们希望用于可视化的颜色。接下来,数据点在散点图中可视化。

mds_colors = ['purple','blue', 'yellow']
for i in np.unique(raw_data.target):
  d_subset = data_3d[raw_data.target == i]

  x = [row[0] for row in d_subset]
  y = [row[1] for row in d_subset]
  plt.scatter(x,y,c=mds_colors[i],label=raw_data.target_names[i])
plt.legend()
plt.show()

上述代码的输出如下图 6-6 所示:

图 6-6 IRIS 数据的输出

06_06

上述 Python 实现示例是 IRIS 数据的可视化。这是一个相当简单的例子,但不涉及压力和组件数量的优化。我们现在将在一个精心策划的数据集上实施 MDS。

06_06a

让我们假设我们有五个城市,它们之间的距离分别在表 6-2 中给出。

步骤 1:我们已经在上一个代码中导入了库。

import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.manifold import MDS
from sklearn.preprocessing import MinMaxScaler
import pandas as pd
import warnings
warnings.filterwarnings("ignore")

步骤 2:现在让我们创建数据集。我们在这里创建数据集,但在实际业务场景中,它将仅以距离的形式存在。

data_dummy_cities = {'A':[0,40,50,30,40],
          'B':[40,0,40,50,20],
          'C':[50,40,0,20,50],
          'D':[30,50,20,0,20],
          'E':[40,20,50,20,0],
          }
cities_dataframe = pd.DataFrame(data_dummy_cities, index =['A','B','C','D','E'])
cities_dataframe

06_06b

步骤 3:我们现在将使用 MinMaxScalar()函数来缩放数据集,就像我们在上一个编码练习中所做的那样。

scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(cities_dataframe) 

步骤 4:现在,让我们朝着找到最优组件数量的方向前进。我们将迭代不同组件数量的值。对于每个组件数量的值,我们将获得压力的值。当观察到一个拐点时,那就是最优的组件数量。

作为第一步,我们将声明一个空数据框,用于存储组件数量和相应压力值的值。然后,我们在 for 循环中从 1 到 10 进行迭代。最后,对于每个组件值(1 到 10),我们获取相应的压力值。

MDS_stress = []
for i in range(1, 10):
    mds = MDS(n_components=i)
    pts = mds.fit_transform(df_scaled)
    MDS_stress.append(mds.stress_)

步骤 5:我们已经得到了压力值。我们现在将这些值绘制在图表中。每个轴的相应标签也给出了。观察值为 2 和 3 的拐点。这可以是最优组件数量的值。

plt.plot(range(1, 10), MDS_stress)
plt.xticks(range(1, 5, 2))
plt.title('Plot of stress')
plt.xlabel('Number of components')
plt.ylabel('Stress values')
plt.show()
图 6-7 屏幕图以选择优化的组件数量

06_07

步骤 6:我们现在将运行组件数量= 3 的解决方案。如果我们查看压力值,组件数量= 3,它将生成压力值的最小值为 0.00665。

mds = MDS(n_components=3)
x = mds.fit_transform(df_scaled)
cities = ['A','B','C','D','E']

plt.figure(figsize=(5,5))
plt.scatter(x[:,0],x[:,1])
plt.title('MDS with Sklearn')
for label, x, y in zip(cities, x[:, 0], x[:, 1]):
    plt.annotate(
        label,
        xy = (x, y), 
        xytext = (-10, 10),
        textcoords = 'offset points'
    )
plt.show()
print(mds.stress_)
图 6-8 MDS 数据集的输出,在绘图中表示了 5 个城市

06_08

这就结束了我们关于 MDS 算法的部分。我们讨论了基础和概念,优缺点,算法评估以及 MDS 的 Python 实现。它是可视化和降维的一个很好的解决方案。它是非线性降维方法之一。

我们现在将转向 t-SNE,在本章中的第二个降维方法。

6.3 t-分布随机邻居嵌入(t-SNE)

如果数据集真的是高维的,分析就变得很麻烦。可视化甚至更加混乱。我们在第二章的维度诅咒部分中对此进行了详细的讨论。建议您在继续之前重新查看这个概念。

这样一个真正高维的数据集可以是图像数据。我们发现很难理解这种真正高维的数据。

您可能在智能手机上使用过人脸识别软件。对于这样的解决方案,必须分析面部图像并训练机器学习模型。看一下下面的图片,图 6-9 中我们有一个人脸,一辆自行车,一台吸尘器和一个游戏的屏幕截图。

图 6-9 图像对算法来说相当复杂,很难解释。图像可以是任何形式的,可以是一个人,或者一台设备,甚至是任何游戏屏幕。

06_09

图像是一个复杂的数据点。每个图像由像素组成,每个像素可以由 RGB(红色,绿色,蓝色)值组成。而每个红色、绿色、蓝色的值都可以从 0 到 255。结果数据集将是一个非常高维的数据集。

现在,回顾一下我们在第三章学习的主成分分析(PCA)。PCA 是一种线性算法。作为一种线性算法,它的能力受到限制,无法解决非线性和复杂多项式函数。此外,当一个高维数据集必须表示在一个低维空间时,算法应该将类似的数据点保持在彼此附近,这对于线性算法来说可能是一个挑战。PCA 是一种线性降维技术,它试图将不同的数据点分开得尽可能远,因为 PCA 正在尝试最大化数据点之间的方差。结果分析不够稳健,可能不适合进一步使用和可视化。因此,我们有非线性算法如 t-SNE 来帮助。

形式上来说,t-SNE 是一种非线性降维技术,非常适用于高维数据。它基于由 Sam Roweis 和 Geoffrey Hinton 开发的随机邻居嵌入。t-分布变体是由 Lauren van der Maaten 提出的。因此,t-SNE 是对 SNE 算法的改进。

在高水平上,SNE 测量高维空间和低维空间中实例对之间的相似性。一个好的解决方案是这些相似度测量之间的差异最小,因此 SNE 通过成本函数优化这些相似度测量。

我们现在将逐步检验 t-SNE 的过程。下面描述的过程在数学上有些繁重。

  1. 假设我们有一个高维空间,在这个高维空间中有一些点。

  2. 现在我们将度量各个点之间的相似性。对于一个点 x[i],我们将创建一个以该点为中心的高斯分布。我们在书的前几章已经学习了高斯或正态分布。高斯分布如图 6-10 所示。

图 6-10 高斯或正态分布,我们之前已经学过。图片来源:维基百科。

06_10

  1. 现在,我们将测量在高斯分布下落在该点(比如 x[j])的点的密度,然后重新归一化它们以获得相应的条件概率(p[j|i])。对于附近和相似的点,这个条件概率会很高,而对于远离和不相似的点,条件概率(p[j|i])的值会非常小。这些概率值是在高维空间中的。对于好奇的人,这个条件概率的数学公式是等式 6-3:

06_10a

其中σ是以 x[i]为中心的高斯分布的方差。这个条件概率的数学证明超出了本书的范围。

  1. 现在我们将在低维空间测量另一组概率。对于这组测量,我们使用柯西分布

柯西分布

  1. 柯西分布,属于连续概率分布家族。虽然与正态分布有些相似,正如我们在图 6-11 中表示的,柯西分布的峰值较窄,扩散速度较慢。这意味着,与正态分布相比,远离峰值的值的概率更高。有时,柯西分布也被称为洛伦兹分布。有趣的是,柯西分布没有定义良好的均值,但中位数是对称中心。
图 6-11 高斯分布与柯西分布的比较。(图片来源:Quora)

06_11

  1. 假设我们得到 y[i]和 y[j]作为高维数据点 x[i]和 x[j]的低维对应物。因此,我们可以像上一步那样计算概率分数。使用柯西分布,我们也可以得到第二组概率 q[j|i]。数学公式如下所示,见等式 6-4。

06_11a

  1. 到目前为止,我们已经计算了两组概率(p[j|i])和(q[j|i])。在这一步中,我们比较这两个分布并测量两者之间的差异。换句话说,当计算(p[j|i])时,我们在高维空间中测量了相似性的概率,而对于(q[j|i]),我们在低维空间中进行了相同的操作。理想情况下,如果两个空间的映射相似,那么(p[j|i])和(q[j|i])之间就不应该有任何差异。因此,SNE 算法试图最小化条件概率(p[j|i])和(q[j|i])之间的差异。

  2. 通过使用 Kullback-Liebler 散度或 KL 散度来衡量两个概率分布之间的差异,我们将在这里探讨。

KL 散度

KL 散度或相对熵用于衡量两个概率分布之间的差异 - 通常一个概率分布是数据或测量得分。第二个概率分布是原始概率分布的近似值或预测值。例如,如果原始概率分布是 X,近似概率分布是 Y。KL 散度可用于测量 X 和 Y 概率分布之间的差异。绝对来说,如果值为 0,则意味着两个分布相似。KL 散度适用于神经科学、统计学和流体力学。

  1. 为了最小化 KL 成本函数,我们使用梯度下降方法。我们已经在讨论 MDS 算法的部分中讨论过梯度下降方法。

  2. 在我们研究 t-SNE 时,还有一点非常重要,即一个重要的超参数称为困惑度。困惑度是一个超参数,允许我们控制和优化每个数据点的邻近数量。

根据官方文件,困惑度的典型值在 5 和 50 之间。

  1. 还可能存在一个额外的细微差别 - t-SNE 算法的输出在连续运行中可能永远不会相同。我们必须优化超参数的值以获得最佳输出。

 流行测验 - 回答这些问题以检查您的理解.. 答案在本书的结尾

  1. 解释自己的话语中的柯西分布。

  2. PCA 是非线性算法。是还是不是。

  3. KL 散度用于衡量两个概率分布之间的差异。是还是不是

现在我们将继续讨论算法的 Python 实现。

6.3.1 t-SNE 的 Python 实现

在这个例子中,我们将使用两个数据集。第一个是已知的 IRIS 数据集,在本书中我们已经多次使用过。第二个数据集非常有趣。它是 MNIST 数据集,这是一个手写数字的数据库。它是用来训练图像处理解决方案的最著名的数据集之一,通常被认为是图像检测解决方案的“Hello World”程序。下面显示了图像表示()。

图 6-12 MNIST 数据集-这是一组手写数字的图像。

06_12

第 1 步:我们将首先导入必要的库。请注意,我们已从 keras 库导入 MNIST 数据集。

from sklearn.manifold import TSNE
from keras.datasets import mnist
from sklearn.datasets import load_iris
from numpy import reshape
import seaborn as sns
import pandas as pd

第 2 步:首先我们将使用 IRIS 数据集。我们将加载 IRIS 数据集。数据集包括两部分 - 一部分是“数据”,另一部分是相应的标签或“目标”。这意味着“数据”是数据的描述,“目标”是 IRIS 的类型。我们使用一小段代码打印特征和标签。

iris = load_iris()
iris_data = iris.data
iris_target = iris.target
iris.feature_names
iris.target_names

第 3 步:下一步是调用 tSNE 算法。我们使用的组件数量为 2,随机状态为 5,以重现结果。然后使用算法拟合数据。

tsne = TSNE(n_components=2, verbose=1, random_state=5)
fitted_data = tsne.fit_transform(iris_data)

06_12a

第 4 步:现在我们将绘制数据。此步骤允许我们可视化算法在上一步中拟合的数据。

首先,我们将初始化一个空的数据框架。我们将逐一添加三列。我们将从 iris_target 开始,然后是 tSNE_first_component 和 tSNE_second_component。tSNE_first_component 是 fitted_data 数据框架的第一列,因此索引为 0。tSNE_second_component 是 fitted_data 数据框架的第二列,因此索引为 1。最后,我们将数据表示为散点图。

iris_df = pd.DataFrame()
iris_df["iris_target"] = iris_target
iris_df["tSNE_first_component"] = fitted_data[:,0]
iris_df["tSNE_second_component"] = fitted_data[:,1]

sns.scatterplot(x="tSNE_first_component", y="tSNE_second_component", hue=iris_df.iris_target.tolist(),
                palette=sns.color_palette("hls", 3),
                data=iris_df).set(title="Iris data tSNE projection")
图 6-13 IRIS 数据集的 tSNE 投影。请注意,我们为数据集中的三个类别得到了三个单独的聚类

06_13

现在我们将为 MNIST 数据集实现算法。

第 1 步:库已经在上一个代码示例中加载。然后我们加载数据集。数据集需要进行 reshape,这在这里完成

(digit, digit_label), (_ , _) = mnist.load_data()
digit = reshape(digit, [digit.shape[0], digit.shape[1]*digit.shape[2]])
Step 2: the subsequent steps are exactly same to the last example we used. 
tsne_MNIST = TSNE(n_components=2, verbose=1, random_state=5)
fitted_data = tsne_MNIST.fit_transform(digit)

mnist_df = pd.DataFrame()
mnist_df["digit_label"] = digit_label
mnist_df["tSNE_first_component"] = fitted_data[:,0]
mnist_df["tSNE_second_component"] = fitted_data[:,1]

sns.scatterplot(x="tSNE_first_component", y="tSNE_second_component", hue=mnist_df.digit_label.tolist(),
                palette=sns.color_palette("hls", 10),
                data=mnist_df).set(title="MNIST data T-SNE projection")
图 6-14 tSNE 对表示为不同颜色的 10 类数字的输出。

06_14

在运行 tSNE 时,请记住以下几个重要点:

  1. 在最终确定解决方案之前,用不同的超参数值运行算法。

  2. 理想情况下,perplexity 应该在 5 和 50 之间,对于优化的解决方案,perplexity 的值应该小于数据点的数量。

  3. tSNE 猜测每个点的近邻数。因此,密集的数据集将需要更高的 perplexity 值。

  4. 特别需要注意的是 perplexity 是一个超参数,它平衡了对数据的局部和全局方面的关注。

tSNE 是广泛流行的算法之一。它用于研究区域的拓扑,但单个 tSNE 不能用于做出最终评估。相反,应创建多个 tSNE 图来做出任何最终推荐。有时会有人抱怨 tSNE 是一个黑箱算法。在某种程度上这可能是真的。使 tSNE 的采用变得更加困难的是,在连续迭代中它不会生成相同的结果。因此,你可能只会发现 tSNE 被推荐用于探索性分析。

这就结束了我们关于 tSNE 的讨论。现在我们将转向案例研究。

6.4 案例研究

请回忆第三章,我们探讨了运用降维技术在电信业的案例研究。在本章中,我们将研究一个小案例,应用 tSNE 或 MDS 进行降维。

你听说过高光谱图像吗?如你所知,我们人类主要看到可见光的颜色有长波长、中波长和短波长三个波段。长波长被感知为红色,中波长为绿色,短波长为蓝色。而频谱成像则将光谱分成许多更多的波段,这种技术可以扩展到可见光以外的波段,因此在生物学、物理学、地球科学、天文学、农业等许多领域都有用途。

高光谱成像收集并处理来自整个电磁波谱的信息。它获取图像中每个像素的光谱。

图 6-15《糖端》土豆条的高光谱图像显示了看不见的缺陷(图片来源:维基百科)

06_15

一个这样的数据集可能是 Pavia 大学数据集。这个数据集是由 ROSIS 传感器在意大利北部的 Pavia 获取的。数据集的详细信息如下,并且可以从(www.ehu.eus/ccwintco/uploads/e/ee/PaviaU.mat www.ehu.eus/ccwintco/uploads/5/50/PaviaU_gt.mat)下载。

在此数据集中,光谱波段为 103,HIS 尺寸为 610*340 像素,包含 9 类。这种类型的数据可以用于作物分析、矿物检查和勘探等。由于此数据包含有关地质图案的信息,因此对于科学目的非常有用。在开发任何图像识别解决方案之前,我们必须减少此数据集的维度数量。此外,如果维度数量较大,计算成本将会更高。因此,显然应该有较少的代表性维度数量。以下显示了一些示例波段。建议您下载数据集(也已在 git Hub repo 中检入)并在数据集上使用各种降维技术以减少维度数量。

图 6-16 数据集中波段的例子。这只是随机的例子,建议您加载数据集并运行降维算法。

06_16

tSNE 和 MDS 可以在许多其他图像数据集和复杂的商业问题中发挥实际作用。下一步列出了一些此类数据集。

6.5 总结

维度缩减是一种非常有趣且有用的解决方案。它使得机器学习变得更加经济高效。想象一下,你有一个包含成千上万个属性或特征的数据集。你对数据了解不多;业务理解很少,同时你需要在数据集中找到模式。你甚至不确定这些变量是否都是相关的,还是只是随机噪声。在这样一个时刻,当我们需要快速降低数据集的维度,使其更加易于处理并减少时间时,维度缩减就是解决方案。

我们在书中较早地介绍了维度缩减技术。本章涵盖了两种高级技术——tSNE 和 MDS。这两种技术不应被视为我们讨论的其他更简单技术的替代品。相反,如果我们没有得到有意义的结果,可以尝试使用这两种技术。建议先使用 PCA,然后再尝试 tSNE 或 MDS。

我们正在增加书中的复杂性。这一章以图像开始——我们只是初探了一下。在下一章中,我们将处理文本数据,也许你会觉得非常有趣和有用。

实际下一步和建议阅读

  1. 使用第二章中使用的车辆数据集进行聚类,并在其上实施 MDS。比较在实施 MDS 之前和之后的聚类性能。

  2. 获取第二章中用于 Python 示例的数据集,并将其用于实施 MDS。

  3. 对于 MDS,您可以参考以下研究论文:

  4. Lauren van der Maaten、Eric Postma 和 H. Japp Van Den Herik 撰写的《维度缩减:一项比较性回顾》维度缩减:一项比较性回顾

  5. Satish V. Ukkusuri 和 Jian Lu 提出了一种基于多维缩放的数据维度缩减方法,用于城市道路网络的短期交通流预测。多维缩放方法在城市道路网络短期交通流预测中的应用

  6. 从下面的链接获取 tSNE 研究论文并研究它们。

  7. Laurens van der Maaten 和 Geoffrey Hinton 撰写的《使用 t-SNE 可视化数据》使用 t-SNE 可视化数据

  8. 《单细胞转录组学中使用 t-SNE 的艺术》单细胞转录组学中使用 t-SNE 的艺术

  9. 还有一篇可能感兴趣的论文-《使用 KNN、SNN 和 SVM 分类器对 t-SNE 和 MDS 维度缩减技术的性能评估》t-SNE 和 MDS 维度缩减技术的性能评估

第七章: 无监督学习用于文本数据

“每个人用相同的语言微笑 - 乔治·卡林”

我们的世界有这么多语言。这些语言是我们表达思想和情感的最常见媒介。用文字表达我们的想法的能力是人类独有的。这些词语对我们来说是信息的来源。这些词语可以被写成文本。在本章中,我们将探讨我们可以对文本数据进行的分析。文本数据属于非结构化数据,并携带着大量有用信息,因此是业务洞察的有用来源。我们使用自然语言处理或 NLP 来分析文本数据。

与此同时,为了分析文本数据,我们必须准备好数据分析。简单来说,由于我们的算法和处理器只能理解数字,我们必须用数字或向量表示文本数据。我们在本章中探讨了所有这些步骤。文本数据包含了一些重要用例的关键,如情感分析、文档分类、语言翻译等等。我们将通过案例研究来涵盖这些用例,并在此基础上开发 Python 解决方案。

本章从定义文本数据、文本数据的来源和文本数据的各种用例开始。然后,我们将转向清理和处理文本数据的步骤和过程。我们将涵盖 NLP 的概念、数学基础和将文本数据表示为向量的方法。我们将为用例创建 Python 代码。最后,我们分享了关于文本数据的案例研究。在这本书中,我们提供了适当深度的数学支持,而不会深入探讨过多 - 这是实际世界和数学概念的最佳结合。

在本书的第七章中,我们将涵盖以下主题:

  1. 文本数据和文本数据分析的各种用例

  2. 我们在处理文本数据时面临的挑战

  3. 文本数据的预处理和数据清理

  4. 将文本数据表示为向量的方法

  5. 使用 Python 进行情感分析 - 一个案例研究

  6. 使用 Python 进行文本聚类

欢迎来到第七章,祝你一切顺利!

7.1 技术工具包

我们将继续使用到目前为止所用的相同版本的 Python 和 Jupyter 笔记本。本章中使用的代码和数据集已经上传到了这个位置。

在本章中,您需要安装一些 Python 库,它们是 - XXXX。除此之外,我们还需要 numpy 和 pandas。使用库,我们可以非常快速地实现算法。否则,编写这些算法是非常耗时且繁琐的任务。

在这里,我们正在处理文本数据,也许你会发现它非常有趣和有用。

让我们开始书的第七章吧!

7.2 文本数据无处不在

请回忆一下书的第一章,我们探讨了结构化和非结构化数据集。非结构化数据可以是文本、音频、图像或视频。下面给出了非结构化数据及其来源的示例(请参见图 7-1),我们解释了非结构化数据的主要类型:文本、图像、音频和视频以及它们的示例。本章重点是文本数据。

图 7-1 非结构化数据可以是文本、图像、音频、视频。本章我们处理文本数据

一个手机的截图 自动产生描述

语言确实是赐予人类的礼物。我们沉迷于说话、发短信、写作、倾听来传达我们的想法。而这是通过生成的文本数据来完成的,这些数据是由博客、社交媒体帖子、推文、评论、故事、评价、对话等产生的。文本数据通常更加直接,情感更具表达力。必须解锁文本数据的潜力,并从中得出见解。我们可以更好地了解我们的客户,探索业务流程,并衡量提供的服务质量。我们生成的文本数据以新闻、Facebook 评论、推文、Instagram 帖子、客户评价、反馈、博客、文章、文学、故事等形式存在。这些数据代表了各种情绪和表达。

你是否在亚马逊上评论过产品或服务?你给产品评星级,同时也可以输入自由文本。去亚马逊看一些评论吧。你可能会发现一些评论有很多文字作为反馈。这些文本对产品/服务提供商来说是很有用的,可以帮助他们改进他们的产品/服务。此外,你可能参与了一些调查,要求你分享反馈。另外,随着 Alexa、Siri、Cortana 等技术的出现,语音指令正在充当人类和机器之间的接口 - 这又是一个丰富的数据来源。甚至我们打给客服中心的电话也可以成为文本数据的来源。这些通话可以被记录,并通过语音转文字转换,我们可以生成大量的文本数据。海量数据集,对吧!

我们将在下一节讨论一些文本数据的重要用例。

7.3 文本数据的用例

文本数据确实非常有用。对于业务而言,这真是一个见解丰富的来源。下面列出了一些令人感兴趣的用例。列表并不是详尽无遗的。同时,并非所有给出的用例都实施无监督学习。有些用例也需要监督学习。尽管如此,为了让你了解,我们分享了基于监督学习和无监督学习的两种用例 - 基于监督学习和无监督学习的用例。

  1. 情感分析:你可能参与过调查或针对产品/调查提供了反馈。这些调查为我们生成了大量文本数据。可以分析这些文本数据,我们可以确定评论中的情感是积极的还是消极的。简单来说,情感分析就是评估文本数据中的积极性或消极性。因此,客户对产品或服务的情感是什么。我们可以使用监督式和非监督式学习进行情感分析。

  2. 新闻分类或文档分类:看看 Google 新闻网页,你会发现每个新闻都被分类为体育、政治、科学、商业或任何其他类别。传入的新闻将根据实际文本的内容进行分类。同样,想象一下,我们手头有一些文档,我们可能想根据它们的类别或研究领域进行区分。例如,医学、经济学、历史、艺术、物理等。这样的用例确实会节省大量时间和人力。

  3. 语言翻译:从一种语言到另一种语言的文本翻译是一个非常有趣的用例。使用自然语言处理可以在各种语言之间进行翻译。语言翻译非常棘手,因为不同的语言有不同的语法规则。通常,基于深度学习的解决方案最适合语言翻译。

  4. 垃圾邮件过滤:可以使用自然语言处理和监督式机器学习来构建电子邮件垃圾邮件过滤器。监督式学习算法可以分析传入邮件的参数,并可以预测该邮件是否属于垃圾邮件文件夹。预测可以基于诸如发件人电子邮件 ID、主题行、邮件正文、附件、邮件时间等各种参数。通常,在这里使用监督式学习算法。

  5. 词性标注或 POS 标注是其中一个受欢迎的用例。这意味着我们能够区分句子中的名词、形容词、动词、副词等。命名实体识别或 NER 也是自然语言处理的著名应用之一。它涉及在句子中识别人物、地点、组织、时间、数字。例如,John 住在 London 并为 Google 工作。NER 能够生成类似[John][Person]住在[London][Location]并为[Google][organization]工作的理解。

  6. 生成句子,为图像加标题,语音转文本或文本转语音任务,手写识别是其他一些重要和受欢迎的用例。

上述列出的用例并不是穷尽的。还有大量其他用例可以使用自然语言处理实现。NLP 也是一个非常受欢迎的研究领域。我们在本章末尾分享了一些重要的论文供您参考。

虽然文本数据非常重要,但同时也是一种难以分析的数据集。需要注意的是,我们的计算机和处理器只能理解数字。因此,文本仍然需要表示为数字,以便我们可以对其进行数学和统计计算。但在深入研究文本数据准备之前,我们将介绍在处理文本数据集时面临的一些挑战。

7.4 文本数据的挑战

文本可能是最难处理的数据。有很多种方式可以表达同样的想法。例如,如果我问:“嘿,伙计,你几岁了?”和“嘿,伙计,我可以知道你多大了吗?”意思相同,对吧!对于人类来说,回答这两个问题是相同的,并且很容易解释。但对于机器来说可能是同样艰巨的任务。

我们面临的最常见的挑战有:

  1. 处理文本数据可能很复杂。文本中可能有很多垃圾字符,比如$^%*&之类的。

  2. 随着现代通信的发展,我们开始使用缩写词,比如“u”可以代表“you”,“brb”可以代表“be right back”等。

  3. 语言在变化,解除了限制,不断发展。它每天都在变化,新词汇不断被加入到语言中。

如果你做一个简单的谷歌搜索,你会发现每年都有相当多的词汇被添加到词典中。

  1. 世界上有近 6500 种语言,每一种语言都具有其独特性。每一种语言都构成了我们的世界。例如,阿拉伯语、中文、英语、法语、德语、印地语、意大利语、日语、西班牙语等。每种语言都有自己独特的用法和语法规则。甚至写作方式也可能不同 - 有些从左到右书写;有些可能从右到左书写,或者垂直书写!相同的情感,在不同语言中可能需要更少或更多的词汇来表达。

  2. 一个词的意义取决于上下文。一个词在不同的上下文中可以是形容词,也可以是名词。看看下面这些例子:

  3. “这本书是必读的”和“请为我预订一个房间”。

  4. “汤米”可以是一个名字,但当用作“汤米·希尔菲格”时,它的用法完全改变了。

  5. “苹果”是一种水果,而“Apple”是一家生产 Macintosh、iPhone 等产品的公司。

  6. “四月”是一个月份,也可以是一个名字。

  7. 再看一个例子 - “马克从英国去了法国,正在那里和约翰一起工作。他想念他的朋友”。人类很容易理解第二句中的“他”是指马克而不是约翰,这对于机器来说可能不那么简单。

  8. 同一个词可能有很多同义词,比如“好”在不同情境中可以用“积极的”、“精彩的”、“出色的”、“异常的”来替换。或者,像“studying”、“studying”、“studies”、“studies”这样的词都与同一个词根“study”相关。

  9. 文本数据的大小也可能令人生畏。管理文本数据集、存储它、清理它并更新它本身就是一项艰巨的任务。

像任何其他的机器学习项目一样,文本分析遵循机器学习的原则,尽管确切的过程略有不同。回想一下第一章,我们在图 7-2 中展示了机器学习项目的流程。建议您从第一章刷新一下流程。

图 7-2 数据科学项目的整体步骤对文本数据也是一样的。文本数据的预处理与结构化数据集非常不同。

一个手机说明的截图 Description automatically generated

定义业务问题、数据收集和监控等工作保持不变。主要的差异在于文本的处理,包括数据清理、特征创建、文本数据的表示等。我们现在来介绍它。

弹出测验 – 回答这些问题来检查你的理解。答案在书的末尾。

(1) 文本数据的三个最有影响力的用例是什么?

(2) 为什么处理文本数据如此繁琐?

7.5 文本数据的预处理

文本数据,像任何其他数据源一样,可能会杂乱无章。我们在数据发现阶段清理了一部分数据,而在预处理阶段清理了大部分数据。同时,我们需要从数据集中提取特征。这个清理过程有时是相似的,并且可以应用于大多数文本数据集。一些文本数据集可能需要定制的处理方法。我们将从清理原始文本数据开始。

7.5.1 数据清理

数据质量的重要性不容置疑。文本数据越干净,分析结果就会越好。与此同时,预处理并不是一项简单的任务。这是一个复杂且耗时的任务。

文本数据需要进行清理,因为它包含了大量的垃圾字符、无关的词语、噪音和标点符号、URL 等。清理文本数据的主要方法有:

  1. 停用词去除: 在任何语言中,有一些词是最常见的。停用词是词汇中最常见的单词,比关键词的重要性低。例如,“是”,“一个”,“这”,“有”,“已经”,“它”等。一旦我们从文本中去除了停用词,数据的维度就被减少了,因此解决方案的复杂性也减少了。

    与此同时,在去除停用词时,我们必须非常了解上下文。例如,如果我们问一个问题“下雨了吗?”。那么答案“下雨了”就是一个完整的答案。

    要去除停用词,可以定义一个定制的停用词列表并去除它们。否则,也可以使用标准库去除停用词。

当我们在处理需要上下文信息很重要的解决方案时,我们不会去除停用词。

  1. 基于频率的词汇移除: 有时,您可能希望移除文本中最常见或非常独特的词。这个过程是获取文本中词语的频率,然后设置一个频率阈值。我们可以移除最常见的词。或者也许您希望移除整个数据集中只出现一次/两次的词。根据需求,您将决定。

  2. 基于库的清洗是在我们希望使用预定义和自定义库来清理数据时进行的。我们可以创建一个词库,其中包含我们不希望在文本中出现的词,然后可以迭代地从文本数据中移除它们。这种方法允许我们以自己的选择实施清理。

  3. 垃圾或不需要的字符: 文本数据,特别是推文、评论等,可能包含许多 URL、标签、数字、标点符号、社交媒体提及、特殊字符等。我们可能需要从文本中清除它们。与此同时,我们必须小心,因为对于一个领域不重要的一些词对于另一个领域可能是非常重要的。如果数据被从网站或 HTML/XML 来源中爬取,我们需要摆脱所有 HTML 实体、标点符号、非字母等。

清理文本数据时,始终要牢记业务背景。

我们知道很多新类型的表达进入了语言。例如,lol,hahahaha,brb,rofl 等。这些表达必须被转换为它们的原始含义。甚至像:-),;-)等表情符号也必须被转换为它们的原始含义。

  1. 数据编码: 有许多数据编码可用,如 ISO/IEC,UTF-8 等。通常,UTF-8 是最流行的。但不是硬性规定一定要使用 UTF-8。

  2. 词典归一化: 根据上下文和使用情况,同一个词可能以不同的方式被表示。在词典归一化期间,我们清理这样的歧义。基本思想是将单词缩减到其根形式。因此,从彼此派生出来的词可以映射到中心词,只要它们有相同的核心含义。

    看一下图 7-2,在这里我们展示了相同的单词“eat”,已经被用在各种形式中。根词是“eat”,但这些不同的形式是 eat 的许多不同表达。

图 7-3 Ate,eaten,eats,eating 都有相同的根词——eat。词干提取和词形恢复可用于获取根词。

我们希望将所有这些词,如 eating,eaten 等,映射到它们的中心词“eat”,因为它们具有相同的核心含义。在这方面有两种主要方法可以处理:

  1. 词干提取: 词干提取是一种基本的基于规则的方法,将一个单词映射到它的核心词。它会去掉单词末尾的“es”,“ing”,“ly”,“ed”等。例如,studies 会变成 studi,studying 会变成 study。显然,作为一种基于规则的方法,输出的拼写可能并不总是准确的。

  2. 词形还原:是一种有组织的方法,将单词缩减为它们的词典形式。单词的Lemma是其词典或规范形式。例如,eats, eating, eaten 等所有单词都有相同的根词 eat。词形还原提供比词干提取更好的结果,但它需要比词干提取更多的时间。

这些只是清洗文本数据的一些方法。这些技术在很大程度上会有所帮助。但是,仍然需要商业眼光来进一步理解数据集。我们将通过开发 Python 解决方案来使用这些方法清洁文本数据。

一旦数据被清洗,我们就必须开始表示数据,以便机器学习算法可以处理它 - 这是我们的下一个主题。

7.5.2 从文本数据集中提取特征

文本数据,就像任何其他数据源一样可能会混乱和嘈杂。我们在上一节中探讨了清理它的概念和技术。现在我们已经清理了数据,准备好使用。下一步是以算法可以理解的格式表示这些数据。我们知道我们的算法只能理解数字。文本数据在其最纯粹的形式下无法被算法理解。因此,一切都需要转换为数字。

一个非常简单的技术可以是简单地对我们的单词进行独热编码,并将它们表示为矩阵。

我们在书的前几章已经介绍了独热编码

如果我们描述这些步骤,那么单词可以首先转换为小写,然后按字母顺序排序。然后可以为它们分配一个数字标签。最后,单词将被转换为二进制向量。让我们通过一个例子来理解。

例如,文本是“It is raining heavily”。我们将使用以下步骤:

  1. 小写单词,所以输出将会是“it is raining heavily”

  2. 现在我们将它们按字母顺序排列。结果是 - heavily, is, it, raining。

  3. 现在我们可以为每个单词分配位置值,如 heavily:0, is:1, it:2, raining:3。

  4. 最后,我们可以将它们转换为如下所示的二进制向量

[[0\. 0\. 1\. 0.] #it
[0\. 1\. 0\. 0.] #is
[0\. 0\. 0\. 1.] #raining
[1\. 0\. 0\. 0.]] #heavily

尽管这种方法非常直观和简单易懂,但由于语料库和词汇的庞大规模,实际上是不可能的。

语料库指的是一系列文本。它是拉丁文的意思。它可以是一组书面文字或口头文字,用于进行语言分析。

此外,处理如此多维的大数据集将在计算上非常昂贵。因此创建的矩阵也将非常稀疏。因此,我们需要寻找其他方法来表示我们的文本数据。

有更好的替代方案可用于一热编码。这些技术侧重于单词的频率或单词的使用上下文。这种科学方法的文本表示更准确、更健壮、更具解释性。它也产生更好的结果。有多种这样的技术,如 tf-idf、词袋模型等。我们将在接下来的章节中讨论其中一些技术。但我们首先会考察标记化的一个重要概念!

Tokenization(标记化)

Tokenization(标记化)就是简单地将文本或一组文本分解成单个标记。它是自然语言处理的基本构件。看一下图 7-3 中的示例,我们为句子中的每个单词创建了单独的标记。标记化是一个重要的步骤,因为它允许我们为每个单词分配唯一的标识符或标记。一旦我们为每个单词分配了特定的标记,分析就变得不那么复杂了。

图 7-3 标记化可以将句子分解为不同的单词标记。

图 7-3 Tokenization 可以将句子分解为不同的单词标记。

标记通常用于单个单词,但并不总是必要的。我们可以将一个单词或单词的子词或字符进行标记化。在子词的情况下,同一句子可以有子词标记,如 rain-ing。

如果我们希望在字符级别执行标记化,那么可以是 r-a-i-n-i-n-g。实际上,在上一节讨论的一热编码方法中,标记化是在单词上进行的。

标记化是自然语言处理解决方案的基石。

一旦我们获得了标记,那么这些标记就可以用来准备一个词汇表。词汇表是语料库中唯一标记的集合。

有多个用于标记化的库。Regexp 标记化使用给定的模式参数来匹配标记或标记之间的分隔符。Whitespace 标记化通过将任何空白字符序列视为分隔符来使用。然后我们有 blankline,它使用空白行的序列作为分隔符。Wordpunct 标记化通过匹配字母字符序列和非字母非空白字符序列来进行标记化。当我们为文本数据创建 Python 解决方案时,我们将执行标记化。

现在,我们将探讨更多表示文本数据的方法。第一个这样的方法是词袋模型。

词袋模型方法

正如名称所示,语料库中的所有单词都会被使用。在词袋模型方法中,或者说 BOW 方法中,文本数据被标记化为语料库中的每个单词,然后计算每个标记的相应频率。在此过程中,我们忽略语法、单词的顺序或上下文。我们只专注于简单性。因此,我们将每个文本(句子或文档)表示为它自己的单词袋

在整个文档的 BOW 方法中,我们将语料库的词汇表定义为语料库中存在的所有唯一单词。请注意,我们使用语料库中的所有唯一单词。如果我们愿意,我们也可以设置一个阈值,即单词被选中的频率的上限和下限。一旦我们获得了唯一的单词,那么每个句子都可以用与基础词汇向量相同维度的向量来表示。这个向量表示包含了句子中每个单词在词汇表中的频率。这听起来可能很复杂,但实际上这是一种直接的方法。

让我们通过一个例子来理解这种方法。假设我们有两个句子——It is raining heavily 和 We should eat fruits。

要表示这两个句子,我们将计算这些句子中每个单词的频率,如图 7-4 所示。

图 7-4 每个单词的频率已经计算出来了。在这个例子中,我们有两个句子。

现在,如果我们假设只有这两个单词代表整个词汇表,我们可以将第一个句子表示如图 7-5 所示。请注意,该表格包含了所有单词,但是不在句子中的单词的值为 0。

图 7-5 第一个句子对于词汇表中的所有单词进行了表示,我们假设词汇表中只有两个句子。

在这个例子中,我们讨论了如何使用 BOW 方法将句子表示为向量。但 BOW 方法没有考虑单词的顺序或上下文。它只关注单词的频率。因此,它是一种非常快速的方法来表示数据,并且与其同行相比计算成本较低。由于它是基于频率的,所以它通常用于文档分类。

但是,由于其纯粹基于频率的计算和表示,解决方案的准确性可能会受到影响。在语言中,单词的上下文起着重要作用。正如我们之前所看到的,苹果既是一种水果,也是一个著名的品牌和组织。这就是为什么我们有其他考虑比仅仅是频率更多参数的高级方法。下面我们将学习一种这样的方法,即 tf-idf 或词项频率-逆文档频率。

弹出测验——回答这些问题以检查你的理解。答案在本书的末尾。

(1) 用简单的语言解释标记化,就好像你在向一个不懂 NLP 的人解释一样。

(2) Bag of words 方法关注单词的上下文而不仅仅是频率。True or False.

(3) Lemmatization is less rigorous approach then stemming. True or False.

tf-idf(词项频率和逆文档频率)

我们在上一节学习了词袋方法。在词袋方法中,我们只重视单词的频率。这个想法是,频率较高的词可能不像频率较低但更重要的词那样提供有意义的信息。例如,如果我们有一套医学文件,我们想要比较两个词“疾病”和“糖尿病”。由于语料库包含医学文件,单词疾病必然会更频繁,而“糖尿病”这个词会更少,但更重要,以便识别处理糖尿病的文件。tf-idf 方法使我们能够解决这个问题,并提取更重要的词的信息。

在术语频率和逆文档频率(tf-idf)中,我们考虑单词的相对重要性。TF-idf 表示术语频率,idf 表示逆文档频率。我们可以定义 tf-idf 如下:

  1. 术语频率(t是整个文档中术语的计数。例如,文档“D”中单词“a”的计数。

  2. 逆文档频率(id是整个语料库中总文档数(N)与包含单词“a”的文档数(df)的比率的对数。

因此,tf-idf 公式将为我们提供单词在整个语料库中的相对重要性。数学公式是 tf 和 idf 的乘积,表示为

w[i,j] = tf[i,j] * log (N/df[i]) (方程式 7-1)

其中 N:语料库中的文档总数

tf[i,j]是文档中单词的频率

df[i]是包含该词的语料库中的文档数量。

这个概念听起来可能很复杂。让我们通过一个例子来理解。

假设我们有一百万本运动期刊的集合。这些运动期刊包含了许多不同长度的文章。我们还假设所有文章都只有英语。所以,假设在这些文件中,我们想要计算单词“ground”和“backhand”的 tf-idf 值。

假设有一个包含 100 个词的文档,其中“ground”出现了五次,而“backhand”只出现了两次。所以 ground 的 tf 为 5/100 = 0.05,而 backhand 的 tf 为 2/100 = 0.02。

我们了解到,“ground”这个词在体育中是相当常见的词,而“backhand”这个词的使用次数会较少。现在,我们假设“ground”出现在 100,000 个文档中,而“backhand”只出现在 10 个文档中。所以,“ground”的 idf 为 log (1,000,000/100,000) = log (10) = 1。对于“backhand”,它将是 log (1,000,000/10) = log (100,000) = 5。

要得到“ground”的最终值,我们将 tf 和 idf 相乘= 0.05 x 1 = 0.05。

要得到“backhand”的最终值,我们将 tf 和 idf 相乘= 0.02 x 5 = 0.1。

我们可以观察到,“背手”这个词的相对重要性要比单词“地面”的相对重要性更高。这就是 tf-idf 相对于基于频率的 BOW 方法的优势。但是,与 BOW 相比,tf-idf 计算时间更长,因为必须计算所有的 tf 和 idf。尽管如此,与 BOW 方法相比,tf-idf 提供了一个更好、更成熟的解决方案。

接下来我们将在下一节中介绍语言模型。

语言模型

到目前为止,我们已经学习了词袋模型方法和 tf-idf。现在我们将专注于语言模型。

语言模型分配概率给词的序列。N-grams 是语言模型中最简单的模型。我们知道,为了分析文本数据,它们必须被转换为特征向量。N-gram 模型创建了特征向量,使得文本可以以进一步分析的格式来表示。

n-gram 是一个概率语言模型。在 n-gram 模型中,我们计算第 N^(th) 个词在给定(N-1)个词的序列的情况下出现的概率。更具体地说,n-gram 模型将基于词 x[i-(n-1)], x[i-(n-2)]…x[i-1] 预测下一个词 x[i]。若我们希望使用概率术语,可以表示为给定前面的词 x[i-(n-1)], x[i-(n-2)]…x[i-1] 的条件概率 P(x[i] | x[i-(n-1)], x[i-(n-2)]…x[i-1])。该概率通过文本语料库中序列的相对频率计算得出。

若条目是单词,n-grams 可能被称为shingles

让我们用一个例子来学习这个。

假设我们有一个句子 It is raining heavily。我们已经使用不同的 n 值展示了相应的表示方式。您应该注意到,对于不同的 n 值,单词的序列以及它们的组合方式是如何变化的。如果我们希望使用 n=1 或单个词来进行预测,表示将如图 7-6 所示。请注意,此处每个词单独使用。称为unigrams

如果我们希望使用 n=2,那么现在使用的词数量将变为两个。它们被称为bigrams,这个过程将继续下去。

图 7-6 Unigrams, bigrams, trigrams 可以用来表示相同的句子。这个概念也可以扩展到 n-grams。

因此,如果我们有一个 unigram,那么它是一个词的序列;对于两个词的序列,称为 bigram;对于三个词的序列,称为 trigram,依此类推。因此,trigram 模型将使用仅考虑前两个词的条件概率来逼近后一个词的概率,而 bigram 则仅考虑前一个词的条件概率。这确实是一个非常强的假设,即一个词的概率只取决于之前的一个词,被称为马尔可夫假设。一般来说,N > 1 被认为较 unigrams 更加信息丰富。但显然计算时间也会增加。

n-gram 方法对 n 的选择非常敏感。它还在很大程度上取决于所使用的训练语料库,这使得概率非常依赖于训练语料库。因此,如果遇到一个未知单词,模型将很难处理该新单词。

我们现在将创建一个 Python 示例。我们将展示一些使用 Python 进行文本清洗的示例。

使用 Python 进行文本清洗

我们现在将用 Python 清洗文本数据。有一些库可能需要安装。我们将展示一些小的代码片段。你可以根据需要使用它们。我们还附上了代码片段及其结果的相应截图。

代码 1:删除文本中的空格。我们导入库re,它被称为正则表达式。文本是 It is raining outside with a lot of blank spaces in between.

import re
doc = "It is     raining       outside"
new_doc = re.sub("\s+"," ", doc)
print(new_doc)

代码 2:现在我们将从文本数据中删除标点符号。

text_d = "Hey!!! How are you doing? And how is your health! Bye, take care."
re.sub("[^-9A-Za-z ]", "" , text_d)

代码 3:这是另一种去除标点符号的方法。

import string
text_d = "Hey!!! How are you doing? And how is your health! Bye, take care."
cleaned_text = "".join([i for i in text_d if i not in string.punctuation])
cleaned_text

代码 4:我们现在将删除标点符号,并将文本转换为小写。

text_d = "Hey!!! How are you doing? And how is your health! Bye, take care."
cleaned_text = "".join([i.lower() for i in text_d if i not in string.punctuation])
cleaned_text

代码 5:我们现在将使用标准的nltk库。标记化将在这里使用 NLTK 库完成。输出也附在下面。

import nltk
text_d = "Hey!!! How are you doing? And how is your health! Bye, take care."
nltk.tokenize.word_tokenize(text_d)

请注意,在代码的输出中,我们有包括标点符号的所有单词作为不同的标记。如果你希望排除标点符号,可以使用之前共享的代码片段来清理标点符号。

代码 6:接下来是停用词。我们将使用 nltk 库删除停用词。然后,我们将对单词进行标记。

stopwords = nltk.corpus.stopwords.words('english')
text_d = "Hey!!! How are you doing? And how is your health! Bye, take care."
text_new = "".join([i for i in text_d if i not in string.punctuation])
print(text_new)
words = nltk.tokenize.word_tokenize(text_new)
print(words)
words_new = [i for i in words if i not in stopwords]
print(words_new)

代码 7:我们现在将在文本示例上执行词干提取。我们使用 NLTK 库进行词干提取。首先对单词进行标记,然后我们对其应用词干提取。

import nltk
from nltk.stem import PorterStemmer
stem = PorterStemmer()
text = "eats eating studies study"
tokenization = nltk.word_tokenize(text)
for word in tokenization:
    print("Stem for {} is {}".format(word, stem.stem(w)))

代码 8:我们现在将在文本示例上执行词形还原。我们使用 NLTK 库进行词形还原。首先对单词进行标记,然后我们对其应用词形还原。

import nltk
from nltk.stem import WordNetLemmatizer
wordnet_lemmatizer = WordNetLemmatizer()
text = "eats eating studies study"
tokenization = nltk.word_tokenize(text)
for word in tokenization:
    print("Lemma for {} is {}".format(word, wordnet_lemmatizer.lemmatize(w)))

观察并比较词干提取和词形还原的两个输出之间的差异。对于 studies 和 studying,词干提取生成的输出为 studi,而词形还原生成了正确的输出 study。

到目前为止,我们已经介绍了词袋模型、tf-idf 和 N-gram 方法。但在所有这些技术中,忽略了单词之间的关系,而这些关系在词嵌入中被使用 - 我们的下一个主题。

词嵌入

"一个单词的特点在于它周围的公司" - 约翰·鲁珀特·费斯。

到目前为止,我们研究了许多方法,但所有的技术都忽略了单词之间的上下文关系。让我们通过一个例子来学习。

假设我们的词汇表中有 100,000 个单词,从 aa 到 zoom。现在,如果我们执行上一节学习的 one-hot 编码,所有这些单词都可以以向量形式表示。每个单词将有一个唯一的向量。例如,如果单词 king 的位置在 21000,那么向量的形状将如下所示,其中在 21000 位置上有 1,其余位置为 0。

[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0…………………1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

这种方法存在一些明显的问题:

  1. 维度的数量非常高,计算复杂。

  2. 数据在本质上非常稀疏。

  3. 如果要输入 n 个新单词,则词汇量增加 n,因此每个向量的维度增加 n。

  4. 这种方法忽略了单词之间的关系。我们知道,ruler(统治者)、king(国王)、monarch(君主)有时可以互换使用。在 one-hot 编码方法中,任何这种关系都被忽略了。

如果我们希望进行语言翻译或生成聊天机器人,我们需要将这样的知识传递给机器学习解决方案。单词嵌入为这个问题提供了解决方案。它们将高维度的单词特征转换为较低的维度,同时保持上下文关系。单词嵌入允许我们创建更加泛化的模型。我们可以通过示例来理解含义。

如图 7-7 所示的示例中,"man" 到 "woman" 的关系类似于 "king" 到 "queen","eat" 到 "eating" 类似于 "study" 到 "studying" 或 "UK" 到 "London" 类似于 "Japan" 到 "Tokyo"。

图 7-7 单词嵌入可以用来表示单词之间的关系。例如,从 men(男人)到 women(女人)之间存在一种关系,类似于 king(国王)到 queen(女王)之间的关系。

图片

简而言之,使用单词嵌入,我们可以将具有相似含义的单词表示为类似的形式。单词嵌入可以被认为是一类技术,其中我们将每个单词在预定义的向量空间中表示出来。语料库中的每个单词都被映射到一个向量上。根据单词的使用情况来理解分布式表示。因此,可以使用类似的单词具有相似的表示。这使得解决方案能够捕捉单词及其关系的潜在含义。因此,单词的含义起着重要作用。相比于词袋方法,这种表示更加智能,词袋方法中的每个单词都是独立处理的,不考虑它们的使用情况。而且,维度的数量较少,相比于 one-hot 编码,每个单词由 10 或 100 维的向量表示,这比 one-hot 编码方法中使用的超过 1000 维的向量表示要少得多。

我们将在下一节介绍两种最流行的技术 Word2Vec 和 GloVe。这一节提供了对 Word2Vec 和 GloVe 的理解。Word2Vec 和 GloVe 的数学基础超出了本书的范围。我们将理解解决方案的工作机制,然后使用 Word2Vec 和 GloVe 开发 Python 代码。到目前为止,书中还有一些术语我们没有讨论过,所以下一节关于 Word2Vec 和 GloVe 可能会很繁琐。如果你只对解决方案的应用感兴趣,可以跳过下一节。

Word2Vec 和 GloVe

Word2Vec 首次发表于 2013 年。它是由 Google 的 Tomas Mikolov 等人开发的。我们会在章节结束时分享论文的链接。建议你彻底研究这篇论文。

Word2Vec 是一组用于生成词嵌入的模型。输入是一个大型文本语料库。输出是一个向量空间,具有非常多的维度。在这个输出中,语料库中的每个单词被分配了一个唯一且对应的向量。最重要的一点是,在语料库中具有相似或共同上下文的单词,在生成的向量空间中位置相似。

在 Word2Vec 中,研究人员介绍了两种不同的学习模型 - 连续词袋模型和连续跳字模型,我们简要介绍如下:

  1. 连续词袋模型或 CBOW:在 CBOW 中,模型从周围上下文单词的窗口中预测当前单词。因此,CBOW 学习了在词袋方法中,单词的顺序不起作用。同样,在 CBOW 中,单词的顺序是无关紧要的。

  2. 连续跳字模型:它使用当前单词来预测周围窗口的上下文单词。在这样做时,它给邻近单词分配比远离单词更多的权重。

GloVe 或全局词向量是用于生成词向量表示的无监督学习算法。它由斯坦福的 Pennington 等人于 2014 年开发,并在 2014 年推出。它是两种技术的组合 - 矩阵分解技术和 Word2Vec 中使用的基于本地上下文的学习。GloVe 可用于找到像邮政编码和城市、同义词等关系。它为具有相同形态结构的单词生成了一组单一的向量。

这两个模型(Word2Vec 和 GloVe)都从共现信息中学习和理解它们的单词的向量表示。共现意味着单词在大型语料库中一起出现的频率。主要区别在于 Word2Vec 是基于预测的模型,而 GloVe 是基于频率的模型。Word2Vec 预测给定单词的上下文,而 GloVe 通过创建一个共现矩阵来学习单词在给定上下文中出现的频率。

弹出测验 - 回答这些问题以检查您的理解。书的末尾有答案

(1)BOW 比 tf-idf 方法更严格。真或假。

(2)Word2Vec 和 GloVe 之间的区别。

我们现在将在下一节中转到案例研究和 Python 实现。

用 Python 实现的情感分析案例研究

到目前为止,我们已经讨论了许多关于 NLP 和文本数据的概念。在本节中,我们首先将探讨一个业务案例,然后在同一案例上开发 Python 解决方案。我们正在进行情感分析。

产品评论是信息的丰富来源 - 对客户和组织都是如此。每当我们希望购买任何新产品或服务时,我们倾向于查看其他客户的评论。您可能自己也曾评论过产品和服务。这些评论可以在亚马逊、博客、调查等处找到。

让我们考虑一个案例。一个供水公用事业提供商收到了客户的投诉,对供水的评论以及对整体体验的评论。可能的流是 - 产品质量、定价、注册体验、注册流程的简易性、付款流程、供水评论、电力评论等等。我们想要确定评论的一般背景 - 它是积极的、消极的还是中立的。评论有分配的星级数量、实际文本评论、关于产品/服务的优点和缺点、属性等。但与此同时,也存在一些业务问题,如:

  1. 许多时候,观察到产品/服务收到的星级数量非常高,而实际评论却相当负面。

  2. 组织和产品所有者需要知道客户喜欢哪些功能,哪些功能不受客户喜欢。然后团队可以着手改进客户不喜欢的功能。

  3. 还需要评估并密切关注竞争!组织需要知道竞争对手的热门产品的属性。

  4. 产品所有者可以更好地计划他们希望在未来发布的功能。

因此,业务团队将能够回答这两个最重要的问题:

  1. 我们客户对产品和服务的满意度水平是多少?

  2. 客户的主要痛点和不满,驱动客户参与的因素,哪些服务是复杂且耗时的,哪些服务/产品最受欢迎?

这个商业用例将带来以下业务利益:

  1. 最令人满意且最受欢迎的产品和服务应该继续。

  2. 那些不受欢迎且得到负面评分的功能必须得到改进,挑战也必须得到缓解。

  3. 各自的团队,如财务、运营、投诉、CRM 等,可以被通知,并且他们可以分别工作以改善客户体验。

  4. 喜欢或不喜欢服务的精确原因将对相应的团队有助于朝正确的方向努力。

  5. 总的来说,它将为测量客户基础的净推荐得分(NPS)提供一个基准。企业可以努力提升整体客户体验。

  6. 我们可能希望通过仪表板来表示这些发现。这个仪表板将定期刷新,比如每月或每季度刷新一次。

要解决这个业务问题,团队可以从网站、调查、亚马逊、博客等收集相关数据。然后对该数据集进行分析。分析结构化数据相对容易。在这个例子中,我们将处理文本数据。

Python Jupyter 笔记本在 GitHub 地址上进行了检入。建议您使用 GitHub 地址上的 Jupyter 笔记本,因为它包含更多步骤。

第 1 步:我们在这里导入所有库。

#### Loading all the required libraries here
from lxml import html  
import requests
import pandas as pd
from nltk.corpus import stopwords
from textblob import TextBlob
import matplotlib.pyplot as plt
import sys
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import sklearn
import scikitplot as skplt
import nltk
#to ignore warnings
import warnings
warnings.filterwarnings("ignore")
nltk.download('stopwords')
nltk.download('punkt')
nltk.download('wordnet')

第 2 步:我们在这里定义标签。这些标签用于从评论中获取产品的属性。

xpath_reviews = '//div[@data-hook="review"]'
reviews = parser.xpath(xpath_reviews)
xpath_rating  = './/i[@data-hook="review-star-rating"]//text()' 
xpath_title   = './/a[@data-hook="review-title"]//text()'
xpath_author  = './/a[@data-hook="review-author"]//text()'
xpath_date    = './/span[@data-hook="review-date"]//text()'
xpath_body    = './/span[@data-hook="review-body"]//text()'
xpath_helpful = './/span[@data-hook="helpful-vote-statement"]//text()'

第 3 步:我们现在正在准备好提取数据。我们正在创建一个数据框来存储客户评论。然后我们迭代所有评论,然后提取信息。

# Create a dataframe here. 

reviews_df = pd.DataFrame()
for review in reviews:
    rating  = review.xpath(xpath_rating)
    title   = review.xpath(xpath_title)
    author  = review.xpath(xpath_author)
    date    = review.xpath(xpath_date)
    body    = review.xpath(xpath_body)
    helpful = review.xpath(xpath_helpful)

    review_dict = {'rating': rating,
                   'title': title,
                   'author': author,             
                   'date': date,
                   'body': body,
                   'helpful': helpful}
    reviews_df = reviews_df.append(review_dict, ignore_index=True)
all_reviews = pd.DataFrame()

第 4 步:让我们迭代通过评论然后填写详细信息。

# Fill the values of the reviews here. . 

for i in range(1,90):
    amazon_url = 'https://www.amazon.co.uk/Hive-Heating-Thermostat-Professional-Installation/product-reviews/B011B3J6KY/ref=cm_cr_othr_d_show_all?ie=UTF8&reviewerType=all_reviews&pageNumber='+str(i)
    headers = {'User-Agent': user_agent}
    page = requests.get(amazon_url, headers = headers)
    parser = html.fromstring(page.content)
    xpath_reviews = '//div[@data-hook="review"]'
    reviews = parser.xpath(xpath_reviews)
    reviews_df = pd.DataFrame()
    xpath_rating  = './/i[@data-hook="review-star-rating"]//text()' 
    xpath_title   = './/a[@data-hook="review-title"]//text()'
    xpath_author  = './/a[@data-hook="review-author"]//text()'
    xpath_date    = './/span[@data-hook="review-date"]//text()'
    xpath_body    = './/span[@data-hook="review-body"]//text()'
    xpath_helpful = './/span[@data-hook="helpful-vote-statement"]//text()'
    #print(i)
    for review in reviews:
        rating  = review.xpath(xpath_rating)
        title   = review.xpath(xpath_title)
        author  = review.xpath(xpath_author)
        date    = review.xpath(xpath_date)
        body    = review.xpath(xpath_body)
        helpful = review.xpath(xpath_helpful)

        review_dict = {'rating': rating,
                       'title': title,
                       'author': author,             
                       'date': date,
                       'body': body,
                       'helpful': helpful}
        reviews_df = reviews_df.append(review_dict, ignore_index=True)
    #print(reviews_df)
    all_reviews = all_reviews.append(reviews_df)

第 5 步:让我们看看我们生成的输出。

all_reviews.head()

第 6 步:现在我们将输出保存到一个路径。您可以提供自己的路径。

out_folder = '/Users/vaibhavverdhan/Book/UnsupervisedLearningBookFinal/'
all_reviews.to_csv(out_folder + 'Reviews.csv')

第 7 步:加载数据并分析。

#Load the data now and analyse it
data_path = '/Users/vaibhavverdhan/Book/UnsupervisedLearningBookFinal/'
reviewDataCSV = 'Reviews.csv'
reviewData = (pd.read_csv(data_path+reviewDataCSV,index_col=0,))

第 8 步:我们现在将查看数据集的基本信息。

reviewData.shape
reviewData.rating.unique()
reviewData.rating.value_counts()

第 9 步:我们现在将查看评论中给出的星级分布。这将帮助我们理解客户给出的评论。

labels = '5 Stars', '1 Star', '4 Stars', '3 Stars', '2 Stars'
sizes = [reviewData.rating.value_counts()[0], reviewData.rating.value_counts()[1],reviewData.rating.value_counts()[2],reviewData.rating.value_counts()[3],reviewData.rating.value_counts()[4]]
colors = ['green', 'yellowgreen', 'coral', 'lightblue', 'grey']
explode = (0, 0, 0, 0, 0)  # explode 1st slice

# Plot
plt.pie(sizes, explode=explode, labels=labels, colors=colors,
        autopct='%1.1f%%', shadow=True, startangle=140)

plt.axis('equal')
plt.show()

第 10 步:将文本转换为小写,并删除停用词和频率最高的单词。

reviewData.body = reviewData.body.str.lower()
reviewData.body = reviewData.body.str.replace('[^\w\s]','')
stop = stopwords.words('english')
reviewData.body = reviewData.body.apply(lambda x: " ".join(x for x in x.split() if x not in stop))
freq = list(freq.index)
reviewData.body = reviewData.body.apply(lambda x: " ".join(x for x in x.split() if x not in freq))
freq = pd.Series(' '.join(reviewData.body).split()).value_counts()[-10:]
freq = list(freq.index)
reviewData.body = reviewData.body.apply(lambda x: " ".join(x for x in x.split() if x not in freq))

第 11 步:现在对数据进行分词。

from nltk.tokenize import word_tokenize
tokens = word_tokenize(reviewData.iloc[1,1])
print(tokens)

第 12 步:我们现在正在执行词形还原。

from textblob import Word
reviewData.body = reviewData.body.apply(lambda x: " ".join([Word(word).lemmatize() for word in x.split()]))
reviewData.body.head()

第 13 步:现在我们正在将所有评论附加到字符串上。

sentimentString = reviewData.iloc[1,1]
# append to this string 
for i in range(2,len(reviewData)):
    sentimentString = sentimentString + reviewData.iloc[i,1]

第 14 步:情感分析在这里完成。我们从 TextBlob 中获取情感方法。它为情感生成极性和主观性。

# the functions generates polarity and subjectivity here, subsetting the polarity only here
allReviewsSentiment = reviewData.body[:900].apply(lambda x: TextBlob(x).sentiment[0])
# this contains boths subjectivity and polarity
allReviewsSentimentComplete = reviewData.body[:900].apply(lambda x: TextBlob(x).sentiment)
allReviewsSentimentComplete.head()

第 15 步:将情感保存到 csv 文件中。

allReviewsSentiment.to_csv(out_folder + 'ReviewsSentiment.csv')

第 15 步:我们现在将给情感分配一个含义或标签。我们正在将每个分数分类为非常满意到非常不满意。

allReviewsSentimentDF = allReviewsSentiment.to_frame()
# Create a list to store the data
grades = []

# For each row in the column,
for row in allReviewsSentimentDF['body']:
    # if more than a value,
    if row >= 0.75:
       grades.append('Extremely Satisfied')
    elif (row >= 0.5) & (row < 0.75):
        grades.append('Satisfied')
    elif (row >= 0.2) & (row < 0.5):
        grades.append('Nice')
    elif (row >= -0.2) & (row < 0.2):
        grades.append('Neutral')
    elif (row > -0.5) & (row <= -0.2):
        grades.append('Bad')
    elif (row >= -0.75) & (row < -0.5):
        grades.append('Dis-satisfied')
    elif  row < -0.75:
        grades.append('Extremely Dis-satisfied')
    else:
        # Append a failing grade
        grades.append('No Sentiment')

# Create a column from the list
allReviewsSentimentDF['SentimentScore'] = grades
allReviewsSentimentDF.head()

第 16 步:我们现在将查看情感得分并绘制它们。最后,我们将它们与主数据集合并。

allReviewsSentimentDF.SentimentScore.value_counts()
allReviewsSentimentDF['SentimentScore'].value_counts().plot(kind='bar')
#### Merge the review data with Sentiment generated

# add column Polarity Score
reviewData['polarityScore'] = allReviewsSentimentDF['body']

在这个案例研究中,您不仅从网站上抓取了评论,还分析了数据集。如果我们比较情感,我们可以看到给产品的星级并不代表真实情况。

在 () 中,我们正在比较实际的星级和情感分析的输出。我们可以观察到,73% 的人给出了 5 星,7% 的人给出了 4 星,而在情感分析中,大多数评论被分类为中性。这就是情感分析的真正力量!

图 7-8 比较左侧的原始星级分布,观察情感分析的实际结果

情感分析是一个非常重要的用例。它对企业和产品团队非常有用。

我们现在将转到使用 Python 进行文档分类的第二个案例研究。

使用 Python 进行文本聚类

想象一下。你有一堆的文本数据集或文档。但是它们都混在一起了。我们不知道文本属于哪个类别。在这种情况下,我们假设我们手头有两类文本数据集 - 一个与足球有关的数据,第二类是旅行。我们将开发一个可以分离这两个类别的模型。

第一步:导入所有库

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
import numpy as np
import pandas as pd

第二步:我们现在正在创建一个虚拟数据集。这段文本数据是我们自己写的几句话。有两个类别 -

text = ["It is a good place to travel",
            "Football is a nice game", "Lets go for holidays and travel to Egypt",
            "It is a goal, a great game.", "Enjoy your journey and fortget the rest", "The teams are ready for the same" ]

第三步:我们将使用 tfidf 对数据进行向量化处理。

tfidf_vectorizer = TfidfVectorizer(stop_words='english')
X = tfidf_vectorizer.fit_transform(text)

第四步:现在让我们进行聚类。

k = 2
model = KMeans(n_clusters=k, init='k-means++', max_iter=10, n_init=2)
model.fit(X)

第五步:让我们表示质心并打印输出。

centroids = model.cluster_centers_.argsort()[:, ::-1]
features = vectorizer.get_feature_names()

for i in range(k):
    print("Cluster %d:" % i),
    for ind in centroids[i, :10]:
        print("%s" % terms[ind])

您还可以将此示例扩展到其他数据集。使用自己的数据集并复制上面示例中的代码。

不再有使用 Word2Vec 和 GloVe 的 Python Jupyter 笔记本了。我们已经将代码检入到了本书的 GitHub 位置。建议您使用它。这是一个非常重要的表示文本数据的资源。

至此,我们来到了这个令人兴奋的章节的结尾。现在让我们进入总结部分。

7.6 小结

文本数据是最有用的数据集之一。很多智慧隐藏在文本中。日志、博客、评论、推文、投诉、评论、文章等等 - 文本数据的来源很多。机构已经开始投资建立访问文本数据和存储文本数据的基础设施。分析文本数据需要更好的处理能力和更好的计算机。它需要特殊的技能和更深入的理解概念。NLP 是一个不断发展的领域,许多研究正在进行中。与此同时,我们不能忽视商业敏锐度和知识的重要性。

数据分析和机器学习并不容易。我们必须理解很多关于数据清洗、数据探索、数据表示和建模的概念。但是,分析非结构化数据可能比结构化数据集更加复杂。在上一章中,我们处理了图像数据集。在当前章节中,我们处理了文本数据。

文本数据是最难分析的数据之一。文本数据有很多排列组合。清洗文本数据并不容易,是一项相当复杂的任务。在本章中,我们讨论了几种清洗文本数据的重要技术。我们还介绍了几种将文本数据表示为向量形式的重要方法。建议您对这些方法进行实践,并通过应用每种技术进行比较性能。

随着这一章的结束,我们也结束了书的第二部分。在书的下一部分,复杂性会增加。我们将研究更深层次的无监督学习算法概念。

你现在可以转到练习问题。

实用的下一步和建议阅读

  1. 从下面的链接获取数据集。在这里你会找到很多文本数据集。建议你实现聚类和降维解决方案。

    blog.cambridgespark.com/50-free-machine-learning-datasets-natural-language-processing-d88fb9c5c8da

  2. 这是文本数据集的第二个来源,你会找到许多有用的数据集。

    www.kaggle.com/datasets?search=text

  3. 你被建议阅读研究论文《在向量空间中高效估计词表示》(Efficient Estimation of Word Representations in Vector Space),作者是 Tomas Mikolov、Kai Chen、Greg Corrado、Jeffrey Dean。

    arxiv.org/pdf/1301.3781.pdf

  4. 你被建议阅读研究论文《GloVe:用于词表示的全局向量》(GloVe: Global Vectors for Word Representation),作者是 Jeffrey Pennington、Richard Socher、Christopher D. Manning。

    nlp.stanford.edu/pubs/glove.pdf

  5. 有几篇论文被广泛引用。

  6. Avrim Blum 和 Tom Mitchell:使用共训练结合标记和未标记数据,1998 年

  7. Kevin Knight:贝叶斯推断与眼泪,2009 年。

  8. Thomas Hofmann:概率隐语义索引,SIGIR 1999。

  9. Donald Hindle 和 Mats Rooth。结构歧义和词汇关系,计算语言学,1993 年。

  10. Collins 和 Singer:命名实体分类的无监督模型,EMNLP 1999。

  11. 你被建议阅读研究论文《使用 TF-IDF 确定文档查询中单词相关性》(Using TF-IDF to Determine Word Relevance in Document Queries),作者是 Juan Ramos。

    citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.121.1424&rep=rep1&type=pdf

  12. 你被建议阅读研究论文《使用 TF-IDF 和下一个词否定改进的文本情感分类模型》(An Improved Text Sentiment Classification Model Using TF-IDF and Next Word Negation),作者是 Bijoyan Das 和 Sarit Chakraborty。

    arxiv.org/pdf/1806.06407.pdf

第八章:深度学习:基本概念

本章介绍

  • 深度学习

  • 深度学习的构建模块

  • 神经网络中的层

  • 激活函数

  • 使用深度学习的监督学习

  • 无监督学习使用深度学习

  • 使用 tensorflow 和 keras 的 Python 代码

  • 深度学习库

“生活真的很简单,但我们一直坚持要把它弄复杂 - 孔子”

欢迎来到本书的第三部分。到目前为止,你已经学习了很多概念、案例研究和 Python 代码。从本章开始,复杂性水平将更高。

在本书的前两部分,我们涵盖了各种无监督学习算法,如聚类,降维等等。我们讨论了简单和高级算法。我们还在本书的最后一部分讨论了处理文本数据的方法。从本书的第三部分开始,我们将开始深度学习的旅程。

深度学习和神经网络改变了世界和商业领域。你一定听说过深度学习和神经网络。它们的实施和复杂性导致更好的癌症检测,自动驾驶汽车,改进的灾害管理系统,更好的污染控制系统,减少交易欺诈等等。

在本书的第三部分,我们将探讨使用深度学习的无监督学习。我们将学习什么是深度学习和神经网络的基础知识。我们将研究神经网络中的层,激活函数,深度学习的过程和各种库。然后我们将转向自动编码器、生成对抗网络和深度信念网络。这些话题确实很复杂,有时候相当数学密集。我们将使用不同类型的数据集来解决问题,但主要是非结构化的数据集。和往常一样,Python 将用于生成解决方案。我们还分享了许多外部资源来补充这些概念。请注意,这些都是非常先进的主题,对于这些主题仍在进行大量的研究。

我们将第三部分分为三章。第八章介绍了所需的深度学习和神经网络的基础概念。接下来的两章将专注于自动编码器,GAN 和深度信念网络。本书的最后一章讨论了这些模型的部署。

本章讨论了神经网络和深度学习的概念。我们将讨论什么是神经网络,什么是激活函数,什么是不同的优化函数,神经网络训练过程等。在你理解自动编码器和生成对抗网络的更深层次概念之前,了解这些深度学习概念对你来说至关重要。本章涵盖的概念是神经网络和深度学习的基础,也是下两章进一步学习的基础。因此,你清楚这些概念非常重要。在本章末尾还有更详细的外部资源可供获取这些概念。

欢迎来到第八章,祝一切顺利!

8.1 技术工具包

我们将继续使用迄今为止使用的相同版本的 Python 和 Jupyter 笔记本。本章中使用的代码和数据集已经保存到了此位置。

您需要在本章中安装一些 Python 库,它们是 - tensorflow 和 keras。

让我们开始学习第八章吧!

8.2 深度学习:是什么?它是做什么的?

近几年来,深度学习已经积累了很多动力。神经网络正在推动机器学习解决方案的边界。深度学习只是机器学习。深度学习是基于神经网络的。它利用了相似的概念,即利用历史数据,理解属性和收集的智能可以用于找到模式或预测未来,尽管深度学习比我们迄今为止涵盖的算法更复杂。

回想一下第一章,我们在那里介绍了结构化和非结构化数据集的概念。非结构化数据集包括文本、图像、音频、视频等。在图 8-1 中,我们描述了文本、图像、音频、视频数据集的主要来源。

图 8-1 非结构化数据集如文本、音频、图像、视频可以使用深度学习进行分析。这样的数据集有多个来源。

手机截图 描述自动生成的

虽然深度学习也可以用于结构化数据集,但它在非结构化数据集上的表现却是非常出色的。其主要原因之一是,经典的机器学习算法在像图像、文本、音频和视频这样的非结构化数据集上有时并不那么有效。我们列举了深度学习在各个领域取得的一些突破性解决方案。

  1. 医学和制药: 深度学习在骨骼和关节问题的识别,或者在确定动脉或静脉中是否有血栓等领域发挥作用。在制药领域,它可以加快临床试验的进程,并帮助更快地找到目标药物。

  2. 银行和金融业: 基于深度学习的算法可用于检测交易中潜在的欺诈行为。利用基于图像识别的算法,我们还可以区分支票上的伪造签名。

  3. 汽车行业: 你一定听说过自动驾驶,即自动驾驶汽车。使用深度学习,算法能够检测道路上的交通信号、行人、其他车辆、它们之间的距离等。

  4. 使用深度学习技术可以实现自动语音识别。通过复杂的神经网络,人类能够创建语音识别算法。这些解决方案被应用于 Siri、Alexa、Translator、百度等产品中。

  5. 零售业: 在零售行业中,使用基于深度学习的算法,人类能够改善客户定位,并开发先进和定制的营销策略。使用深度学习改进了提供下一优产品推荐的模型。我们能够获得更好的投资回报率(ROI),并改善交叉销售和上销售策略。

  6. 图像识别: 神经网络正在改进我们的图像识别技术:可以使用卷积神经网络来实现,这种技术正在改进计算机视觉。使用案例有很多,比如:

  7. 深度学习对于区分癌细胞和良性细胞非常有效。可以通过使用癌细胞和良性细胞的图像来实现。

  8. 使用神经网络已经开发了自动车牌识别系统。

  9. 使用深度学习可以开发目标检测方法。

  10. 使用深度学习可以开发运动感知和跟踪系统。

  11. 在灾难管理系统中,深度学习可以检测受影响区域的人员存在。想象一下,使用更好的检测技术可以拯救多么宝贵的时间,最终拯救人的生命。

所列举的使用案例并不详尽。通过深度学习,我们能够改进用于测量客户情感、语言翻译、文本分类、命名实体识别等的自然语言处理(NLP)解决方案。在生物信息学、军事、移动广告、电信、科技、供应链等领域的使用案例中,深度学习正在为未来铺平道路。

我们已经介绍了深度学习的强大之处。现在我们将开始讲述神经网络的构建模块。

8.3 神经网络的构建模块

人工神经网络(ANNs)据说受到了人脑工作方式的启发。人脑是我们目前能够接触到的最好的机器。当我们看到一张图片、一张脸或听到一首曲子时,我们会为其贴上一个标签或名称。这使我们能够训练我们的大脑和感官,以便在再次看到/听到时识别出一张图片、一张脸或一首曲子。

人工神经网络通过学习或接受训练来执行类似的任务。

 小测验 - 回答这些问题来检查你的理解。本书末尾附有答案。

1.  深度学习的含义是什么?

2.  神经网络不能用于无监督学习。真或假?

3.  探索深度学习在非传统业务领域的更多应用案例。

接下来,我们将在下一节中探讨神经网络如何帮助解决商业问题。

8.3.1 解决方案的神经网络

在深度学习中,监督和非监督学习的概念也适用。我们将介绍网络的两种训练类型:有监督和无监督。这为您提供了完整的图片。同时,为了充分欣赏无监督的深度学习,建议首先了解有监督深度学习的过程。

举个例子,让我们了解深度学习的过程。例如,我们希望创建一个可以识别人脸的解决方案,也就是说,一个可以区分面部并通过给面部分配一个名称来识别人物的解决方案。为了训练模型,我们将准备一个包含人脸图像和相应名称的数据集。在训练过程中,人工神经网络(ANN)不具有先前对图像数据集或属性的了解。在训练过程中,ANN 会从训练数据中学习属性和识别特征,并利用这些学习到的属性来区分面部。此时,我们只涵盖了高层次的过程,后续章节将更详细地介绍这个过程。

您可以在图 8-2 中看到神经网络的表示形式。

图 8-2 是一个典型的神经网络,有神经元和各种层。

神经网络中的过程相当复杂。我们将首先介绍神经网络中所有构建块的内容,例如神经元、激活函数、权重、偏置项等,并然后讨论神经网络中的过程。我们将从主角-神经元开始。

8.3.2 人工神经元和感知器

人类大脑包含数十亿个神经元。神经元是我们大脑中相互连接的细胞。这些神经元接收信号,处理它们并产生结果。人工神经元基于生物神经元,并可以被认为是生物神经元的简化计算模型。

1943 年,研究人员 Warren McCullock 和 Walter Pitts 提出了一个简化的脑细胞概念,称为 McCullock-Pitts(MCP)神经元。它可以被认为是一个具有二进制输出的简单逻辑门。

人工神经元的工作方法类似于生物神经元,尽管它们比生物神经元简单得多。感知器是生物神经元的数学模型。在实际的生物神经元中,树突从其他神经元的轴突中接收电信号。在感知器中,这些电信号被表示为数字值。

人工神经元接收来自前一神经元的输入,或者可以接收输入数据。然后处理输入信息并输出结果。输入可以是原始数据或来自前一神经元的处理信息。神经元将输入与其自身的内部状态结合起来,分别加权并通过非线性函数传递接收到的输出以生成输出。这些非线性函数也被称为激活函数(我们稍后会讨论它们)。您可以将激活函数视为数学函数。可以将神经元表示如图 8-3 所示。

图 8-3 神经元获得输入,使用数学函数处理输入,然后生成输出

简单来说,神经元可以称为一个计算其输入数据集的加权平均值的数学函数,然后将这个总和通过激活函数进行处理。然后神经元的输出可以成为下一个神经元的输入,该神经元再次处理接收到的输入。让我们再深入一点。

在感知器中,每个输入值都乘以一个称为权重的因子。生物神经元在总强度的输入信号超过一定阈值时激活。在感知器中也遵循类似的格式。在感知器中,计算输入数据的加权总和,然后对每个输出应用激活函数。然后每个输出可以馈送到感知器的下一层。

让我们假设感知器 X 有两个输入值“a”和“b”,为了简单起见,它只有一个输出。让 a 和 b 分别的权重为 P 和 Q。所以,加权总和可以表示为:Px + Qb。只有当加权总和超过一定阈值时,感知器才会激活或产生非零输出。让我们称阈值为“C”。因此,我们可以说:

如果 Px + Qy <= C,则 X 的输出将为 0

如果 Px + Qy > C,则 X 的输出将为 1

如果我们概括这一理解,我们可以表示如下。如果我们将感知器表示为一个函数,该函数将输入"x"映射为如下函数

f(x) = 1 如果 w*x + b > 0

否则为 0

其中:

x 是输入值的向量

w 表示权重向量

b 是偏置项

我们将在此处解释偏差和权重项。

回想一下线性方程:y = mx+ c 其中 m 是直线的斜率,c 是常数项。 偏置和权重都可以使用相同的线性方程定义。

权重:权重的作用类似于线性方程中直线的斜率。它定义了 f(x)的值在 x 的值单位变化时的变化。

偏置:偏置的作用类似于线性函数中常数的作用。如果没有偏差,激活函数的输入就是 x 乘以权重。

权重和偏置项是网络中进行训练的参数。

函数的输出将取决于所使用的激活函数。在我们讨论完网络中不同层之后,我们将在下一节介绍各种类型的激活函数。

8.3.3 网络中的不同层

一个简单而有效的组织神经元的方式如下。与其允许任意神经元连接到任意其他神经元,不如将神经元组织成层次结构。一层中的神经元的所有输入仅来自上一层,并且所有输出仅传递到下一层。没有其他连接,例如同一层中的神经元之间的连接,或者属于远程层的神经元之间的连接(对于一个相当特殊的情况有一个小小的例外,但超出了本书的范围)。

我们知道信息通过神经网络流动。这些信息在网络中的一层又一层地被处理和传递。如图 8-4 所示,神经网络有三层。典型的神经网络如下图所示:

图 8-4 一个具有神经元和输入、隐藏和输出层的典型神经网络

图 8-4 所示的神经网络有 3 个输入单元,2 个每个有 4 个神经元的隐藏层和一个最终的输出层。

输入层:顾名思义,它接收输入数据并与隐藏层共享。

隐藏层:它是网络的核心和灵魂。隐藏层的数量取决于手头的问题,层数可以从几层到数百层不等。在这些层中进行所有的处理、特征提取和属性学习。在隐藏层中,所有的输入原始数据都被分解成属性和特征。这种学习对以后的决策有用。

输出层:决策层和网络中的最后一块拼图。它接受来自前面隐藏层的输出,然后做出预测。

例如,输入的训练数据可能是原始图像或处理过的图像。这些图像将被馈送到输入层。数据现在传输到隐藏层,其中所有的计算都是由每一层的神经元完成的。输出是需要完成的任务,例如识别一个对象或者如果我们想要对图像进行分类等。

ANN 由各种连接组成。每个连接的目标是接收输入并将输出提供给下一个神经元。这个输出将作为下一个神经元的输入。另外,正如之前讨论的,每个连接都被赋予一个权重,这代表了其相应的重要性。值得注意的是,一个神经元可以有多个输入和输出连接,这意味着它可以接收多个输入并提供多个输出。

 小测验 - 回答这些问题来检查你的理解。答案在书的末尾。

1.  输入数据被馈送到神经网络的隐藏层中。是或否?

  1. 偏置项类似于线性方程的斜率。对或错。

  2. 查找并探索有史以来训练最深的神经网络。

那么,一个层的作用是什么呢? 一个层接收输入,处理它们,并将输出传递给下一个层。 从技术上讲,层实现的转换必须由它的权重参数化,这也被称为层的参数。 为了简化,为了使神经网络能够“训练”到一个特定的任务,必须更改网络中的某些内容。 结果证明,改变网络的架构(即神经元如何彼此连接)只有很小的影响。 另一方面,正如我们将在本章后面看到的,改变权重是“学习”过程的关键。

我们现在将转移到非常重要的激活函数主题。

8.3.4 激活函数

回想一下,在最后几节中,我们介绍了激活函数。 激活函数的主要作用是决定神经元/感知器是否应该发射。 它们在后期的网络训练中起着核心作用。 有时它们被称为传输函数。 还重要的是要知道为什么我们需要非线性激活函数。 如果我们仅使用线性激活函数,输出也将是线性的。 同时,线性函数的导数将是常数。 因此,学习的可能性将不大。 因此,出于这样的原因,我们更喜欢具有非线性激活函数。

我们现在将研究最常见的激活函数。

Sigmoid 函数

Sigmoid 是一个有界的单调数学函数。 Sigmoid 是一个数学函数,当输入值增加时,其输出值总是增加的。 其输出值始终在 -1 和 1 之间。

它是一个可微函数,具有 S 形曲线,其一阶导函数呈钟形。 它具有非负的导函数,并且对所有实数输入值定义。 如果神经元的输出值在 0 和 1 之间,则使用 Sigmoid 函数。

从数学上讲,sigmoid 函数是:

(方程式 8.1)

S(x) = 1 = e^x

1 + e^(-x) e^x +1

sigmoid 函数的图形可以在图 8-5 中显示。

图 8-5 显示了一个 sigmoid 函数。请注意函数的形状和最小/最大值。

page9image55589296

Sigmoid 函数在复杂学习系统中找到其应用。 通常用于二进制分类和网络的最终输出层。

Tanh 函数

在数学中,双曲正切函数或 tanh 是一个可微的双曲函数。它是一个平滑函数,其输入值在 -1 到 +1 的范围内。

Tanh 函数被写成方程式 8.2。

(方程式 8.2)

Tanh(x) = ex – e-x

ex + e-x

双曲线的图形表示如图 8-6 所示。它是 Sigmoid 函数的缩放版本,因此可以从 Sigmoid 函数导出 tanh 函数,反之亦然。

图 8-6 这里显示了一个 tanh 函数,它是 sigmoid 函数的缩放版本

page10image55837712

tanh 函数通常用于隐藏层。它使均值接近于零,从而使网络中的下一层训练更容易。这也被称为数据居中

Rectified Linear Unit 或 ReLU

Rectified Linear Unit 或 ReLU 是一个定义参数的正部分的激活函数。我们在下面展示了 ReLU 函数。注意,即使对于负值,值也为 0,并且从 0 开始倾斜。

(方程 8.3)

F(x) = max (0, x)

即,如果是正数,则输出为 x,否则为 0

图 8-7 显示了 ReLU 函数

图 8-7 这里显示了 ReLU 函数,它是神经网络隐藏层中受欢迎的激活函数之一。ReLU 易于使用且训练成本较低。

page11image55974400

它是一个简单的函数,因此计算成本较低,速度更快。它是无界的,并且不以零为中心。除零点外,它在所有位置上都可微。由于 ReLU 函数较不复杂,计算成本较低,因此在隐藏层中被广泛用于更快地训练网络。

Softmax 函数

Softmax 函数用于神经网络的最后一层,以从网络中生成输出。它是激活函数,输出可以是图像的最终分类,用于不同的类别。它是一个对于多类分类问题有用的激活函数,并强制神经网络输出总和为 1。

例如,如果图像的不同类别是汽车、摩托车或卡车,则 Softmax 函数将为每个类别生成三个概率。获得最高概率的类别将是预测的类别。

还有其他激活函数,如 ELU、PeLU 等,超出了本书的范围。我们在本章末尾提供了各种激活函数的摘要。

我们将在下一节中介绍超参数。它们是我们在训练网络时拥有的控制杆。

8.3.5 超参数

在训练网络时,算法不断学习原始输入数据的属性。同时,网络无法自行学习所有内容,有些参数需要提供初始设置。这些变量决定了神经网络的结构以及训练网络时有用的相应变量。

我们在开始网络训练之前提供超参数。超参数的一些例子包括网络中隐藏层数的数量、每个层中神经元的数量、层中使用的激活函数、权重初始化等。我们必须选择超参数的最佳值。为此,我们选择一些合理的超参数值,训练网络,然后测量网络的性能,然后调整超参数,然后重新训练网络,重新评估和重新调整,这个过程继续进行。

超参数是由我们控制的,因为我们输入超参数来提高性能。

梯度下降和随机梯度下降

在任何基于预测的解决方案中,我们希望尽可能好地预测,换句话说,我们希望尽可能减少错误。误差是实际值和预测值之间的差异。机器学习解决方案的目的是找到我们函数的最优值。我们想减少错误或最大化准确性。梯度下降可以帮助实现这一目的。

梯度下降技术是一种优化技术,用于找到函数的全局最小值。我们迭代地沿着最陡的下降方向前进,这个方向是由负梯度定义的。

但梯度下降在运行非常大的数据集或具有非常高维数的数据集时可能运行速度较慢。这是由于梯度下降算法的一次迭代会针对训练数据集中的每个实例进行预测。因此,很显然,如果我们有成千上万条记录,它将花费很长时间。对于这种情况,我们有随机梯度下降。

在随机梯度下降中,系数不是在数据批次结束时更新,而是对每个训练实例进行更新,因此需要较少的时间。

下面的图显示了梯度下降的工作方式。注意我们如何朝向全局最小值进展。

图 8-8 梯度下降的概念。这是最小化损失函数的机制

page14image55583056

学习和学习率

对于网络,我们采取各种步骤来提高解决方案的性能,学习率就是其中之一。学习率将定义模型减少错误所采取的纠正步骤的大小。学习率定义了我们应该如何调整网络权重的值,以使其相对于损失梯度(这个过程的更多信息将在下一节提供)调整的数量。如果我们采用较高的学习率,准确性会降低。如果我们采用非常低的学习率,训练时间将大大增加。

 小测验-回答这些问题以检验您的理解。答案在本书结尾处。

1.  比较并对比 Sigmoid 和 tanh 函数。

2.  ReLU 通常用于网络的输出层。是或否?

  1. 梯度下降是一种优化技术。真或假。

我们现在已经了解了深度学习的主要概念。现在让我们研究一下神经网络是如何工作的。我们将了解各个层是如何相互作用的,以及信息是如何从一层传递到另一层的。

8.4 深度学习如何在监督方式下工作?

我们现在已经介绍了神经网络的主要组成部分。现在是所有部分汇聚在一起,组织整个学习的时候了。神经网络的训练是一个非常复杂的过程。整个过程可以按照以下步骤一步步地进行检查。

也许你会想知道神经网络的学习是什么意思。学习是一个过程,为了所有网络层找到最佳和最优化的权重和偏置值,以便我们可以达到最佳准确度。由于深度神经网络在权重和偏置项方面实际上具有无限的可能性,我们必须找到所有参数的最优值。考虑到改变一个值会影响其他值,这似乎是一项艰巨的任务,而实际上它是一个各种网络参数在变化的过程。

回想一下,在第一章中我们介绍了监督学习的基础知识。我们将在这里刷新对此的理解。刷新监督学习的原因是确保您能够充分理解训练神经网络的过程。

8.4.1 监督学习算法

一个快速的定义是 - 监督学习算法具有“指导”或“监督”,以指导向着实现未来预测的业务目标。

正式地说,监督模型是使用输入数据和期望输出来预测未来的统计模型。输出是我们希望预测的值,被称为目标变量,用于进行预测的数据称为训练数据。目标变量有时被称为标签。数据中存在的各种属性或变量称为独立变量。每个历史数据点或训练示例都包含这些独立变量和相应的目标变量。监督学习算法对未来看不见的数据进行预测。解决方案的准确性取决于所进行的训练以及从标记历史数据中学到的模式。

大多数深度学习解决方案都基于监督学习。然而,无监督深度学习正在迅速获得认可,因为未标记的数据集比标记的数据集更加丰富。

例如,如果我们希望创建一个可以识别人脸的解决方案。在这种情况下,我们将有:

训练数据:来自不同角度的人脸图像。

目标变量:人的姓名。

这个训练数据集可以输入到算法中。然后算法将理解各种面部的属性,或者换句话说,学习各种属性。基于所进行的训练,算法可以对面部进行预测。如果概率得分表明面部属于某人 X,那么我们可以安全地说该面部属于某人 X。

监督学习问题用于需求预测、信用卡欺诈检测、客户流失预测、保费估算等。它们在零售、电信、银行和金融、航空、保险等行业被广泛应用。

我们现在为您刷新了监督学习的概念。我们将现在转移到神经网络训练的第一步,即前馈传播。

8.4.2 第 1 步:前馈传播

让我们开始神经网络中的过程。我们尝试在图 8-9 中创建一个说明性的图表。这是我们为了解释该过程而创建的网络的基本骨架。我们假设有一些输入数据点,我们将有输入数据层,该层将消耗输入数据。信息从输入层流向数据转换层(隐藏层)。在隐藏层中,使用激活函数处理数据,并基于权重和偏置项进行处理。然后对数据集进行预测。这称为前馈传播,因为在此过程中,输入变量按顺序从输入层到输出层计算。

让我们拿同样的问题来解释 8.3 节中监督算法中的过程。例如,如果我们希望创建一个能够识别人脸的解决方案。在这种情况下,我们将有

训练数据:来自各个角度的不同人脸图像。

目标变量:人的姓名。

这个训练数据集可以输入到算法中。然后网络将理解各种面孔的属性,或者换句话说,学习面部数据的各种属性。基于所进行的训练,算法可以对面部进行预测。如果概率得分表明面部属于某人 X,那么我们可以安全地说该面部属于某人 X。

图 8-9 神经网络训练过程的基本骨架,我们有输入层和数据转换层。

一旦在隐藏层中完成数据处理,就会生成一个预测,即面部属于某人 X 的概率。

8.4.3 第 2 步:添加损失函数

第 1 步生成输出。现在我们必须评估该网络的准确性。我们希望我们的网络在识别面部时具有尽可能高的准确性。并使用算法生成的预测,我们将控制并提高网络的准确性。

网络中的准确度测量可以通过损失函数来实现,也被称为目标函数。损失函数比较实际值和预测值。损失函数计算差异分数,因此能够测量网络的表现如何以及错误率是多少。

让我们更新我们在第 1 步创建的图表,通过添加一个损失函数和相应的损失分数,来测量网络的准确性,如图 8-10 所示。

图 8-10 添加了一个损失函数来测量准确性。

8.4.4 第 3 步:计算错误

我们在网络的第 1 步生成了预测。在第 2 步,我们将输出与实际值进行比较,以得到预测误差。我们解决方案的目标是最小化这个错误,即最大化准确性。

为了不断降低误差,损失分数(预测-实际)被用作反馈来调整权重的值。这个任务是由反向传播算法完成的。

8.5 反向传播

在上个部分的第 3 步中,我们说我们使用优化器不断更新权重以减小错误。学习速率定义了减小错误的修正步长的大小,反向传播用于调整连接权重。这些权重是根据错误向后更新的。随后,重新计算错误,计算梯度下降,并调整相应的权重。因此,反向传播有时被称为深度学习中的中心算法。

反向传播最初是在 20 世纪 70 年代提出的。然后在 1986 年,大卫·鲁梅尔哈特,杰弗里·辛顿和罗纳德·威廉斯的论文得到了很多赞赏。在现代,反向传播是深度学习解决方案的支柱。

下图显示了反向传播的过程,信息从输出层向隐藏层反向流动。注意,信息的流动是与正向传播相反的,正向传播中信息从左到右流动。反向传播的过程如图 8-11 所示。

图 8-11 反向传播作为一个过程- 信息从最终层向初始层流动

page12image55891232

首先,我们将以非常高层次描述过程。记住,在训练过程的开始,在步骤 1 中,权重被分配了一些随机值。使用这些随机值,生成了初始输出。由于这是第一次尝试,收到的输出可能与实际值相差很大,相应的损失分数也很高。但这将要改善。在训练神经网络时,权重(和偏置)会在正确的方向上稍微调整,随后,损失分数减少。我们迭代这个训练循环很多次,最终得到最优的最小化损失函数的权重值。

反向传播允许我们在网络训练过程中迭代地减少误差。

下面的部分将涉及大量数学内容。如果你对过程背后的数学不感兴趣,可以跳过它。

8.5.1 反向传播背后的数学

要训练神经网络,我们计算一个损失函数。损失函数告诉我们预测值与实际值之间的差异有多大。反向传播计算损失函数相对于每个权重的梯度。有了这些信息,每个权重都可以在迭代中逐渐更新,从而逐渐减少损失。

在反向传播中,梯度是从网络的最后一层通过隐藏层到第一层计算的。所有层的梯度使用微积分链式法则组合在一起,以获得任何特定层的梯度。

现在我们将更详细地介绍这个过程。首先让我们表示一些数学符号:

  1. h^(i)) – 隐藏层 i 的输出

  2. g^(i))- 隐藏层 i 的激活函数

  3. w^(i))- 层 i 中的隐藏权重矩阵

  4. b^(i))- 层 i 中的偏置

  5. x- 输入向量

  6. N – 网络中的总层数

  7. W^(i))[jk]- 网络中从第(i-1)层节点 j 到第 i 层节点 k 的权重

  8. ∂A/∂B:它是 A 相对于 B 的偏导数

在网络训练过程中,输入 x 被馈送到网络,并通过各层生成输出ŷ。期望的输出是 y。因此,比较 y 和ŷ的成本函数或损失函数将是 C(y, ŷ)。此外,网络的任何隐藏层的输出都可以表示为:

(方程式 8.4)

h^((i)) = g^((i)) (W^((i)T) x + b^((i)))

这里,i(索引)可以是网络中的任意层

最终层的输出是:

(方程 8.5)

y(x) = W^((N)T) h^((N-1)) + b^((N))

在网络训练过程中,我们调整网络的权重以减少 C。因此,我们计算 C 相对于网络中每个权重的导数。以下是网络中每个权重的 C 导数

现在我们知道神经网络有很多层。反向传播算法从网络的最后一层开始计算导数,即第 N^(th)层。然后这些导数向后传播。因此,N 层的导数将被馈送到网络的(N-1)层,依此类推。

使用微积分链式法则分别计算 C 的导数的每个分量。

根据链式法则,对于一个依赖于 b 的函数 c,其中 b 依赖于 a,c 相对于 a 的导数可以写成

因此,在反向传播中,第 N 层的导数被用于第 (N-1) 层,以便保存它们然后在第 (N-2) 层再次使用。我们从网络的最后一层开始,遍历所有层直到第一层,并且每次,我们使用最后一次计算的导数来得到当前层的导数。因此,与普通方法相比,反向传播变得极其高效,因为我们不需要分别计算网络中的每个权重。

一旦我们计算出梯度,我们将更新网络中的所有权重。目标是最小化成本函数。我们已经在上一节中学习了类似梯度下降的方法。

我们现在将继续进行神经网络训练过程的下一步。

步骤 4:优化

在上一节中我们学习了反向传播。它让我们能够优化我们的网络并实现最佳准确度。因此,我们也更新了图中的图 8-12。请注意优化器提供了定期和连续的反馈以达到最佳解决方案。

图 8-12 优化是最小化损失函数的过程

一旦我们为我们的网络获得了权重和偏差的最佳值,我们称之为我们的网络已经训练完成。我们现在可以用它对未经训练的数据集进行预测。

现在你已经了解了深度学习的各个组件以及它们在监督方式下如何协同工作。我们现在将简要描述无监督深度学习。

8.6 无监督深度学习的工作原理

我们知道无监督学习解决方案用于未标记的数据集。对于无监督设置中的深度学习,训练数据集是未标记的。

与有标签的监督数据集相比,无监督方法必须自我组织以获取密度、概率分布、偏好和分组。我们可以使用监督和无监督方法来解决类似问题。例如,监督深度学习方法可用于识别狗和猫,而无监督深度学习方法可能用于将狗和猫的图片聚类到不同的组中。在机器学习中也观察到,许多最初构想为监督学习的解决方案,随着时间的推移采用了无监督学习方法来丰富数据,从而改进了监督学习解决方案。

在无监督深度学习的学习阶段,预期网络将模拟数据,然后根据错误进行自我改进。在监督学习算法中,还有其他方法起到与反向传播算法相同的作用。它们是:

  1. 波兹曼学习规则

  2. 对比散度

  3. 最大似然

  4. 霍普菲尔德学习规则

  5. Gibbs 采样

  6. 深度信念网络等等

在本书中,我们将讨论自动编码器和深度信念网络。我们还将探索 GAN(生成对抗网络)。现在是时候让你检查所有用于深度学习的工具和库了。

 - 答题测验 - 回答这些问题以检查你的理解。答案在书的末尾。

  1. 以简单形式写出反向传播技术的主要步骤。

  2. 反向传播在无监督学习中更受欢迎。正确还是错误?

  3. 深度学习的目标是最大化损失函数。正确还是错误?

8.7 流行的深度学习库

在过去的几章中,我们使用了许多库和包来实现解决方案。在行业中有很多用于深度学习的库。这些包加快了解决方案的构建,并减少了工作量,因为大部分繁重的工作都是由这些库完成的。

我们在这里讨论最流行的深度学习库:

TensorFlow:Google 开发的 TensorFlow(TF)可以说是最流行和广泛使用的深度学习框架之一。它于 2015 年推出,自那以后被全球许多企业和品牌使用。

Python 主要用于 TF,但也可以使用 C++、Java、C#、Javascript、Julia。你需要在系统上安装 TF 库并导入该库。

前往 www.tensorflow.org/install 并按照说明安装 TensorFlow。

TensorFlow 是最流行的库之一,也可以在 iOS 和 Android 等移动设备上运行。

Keras:Keras 是一个成熟的 API 驱动解决方案,非常易于使用。它是初学者的最佳选择之一,也是在易于理解和快速原型设计简单概念方面最好的选择之一。Keras 最初于 2015 年发布,是最受推荐的库之一。

前往 keras.io 并按照说明安装 Keras。可以使用 Tf.keras 作为 API。

使用 Python 生成器进行序列化/反序列化 API、回调和数据流传输非常成熟。在 Keras 中,庞大的模型被简化为单行函数,这使得它成为一个 less configurable 环境,因此非常方便和易于使用。

PyTorch:Facebook 的心头宝贝 PyTorch 于 2016 年发布,是另一个流行的框架。PyTorch 使用动态更新的图形,并允许数据并行和分布式学习模型。在 PyTorch 中有像 pdb 或 PyCharm 这样的调试器可用。对于小型项目和原型设计,PyTorch 可能是一个不错的选择。

Sonnet:DeepMind 的 Sonnet 是使用和基于 TF 开发的。Sonnet 专为复杂的神经网络应用和架构设计而设计。它通过创建与神经网络特定部分相对应的主要 Python 对象来工作。然后这些 Python 对象独立于计算 TF 图形地连接。由于这种分离(创建 Python 对象并将其与图形关联),设计得到了简化。

拥有高级面向对象类库非常有帮助,因为在我们开发机器学习解决方案时,它允许我们抽象化。

MXNet:Apache 的 MXNet 是一个高度可扩展的深度学习工具,易于使用并具有详细的文档。MXNet 支持许多语言,如 C ++、Python、R、Julia、JavaScript、Scala、Go 和 Perl。

还有其他框架,如 Swift、Gluon、Chainer、DL4J 等,但我们只讨论了流行的框架。

我们现在将检查 tensorflow 和 keras 中的一个简短的代码。这只是为了测试您是否已正确安装这些库。您可以在 https://www.tensorflow.orghttps://keras.io 上了解更多关于 tensorflow 和 keras 的信息。

8.7.1 keras 和 tensorflow 的 Python 代码

我们在 tensorflow 中实现了一个非常简单的代码。我们只是导入了 tensor flow 库并打印“hello”。我们还会检查 tensorflow 的版本。

import tensorflow as tf
hello = tf.constant('Hello, TensorFlow!')
sess = tf.Session()
print(sess.run(hello))
print("TensorFlow version:", tf.__version__)

如果此代码可以在您的计算机上运行并为您打印出 tensorflow 的版本,则意味着您已经正确安装了tensorflow

from tensorflow import keras
from keras import models

如果该代码对您运行并打印出 keras 的版本,那么这意味着您已正确安装了keras

8.8 结束语

深度学习正在改变我们生活的世界。它使我们能够训练和创建真正复杂的解决方案,这些只是以前的想法。深度学习的影响可以在多个领域和行业中看到。也许没有一个行业不受深度学习奇迹的影响。

深度学习是研究和开发中最受追捧的领域之一。每年都有许多关于深度学习的期刊和论文发表。世界上重要机构和大学(如牛津、斯坦福等)的研究人员正全神贯注于寻找改进的神经网络架构。与此同时,世界知名机构(如谷歌、Facebook 等)的专业人员和工程师正在努力创建复杂的架构来提高我们的性能。

深度学习使得我们的系统和机器能够解决通常被认为是人类领域的问题。人们改进了药物行业的临床试验流程,改进了欺诈检测软件、自动语音检测系统、各种图像识别解决方案、更强大的自然语言处理解决方案、改进客户关系管理和推荐系统的定向营销解决方案、更好的安全流程等等。这个列表相当长,并且每天都在增长。

与此同时,仍然存在一些挑战。对深度学习的期望不断增加。深度学习不是解决所有问题的灵丹妙药或魔法棒。它确实是更为复杂的解决方案之一,但绝对不是所有业务问题的 100%解决方案。与此同时,我们需要为算法提供的数据集并不总是可用的。缺乏代表业务问题的高质量数据集。经常可以观察到,像谷歌、Meta 或亚马逊这样的大型组织可以负担得起收集如此大规模的数据集。而且很多时候,我们在数据中发现了很多质量问题。拥有处理这些复杂算法的处理能力也是一个挑战。随着云计算的出现,尽管这个问题在一定程度上已经得到了解决。

在本章中,我们探讨了神经网络和深度学习的基础知识。我们涵盖了神经元、激活函数、网络不同层次和损失函数的细节。我们还详细介绍了反向传播算法——用于训练监督深度学习解决方案的核心算法。然后,我们简要介绍了无监督深度学习算法。我们将在接下来的章节中详细介绍这些无监督深度学习解决方案。

我们还展示了主要的激活函数在图 8-13 中。

图 8-13 主要激活函数一览(图片来源:towardsdatascience)

神经网络中的激活函数 | 作者:SAGAR SHARMA | Towards Data Science

您现在可以转向练习问题。

实用的下一步和建议阅读

  1. 深度学习与 Python》这本由 François Chollet 编写的书籍是澄清深度学习概念的最佳资源之一。它涵盖了深度学习和神经网络的所有概念,并由 Keras 的创作者编写。

  2. 阅读以下研究论文:

  3. 通过 G.Hinton 等人的论文《在神经网络中提炼知识

  4. 通过 R. Srivastava 等人的论文《训练非常深的网络

  5. 通过 Tomas Mikolov 等人的论文《词和短语的分布式表示及其组合性(Word2Vec)

  6. 通过 Ian J. Goodfellow 等人的论文《生成对抗网络(GANs)

  7. 通过 Kaining He 等人的论文《用于图像识别的深度残差学习(ResNet)

8.9 总结

  • 深度学习和神经网络

  • 神经元的定义

  • 不同类型的激活函数

  • 不同类型的优化函数

  • 神经网络训练过程

  • 深度学习的各种库

第九章:自编码器

“在强烈的复杂性中,简单的事物显现出来 - 温斯顿·丘吉尔”

本章内容包括:

  • 自编码器

  • 自编码器的训练

  • 自编码器的类型

  • 使用 tensorflow 和 keras 的 Python 代码

在书的最后一章,我们探讨了深度学习的概念。这些是使您能够掌握无监督深度学习的基础概念。因此,让我们开始无监督深度学习的第一个主题。我们从自编码器开始作为第一个主题。我们将首先介绍自编码器的基础知识,它们是什么以及我们如何训练自编码器。然后我们将进入不同类型的自编码器,然后是有关实现的 Python 代码。

欢迎来到第九章,祝你好运!

9.1 技术工具包

我们将继续使用迄今为止使用的相同版本的 Python 和 Jupyter 笔记本。本章中使用的代码和数据集已经在此位置签入。

在本章中,您需要安装几个 Python 库 - tensorflow 和 keras。

让我们开始阅读书籍的第九章吧!

9.2 特征学习

预测建模是一个非常有趣的话题。在各种领域和业务功能中,预测建模被用于各种目的,如预测明年业务销售额、预测预期降雨量、预测即将到来的信用卡交易是否为欺诈、预测客户是否会购买等。用例很多,上述所有用例都属于监督学习算法。

我们使用的数据集具有变量或属性。它们也被称为特征。

尽管我们希望创建这些预测模型,但我们也有兴趣了解对于进行预测有用的变量。让我们考虑一个银行想要预测即将到来的交易是否是欺诈的情况。在这种情况下,银行希望知道哪些因素是重要的,以便将即将到来的交易标识为欺诈。可能考虑的因素包括交易金额、交易时间、交易的来源/目的地等。用于进行预测的重要变量称为显著变量

为了创建基于机器学习的预测模型,我们使用特征工程。特征工程,又称特征提取,是从原始数据中提取特征以提高模型整体质量并增强准确性的过程,与仅向机器学习模型提供原始数据的模型相比。

特征工程可以使用领域理解、各种手动方法以及一些自动化方法来完成。其中一种方法称为特征学习。特征学习是一组技术,它们帮助解决方案自动发现所需的特征检测表示。利用特征学习,不需要手动特征工程。特征学习对使用图像、文本、音频和视频的数据集的影响更加重要。

特征学习可以是有监督和无监督的。对于有监督的特征学习,神经网络是最好的例子。对于无监督的特征学习,我们有矩阵分解、聚类算法和自编码器等例子。在本书的上一章中已经详细介绍了聚类和矩阵分解。在本章中,我们将讨论自编码器。

接下来,我们将在下一节中介绍自编码器。

9.3 自编码器介绍

当我们开始处理任何数据科学问题时,数据起到最重要的作用。具有大量噪声的数据集是数据科学和机器学习中最大的挑战之一。现在有很多解决方案,自编码器就是其中之一。

简而言之,自编码器是一种人工神经网络,用于学习数据编码。它们通常用于降维方法。它们还可以用作生成模型,可以为我们创建类似于旧数据的合成数据。例如,如果我们没有足够的数据来训练机器学习模型,我们可以使用生成的合成数据来训练模型。

自编码器是前馈神经网络,它们将输入压缩成低维代码,然后尝试从这个表示中重建输出。自编码器的目标是学习高维数据集的低维表示(有时也称编码)。回顾一下前面章节中的主成分分析(PCA)。自编码器可以看作是 PCA 的一般化。PCA 是一种线性方法,而自编码器也可以学习非线性关系。因此,自编码器非常适合用于捕捉输入数据的最重要属性的降维解决方案。

接下来,我们将在下一节中研究自编码器的各个组成部分。

9.4 自编码器的组成部分

自动编码器的结构相当简单。自动编码器由三部分组成:编码器、瓶颈或代码以及解码器,如(图 9.1)所示。简单来说,编码器压缩输入数据,瓶颈或代码包含这些压缩信息,解码器解压知识,从而将数据重构回其原始形式。一旦解压完成并且数据被重构成其已编码形式,输入和输出可以进行比较。

让我们更详细地学习这些组件。

  1. 编码器:输入数据通过编码器。编码器实际上是一个全连接的人工神经网络。它将输入数据压缩成编码表示,并且,在过程中生成的输出大大缩小。编码器将输入数据压缩成一个称为瓶颈的压缩模块。

  2. 瓶颈:瓶颈可以称为编码器的大脑。它包含压缩的信息表示,瓶颈的工作是只允许最重要的信息通过。

  3. 解码器:从瓶颈接收到的信息由解码器解压。它将数据重新创建为其原始或编码形式。一旦解码器完成其工作,实际值将与解码器创建的解压值进行比较。

图 9.1 自动编码器的结构,包括输入层、隐藏层和输出层。

09_01

对于自动编码器,应该注意几个重要的点:

  1. 在自动编码器中,当解压缩与原始输入相比时会出现信息丢失。因此,当压缩数据解压时,与原始数据相比会存在损失。

  2. 自动编码器是特定于数据集的。这意味着在花朵图片上训练的算法不会在交通信号灯图片上起作用,反之亦然。这是因为自动编码器学到的特征只适用于花朵。因此,我们可以说自动编码器只能压缩与训练数据相似的数据。

  3. 训练专门的算法实例以在特定类型的输入上良好执行相对较容易。我们只需要代表性的训练数据集来训练自动编码器。

我们现在已经涵盖了自动编码器的主要组件。接下来让我们进入训练自动编码器的过程。

9.5 自动编码器的训练

需要注意的是,如果在数据中变量之间没有相关性,那么很难对输入数据进行压缩和随后进行解压。为了创建一个有意义的解决方案,输入数据中的变量之间应该存在某种程度的关系或相关性。为了创建一个自动编码器,我们需要一个编码方法,一个解码方法和一个损失函数来比较实际值与解压值。

过程如下:

  • 首先,输入数据通过编码器模块。

  • 编码器将模型的输入压缩成紧凑的瓶颈。

  • 瓶颈的任务是限制信息的流动,并只允许重要信息通过,因此有时将瓶颈称为知识表示

  • 紧随瓶颈之后的是解码器,它对信息进行解压缩,并将数据重新创建为其原始或编码形式。

  • 这个编码器-解码器架构非常有效地从输入数据中获取最重要的属性。

  • 解决方案的目标是生成与输入相同的输出。

通常观察到解码器架构是编码器架构的镜像。这不是强制性的,但通常会遵循。我们确保输入和输出的维度相同。

我们需要为训练自动编码器定义四个超参数:

  1. Code size: 这可能是最重要的超参数。它表示中间层中节点的数量。这决定了数据的压缩程度,也可以作为正则化项。代码大小的值越小,数据的压缩程度就越大。

  2. 层数是指自动编码器的深度的参数。具有更深度的模型显然更复杂,处理时间也更长。

  3. 每层的节点数是每层使用的权重。随着每个后续层的输入在层之间变小,每层的节点数通常会减少。然后在解码器中增加。

  4. 最后一个超参数是所使用的损失函数。如果输入值在[0,1]范围内,首选二进制交叉熵,否则使用均方误差。

我们已经介绍了用于训练自动编码器的超参数。训练过程类似于我们在上一章中已经介绍过的反向传播。

我们现在将在下一节中介绍自动编码器的一些重要应用。

9.6 自动编码器的应用

自动编码器能够解决许多固有于无监督学习的问题。自动编码器的主要应用包括:

  1. 降维是自动编码器的主要应用之一。有时观察到自动编码器可以学习比主成分分析和其他技术更复杂的数据投影。

  2. 异常检测也是自动编码器的一种应用。错误或重建数据之间的误差(实际数据和重建数据之间的误差)可以用来检测异常。

  3. 数据压缩也被认为是自动编码器的主要应用之一。但已经观察到,通过训练算法很难打败像 JPEG 这样的基本解决方案。此外,由于自动编码器是数据特定的,它们只能用于它们已经训练过的数据类型。如果我们希望增加容量以包含更多数据类型并使其更加通用,那么所需的训练数据量将会非常高,显然所需的时间也会很长。

  4. 还有其他应用,比如药物发现,机器翻译,图像去噪等。但是在现实世界中,自动编码器的实际实现还不是很多。这是因为诸多原因,如数据集的不可用性,基础设施,各种系统的准备情况等。

我们现在将在下一节继续介绍自动编码器的类型。

9.7 自动编码器的类型

有五种主要类型的自动编码器。下面简要描述了不同类型编码器的情况。我们将本节的数学内容保持简单,并跳过幕后的数学原理,因为这些原理相当复杂难以理解。对于好奇的读者,本章的进一步阅读部分中分享了理解幕后数学原理的链接。

  1. 欠完备自动编码器:欠完备自动编码器是自动编码器的最简单形式。它只是接受一个输入数据集,然后从压缩的瓶颈区域重新构建相同的数据集。通过根据重构误差惩罚神经网络,模型将学习数据的最重要属性。通过学习最重要的属性,模型将能够从压缩状态重构原始数据。由于在重构压缩数据时存在损失,这种损失被称为重构损失。

    欠完备自动编码器在本质上是无监督的,因为它们没有任何目标标签来训练。这种类型的自动编码器用于降维。回想一下第二章中我们讨论的降维(PCA),以及第六章中我们讨论的高级降维算法(t-SNE 和 MDS)。

图 9.2 随着维度的增加,性能开始改善,但在一段时间后开始下降。维度灾难是一个真正的问题

09_02

使用欠完备自编码器可以进行降维,因为会创建一个压缩形式的输入数据瓶颈。可以利用网络将这种压缩数据解压缩。回想一下在第三章中我们讨论过 PCA 提供了输入变量的线性组合。要获取更多详细信息并刷新 PCA,请参阅第三章。我们知道 PCA 尝试获得一个低维超平面来描述原始数据集,而欠完备自编码器也可以学习非线性关系。我们在下图 9.3 中展示了它们之间的差异:

图 9.3 PCA 是线性的,而自编码器是非线性的。这是两种算法之间的核心区别。

09_03

有趣的是,如果从欠完备自编码器中去除所有非线性激活函数,只使用线性层,那么它等价于只有 PCA。为了使自编码器能够泛化而不是记住训练数据,欠完备自编码器是受到限制的,并通过瓶颈的大小进行微调。它允许解决方案不记住训练数据并且泛化能力非常好。

如果一个机器学习模型在训练数据上表现非常好,但在未见过的测试数据上却不起作用,则称为过拟合。

  1. 稀疏自编码器:稀疏自编码器与欠完备自编码器类似,只是它们使用了不同的方法来解决过拟合问题。在概念上,稀疏自编码器改变了每个隐藏层的节点数量并使其灵活。现在,由于不可能有一个具有这种灵活神经元数量能力的神经网络,因此为其定制了损失函数。在损失函数中,引入了一个项来捕捉激活的神经元数量。还有一个与激活的神经元数量成比例的惩罚项。激活的神经元数量越多,惩罚就越高。这个惩罚被称为稀疏函数。通过使用惩罚,可以减少激活的神经元数量,因此惩罚较低,网络能够解决过拟合问题。

  2. 收缩自编码器:收缩自编码器与其他自编码器的工作原理类似。它们认为输入数据相似的部分应该被编码成相同的。因此,它们应该具有相同的潜在空间表示。这意味着输入数据和潜在空间之间不应该有太大的差异。

  3. 去噪自编码器:去噪意味着去除噪音,这正是去噪自编码器的精确任务。它们不接受图像作为输入,而是接受图像的嘈杂版本作为输入,如下图 9.4 所示。

图 9.4 原始图像,嘈杂输出和自编码器的输出

09_04

去噪自编码器中的过程如下所示。 通过向原始图像添加噪声来改变原始图像。 这个嘈杂图像被输入到编码器-解码器架构中,接收的输出与原始图像进行比较。 自编码器学习了图像的表示,该表示用于去除噪音,并且通过将输入图像映射到低维流形来实现。

图 9.5:自编码器中的去噪过程。 它从原始图像开始,添加噪音产生一个嘈杂图像,然后将其输入到自编码器中。

09_05

我们可以使用去噪自编码器进行非线性降维。

  1. 变分自编码器:在标准自编码器模型中,使用瓶颈以压缩形式表示输入。 它们是概率生成模型,只需要神经网络作为整体结构的一部分。 它们是使用期望最大化元算法进行训练的。 技术细节超出了本书的范围。

我们现在将在下一节中学习如何使用 Python 创建一个自编码器。

9.8 自编码器的 Python 实现

我们在这里创建了两个版本的自编码器。 代码来源于 Keras 网站的官方来源(blog.keras.io/building-autoencoders-in-keras.html),并已经修改以供我们使用。

步骤 1:首先,我们将导入必要的库:

import keras
from keras import layers

步骤 2:我们在这里创建我们的网络架构

# This is the size of our encoded representations
encoding_dim = 32  # 32 floats -> compression of factor 24.5, assuming the input is 784 floats

# This is our input image
input_img = keras.Input(shape=(784,))
# "encoded" is the encoded representation of the input
encoded = layers.Dense(encoding_dim, activation='relu')(input_img)
# "decoded" is the lossy reconstruction of the input
decoded = layers.Dense(784, activation='sigmoid')(encoded)

# This model maps an input to its reconstruction
autoencoder = keras.Model(input_img, decoded)

步骤 3:向模型添加更多细节

# This model maps an input to its encoded representation
encoder = keras.Model(input_img, encoded) 

# This is our encoded (32-dimensional) input
encoded_input = keras.Input(shape=(encoding_dim,))
# Retrieve the last layer of the autoencoder model
decoder_layer = autoencoder.layers[-1]
# Create the decoder model
decoder = keras.Model(encoded_input, decoder_layer(encoded_input))

autoencoder.compile(optimizer='adam', loss='binary_crossentropy')

步骤 4:加载数据集

(x_train, _), (x_test, _) = mnist.load_data()

步骤 5:创建训练和测试数据集

x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))
print(x_train.shape)
print(x_test.shape)

步骤 6:现在适配模型

autoencoder.fit(x_train, x_train,
                epochs=5,
                batch_size=128,
                shuffle=True,
                validation_data=(x_test, x_test))

09_05a

步骤 7:在测试数据集上进行测试

# Encode and decode some digits
# Note that we take them from the *test* set
encoded_imgs = encoder.predict(x_test)
decoded_imgs = decoder.predict(encoded_imgs)

步骤 8:并绘制结果。 您可以看到原始图像和最终输出。

# Use Matplotlib (don't ask)
import matplotlib.pyplot as plt

n = 10  # How many digits we will display
plt.figure(figsize=(20, 4))
for i in range(n):
    # Display original
    ax = plt.subplot(2, n, i + 1)
    plt.imshow(x_test[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

    # Display reconstruction
    ax = plt.subplot(2, n, i + 1 + n)
    plt.imshow(decoded_imgs[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
plt.show()

09_05b

9.9 思考收尾

深度学习是一个强大的工具。 有了合理的业务问题和高质量的数据集,我们可以创建许多创新解决方案。 自编码器只是这类解决方案中的一种。

在本章中,我们从特征工程开始,它允许我们从数据集中提取最重要的特征。 然后我们转向自编码器。 自编码器是一种仅用于学习未标记数据集的高效编码的神经网络类型。 自编码器可以应用于许多业务问题,如面部识别,异常检测,图像识别,药物发现,机器翻译等。

在本章中,我们涵盖了自编码器。 在下一章中,我们将讨论生成人工智能或 GenAI,这是一个热门话题。 这将是本书的第十章,也是倒数第二章。

9.10 总结

  • 我们在本章中学习了特征工程。 特征工程或特征提取是从原始数据中提取特征以改善模型的总体质量和增强与仅将原始数据馈送到机器学习模型相比的精度的过程。

    • 然后我们在接下来的章节中介绍了自编码器。我们在本章中学习了自编码器。自编码器是前馈神经网络,它们将输入压缩成低维代码,然后尝试从该表示重构输出。

    • 自编码器的典型架构包括编码器、瓶颈和解码器。异常检测、数据压缩和降维是自编码器的主要用途。

  • 我们还涵盖了不同类型的自编码器,如欠完备、稀疏、去噪、收缩和变分自编码器。

实际的下一步和建议阅读

  1. 这里有一个很棒的博客 blog.keras.io/building-autoencoders-in-keras.html

  2. 阅读 G.E.Hinton、A. Krizhevsky、S.D.Wang 撰写的关于自编码器的论文《转换自编码器》www.cs.toronto.edu/~bonner/courses/2022s/csc2547/papers/capsules/transforming-autoencoders,-hinton,-icann-2011.pdf

  3. 阅读 Dor Bank、Naom Koenigstein、Raja Giryes 撰写的关于自编码器的论文《自编码器》arxiv.org/abs/2003.05991

  4. 阅读 Umberto Michelucci 撰写的关于自编码器的论文《自编码器简介》arxiv.org/abs/2201.03898

  5. 在 tensorflow 官方页面有很好的代码和数据集可用 www.tensorflow.org/tutorials/generative/autoencoder

posted @ 2024-05-02 22:33  绝不原创的飞龙  阅读(56)  评论(0编辑  收藏  举报