原文:
annas-archive.org/md5/6b15c463e64a9f03f0d968a77b424918
译者:飞龙
前言
关于
本节简要介绍了作者、本书的内容覆盖范围、开始时你需要的技术技能,以及完成所有活动和练习所需的硬件和软件要求。
本书简介
无监督学习是一种在没有标签数据的情况下非常有用且实用的解决方案。
Python 应用无监督学习 引导你使用无监督学习技术与 Python 库配合,提取非结构化数据中的有意义信息的最佳实践。本书首先解释了基本的聚类如何工作,以在数据集中找到相似的数据点。一旦你熟悉了 k-means 算法及其操作方式,你将学习什么是降维以及如何应用它。随着学习的深入,你将掌握各种神经网络技术,以及它们如何提升你的模型。研究无监督学习的应用时,你还将学习如何挖掘 Twitter 上的热门话题。你将通过完成各种有趣的活动来挑战自己,例如进行市场购物篮分析,并识别不同产品之间的关系。
到本书的最后,你将掌握使用 Python 自信地构建自己模型所需的技能。
作者简介
Benjamin Johnston 是世界领先的数据驱动医疗科技公司之一的高级数据科学家,参与了整个产品开发过程中的创新数字解决方案的开发,从问题定义到解决方案的研发,再到最终部署。他目前正在完成机器学习博士学位,专攻图像处理和深度卷积神经网络。他在医疗设备设计和开发领域有超过 10 年的经验,担任过多种技术角色,拥有澳大利亚悉尼大学工程学和医学科学两项一等荣誉学士学位。
Aaron Jones 是美国一家大型零售商的全职高级数据科学家,同时也是一名统计顾问。他在零售、媒体和环境科学领域工作时,建立了预测性和推断性模型以及多个数据产品。Aaron 居住在华盛顿州的西雅图,特别关注因果建模、聚类算法、自然语言处理和贝叶斯统计。
Christopher Kruger 曾在广告领域担任高级数据科学家。他为不同行业的客户设计了可扩展的聚类解决方案。Chris 最近获得了康奈尔大学计算机科学硕士学位,目前在计算机视觉领域工作。
学习目标
-
理解聚类的基础知识和重要性
-
从零开始构建 k-means、层次聚类和 DBSCAN 聚类算法,并使用内置包实现
-
探索降维及其应用
-
使用 scikit-learn(sklearn)实现并分析鸢尾花数据集上的主成分分析(PCA)
-
使用 Keras 构建 CIFAR-10 数据集的自编码器模型
-
使用机器学习扩展(Mlxtend)应用 Apriori 算法研究交易数据
受众
Python 应用无监督学习是为开发人员、数据科学家和机器学习爱好者设计的,旨在帮助他们了解无监督学习。具有一定的 Python 编程基础,以及包括指数、平方根、均值和中位数等数学概念的基本知识将会非常有帮助。
方法
Python 应用无监督学习采用实践操作的方式,使用 Python 揭示您非结构化数据中的隐藏模式。它包含多个活动,利用现实生活中的商业场景,帮助您在高度相关的环境中练习并应用您的新技能。
硬件要求
为了获得最佳的学生体验,我们推荐以下硬件配置:
-
处理器:Intel Core i5 或同等配置
-
内存:4 GB RAM
-
存储:5 GB 可用空间
软件要求
我们还建议您提前安装以下软件:
-
操作系统:Windows 7 SP1 64 位,Windows 8.1 64 位,或 Windows 10 64 位;Linux(Ubuntu,Debian,Red Hat 或 Suse);或最新版本的 OS X
-
Python(3.6.5 或更高版本,最好是 3.7;通过
www.python.org/downloads/release/python-371/
可获得) -
Anaconda(这是用于
mlp_toolkits
中的basemap
模块的;请访问www.anaconda.com/distribution/
,下载 3.7 版本并按照说明进行安装。)
约定
文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名以如下方式显示:“使用math
包没有先决条件,并且它已包含在所有标准 Python 安装中。”
一段代码的写法如下:
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import numpy as np
import math
%matplotlib inline
新术语和重要词汇以粗体显示。您在屏幕上看到的词汇,例如在菜单或对话框中显示的内容,会以这种形式出现在文本中:“接下来,点击metro-jul18-dec18
。”
安装与设置
每一段伟大的旅程都始于一个谦逊的步伐。我们即将展开的无监督学习之旅也不例外。在我们能够利用数据做出惊人的成就之前,我们需要准备好最有效的工作环境。接下来,我们将了解如何做到这一点。
在 Windows 上安装 Anaconda
Anaconda 是一个 Python 包管理器,能够轻松地安装并使用本书所需的库。要在 Windows 上安装它,请按照以下步骤进行:
-
Windows 上的 Anaconda 安装非常用户友好。请访问下载页面以获取安装可执行文件:
www.anaconda.com/distribution/#download-section
。 -
双击计算机上的安装程序。
-
按照屏幕上的提示完成 Anaconda 的安装。
-
安装完成后,你可以访问 Anaconda Navigator,它将像其他应用程序一样出现在你的应用列表中。
在 Linux 上安装 Anaconda
Anaconda 是一个 Python 包管理器,可以轻松地安装并使用本书所需的库。在 Linux 上安装它,请按照以下步骤操作:
-
请访问 Anaconda 下载页面以获取安装 shell 脚本:
www.anaconda.com/distribution/#download-section
。 -
要直接将 shell 脚本下载到你的 Linux 实例中,可以使用
curl
或wget
下载库。以下示例演示了如何使用curl
从 Anaconda 下载页面找到的 URL 获取文件:curl -O https://repo.anaconda.com/archive/Anaconda3-2019.03-Linux-x86_64.sh
-
下载 shell 脚本后,可以使用以下命令运行它:
bash Anaconda3-2019.03-Linux-x86_64.sh
-
运行上述命令将引导你进入一个非常用户友好的安装过程。系统会提示你选择安装位置以及你希望 Anaconda 如何工作。在这种情况下,你只需保留所有标准设置即可。
-
安装 Anaconda 后,你必须创建环境,在这些环境中你可以安装你希望使用的包。Anaconda 环境的一个巨大优点是,你可以为你正在进行的特定项目创建独立的环境!要创建一个新环境,使用以下命令:
conda create --name my_packt_env python=3.7
-
一旦环境创建完成,你可以使用命名明确的
activate
命令激活它:conda activate my_env
就这样!你现在已经进入了自己的自定义环境,这将允许你根据需要为你的项目安装所需的包。要退出环境,你只需使用
conda deactivate
命令。
在 macOS 上安装 Anaconda
Anaconda 是一个 Python 包管理器,允许你轻松安装并使用本书所需的库。在 macOS 上安装它,请按照以下步骤操作:
-
Windows 上的 Anaconda 安装非常用户友好。请访问下载页面以获取安装可执行文件:
www.anaconda.com/distribution/#download-section
。 -
确保选择 macOS,并双击 Download 按钮以下载 Python 3 安装程序。
-
按照屏幕上的提示完成 Anaconda 的安装。
-
安装完成后,你可以访问 Anaconda Navigator,它将像其他应用程序一样出现在你的应用列表中。
在 Windows 上安装 Python
-
在此处查找你所需的 Python 版本:
www.python.org/downloads/windows/
。 -
确保根据您的计算机系统安装正确的“位”版本,可以是 32 位或 64 位。您可以在操作系统的系统属性窗口中找到该信息。
下载安装程序后,只需双击文件并按照屏幕上的友好提示进行操作。
在 Linux 上安装 Python
在 Linux 上安装 Python,请执行以下操作:
-
打开命令提示符并通过运行
python3 --version
验证 Python 3 是否已经安装。 -
要安装 Python 3,请运行以下命令:
sudo apt-get update sudo apt-get install python3.6
-
如果遇到问题,网络上有大量资源可以帮助您排除故障。
在 macOS X 上安装 Python
在 macOS X 上安装 Python,请执行以下操作:
-
按住 CMD + Space,在打开的搜索框中输入
terminal
,然后按 Enter 打开终端。 -
通过运行
xcode-select --install
在命令行中安装 Xcode。 -
安装 Python 3 最简单的方法是使用 homebrew,可以通过运行
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
来安装 homebrew。 -
将 homebrew 添加到您的
PATH
环境变量中。通过运行sudo nano ~/.profile
打开命令行中的配置文件,并在文件底部插入export PATH="/usr/local/opt/python/libexec/bin:$PATH"
。 -
最后一步是安装 Python。在命令行中运行
brew install python
。 -
请注意,如果安装 Anaconda,最新版本的 Python 将自动安装。
附加资源
本书的代码包也托管在 GitHub 上,网址为:https://github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python。我们还提供了来自我们丰富书籍和视频目录的其他代码包,您可以在 https://github.com/PacktPublishing/ 上查看它们!
我们还提供了一个 PDF 文件,里面包含本书中使用的带色彩的截图/图表。您可以在此处下载: https://www.packtpub.com/sites/default/files/downloads/9781789952292_ColorImages.pdf。
第一章:聚类简介
学习目标
到本章结束时,你将能够:
-
区分监督学习与无监督学习
-
解释聚类的概念
-
使用内建的 Python 包实现 k-means 聚类算法
-
计算数据的轮廓系数(Silhouette Score)
在本章中,我们将讨论聚类的概念。
引言
你是否曾经被要求查看一些数据,但最终一无所获?也许你不熟悉该数据集,或者甚至不知道从哪里开始。这可能非常令人沮丧,甚至会让你感到尴尬,尤其是当是别人让你处理这个任务时。
你不是孤单的,实际上,数据本身很多时候也过于混乱,难以理解。你可能正在模仿很多无监督算法的做法,试图从数据中找出意义。当你试图理解电子表格中的那些数字时,可能正是这些无监督算法在做的事情。现实中,很多真实世界中的数据集并没有任何规律或合理性,你将被要求在几乎没有背景准备的情况下分析它们。不过不用担心——本书将为你提供所需的知识,以便你在处理数据探索任务时不再感到沮丧。
在本书中,我们为你开发了一些最佳内容,帮助你理解无监督算法如何工作以及如何使用它们。我们将涵盖如何在数据中寻找聚类的基础知识,如何减少数据的规模以便更容易理解,以及无监督学习的各个方面如何应用于实际世界。希望你能够通过本书,深入理解无监督学习,了解它能解决的问题以及不能解决的问题。
感谢你的加入,祝你旅途愉快!
无监督学习与监督学习的区别
无监督学习是当前机器学习领域最令人兴奋的研究方向之一。如果你之前有研究过机器学习教材,可能已经熟悉过常见的监督学习和无监督学习问题的区分。监督学习包括使用带标签的数据集来进行分类(例如,在研究肺部健康数据集时预测吸烟者和非吸烟者)或拟合回归线(例如,根据房间数量预测房屋销售价格)。这种模型最接近人类直观的学习方法。
如果你想通过基本的烹饪知识来学习如何不把食物烧焦,你可以通过将食物放在炉子上并观察它烧焦所需的时间(输入),来构建一个数据集。最终,随着你不断地烧焦食物,你会建立一个关于何时会烧焦的心理模型,并避免将来再次发生。监督学习的发展曾经是快速而有价值的,但近年来已经逐渐平缓——许多了解数据的障碍已经被克服:
图 1.1:无监督学习与监督学习的区别
相反,无监督学习涵盖了大量无标签数据的问题。在这种情况下,有标签的数据是指提供了“目标”结果的数据,你试图找出与提供的数据的相关性(例如,在之前的例子中你知道你在找的是食物是否被烧焦)。无标签数据是指你不知道“目标”结果是什么,只有提供的输入数据。
基于之前的例子,假设你被丢到了地球上,对烹饪一无所知。你得到了 100 天的时间、一台炉子和一冰箱,里面满是食物,但没有任何关于该做什么的指示。你对厨房的初步探索可能会有无数种方向——在第 10 天,你可能终于学会如何打开冰箱;在第 30 天,你可能会学到食物可以放在炉子上;而在许多天之后,你可能会不经意间做出一道可食用的餐点。正如你所看到的,试图在没有足够信息结构的厨房中找到意义,会导致产生非常嘈杂且与实际做饭完全无关的数据。
无监督学习可以成为解决这个问题的答案。通过回顾你 100 天的数据,聚类可以用来寻找在某些天生产了餐点的相似模式,这样你就可以轻松回顾那些日子里你做了什么。然而,无监督学习并不是一种神奇的答案——仅仅发现聚类同样可能帮助你找到一些相似但最终无用的数据。
这个挑战正是让无监督学习如此令人兴奋的原因。我们如何才能找到更聪明的技术,加速发现对最终目标有益的信息聚类的过程?
聚类
如果你想找出数据集中的潜在含义,能够找到其中相似数据的分组是非常有价值的。如果你是商店老板,并且想要了解哪些客户更有价值,但并没有一个明确的“有价值”标准,那么聚类将是一个很好的起点,帮助你在数据中找到模式。你可能有一些关于“有价值客户”高层次的想法,但在面对大量数据时,你并不完全确定。通过聚类,你可以找到数据中相似群体之间的共性。如果你更深入地研究一个相似的群体,可能会发现这个群体中的每个人在你的网站上停留的时间比其他人都长。这能帮助你识别出“价值”的标准,并为未来的监督学习实验提供清晰的样本数据。
识别聚类
下图展示了两个散点图:
图 1.2:两个不同的散点图
下图将散点图分为两个不同的聚类:
图 1.3:散点图清晰展示了在提供的数据集中存在的聚类
两个图都显示了从高斯分布中随机生成的数字对(x,y 坐标)。仅仅通过瞥一眼图 1.2,你就应该能清楚地看到数据中聚类的位置——但在现实生活中,永远不会这么简单。现在你知道数据可以清晰地分成两个聚类,你可以开始理解这两个群体之间的差异。
从无监督学习在更大范围的机器学习环境中的位置稍作回顾,让我们从理解聚类的基本构成开始。最基本的定义将聚类视为大型数据集的子集中的相似数据组。举个例子,假设你有一个房间,里面有 10 个人,每个人的职业要么是金融行业,要么是科学家。如果你让所有金融行业的人员站在一起,所有科学家也站到一起,那么你就实际上形成了基于职业类型的两个聚类。找到聚类在识别更相似的项时极具价值,而在规模的另一端,它也能帮助识别相互之间有很大差异的项。
二维数据
为了理解这一点,假设你的雇主给了你一个简单的包含 1,000 行数据的数据集,其中有两列数字数据,如下所示:
图 1.4:NumPy 数组中的二维原始数据
初看之下,这个数据集没有提供任何实际的结构或理解——至少可以说是令人困惑的!
数据集中的维度是另一种简单的计数特征数量的方法。在大多数组织良好的数据表中,你可以通过查看列的数量来知道特征的数量。因此,使用一个大小为(1,000 x 2)的 1,000 行数据集,你将有 1,000 个观测值,涵盖两个维度:
你可以通过将第一列与第二列绘制出来,更好地了解数据的结构。会有很多时候,组间差异的原因看起来微不足道,但那些你能够采取行动的差异案例往往是非常有意义的!
练习 1:识别数据中的聚类
你将看到二维图形。请查看提供的二维图形并识别聚类,以强调机器学习的重要性。在不使用任何算法方法的情况下,识别数据中的聚类位置。
本练习将帮助你开始培养通过自身的眼睛和思维过程识别聚类的直觉。在完成这些练习时,思考一下为什么一组数据点应该被视为一个聚类,而另一组数据点不应该被视为聚类:
-
识别以下散点图中的聚类:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_05.jpg
图 1.5 二维散点图
聚类如下:
图 1.6:散点图中的聚类
-
图 1.7:二维散点图
聚类如下:
图 1.8:散点图中的聚类
-
识别散点图中的聚类:
图 1.9:二维散点图
聚类如下:
图 1.10:散点图中的聚类
这些例子大多数你可能很容易理解——这就是重点!人类的大脑和眼睛在发现现实世界中的模式方面非常出色。仅仅通过几毫秒的查看,你就能分辨出哪些数据是组合在一起的,哪些不是。虽然对你来说很容易,但计算机无法像我们一样查看和处理图表。然而,这并不总是坏事——回顾图 1.10,你能仅凭观察图表就找到数据中的六个离散聚类吗?你可能只找到了三个到四个聚类,而计算机可以看到所有六个。人类的大脑非常强大,但它也缺乏基于严格逻辑方法所能处理的细微差别。通过算法聚类,你将学习如何建立一个比人类在这些任务中表现更好的模型!
让我们在下一节中看看这个算法。
k-means 聚类简介
希望到现在为止,你已经可以看到,在机器学习的工作流中,寻找聚类是非常有价值的。那么,如何实际找到这些聚类呢?其中一个最基础但最流行的方法是使用一种叫做 k-means 聚类 的聚类分析方法。k-means 通过在你的数据中寻找 K 个聚类,整个工作流程实际上非常直观——我们将从 k-means 的无数学介绍开始,随后进行 Python 实现。
无数学 k-means 解析
下面是 k-means 聚类的无数学算法:
-
选择 K 个质心(K = 期望的不同聚类数量)。
-
随机地将 K 个质心放置在你的现有训练数据中。
-
计算每个质心到你训练数据中所有点的欧几里得距离。
-
训练数据点会根据它们与质心的距离进行分组。
-
在每个质心分组中的数据点中,计算均值数据点,并将质心移动到该位置。
-
重复这个过程,直到收敛,或者每个组内的成员不再变化。
就这样!下面是一步步的过程,带有一个简单的聚类示例:
图 1.11: 原始数据图,标注在 x,y 坐标上
在图 1.11 中给出的原始数据的基础上,我们可以通过展示每一步的预测聚类来显示 k-means 的迭代过程:
图 1.12: 从左到右读取——红色点是随机初始化的质心,最接近的数据点被分配到各个质心的分组中
k-means 聚类深度解析
为了更深入地理解 k-means,让我们再次走过介绍部分给出的示例,并加入一些支持 k-means 的数学内容。这里的关键组件是欧几里得距离公式:
图 1.13: 欧几里得距离公式
质心在开始时随机设置为你 n 维空间中的点。每个质心作为 (a,b) 输入到前面的公式中,而你空间中的点作为 (x,y) 输入。计算每个点与每个质心坐标之间的距离,选择距离最短的质心作为该点所属的组。
该过程如下:
-
随机质心:[ (2,5) , (8,3) , (4, 5) ]
-
任意点 x: (0, 8)
-
从点到每个质心的距离:[ 3.61, 9.43, 5.00 ]
-
点 x 被分配给质心 1。
曼哈顿距离(替代距离度量)
欧几里得距离是许多机器学习应用中最常用的距离度量,通常被称为距离度量;然而,它并不是唯一的,也不是在每种情况下最好的距离度量。另一个在聚类中常用的距离度量是曼哈顿距离。
曼哈顿距离之所以如此命名,是因为该度量的直觉就像是你在一个大城市(比如纽约市)里开车,城市有许多方形街区。欧几里得距离依赖于对角线,因为它基于勾股定理,而曼哈顿距离则将距离限制为只有直角。曼哈顿距离的公式如下:
图 1.14:曼哈顿距离公式
这里,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_01.png是像欧几里得距离一样的向量。在我们之前关于欧几里得距离的例子中,我们希望找到两个点之间的距离,如果https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_02.png和https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_03.png,那么曼哈顿距离将等于https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_04.png。这个功能适用于任何维度。在实践中,曼哈顿距离可能在处理高维数据时表现得比欧几里得距离更好。
更深的维度
当数据只有二维时,前面的例子很容易可视化。这是为了方便,帮助说明 k-means 是如何工作的,但也可能会让你产生聚类很简单的误解。在许多应用中,你的数据可能会大得多,甚至大到无法通过可视化感知(超过三维的数据对于人类来说是无法感知的)。在前面的例子中,你可以通过心算一些二维线条来将数据分成不同的组。而在更高维度时,你将需要计算机的帮助,找到一个适合分隔数据集的 n 维超平面。在实践中,这就是像 k-means 这样的聚类方法能够提供巨大价值的地方。
图 1.15:二维、三维和 n 维图
在接下来的练习中,我们将计算欧几里得距离。我们将使用NumPy
和Math
包。NumPy
是一个用于 Python 的科学计算包,它将常见的数学函数以高度优化的格式进行预打包。通过使用像NumPy
或Math
这样的包,我们可以减少从头编写自定义数学函数所花费的时间,从而专注于开发我们的解决方案。
练习 2:在 Python 中计算欧几里得距离
在这个练习中,我们将创建一个示例点以及三个样本中心点,以帮助说明欧几里得距离是如何工作的。理解这个距离公式是我们进行聚类工作的基础。
在本次练习结束时,我们将能够从零开始实现欧几里得距离,并完全理解它在特征空间中对点的作用。
在本次练习中,我们将使用标准的 Python 内置 math
包。使用 math
包没有任何前提要求,并且它包含在所有 Python 的标准安装中。顾名思义,这个包非常有用,允许我们直接使用各种基本的数学构件,如指数、平方根等:
-
打开 Jupyter Notebook,并创建一个天真的公式来计算欧几里得距离,具体如下:
import math import numpy as np def dist(a, b): return math.sqrt(math.pow(a[0]-b[0],2) + math.pow(a[1]-b[1],2))
这种方法被认为是天真的,因为它对数据点执行逐元素计算(慢),相比之下,使用向量和矩阵运算的更实际实现能够显著提高性能。
-
按如下方式在 Python 中创建数据点:
centroids = [ (2, 5), (8, 3), (4,5) ] x = (0, 8)
-
使用你创建的公式,计算示例点与所提供的三个质心之间的欧几里得距离:
centroid_distances =[] for centroid in centroids: centroid_distances.append(dist(x,centroid)) print(centroid_distances) print(np.argmin(centroid_distances))
输出如下:
[3.605551275463989, 9.433981132056603, 5.0] 0
由于 Python 是零索引的,列表中质心距离的零位置向我们表明,示例点 x 将被分配给三个质心中的第一个。
这个过程会对数据集中的每个点重复,直到每个点都被分配到一个聚类。每分配一个点后,会计算每个聚类中所有点的平均点。计算这些点的平均值与计算单个整数的平均值相同。
现在,既然你已经通过欧几里得距离作为主要度量方法在数据中找到了聚类,回想一下你是如何在 练习 2 中轻松完成这一任务的,在 Python 中计算欧几里得距离。对于我们的人类思维来说,看到图中的点群并确定哪些点属于不同的聚类是非常直观的。然而,我们如何让一个天真的计算机重复这一任务呢?通过理解这个练习,你帮助计算机学习一种通过距离来形成聚类的方法。我们将在下一个练习中继续使用这些距离度量。
练习 3:通过距离的概念形成聚类
通过理解这个练习,你将帮助计算机学习通过距离来形成聚类的方法。我们将在本次练习中继续使用这些距离度量:
-
存储分配给聚类一的点 [ (0,8), (3,8), (3,4) ]:
cluster_1_points =[ (0,8), (3,8), (3,4) ]
-
计算所有点的平均点以找到新的质心:
mean =[ (0+3+3)/3, (8+8+4)/3 ] print(mean)
输出如下:
[2.0, 6.666666666666667]
-
在计算出新的质心后,您将重复在练习 2中看到的聚类成员计算,即在 Python 中计算欧几里得距离,然后再进行前两步来找到新的聚类质心。最终,新的聚类质心将与进入问题时的质心相同,练习也将完成。重复的次数取决于您正在聚类的数据。
一旦您将质心位置移动到新的均值点(2, 6.67),可以将其与您进入问题时输入的初始质心列表进行比较。如果新的均值点与当前列表中的质心不同,这意味着您需要再执行前两个练习的迭代。直到您计算出的新的均值点与您开始时的质心相同,您就完成了 k-means 的一次运行,并达到了称为收敛的点。
在下一个练习中,我们将从头实现 k-means。
练习 4:从头实现 k-means
在这个练习中,我们将研究从头实现 k-means。这个练习依赖于 scikit-learn,一个开源的 Python 包,它使得快速原型化流行的机器学习模型成为可能。在 scikit-learn 中,我们将使用 datasets
功能来创建一个合成的簇数据集。除了利用 scikit-learn 的强大功能外,我们还将依赖 Matplotlib,这是一个流行的 Python 绘图库,它使得我们可以轻松地可视化数据。为此,请执行以下步骤:
-
导入必要的库:
from sklearn.datasets import make_blobs import matplotlib.pyplot as plt import numpy as np import math %matplotlib inline
-
生成一个随机的聚类数据集进行实验,X = 坐标点,y = 聚类标签,并定义随机质心:
X, y = make_blobs(n_samples=1500, centers=3, n_features=2, random_state=800) centroids = [[-6,2],[3,-4],[-5,10]]
-
打印数据:
X
输出如下:
array([[-3.83458347, 6.09210705], [-4.62571831, 5.54296865], [-2.87807159, -7.48754592], ..., [-3.709726 , -7.77993633], [-8.44553266, -1.83519866], [-4.68308431, 6.91780744]])
-
按如下方式绘制坐标点:
plt.scatter(X[:, 0], X[:, 1], s=50, cmap='tab20b') plt.show()
绘图如下所示:
图 1.16:坐标点的绘图
-
打印
y
数组:y
输出如下:
array([2, 2, 1, ..., 1, 0, 2])
-
按照正确的聚类标签绘制坐标点:
plt.scatter(X[:, 0], X[:, 1], c=y,s=50, cmap='tab20b') plt.show()
绘图如下所示:
图 1.17:带有正确聚类标签的坐标点绘图
练习 5:实现带优化的 k-means
让我们自己重新创建这些结果!我们将通过一个例子来实现这个过程,并进行一些优化。这个练习是在前一个练习的基础上构建的,应在同一个 Jupyter notebook 中进行。对于这个练习,我们将依赖 SciPy,一个 Python 包,它提供了对高效版本科学计算的便捷访问。特别是,我们将使用 cdist
实现欧几里得距离,该函数的功能以更高效的方式复制了我们距离度量的基本实现:
-
欧几里得距离的非向量化实现如下:
def dist(a, b): return math.sqrt(math.pow(a[0]-b[0],2) + math.pow(a[1]-b[1],2))
-
现在,实现优化的欧几里得距离:
from scipy.spatial.distance import cdist
-
存储 X 的值:
X[105:110]
输出如下:
array([[-3.09897933, 4.79407445], [-3.37295914, -7.36901393], [-3.372895 , 5.10433846], [-5.90267987, -3.28352194], [-3.52067739, 7.7841276 ]])
-
计算距离并选择最短距离的索引作为一个聚类:
for x in X[105:110]: calcs = [] for c in centroids: calcs.append(dist(x, c)) print(calcs, "Cluster Membership: ", np.argmin(calcs, axis=0))
-
定义
k_means
函数如下,并随机初始化 k 个质心。使用while
循环重复该过程,直到新旧centroids
之间的差值为0
:def k_means(X, K): # Keep track of history so you can see k-means in action centroids_history = [] labels_history = [] rand_index = np.random.choice(X.shape[0], K) centroids = X[rand_index] centroids_history.append(centroids) while True: # Euclidean distances are calculated for each point relative to # centroids, and then np.argmin returns the index location of the # minimal distance - which cluster a point is assigned to labels = np.argmin(cdist(X, centroids), axis=1) labels_history.append(labels) # Take mean of points within clusters to find new centroids new_centroids = np.array([X[labels == i].mean(axis=0) for i in range(K)]) centroids_history.append(new_centroids) # If old centroids and new centroids no longer change, k-means is # complete and end. Otherwise continue if np.all(centroids == new_centroids): break centroids = new_centroids return centroids, labels, centroids_history, labels_history centers, labels, centers_hist, labels_hist = k_means(X, 3)
注意
请不要破坏这段代码,因为这样可能会导致错误。
-
将中心的历史步骤及其标签压缩在一起:
for x, y in history: plt.figure(figsize=(4,3)) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='tab20b'); plt.scatter(x[:, 0], x[:, 1], c='red') plt.show()
第一张图如下所示:
图 1.18:第一次散点图
第二张图如下所示:
图 1.19:第二次散点图
第三张图如下所示:
图 1.20:第三次散点图
正如你在上面的图中看到的,K-means 采用迭代方法,通过距离不断精细化最佳聚类。该算法从随机初始化开始,根据数据的复杂性,迅速找到最合理的分隔。
聚类性能:轮廓分数
理解无监督学习方法的性能本质上比监督学习方法要困难得多,因为通常没有明确的“最佳”解决方案。对于监督学习,有许多可靠的性能指标——其中最直接的指标就是通过将模型预测标签与实际标签进行比较,并查看模型预测正确的数量来衡量准确度。不幸的是,对于聚类,我们没有标签可以依赖,需要建立对聚类“差异”的理解。我们通过轮廓分数(Silhouette Score)指标来实现这一点。这个方法的固有特性是,我们还可以使用轮廓分数来寻找无监督学习方法的最佳“K”聚类数量。
轮廓分数指标通过分析一个点在其聚类中的适配程度来工作。该指标的范围是从-1 到 1——如果你在聚类中计算的平均轮廓分数为 1,那么你将达成完美的聚类,且不会有任何混淆,知道哪个点属于哪个聚类。如果你想象我们上一个练习中的散点图,轮廓分数会接近 1,因为聚类之间的“球”非常紧凑,并且每个“球”之间有明显的距离。不过这种情况非常罕见——轮廓分数应视为尽力而为的结果,因为获得 1 的可能性极低。
从数学上讲,轮廓分数的计算通过简化的轮廓指数(SSI)非常简单,如下所示:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_05.png,其中 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_06.png 是点 i 到其所在聚类质心的距离,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_07.png 是点 i 到最近的聚类质心的距离。
这里捕捉到的直觉是,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_08.png表示点i的簇作为一个明确簇的凝聚度,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_01_Formula_07.png表示簇之间的距离。我们将使用 scikit-learn 中silhouette_score
的优化实现来完成活动 1,实现 k-means 聚类。使用它非常简单,只需传入特征数组和从 k-means 聚类方法中预测的簇标签。
在下一个练习中,我们将使用 pandas 库来读取 CSV。Pandas 是一个 Python 库,通过使用 DataFrame 使得数据处理变得更容易。要在 Python 中读取数据,你可以使用variable_name = pd.read_csv('file_name.csv', header=None)
。
练习 6:计算轮廓系数
在这个练习中,我们将学习如何计算一个数据集的轮廓系数(Silhouette Score),并且该数据集有一个固定数量的簇。为此,我们将使用 Iris 数据集,数据集可以在github.com/TrainingByPackt/Unsupervised-Learning-with-Python/tree/master/Lesson01/Exercise06
找到。
注意
这个数据集是从archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
下载的,可以通过github.com/TrainingByPackt/Unsupervised-Learning-with-Python/tree/master/Lesson01/Exercise06
访问。
-
使用 pandas 加载 Iris 数据文件,pandas 是一个通过使用 DataFrame 使数据处理变得更加容易的库:
import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import silhouette_score from scipy.spatial.distance import cdist iris = pd.read_csv('iris_data.csv', header=None) iris.columns = ['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm', 'species']
-
分离
X
特征,因为我们希望将其视为无监督学习问题:X = iris[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm']]
-
引入我们之前做的
k_means
函数作为参考:def k_means(X, K): #Keep track of history so you can see k-means in action centroids_history = [] labels_history = [] rand_index = np.random.choice(X.shape[0], K) centroids = X[rand_index] centroids_history.append(centroids) while True: # Euclidean distances are calculated for each point relative to # centroids, #and then np.argmin returns # the index location of the minimal distance - which cluster a point # is #assigned to labels = np.argmin(cdist(X, centroids), axis=1) labels_history.append(labels) #Take mean of points within clusters to find new centroids: new_centroids = np.array([X[labels == i].mean(axis=0) for i in range(K)]) centroids_history.append(new_centroids) # If old centroids and new centroids no longer change, k-means is # complete and end. Otherwise continue if np.all(centroids == new_centroids): break centroids = new_centroids return centroids, labels, centroids_history, labels_history
-
将我们的 Iris
X
特征 DataFrame 转换为NumPy
矩阵:X_mat = X.values
-
在 Iris 矩阵上运行我们的
k_means
函数:centroids, labels, centroids_history, labels_history = k_means(X_mat, 3)
-
计算
PetalLengthCm
和PetalWidthCm
列的轮廓系数(Silhouette Score):silhouette_score(X[['PetalLengthCm','PetalWidthCm']], labels)
输出结果类似于:
0.6214938502379446
在这个练习中,我们计算了 Iris 数据集的PetalLengthCm
和PetalWidthCm
列的轮廓系数。
活动 1:实现 k-means 聚类
情景:在面试中,你被要求从零实现一个 k-means 聚类算法,以证明你理解其工作原理。我们将使用 UCI ML 库提供的 Iris 数据集。Iris 数据集是数据科学界的经典,具有用于预测鸢尾花物种的特征。下载链接将在后面的活动中提供。
对于这个活动,你可以使用 Matplotlib、NumPy、scikit-learn 指标和 pandas。
通过轻松加载和重塑数据,你可以更多地专注于学习 k-means,而不是编写数据加载器功能。
Iris 数据列如下所示,供参考:
['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm', 'species']
目标:要真正理解某个事物如何运作,您需要从头开始构建它。将您在前面的章节中学到的知识付诸实践,并在 Python 中从零开始实现 k-means。
请打开您最喜欢的编辑平台并尝试以下内容:
-
使用
NumPy
或math
包及欧几里得距离公式,编写一个计算两个坐标之间距离的函数。 -
编写一个函数,计算数据集中每个点到质心的距离,并返回簇的成员身份。
-
编写一个 k-means 函数,接受一个数据集和簇的数量(K),并返回最终的聚类质心以及构成该聚类的成员数据点。在从零开始实现 k-means 后,将您的自定义算法应用到鸢尾花数据集,数据集位置如下:
github.com/TrainingByPackt/Unsupervised-Learning-with-Python/tree/master/Lesson01/Activity01
。注意
这个数据集是从
archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
下载的。可以在github.com/TrainingByPackt/Unsupervised-Learning-with-Python/tree/master/Lesson01/Activity01
访问。UCI 机器学习库 [
archive.ics.uci.edu/ml
]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。 -
移除此数据集中提供的类别,看看您的 k-means 算法能否仅根据植物特征将不同的鸢尾花物种分到正确的组别!
-
使用 scikit-learn 实现计算轮廓系数。
结果:通过完成这个练习,您将获得调优 k-means 聚类算法以适应真实世界数据集的实践经验。鸢尾花数据集被视为数据科学领域经典的“hello world”问题,适合用于测试基础技术。您的最终聚类算法应该能够较好地找到数据中存在的三类鸢尾花物种,具体如下:
图 1.21:鸢尾花物种的三类聚类期望图
注意
此活动的解决方案可以在第 306 页找到。
总结
在本章中,我们探讨了聚类的定义以及它在各种数据挑战中的重要性。基于这一聚类知识的基础,您实现了 k 均值算法,这是一种最简单但也最流行的无监督学习方法之一。如果您能够在这里总结并能够逐步向您的同学解释 k 均值算法的操作步骤,那么干得漂亮!如果不能,请返回并复习之前的材料——从这里开始,内容将变得更加复杂。接下来,我们将转向层次聚类,其中的一种配置重复使用了我们在 k 均值中使用的质心学习方法。在下一章中,我们将进一步阐述其他聚类方法和方法。
第二章:层次聚类
学习目标
到本章结束时,你将能够:
-
从头开始实现层次聚类算法,使用软件包
-
执行聚合聚类
-
比较 k-means 和层次聚类
在这一章中,我们将使用层次聚类来构建更强的分组,这些分组在逻辑上更合理。
介绍
在这一章中,我们将在第一章《聚类介绍》中构建的基本思想基础上扩展,通过将聚类与相似性的概念结合起来。我们将再次实现欧几里得距离的各种形式来捕捉相似性这一概念。需要记住的是,欧几里得距离只是最流行的距离度量之一,而不是唯一的度量!通过这些距离度量,我们将通过引入层次结构的概念,扩展我们在上一章中探索的简单邻居计算。通过使用层次结构传递聚类信息,我们可以构建出更强的、在逻辑上更合理的分组。
聚类复习
第一章《聚类介绍》涵盖了最基础聚类算法之一:k-means 的高层次直觉和深入细节。虽然这确实是一种简单的方法,但不要小看它;它将是你继续探索无监督学习世界时,工具箱中的一个宝贵补充。在许多实际应用场景中,公司通过最简单的方法,比如 k-means 或线性回归(监督学习)取得了突破性的发现。作为复习,我们快速回顾一下什么是聚类以及 k-means 如何找到它们:
](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_02_01.jpg)
图 2.1:区分监督学习和无监督学习问题的属性
如果你被给了一组没有任何指导的随机数据,你可能会开始使用基本统计方法进行探索——例如,求出每个特征的均值、中位数和众数。记住,从一个简单存在的高层次数据模型来看,是否是监督学习或无监督学习是由你为自己设定的目标或由经理设定的目标决定的。如果你确定其中一个特征实际上是标签,并且你想查看数据集中其余特征对它的影响,那么这将成为一个监督学习问题。然而,如果在初步探索后你意识到你所拥有的数据实际上只是没有目标的特征集合(例如一组健康指标、网店的购买发票等),那么你可以通过无监督方法来分析它。
无监督学习的一个经典例子是在来自网店发票的集合中找到相似客户的簇。您的假设是通过了解哪些人最相似,您可以创建更细粒度的营销活动,以迎合每个簇的兴趣。实现这些相似用户簇的一种方式是通过 k-means。
k-means 刷新
k-means 聚类是通过在您的数据中找到“k”个簇来工作,通过成对的欧氏距离计算。“K”点(也称为质心)在您的数据中随机初始化,并且计算每个数据点到每个质心的距离。这些距离的最小值指定了数据点属于哪个簇。一旦每个点都被分配到一个簇中,就会计算出簇内数据点的平均值作为新的质心。这个过程重复进行,直到新计算出的簇质心不再改变位置。
层次结构的组织
自然界和人造世界都包含许多将系统组织成层次结构的例子,大多数情况下这是很有道理的。从这些层次结构中开发出的常见表示可以在基于树的数据结构中看到。想象一下,你有一个父节点和任意数量的子节点,这些子节点随后可以成为自己的父节点。通过将概念组织成树形结构,您可以构建一个信息密集的图表,清晰地显示事物与其同行及其更大抽象概念的关系。
一个帮助说明这一概念的自然界的例子可以在如何看待动物的层次结构中看到,这些层次结构从父类到个体物种:
图 2.2: 在层次树结构中导航动物物种的关系
在图 2.2 中,您可以看到动物品种之间关系信息的一个示例,以一种既节省空间又传递大量信息的方式进行映射。这个例子可以看作是其自身的一棵树(显示猫和狗如何不同但都是驯养动物),也可以看作是一个更大树的一部分,显示驯养与非驯养动物的分解。
假设大多数人不是生物学家,让我们回到一个销售产品的网店的概念。如果你销售多种产品,那么你可能希望为客户创建一个层次化的导航系统。通过仅展示产品目录中的所有信息,客户将只暴露于与其兴趣相匹配的树路径。层次化导航的好处的一个示例可以在图 2.3 中看到:
图 2.3:在层次树结构中导航产品类别
显然,层级导航系统的优势在改善客户体验方面是无法过分强调的。通过将信息组织成层级结构,你可以从数据中构建一个直观的结构,展示明确的嵌套关系。如果这听起来像是另一种在数据中寻找聚类的方法,那么你绝对走在正确的轨道上!通过使用类似欧氏距离这样的距离度量方法(如 k-means 中的欧氏距离),我们可以开发出一棵树,展示数据的许多切割点,允许用户根据需要主观地创建聚类。
分层聚类简介
直到这一点为止,我们已经展示了层级结构可以作为一个很好的组织信息的方式,清晰地展示了数据点之间的嵌套关系。虽然这在理解项目之间的父子关系时非常有帮助,但在形成聚类时也非常有用。以前一节的动物示例为扩展,假设你仅仅获得了两种动物的特征:它们的身高(从鼻尖到尾端的长度)和体重。利用这些信息,你需要重新构建相同的结构,以便识别数据集中哪些记录对应于狗或猫,并区分它们的亚种。
由于你仅仅获得了动物的身高和体重信息,你无法推断出每个物种的具体名称。然而,通过分析你所得到的特征,你可以在数据中构建一个结构,用以近似表示数据中存在的动物物种。这为无监督学习问题提供了一个理想的背景,而分层聚类则是一个非常适合解决该问题的方法。在下面的图表中,你将看到我们在左侧创建的两个特征:左栏是动物的身高,右栏是动物的体重。接着,这些数据被绘制在一个二维坐标图上,身高为 x 轴,体重为 y 轴:
图 2.4:一个包含动物身高和体重的二维特征数据集示例
一种进行分层聚类的方法是从每个数据点作为其自身的聚类开始,并递归地将相似的点结合在一起形成聚类——这被称为凝聚型分层聚类。我们将在后面的章节中详细介绍不同的分层聚类方法。
在凝聚型层次聚类方法中,数据点相似性的概念可以在我们在 k-means 中看到的范式下进行思考。在 k-means 中,我们使用欧几里得距离来计算个体点到预期“k”簇的质心之间的距离。对于这种层次聚类方法,我们将重新使用相同的距离度量来确定数据集中记录之间的相似性。
最终,通过递归地将数据中每个记录与其最相似的记录进行组合,您会从底部构建一个层次结构。所有单成员簇将汇聚成一个顶层簇,形成层次结构的最上层。
执行层次聚类的步骤
为了理解凝聚型层次聚类的工作原理,我们可以通过一个简单的示例程序追踪它如何合并并形成一个层次结构:
-
给定 n 个样本数据点,将每个点视为一个单独的“簇”,其成员仅为该点。
-
计算所有簇的质心之间的成对欧几里得距离。
-
将最接近的点对聚集在一起。
-
重复步骤 2 和 步骤 3,直到您得到一个包含所有数据的单一簇。
-
绘制一个树状图,显示您的数据如何以层次结构的形式汇聚在一起。树状图只是用来表示树形结构的图表,展示簇从上到下的排列。
-
决定要在哪个层级创建簇。
分层聚类的示例演示
尽管比 k-means 稍微复杂一些,层次聚类在逻辑上并没有太大变化。以下是一个简单的示例,通过稍微详细的步骤演示前述过程:
-
给定四个样本数据点的列表,将每个点视为一个质心,该质心也是它自身的簇,点的索引为 0 到 3:
簇(4):[ (1,7) ], [ (-5,9) ], [ (-9,4) ] , [ (4, -2) ]
质心(4):[ (1,7) ], [ (-5,9) ], [ (-9,4) ] , [ (4, -2) ]
-
计算所有簇的质心之间的成对欧几里得距离。在下图显示的矩阵中,点的索引在水平方向和垂直方向上都介于 0 和 3 之间,表示各自点之间的距离。沿对角线的值极高,以确保我们不会将一个点选为其自身的邻居(因为它在技术上是“最接近”的点)。请注意,这些值在对角线两侧是对称的:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_02_05.jpg
图 2.5:距离数组
-
将最接近的点对聚集在一起。
在这种情况下,点[1,7]和[-5,9]由于最接近而合并成一个簇,其余两个点则保持为单成员簇:
图 2.6:距离数组
这里是最终得到的三个簇:
[ [1,7], [-5,9] ] [-9,4] [4,-2]
-
计算两个成员簇的质心,如下所示:
mean([ [1,7], [-5,9] ]) = [-2,8]
-
将聚类中心添加到两个单一成员的聚类中心,并重新计算距离。
聚类 (3):
[ [1,7], [-5,9] ] [-9,4] [4,-2]
聚类中心 (3):
[-2,8] [-9,4] [4,-2]
输出将类似于以下图示,最短距离通过红色箭头标示:
图 2.7:距离数组
-
由于它具有最短的距离,点[-9,4]被添加到聚类 1:
聚类 (2):
[ [1,7], [-5,9], [-9,4] ] [4,-2]
-
由于只有点(4,-2)与其邻近点的距离最远,你可以将它添加到聚类 1,从而统一所有聚类:
聚类 (1):
[ [ [1,7], [-5,9], [-9,4], [4,-2] ] ]
-
绘制树状图以展示点与聚类之间的关系:
图 2.8:展示点与聚类之间关系的树状图
在这个过程的最后,你可以通过树状图可视化你创建的层次结构。这个图展示了数据点的相似性,看起来与我们之前讨论的层次树状结构相似。一旦你拥有了这个树状图结构,你可以解释数据点之间的关系,并主观决定聚类应存在于哪个“层级”。
回顾之前涉及狗和猫物种的动物分类示例,假设你面临以下树状图:
图 2.9:一个动物分类的树状图
层次聚类和树状图的优点在于,你可以看到所有可能的聚类拆分。如果你只对将物种数据集分为狗和猫感兴趣,你可以在分组的第一级停止聚类。然而,如果你想将所有物种分为家养动物和非家养动物,你可以在第二级停止聚类。
练习 7:构建层次结构
让我们尝试在 Python 中实现前述的层次聚类方法。通过为直觉打下框架,现在我们可以探索如何使用SciPy
提供的辅助函数构建层次聚类。这个练习使用了SciPy
,这是一个开源库,提供了许多有助于科学和技术计算的函数;例如,它简化了线性代数和微积分相关方法的实现。除了SciPy
,我们还将使用 Matplotlib 来完成这个练习:
-
生成如下虚拟数据:
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster from sklearn.datasets import make_blobs import matplotlib.pyplot as plt %matplotlib inline # Generate a random cluster dataset to experiment on. X = coordinate points, y = cluster labels (not needed) X, y = make_blobs(n_samples=1000, centers=8, n_features=2, random_state=800)
-
可视化数据如下:
plt.scatter(X[:,0], X[:,1]) plt.show()
输出结果如下:
图 2.10:虚拟数据的绘图
在绘制这个简单的示例数据之后,应该很清楚我们的虚拟数据包含了八个聚类。
-
我们可以通过内置的
SciPy
包轻松生成距离矩阵,使用’linkage
’:# Generate distance matrix with 'linkage' function distances = linkage(X, method="centroid", metric="euclidean") print(distances)
输出结果如下:
图 2.11:距离的矩阵
在第一种情况中,你可以看到,定制超参数确实会影响找到理想链接矩阵的性能。如果你回顾我们之前的步骤,链接的工作原理是通过计算每个数据点之间的距离来完成的。在
linkage
函数中,我们可以选择度量标准和方法(稍后我们会详细讲解)。 -
在我们确定了链接矩阵后,可以轻松地将其传递给
SciPy
提供的树状图函数:dn = dendrogram(distances) plt.show()
输出如下:
图 2.12:距离的树状图
这个图将帮助我们更好地理解数据的潜在分组情况。
-
使用这些信息,我们可以通过使用
SciPy
的fcluster
函数来完成我们的层次聚类练习。公式中的数字3
在这里表示 -
以下示例表示你将设置的最大簇间距离阈值超参数。这个超参数可以根据你所使用的数据集进行调整;然而,对于本次练习,它的默认值为
3
:scipy_clusters = fcluster(distances, 3, criterion="distance") plt.scatter(X[:,0], X[:,1], c=scipy_clusters) plt.show()
输出如下:
图 2.13:距离的散点图
只需调用 SciPy
提供的几个辅助函数,你就可以轻松地在几行代码中实现聚合聚类。尽管 SciPy
在许多中间步骤中提供了帮助,但这个例子仍然稍显冗长,可能不是你在日常工作中会遇到的情况。我们将在稍后的部分介绍更加简洁的实现方式。
链接
在练习 7,构建层次结构中,你使用了被称为质心链接的层次聚类方法。链接是指确定如何计算簇间距离的概念,这取决于你所面临的问题类型。选择质心链接是因为它本质上与我们在 k-means 中使用的新的质心搜索方法相似。然而,在将数据点聚类时,这并不是唯一的选择。另两种常见的用于计算簇间距离的方法是单链接和完全链接。
单链接通过找到两个簇之间一对点的最小距离来确定链接标准。简单来说,它通过基于两簇之间最接近的点来合并簇。数学表达式如下:
dist(a,b) = min( dist( a[i]), b[j] ) )
完全链接与单链接相反,它通过找到两个簇之间一对点的最大距离来确定链接标准。简单来说,它通过基于两簇之间最远的点来合并簇。数学表达式如下:
dist(a,b) = max( dist( a[i]), b[j] ) )
确定哪种连接标准最适合你的问题既是艺术也是科学,并且极大依赖于你特定的数据集。选择单一连接的一种原因是你的数据在邻近点上非常相似,因此,当存在差异时,这些数据就会表现出极大的不同。由于单一连接通过找到最接近的点来工作,因此它不会受到这些远离点的影响。相反,如果你的数据在类间较远,但类内相对密集,那么完全连接可能是一个更好的选择。质心连接有类似的优点,但如果数据非常嘈杂且聚类的“中心”不明确,它可能会失效。通常,最好的方法是尝试几种不同的连接标准选项,看看哪种最符合你的数据,并与目标最相关。
活动 2:应用连接标准
回想一下我们在上一练习中生成的八个聚类的虚拟数据。在现实世界中,你可能会获得类似的实际数据,这些数据表现得像离散的高斯“团块”。假设这些虚拟数据代表了某个特定商店中不同的顾客群体。商店经理要求你分析顾客数据,将顾客分类成不同的群体,以便根据每个群体的特点定制营销材料。
使用在上一练习中已经生成的数据,或生成新数据,你将分析哪些连接方式最适合将顾客分为不同的群体。
一旦你生成了数据,使用 SciPy 提供的文档来查看linkage
函数中可用的连接类型。然后,通过将它们应用到你的数据中来评估这些连接类型。你应该测试的连接类型在以下列表中:
['centroid', 'single', 'complete', 'average', 'weighted']
完成这个活动后,你将理解连接标准——了解它对于你的层次聚类效果至关重要。目标是理解连接标准在不同数据集中的作用,以及它如何将一个无效的聚类转变为一个有效的聚类。
你可能会发现我们没有涵盖所有之前提到的连接类型——这项活动的关键部分是学习如何解析包提供的文档字符串,以探索它们的所有功能。
以下是完成此活动所需的步骤:
-
可视化我们在练习 7中创建的数据集,构建层次结构。
-
创建一个包含所有可能连接方法超参数的列表。
-
遍历你刚刚创建的列表中的每个方法,并展示它们对相同数据集的影响。
你应该为每种连接方式生成一个图表,并利用这些图表评论哪种连接方式最适合该数据。
你将生成的图表应该类似于下图所示:
图 2.14:所有方法的预期散点图
注意
这个活动的解决方案在第 310 页。
聚合式与分裂式聚类
迄今为止,我们的层次聚类实例都是聚合式的——也就是说,它们是从下往上构建的。虽然这种方法通常是这种类型聚类中最常见的做法,但重要的是要知道,它并不是创建层次结构的唯一方式。相反的层次方法,即从上往下构建,也可以用于创建分类法。这个方法叫做分裂式层次聚类,它的工作原理是将数据集中所有的数据点放在一个大的聚类中。分裂式方法的许多内部机制与聚合式方法非常相似:
](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_02_15.jpg)
图 2.15:聚合式与分裂式层次聚类
与大多数无监督学习中的问题一样,选择最佳方法通常高度依赖于你所面临的具体问题。
想象一下,你是一位刚刚购买了一家新杂货店的企业家,需要为商店进货。你收到了一个装满食物和饮料的大货柜,但你已经丢失了所有的货物信息!为了最有效地销售商品,你必须将类似的商品分组(如果你把所有东西随机摆放到货架上,商店将会是一团糟)。为了解决这个组织问题,你可以采取自下而上或自上而下的方法。从自下而上的角度,你会将整个运输容器视为杂乱无章——然后,你会拿起一个随机的物品,并找到它最相似的商品。例如,你可能会拿起苹果汁,意识到将它与橙汁放在一起是合理的。采用自上而下的方法,你会将所有物品视为一个大组。然后,你会遍历库存,并根据最大差异将这些组拆分开来。例如,你最初可能会认为苹果汁和豆腐是搭配的,但仔细想想,它们实际上非常不同。因此,你会把它们分成更小、不相似的组。
一般来说,帮助理解聚合方法是自下而上的方式,而分裂方法是自上而下的方式——但它们在性能上如何权衡呢?由于聚合方法的贪婪性质,它可能会被局部邻居所迷惑,无法看到任何时候形成的聚类的更大意义。另一方面,分裂方法的好处在于,它从一开始就能看到整个数据分布,并选择最佳的方式来拆分聚类。了解整个数据集的分布有助于潜在地创建更准确的聚类,这一点不容忽视。不幸的是,通常情况下,顶层的分裂方法会以更深的复杂度来交换更高的准确度。在实际应用中,聚合方法通常能正常工作,并且在层次聚类中应该是优先的起始点。如果在检查层次结构后,你对结果不满意,可以考虑使用分裂方法。
练习 8:使用 scikit-learn 实现聚合层次聚类
在大多数实际应用中,你可能会发现自己在使用一个将所有内容抽象化的包来实现层次聚类,比如 scikit-learn。Scikit-learn 是一个免费且不可或缺的 Python 机器学习包。它便捷地提供了许多流行算法的高度优化版本,如回归、分类和聚类等。通过使用像 scikit-learn 这样的优化包,你的工作变得更加轻松。然而,只有在你完全理解了前面章节中层次聚类的工作原理后,才应使用它。以下练习将比较两种形成聚类的潜在方法——使用 SciPy 和 scikit-learn。通过完成这个练习,你将了解它们各自的优缺点,以及从用户角度看,哪种方法最适合你:
-
Scikit-learn 使得实现变得非常简单,只需几行代码:
from sklearn.cluster import AgglomerativeClustering from sklearn.datasets import make_blobs import matplotlib.pyplot as plt from scipy.cluster.hierarchy import linkage, dendrogram, fcluster ac = AgglomerativeClustering(n_clusters = 8, affinity="euclidean", linkage="average") X, y = make_blobs(n_samples=1000, centers=8, n_features=2, random_state=800) distances = linkage(X, method="centroid", metric="euclidean") sklearn_clusters = ac.fit_predict(X) scipy_clusters = fcluster(distances, 3, criterion="distance")
首先,我们通过传入我们熟悉的参数,如
affinity
(距离函数)和linkage
(如同我们在活动 2中所做的那样,探索你的选项,实现连接准则),将模型分配给ac
变量。 -
在实例化模型到变量后,我们可以简单地传入我们感兴趣的数据集,并使用
.fit_predict()
来确定聚类的归属,并将结果分配给另一个变量。 -
然后,我们可以通过绘图比较每种方法的最终聚类结果,看看 scikit-learn 方法的聚类:
plt.figure(figsize=(6,4)) plt.title("Clusters from Sci-Kit Learn Approach") plt.scatter(X[:, 0], X[:, 1], c = sklearn_clusters ,s=50, cmap='tab20b') plt.show()
以下是 scikit-learn 方法聚类的输出:
图 2.16:Scikit-Learn 方法的图示
看看 SciPy 学习方法的聚类:
plt.figure(figsize=(6,4))
plt.title("Clusters from SciPy Approach")
plt.scatter(X[:, 0], X[:, 1], c = scipy_clusters ,s=50, cmap='tab20b')
plt.show()
输出如下:
图 2.17:SciPy 方法的图示
正如你在我们的示例问题中看到的,两个方法最终收敛到了基本相同的聚类。虽然从玩具问题的角度来看这很好,但你很快会在下一个活动中学到,输入参数的微小变化可能会导致完全不同的结果!
活动 3:比较 k-means 和层次聚类
你正在管理一家商店的库存,并收到一大批葡萄酒,但在运输过程中瓶上的品牌标签掉了。幸运的是,供应商提供了每瓶酒的化学成分数据及其对应的序列号。不幸的是,你不能打开每一瓶酒进行品尝测试,你必须找到一种方法,根据化学成分将未标记的瓶子重新分组!你知道从订单列表中,自己订购了三种不同类型的葡萄酒,并且只给出了两种葡萄酒属性来将它们重新分组。在这个活动中,我们将使用葡萄酒数据集。
注意
葡萄酒数据集可以从 archive.ics.uci.edu/ml/machine-learning-databases/wine/
下载。也可以通过 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson02/Activity03
访问。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院
本活动的目的是在葡萄酒数据集上实现 k-means 和层次聚类,并探索哪种方法最终更准确或更易于使用。你可以尝试不同的 scikit-learn 实现组合,并使用 SciPy 和 NumPy 中的辅助函数。你可以使用轮廓系数(silhouette score)比较不同的聚类方法,并在图表上可视化聚类结果。
预期结果:
完成此活动后,你将理解 k-means 和层次聚类在相似数据集上的工作原理。你可能会注意到,根据数据的形状,一个方法比另一个方法表现得更好。此活动的另一个关键收获是理解在任何给定用例中超参数的重要性。
以下是完成此活动的步骤:
-
从 scikit-learn 导入必要的包(
KMeans
、AgglomerativeClustering
和silhouette_score
)。 -
将葡萄酒数据集读入 pandas DataFrame 并打印一个小样本。
-
可视化葡萄酒数据集,以理解其数据结构。
-
使用 sklearn 实现的 k-means 算法处理葡萄酒数据集,已知有三种葡萄酒类型。
-
在葡萄酒数据集上使用 sklearn 实现的层次聚类。
-
绘制 k-means 聚类的预测结果。
-
绘制来自层次聚类的预测聚类。
-
比较每种聚类方法的轮廓系数。
如下所示,绘制来自 k-means 聚类方法的预测聚类:
图 2.18:k-means 方法的预期聚类
如下所示,绘制来自凝聚层次聚类方法的预测聚类:
图 2.19:来自凝聚方法的预期聚类
注意
本活动的解决方案见第 312 页。
k-means 与层次聚类
现在我们已经扩展了对 k-means 聚类如何工作的理解,接下来要探讨的是层次聚类在整个过程中所扮演的角色。如在连接准则部分所提到的,当使用质心进行数据点分组时,确实存在一些直接重叠的可能性。到目前为止,所有提到的方法都通用地使用了距离函数来确定相似度。由于我们在上一章的深入探讨,我们一直使用欧几里得距离,但我们理解,任何距离函数都可以用来确定相似度。
实际操作中,选择某种聚类方法而非另一种方法时,有一些快速的要点:
-
层次聚类的优势在于不需要预先传入明确的“k”聚类数。这意味着你可以在算法完成后找到所有潜在的聚类,并决定哪些聚类最为合理。
-
从简化的角度来看,k-means 聚类有其优势——在商业应用中,往往需要找到既能向非技术观众解释,又能生成高质量结果的方法。k-means 可以轻松地填补这一空白。
-
层次聚类在处理形状异常的数据时,比 k-means 聚类有更多的参数可供调整。虽然 k-means 非常擅长发现离散聚类,但当涉及到混合聚类时,它可能会出现问题。通过调整层次聚类中的参数,你可能会得到更好的结果。
-
Vanilla k-means 聚类通过初始化随机质心并找到与这些质心最接近的点来工作。如果它们在特征空间中被随机初始化在离数据很远的地方,那么可能需要相当长的时间才能收敛,或者甚至可能永远无法到达那个点。层次聚类则不太容易受到这种弱点的影响。
总结
本章中,我们讨论了层次聚类的工作原理以及它最适合应用的场景。特别是,我们讨论了如何通过评估树状图(dendrogram)图来主观选择聚类的各个方面。如果你完全不知道数据中要寻找什么,这相较于 k-means 聚类来说是一个巨大的优势。我们还讨论了驱动层次聚类成功的两个关键参数:聚合法和分裂法,以及连接准则。聚合法聚类采用自下而上的方法,通过递归地将相邻的数据组合在一起,直到形成一个大簇。而分裂法聚类则采用自上而下的方法,从一个大簇开始,递归地将其拆分,直到每个数据点都属于自己的簇。分裂法聚类由于从一开始就能完整地看到数据,具有更高的准确性;然而,它增加了一层复杂性,可能会降低稳定性并增加运行时间。
连接准则处理的是如何计算候选簇之间距离的概念。我们已经探讨了质心如何在超越 k-means 聚类后再次出现,以及单链和完全链连接准则。单链连接通过比较每个簇中最接近的点来寻找簇间的距离,而完全链连接则通过比较每个簇中更远的点来寻找簇间的距离。从你在本章中获得的理解来看,你现在能够评估 k-means 聚类和层次聚类如何最好地解决你所面临的挑战。在下一章中,我们将介绍一种在高度复杂数据中最适合我们的聚类方法:DBSCAN(基于密度的空间聚类噪声应用)。
第三章:邻域方法与 DBSCAN
学习目标
在本章结束时,你将能够:
-
理解邻域方法在聚类中的工作原理,从头到尾
-
从头实现 DBSCAN 算法,使用相关软件包
-
从 k-means、层次聚类和 DBSCAN 中选择最适合的算法来解决你的问题
本章我们将介绍 DBSCAN 聚类方法,这对于处理高度复杂的数据最为适用。
介绍
到目前为止,我们已经介绍了两种流行的聚类方法:k-means 和层次聚类。每种聚类技术在实施时都有优缺点。让我们再次回顾前两章的内容,以便为本章的内容提供更好的背景。
在无监督学习的挑战领域中,你将会面临一组特征数据,但没有补充标签告诉你这些特征变量的具体含义。尽管你无法直接得知目标标签是什么,但你可以通过将相似的群体聚集在一起,查看组内的相似性,来从数据中挖掘出一定的结构。我们之前讲解的聚类相似数据点的第一个方法是 k-means。
k-means 最适用于简单的数据挑战,其中速度至关重要。通过简单地查看最接近的数据点,计算开销不大,但当面对高维数据集时,挑战也会增大。如果你不知道可能需要寻找多少个聚类,k-means 也不适合使用。在第二章,层次聚类中,我们曾经探讨过通过化学分析数据来判断哪些葡萄酒属于同一类别。这一练习之所以有效,是因为你知道有三种类型的葡萄酒已经被订购。然而,如果你对最初的排序没有任何直觉,k-means 的效果会大打折扣。
我们探讨的第二种聚类方法是层次聚类。这种方法可以通过两种方式工作——合并式或分裂式。合并式聚类采用自下而上的方式,将每个数据点视为一个独立的簇,并通过连接准则递归地将它们聚集在一起。分裂式聚类则朝相反方向工作,它将所有数据点视为一个大的类别,并递归地将其拆分为更小的簇。这种方法的优势在于能够全面理解整个数据分布,因为它计算了分割的潜力;然而,由于其更高的复杂性,通常在实际操作中并不常用。在不知道数据任何信息的情况下,层次聚类是聚类需求的有力竞争者。通过使用树状图,你可以可视化数据中的所有分割,并在事后考虑哪个簇的数量更合理。在你的特定使用案例中,这非常有帮助;然而,它也带来了与 k 均值聚类相似的较高计算成本。
在本章中,我们将介绍一种在高度复杂数据中最适合的聚类方法:DBSCAN(基于密度的空间聚类与噪声)。经典地,这种方法一直被认为在数据集密集分布的情况下表现优秀。让我们一起探讨它为什么在这些用例中表现如此出色。
聚类作为邻域
在前两章中,我们探讨了相似性的概念,这一概念通过欧几里得距离来描述——与某一点较近的数据点可以视为相似,而在欧几里得空间中距离较远的数据点则可以视为不相似。这一概念在 DBSCAN 算法中再次出现。正如其冗长的名称所暗示的,DBSCAN 方法通过引入密度的概念,扩展了基本的距离度量评估。如果一群数据点都位于彼此相同的区域中,它们可以被视为同一簇的成员:
图 3.1:邻居与聚类有直接的联系
在前面的示例中,我们可以看到四个邻域。
与我们之前讨论过的仅关注距离的传统方法相比,基于密度的方法有许多优势。如果你仅将距离作为聚类的阈值,那么在面对稀疏特征空间和离群点时,可能会发现你的聚类结果毫无意义。无论是 k 均值聚类还是层次聚类,都会自动将空间中的所有数据点分组,直到没有剩余点为止。
虽然层次聚类确实在某种程度上绕过了这个问题,因为你可以在聚类后使用树状图来指定聚类的形成位置,但 k-means 仍然是最容易失败的,因为它是最简单的聚类方法。当我们开始评估基于邻域的聚类方法时,这些问题就不那么明显了:
图 3.2:示例树状图
通过在 DBSCAN 中引入邻居密度的概念,我们可以根据运行时选择的超参数,选择是否将异常值排除在聚类之外。只有具有密切邻居的数据点才会被视为同一聚类的成员,而那些距离较远的数据点则可以被视为未聚类的异常值。
DBSCAN 简介
正如前一节所提到的,当我们分析基于密度的聚类方法的优势时,DBSCAN 的强大之处就显现出来了。DBSCAN 将密度评估为邻域半径和在邻域中找到的最小点数的组合,这些点数被视为一个聚类。
如果我们重新考虑你被要求为商店整理一批未标记的葡萄酒货物的场景,可能更容易理解这个概念。在之前的示例中,已经明确说明,我们可以根据葡萄酒的特征(如科学化学特性)找到相似的葡萄酒。了解这些信息后,我们可以更轻松地将相似的葡萄酒分组,并且快速将产品整理好以供销售。希望到现在为止这一点已经清楚了——但可能不太清楚的是,你为商店订购的商品通常反映了现实世界的购买模式。为了在库存中促进品种多样性,但又能保证最受欢迎的葡萄酒有足够的库存,你的商品种类往往会呈现出高度不均衡的分布。大多数人喜欢经典的葡萄酒,如白葡萄酒和红葡萄酒,但你可能仍会为那些喜欢昂贵葡萄酒的顾客提供更多异国情调的葡萄酒。这使得聚类更加困难,因为存在不均衡的类别分布(例如,你不会订购每种葡萄酒各 10 瓶)。
DBSCAN 与 k-means 和层次聚类的不同之处在于,你可以将直觉融入到如何评估我们感兴趣的顾客聚类的过程中。它可以以更简单的方式去除噪声,并且仅指出那些在营销活动中具有最高潜力的顾客。
通过基于邻域的聚类方法,我们可以区分出那些可以视为随机噪声的偶尔顾客,以及那些一次次光顾我们商店的更有价值的顾客。这种方法自然会引发关于如何确定邻域半径和每个邻域最小点数的最佳数值的问题。
作为一种高级启发式方法,我们希望将邻域半径设置得较小,但又不能太小。在极端的一端,你可以将邻域半径设置得非常大——这可能导致将所有点视为一个庞大的聚类。而在另一端,你可以将邻域半径设置得非常小。过小的邻域半径可能导致没有任何点被聚集在一起,并且出现大量单一成员的聚类。
类似的逻辑适用于构成聚类的最小点数。最小点数可以看作是一个次要阈值,它根据你数据空间中可用的数据来调整邻域半径。如果你在特征空间中的所有数据非常稀疏,最小点数会变得尤为重要,它与邻域半径配合使用,以确保不会只是大量无关的数据点。当数据非常密集时,最小点数阈值就不像邻域半径那样成为主导因素。
如你所见,这两个超参数规则的最佳选择通常依赖于数据集的具体情况。很多时候,你需要找到一个“恰到好处”的区间,即超参数既不过小,也不过大。
DBSCAN 深入解析
为了观察 DBSCAN 如何工作,我们可以通过一个简单的示例程序,跟踪其如何合并形成不同的聚类和噪声标记数据点:
-
给定 n 个未访问的样本数据点,在循环中依次遍历每个点并标记为已访问。
-
从每个点出发,查看与数据集中其他所有点的距离。
-
对于所有位于邻域半径超参数内的点,将它们连接为邻居。
-
检查邻居的数量是否至少达到所需的最小点数。
-
如果达到最小点数阈值,将点归为一个聚类。如果没有,将该点标记为噪声。
-
重复此过程,直到所有数据点被分类到聚类中或标记为噪声。
在某些方面,DBSCAN 算法相对简单——虽然引入了通过邻域半径和最小点数来衡量密度的新概念,但它的核心仍然是使用距离度量进行评估。
DBSCAN 算法演示
这是一个简单的示例,稍微详细地演示了前述步骤:
-
给定四个样本数据点,将每个点视为一个独立的聚类 [ (1,7) ]、[ (-8,6) ]、[ (-9,4) ] 、[ (4, -2) ]:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_03_03.jpg
图 3.3:样本数据点的绘制
-
计算每一对点之间的欧几里得距离:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_03_04.jpg
图 3.4:点之间的距离
-
从每个点出发,扩展一个邻域大小并形成簇。为了这个示例,假设我们通过了半径为三的邻域。这意味着任何两个点,如果它们之间的距离小于三,就会被视为邻居。点(-8,6)和(-9,4)现在是聚类的候选点。
-
没有邻居的点被标记为噪声,并且保持未聚类状态。点(1,7)和(4,-2)由于在聚类中无用,超出了我们的兴趣范围。
-
有邻居的点随后会被评估,看它们是否符合最小点数阈值。在这个示例中,如果我们设置最小点数阈值为二,那么点(-8,6)和(-9,4)就可以正式组合成一个簇。如果最小点数阈值为三,那么这个集合中的所有四个数据点将被视为多余的噪声。
-
在剩余的未访问数据点上重复此过程。
在这个过程结束时,你将获得整个数据集,所有数据要么被归类为簇的一部分,要么被视为无关的噪声。通过走完整个玩具示例,你可以发现,DBSCAN 的性能高度依赖于你事先选择的阈值超参数。这意味着你可能需要多次运行 DBSCAN,并尝试不同的超参数选项,以了解它们如何影响整体性能。
DBSCAN 的一个优点是,它摒弃了我们在 k 均值和以质心为中心的层次聚类实现中看到的质心概念。这个特性使得 DBSCAN 更适合处理复杂的数据集,因为大多数现实世界中的数据并不像干净的簇那样分布。
练习 9:评估邻域半径大小的影响
对于这个练习,我们将采用与之前示例中常见的方式相反的顺序,先查看 scikit-learn 中 DBSCAN 的封装实现,然后再自己实现。这是故意为之,以充分探讨不同邻域半径大小如何极大地影响 DBSCAN 的性能。
完成这个练习后,你将熟悉调整邻域半径大小如何影响 DBSCAN 的性能。理解这些 DBSCAN 的特点很重要,因为它们可以帮助你在未来通过高效地排查聚类算法问题来节省时间。
-
生成一些虚拟数据:
from sklearn.cluster import DBSCAN from sklearn.datasets import make_blobs import matplotlib.pyplot as plt %matplotlib inline # Generate a random cluster dataset to experiment on. X = coordinate points, #y = cluster labels (not needed) X, y = make_blobs(n_samples=1000, centers=8, n_features=2, random_state=800) # Visualize the data plt.scatter(X[:,0], X[:,1]) plt.show()
输出结果如下:
图 3.5:可视化的玩具数据示例
-
在为这个玩具问题绘制虚拟数据后,你将看到数据集有两个特征,并大约有七到八个簇。要使用 scikit-learn 实现 DBSCAN,你需要实例化一个新的 scikit-learn 类:
db = DBSCAN(eps=0.5, min_samples=10, metric='euclidean')
我们的 DBSCAN 实例存储在
db
变量中,超参数在创建时传入。为了这个例子,你可以看到邻域半径(eps
)设置为 0.5,而最小点数设置为 10。为了与前几章一致,我们将继续使用欧几里得距离作为度量标准。 -
让我们设置一个循环,允许我们交互式地探索潜在的邻域半径大小选项:
eps = [0.2,0.7] for ep in eps: db = DBSCAN(eps=ep, min_samples=10, metric='euclidean') plt.scatter(X[:,0], X[:,1], c=db.fit_predict(X)) plt.title('Toy Problem with eps: ' + str(ep)) plt.show()
上述代码产生以下两张图:
图:3.6: 结果图
从图中可以看到,设置邻域半径过小会导致所有数据被视为随机噪声(紫色点)。稍微增加邻域半径,可以让我们形成更有意义的簇。试着重新创建前面的图形并尝试不同的eps
值。
DBSCAN 属性 - 邻域半径
在练习 9,评估邻域半径大小的影响中,你看到了设置合适的邻域半径对你的 DBSCAN 实现性能的影响。如果邻域太小,你会遇到所有数据都未被聚类的问题。如果你设置的邻域过大,那么所有数据会被聚集到一个簇中,且无法提供任何价值。如果你用自己的eps
值进一步探索前面的练习,你可能会注意到,仅凭邻域大小很难得到理想的聚类效果。这时,最小点数阈值就显得非常重要。我们稍后会讨论这一主题。
为了更深入了解 DBSCAN 的邻域概念,我们来看一下你在实例化时传入的eps
超参数。eps
代表 epsilon,是算法在寻找邻居时查看的距离。这个 epsilon 值会被转换为一个半径,围绕任意给定数据点以圆形方式进行扫描,以形成邻域:
图 3.7: 邻域半径可视化,红色圆圈为邻域
在这个例子中,中心点将有四个邻居。
这里需要注意的一个关键点是,你的邻域搜索形成的形状在二维空间中是圆形,在三维空间中是球形。根据数据的结构,这可能会影响你模型的表现。再次强调,簇可能看起来像是一个直观的结构,但这并不总是如此。幸运的是,DBSCAN 在处理这种你感兴趣的簇,但又不符合明确的簇状结构时非常有效。
图 3.8: 不同邻域半径大小的影响
在左侧,数据点会被分类为随机噪声。右侧,数据点有多个邻居,可能会成为一个独立的簇。
活动 4:从头实现 DBSCAN
在面试中使用生成的二维数据集时,你被要求从头开始实现 DBSCAN 算法。为此,你需要编码邻域搜索的直觉,并进行递归调用以添加邻居。
根据你在前几章中学到的 DBSCAN 和距离度量,使用 Python 从头实现 DBSCAN。你可以自由使用 NumPy 和 SciPy 来评估距离。
完成此活动的步骤如下:
-
生成一个随机簇数据集
-
可视化数据
-
从头创建函数,允许你在数据集上调用 DBSCAN
-
使用你创建的 DBSCAN 实现来寻找生成数据集中的簇。可以根据需要调整超参数,并根据其表现进行调优。
-
可视化从头开始实现的 DBSCAN 聚类效果
本练习的预期结果是让你理解 DBSCAN 的工作原理,从而在使用 scikit-learn 的完整实现之前,能够从头开始实现 DBSCAN。采取这种方法来学习任何机器学习算法是很重要的,它有助于你“获得”使用更简单实现的能力,同时在未来仍能深入讨论 DBSCAN:
图 3.9:预期结果
注意
此活动的解决方案可以在第 316 页找到。
DBSCAN 属性 – 最小点数
除了邻域半径之外,DBSCAN 成功实现的另一个核心组件是需要的最小点数,以证明某个数据点属于某个簇。如前所述,当数据集较为稀疏时,这个下限能明显有利于你的算法。不过,当数据非常密集时,这个参数并非无用——虽然将单个数据点随机散布在特征空间中可以很容易地被归类为噪声,但当数据随机形成两到三个点的“斑块”时,问题就变得模糊了。例如,这些数据点是应该作为一个独立的簇,还是也应该归类为噪声?最小点数阈值帮助解决了这个问题。
在 scikit-learn 实现的 DBSCAN 中,此超参数在min_samples
字段中设置,该字段会在创建 DBSCAN 实例时传递。此字段与邻域半径大小超参数配合使用时非常有价值,可以帮助完善基于密度的聚类方法:
图 3.10:最小点数阈值决定数据点是否为噪声或簇
在右侧,如果最小点数阈值为 10 个点,它会将该邻域中的数据分类为噪声。
在现实场景中,当你拥有大量数据时,最小点数将产生显著影响。以葡萄酒聚类为例,如果你的商店实际上是一个大型酒类仓库,你可能会有成千上万种葡萄酒,每种酒只有一两瓶,这些酒可能会被轻松视为自己的独立聚类。根据你的使用场景,这可能会有帮助;然而,重要的是要记住数据的主观大小。如果你的数据有数百万个数据点,那么随机噪声可能会被视为数百甚至数千个随机的单次销售。然而,如果你的数据量在几百或几千个数据点的规模上,单个数据点可能会被视为随机噪声。
练习 10:评估最小点数阈值的影响
类似于我们的练习 9,评估邻域半径大小的影响,我们探索了设置适当邻域半径大小的值,我们将重复这一练习,但这次会在多种数据集上更改最小点数阈值。
使用我们当前实现的 DBSCAN,我们可以轻松地调整最小点数阈值。调整这个超参数,并观察它在生成的数据上的表现。
通过调整 DBSCAN 的最小点数阈值,你将理解它如何影响聚类预测的质量。
再次,从随机生成的数据开始:
-
按如下方式生成随机聚类数据集:
from sklearn.cluster import DBSCAN from sklearn.datasets import make_blobs import matplotlib.pyplot as plt %matplotlib inline X, y = make_blobs(n_samples=1000, centers=8, n_features=2, random_state=800)
-
按如下方式可视化数据:
# Visualize the data plt.scatter(X[:,0], X[:,1]) plt.show()
图 3.11:生成数据的绘图
-
使用与之前相同的绘图数据,让我们从练习 1,评估邻域半径大小的影响中选择一个表现较好的邻域半径大小——
eps
= 0.7:db = DBSCAN(eps=0.7, min_samples=10, metric='euclidean')
注意
eps
是一个可调的超参数。然而,正如前文所述,0.7 是来自之前实验的结果,因此我们选择eps = 0.7
作为最优值。 -
在实例化 DBSCAN 聚类算法后,让我们将
min_samples
超参数视为我们希望调整的变量。我们可以通过循环来查找最适合我们使用场景的最小点数:num_samples = [10,19,20] for min_num in num_samples: db = DBSCAN(eps=0.7, min_samples=min_num, metric='euclidean') plt.scatter(X[:,0], X[:,1], c=db.fit_predict(X)) plt.title('Toy Problem with Minimum Points: ' + str(min_num)) plt.show()
观察生成的第一个图,我们可以看到,如果你按照练习 1,评估邻域半径大小的影响,使用 10 个最小点作为聚类成员资格的阈值,你将达到的结果:
图 3.12:具有 10 个最小点的玩具问题绘图
剩下的两个超参数选项对 DBSCAN 聚类算法的性能有很大影响,并展示了单一数值的变化如何显著影响性能:
图 3.13:玩具问题的绘图
正如你所看到的,仅仅将最小点数从 19 改为 20,就为我们的特征空间添加了一个额外的(错误的!)聚类。通过这次练习,你已了解最小点数的概念,现在你可以调整 scikit-learn 实现中的 epsilon 值和最小点数阈值,以获得最优的聚类数。
注意
在我们最初生成数据时,我们创建了八个聚类。这表明,最小点数的小变化可以添加整个新的聚类,而这些聚类显然不应出现在数据中。
活动 5:将 DBSCAN 与 k-means 和层次聚类进行比较
你正在管理商店库存,收到了一大批葡萄酒货物,但在运输过程中瓶子的品牌标签掉落了。幸运的是,供应商提供了每瓶葡萄酒的化学成分数据以及各自的序列号。不幸的是,你不能打开每瓶酒并品尝其差异——你必须找到一种方法,根据化学成分将未标记的酒瓶重新分组!你从订单列表中得知,订购了三种不同类型的葡萄酒,并且只提供了两个酒的属性来将这些葡萄酒分组。
在第二章、层次聚类中,我们已经看到 k-means 和层次聚类如何在葡萄酒数据集上表现。在我们的最佳情况下,得到了 0.59 的轮廓系数。现在,使用 scikit-learn 实现的 DBSCAN,让我们看看能否获得更好的聚类结果。
以下步骤将帮助你完成此活动:
-
导入必要的包
-
加载葡萄酒数据集并检查数据的结构
-
可视化数据
-
使用 k-means、凝聚层次聚类和 DBSCAN 生成聚类
-
评估不同的 DBSCAN 超参数选项及其对轮廓系数的影响
-
基于最高的轮廓系数生成最终的聚类
-
可视化使用三种方法生成的聚类
注意
我们已从
archive.ics.uci.edu/ml/datasets/wine
下载了此数据集。你可以通过github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson03/Activity05
访问该数据集。UCI 机器学习库[
archive.ics.uci.edu/ml
]。加利福尼亚州尔湾:加利福尼亚大学信息与计算机科学学院。
完成此活动后,你将重新创建一个聚类问题的完整工作流。你已经在第二章、层次聚类中熟悉了数据,并且在完成此活动后,你将执行模型选择,找到最适合你数据集的最佳模型和超参数。你将得到葡萄酒数据集在每种聚类方法下的轮廓系数。
注意
该活动的解决方案可以在第 319 页找到。
DBSCAN 与 k-means 和层次聚类的比较
现在你已经了解了 DBSCAN 是如何实现的,以及可以调整哪些不同的超参数来优化性能,我们来看看它与我们在第一章,聚类简介和第二章,层次聚类中介绍的聚类方法的比较。
你可能在活动 5,将 DBSCAN 与 k-means 和层次聚类进行比较中注意到,DBSCAN 在通过轮廓分数寻找最优簇时可能有些挑剔。这是邻域方法的一个缺点——当你对数据中的簇的数量有一些了解时,k-means 和层次聚类表现得更为出色。在大多数情况下,这个数量足够小,你可以迭代地尝试几个不同的数量,看看效果如何。相反,DBSCAN 采用更自下而上的方法,通过调整超参数来发现它认为重要的簇。在实践中,当前两种方法失败时,考虑使用 DBSCAN 是很有帮助的,因为它需要大量的调整才能正常工作。话虽如此,当 DBSCAN 实现正常工作时,它通常会远远超越 k-means 和层次聚类。(实际上,这种情况通常发生在高度交织但仍然离散的数据上,比如包含两个半月形状的特征空间)。
与 k-means 和层次聚类相比,DBSCAN 可能更加高效,因为它只需要对每个数据点进行一次检查。与需要多次迭代寻找新质心并评估其最近邻的位置不同,一旦一个点被分配到 DBSCAN 中的一个簇,它的簇成员就不会再改变。DBSCAN 与层次聚类相比,与 k-means 的另一个关键区别是,它不需要在创建时明确指定预期的簇的数量。这在没有外部指导如何将数据集拆分时非常有用。
总结
DBSCAN 在聚类方法上采取了与 k-means 和层次聚类不同的有趣方式。虽然层次聚类在某些方面可以看作是 k-means 中最近邻方法的扩展,但 DBSCAN 通过应用密度的概念来处理查找邻居的问题。当数据非常复杂且交织在一起时,这种方法尤为有用。虽然 DBSCAN 非常强大,但并不是万无一失的,并且根据原始数据的情况,它有时可能会显得过于复杂。
然而,将 DBSCAN 与 k-means 和层次聚类结合使用时,DBSCAN 为无监督学习任务中的数据聚类提供了强大的工具箱。在遇到这类问题时,比较每种方法的性能并找出最合适的方案是值得的。
在探索完聚类之后,我们将进入无监督学习中另一个关键的技能:降维。通过智能地减少维度,我们可以让聚类变得更易理解,并能够向利益相关者传达。降维对于以最有效的方式创建各种机器学习模型也是至关重要的。
第四章:降维和 PCA
学习目标
到本章结束时,您将能够:
-
应用降维技术。
-
描述主成分和降维背后的概念。
-
在使用 scikit-learn 解决问题时应用主成分分析(PCA)。
-
比较手动 PCA 与 scikit-learn 的 PCA。
本章将探讨降维及其不同的降维技术。
介绍
本章是三章系列中的第一章,探讨我们在无监督学习算法中使用不同特征集(或空间)的应用,我们将从降维的讨论开始,特别是主成分分析(PCA)。接下来,我们将通过探索两种独立且强大的机器学习架构——基于神经网络的自编码器,扩展我们对不同特征空间好处的理解。神经网络在监督学习问题中无疑有着应得的声誉,而通过使用自编码器阶段,它们已被证明在无监督学习问题的应用上足够灵活。最后,在本微系列的最后一章中,我们将基于神经网络实现和降维的基础,讨论 t 分布最近邻算法。
什么是降维?
降维是数据科学家工具包中的一个重要工具,并且由于其广泛的应用场景,它在该领域几乎被视为基本知识。因此,在我们考虑降维及其必要性之前,我们首先需要理解什么是维度。简单来说,维度是与数据样本相关的维度、特征或变量的数量。通常,可以将其视为电子表格中的列数,其中每个样本占据一行,每一列描述样本的某个属性。以下表格就是一个例子:
图 4.1:具有三个不同特征的两个数据样本
在图 4.1中,我们有两个数据样本,每个样本有三个独立的特征或维度。根据所解决的问题或数据集的来源,我们可能希望在不丢失已提供信息的情况下,减少每个样本的维度数量。这就是降维发挥作用的地方。
但是,降维究竟如何帮助我们解决问题呢?我们将在接下来的部分更详细地介绍应用;但假设我们有一个非常大的时间序列数据集,例如回声心动图或心电图(在一些国家也称为 EKG)信号,如下图所示:
图 4.2:心电图(ECG 或 EKG)
这些信号是从您公司新型号的手表中捕获的,我们需要寻找心脏病或中风的迹象。在查看数据集时,我们可以做出以下几项观察:
-
大多数单独的心跳信号非常相似。
-
数据中存在来自录音系统或患者在录音过程中移动的噪音。
-
尽管有噪音,心跳信号仍然可见。
-
数据量非常大——超出了手表可用硬件的处理能力。
正是在这种情况下,降维技术真正显示其优势!通过使用降维技术,我们能够从信号中去除大量噪音,这反过来将有助于提高应用于数据的算法性能,并减少数据集的大小,从而降低硬件要求。本章中我们将讨论的技术,特别是 PCA 和自编码器,在研究和行业中已被广泛应用于有效地处理、聚类和分类这类数据集。在本章结束时,您将能够将这些技术应用于您自己的数据,并希望看到您自己机器学习系统性能的提升。
降维的应用
在开始详细研究降维和 PCA 之前,我们将讨论这些技术的一些常见应用:
-
预处理/特征工程:这种方法最常见的应用是在机器学习解决方案开发的预处理或特征工程阶段。在算法开发过程中提供的信息质量,以及输入数据与期望结果之间的相关性,对于设计高性能的解决方案至关重要。在这种情况下,PCA 可以提供帮助,因为我们能够从数据中提取出最重要的信息成分,并将其提供给模型,从而确保只提供最相关的信息。这还可以带来第二个好处,即我们减少了提供给模型的特征数量,因此计算量也能相应减少,这可以减少系统的整体训练时间。
-
噪音减少:降维也可以作为一种有效的噪音减少/滤波技术。预期信号或数据集中的噪音并不占数据变异的主要成分。因此,我们可以通过去除变异较小的成分来去除信号中的一部分噪音,然后将数据恢复到原始数据空间。在以下示例中,左侧的图像已经过滤掉了最重要的 20 个数据源,生成了右侧的图像。我们可以看到图像质量有所降低,但关键信息仍然保留:
图 4.3:经过维度减少滤波的图像。左:原始图像(照片由来自 Pexels 的 Arthur Brognoli 拍摄),右:滤波后的图像
注意
这张照片是由来自 Pexels 的 Arthur Brognoli 拍摄,并可在www.pexels.com/photo-license/
上免费使用。
-
生成可信的人工数据集:由于 PCA 将数据集分解为信息(或变化)的组件,我们可以通过调整特征值之间的比率来研究每个组件的效果或生成新的数据集样本。我们可以缩放这些组件,从而增加或减少特定组件的重要性。这也被称为统计形状建模,因为其中一种常见方法是使用它来创建形状的合理变体。它还被用来在图像中检测面部特征点,这是主动形状建模过程中的一个步骤。
-
金融建模/风险分析:降维为金融行业提供了一个有用的工具箱,因为能够将大量单独的市场指标或信号整合为较少的组件,可以加快和更高效地进行计算。同样,这些组件可以用来突出那些高风险的产品/公司。
维数灾难
在我们理解使用降维技术的好处之前,我们必须先了解为什么需要减少特征集的维度。维数灾难是一个常用的术语,用来描述在处理具有高维度特征空间的数据时出现的问题;例如,为每个样本收集的属性数量。考虑一个《吃豆人》游戏中点位置的数据集。你的角色吃豆人在虚拟世界中的位置由两个维度或坐标(x,y)定义。假设我们正在创建一个新的电脑敌人:一个由 AI 驱动的幽灵来对抗玩家,并且它需要一些关于我们角色的信息来做出自己的游戏逻辑决策。为了使这个机器人有效,我们需要玩家的位置(x,y)以及每个方向上的速度(vx,vy),此外还需要玩家最后五个(x,y)位置,剩余的心数以及迷宫中剩余的能量豆数(能量豆暂时允许吃豆人吃掉幽灵)。现在,对于每个时间点,我们的机器人需要 16 个单独的特征(或维度)来做出决策。显然,这比只提供位置的两个维度要多得多。
图 4.4:《吃豆人》游戏中的维度
为了解释降维的概念,我们将考虑一个虚构的数据集(见图 4.5),其中 x 和 y 坐标作为特征,形成了特征空间中的两个维度。需要注意的是,这个例子绝不是数学证明,而是旨在提供一种可视化增加维度后影响的方式。在这个数据集中,我们有六个独立的样本(或点),我们可以可视化当前在特征空间内占据的体积,约为 (3 - 1) x (4 - 2) = 2 x 2 = 4 平方单位。
图 4.5:二维特征空间中的数据
假设数据集包含相同数量的点,但每个样本都有一个额外的特征(z 坐标)。此时,所占数据体积大约为 2 x 2 x 2 = 8 立方单位。因此,我们现在有相同数量的样本,但包围数据集的空间变得更大。这样,数据在可用空间中占据的相对体积变小,数据变得更加稀疏。这就是维度灾难;随着可用特征数量的增加,数据的稀疏性增加,从而使得统计上有效的相关性变得更加困难。回到我们创建视频游戏机器人来与人类玩家对战的例子,我们有 12 个特征,包含不同类型的特征:速度、速度变化、加速度、技能水平、选择的武器和可用弹药。根据这些特征的可能值范围以及每个特征对数据集方差的贡献,数据可能非常稀疏。即使在受限的吃豆人世界中,每个特征的潜在方差也可能非常大,有些特征的方差远大于其他特征。
因此,在不处理数据集稀疏性的情况下,我们通过额外的特征获得了更多信息,但可能无法提高机器学习模型的性能,因为统计相关性变得更加困难。我们希望做的是保留额外特征提供的有用信息,同时最小化稀疏性的负面影响。这正是降维技术的设计目的,而这些技术在提高机器学习模型性能方面可能非常强大。
本章将讨论多种降维技术,并将在更详细的工作示例中介绍其中一种最重要和有用的方法——主成分分析(PCA)。
降维技术概述
正如在引言部分所讨论的,任何降维技术的目标都是在保持提供的有用信息的同时管理数据集的稀疏性,因此降维通常是分类阶段前的一个重要预处理步骤。大多数降维技术旨在通过特征投影的过程来完成这一任务,将数据从高维空间调整到较低维度的空间,以去除数据的稀疏性。再次通过可视化投影过程来理解这一点,可以考虑在三维空间中的一个球体。我们可以将球体投影到二维空间,变成一个圆形,虽然会有一些信息丢失(z 坐标的值),但仍保留了描述其原始形状的大部分信息。我们仍然知道原点、半径和流形(轮廓),而且仍然非常清楚它是一个圆。因此,如果我们仅仅得到了二维投影,凭借这些信息也能够重新构建原始的三维形状。所以,根据我们尝试解决的问题,我们可能已经在保留重要信息的同时减少了维度:
图 4.6:将一个三维球体投影到二维空间
通过在降维阶段对数据集进行预处理,能够获得的附加好处是提高的计算性能。由于数据已经被投影到较低维度的空间,它将包含更少但可能更强大的特征。特征较少意味着在后续的分类或回归阶段,处理的数据集的大小显著较小。这将可能减少分类/回归所需的系统资源和处理时间,在某些情况下,降维技术还可以直接用于完成分析。
这个类比还引入了降维的一个重要考虑因素。我们总是试图在将数据投影到低维空间时,平衡信息丢失和减少数据稀疏性之间的关系。根据问题的性质和使用的数据集,正确的平衡可能会自然出现,并且相对直接。在某些应用中,这个决策可能依赖于额外验证方法的结果,比如交叉验证(特别是在监督学习问题中)或领域专家的评估。
我们喜欢将降维中的这一权衡方式比作在计算机上传输文件或图像时的压缩过程。降维技术,如 PCA,实质上是将信息压缩成较小的大小以便传输,而在许多压缩方法中,压缩过程中会发生一些信息丢失。有时,这些丢失是可以接受的;例如,如果我们要传输一张 50MB 的图像并需要将其压缩到 5MB,我们可以预期仍然能够看到图像的主要内容,但一些较小的背景细节可能会变得模糊不清。我们也不会期望从压缩后的图像恢复出完全无损的原始图像,但可以期望在恢复时会出现一些附加的伪影,比如模糊。
降维与无监督学习
降维技术在机器学习中有许多用途,因为能够提取数据集中的有用信息可以在许多机器学习问题中提高性能。与监督学习方法不同,降维技术在无监督学习中尤其有用,因为数据集不包含任何实际标签或目标。无监督学习中,训练环境用于以适合问题解决的方式组织数据(例如,分类问题中的聚类),这种组织方式通常基于数据集中的最重要信息。降维提供了提取重要信息的有效手段,且由于我们可以使用多种方法,因此回顾一些可用选项是有益的:
-
线性判别分析(LDA):这是一种非常实用的技术,既可以用于分类,也可以用于降维。LDA 将在第七章中更详细地讲解:主题建模。
-
非负矩阵分解(NNMF):与许多降维技术一样,这种方法依赖于线性代数的性质来减少数据集中的特征数量。NNMF 也将在第七章,主题建模中进行更详细的讨论。
-
奇异值分解(SVD):这与 PCA(本章中将详细讨论)有些相关,也是一个矩阵分解过程,与 NNMF 并无太大不同。
-
独立成分分析(ICA):这与 SVD 和 PCA 有一些相似之处,但通过放宽数据为高斯分布的假设,可以实现非高斯数据的分离。
到目前为止描述的每种方法都使用线性分离来减少数据在其原始实现中的稀疏性。一些方法还有使用非线性核函数的变体,能够以非线性的方式减少稀疏性。根据所使用的数据集,非线性核可能在从信号中提取最有用的信息方面更为有效。
主成分分析(PCA)
如前所述,PCA 是一种常用且非常有效的降维技术,通常作为许多机器学习模型和技术的预处理阶段。因此,我们将在本书中专门花一章更详细地探讨 PCA,超越其他方法。PCA 通过将数据分解为一系列组件来减少数据的稀疏性,每个组件代表数据中的一个信息源。顾名思义,PCA 中产生的第一个组件,主成分,包含了数据中大部分的信息或方差。主成分通常可以被认为是除了均值之外,贡献最多有趣信息的部分。随着每个后续组件的加入,数据中传递的信息减少,但更加微妙。如果我们将所有这些组件都考虑在内,使用 PCA 将没有任何好处,因为它将恢复原始数据集。为了澄清这个过程以及 PCA 返回的信息,我们将使用一个实际的例子,通过手动完成 PCA 计算。但首先,我们需要回顾一些基础的统计学概念,这些概念是进行 PCA 计算所必需的。
均值
均值,或称平均值,简单来说就是将所有值相加后,除以数据集中值的数量。
标准差
协方差矩阵通常被称为数据的分布,与方差相关,标准差是衡量数据与均值的接近程度的指标。在正态分布的数据集中,大约 68%的数据位于均值的一个标准差范围内。
方差与标准差之间的关系相当简单——方差是标准差的平方。
协方差
当标准差或方差是计算单一维度数据的分布时,协方差是一个维度(或特征)与另一个维度的方差。当一个维度的协方差与其自身计算时,结果与仅计算该维度的方差相同。
协方差矩阵
协方差矩阵是可以计算数据集协方差值的矩阵表示。除了在数据探索中非常有用外,协方差矩阵在执行 PCA(主成分分析)时也是必需的。为了确定一个特征相对于另一个特征的方差,我们只需查找协方差矩阵中对应的值。参见图 4.7,我们可以看到,在第 1 列、第 2 行,值是特征或数据集Y相对于X的方差(cov(Y, X))。我们还可以看到,有一列协方差值是针对同一特征或数据集计算的;例如,cov(X, X)。在这种情况下,值就是X的方差。
图 4.7:协方差矩阵
通常,每个协方差的具体数值并不像观察矩阵中每个协方差的大小和相对大小那样有趣。某个特征与另一个特征的协方差较大,意味着一个特征与另一个特征有显著的变化,而接近零的值则表示变化极小。另一个值得关注的协方差特性是其符号;正值表示当一个特征增加或减少时,另一个特征也随之增加或减少,而负协方差则表示两个特征相互背离,一个增加时另一个减少,反之亦然。
值得庆幸的是,numpy
和scipy
提供了高效的函数来为你完成这些计算。在下一个练习中,我们将使用 Python 来计算这些值。
练习 11:理解统计学基础概念
在本练习中,我们将简要回顾如何使用numpy
和pandas
这两个 Python 包来计算一些基础的统计概念。在本练习中,我们将使用一个包含不同鸢尾花物种测量数据集,该数据集由英国生物学家和统计学家罗纳德·费舍尔爵士于 1936 年创建。该数据集可以在随附的源代码中找到,包含了三种不同鸢尾花品种(鸢尾花 Setosa、鸢尾花 Versicolor 和鸢尾花 Virginica)的四个独立测量值(花萼宽度和长度,花瓣宽度和长度)。
注意
该数据集来自 archive.ics.uci.edu/ml/machine-learning-databases/iris/
。它可以从 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Exercise11
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。
执行的步骤如下:
-
导入
pandas
、numpy
和matplotlib
包以供使用:import pandas as pd import numpy as np import matplotlib.pyplot as plt
-
加载数据集并预览前五行数据:
df = pd.read_csv('iris-data.csv') df.head()
输出如下:
图 4.8:数据的头部
-
我们只需要
Sepal Length
和Sepal Width
特征,因此删除其他列:df = df[['Sepal Length', 'Sepal Width']] df.head()
输出如下:
图 4.9:清洗后的数据头部
-
通过绘制
Sepal Length
与Sepal Width
的值来可视化数据集:plt.figure(figsize=(10, 7)) plt.scatter(df['Sepal Length'], df['Sepal Width']); plt.xlabel('Sepal Length (mm)'); plt.ylabel('Sepal Width (mm)'); plt.title('Sepal Length versus Width');
输出如下:
图 4.10:数据的图示
-
使用
pandas
方法计算均值:df.mean()
输出如下:
Sepal Length 5.843333 Sepal Width 3.054000 dtype: float64
-
使用
numpy
方法计算均值:np.mean(df.values, axis=0)
输出如下:
array([5.84333333, 3.054 ])
-
使用
pandas
方法计算标准差值:df.std()
输出如下:
Sepal Length 0.828066 Sepal Width 0.433594 dtype: float64
-
使用
numpy
方法计算标准差值:np.std(df.values, axis=0)
输出如下:
array([0.82530129, 0.43214658])
-
使用
pandas
方法计算方差值:df.var()
输出如下:
Sepal Length 0.685694 Sepal Width 0.188004 dtype: float64
-
使用
numpy
方法计算方差值:np.var(df.values, axis=0)
输出如下:
array([0.68112222, 0.18675067])
-
使用
pandas
方法计算协方差矩阵:df.cov()
输出如下:
图 4.11:使用 Pandas 方法的协方差矩阵
-
使用
numpy
方法计算协方差矩阵:np.cov(df.values.T)
输出如下:
图 4.12:使用 NumPy 方法的协方差矩阵
现在我们知道如何计算基础的统计值,接下来我们将重点讨论 PCA 的其他组成部分。
特征值和特征向量
特征值和特征向量的数学概念在物理学和工程学领域中非常重要,它们也是计算数据集主成分的最后步骤。特征值和特征向量的精确定义超出了本书的范围,因为它涉及较为复杂的内容,并且需要有一定的线性代数基础。将数据集 (a) 分解为特征值 (S) 和特征向量 (U) 的线性代数方程如下:
图 4.13:特征向量/特征值分解
在 图 4.13 中,U 和 V 作为数据集 a 的左右值相关。如果 a 的形状为 m x n,则 U 将包含形状为 m x m 的值,V 的形状为 n x n。
简而言之,在 PCA 的上下文中:
-
特征向量 (U) 是对数据集做出信息贡献的成分,如本节第一段所述的主成分。每个特征向量描述了数据集中的某种变异性。
-
特征值 (S) 是描述每个特征向量对数据集贡献多少的单独数值。如我们之前所述,描述最大贡献的信号特征向量称为主成分,因此它将具有最大的特征值。因此,具有最小特征值的特征向量对数据的方差或信息贡献最少。
练习 12:计算特征值和特征向量
如我们之前所讨论的,手动推导和计算特征值及特征向量稍显复杂,并且超出了本书的范围。幸运的是,numpy
为我们提供了计算这些值的所有功能。再次说明,我们将使用 Iris 数据集作为示例:
注意
该数据集来自 archive.ics.uci.edu/ml/machine-learning-databases/iris/
。
可以从 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Exercise12
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州尔湾:加利福尼亚大学信息与计算机科学学院。
-
导入
pandas
和numpy
包:import pandas as pd import numpy as np
-
加载数据集:
df = pd.read_csv('iris-data.csv') df.head()
输出如下所示:
图 4.14: 数据集的前五行
-
同样,我们只需要
花萼长度
和花萼宽度
特征,因此删除其他列:df = df[['Sepal Length', 'Sepal Width']] df.head()
输出如下所示:
图 4.15: 花萼长度和花萼宽度特征
-
从 NumPy 的线性代数模块中,使用单值分解函数来计算
特征值
和特征向量
:eigenvectors, eigenvalues, _ = np.linalg.svd(df.values, full_matrices=False)
注意
使用
full_matrices=False
函数参数是一个标志,表示函数返回我们需要形状的特征向量;即:# 样本 x # 特征。 -
观察特征值,我们可以看到第一个值是最大的,因此第一个特征向量贡献了最多的信息:
eigenvalues
输出如下所示:
array([81.25483015, 6.96796793])
-
观察特征值作为数据集总方差的百分比非常方便。我们将使用累积和函数来实现这一点:
eigenvalues = np.cumsum(eigenvalues) eigenvalues
输出如下所示:
array([81.25483015, 88.22279808])
-
除以最后一个或最大值来转换为百分比:
eigenvalues /= eigenvalues.max() eigenvalues
输出如下所示:
array([0.92101851, 1\. ])
我们可以看到,这里第一个(或主)成分包含了数据中 92%的变化量,因此包含了大部分信息。
-
现在,让我们看看
特征向量
:eigenvectors
输出的一部分如下所示:
图 4.16: 特征向量
-
确认特征向量矩阵的形状为
# 样本 x # 特征
;即,150
x2
:eigenvectors.shape
输出如下所示:
(150, 2)
-
因此,从特征值中我们可以看出,第一个特征向量是主成分。看看第一个特征向量的值:
P = eigenvectors[0] P
输出如下:
array([-0.07553027, -0.11068158])
我们已经将数据集分解为主成分,并且利用特征向量,我们可以进一步减少可用数据的维度。在后续的示例中,我们将考虑 PCA 并将该技术应用于示例数据集。
PCA 的过程
现在,我们已经准备好所有步骤来完成 PCA,减少数据集的维度。
完成 PCA 的总体算法如下:
-
导入所需的 Python 包(
numpy
和pandas
)。 -
加载整个数据集。
-
从可用数据中选择你希望用于降维的特征。
注意
如果数据集的特征之间存在显著的尺度差异,例如,一个特征的值范围在 0 到 1 之间,而另一个在 100 到 1,000 之间,你可能需要对其中一个特征进行归一化,因为这种量级差异会消除较小特征的影响。在这种情况下,你可能需要将较大特征除以其最大值。
举个例子,看看这个:
x1 = [0.1, 0.23, 0.54, 0.76, 0.78]
x2 = [121, 125, 167, 104, 192]
x2 = x2 / np.max(x2) # 将 x2 归一化到 0 和 1 之间
-
计算所选(并可能已归一化)数据的
协方差
矩阵。 -
计算
协方差
矩阵的特征值和特征向量。 -
按从高到低的顺序对特征值(及其对应的特征向量)进行排序。
-
计算特征值在数据集总方差中的百分比。
-
选择所需的特征值(及其对应的特征向量)数量,以组成一个预定的最小组成方差值。
注意
在这一阶段,排序后的特征值表示数据集总方差的百分比。因此,我们可以利用这些值来选择所需的特征向量的数量,无论是为了解决问题,还是为了充分减少应用于模型的数据集的大小。例如,假设我们要求 PCA 输出中至少包含 90%的方差。那么,我们将选择那些包含至少 90%方差的特征值(及其对应的特征向量)的数量。
-
将数据集与选定的特征向量相乘,你就完成了 PCA,减少了表示数据的特征数量。
-
绘制结果。
在进行下一个练习之前,请注意,转置是线性代数中的一个术语,意思是将行和列互换。假设我们有一个矩阵 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_04_Formula_01.png,那么 X 的转置将是 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/app-unspr-lrn-py/img/C12626_04_Formula_02.png。
练习 13:手动执行 PCA
在这个练习中,我们将手动完成主成分分析(PCA),再次使用鸢尾花数据集。在这个例子中,我们希望将数据集中的维度数减少到足以包含至少 75% 的可用方差:
注意
此数据集取自 archive.ics.uci.edu/ml/machine-learning-databases/iris/
。可以从 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Exercise13
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州欧文:加利福尼亚大学信息与计算机科学学院。
-
导入
pandas
和numpy
包:import pandas as pd import numpy as np import matplotlib.pyplot as plt
-
加载数据集:
df = pd.read_csv('iris-data.csv') df.head()
输出如下:
图 4.17:数据集的前五行
-
再次,我们只需要
花萼长度
和花萼宽度
特征,因此去除其他列。在这个例子中,我们没有对所选数据集进行归一化:df = df[['Sepal Length', 'Sepal Width']] df.head()
输出如下:
图 4.18:花萼长度和花萼宽度特征
-
计算所选数据的
协方差
矩阵。请注意,我们需要对协方差
矩阵进行转置,以确保它基于特征数(2)而不是样本数(150):data = np.cov(df.values.T) # The transpose is required to ensure the covariance matrix is #based on features, not samples data data
输出如下:
图 4.19:所选数据的协方差矩阵
-
计算协方差矩阵的特征向量和特征值。再次使用
full_matrices
函数参数:eigenvectors, eigenvalues, _ = np.linalg.svd(data, full_matrices=False)
-
特征值是什么?这些特征值按从高到低的顺序返回:
eigenvalues
输出如下:
array([0.6887728 , 0.18492474])
-
对应的特征向量是什么?
eigenvectors
输出如下:
图 4.20:特征向量
-
计算特征值作为数据集中方差的百分比:
eigenvalues = np.cumsum(eigenvalues) eigenvalues /= eigenvalues.max() eigenvalues
输出如下:
array([0.78834238, 1\. ])
-
根据练习介绍,我们需要描述至少包含 75% 可用方差的数据。根据步骤 7,主成分包含 78% 的可用方差。因此,我们只需要数据集中的主成分。主成分是什么?
P = eigenvectors[0] P
输出如下:
array([-0.99693955, 0.07817635])
现在,我们可以应用降维过程。执行主成分与数据集转置矩阵的矩阵乘法。
注意
降维过程是所选特征向量与待转换数据的矩阵乘法。
-
如果不对
df.values
矩阵进行转置,就无法进行矩阵乘法:x_t_p = P.dot(df.values.T) x_t_p
输出的一部分如下:
图 4.21:矩阵乘法结果
注意
为了执行矩阵乘法,数据集的转置是必需的,因为矩阵的内维必须相同才能进行矩阵乘法。为了使 A.dot(B) 有效,A 必须具有 m x n 的形状,B 必须具有 n x p 的形状。在本例中,A 和 B 的内维都是 n。
在以下示例中,PCA 的输出是一个单列、150 个样本的数据集。因此,我们只是将初始数据集的大小减少了一半,包含了数据中约 79% 的方差:
图 4.22: PCA 输出结果
-
绘制主成分的值:
plt.figure(figsize=(10, 7)) plt.plot(x_t_p); plt.title('Principal Component of Selected Iris Dataset'); plt.xlabel('Sample'); plt.ylabel('Component Value');
输出结果如下:
图 4.23: 使用手动 PCA 转换后的 Iris 数据集
在本次练习中,我们简单地计算了数据集的协方差矩阵,而没有对数据集进行任何预处理。如果两个特征的均值和标准差大致相同,这是完全可以接受的。然而,如果一个特征的值远大于另一个特征(并且均值也有所不同),那么在分解为主成分时,这个特征可能会主导另一个特征,从而可能会完全丧失较小特征所提供的信息。在计算协方差矩阵之前,一种简单的归一化方法是从特征中减去各自的均值,从而使数据集围绕零进行中心化。我们将在练习 15,通过手动 PCA 可视化方差减少中演示这一过程。
练习 14: Scikit-Learn PCA
通常情况下,我们不会手动完成 PCA,尤其是当 scikit-learn 提供了一个优化的 API,并且它的便捷方法能让我们轻松地将数据转换到低维空间并返回时。在本次练习中,我们将更详细地研究如何在 Iris 数据集上使用 scikit-learn 的 PCA:
注意
该数据集来自 archive.ics.uci.edu/ml/machine-learning-databases/iris/
。
数据集可以从 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Exercise14
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州尔湾市:加利福尼亚大学信息与计算机科学学院。
-
从
sklearn
包中导入pandas
、numpy
和PCA
模块:import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA
-
加载数据集:
df = pd.read_csv('iris-data.csv') df.head()
输出结果如下:
图 4.24: 数据集的前五行
-
同样,我们只需要
花萼长度
和花萼宽度
两个特征,因此需要删除其他列。在这个示例中,我们没有对选定的数据集进行归一化处理:df = df[['Sepal Length', 'Sepal Width']] df.head()
输出如下:
图 4.25:花萼长度和花萼宽度特征
-
将数据拟合到 scikit-learn 的 PCA 模型上,使用协方差数据。使用默认值,就像我们这里所做的那样,会生成该数据集可能的最大特征值和特征向量数量:
model = PCA() model.fit(df.values)
输出如下:
图 4.26:将数据拟合到 PCA 模型
在这里,
copy
表示数据在模型内的拟合会在应用任何计算之前进行复制。iterated_power
显示花萼长度
和花萼宽度
特征是保留的主成分数量。默认值为None
,它会选择组件数量为样本数或特征数中的最小值减一。random_state
允许用户为 SVD 求解器使用的随机数生成器指定种子。svd_solver
指定在 PCA 过程中使用的 SVD 求解器。tol
是 SVD 求解器使用的容差值。通过whiten
,组件向量会乘以样本数的平方根。这将删除一些信息,但可以改善某些后续估计器的性能。 -
成分(特征值)所描述的方差百分比包含在
explained_variance_ratio_
属性中。显示explained_variance_ratio_
的值:model.explained_variance_ratio_
输出如下:
array([0.78834238, 0.21165762])
-
通过
components_
属性显示特征向量:model.components_
输出如下:
图 4.27:特征向量
-
在这个练习中,我们将再次只使用主要成分,因此我们将创建一个新的
PCA
模型,这次指定成分(特征向量/特征值)的数量为1
:model = PCA(n_components=1)
-
使用
fit
方法将covariance
矩阵拟合到PCA
模型,并生成相应的特征值/特征向量:model.fit(df.values)
图 4.28:特征值和特征向量的最大数量
使用多个默认参数拟合模型,具体参数如前面的输出所示。
copy = True
是提供给fit
方法的数据,该数据在应用 PCA 之前会被复制。iterated_power='auto'
用于定义内部 SVD 求解器的迭代次数。n_components=1
指定 PCA 模型只返回主成分。random_state=None
指定需要时内部 SVD 求解器使用的随机数生成器。svd_solver='auto'
是使用的 SVD 求解器类型。tol=0.0
是 SVD 求解器认为已收敛的容差值。whiten=False
指定不修改特征向量。如果设置为True
,白化会进一步通过乘以样本数量的平方根并除以奇异值来修改成分。这可以帮助改善后续算法步骤的性能。通常,除了组件数量(
n_components
)之外,你无需担心调整任何这些参数。例如,你可以将n_components
传递给fit
方法,如model.fit(data, n_components=2)
。 -
使用
components_
属性显示特征向量:model.components_
输出如下:
array([[ 0.99693955, -0.07817635]])
-
使用模型的
fit_transform
方法将鸢尾花数据集转换到低维空间。将转换后的值赋给data_t
变量。data_t = model.fit_transform(df.values)
-
绘制转换后的值以可视化结果:
plt.figure(figsize=(10, 7)) plt.plot(data_t); plt.xlabel('Sample'); plt.ylabel('Transformed Data'); plt.title('The dataset transformed by the principal component');
输出如下:
图 4.29:使用 scikit-learn PCA 转换的鸢尾花数据集
恭喜你!你刚刚使用手动 PCA 以及 scikit-learn API 减少了鸢尾花数据集的维度。但在我们过早庆祝之前,比较图 4.23 和图 4.29;这两张图应该是相同的,对吧?我们使用了两种不同的方法在同一数据集上完成 PCA,并且都选择了主成分。在下一个活动中,我们将探讨为什么两者之间存在差异。
活动 6:手动 PCA 与 scikit-learn
假设你被要求将一个旧应用程序中手动执行 PCA 的遗留代码移植到一个新的使用 scikit-learn 的应用程序。在移植过程中,你注意到手动 PCA 的输出与移植后的输出之间存在一些差异。为什么手动 PCA 和 scikit-learn 之间会有输出差异?比较两种方法在鸢尾花数据集上的结果。它们之间有什么区别?
注意
该数据集来源于archive.ics.uci.edu/ml/machine-learning-databases/iris/
。可以从github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Activity06
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州尔湾:加利福尼亚大学信息与计算机科学学院。
-
导入
pandas
、numpy
和matplotlib
绘图库以及 scikit-learn 的PCA
模型。 -
加载数据集并按照之前的练习仅选择萼片特征。显示数据的前五行。
-
计算数据的
协方差
矩阵。 -
使用 scikit-learn API 并仅使用第一个主成分转换数据。将转换后的数据存储在
sklearn_pca
变量中。 -
使用手动 PCA 和仅第一个主成分转换数据。将转换后的数据存储在
manual_pca
变量中。 -
在同一图表上绘制
sklearn_pca
和manual_pca
的值,以可视化它们的差异。 -
请注意,两个图表看起来几乎相同,但有一些关键的差异。这些差异是什么?
-
看看是否能够修改手动 PCA 过程的输出,使其与 scikit-learn 版本一致。
注意
提示:scikit-learn API 在转换前会减去数据的均值。
预期输出:在本活动结束时,你将使用手动 PCA 和 scikit-learn PCA 方法对数据集进行转化。你将生成一张图表,展示两个降维数据集实际上是相同的,并且你应该理解为什么它们最初看起来有很大的不同。最终图表应类似于以下内容:
图 4.30:预期的最终图表
该图将展示通过两种方法完成的降维实际上是相同的。
注意
本活动的解决方案可以在第 324 页找到。
恢复压缩的数据集
现在我们已经覆盖了一些不同的将数据集转化为低维空间的例子,我们应当考虑这种转化对数据产生了什么实际效果。将 PCA 作为预处理步骤来压缩数据中的特征数量,会导致部分方差被丢弃。以下练习将引导我们完成这一过程,帮助我们了解通过转化丢弃了多少信息。
练习 15:通过手动 PCA 可视化方差减少
降维的一个最重要的方面是理解由于降维过程,从数据集中移除了多少信息。移除过多的信息会给后续处理带来额外挑战,而移除的信息不足则会破坏 PCA 或其他技术的目的。在本练习中,我们将可视化 PCA 将 Iris 数据集移除的多少信息:
注意
该数据集来自于archive.ics.uci.edu/ml/machine-learning-databases/iris/
。
它可以从github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Exercise15
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚大学欧文分校,信息与计算机科学学院。
-
导入
pandas
、numpy
和matplotlib
绘图库:import pandas as pd import numpy as np import matplotlib.pyplot as plt
-
从鸢尾花数据集读取
花萼
特征:df = pd.read_csv('iris-data.csv')[['Sepal Length', 'Sepal Width']] df.head()
输出如下:
图 4.31:花萼特征
-
通过减去相应的均值,使数据集围绕零居中:
注意
means = np.mean(df.values, axis=0) means
输出如下:
array([5.84333333, 3.054 ])
为了计算数据并打印结果,请使用以下代码:
data = df.values - means data
输出的部分如下:
图 4.32:输出的部分
-
使用手动 PCA 基于第一个主成分来变换数据:
eigenvectors, eigenvalues, _ = np.linalg.svd(np.cov(data.T), full_matrices=False) P = eigenvectors[0] P
输出如下:
array([-0.99693955, 0.07817635])
-
将数据转换为低维空间:
data_transformed = P.dot(data.T)
-
重塑主成分以便后续使用:
P = P.reshape((-1, 1))
-
为了计算减少数据集的逆变换,我们需要将选定的特征向量恢复到更高维空间。为此,我们将对矩阵进行求逆。矩阵求逆是另一种线性代数技术,这里我们只会简单介绍。一个方阵,A,如果存在另一个方阵B,且满足AB=BA=I,其中I是一个特殊矩阵,称为单位矩阵,只有主对角线上的值为
1
,则该方阵被称为可逆矩阵:P_transformed = np.linalg.pinv(P) P_transformed
输出如下:
array([[-0.99693955, 0.07817635]])
-
为矩阵乘法准备变换后的数据:
data_transformed = data_transformed.reshape((-1, 1))
-
计算减少数据的逆变换,并绘制结果以可视化去除数据方差的效果:
data_restored = data_transformed.dot(P_transformed) data_restored
输出的部分如下:
图 4.33:减少数据的逆变换
-
将
means
添加回变换后的数据:data_restored += means
-
通过绘制原始数据集和变换后的数据集来可视化结果:
plt.figure(figsize=(10, 7)) plt.plot(data_restored[:,0], data_restored[:,1], linestyle=':', label='PCA restoration'); plt.scatter(df['Sepal Length'], df['Sepal Width'], marker='*', label='Original'); plt.legend(); plt.xlabel('Sepal Length'); plt.ylabel('Sepal Width'); plt.title('Inverse transform after removing variance');
输出如下:
图 4.34:去除方差后的逆变换
-
该数据集只有两个变化成分。如果我们不去除任何成分,那么逆变换的结果会是什么?再次将数据转换为低维空间,但这次使用所有的特征向量:
P = eigenvectors data_transformed = P.dot(data.T)
-
转置
data_transformed
,使其具有正确的形状以进行矩阵乘法:data_transformed = data_transformed.T
-
现在,将数据恢复到更高维空间:
data_restored = data_transformed.dot(P) data_restored
输出的部分如下:
图 4.35:恢复的数据
-
将均值添加回恢复的数据:
data_restored += means
-
在原始数据集的背景下可视化恢复的数据:
plt.figure(figsize=(10, 7)) plt.scatter(data_restored[:,0], data_restored[:,1], marker='d', label='PCA restoration', c='k'); plt.scatter(df['Sepal Length'], df['Sepal Width'], marker='o', label='Original', c='k'); plt.legend(); plt.xlabel('Sepal Length'); plt.ylabel('Sepal Width'); plt.title('Inverse transform after removing variance');
输出如下:
图 4.36:去除方差后的逆变换
如果我们比较本练习中生成的两个图,我们可以看到,PCA 降维后的数据集与恢复的数据集基本上是两个特征集之间的负线性趋势线。我们可以将其与从所有可用成分恢复的数据集进行比较,在该数据集中我们已经完整地重建了原始数据集。
练习 16:通过可视化降方差
在本练习中,我们将再次可视化降维对数据集的影响;不过这次,我们将使用 scikit-learn API。由于 scikit-learn 模型的强大功能和简便性,这也是在实际应用中常用的方法:
注意
该数据集来自 archive.ics.uci.edu/ml/machine-learning-databases/iris/
。可以从 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Exercise16
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。
-
导入
pandas
、numpy
和matplotlib
绘图库,以及从 scikit-learn 中导入PCA
模型:import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA
-
从鸢尾花数据集中读取
Sepal
特征:df = pd.read_csv('iris-data.csv')[['Sepal Length', 'Sepal Width']] df.head()
输出如下:
图 4.37:来自鸢尾花数据集的 Sepal 特征
-
使用 scikit-learn API 基于第一个主成分对数据进行变换:
model = PCA(n_components=1) data_p = model.fit_transform(df.values)
输出如下:
-
计算降维数据的逆变换,并绘制结果以可视化去除数据中方差的效果:
data = model.inverse_transform(data_p); plt.figure(figsize=(10, 7)) plt.plot(data[:,0], data[:,1], linestyle=':', label='PCA restoration'); plt.scatter(df['Sepal Length'], df['Sepal Width'], marker='*', label='Original'); plt.legend(); plt.xlabel('Sepal Length'); plt.ylabel('Sepal Width'); plt.title('Inverse transform after removing variance');
输出如下:
图 4.38:去除方差后的逆变换
-
该数据集中只有两个变化成分。如果我们不去除任何成分,逆变换的结果会是什么?
model = PCA() data_p = model.fit_transform(df.values) data = model.inverse_transform(data_p); plt.figure(figsize=(10, 7)) plt.scatter(data[:,0], data[:,1], marker='d', label='PCA restoration', c='k'); plt.scatter(df['Sepal Length'], df['Sepal Width'], marker='o', label='Original', c='k'); plt.legend(); plt.xlabel('Sepal Length'); plt.ylabel('Sepal Width'); plt.title('Inverse transform after removing variance');
输出如下:
图 4.39:去除方差后的逆变换
再次,我们展示了从数据集中去除信息的效果,并且展示了如何使用所有可用的特征向量重新创建原始数据。
之前的练习指定了使用 PCA 将数据维度减少到二维,部分原因是为了方便结果的可视化。然而,我们也可以使用 PCA 将数据维度减少到任何小于原始数据集的值。以下示例演示了如何使用 PCA 将数据集减少到三维,从而实现可视化。
练习 17:在 Matplotlib 中绘制 3D 图形
在 matplotlib 中创建 3D 散点图并不像简单地将一系列 (x, y, z) 坐标提供给散点图那么简单。在本练习中,我们将通过一个简单的 3D 绘图示例,使用鸢尾花数据集进行操作:
注意
本数据集来自 archive.ics.uci.edu/ml/machine-learning-databases/iris/
。
可以从 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Exercise17
下载。
UCI 机器学习数据库 [archive.ics.uci.edu/ml
]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。
-
导入
pandas
和matplotlib
。为了启用 3D 绘图,你还需要导入Axes3D
:from mpl_toolkits.mplot3d import Axes3D import pandas as pd import matplotlib.pyplot as plt
-
读取数据集并选择
Sepal Length
、Sepal Width
和Petal Width
列df = pd.read_csv('iris-data.csv')[['Sepal Length', 'Sepal Width', 'Petal Width']] df.head()
输出结果如下:
图 4.40:数据的前五行
-
在三维空间中绘制数据,并使用
projection='3d'
参数与add_subplot
方法来创建 3D 图:fig = plt.figure(figsize=(10, 7)) ax = fig.add_subplot(111, projection='3d') # Where Axes3D is required ax.scatter(df['Sepal Length'], df['Sepal Width'], df['Petal Width']); ax.set_xlabel('Sepal Length (mm)'); ax.set_ylabel('Sepal Width (mm)'); ax.set_zlabel('Petal Width (mm)'); ax.set_title('Expanded Iris Dataset');
绘图结果如下:
图 4.41:扩展版鸢尾花数据集
注意
尽管导入了 Axes3D,但没有直接使用,它对于配置三维绘图窗口是必需的。如果省略了 Axes3D 的导入,projection='3d'
参数将返回一个 AttributeError
。
活动 7:使用扩展版鸢尾花数据集进行 PCA
在本活动中,我们将使用完整的鸢尾花数据集,观察选择不同数量组件进行 PCA 分解的效果。本活动旨在模拟一个真实世界问题中的过程,我们试图确定选择最佳组件数,同时平衡降维程度和信息丢失。因此,我们将使用 scikit-learn 的 PCA 模型:
注意
本数据集来自 archive.ics.uci.edu/ml/machine-learning-databases/iris/
。
可以从 github.com/TrainingByPackt/Applied-Unsupervised-Learning-with-Python/tree/master/Lesson04/Activity07
下载。
UCI 机器学习库 [archive.ics.uci.edu/ml
]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。
-
导入
pandas
和matplotlib
。为了启用三维绘图,您还需要导入Axes3D
。 -
读取数据集,并选择
花萼长度
、花萼宽度
和花瓣宽度
列。 -
在三维空间中绘制数据。
-
创建一个
PCA
模型,未指定组件数量。 -
将模型拟合到数据集。
-
显示特征值或
explained_variance_ratio_
。 -
我们希望减少数据集的维度,但仍保持至少 90%的方差。为保持 90%的方差,所需的最小组件数是多少?
-
创建一个新的
PCA
模型,这次指定所需的组件数量,以保持至少 90%的方差。 -
使用新模型变换数据。
-
绘制变换后的数据。
-
将变换后的数据恢复到原始数据空间。
-
在一个子图中绘制恢复后的三维数据,在第二个子图中绘制原始数据,以可视化去除部分方差的效果:
fig = plt.figure(figsize=(10, 14)) # Original Data ax = fig.add_subplot(211, projection='3d') # Transformed Data ax = fig.add_subplot(212, projection='3d')
预期输出:最终图形将如下所示:
图 4.42:预期图
注意
本活动的解决方案可以在第 328 页找到。
总结
本章介绍了降维和 PCA 的过程。我们完成了一些练习,并发展了提取数据中最重要方差成分的技能,以减少数据集的大小,既使用手动 PCA 过程,也使用 scikit-learn 提供的模型。在本章中,我们还将降维后的数据集恢复到原始数据空间,并观察去除方差对原始数据的影响。最后,我们讨论了 PCA 和其他降维过程的多种潜在应用。在下一章中,我们将介绍基于神经网络的自编码器,并使用 Keras 包实现它们。