无监督机器学习研讨会(一)

原文:annas-archive.org/md5/59ef285f4ffb779ed4c411e356902e16

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:The

无监督学习

研讨会

开始使用无监督学习算法,并简化你的未整理数据,以帮助进行未来的预测

Aaron Jones, Christopher Kruger, 和 Benjamin Johnston

无监督学习研讨会

版权 © 2020 Packt Publishing

版权所有。未经出版商事先书面许可,本课程的任何部分不得以任何形式或任何手段复制、存储于检索系统中或传播,但在关键文章或评论中嵌入简短的引用除外。

本课程在准备过程中已尽力确保所提供信息的准确性。然而,本课程所包含的信息是按“原样”销售的,不提供任何形式的明示或暗示的担保。无论是作者、Packt Publishing 还是其经销商和分销商,都不对因本课程直接或间接引起的或被声称引起的任何损害负责。

Packt Publishing 力求通过恰当使用大写字母提供课程中提及的所有公司和产品的商标信息。然而,Packt Publishing 无法保证这些信息的准确性。

作者: Aaron Jones, Christopher Kruger, 和 Benjamin Johnston

审阅人: Richard Brooker, John Wesley Doyle, Priyanjit Ghosh, Sani Kamal, Ashish Pratik Patil, Geetank Raipuria, 和 Ratan Singh

执行编辑: Rutuja Yerunkar

采购编辑: Manuraj Nair, Royluis Rodrigues, Anindya Sil, 和 Karan Wadekar

生产编辑: Salma Patel

编辑委员会: Megan Carlisle, Samuel Christa, Mahesh Dhyani, Heather Gopsill, Manasa Kumar, Alex Mazonowicz, Monesh Mirpuri, Bridget Neale, Dominic Pereira, Shiny Poojary, Abhishek Rane, Brendan Rodrigues, Erol Staveley, Ankita Thakur, Nitesh Thakur, 和 Jonathan Wray

初版:2020 年 7 月

生产参考:1280720

ISBN:978-1-80020-070-8

由 Packt Publishing Ltd. 出版

Livery Place, 35 Livery Street

英国伯明翰 B3 2PB

目录

前言    i

1. 聚类简介    1

简介    2

无监督学习与有监督学习   2

聚类    4

识别聚类   5

二维数据   6

练习 1.01:数据中的聚类识别   7

k-means 聚类简介   11

无数学 k-means 步骤详解   11

k-means 聚类深入讲解    13

替代距离度量 – 曼哈顿距离 14

更深的维度 15

练习 1.02:在 Python 中计算欧氏距离 16

练习 1.03:通过距离概念形成聚类 18

练习 1.04:从零开始的 k-means – 第一部分:数据生成 20

练习 1.05:从零开始的 k-means – 第二部分:实现 k-means 24

聚类性能 – 轮廓系数 29

练习 1.06:计算轮廓系数 31

活动 1.01:实现 k-means 聚类 33

总结 35

2. 层次聚类 37

简介 38

聚类复习 38

k-means 复习 39

层次结构的组织 39

层次聚类简介 41

层次聚类步骤 43

层次聚类示例演练 43

练习 2.01:构建层次结构 47

连接 52

练习 2.02:应用连接标准 53

凝聚型与分裂型聚类 58

练习 2.03:使用 scikit-learn 实现凝聚层次聚类 60

活动 2.01:比较 k-means 和层次聚类 64

k-means 与层次聚类 68

总结 69

3. 邻域方法与 DBSCAN 71

简介 72

聚类作为邻域 73

DBSCAN 简介 75

DBSCAN 详细解析 76

DBSCAN 算法演练 77

练习 3.01:评估邻域半径大小的影响 80

DBSCAN 属性 - 邻域半径 84

活动 3.01:从头实现 DBSCAN 86

DBSCAN 属性 - 最小点数 88

练习 3.02:评估最小点数阈值的影响 89

活动 3.02:将 DBSCAN 与 k-means 和层次聚类进行比较 93

DBSCAN 与 k-means 和层次聚类的对比 95

总结 96

4. 主成分分析与降维技术 99

介绍 100

什么是降维? 100

降维技术的应用 102

维度灾难 104

降维技术概览 106

降维 108

主成分分析 109

均值 109

标准差 109

协方差 110

协方差矩阵 110

练习 4.01:使用 pandas 库计算均值、标准差和方差 111

特征值与特征向量 116

练习 4.02:计算特征值和特征向量 117

PCA 的过程 121

练习 4.03:手动执行 PCA 123

练习 4.04:使用 scikit-learn 进行 PCA 128

活动 4.01:手动 PCA 与 scikit-learn 对比 133

恢复压缩后的数据集 136

练习 4.05:使用手动 PCA 可视化方差减少 136

练习 4.06:使用 scikit-learn 可视化方差减少 143

练习 4.07:在 Matplotlib 中绘制 3D 图 147

活动 4.02: 使用扩展的种子数据集进行 PCA 150

总结 153

5. 自编码器 155

简介 156

人工神经网络基础 157

神经元 159

Sigmoid 函数 160

修正线性单元 (ReLU) 161

练习 5.01: 建模人工神经网络的神经元 161

练习 5.02: 使用 ReLU 激活函数建模神经元 165

神经网络: 架构定义 169

练习 5.03: 定义一个 Keras 模型 171

神经网络: 训练 173

练习 5.04: 训练一个 Keras 神经网络模型 175

活动 5.01: MNIST 神经网络 185

自编码器 187

练习 5.05: 简单自编码器 188

活动 5.02: 简单 MNIST 自编码器 193

练习 5.06: 多层自编码器 194

卷积神经网络 199

练习 5.07: 卷积自编码器 200

活动 5.03: MNIST 卷积自编码器 205

总结 207

6. t-分布随机邻居嵌入 209

简介 210

MNIST 数据集 210

随机邻居嵌入 (SNE) 212

t-分布 SNE 213

练习 6.01: t-SNE MNIST 214

活动 6.01: 葡萄酒 t-SNE 227

解释 t-SNE 图 229

困惑度 230

练习 6.02: t-SNE MNIST 和困惑度 230

活动 6.02: t-SNE 葡萄酒与困惑度 235

迭代   236

练习 6.03:t-SNE MNIST 和迭代   237

活动 6.03:t-SNE 葡萄酒和迭代   242

关于可视化的最终思考   243

总结   243

7. 主题建模   245

简介   246

主题模型   247

练习 7.01:设置环境   249

主题模型的高级概览   250

商业应用   254

练习 7.02:数据加载   256

清理文本数据   259

数据清理技术   260

练习 7.03:逐步清理数据   261

练习 7.04:完整数据清理   266

活动 7.01:加载和清理 Twitter 数据   268

潜在狄利克雷分配   270

变分推理   272

词袋模型   275

练习 7.05:使用计数向量化器创建词袋模型   276

困惑度   277

练习 7.06:选择主题数量   279

练习 7.07:运行 LDA   281

可视化   286

练习 7.08:可视化 LDA   287

练习 7.09:尝试四个主题   291

活动 7.02:LDA 和健康推文   296

练习 7.10:使用 TF-IDF 创建词袋模型   298

非负矩阵分解   299

弗罗贝纽斯范数   301

乘法更新算法   301

练习 7.11:非负矩阵分解   302

练习 7.12:可视化 NMF   306

活动 7.03:非负矩阵分解 309

总结 310

市场购物篮分析 313

介绍 314

市场购物篮分析 314

应用案例 317

重要的概率性指标 318

练习 8.01:创建样本事务数据 319

支持度 321

置信度 322

提升度和杠杆度 323

信念度 324

练习 8.02:计算指标 325

事务数据的特征 328

练习 8.03:加载数据 329

数据清理和格式化 333

练习 8.04:数据清理和格式化 334

数据编码 339

练习 8.05:数据编码 341

活动 8.01:加载和准备完整的在线零售数据 343

Apriori 算法 344

计算修正 347

练习 8.06:执行 Apriori 算法 348

活动 8.02:在完整的在线零售数据集上运行 Apriori 算法 354

关联规则 356

练习 8.07:推导关联规则 358

活动 8.03:在完整的在线零售数据集上找出关联规则 365

总结 367

热点分析 369

介绍 370

空间统计 371

概率密度函数 372

在商业中使用热点分析 374

核密度估计 375

带宽值 376

练习 9.01:带宽值的影响 376

选择最优带宽 380

练习 9.02:使用网格搜索选择最优带宽 381

核函数 384

练习 9.03:核函数的影响 387

核密度估计推导 389

练习 9.04:模拟核密度估计推导 389

活动 9.01:一维密度估计 393

热点分析 394

练习 9.05:使用 Seaborn 加载数据和建模 396

练习 9.06:与底图一起工作 404

活动 9.02:分析伦敦犯罪 411

总结 414

附录 417

前言

关于本书

你是否觉得很难理解像 WhatsApp 和 Amazon 这样的知名公司如何从大量杂乱无章的数据中提取有价值的洞察?无监督学习工作坊将让你自信地应对混乱且没有标签的数据集,以轻松互动的方式使用无监督算法。

本书从介绍最流行的无监督学习聚类算法开始。你将了解层次聚类与 k-means 的不同,并理解如何将 DBSCAN 应用于复杂且噪声较多的数据。接下来,你将使用自编码器进行高效的数据编码。

随着学习的深入,你将使用 t-SNE 模型将高维信息转换为低维,以便更好地可视化,同时还会使用主题建模来实现自然语言处理。在后续章节中,你将使用市场篮分析来发现顾客和商家之间的关键关系,然后使用热点分析来估算某一地区的人口密度。

到本书结束时,你将掌握在混乱的数据集中应用无监督算法来发现有用模式和洞察的技能。

读者群体

如果你是刚入门的数据科学家,想要学习如何实现机器学习算法以构建预测模型,那么本书适合你。为了加快学习进程,建议你具备扎实的 Python 编程语言基础,因为你将编辑类和函数,而不是从零开始创建它们。

关于各章节

第一章聚类简介,介绍了聚类(无监督学习中最知名的算法家族),然后深入讲解最简单、最流行的聚类算法——k-means。

第二章层次聚类,讲解了另一种聚类技术——层次聚类,并说明它与 k-means 的不同。本章将教你两种主要的聚类方法:凝聚型和分裂型。

第三章邻域方法和 DBSCAN,探讨了涉及邻居的聚类方法。与另外两种聚类方法不同,邻域方法允许存在未被分配到任何特定聚类的异常点。

第四章降维与 PCA,教你如何通过主成分分析来减少特征数量,同时保持整个特征空间的解释能力,从而在大型特征空间中导航。

第五章自编码器,向你展示如何利用神经网络找到数据编码。数据编码就像是特征的组合,能够降低特征空间的维度。自编码器还会解码数据并将其恢复到原始形式。

第六章t-分布随机邻居嵌入,讨论了将高维数据集降维到二维或三维进行可视化的过程。与 PCA 不同,t-SNE 是一种非线性概率模型。

第七章主题建模,探讨了自然语言处理的基本方法论。你将学习如何处理文本数据,并将潜在的狄利克雷分配和非负矩阵分解模型应用于标记与文本相关的主题。

第八章市场篮子分析,探讨了零售业务中使用的经典分析技术。你将以可扩展的方式,构建解释项目组之间关系的关联规则。

第九章热点分析,教你如何使用样本数据估算某些随机变量的真实人口密度。该技术适用于许多领域,包括流行病学、天气、犯罪和人口学。

约定

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

“使用我们从matplotlib.pyplot导入的散点图功能绘制坐标点。”

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

一段代码如下所示:

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
seeds = pd.read_csv('Seed_Data.csv')

新术语和重要词汇显示如下:

无监督学习是一个实践领域,旨在帮助在杂乱的数据中找到模式,是当前机器学习中最令人兴奋的发展领域之一。”

长的代码片段会被截断,并在截断代码的顶部放置对应的 GitHub 代码文件名称。整个代码的永久链接将放在代码片段下方。应该如下所示:

Exercise1.04-Exercise1.05.ipynb
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)
The complete code for this step can be found at https://packt.live/2JM8Q1S.

代码呈现

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

例如:

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

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

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

多行注释被三引号包围,如下所示:

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

设置你的环境

在我们详细探讨本书之前,我们需要设置特定的软件和工具。在接下来的部分中,我们将看到如何进行这些操作。

硬件要求

为了获得最佳用户体验,我们推荐 8GB 内存。

安装 Python

接下来的部分将帮助你在 Windows、macOS 和 Linux 系统中安装 Python。

在 Windows 上安装 Python

  1. 在官方安装页面www.python.org/downloads/windows/上找到你所需的 Python 版本。

  2. 确保根据你的计算机系统安装正确的“-bit”版本,可以是 32-bit 或 64-bit。你可以在操作系统的 系统属性 窗口中查看此信息。

  3. 下载安装程序后,简单地双击文件并按照屏幕上的用户友好提示进行操作。

在 Linux 上安装 Python

  1. 打开终端并通过运行 python3 --version 验证 Python 3 是否已安装。

  2. 要安装 Python 3,运行以下命令:

    sudo apt-get update
    sudo apt-get install python3.7
    
  3. 如果遇到问题,有很多在线资源可以帮助你排查问题。

在 macOS 上安装 Python

以下是在 macOS 上安装 Python 的步骤:

  1. 通过按住 Cmd + Space,在打开的搜索框中输入 terminal,然后按 Enter 打开终端。

  2. 通过运行 xcode-select --install 命令在命令行中安装 Xcode。

  3. 安装 Python 3 最简单的方法是使用 Homebrew,可以通过运行 ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 在命令行中安装 Homebrew。

  4. 将 Homebrew 添加到你的 PATH 环境变量中。在命令行中运行 sudo nano ~/.profile 打开你的配置文件,并在文件底部插入 export PATH="/usr/local/opt/python/libexec/bin:$PATH"

  5. 最后一步是安装 Python。在命令行中运行 brew install python

  6. 请注意,如果安装 Anaconda,最新版本的 Python 将会自动安装。

安装 pip

Python 默认不包含 pip(Python 的包管理器),因此我们需要手动安装它。一旦安装了 pip,就可以按照 安装库 部分提到的方法安装其余的库。安装 pip 的步骤如下:

  1. 转到 bootstrap.pypa.io/get-pip.py 并将文件保存为 get-pip.py

  2. 转到保存get-pip.py的文件夹。在该文件夹中打开命令行(Linux 用户使用 Bash,Mac 用户使用 Terminal)。

  3. 在命令行中执行以下命令:

    python get-pip.py
    

    请注意,你应该先安装 Python 后再执行此命令。

  4. 一旦安装了 pip,你就可以安装所需的库。要安装 pandas,只需执行 pip install pandas。要安装某个特定版本的库,例如 pandas 的版本 0.24.2,可以执行 pip install pandas=0.24.2

安装 Anaconda

Anaconda 是一个 Python 包管理器,能够轻松地安装并使用本课程所需的库。

在 Windows 上安装 Anaconda

  1. Windows 上的 Anaconda 安装非常用户友好。请访问下载页面,在 www.anaconda.com/distribution/#download-section 获取安装可执行文件。

  2. 双击计算机上的安装程序。

  3. 按照屏幕上的提示完成 Anaconda 的安装。

  4. 安装完成后,你可以访问 Anaconda Navigator,它将像其他应用程序一样正常显示。

在 Linux 上安装 Anaconda

  1. 访问 Anaconda 下载页面,获取安装 shell 脚本,网址为www.anaconda.com/distribution/#download-section

  2. 要直接将 shell 脚本下载到你的 Linux 实例中,你可以使用curlwget下载库。下面的示例展示了如何使用curl从 Anaconda 下载页面找到的 URL 下载文件:

    curl -O https://repo.anaconda.com/archive/Anaconda3-2019.03-Linux-x86_64.sh
    
  3. 下载完 shell 脚本后,你可以使用以下命令运行它:

    bash Anaconda3-2019.03-Linux-x86_64.sh
    

    运行上述命令后,你将进入一个非常用户友好的安装过程。系统会提示你选择安装 Anaconda 的位置以及如何配置 Anaconda。在这种情况下,你只需保持所有默认设置。

在 macOS X 上安装 Anaconda

  1. 在 macOS 上安装 Anaconda 非常用户友好。访问下载页面获取安装可执行文件,网址为www.anaconda.com/distribution/#download-section

  2. 确保选择了 macOS,并双击Download按钮以下载 Anaconda 安装程序。

  3. 按照屏幕上的提示完成 Anaconda 安装。

  4. 安装完成后,你可以访问 Anaconda Navigator,它将像其他应用程序一样正常显示。

设置虚拟环境

  1. 安装完 Anaconda 后,你必须创建环境来安装你希望使用的包。Anaconda 环境的一个优点是,你可以为你正在进行的具体项目构建独立的环境。要创建新的环境,使用以下命令:

    conda create --name my_packt_env python=3.7
    

    在这里,我们将环境命名为my_packt_env并指定 Python 版本为 3.7。这样,你可以在环境中安装多个版本的 Python,这些版本将是虚拟隔离的。

  2. 创建环境后,你可以使用名称恰当的activate命令激活它:

    conda activate my_packt_env
    

    就这样。你现在已经进入了自己的定制化环境,可以根据项目需要安装包。要退出环境,你只需使用conda deactivate命令。

安装库

pip 已预先安装在 Anaconda 中。一旦 Anaconda 安装到你的计算机上,你可以使用pip安装所有所需的库,例如,pip install numpy。或者,你可以使用pip install –r requirements.txt安装所有必需的库。你可以在packt.live/2CnpCEp找到requirements.txt文件。

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

第九章热点分析中,使用了 mpl_toolkits 中的 basemap 模块来生成地图。这个库可能会很难安装。最简单的方法是安装 Anaconda,它包含了 mpl_toolkits。安装 Anaconda 后,可以通过 conda install basemap 来安装 basemap。如果你希望避免重复安装库,而是一次性安装所有库,你可以按照下一节的说明进行操作。

设置机器

如果你是按章节逐步安装依赖项,可能会遇到库的版本不同的情况。为了同步系统,我们提供了一个包含所用库版本的 requirements.txt 文件。使用此文件安装库后,你就不需要在整本书中再次安装任何其他库。假设你现在已经安装了 Anaconda,可以按照以下步骤进行操作:

  1. 从 GitHub 下载 requirements.txt 文件。

  2. 转到 requirements.txt 文件所在的文件夹,并打开命令提示符(Linux 为 Bash,Mac 为 Terminal)。

  3. 在其上执行以下命令:

    conda install --yes --file requirements.txt --channel conda-forge
    

    它应该会安装本书中所有编程活动所需的包。

访问代码文件

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

我们已尽力支持所有活动和练习的交互式版本,但我们仍然推荐进行本地安装,以便在无法获得此支持的情况下使用。

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

第二章:1. 聚类介绍

概述

在数据中寻找洞察和价值是机器学习崛起时所承诺的雄心壮志。在机器学习中,有些方法是通过预测性方式深入理解密集信息,还有一些方法则是根据输入的变化来预测结果。在本章中,我们将了解监督学习和无监督学习是什么,以及它们如何应用于不同的使用场景。一旦你对无监督学习的应用领域有了更深入的理解,我们将逐步介绍一些能快速提供价值的基础技术。

到本章结束时,你将能够使用内置的 Python 包实现 k-means 聚类算法,并计算轮廓系数。

介绍

你有没有被要求查看一些数据,但最终一无所获?也许你对数据集不熟悉,或者甚至不知道从哪里开始。这可能让你感到非常沮丧,甚至尴尬,尤其是如果是别人要求你负责这项任务的话。

你不是孤单的,有趣的是,数据本身有时也太混乱,无法理清。当你试图弄清楚电子表格中那些数字的意义时,你很可能是在模仿许多无监督算法的做法,它们试图从数据中找到意义。现实情况是,许多未经处理的真实世界数据集可能没有任何有用的见解。一个值得考虑的例子是,现如今,个人每天都会生成大量的细粒度数据——无论是他们在网站上的行为、购买历史,还是他们手机上使用的应用程序。如果你仅仅从表面上查看这些信息,它会是一团大杂烩,完全没有任何明确的意义。但别担心;本书将帮助你准备好应对这些繁重的任务,让你在处理数据探索任务时不再感到沮丧,无论数据有多庞大。

本书中,我们开发了一些最佳的内容,帮助你理解无监督算法如何工作以及如何使用它们。我们将涵盖如何在数据中找到聚类的基础知识,如何减少数据的大小以便更容易理解,以及无监督学习的这些方面如何应用于现实世界。我们希望你能从本书中收获关于无监督学习的扎实实际理解,了解它能解决的问题以及无法解决的问题。

无监督学习与监督学习

无监督学习是帮助在杂乱数据中寻找模式的领域,是当前机器学习中最令人兴奋的一个发展方向。如果你曾经研究过机器学习的书籍,你大概熟悉问题通常会分为监督学习或无监督学习这两种类型。监督学习涵盖了有标记数据集的问题集,数据集可以用于对数据进行分类(例如,在分析肺部健康数据集时预测吸烟者和非吸烟者)或在明确定义的数据中寻找模式(例如,根据房屋的卧室数量预测房屋的售价)。该模型最接近人类直观的学习方式。

例如,如果你想学习如何在基本的烹饪知识下避免烧焦食物,你可以通过将食物放在炉灶上并观察食物烧焦所需的时间(输入),来构建一个数据集(输出)。随着你不断地烧焦食物,你将建立一个心理模型,了解何时会发生烧焦并学会如何避免将来再发生。监督学习的发展曾经是快速且有价值的,但近年来已经逐渐平稳。许多关于了解数据的障碍已经得到解决,并在以下图片中列出:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_01.jpg

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

图 1.1:无监督学习和监督学习的区别

相反,无监督学习涵盖了拥有大量未标记数据的问题集。在这种情况下,标记数据是指具有给定“目标”结果的数据,你需要通过提供的数据找出与之相关的关系。例如,在前面的例子中,你知道你的“目标结果”是食物是否烧焦;这就是标记数据的一个例子。未标记数据则是你不知道“目标”结果是什么,只提供了输入数据。

基于前面的例子,假设你被放到地球上,完全不知道烹饪是怎么回事。你被给了 100 天时间,一个炉灶,以及一冰箱满满的食物,却没有任何指导说明该做什么。你初次探索厨房的过程可能会走向无数个方向。第 10 天,你可能终于学会如何打开冰箱;第 30 天,你可能学会了食物可以放到炉灶上;而经过更多的时间,你可能会不小心做出一顿可以食用的饭菜。正如你所看到的,试图在没有足够信息结构的厨房里找到意义,往往会导致产生非常杂乱无序的数据,这些数据对于实际做饭完全没有用处。

无监督学习可能是解决此问题的答案。回顾你的 100 天数据,你可以使用聚类来找出各天之间相似属性的模式,并推断哪些食物相似,可能会导致一顿“好”饭。然而,无监督学习并不是一种神奇的解决方案。仅仅找到聚类,同样可能帮助你发现一些相似却最终没有用的数据。以烹饪为例,我们可以通过“第三变量”的概念来说明这一缺点。仅仅因为你有一个很好的菜谱聚类,并不意味着它们是无懈可击的。在你的研究过程中,你可能发现一个统一的因素,所有的好饭菜都是在炉子上做的。这并不意味着每一顿在炉子上做的饭菜都会是好饭菜,而且你不能轻易地把这个结论套用到所有未来的场景中。

这正是使无监督学习如此令人兴奋的挑战。我们如何找到更智能的技术,加速找到对最终目标有益的信息聚类的过程?以下章节将帮助我们回答这个问题。

聚类

聚类是一个涉及在数据集中找到相似数据组的总体过程,如果你试图发现数据的潜在含义,它可以非常有价值。如果你是一个商店老板,想要了解哪些顾客更有价值,但又没有确切的价值定义,聚类是一个很好的起点,可以帮助你在数据中找出模式。你可能有一些关于什么是有价值顾客的高层次想法,但在面对大量可用数据时,你并不完全确定。通过聚类,你可以发现数据中相似群体之间的共性。例如,如果你深入观察一个相似群体的聚类,你可能会发现这个群体的每个人在你的网站上停留的时间都比其他人长。这可以帮助你了解什么是价值,并为未来的监督学习实验提供干净的样本数据。

确定聚类

以下图像显示了两个散点图:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_02.jpg

图 1.2:两个不同的散点图

以下图像将这两个散点图分成了两个不同的聚类:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_03.jpg

图 1.3:清晰显示提供的数据集中存在聚类的散点图

图 1.2图 1.3 显示了从两个不同位置的高斯分布中随机生成的数对(x 和 y 坐标)。仅通过看第一张图,应该很容易看出数据中存在的聚类;但在现实中,情况绝不会如此简单。现在你已经知道数据可以清晰地分成两个聚类,你可以开始理解这两个群体之间存在的差异。

从无监督学习在更大机器学习环境中的定位开始,让我们先了解聚类的基本构建块。最基本的定义将聚类视为一个较大数据集的子集,即相似数据的分组。举个例子,假设你有一个房间,里面有 10 个人,每个人的工作要么是在金融领域,要么是科学家。如果你让所有金融工作者站到一起,所有科学家也站在一起,你就已经根据职业类型有效地形成了两个聚类。寻找聚类可以在识别相似的项时非常有价值,而在另一端,它也能帮助识别出彼此间有较大差异的项。

二维数据

为了理解这一点,假设你从雇主那里获得了一个简单的 1,000 行数据集,包含两列数值数据,如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_04.jpg

图 1.4:二维原始数据在数组中的表示

初看之下,这个数据集没有提供任何实际的结构或理解。

数据集中的维度是另一种简单的方式,用来计数可用特征的数量。在大多数有组织的数据表中,你可以通过列数来查看特征的数量。因此,使用一个 1,000 行的数据集示例,大小为(1,000 x 2),你将有 1,000 个观测值,跨越两个维度。请注意,数据集的维度不应与数组的维度混淆。

你可以通过将第一列与第二列进行绘图,以便更好地了解数据结构。会有很多时候,组之间差异的原因可能显得微不足道;然而,能够采取行动的那些差异会极具回报。

练习 1.01:识别数据中的聚类

你会得到两个维度的数据图表,怀疑这些数据可能存在相似的聚类。请查看练习中提供的二维图表,并识别数据点的分组,目的是强调机器学习的重要性。在不使用任何算法方法的情况下,识别这些聚类在数据中的位置。

这个练习将帮助你建立直觉,了解我们如何使用自己的眼睛和思维过程来识别聚类。在完成这个练习时,思考为什么一组数据点应该被视为一个聚类,而另一组数据点则不应被视为聚类。按照以下步骤完成这个练习:

  1. 在以下散点图中识别聚类:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_05.jpg

    图 1.5:二维散点图

    聚类如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_06.jpg

    图 1.6:散点图中的聚类

  2. 在以下散点图中识别聚类:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_07.jpg

    图 1.7:二维散点图

    聚类如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_08.jpg

    图 1.8:散点图中的聚类

  3. 在下图中识别聚类:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_09.jpg

图 1.9:二维散点图

聚类如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_10.jpg

图 1.10:散点图中的聚类

这些例子大多数对你来说应该非常容易理解,这正是重点。人类的大脑和眼睛在发现现实世界中的模式方面非常出色。在看到每个图表的毫秒内,你就能分辨出哪些是匹配的,哪些不是。虽然对你来说很容易,但计算机并不具备像我们一样查看和处理图表的能力。

然而,这并不总是坏事。回顾前面的散点图。你能仅通过查看该图就找到数据中的六个离散聚类吗?你可能只找到了三到四个聚类,而计算机能够看到所有六个。人类的大脑非常强大,但它也缺乏基于严格逻辑的方法所带来的细节。通过算法聚类,你将学会如何建立一个比人类更擅长完成这些任务的模型。

我们将在下一节中介绍聚类算法。

k-means 聚类简介

希望到现在为止,你能看到在机器学习工作流中,寻找聚类是非常有价值的。但是,你如何实际找到这些聚类呢?一种最基本但又非常流行的方法是使用一种叫做 k-means 聚类 的聚类分析技术。k-means 聚类通过在数据中寻找 k 个聚类来工作,这个工作流实际上非常直观。我们将从无数学推导的 k-means 介绍开始,然后进行 Python 实现。聚类成员是指在算法处理数据时,点被分配到哪里。你可以将其看作是为一支运动队挑选队员,其中所有队员都在一个池中,但每一次运行后,队员会被分配到某个队伍(在这里是某个聚类)。

无数学推导的 k-means 算法演示

无数学推导的 k-means 聚类算法非常简单:

  1. 首先,我们会选择“k”个质心,其中“k”是我们预期的聚类数量。k 的值由我们选择,决定了我们得到的聚类类型。

  2. 然后,我们会将“k”个质心随机放置在现有的训练数据中。

  3. 接下来,将计算每个质心到训练数据中所有点的距离。我们稍后会详细讲解距离函数,但现在,我们先把它当作是点与点之间的距离。

  4. 现在,所有的训练点将与其最近的质心分组。

  5. 将分组的训练点与各自的质心分开,计算该组中的均值数据点,并将之前的质心移动到均值位置。

  6. 这个过程需要重复,直到收敛或达到最大迭代次数。

就是这样。下面的图像表示原始原始数据:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_11.jpg

图 1.11:原始原始数据在 x 和 y 坐标上的图示

根据前面图像中的原始数据,我们可以通过展示每一步的预测聚类来可视化 k-means 的迭代过程:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_12.jpg

图 1.12:从左到右读取,红色点是随机初始化的质心,并将最近的数据点分配给每个质心的分组

K-means 聚类深入讲解

为了更深入地理解 k-means,我们再通过引言中提供的示例,并结合一些支持 k-means 的数学原理进行讲解。支撑这个算法的最重要的数学公式是距离函数。距离函数基本上是任何能够定量地表示一个物体距离另一个物体有多远的公式,其中最常用的是欧几里得距离公式。这个公式通过相减每个点的相应组件并平方以去除负值,然后将结果的距离加起来并开平方:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_13.jpg

图 1.13:欧几里得距离公式

如果你注意到,前面的公式适用于只有二维数据点(坐标数)的情况。表示高维数据点的一般方式如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_14.jpg

图 1.14:高维点的欧几里得距离公式

让我们来看一下在高维空间中计算两点 pq 之间欧几里得距离时涉及的术语。这里,n 是这两个点的维度数。我们计算点 pq 的相应组件之间的差异(pi 和 qi 分别是点 pq 的第 i 组件),并将每个差值平方。然后,将所有 n 维度的平方差值相加,再对这个和开平方。这个值表示点 pq 之间的欧几里得距离。如果你将 n = 2 代入前面的公式,它将分解为图 1.13 中表示的公式。

现在再次回到我们关于 k-means 的讨论。质心在开始时会随机设置为你 n 维空间中的点。这些质心中的每一个都作为 (a, b) 输入到前面的公式中,而空间中的一个点则作为 (x, y) 输入。然后计算每个点与每个质心坐标之间的距离,选择距离最短的质心作为该点的分组。

举个例子,我们选择三个随机质心,一个任意点,并使用欧几里得距离公式,计算从每个点到质心的距离:

  • 随机质心:[ (2,5), (8,3), (4,5) ]。

  • 任意点 x:(0, 8)。

  • 从点到每个质心的距离:[ 3.61, 9.43, 5.00 ]。

由于任意点 x 最接近第一个质心,它将被分配给第一个质心。

曼哈顿距离:替代距离度量

欧几里得距离是许多机器学习应用中最常用的距离度量,并且通常被俗称为距离度量;然而,它并不是唯一的,甚至在某些情况下也不是最佳的距离度量。另一个流行的可以用于聚类的距离度量是曼哈顿距离

曼哈顿距离之所以叫这个名字,是因为它类似于在拥有许多方形街区的大城市(例如纽约市)中旅行的概念。欧几里得距离由于基于勾股定理而依赖于对角线,而曼哈顿距离则将距离限制为仅在直角之间。曼哈顿距离的公式如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_15.jpg

图 1.15:曼哈顿距离公式

这里 pi 和 qi 分别是点 pq 的第 i 个分量。基于我们之前讨论的欧几里得距离的例子,如果我们想找到两个点之间的距离,假设这两个点分别是 (1,2) 和 (2,3),那么曼哈顿距离将等于 |1-2| + |2-3| = 1 + 1 = 2。这种功能可以扩展到任何维度。在实践中,当处理高维数据时,曼哈顿距离可能会优于欧几里得距离。

更高维度

当数据只有二维时,前面的示例可以清晰地可视化。这是为了方便起见,帮助阐明 k-means 的工作原理,同时可能会导致你误解聚类的容易程度。在你自己的许多应用中,数据可能会大得多,以至于无法通过可视化来感知(超过三维的数据对于人类来说是不可感知的)。在前面的示例中,你可以通过脑中计算几个二维的线条来将数据分成不同的组。在更高维度的情况下,你需要借助计算机来找到一个 n 维超平面,合理地分隔数据集。实际上,这就是 k-means 等聚类方法提供显著价值的地方。下图展示了二维、三维和 n 维的图形:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_16.jpg

图 1.16:二维、三维和 n 维图形

在下一个练习中,我们将计算欧几里得距离。我们将通过使用 NumPyMath Python 包来构建工具集。NumPy 是一个用于 Python 的科学计算包,它将常见的数学函数预先打包为高度优化的格式。

顾名思义,Math 包是一个基础库,它使实现基础数学构件(如指数和平方根)变得更加容易。通过使用像 NumPyMath 这样的包,我们可以减少从头开始创建自定义数学函数的时间,而是专注于开发解决方案。你将在接下来的练习中看到如何实际使用这些包。

练习 1.02:在 Python 中计算欧几里得距离

在这个练习中,我们将创建一个示例点,以及三个示例质心,帮助说明欧几里得距离的工作原理。理解这个距离公式是我们在聚类中工作的基础。

执行以下步骤以完成此练习:

  1. 打开 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))
    \ ) to split the logic across multiple lines. When the code is executed, Python will ignore the backslash, and treat the code on the next line as a direct continuation of the current line.
    

    这种方法被认为是幼稚的,因为它对数据点进行逐元素计算(较慢),与使用向量和矩阵数学的更现实的实现相比,后者可以显著提高性能。

  2. 在 Python 中创建数据点,如下所示:

    centroids = [ (2, 5), (8, 3), (4,5) ]
    x = (0, 8)
    
  3. 使用你创建的公式来计算 步骤 1 中的欧几里得距离:

    # Calculating Euclidean Distance between x and centroid
    centroid_distances =[]
    for centroid in centroids:
        print("Euclidean Distance between x {} and centroid {} is {}"\
              .format(x ,centroid, dist(x,centroid)))
        centroid_distances.append(dist(x,centroid))
    Euclidean Distance between x (0, 8) and centroid (2, 5) 
    is 3.605551275463989
    Euclidean Distance between x (0, 8) and centroid (8, 3) 
    is 9.433981132056603
    Euclidean Distance between x (0, 8) and centroid (4, 5) is 5.0
    

    我们的点 x 与质心之间的最短距离是 3.61,相当于 (0, 8)(2, 5) 之间的距离。由于这是最小距离,我们的示例点 x 将被分配到第一个质心所在的组。

在这个例子中,我们的公式应用于一个单独的点 x (0, 8)。在这个单点之后,相同的过程将对数据集中的每个剩余点重复,直到每个点都被分配到一个簇。每分配一个点后,都会计算该簇内所有点的均值。计算这些点的均值与计算单个整数的均值是一样的。

虽然这个示例中只有一个点,但通过完成这个过程,你实际上已经使用欧几里得距离将一个点分配到了它的第一个簇。我们将在下一个练习中使用多个点来扩展这个方法。

注意

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

你也可以在线运行这个示例,网址是packt.live/3ebDwpZ

练习 1.03:使用距离的概念形成簇

对我们人类的大脑来说,看到图中的点组并判断哪些点属于不同的簇是非常直观的。然而,如何让计算机重复执行这一任务呢?在这个练习中,你将帮助计算机学习如何使用“距离”的概念来形成自己的簇。我们将在下一个练习中进一步探讨如何使用这些距离度量:

  1. 创建一个点的列表,[ (0,8), (3,8), (3,4) ],它们被分配到第一个簇:

    cluster_1_points =[ (0,8), (3,8), (3,4) ]
    
  2. 要在点集列表中找到新的质心,计算所有点的平均点。均值的计算适用于无限个点,你只需将每个位置的整数相加并除以点的总数。例如,如果你的两个点是(0,1,2)和(3,4,5),那么均值计算为[ (0+3)/2, (1+4)/2, (2+5)/2 ]:

    mean =[ (0+3+3)/3, (8+8+4)/3 ]
    print(mean)
    

    输出结果如下:

    [2.0, 6.666666666666667]
    

    计算出新的质心后,重复我们在练习 1.02中看到的簇成员计算,在 Python 中计算欧几里得距离,然后重复前面两个步骤以找到新的簇质心。最终,新的簇质心将与簇成员计算之前的质心相同,练习将完成。这个过程重复多少次取决于你正在聚类的数据。

    一旦你将质心位置移动到新的均值点(2, 6.67),你可以将其与最初输入问题时的质心列表进行比较。如果新的均值点与当前列表中的质心不同,你将需要再次执行前面两个练习的迭代过程。一旦你计算出的新均值点与开始时的质心相同,就意味着你完成了一次 k-means 运行,并达到了一个叫做收敛的点。然而,在实际操作中,达到收敛所需的迭代次数可能非常大,这种大规模计算在实践中可能不可行。在这种情况下,我们需要设定一个最大迭代次数限制。一旦达到这个迭代限制,我们就停止进一步处理。

    注意

    要获取本节的源代码,请参考packt.live/3iJ3JiT

    你还可以在线运行此示例,访问链接:packt.live/38CCpOG

在下一个练习中,我们将从头实现 k-means。为此,我们将开始使用 Python 生态系统中的常见包,这些包将作为你职业生涯中其他部分的构建模块。最受欢迎的机器学习库之一是 scikit-learn(scikit-learn.org/stable/user_guide.html),它包含许多内置的算法和函数,支持你理解算法的工作原理。我们还将使用 SciPy(docs.scipy.org/doc/scipy/reference/)中的函数,它是一个类似 NumPy 的包,抽象出基本的科学数学函数,从而实现更高效的部署。最后,下一个练习将介绍matplotlibmatplotlib.org/3.1.1/contents.html),这是一个绘图库,可以创建你所处理数据的图形表示。

练习 1.04:从头实现 K-means – 第一部分:数据生成

接下来的两个练习将专注于生成练习数据和从头实现 k-means 算法。这个练习依赖于 scikit-learn,一个开源的 Python 包,能够快速原型化流行的机器学习模型。在 scikit-learn 中,我们将使用datasets功能来创建一个合成的 blob 数据集。除了利用 scikit-learn 的强大功能,我们还将依赖于 Matplotlib,这是一个流行的 Python 绘图库,使我们能够轻松地可视化数据。为此,按照以下步骤操作:

  1. 导入必要的库:

    from sklearn.datasets import make_blobs
    from sklearn.cluster import KMeans
    import matplotlib.pyplot as plt
    import numpy as np
    import math
    np.random.seed(0)
    %matplotlib inline
    

    注意

    你可以在KMeans库的官方文档中找到更多细节:scikit-learn.org/stable/modules/clustering.html#k-means

  2. 生成一个随机的聚类数据集来进行实验,X = 坐标点,y = 聚类标签,并定义随机的质心。我们将使用从sklearn.datasets导入的make_blobs函数来实现,正如其名字所示,它生成数据点的簇。

    X, y = make_blobs(n_samples=1500, centers=3, \
                      n_features=2, random_state=800)
    centroids = [[-6,2],[3,-4],[-5,10]]
    

    这里,n_samples参数决定了由数据点簇生成的总数据点数量。centers参数决定了数据簇的质心数量。n_feature属性定义了数据集生成的维度数量。这里,数据将是二维的。

    为了在所有迭代中生成相同的数据点(这些数据点是随机生成的),以保证结果的可复现性,我们将random_state参数设置为800random_state参数的不同值会产生不同的结果。如果不设置random_state参数,每次执行时都会获得不同的结果。

  3. 打印数据:

    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]])
    
  4. 使用从matplotlib.pyplot导入的散点图功能绘制坐标点。此函数接受坐标点的输入列表,并将其图形化展示,以便更容易理解。如果您想更深入地了解该函数提供的参数,请查阅matplotlib文档:

    plt.scatter(X[:, 0], X[:, 1], s=50, cmap='tab20b')
    plt.show()
    

    绘图如下所示:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_17.jpg

    图 1.17:坐标图

  5. 打印y数组,该数组是由 scikit-learn 提供的标签,并作为比较的基准真值。

    y
    

    输出如下:

    array([2, 2, 1, ..., 1, 0, 2])
    
  6. 使用正确的聚类标签绘制坐标点:

    plt.scatter(X[:, 0], X[:, 1], c=y,s=50, cmap='tab20b')
    plt.show()
    

    绘图如下所示:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_18.jpg

图 1.18:带有正确聚类标签的坐标图

通过完成前面的步骤,您已经生成了数据,并通过可视化了解了数据的构成。通过可视化基准真值,您为算法准确性提供了一个相对度量的基准。

注意

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

您还可以在packt.live/3ecjKdT在线运行此示例。

拥有数据后,在接下来的练习中,我们将继续构建无监督学习工具集,使用从SciPy包中优化过的欧几里得距离函数cdist。您将比较一个非向量化的、易于理解的版本和cdist,后者经过特别优化,以实现最佳性能。

练习 1.05:从头开始实现 K-means 算法——第二部分:实现 K-means

让我们自己重现这些结果。我们将通过一个示例进行讲解,并进行一些优化。

注意

本练习是上一练习的延续,应在同一个 Jupyter notebook 中进行。

本次练习,我们将依赖于 SciPy,这是一个 Python 包,允许轻松访问高效优化的科学计算版本。特别地,我们将使用 cdist 实现欧几里得距离,其功能在更高效的方式下复制了我们度量距离的基本实现。请按照以下步骤完成此练习:

  1. 本练习的基础将是将一个基本的欧几里得距离实现与 SciPy 提供的优化版本进行比较。首先,导入优化的欧几里得距离参考:

    from scipy.spatial.distance import cdist
    
  2. 确定您想要探索的 X 子集。对于本例,我们仅选择了五个点以使讲解更清晰;然而,这种方法适用于任意数量的点。我们选择了点 105-109(包括 105 和 109):

    X[105:110]
    

    输出结果如下:

    array([[-3.09897933,  4.79407445],
           [-3.37295914, -7.36901393],
           [-3.372895  ,  5.10433846],
           [-5.90267987, -3.28352194],
           [-3.52067739,  7.7841276 ]])
    
  3. 计算距离并选择最短距离的索引作为一个簇:

    """
    Finds distances from each of 5 sampled points to all of the centroids
    """
    for x in X[105:110]:
        calcs = cdist(x.reshape([1,-1]),centroids).squeeze()
        print(calcs, "Cluster Membership: ", np.argmin(calcs))
    [4.027750355981394, 10.70202290628413, 5.542160268055164] 
     Cluster Membership:  0
    [9.73035280174993, 7.208665829113462, 17.44505393393603] 
     Cluster Membership:  1
    [4.066767506545852, 11.113179986633003, 5.1589701124301515] 
     Cluster Membership:  0
    [5.284418164665783, 8.931464028407861, 13.314157359115697] 
     Cluster Membership:  0
    [6.293105164930943, 13.467921029846712, 2.664298385076878] 
     Cluster Membership:  2
    
  4. 如下所示定义 k_means 函数并随机初始化 k-中心。重复此过程,直到新旧 centroids 之间的差异为 0,使用 while 循环:

    Exercise1.04-Exercise1.05.ipynb
    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)
    The complete code for this step can be found at https://packt.live/2JM8Q1S.
    

    注意

    请不要中断此代码,因为这可能会导致错误。

  5. 将历史步骤的中心和它们的标签捆绑在一起:

    history = zip(centers_hist, labels_hist)
    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()
    

    如果没有设置随机种子,以下图可能会与您看到的不同。第一个图如下所示:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_19.jpg

图 1.19:第一个散点图

第二个图如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_20.jpg

图 1.20:第二个散点图

第三个图如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_21.jpg

图 1.21:第三个散点图

第四个图如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_22.jpg

图 1.22:第四个散点图

第五个图如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_23.jpg

图 1.23:第五个散点图

如前面图片所示,k-means 使用迭代方法根据距离优化集群。该算法从随机初始化中心开始,并根据数据的复杂度,迅速找到最合理的分割。

注意

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

您也可以在线运行此示例,网址是 packt.live/3ecjKdT

聚类性能 – 轮廓分数

理解无监督学习方法的表现本质上比监督学习方法要困难得多,因为没有可用的真实标签。对于监督学习,有许多强健的性能指标,其中最直接的就是通过比较模型预测的标签与实际标签,看看模型预测对了多少个。不幸的是,对于聚类,我们没有标签可依赖,需要建立对我们的簇有多“不同”的理解。我们通过轮廓得分指标来实现这一点。我们还可以使用轮廓得分来找到无监督学习方法的最佳“K”值。

轮廓指标通过分析一个点在其簇内的拟合程度来工作。该指标的范围从-1 到 1。如果你的聚类的平均轮廓得分为 1,那么你将获得完美的簇,并且对于每个点属于哪个簇几乎没有任何混淆。在前面的练习中的图形中,轮廓得分将接近 1,因为这些簇紧密聚集,并且每个簇之间有相当大的距离。然而,这种情况非常罕见;轮廓得分应该被视为尽力而为的一个尝试,因为得到 1 是非常不可能的。如果轮廓得分为正,意味着该点离其分配的簇比离邻近的簇更近。如果轮廓得分为 0,则表示该点位于分配的簇和下一个最接近簇之间的边界上。如果轮廓得分为负,则表示该点被分配到了错误的簇,实际上该点可能属于邻近的簇。

从数学上讲,轮廓得分的计算相当简单,可以通过简化轮廓指数SSI)来获得:

SSIi = bi - ai/ max(ai, bi)

这里的ai 是点i到其自身簇质心的距离,bi 是点i到最近簇质心的距离。

这里捕捉到的直觉是,ai 表示点i所在簇的凝聚力,即它作为一个清晰簇的程度,而 bi 表示各簇之间的距离。我们将在活动 1.01中使用 scikit-learn 中silhouette_score的优化实现,实现 k-means 聚类。使用这个方法很简单,只需要传入特征数组和来自 k-means 聚类方法的预测簇标签。

在下一个练习中,我们将使用pandas库(pandas.pydata.org/pandas-docs/stable/)来读取 CSV 文件。Pandas 是一个 Python 库,通过 DataFrame 简化数据处理。如果回顾您使用 NumPy 构建的数组,您可能会注意到,最终的数据结构相当笨重。为了从数据中提取子集,您需要使用方括号和特定的行号进行索引。与这种方法不同,pandas 提供了一种更易于理解的数据操作方法,使得将数据移到适合无监督学习及其他机器学习技术所需的格式变得更加简单。

注意

要在 Python 中读取数据,您将使用variable_name = pd.read_csv('file_name.csv', header=None)

这里,参数header = None明确表示没有列名。如果您的文件包含列名,则保留这些默认值。此外,如果您的文件包含列名,但您指定了header = None,Pandas 将把包含列名的行当作数据行处理。

练习 1.06:计算轮廓系数

在这个练习中,我们将计算一个固定聚类数的数据集的轮廓系数。为此,我们将使用种子数据集,数据集可在packt.live/2UQA79z获得。以下说明提供了关于此数据集的更多信息,并将在下一个活动中进行进一步探索。为了完成此练习,请忽略该数据集具体包含哪些内容,因为学习轮廓系数更为重要。在下一个活动中,您将根据需要获得更多的背景知识,以创建智能的机器学习系统。按照以下步骤完成此练习:

注意

此数据集来自archive.ics.uci.edu/ml/datasets/seeds。它可以通过packt.live/2UQA79z访问。

引用:贡献者衷心感谢波兰科学院农业物理研究所(位于卢布林)对他们工作的支持。

  1. 使用 pandas 加载种子数据文件,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
    np.random.seed(0)
    seeds = pd.read_csv('Seed_Data.csv')
    
  2. 分离X特征,因为我们希望将其视为无监督学习问题:

    X = seeds[['A','P','C','LK','WK','A_Coef','LKG']]
    
  3. 带回我们之前制作的k_means函数供参考:

    Exercise 1.06.ipynb
    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)
    The complete code for this step can be found at https://packt.live/2UOqW9H.
    
  4. 将我们的种子X特征 DataFrame 转换为NumPy矩阵:

    X_mat = X.values
    
  5. 在种子矩阵上运行我们的k_means函数:

    centroids, labels, centroids_history, labels_history = \
    k_means(X_mat, 3)
    
  6. 计算Area ('A')Length of Kernel ('LK')列的轮廓系数:

    silhouette_score(X[['A','LK']], labels)
    

    输出应该类似于以下内容:

    0.5875704550892767
    

在这个练习中,我们计算了Area ('A')Length of Kernel ('LK')列的轮廓系数。我们将在下一个活动中使用这一技术来确定我们 k-means 聚类算法的性能。

注意

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

您还可以在packt.live/3fbtJ4y在线运行此示例。

活动 1.01:实现 k-means 聚类

您正在从头实现一个 k-means 聚类算法,以证明您理解其工作原理。您将使用 UCI ML 库提供的种子数据集。种子数据集在数据科学界是经典之作,包含了小麦种子特征,用于预测三种不同类型的小麦品种。下载位置将在本活动中后续提供。

在本活动中,您应该使用 Matplotlib、NumPy、scikit-learn 指标和 pandas。

通过轻松加载和重塑数据,您可以将更多精力集中在学习 k-means 上,而不是编写数据加载器功能。

提供以下种子数据特征供参考:

1\. area (A), 
2\. perimeter (P)
3\. compactness (C) 
4\. length of kernel (LK)
5\. width of kernel (WK)
6\. asymmetry coefficient (A_Coef)
7\. length of kernel groove (LKG)

这里的目标是深入理解 k-means 是如何工作的。为此,您需要将前面章节中学到的知识付诸实践,在 Python 中从头实现 k-means。

请打开您喜欢的编辑平台,尝试以下步骤:

  1. 使用NumPymath包和欧几里得距离公式,编写一个函数,计算两个坐标之间的距离。

  2. 编写一个函数,计算数据集中每个点到质心的距离,并返回聚类成员关系。

  3. 编写一个 k-means 函数,该函数接受数据集和聚类数(K)作为输入,返回最终的聚类质心以及组成该聚类的成员数据点。在从头实现 k-means 之后,将您的自定义算法应用于种子数据集,数据集位于:packt.live/2Xh2FdS

    注意

    本数据集来源于archive.ics.uci.edu/ml/datasets/seeds,可以通过packt.live/2Xh2FdS访问。

    UCI 机器学习库 [archive.ics.uci.edu/ml]。加利福尼亚州尔湾:加利福尼亚大学信息与计算机科学学院。

    引用:贡献者衷心感谢波兰科学院农业物理研究所对其工作的支持。

  4. 移除此数据集中提供的类别,并查看您的 k-means 算法是否能仅凭植物特征将不同的小麦品种分组到正确的类别中!

  5. 使用 scikit-learn 实现计算轮廓系数。

完成本练习后,你已获得了在真实世界数据集上调优 k-means 聚类算法的实践经验。种子数据集被视为数据科学领域中的经典“hello world”问题,有助于测试基础技术。你的最终聚类算法应能有效地识别数据中存在的三种小麦物种类型,如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_01_24.jpg

图 1.24:预期的三种小麦物种聚类图

注意

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

总结

在本章中,我们探讨了什么是聚类及其在各种数据挑战中的重要性。在掌握聚类知识的基础上,你实现了 k-means,这是最简单但也是最受欢迎的无监督学习方法之一。如果你已经阅读了本总结,并能逐步向朋友解释 k-means 的工作原理,那么你已经准备好进入更复杂的聚类形式。

接下来,我们将介绍层次聚类,在某种配置下,它重用了我们在 k-means 中使用的质心学习方法。我们将在下一章通过概述更多的聚类方法和技术,进一步发展这一方法。

第三章:2. 层次聚类

概述

在本章中,我们将使用常见的 Python 包从头实现层次聚类算法,并进行凝聚层次聚类。我们还将比较 k-means 和层次聚类。我们将利用层次聚类构建更强的、更有逻辑性的分组。在本章结束时,我们将能够使用层次聚类构建更强的、更有逻辑性的分组。

引言

在本章中,我们将在第1 章聚类简介的基础概念上展开,通过相似度的概念来围绕聚类展开。我们将再次实现欧几里得距离的不同形式,以捕捉相似性的概念。需要牢记的是,欧几里得距离只是最常用的距离度量之一,并非唯一的度量。通过这些距离度量,我们将在前一章探索的简单邻居计算基础上引入层次结构的概念。通过使用层次结构来传达聚类信息,我们可以构建更强的、更有逻辑性的分组。与 k-means 类似,层次聚类对于客户细分或识别相似产品类型等场景非常有帮助。然而,层次聚类的一个小优势是,它能够以更清晰的方式解释结果。在本章中,我们将概述一些层次聚类可能是你所需要的解决方案的情况。

聚类复习

第一章聚类简介,讲解了最基本的聚类算法之一:k-means 的高层概念和深入细节。尽管它确实是一个简单的方法,但不要小看它;它将成为你继续探索无监督学习世界时非常有价值的工具。在许多实际应用场景中,公司通过最简单的方法,如 k-means 或线性回归(用于有监督学习),获得了宝贵的发现。一个例子是评估大量的客户数据——如果直接在表格中评估这些数据,通常难以发现有价值的信息。然而,即使是一个简单的聚类算法,也能够识别数据中哪些组是相似的,哪些是不同的。为了复习一下,我们来快速回顾一下什么是聚类,以及 k-means 如何找到这些聚类:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_01.jpg

图 2.1:区分有监督与无监督问题的属性

如果你得到了一组随机的数据,没有任何指导方针,你可能会用基本的统计方法开始探索——例如,计算每个特征的均值、中位数和众数。给定一个数据集,选择监督学习或无监督学习作为推导洞察的方法,取决于你为自己设定的数据目标。如果你确定其中一个特征实际上是标签,并且你想要查看数据集中的其他特征如何影响它,那么这将成为一个监督学习问题。然而,如果在初步探索后,你意识到你拥有的数据仅仅是一组没有目标的特征(例如一组健康指标、网上商店的购买发票等),那么你可以通过无监督方法进行分析。

无监督学习的经典例子是通过分析来自网上商店的一组发票,找到类似客户的聚类。你的假设是,通过找到最相似的人群,你可以创建更具针对性的营销活动,吸引每个聚类的兴趣。实现这些相似用户聚类的一种方法是使用 k-means。

k-means 复习

k-means 聚类通过找到数据中“k”个聚类来工作,采用某些距离计算方法,如欧几里得距离、曼哈顿距离、汉明距离、明科夫斯基距离等。“K”个点(也称为质心)在数据中随机初始化,然后计算每个数据点到每个质心的距离。这些距离中的最小值决定了某个数据点属于哪个聚类。每个点被分配到一个聚类后,计算该聚类内的数据点的均值作为新的质心。这个过程会重复进行,直到新计算出的聚类质心的位置不再变化,或者达到最大迭代次数。

层级结构的组织

无论是自然界还是人造世界,都有许多将系统组织成层级结构的例子,并且大多数情况下,这样做是非常有意义的。从这些层级结构中发展出来的一个常见表示法可以在基于树的结构中看到。想象你有一个父节点,下面有若干子节点,子节点可以进一步成为父节点。通过将信息组织成树形结构,你可以构建一个信息密集型的图表,清晰地显示事物之间与同类及其更大的抽象概念的关系。

从自然界中有一个例子可以帮助说明这个概念,即我们如何看待动物的层级结构,从父类到各个物种:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_02.jpg

图 2.2:动物物种在层级树结构中的关系

在前面的示意图中,你可以看到一个例子,展示了动物品种之间的关系信息是如何以既节省空间又能传达大量信息的方式进行映射的。这个示例既可以看作是一棵独立的树(展示了猫和狗的区别,但它们都是家养动物),也可以看作是一个更大树的一部分,展示了家养与非家养动物的分类。

作为一个面向商业的示例,让我们回到一个销售产品的在线商店的概念。如果你售卖种类繁多的产品,那么你可能会想为客户创建一个层级导航系统。通过避免一次性展示所有的产品目录信息,客户只会看到与他们兴趣相匹配的树形路径。下面的示意图展示了一个层级导航系统的示例:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_03.jpg

图 2.3:层级树结构中的产品分类

显然,层级导航系统在提升客户体验方面的好处不容小觑。通过将信息组织成层级结构,你可以为数据构建一个直观的结构,展示明确的嵌套关系。如果这听起来像是另一种在数据中寻找簇的方法,那么你肯定走在了正确的道路上。通过使用类似的距离度量,例如 k-means 的欧氏距离,我们可以开发出一棵树,展示许多数据切割,让用户可以根据自己的需求主观地创建簇。

层次聚类简介

到目前为止,我们已经展示了层级结构如何成为组织信息的优秀方式,能够清晰地显示数据点之间的嵌套关系。虽然这有助于我们理解项目之间的父子关系,但在形成簇时也非常实用。以前一节中的动物示例为基础,假设你仅仅得到了两种动物的特征:它们的身高(从鼻尖到尾部的长度)和体重。使用这些信息,你需要重新创建一个层级结构,以便识别数据集中哪些记录对应的是狗和猫,以及它们相对的亚种。

由于你只给出了动物的身高和体重,无法推断每个物种的具体名称。然而,通过分析你所提供的特征,你可以在数据中构建一个结构,近似于数据中存在哪些动物物种。这为无监督学习问题奠定了基础,而层次聚类是解决此类问题的理想方法。在以下图表中,你可以看到我们在左侧创建的两个特征,左列为动物身高,右列为动物体重。然后,这些数据被绘制在一个二维坐标图上,X 轴为身高,Y 轴为体重:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_04.jpg

图 2.4:一个包含动物身高和动物体重的双特征数据集示例

一种处理层次聚类的方法是从每个数据点开始,将其视为自己的簇,并递归地将相似的点组合在一起形成簇——这就是聚合层次聚类。在聚合与分裂聚类章节中,我们将详细介绍不同的层次聚类方法。

在聚合层次聚类方法中,数据点相似度的概念可以借鉴我们在 k-means 中看到的范式。在 k-means 中,我们使用欧几里得距离来计算每个数据点到期望的“k”个簇的质心的距离。在层次聚类方法中,我们将重新使用相同的距离度量来确定数据集中记录之间的相似性。

最终,通过递归地将数据中的单个记录与其最相似的记录组合在一起,你将从底部向上构建一个层次结构。最终,单独的单一成员簇将合并成一个位于层次结构顶部的单一簇。

执行层次聚类的步骤

为了理解聚合层次聚类的工作原理,我们可以跟踪一个简单的示例程序,它是如何通过合并形成层次结构的:

  1. 给定 n 个样本数据点,将每个点视为一个单独的“簇”,其中只有该点作为成员(质心)。

  2. 计算数据集中所有簇的质心之间的成对欧几里得距离。(在此,簇之间的最小距离、最大距离、平均距离或两个质心之间的距离也可以考虑。在这个示例中,我们考虑的是两个簇质心之间的距离)。

  3. 将最接近的簇/点组合在一起。

  4. 重复步骤 2步骤 3,直到你得到一个包含所有数据的单一簇。

  5. 绘制树状图以展示数据如何在层次结构中聚合。树状图只是用于表示树状结构的图示,显示簇从上到下的排列。我们将在接下来的演示中详细讨论这如何有助于理解数据的结构。

  6. 决定你希望在哪个层级创建簇。

层次聚类的示例演示

尽管比 k-means 稍微复杂一些,层次聚类在逻辑上实际上与它非常相似。这里有一个简单的示例,稍微详细地介绍了前面的步骤:

  1. 给定四个样本数据点的列表,将每个点视为一个质心,它也是其自身的簇,点的索引从 0 到 3:

    Clusters (4): [ (1,7) ], [ (-5,9) ], [ (-9,4) ] , [ (4, -2) ]
    Centroids (4): [ (1,7) ], [ (-5,9) ], [ (-9,4) ] , [ (4, -2) ]
    
  2. 计算所有簇的质心之间的成对欧几里得距离。

    请参考第一章K-means 聚类深入演练部分,复习欧几里得距离的计算方法。

    图 2.5中显示的矩阵里,点的索引在水平方向和垂直方向上都是 0 到 3,表示各个点之间的距离。注意,数值在对角线两侧是镜像对称的——这是因为你在比较每个点与其他所有点的距离,所以只需要关注对角线一侧的数值:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_05.jpg

    图 2.5:一个距离数组

  3. 将距离最短的点对组合在一起。

    在这种情况下,点 [1,7] 和 [-5,9] 因为距离最近而合并成一个簇,其余两个点作为单成员簇保留:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_06.jpg

    [ [1,7], [-5,9] ]
    [-9,4]
    [4,-2] 
    
  4. 计算两个成员簇的点之间的均值,以找到新的质心:

    mean([ [1,7], [-5,9] ]) = [-2,8]
    
  5. 将质心添加到两个单成员质心中,并重新计算距离:

    Clusters (3): 
    [ [1,7], [-5,9] ]
    [-9,4]
    [4,-2] 
    

    质心(3):

    [-2,8]
    [-9,4]
    [4,-2]
    

    再次,我们将计算点与质心之间的欧几里得距离:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_07.jpg

    图 2.7:一个距离数组

  6. 如前图所示,点 [-9,4] 是距离质心最近的点,因此它被添加到簇 1。现在,簇列表更新为以下内容:

    Clusters (2): 
    [ [1,7], [-5,9], [-9,4] ]
    [4,-2] 
    
  7. 只剩下点 [4,-2] 作为距离其邻近点最远的点,你可以将它直接加入簇 1 来统一所有簇:

    Clusters (1): 
    [ [ [1,7], [-5,9], [-9,4], [4,-2] ] ]
    
  8. 绘制一个树状图以展示数据点与簇之间的关系:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_08.jpg

图 2.8:显示数据点与簇之间关系的树状图

树状图展示了数据点之间的相似性,并且与我们之前讨论的层次树结构非常相似。与任何可视化技术一样,它会丢失一些信息;然而,当确定你希望形成多少个聚类时,树状图是非常有帮助的。在前面的示例中,你可以在 X 轴上看到四个潜在的聚类,如果每个点都是一个独立的聚类。当你垂直查看时,可以看到哪些点距离最接近,可能会被归为同一聚类。例如,在前面的树状图中,索引 0 和 1 的点最接近,可以形成一个聚类,而索引 2 仍然是一个单独的聚类。

回顾前面的动物分类示例,其中涉及狗和猫物种,假设你看到了以下树状图:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_09.jpg

图 2.9:动物分类树状图

如果你只对将物种数据集分为狗和猫感兴趣,你可以在第一个分组层级停止聚类。然而,如果你想将所有物种分为家养动物和非家养动物,你可以在第二个层级停止聚类。层次聚类和树状图的一个优点是,你可以看到潜在聚类的完整划分,以供选择。

练习 2.01:构建层级结构

让我们在 Python 中实现前述的层次聚类方法。通过概述直观的框架,我们现在可以使用 SciPy 提供的一些辅助函数来探索构建层次聚类的过程。SciPy(www.scipy.org/docs.html)是一个开源库,提供了对科学计算和技术计算有用的函数。它的示例包括线性代数和微积分相关方法的简单实现。在这个练习中,我们将专门使用 SciPycluster子模块的有用函数。除了 scipy,我们还将使用 matplotlib 来完成这个练习。按照以下步骤完成这个练习:

  1. 生成一些虚拟数据,如下所示:

    from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
    from sklearn.datasets import make_blobs
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  2. 生成一个随机的聚类数据集进行实验。X = 坐标点,y = 聚类标签(不需要):

    X, y = make_blobs(n_samples=1000, centers=8, \
                      n_features=2, random_state=800)
    
  3. 可视化数据,如下所示:

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

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_10.jpg

    图 2.10:虚拟数据的绘图

    在绘制这个简单的玩具示例后,应该能清楚地看到我们的虚拟数据包含了八个聚类。

  4. 我们可以使用内置的SciPy包中的linkage轻松生成距离矩阵。稍后我们将深入了解linkage函数的原理;但是,目前了解有现成的工具可以计算点与点之间的距离是非常重要的:

    # Generate distance matrix with 'linkage' function
    distances = linkage(X, method="centroid", metric="euclidean")
    print(distances)
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_11.jpg

    图 2.11:距离矩阵

    如果你尝试通过自动填写 linkage 函数的 method 超参数进行不同的方法实验,你会看到它们如何影响整体性能。链接方法通过简单地计算每个数据点之间的距离来工作。我们将在 Linkage 主题中详细讲解它具体计算的内容。在 linkage 函数中,我们可以选择度量和方法(稍后会详细介绍)。

    在确定链接矩阵后,我们可以轻松地通过 SciPy 提供的 dendrogram 函数进行传递。顾名思义,dendrogram 函数利用 步骤 4 中计算的距离生成一种直观简洁的方式来解析分组信息。

  5. 我们将使用一个自定义函数来清理原始输出的样式(请注意,以下代码片段中的函数使用的是 SciPy 的基础树状图实现,唯一的自定义代码是用来清理视觉输出的):

    # Take normal dendrogram output and stylize in cleaner way
    def annotated_dendrogram(*args, **kwargs):
        # Standard dendrogram from SciPy
        scipy_dendro = dendrogram(*args, truncate_mode='lastp', \
                                  show_contracted=True,\
                                  leaf_rotation=90.)
        plt.title('Blob Data Dendrogram')
        plt.xlabel('cluster size')
        plt.ylabel('distance')
        for i, d, c in zip(scipy_dendro['icoord'], \
                           scipy_dendro['dcoord'], \
                           scipy_dendro['color_list']):
            x = 0.5 * sum(i[1:3])
            y = d[1]
            if y > 10:
                plt.plot(x, y, 'o', c=c)
                plt.annotate("%.3g" % y, (x, y), xytext=(0, -5), \
                             textcoords='offset points', \
                             va='top', ha='center')
        return scipy_dendro
    dn = annotated_dendrogram(distances)
    plt.show()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_12.jpg

    图 2.12:距离的树状图

    这个图形将帮助我们从数据潜在的分群角度获取一些见解。基于之前步骤计算的距离,它展示了一个潜在路径,我们可以用它来创建三个独立的群组,距离为七,且这些群组足够独特,能够独立存在。

  6. 利用这些信息,我们可以通过使用 SciPy 中的 fcluster 函数来结束我们的层次聚类练习:

    scipy_clusters = fcluster(distances, 3, criterion="distance")
    plt.scatter(X[:,0], X[:,1], c=scipy_clusters)
    plt.show()
    

    fcluster 函数利用来自树状图的距离和信息,根据指定的阈值将数据分组。前面示例中的数字 3 代表你可以设置的最大聚类间距离阈值超参数。这个超参数可以根据你正在处理的数据集进行调整;然而,在本练习中,它被设定为 3。最终的输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_13.jpg

图 2.13:距离的散点图

在前面的图中,你可以看到通过使用我们的阈值超参数,我们已经识别出八个不同的聚类。只需调用 SciPy 提供的几个辅助函数,你就能轻松地在几行代码中实现聚合聚类。尽管 SciPy 确实帮助处理了许多中间步骤,但这个示例仍然相对冗长,可能不完全符合你日常工作中的精简代码。我们稍后会介绍更简化的实现方式。

注意

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

你也可以在线运行这个示例,访问 packt.live/2Cdyiww

链接

练习 2.01建立层次结构 中,你使用被称为质心连接的方法实现了层次聚类。连接是确定如何计算聚类之间距离的概念,并且依赖于你面临的问题类型。选择质心连接用于练习 2.02应用连接准则,因为它基本上镜像了我们在 k-means 中使用的新质心搜索。然而,这并不是聚类数据点时唯一的选项。确定聚类之间距离的另外两个常见选择是单一连接和完全连接。

单一连接 通过找到两聚类之间一对点之间的最小距离作为连接的标准。简单来说,它基本上通过根据两个聚类之间最接近的点来组合聚类。数学上表示如下:

dist(a,b) = min( dist( a[i]), b[j] ) )

在前面的代码中,a[i] 是第一个聚类中的 i 点,而 b[j] 是第二个聚类中的 j 点。

完全连接 是单一连接的对立面,它通过找到两聚类之间一对点之间的最大距离作为连接的标准。简单来说,它通过根据两个聚类之间最远的点来组合聚类。数学上表示如下:

dist(a,b) = max( dist( a[i]), b[j] ) )

在前面的代码中,a[i]b[j] 分别是第一个和第二个聚类中的 ij 点。确定哪种连接准则最适合你的问题既是艺术也是科学,而且它在很大程度上依赖于你的特定数据集。选择单一连接的一个原因是如果你的数据在最近邻的意义上相似;因此,当存在差异时,数据将非常不相似。由于单一连接通过寻找最接近的点来工作,它不会受到这些遥远异常值的影响。然而,由于单一连接通过寻找一对点之间的最小距离来工作,它很容易受到分布在聚类之间的噪声影响。相反,如果你的数据在聚类间的状态上相距较远,那么完全连接可能是更好的选择;当聚类的空间分布不平衡时,完全连接会导致错误的分割。质心连接具有类似的优点,但如果数据非常嘈杂且聚类的“中心”不够明显,它就会失效。通常,最佳的方法是尝试几种不同的连接准则选项,看看哪种最适合你的数据,并以对你的目标最相关的方式进行操作。

练习 2.02:应用连接准则

回忆我们在前一个练习中生成的八个聚类的虚拟数据。在现实世界中,你可能会得到类似于离散高斯簇的真实数据。假设这些虚拟数据代表了某个商店中不同购物群体的情况。商店经理要求你分析这些购物者数据,以便将客户分为不同的群体,进而为每个群体量身定制营销材料。

使用我们在前一个练习中生成的数据,或者生成新数据,你将分析哪些连接方法能更好地将客户分组到不同的聚类中。

一旦生成数据,查看通过 SciPy 提供的文档,了解在 linkage 函数中有哪些连接类型可用。然后,通过将它们应用于你的数据来评估这些连接类型。你应测试的连接类型在以下列表中展示:

['centroid', 'single', 'complete', 'average', 'weighted']

我们尚未覆盖所有之前提到的连接类型——这项活动的关键部分是学习如何解析使用包提供的文档字符串,以探索它们的所有功能。请按照以下步骤完成本练习:

  1. 可视化我们在 练习 2.01 中创建的 x 数据集,构建层次结构

    from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
    from sklearn.datasets import make_blobs
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  2. 生成一个随机的聚类数据集进行实验。X = 坐标点,y = 聚类标签(不需要):

    X, y = make_blobs(n_samples=1000, centers=8, \
                      n_features=2, random_state=800)
    
  3. 如下所示可视化数据:

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

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_14.jpg

    图 2.14: 生成的聚类数据集的散点图

  4. 创建一个包含所有可能连接方法超参数的列表:

    methods = ['centroid', 'single', 'complete', \
               'average', 'weighted']
    
  5. 遍历你刚创建的列表中的每个方法,并显示它们对相同数据集的影响:

    for method in methods:
        distances = linkage(X, method=method, metric="euclidean")
        clusters = fcluster(distances, 3, criterion="distance") 
        plt.title('linkage: ' + method)
        plt.scatter(X[:,0], X[:,1], c=clusters, cmap='tab20b')
        plt.show()
    

    重心连接法的图示如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_15.jpg

图 2.15: 重心连接方法的散点图

单连接法的图示如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_16.jpg

图 2.16: 单连接方法的散点图

完全连接法的图示如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_17.jpg

图 2.17: 完全连接方法的散点图

平均连接法的图示如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_18.jpg

图 2.18: 平均连接方法的散点图

加权连接法的图示如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_19.jpg

图 2.19: 加权连接方法的散点图

如前面的图所示,通过简单地改变连接准则,你可以显著改变聚类的效果。在这个数据集中,质心和平均连接在找到有意义的离散聚类方面效果最好。从我们生成的包含八个聚类的数据集来看,质心和平均连接是唯一能够显示出用八种不同颜色表示的聚类的连接方式。其他连接类型效果较差,最明显的是单一连接。单一连接的效果较差,因为它假设数据是以细长的“链”格式存在,而不是聚类的形式。其他连接方法更优秀,因为它们假设数据是以聚集组的形式呈现。

注意

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

你也可以在在线运行这个示例:packt.live/2Zb4zgN

聚合型与分裂型聚类

到目前为止,我们的层次聚类实例都是聚合型的——也就是说,它们是从底部向上构建的。虽然这是这种聚类类型最常见的方法,但重要的是要知道,这并不是创建层次结构的唯一方式。相反的层次方法——即从顶部向下构建,也可以用于创建你的分类法。这种方法称为分裂型层次聚类,它通过将数据集中的所有数据点放入一个大的聚类中来工作。分裂型方法的许多内部机制与聚合型方法非常相似:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_20.jpg

图 2.20:聚合型与分裂型层次聚类

与大多数无监督学习中的问题一样,决定最佳方法通常高度依赖于你所面临的具体问题。

想象一下,你是一位刚刚购买了一家新杂货店的创业者,现需要为店铺备货。你收到了一大批食品和饮料的运输箱,但你已经失去了所有的运输信息。为了有效地销售你的产品,你必须将相似的产品归为一类(如果你把所有东西随便放到货架上,店铺会变得一团糟)。为了实现这个组织目标,你可以采取自下而上或自上而下的方法。在自下而上的方法中,你会仔细查看运输箱里的物品,并认为一切都是杂乱无章的——然后,你会拿起一个随机物品,找出最相似的产品。例如,你可能拿起苹果汁,意识到将其与橙汁放在一起是有道理的。在自上而下的方法中,你会认为所有物品一开始就已按某种方式组织成一个大类。然后,你会逐步检查库存,并根据相似度的最大差异将其拆分成多个组。例如,如果你正在组织一家杂货店,你可能一开始认为苹果和苹果汁是属于同一类,但再想想,它们其实是相当不同的。因此,你会将它们拆分成更小的、不相似的组。

一般来说,可以将凝聚聚类看作自下而上的方法,将分裂聚类看作自上而下的方法——但它们在性能上有何权衡?这种立刻抓住最接近的物品的行为被称为“贪婪学习”;它有可能被局部邻居所欺骗,无法看到它在任何给定时刻所形成的簇的更大影响。另一方面,分裂方法的优势在于它从一开始就能看到整个数据分布作为一个整体,并选择最好的方式来拆分簇。对整个数据集外观的这种洞察对于创建更精确的簇非常有帮助,不容忽视。不幸的是,自上而下的方法通常会以牺牲更高的准确性为代价,换来更深的复杂性。在实践中,凝聚方法大多数情况下效果较好,应当作为层次聚类的首选起点。如果在查看层次结构后,你对结果不满意,尝试使用分裂方法可能会有所帮助。

练习 2.03:使用 scikit-learn 实现凝聚聚类

在大多数商业应用场景中,你很可能会使用一个抽象了所有内容的包来实现层次聚类,比如 scikit-learn。Scikit-learn 是一个免费包,是 Python 中进行机器学习时不可或缺的工具。它便捷地提供了最流行算法(如回归、分类和聚类)的高度优化版本。通过使用像 scikit-learn 这样的优化包,你的工作变得更加轻松。然而,只有在你完全理解层次聚类的工作原理之后,才应该使用它,正如我们在前面的章节中讨论的那样。这个练习将比较使用 SciPy 和 scikit-learn 进行聚类的两种潜在路线。通过完成此练习,你将了解它们各自的优缺点,以及从用户角度来看哪个最适合你。请按照以下步骤完成此练习:

  1. Scikit-learn 使得实现变得轻松,仅需几行代码。首先,导入必要的包并将模型分配给 ac 变量。然后,创建如前述练习中所示的 blob 数据:

    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)
    

    首先,我们通过传入熟悉的参数(如 affinity(距离函数)和 linkage)将模型分配给 ac 变量。

  2. 然后重用我们在之前练习中使用的 linkage 函数和 fcluster 对象:

    distances = linkage(X, method="centroid", metric="euclidean")
    sklearn_clusters = ac.fit_predict(X)
    scipy_clusters = fcluster(distances, 3, criterion="distance")
    

    在将模型实例化为一个变量后,我们可以简单地使用 .fit_predict() 将数据集拟合到所需模型,并将其赋值给另一个变量。这将为我们提供有关理想聚类的信息,作为模型拟合过程的一部分。

  3. 然后,我们可以通过绘图比较每种方法的最终聚类结果。让我们来看一下 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 方法的聚类结果输出:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_21.jpg

图 2.21: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()

输出如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_22.jpg

图 2.22:SciPy 方法的图示

如你所见,两个方法基本上汇聚到了相同的聚类结果。

注意

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

你还可以在网上运行此示例,网址为 packt.live/3f5PRgy

虽然这对于玩具问题来说很棒,但在下一次活动中,你将了解到,输入参数的微小变化可能会导致截然不同的结果。

活动 2.01:比较 K-means 和层次聚类

您正在管理一家商店的库存,并收到了大量葡萄酒货物,但品牌标签在运输过程中从瓶子上脱落。幸运的是,您的供应商为每瓶酒提供了化学成分数据,并附上了各自的序列号。不幸的是,您无法打开每瓶酒进行品尝测试——您必须找到一种方法,根据化学成分数据将未贴标签的酒瓶重新分组。您从订单单上知道,您订购了三种不同类型的葡萄酒,并且只提供了两种葡萄酒属性来重新分组这些酒。在此活动中,我们将使用葡萄酒数据集。该数据集包含三种不同类型葡萄酒的化学成分数据,根据 UCI 机器学习库中的来源,包含以下特征:

本活动的目的是在葡萄酒数据集上实现 k-means 和层次聚类,并确定哪种方法在为每种葡萄酒类型形成三个独立的聚类时更加准确。您可以尝试不同的 scikit-learn 实现组合,并使用 SciPy 和 NumPy 中的辅助函数。您还可以使用轮廓系数来比较不同的聚类方法,并在图表上可视化聚类结果。

完成此活动后,您将亲眼看到两种不同的聚类算法在同一数据集上的表现,从而便于在超参数调优和整体性能评估时进行比较。您可能会注意到,根据数据的形状,一种方法的表现优于另一种方法。此活动的另一个关键结果是理解超参数在任何给定用例中的重要性。

以下是完成此活动的步骤:

  1. 从 scikit-learn 导入必要的包(KMeansAgglomerativeClusteringsilhouette_score)。

  2. 将葡萄酒数据集读取到 pandas DataFrame 中并打印一个小样本。

  3. 通过绘制 OD 读取特征与前花青素特征的关系,来可视化数据集中的一些特征。

  4. 使用sklearn实现的 k-means 算法对葡萄酒数据集进行聚类,知道数据集中有三种葡萄酒类型。

  5. 使用sklearn实现的层次聚类对葡萄酒数据集进行聚类。

  6. 绘制 k-means 算法预测的聚类。

  7. 绘制层次聚类算法预测的聚类。

  8. 比较每种聚类方法的轮廓系数。

完成此活动后,你应该已经绘制出了从 k-means 获得的预测簇,如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_23.jpg

图 2.23:来自 k-means 方法的预期簇

对于层次聚类预测的簇,也应获得类似的图,如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_02_24.jpg

图 2.24:来自聚合方法的预期簇

注意

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

k-means 与层次聚类

在上一章中,我们探讨了 k-means 聚类的优点。现在,了解层次聚类在其中的作用是非常重要的。正如我们在连接部分提到的那样,当使用质心对数据点进行分组时,可能存在一些直接的重叠。到目前为止,我们提到的所有方法都普遍使用了距离函数来确定相似性。由于我们在上一章的深入探讨,我们在这里使用了欧几里得距离,但我们理解任何距离函数都可以用来确定相似性。

实际上,选择一种聚类方法而非另一种时,可以参考以下几点:

  • 层次聚类的优势在于不需要预先传入明确的“k”簇数。这意味着你可以找到所有潜在的簇,并在算法完成后决定哪些簇最有意义。

  • 从简便性角度来看,k-means 聚类具有优势——在商业用例中,通常面临一个挑战:如何找到既能向非技术观众解释又能生成高质量结果的方法。k-means 可以轻松填补这一空缺。

  • 层次聚类在处理形状异常的数据时,调整的参数比 k-means 聚类更多。虽然 k-means 在发现离散簇方面表现优秀,但在处理混合簇时可能会遇到困难。通过调整层次聚类中的参数,你可能会得到更好的结果。

  • 基本的 k-means 聚类通过实例化随机质心并找到与这些质心最接近的点来工作。如果它们被随机实例化在特征空间中远离数据的区域,那么可能需要相当长的时间才能收敛,或者可能根本无法达到收敛点。层次聚类则较不容易受到这种缺陷的影响。

总结

本章中,我们讨论了层次聚类的工作原理以及它可能的最佳应用场景。特别是,我们讨论了如何通过评估树状图来主观地选择簇的各个方面。如果你完全不知道在数据中寻找什么,这比 k 均值聚类有一个巨大的优势。我们还讨论了两个推动层次聚类成功的关键参数:合并方法和分裂方法,以及聚类标准。合并聚类采用自下而上的方法,通过递归地将邻近的数据合并在一起,直到结果为一个大簇。分裂聚类采用自上而下的方法,从一个大簇开始,递归地将其拆分,直到每个数据点都进入其自己的簇。分裂聚类由于从一开始就可以全面查看数据,因此有可能更准确;然而,它增加了一层复杂性,这可能会降低稳定性并增加运行时间。

聚类标准涉及如何计算候选簇之间的距离。我们已经探讨了质心如何在 k 均值聚类之外再次出现,以及单一链接和完全链接标准。单一链接通过比较每个簇中最近的点来计算簇间距离,而完全链接通过比较每个簇中较远的点来计算簇间距离。通过本章所学的知识,你现在能够评估 k 均值聚类和层次聚类如何最好地适应你正在处理的挑战。

虽然由于层次聚类的复杂性增加,它可能比 k 均值聚类表现更好,但请记住,更多的复杂性并不总是好的。作为无监督学习的从业者,你的职责是探索所有选项,并确定既高效又具有性能的解决方案。在下一章中,我们将介绍一种在处理高度复杂和噪声数据时最适用的聚类方法:基于密度的空间聚类应用与噪声

第四章:3. 邻域方法与 DBSCAN

概述

在本章中,我们将展示基于邻域的聚类方法如何从头到尾工作,并通过使用软件包从零开始实现基于密度的空间聚类算法(带噪声)DBSCAN)算法。我们还将从 k-means、层次聚类和 DBSCAN 中识别出最适合解决您问题的算法。到本章结束时,我们将看到 DBSCAN 聚类方法如何在处理高度复杂数据时为我们提供最佳服务。

引言

在前面的章节中,我们评估了多种不同的数据聚类方法,包括 k-means 和层次聚类。虽然 k-means 是最简单的聚类形式,但在适当的场景中,它仍然非常强大。在 k-means 无法捕捉数据集复杂性的情况下,层次聚类证明是一个强有力的替代方法。

无监督学习的关键挑战之一是,您会获得一组特征数据,但没有额外的标签告诉您目标状态是什么。虽然您可能无法明确知道目标标签是什么,但通过将相似的群体聚集在一起并观察群体内的相似性,您可以从数据中提取出一些结构。我们首先介绍的聚类相似数据点的方法是 k-means。K-means 聚类最适合处理那些速度至关重要的简单数据问题。只需查看最接近的数据点(聚类中心),就不需要太多计算开销;然而,当面对高维数据集时,也会面临更大的挑战。如果您不知道需要查找的聚类数目,k-means 也不是理想选择。我们在第二章层次聚类》中探讨的一个例子是,通过分析化学特征来确定哪些葡萄酒在一次杂乱的运输中属于同一类。之所以能够顺利进行,是因为我们知道有三种葡萄酒类型已被订购;然而,如果您不知道原始顺序是什么,k-means 可能就不会那么成功。

我们探索的第二种聚类方法是层次聚类。该方法有两种工作方式——聚合性(agglomerative)或分裂性(divisive)。聚合性聚类采用自下而上的方法,将每个数据点视为自己的簇,并根据链接标准递归地将它们组合在一起。分裂性聚类则采用相反的方式,将所有数据点视为一个大类,并递归地将它们分解成更小的簇。该方法的优点在于能够全面理解数据的分布,因为它计算了分割潜力;然而,由于其复杂性较高,通常在实践中不被采用。层次聚类对于当你对数据一无所知时是一个很有竞争力的选择。通过使用树状图(dendrogram),你可以可视化数据中的所有分割,并考虑事后哪个聚类数最合理。这在特定的使用场景中非常有用,但它的计算成本高于 k-means。

在本章中,我们将介绍一种在处理高度复杂数据时最适合的聚类方法:基于密度的空间聚类应用与噪声(DBSCAN)。传统上,这种方法在具有大量密集交错数据的数据库中一直被认为是一种高效的聚类方法。让我们一起看看它为什么在这些应用场景中表现得如此出色。

作为邻域的簇

到目前为止,我们探讨的相似性概念是通过欧几里得距离来描述的——距离某一点较近的数据点可以看作是相似的,而在欧几里得空间中距离较远的数据点则被视为不相似。这个概念在 DBSCAN 算法中再次出现。如同其冗长的名字所暗示的那样,DBSCAN 方法不仅基于距离度量评估,还引入了密度的概念。如果有一些数据点聚集在同一区域内,它们可以视为同一簇的成员:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_01.jpg

图 3.1:邻居与簇之间有直接联系

在前面的图中,我们可以看到四个邻域。与我们之前讨论的仅仅基于距离的方法相比,基于密度的方法有很多优势。如果你只关注距离作为聚类的阈值,那么在面对稀疏特征空间和离群点时,你可能会发现聚类结果毫无意义。无论是 k-means 还是层次聚类,都会自动将空间中的所有数据点分组,直到没有剩余的点。

尽管层次聚类在一定程度上提供了解决此问题的途径,因为你可以在聚类运行后使用树状图来确定聚类的形成位置,但 k-means 作为聚类的最简单方法,最容易失败。当我们开始评估基于邻域的聚类方法时,这些陷阱就不那么明显了。在下面的树状图中,你可以看到一个典型的陷阱,其中所有数据点都被归为一类。显然,当你沿着树状图向下走时,许多具有潜在差异的数据点被聚在一起,因为每个点都必须属于一个聚类。使用基于邻域的聚类时,这个问题要少得多:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_02.jpg

图 3.2:示例树状图

通过在 DBSCAN 中引入邻居密度的概念,我们可以选择性地将异常值排除在聚类之外,具体取决于我们在运行时选择的超参数。只有那些具有相近邻居的数据点才会被视为同一聚类中的成员,而那些距离较远的点则会被视为未聚类的异常值。

DBSCAN 简介

在 DBSCAN 中,密度是通过邻域半径和在被认为是一个聚类的邻域内找到的最小点数的组合来评估的。如果我们重新考虑一下你负责为商店整理一批未标记的葡萄酒货物的场景,这个概念就会变得更清晰。在前面的例子中,已经明确了我们可以根据葡萄酒的特征,如化学特性,来找到相似的葡萄酒。知道这些信息后,我们可以更容易地将相似的葡萄酒归为一类,迅速地将我们的产品整理好,准备好销售。然而,在现实世界中,你为商店订购的产品将反映出真实的购买模式。为了在库存中促使多样性,同时保证最受欢迎的葡萄酒有足够的存货,你的产品种类会呈现出极为不均衡的分布。大多数人喜欢经典的葡萄酒,如白葡萄酒和红葡萄酒;然而,你可能还会为喜欢昂贵酒款的客户提供一些更为独特的葡萄酒。这使得聚类变得更加困难,因为不同类别的分布不均(例如,你不会为每种葡萄酒都订购 10 瓶)。

DBSCAN 与 k-means 和层次聚类的不同之处在于,你可以将这种直觉融入到我们评估所关注的客户聚类中。它能够更轻松地去除噪声,仅指向那些在营销活动中具有最高再营销潜力的客户。

通过利用邻域的概念进行聚类,我们可以将那些一次性光顾的客户(可以看作是随机噪声)与那些反复光顾我们商店的更有价值的客户区分开来。这种方法让我们重新思考在确定邻域半径和每个邻域的最小点数时,如何建立最佳数值。

作为一种高级启发式方法,我们希望邻域半径较小,但又不能过小。在一个极端情况下,邻域半径可以非常大——这可能会将特征空间中的所有点都视为一个庞大的簇。在另一个极端情况下,邻域半径可以非常小。过小的邻域半径可能导致没有点被聚类在一起,从而产生大量单一成员的簇。

类似的逻辑适用于构成簇的最小点数。最小点数可以看作是一个次要阈值,它根据你的数据空间中的数据来调整邻域半径。如果特征空间中的所有数据都非常稀疏,最小点数就变得非常重要,与邻域半径一起工作,确保你不会只得到大量不相关的数据点。当你拥有非常密集的数据时,最小点数阈值就不像邻域半径那样成为一个主导因素。

正如你从这两个超参数规则中看到的,最佳选项通常依赖于数据集的具体情况。通常,你会希望找到一个完美的“黄金中介”区域,既不过小,也不过大。

DBSCAN 详细介绍

为了了解 DBSCAN 如何工作,我们可以通过一个简单的示例程序的路径,查看它如何将数据点合并成不同的簇和噪声标签的数据点:

  1. n 个未访问的样本数据点中,我们首先通过每个点并标记它为已访问。

  2. 从每个点出发,我们将查看到数据集中每个其他点的距离。

  3. 所有位于邻域半径超参数范围内的点都应视为邻居。

  4. 邻居的数量应至少与所需的最小点数相等。

  5. 如果达到了最小点阈值,点应被归为一个簇,否则标记为噪声。

  6. 这个过程应该重复进行,直到所有数据点都被分类为簇或噪声。

在某些方面,DBSCAN 相当简单——尽管有邻域半径和最小点的新概念,但本质上,它仍然只是使用距离度量来进行评估。

DBSCAN 算法演示

以下步骤将更详细地引导你完成此路径:

  1. 给定六个样本数据点,将每个点视为其自己的簇:[ (1,3) ], [ (-8,6) ], [ (-6,4) ], [ (4,-2) ], [ (2,5) ], [ (-2,0) ]: https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_03.jpg

    图 3.3:样本数据点的绘图

  2. 计算每两个点之间的欧几里得距离:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_04.jpg

    图 3.4:点之间的距离

  3. 从每个点出发,向外扩展邻域大小并形成簇。为了方便理解,假设你通过一个半径为五的邻域。这意味着,如果两点之间的距离小于五个单位,它们就会成为邻居。例如,点 (1,3) 有点 (2,5) 和 (-2,0) 作为邻居。

    根据给定点邻域中点的数量,该点可以被分类为以下三类:

    核心点:如果观察点在其邻域中拥有比最低点数要求更多的数据点,这些点构成了一个簇,则该点被称为该簇的核心点。所有核心点所在的邻域中的其他核心点都属于同一簇。然而,所有不在同一邻域内的核心点属于另一个簇。

    边界点:如果观察点周围没有足够的邻居(数据点),但至少有一个核心点(在其邻域内),则该点表示该簇的边界点。边界点属于其最近核心点的同一簇。

    噪声点:如果数据点在其邻域内没有满足要求的最小数据点数量,并且与任何核心点无关,则该点被视为噪声点,并从聚类中排除。

  4. 拥有邻居的点将进行评估,以查看它们是否通过最小点数的阈值。在这个例子中,如果我们通过了最小点数阈值为二,那么点 (1,3),(2,5),和 (-2,0) 可以正式归为同一簇。如果我们通过最小点数阈值为四,那么这三个数据点将被视为多余的噪声。

  5. 拥有比邻域内所需最小邻居数量更少邻居,并且其邻域不包含核心点的点被标记为噪声点,且不参与聚类。因此,点 (-6,4),(4,-2),和 (-8,6) 属于这一类别。然而,像 (2,5) 和 (2,0) 这样的点,尽管它们不满足邻域内最小点数的标准,但它们确实包含一个核心点作为邻居,因此被标记为边界点。

  6. 以下表格总结了某个特定点的邻居,并将其分类为核心点、边界点和噪声数据点(如前述步骤所提到),邻域半径为 5,最小邻居标准为 2。https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_05.jpg

    图 3.5:表格展示了给定点的邻居详细信息

  7. 对任何剩余的未访问数据点重复此过程。

在此过程结束时,你将把整个数据集划分为聚类或无关的噪声。DBSCAN 的性能在很大程度上取决于你选择的阈值超参数。这意味着你可能需要多次运行 DBSCAN,并使用不同的超参数选项,以了解它们如何影响整体性能。

注意,DBSCAN 不需要我们在 k-means 和以质心为中心的层次聚类实现中看到的质心。这个特点使得 DBSCAN 对于复杂数据集更有效,因为大多数数据并不是像干净的块状物一样的形状。与 k-means 或层次聚类相比,DBSCAN 在应对离群点和噪声方面也更有效。

现在让我们看看随着邻域半径大小的变化,DBSCAN 的性能如何变化。

练习 3.01: 评估邻域半径大小的影响

在本次练习中,我们将颠倒我们通常在前面示例中看到的顺序,首先查看 scikit-learn 中的 DBSCAN 打包实现,然后再自己实现它。这是故意的,目的是充分探索不同邻域半径大小如何显著影响 DBSCAN 的性能。

完成这个练习后,你将熟悉调整邻域半径大小如何改变 DBSCAN 的性能。了解 DBSCAN 的这些方面非常重要,因为它们可以通过高效排查聚类算法问题,为你节省未来的时间:

  1. 导入 scikit-learn 和 matplotlib 中本次练习所需的包:

    from sklearn.cluster import DBSCAN
    from sklearn.datasets import make_blobs
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  2. 生成一个随机聚类数据集来进行实验;X = 坐标点,y = 聚类标签(不需要):

    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()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_06.jpg

    图 3.6: 可视化的玩具数据示例

  3. 在为这个玩具问题绘制虚拟数据之后,你将看到数据集有两个特征,大约七到八个聚类。要使用 scikit-learn 实现 DBSCAN,你需要实例化一个新的 scikit-learn 类:

    db = DBSCAN(eps=0.5, min_samples=10, metric='euclidean')
    

    我们的示例 DBSCAN 实例存储在 db 变量中,并且我们的超参数在创建时传入。为了本示例,你可以看到邻域半径(eps)被设置为 0.5,而最小点数设置为 10。为了与我们之前的章节保持一致,我们将再次使用欧几里得距离作为我们的距离度量。

    注意

    eps 代表 epsilon,是你的算法在查找邻居时会搜索的邻域半径。

  4. 让我们设置一个循环,允许我们交互式地探索潜在的邻域半径大小选项:

    eps = [0.2,0.7,4]
    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()
    

    上述代码产生了以下图表:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_07.jpg

图 3.7: 结果图

从图中可以看出,将邻域大小设置得过小会导致所有内容都被视为随机噪声(紫色点)。稍微增加邻域大小后,我们能够形成更有意义的簇。再大的 epsilon 值会将整个数据集合并为一个簇(紫色数据点)。尝试重新创建前面的图并实验不同的 eps 大小。

注意

要访问这一部分的源代码,请参考 packt.live/3gEijGC

你也可以在 packt.live/2ZPBfeJ 在线运行这个例子。

DBSCAN 属性 – 邻域半径

在前面的练习中,你看到设置合适的邻域半径对 DBSCAN 实现性能的影响。如果你的邻域设置得太小,那么所有数据都会被当作噪声处理,无法聚类。如果将邻域设置得过大,所有数据也会被归为一个簇,无法提供任何价值。如果你用自己的 eps 值进一步探索了前面的练习,你可能会注意到,单纯依靠邻域大小进行有效聚类是非常困难的。这时,最小点数阈值就派上了用场。我们稍后会讲到这个话题。

为了更深入地理解 DBSCAN 的邻域概念,我们来看一下在实例化时传入的 eps 超参数。这个 epsilon 值会被转换为一个半径,围绕任意给定的数据点以圆形方式进行扫描,作为邻域的定义:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_08.jpg

图 3.8:邻域半径的可视化;红色圆圈表示邻域

在这个例子中,中心点会有四个邻居,正如前面的图中所示。

这里需要观察的一个关键点是,你的邻域搜索形成的形状在二维空间中是圆形的,在三维空间中是球形的。这可能会根据数据的结构影响你模型的性能。再一次,簇可能看起来像是直观的结构,但这不一定总是如此。幸运的是,DBSCAN 很好地解决了这类簇的问题,虽然这些簇可能是你感兴趣的,但并不符合显式的簇结构:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_09.jpg

图 3.9:邻域半径大小变化的影响

左侧的数据点将被分类为随机噪声。右侧的数据点有多个邻居,可能会形成一个自己的簇。

活动 3.01:从零开始实现 DBSCAN

在面试中,你被要求使用生成的二维数据集从零开始创建 DBSCAN 算法。为此,你需要将邻域搜索的理论转化为生产代码,并通过递归调用来添加邻居。正如前一节所解释的,你将使用距离扫描,在指定点周围的空间内添加这些邻居。

根据你在前几章中学到的关于 DBSCAN 和距离度量的知识,从零开始用 Python 实现 DBSCAN。你可以自由使用 NumPy 和 SciPy 来评估距离。

以下步骤将帮助你完成此活动:

  1. 生成一个随机聚类数据集。

  2. 可视化数据。

  3. 从零开始创建函数,允许你在数据集上调用 DBSCAN。

  4. 使用你创建的 DBSCAN 实现来找到生成数据集中的聚类。根据需要调整超参数,依据其性能来调优。

  5. 可视化你从零开始实现的 DBSCAN 聚类性能。

这个练习的目标是让你在使用 scikit-learn 中的完整实现之前,从零开始实现 DBSCAN 的工作原理。通过这种方式从零开始处理任何机器学习算法是非常重要的,它帮助你“赚取”使用更简便实现的能力,同时仍然能够在未来深入讨论 DBSCAN:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_10.jpg

图 3.10:预期结果

注意

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

DBSCAN 属性 – 最小点数

除了邻域半径外,成功实现 DBSCAN 的另一个核心组成部分是聚类内所需的最小数据点数,以证明数据点属于该聚类。如前所述,较低的阈值在稀疏数据集上对算法的优化非常明显。这并不意味着它在密集数据中没有用处;然而,虽然将单个随机分布的数据点轻松归类为噪声,但当我们有两个到三个点随机分布时,就变得模糊不清。例如,这些数据点应该是自己的聚类,还是也应该被分类为噪声?最小点数阈值有助于解决这个问题。

在 scikit-learn 实现的 DBSCAN 中,这个超参数出现在创建 DBSCAN 实例时传递的 min_samples 字段。这个字段与邻域半径大小超参数一起非常有价值,能够完善你的基于密度的聚类方法:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_11.jpg

图 3.11:决定数据点是否为噪声或聚类的最小点数阈值

如果最小点数阈值为 10 个点,则它将在该邻域内将数据归类为噪声。

在实际应用中,当你有大量数据时,最小点数会产生很大的影响。回到葡萄酒聚类的例子,如果你的商店实际上是一个大型葡萄酒仓库,你可能有成千上万瓶酒,其中只有一两瓶酒可以被视为一个单独的聚类。根据你的用例,这可能是有用的;然而,需要牢记的是,数据中的主观量级。如果你有数百万个数据点,随机噪声很容易被视为数百甚至数千个单独的销售记录。然而,如果你的数据规模是几百或几千个数据点,单个数据点也可能被视为随机噪声。

练习 3.02:评估最小点数阈值的影响

类似于练习 3.01评估邻域半径大小的影响,我们探讨了设置合适邻域半径大小的值,本次我们将重复该练习,但改为在各种数据集上改变最小点数阈值。

使用我们当前的 DBSCAN 实现,我们可以轻松调整最小点数阈值。调整此超参数,并观察它在生成数据上的表现。

通过调整 DBSCAN 的最小点数阈值,你将了解它如何影响你的聚类预测质量。

再次,我们从随机生成的数据开始:

  1. 生成一个随机聚类数据集,如下所示:

    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)
    
  2. 如下所示,可视化数据:

    # Visualize the data
    plt.scatter(X[:,0], X[:,1])
    plt.show()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_12.jpg

    图 3.12:生成数据的图表

  3. 使用与之前相同的绘图数据,让我们选取练习 3.01评估邻域半径大小的影响中的一个表现更好的邻域半径大小——eps = 0.7

    db = DBSCAN(eps=0.7, min_samples=10, metric='euclidean')
    

    注意

    eps是一个可调的超参数。在前一个练习的步骤 3中,我们使用了0.5的值。在这一步,我们根据对该参数的实验使用了eps = 0.7

  4. 实例化 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()
    

    查看生成的第一个图表,我们可以看到如果你严格按照练习 3.01评估邻域半径大小的影响的要求,用 10 个最小点作为聚类成员资格的阈值,你会得到的结果:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_13.jpg

图 3.13:具有最小 10 个点的玩具问题图

剩余的两个超参数选项可以明显影响你的 DBSCAN 聚类算法的性能,并展示了数字的微小变化如何极大地影响性能:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_03_14.jpg

图 3.14:玩具问题的图表

正如您所看到的,仅仅将最小点数从 19 改为 20 就会在我们的特征空间中增加一个额外(不正确的!)的聚类。通过本练习学到的关于最小点数的知识,您现在可以调整 scikit-learn 实现中的 epsilon 和最小点数阈值,以达到最佳聚类数。

注意

在我们原始生成的数据中,我们创建了八个聚类。这表明最小点数的微小变化可能会添加完全不应存在的新聚类。

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

您还可以在packt.live/31XUeqi上在线运行此示例。

活动 3.02:比较 DBSCAN 与 k-means 和层次聚类

在前面的章节中,我们尝试使用层次聚类将不同的葡萄酒分组在一起。让我们再次尝试使用 DBSCAN 这种方法,看看邻域搜索是否能取得更好的效果。作为提醒,您正在管理商店的库存,并收到了大量葡萄酒的运输过程中标签掉落的情况。幸运的是,您的供应商提供了每瓶酒的化学读数及其对应的序列号。不幸的是,您无法打开每瓶酒品尝其差异 - 您必须找到一种方法将未贴标签的酒重新按照其化学读数分组!您从订单列表中知道,您订购了三种不同类型的葡萄酒,并且只给出了两种葡萄酒属性来将酒类重新分组。

在之前的章节中,我们能够看到 k-means 和层次聚类在葡萄酒数据集上的表现。在我们的最佳情况下,我们能够实现 0.59 的轮廓分数。使用 scikit-learn 的 DBSCAN 实现,让我们看看是否可以获得更好的聚类效果。

这些步骤将帮助您完成活动:

  1. 导入必要的包。

  2. 加载葡萄酒数据集并检查数据的外观。

  3. 可视化数据。

  4. 使用 k-means、凝聚聚类和 DBSCAN 生成聚类。

  5. 评估几种不同的 DSBSCAN 超参数选项及其对轮廓分数的影响。

  6. 基于最高轮廓分数生成最终的聚类。

  7. 可视化使用三种方法生成的聚类。

    注意

    我们从archive.ics.uci.edu/ml/datasets/wine获取了此数据集。[引用:Dua, D. and Graff, C. (2019). UCI Machine Learning Repository [archive.ics.uci.edu/ml]。Irvine, CA: 加利福尼亚大学,信息与计算机科学学院]。您还可以在packt.live/3bW8NME上访问它。

完成此活动后,你将重新构建一个完整的聚类问题工作流程。你已经在第二章层次聚类中熟悉了数据,并且在本活动结束时,你将进行模型选择,找到适合你数据集的最佳模型和超参数。你将为每种聚类类型获得酒类数据集的轮廓系数。

注意

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

DBSCAN 与 k 均值和层次聚类

现在你已经理解了 DBSCAN 的实现方式,以及可以调整的多个超参数来驱动性能,让我们来看看它与我们之前介绍的聚类方法——k 均值聚类和层次聚类——有何不同。

你可能在活动 3.02比较 DBSCAN 与 k 均值和层次聚类中注意到,DBSCAN 在通过轮廓系数找到最优簇时可能有些挑剔。这是邻域方法的一个缺点——当你对数据中簇的数量有一些了解时,k 均值和层次聚类的表现通常非常好。在大多数情况下,这个簇的数量较少,你可以通过多次尝试不同的数量来观察其表现。相比之下,DBSCAN 采取了一种自下而上的方法,通过调整超参数并找到它认为重要的簇。在实际操作中,当前两种方法失败时,考虑使用 DBSCAN 会很有帮助,因为它需要较多的调整才能正常工作。尽管如此,当你的 DBSCAN 实现正常工作时,它通常会远远优于 k 均值和层次聚类(在实际中,这通常发生在高度交织但仍然离散的数据中,例如包含两个半月形的特征空间)。

与 k 均值和层次聚类相比,DBSCAN 可能更高效,因为它只需要查看每个数据点一次。与多次迭代寻找新质心并评估其最近邻的过程不同,在 DBSCAN 中,一旦一个点被分配到某个簇,它的簇成员关系就不会再发生变化。DBSCAN 与层次聚类相比,另一大关键特点是,它不需要在创建时显式地传递期望的簇数量,而 k 均值却需要。这一点在你没有外部指导如何拆分数据集时非常有帮助。

总结

在本章中,我们讨论了层次聚类和 DBSCAN,以及它们最适合应用的情况。虽然层次聚类在某些方面可以被看作是 k-means 中最近邻方法的扩展,但 DBSCAN 通过应用密度的概念来解决寻找邻居的问题。当数据非常复杂且交织在一起时,这种方法可能非常有用。虽然 DBSCAN 非常强大,但它并不是万无一失的,具体效果也取决于原始数据的表现,有时甚至可能显得过于复杂。

然而,结合了 k-means 和层次聚类,DBSCAN 在聚类任务中为无监督学习提供了一个强大的工具箱。在面对该领域的任何问题时,比较每种方法的表现并观察哪种方法效果最佳是非常值得的。

在探索了聚类后,我们将进入无监督学习中另一个关键技能:降维。通过智能地减少维度,我们可以使聚类更加易于理解,并能够与利益相关者进行沟通。降维对于以最有效的方式创建各种机器学习模型也至关重要。在下一章,我们将深入研究主题模型,并查看在这些章节中学习的聚类方面如何应用于自然语言处理(NLP)类型的问题。

第五章:4. 降维技术与 PCA

概述

在本章中,我们将应用降维技术,并描述主成分和降维背后的概念。在使用 scikit-learn 解决问题时,我们将应用主成分分析PCA)。我们还将比较手动 PCA 与 scikit-learn 的实现。到本章结束时,你将能够通过提取数据中最重要的方差成分,来减小数据集的规模。

介绍

在上一章,我们讨论了聚类算法及其在从大量数据中提取潜在意义方面的应用。本章将探讨在无监督学习算法中使用不同特征集(或特征空间),我们将从讨论降维开始,特别是主成分分析PCA)。接着,我们将通过探索两种独立强大的机器学习架构——基于神经网络的自动编码器,来扩展我们对不同特征空间优势的理解。神经网络无疑在监督学习问题中享有强大的声誉。此外,通过使用自动编码器阶段,神经网络已经被证明在无监督学习问题中具有足够的灵活性。最后,我们将基于神经网络实现和降维进行扩展,介绍在第六章中涵盖的 t-分布最近邻方法,t-分布随机邻居嵌入。这些技术在处理高维数据时非常有用,如图像处理或包含多个特征的数据集。一些降维技术的一个重要商业优势是,它有助于去除那些对最终输出影响不大的特征。这为提升算法效率创造了机会,而不损失性能。

什么是降维?

降维是数据科学家工具箱中的一项重要工具,由于其广泛的应用场景,几乎已成为该领域的基本知识。因此,在我们考虑降维及其降维的原因之前,我们必须首先清楚理解“维度”是什么。简单来说,维度是与数据样本相关的维度、特征或变量的数量。通常,可以将其视为电子表格中的列数,其中每个样本占一行,每列描述样本的一个属性。以下表格就是一个例子:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_01.jpg

图 4.1:具有三种不同特征的两组数据样本

在上表中,我们有两个数据样本,每个样本有三个独立的特征或维度。根据正在解决的问题或数据集的来源,我们可能希望减少每个样本的维度数量,而不丢失提供的信息。这就是降维可以帮助我们的地方。但是降维究竟如何帮助我们解决问题呢?我们将在接下来的部分详细介绍其应用。然而,假设我们有一个非常大的时间序列数据集,比如心电图或心电图(在某些国家也称为心电图),如下图所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_02.jpg

图 4.2:心电图(ECG 或 EKG)

这些信号是从贵公司新款手表中捕获的,我们需要寻找心脏病发作或中风的迹象。在查看数据集后,我们可以得出一些观察结果:

  • 大多数单独的心跳非常相似。

  • 数据中存在来自记录系统或患者在记录期间移动时的一些噪声。

  • 尽管存在噪声,心跳信号仍然可见。

  • 数据量非常大 - 使用手表上可用的硬件无法处理太多数据。

正是在这种情况下,降维技术真正发挥了作用。通过使用降维,我们能够从信号中去除大部分噪声,这反过来将有助于算法对数据的性能以及减小数据集大小以满足更低的硬件要求。本章中要讨论的技术,特别是 PCA 和自编码器,已在研究和工业中得到了有效地应用,以有效地处理、聚类和分类这类数据集。

降维的应用场景

在我们开始详细研究降维和 PCA 之前,我们将讨论这些技术的一些常见应用:

  • 预处理/特征工程:最常见的应用之一是在开发机器学习解决方案的预处理或特征工程阶段。在算法开发过程中提供的信息质量,以及输入数据与期望结果之间的相关性,对于设计出高性能的解决方案至关重要。在这种情况下,PCA 可以提供帮助,因为我们能够从数据中分离出最重要的信息成分,并将其提供给模型,以便仅提供最相关的信息。这也有一个次要的好处,即我们减少了提供给模型的特征数量,从而可以减少需要完成的计算量。这可以减少系统的整体训练时间。这个特征工程的一个典型应用案例是预测某笔交易是否存在信用卡盗刷风险。在这种情况下,您可能会面临数百万笔交易,每笔交易有几十个甚至上百个特征。这将是资源密集型的,甚至在实时运行预测算法时都几乎不可能;然而,通过使用特征预处理,我们可以将许多特征提炼成仅仅是最重要的 3-4 个特征,从而减少运行时间。

  • 降噪:降维也可以作为一种有效的降噪/过滤技术。预期信号或数据集中的噪音并不占据数据变化的主要部分。因此,我们可以通过去除较小的变化成分来去除信号中的一些噪音,然后将数据恢复到原始的数据空间。在下面的示例中,左侧的图像已经过滤到前 20 个最重要的数据源,这为我们提供了右侧的图像。我们可以看到图像的质量有所降低,但关键信息仍然存在:https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_03.jpg

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

图 4.3:使用降维过滤的图像

注意

这张照片由 Arthur Brognoli 拍摄,来自 Pexels,并根据www.pexels.com/photo-license/提供免费下载。在这种情况下,左侧是原始图像,右侧是经过过滤的图像。

  • 生成合理的人工数据集:由于主成分分析(PCA)将数据集分解为信息(或变化)的组件,我们可以研究每个组件的影响,或者通过调整特征值之间的比例来生成新的数据集样本。本章稍后会详细介绍特征值。我们可以对这些组件进行缩放,这实际上是增加或减少该特定组件的重要性。这也被称为统计形状建模,因为一个常见的方法是使用它来创建合理的形状变体。它还用于在主动形状建模过程中检测图像中的面部标志点。

  • 金融建模/风险分析:降维为金融行业提供了一个有用的工具包,因为能够将大量的市场指标或信号合并成较少的几个组件,可以加快计算速度并提高效率。类似地,这些组件可以用于突出那些风险较高的产品/公司。

高维灾难

在我们理解使用降维技术的好处之前,我们必须首先理解为什么需要减少特征集的维度。高维灾难是一个常用术语,用来描述在处理具有高维特征空间的数据时所遇到的问题;例如,为每个样本收集的属性数量。考虑一下《吃豆人》游戏中的点位置数据集。你的角色,吃豆人,处于一个由两个维度或坐标(x, y)定义的虚拟世界中的某个位置。假设我们正在创建一个新的电脑敌人:一个由人工智能驱动的鬼怪,用来对抗玩家,它需要一些关于玩家角色的信息来做出自己的游戏逻辑决策。为了让这个机器人有效,我们需要玩家的位置信息(x, y)和他们在各个方向上的速度(vx, vy),以及玩家最近五个(x, y)位置的数据,剩余的心数和迷宫中剩余的能量豆数量(能量豆暂时允许吃豆人吃掉鬼怪)。因此,在每个时刻,我们的机器人需要 16 个单独的特征(或维度)来做出决策。这 16 个特征对应于 5 个先前的位置数据乘以 2 个xy坐标 + 当前玩家位置的 2 个xy坐标 + 玩家速度的 2 个xy坐标 + 1 个心数特征 + 1 个能量豆特征 = 16。这显然比单纯由位置提供的两个维度要多得多:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_04.jpg

图 4.4:吃豆人游戏中的维度

为了解释降维的概念,我们将考虑一个虚构的数据集(参见图 4.5),其中xy坐标作为特征,构成了特征空间中的两个维度。需要注意的是,这个例子绝不是数学证明,而是旨在提供一种可视化增加维度效果的方法。在这个数据集中,我们有六个单独的样本(或点),我们可以在特征空间中可视化目前占据的体积,约为*(3 – 1) x (4 – 2) = 2 x 2 = 4*平方单位:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_05.jpg

图 4.5:二维特征空间中的数据

假设数据集包含相同数量的点,但每个样本增加了一个特征(z坐标)。现在,占据的数据体积大约是2 x 2 x 2 = 8立方单位。因此,我们现在有相同数量的样本,但数据集所包围的空间变大了。因此,数据在可用空间中的相对体积变小,变得更加稀疏。这就是维度的诅咒;随着我们增加可用特征的数量,数据的稀疏性增加,从而使得统计相关性更难以识别。回到我们创建一个视频游戏机器人与人类玩家对战的例子,我们有 16 个特征,这些特征是不同类型的混合:位置、速度、能量道具和生命值。根据每个特征的可能取值范围以及每个特征对数据集的方差,这些数据可能会变得非常稀疏。即使在受限的吃豆人世界中,每个特征的潜在方差也可能非常大,有些特征的方差甚至比其他特征大得多。

因此,在不处理数据集稀疏性的情况下,额外的特征提供了更多的信息,但可能无法提高我们机器学习模型的性能,因为统计相关性更难以识别。我们希望做的是保留额外特征提供的有用信息,同时最小化稀疏性的负面影响。这正是降维技术的设计目的,这些技术在提高机器学习模型性能方面可以非常强大。

在本章中,我们将讨论多种不同的降维技术,并将更加详细地介绍其中最重要和最有用的方法——主成分分析(PCA),并提供一个例子。

降维技术概述

任何降维技术的目标都是管理数据集的稀疏性,同时保留其中提供的有用信息。在我们的分类案例中,降维通常作为一个重要的预处理步骤,在实际分类之前进行。大多数降维技术的目标是通过特征投影的过程来完成这一任务,将数据从高维空间调整到低维空间,以去除数据的稀疏性。同样,为了可视化投影过程,考虑一个三维空间中的球体。我们可以将球体投影到低维的二维空间,得到一个圆形,尽管丢失了一些信息(z坐标的值),但保留了描述其原始形状的大部分信息。我们仍然知道球体的原点、半径和流形(轮廓),并且仍然非常清楚它是一个圆。因此,根据我们要解决的问题,我们可能已经在保留重要信息的同时降低了维度:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_06.jpg

图 4.6:3D 球体投影到 2D 空间

通过在数据集上进行降维预处理,可以获得的第二个好处是改进的计算性能。由于数据已经被投影到低维空间,它将包含更少的但可能更强大的特征。特征较少意味着,在后续的分类或回归阶段,处理的数据集规模显著更小。这可能会减少分类/回归所需的系统资源和处理时间,并且在某些情况下,降维技术还可以直接用于完成分析。

这个类比还引入了降维中的一个重要考虑因素。我们总是在尝试平衡由于投影到低维空间而导致的信息丢失,同时减少数据的稀疏性。根据问题的性质和使用的数据集,正确的平衡可能会显现出来,并且相对简单。在一些应用中,这个决策可能依赖于额外验证方法的结果,比如交叉验证(特别是在监督学习问题中)或领域专家的评估。在这种情况下,交叉验证指的是将数据集的滚动部分划分出来进行测试,而其余部分作为训练集,直到数据集的所有部分都被使用。此方法有助于减少机器学习问题中的偏差。

我们喜欢以一种方式思考降维中的权衡:考虑在计算机上传输文件或图像时进行压缩。降维技术,如 PCA,本质上是将信息压缩到更小的尺寸以便传输的方法,在许多压缩方法中,压缩过程中会发生一些损失。有时,这些损失是可以接受的;例如,如果我们需要将一个 50 MB 的图像缩小到 5 MB 以进行传输,我们仍然可以看到图像的主要内容,但可能一些较小的背景细节会变得模糊不清。我们也不会期望能够从压缩后的版本恢复出原始图像的像素完美表示,但我们可以期待恢复时会有一些额外的伪影,例如模糊。

降维

降维技术在机器学习中有许多应用,因为提取数据集中的有用信息能够在许多机器学习问题中提供性能提升。它们在无监督学习中尤为有用,因为无监督学习方法的数据集不包含任何真实标签或目标。无监督学习中,训练环境被用来以适合解决问题的方式组织数据(例如,通过聚类进行分类),这通常是基于数据集中的最重要信息。降维提供了一种有效的提取重要信息的方法,并且由于我们可以使用多种不同的方法,因此回顾一些可用的选项是有益的:

  • 线性判别分析 (LDA):这是一种特别实用的技术,既可以用于分类,也可以用于降维。LDA 将在第七章主题建模中进行详细介绍。

  • 非负矩阵分解 (NMF):像许多降维技术一样,这依赖于线性代数的性质来减少数据集中的特征数。NMF 也将在第七章主题建模中进行详细介绍。

  • 奇异值分解 (SVD):这与 PCA(本章将详细介绍)有些相关,也是一个矩阵分解过程,与 NMF 没有太大区别。

  • 独立成分分析 (ICA):这与 SVD 和 PCA 有一些相似之处,但通过放宽数据为高斯分布的假设,使得非高斯数据也能被分离出来。

迄今为止所描述的每种方法都使用线性变换来减少数据在原始实现中的稀疏性。这些方法中的一些还有使用非线性核函数进行分离过程的变体,提供了以非线性方式减少稀疏性的能力。根据所使用的数据集,非线性核可能在从信号中提取最有用的信息时更为有效。

主成分分析(PCA)

如前所述,PCA 是一种常用且非常有效的降维技术,它通常是许多机器学习模型和技术的预处理阶段。因此,我们将把本书的这一部分专门用来详细探讨 PCA,而不仅仅是其他方法。PCA 通过将数据分离成一系列组件来减少数据集的稀疏性,其中每个组件代表数据中的一个信息源。正如其名称所示,PCA 生成的第一个组件,即主成分,包含了数据中大部分的信息或方差。主成分通常可以被认为是在均值之外,贡献最多的有趣信息。随着每个后续组件的生成,贡献的信息虽然减少,但压缩数据中的微妙之处也增多。如果我们将所有这些组件放在一起使用,那么 PCA 将没有任何好处,因为它会还原回原始数据集。为了澄清这个过程以及 PCA 返回的信息,我们将通过一个实际的例子,手动完成 PCA 的计算。但首先,我们必须复习一些执行 PCA 计算所需的基础统计概念。

均值

均值,或称平均值,就是将所有值相加并除以数据集中的值的数量。

标准差

通常被称为数据的分布,并与方差相关,标准差是衡量数据有多少接近均值的一个指标。在正态分布的数据集中,约 68%的数据位于均值的一个标准差内(即在(均值 - 1std)到(均值 + 1std)之间,如果数据是正态分布的,你可以找到 68%的数据)。

方差和标准差之间的关系相当简单——方差是标准差的平方。

协方差

当标准差或方差是基于单一维度计算的数据的分布时,协方差则是一个维度(或特征)相对于另一个维度的方差。当计算某一维度相对于自身的协方差时,结果与简单计算该维度的方差相同。

协方差矩阵

协方差矩阵是数据集可能计算出的协方差值的矩阵表示。除了在数据探索中非常有用,协方差矩阵在执行数据集的主成分分析(PCA)时也是必需的。要确定一个特征相对于另一个特征的方差,我们只需在协方差矩阵中查找对应的值。在下图中,我们可以看到,在第 1 列第 2 行的值是特征或数据集 Y 相对于 X 的方差(cov(Y, X)))。我们还可以看到,协方差矩阵的对角线列包含了对相同特征或数据集计算的协方差值;例如,cov(X, X)。在这种情况下,值就是 X 的方差:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_07.jpg

图 4.7:协方差矩阵

通常,每个协方差的确切值并不像查看协方差矩阵中每个协方差的大小和相对大小那样有趣。一个特征相对于另一个特征的协方差值很大,意味着一个特征相对于另一个特征的变化显著,而接近零的值则意味着变化很小。协方差中另一个值得注意的方面是其符号;正值表示随着一个特征的增加或减少,另一个特征也会增加或减少,而负协方差则表示两个特征相互背离,一个特征增加时另一个特征减少,反之亦然。

幸运的是,numpyscipy 提供了高效执行这些计算的函数。在接下来的练习中,我们将在 Python 中计算这些值。

练习 4.01:使用 pandas 库计算均值、标准差和方差

在这个练习中,我们将简要回顾如何使用 numpypandas Python 库计算一些基础的统计概念。在本练习中,我们将使用一个包含不同品种小麦种子测量数据的数据库,该数据集是通过 X 射线成像创建的。这个数据集可以在附带的源代码中找到,包含了来自三种不同小麦品种:Kama、Rosa 和 Canadian 的七个独立测量值(area Aperimeter Pcompactness Clength of kernel LKwidth of kernel WKasymmetry coefficient A_Coeflength of kernel groove LKG)。

注意

这个数据集来源于 archive.ics.uci.edu/ml/datasets/seed(UCI 机器学习库 [archive.ics.uci.edu/ml])。加利福尼亚大学尔湾分校信息与计算机科学学院。引用:贡献者感激地感谢波兰科学院农物理研究所(Institute of Agrophysics of the Polish Academy of Sciences in Lublin)对其工作的支持。数据集也可以从 packt.live/2RjpDxk 下载。

要执行的步骤如下:

  1. 导入pandasnumpymatplotlib包以供使用:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    
  2. 加载数据集并预览前五行数据:

    df = pd.read_csv('../Seed_Data.csv')
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_08.jpg

    图 4.8:数据头

  3. 我们只需要面积A和内核长度LK特征,因此删除其他列:

    df = df[['A', 'LK']]
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_09.jpg

    图 4.9:清洗数据后的数据头

  4. 通过绘制ALK的值来可视化数据集:

    plt.figure(figsize=(10, 7))
    plt.scatter(df['A'], df['LK'])
    plt.xlabel('Area of Kernel')
    plt.ylabel('Length of Kernel')
    plt.title('Kernel Area versus Length')
    plt.show()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_10.jpg

    图 4.10:数据的绘图

  5. 使用pandas方法计算均值:

    df.mean()
    

    输出如下:

    A     14.847524
    LK     5.628533
    dtype: float64
    
  6. 使用numpy方法计算均值:

    np.mean(df.values, axis=0)
    

    输出如下:

    array([14.84752381,  5.62853333])
    
  7. 使用pandas方法计算标准差值:

    df.std()
    

    输出如下:

    A     2.909699
    LK    0.443063
    dtype: float64
    
  8. 使用numpy方法计算标准差值:

    np.std(df.values, axis=0)
    

    输出如下:

    array([2.90276331, 0.44200731])
    
  9. 使用pandas方法计算方差值:

    df.var()
    

    输出如下:

    A     8.466351
    LK    0.196305
    dtype: float64
    
  10. 使用numpy方法计算方差值:

    np.var(df.values, axis=0)
    

    输出如下:

    array([8.42603482, 0.19537046])
    
  11. 使用pandas方法计算协方差矩阵:

    df.cov()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_11.jpg

    图 4.11:使用 pandas 方法的协方差矩阵

  12. 使用numpy方法计算协方差矩阵:

    np.cov(df.values.T)
    

    输出如下:

    array([[8.46635078, 1.22470367],
           [1.22470367, 0.19630525]])
    

现在我们已经知道如何计算基础的统计值,我们将把注意力转向 PCA 的其他组成部分。

注意

要查看本节的源代码,请参考packt.live/2BHiLFz

你也可以在线运行此示例,访问packt.live/2O80UtW

特征值和特征向量

特征值和特征向量是物理学和工程学领域中非常重要的数学概念,它们也是计算数据集主成分的最后步骤。特征值和特征向量的准确数学定义超出了本书的范围,因为它涉及较多的内容,并且需要对线性代数有一定的理解。任何一个大小为 n x n的方阵A,都存在一个形状为n x 1的向量x,使得它满足以下关系:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_12.jpg

图 4.12:表示 PCA 的方程

这里,术语https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_Formula_01.png是一个数值,表示特征值,而x表示相应的特征向量。N表示矩阵A的阶数。矩阵A将有n个特征值和特征向量。我们不深入探讨 PCA 的数学细节,接下来让我们看一下另一种表示前述方程的方式,如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_13.jpg

图 4.13:表示 PCA 的替代方程式

简单来说,将其应用于 PCA,我们可以推导出以下结论:

  • 协方差矩阵 (A):如前一节所述,在进行特征值分解之前,矩阵A应该是方阵。由于在我们的数据集中,行数大于列数(假设数据集的形状是m x n,其中m是行数,n是列数),因此我们无法直接进行特征值分解。为了对矩形矩阵执行特征值分解,首先需要通过计算其协方差矩阵将其转换为方阵。协方差矩阵的形状为n x n,即它是一个n阶的方阵。

  • 特征向量 (U) 是对数据集贡献信息的组成部分,如本节第一段中所述的主成分称为特征向量。每个特征向量描述数据集中的某些变化性。这种变化性由相应的特征值表示。特征值越大,贡献越大。特征向量矩阵的形状为n x n

  • 特征值 (https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_Formula_02.png) 是描述每个特征向量对数据集贡献大小的单个值。如前所述,描述最大贡献的特征向量称为主成分,因此具有最大的特征值。相应地,具有最小特征值的特征向量对数据的方差或信息贡献最小。特征值是对角矩阵,其中对角元素表示特征值。

请注意,即使是数据的协方差矩阵的 SVD 也会产生特征值分解,我们将在练习 4.04中看到,scikit-learn PCA。然而,SVD 使用不同的过程来分解矩阵。请记住,特征值分解仅适用于方阵,而 SVD 也可以应用于矩阵。

注意

方阵:方阵的行数和列数相等。方阵的行数被称为矩阵的阶数。行列数不等的矩阵称为矩形矩阵。

对角矩阵:对角矩阵的非对角元素全为零。

练习 4.02:计算特征值和特征向量

如前所述,手动推导和计算特征值和特征向量有些复杂,超出了本书的范围。幸运的是,numpy为我们提供了计算这些值的所有功能。再次,我们将使用 Seeds 数据集作为示例:

注意

该数据集来源于archive.ics.uci.edu/ml/datasets/seeds。(UCI 机器学习库 [archive.ics.uci.edu/ml]。加利福尼亚州尔湾:加利福尼亚大学信息与计算机科学学院。)引用:贡献者由衷感谢波兰科学院农业物理研究所(位于卢布林)对其工作的支持。该数据集还可以从packt.live/34gOQ0B下载。

  1. 导入 pandasnumpy 包:

    import pandas as pd
    import numpy as np
    
  2. 加载数据集:

    df = pd.read_csv('../Seed_Data.csv')
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_14.jpg

    图 4.14:数据集的前五行

  3. 再次,我们只需要 ALK 特征,所以删除其他列:

    df = df[['A', 'LK']]
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_15.jpg

    图 4.15:核特征的面积和长度

  4. numpy 的线性代数模块中,使用 eig 函数计算 eigenvalueseigenvectors 特征向量。注意此处使用了数据的协方差矩阵:

    eigenvalues, eigenvectors = np.linalg.eig(np.cov(df.T))
    

    注意

    numpy 函数 cov 可以用来计算数据的协方差矩阵。它产生一个与数据特征数相等的方阵。

  5. 看一下特征值;我们可以看到第一个值是最大的,所以第一个特征向量贡献了最多的信息:

    eigenvalues
    

    输出如下:

    array([8.64390408, 0.01875194])
    
  6. 观察特征值作为数据集总方差百分比是很方便的。我们将使用一个累积和函数来实现这一点:

    eigenvalues = np.cumsum(eigenvalues)
    eigenvalues
    

    输出如下:

    array([8.64390408, 8.66265602])
    
  7. 除以最后一个或最大值,将特征值转换为百分比:

    eigenvalues /= eigenvalues.max()
    eigenvalues
    

    输出如下:

    array([0.99783531, 1.])
    

    我们可以看到,第一个(或主)成分包含了数据中 99%的变化,因此,包含了大部分信息。

  8. 现在,让我们来看看 eigenvectors

    eigenvectors
    

    输出的部分如下:

    array([[ 0.98965371, -0.14347657],
           [ 0.14347657,  0.98965371]])
    
  9. 确认特征向量矩阵的形状是(n x n)格式;即 2 x 2

    eigenvectors.shape
    

    输出如下:

    (2, 2)
    
  10. 所以,从特征值中,我们看到主成分是第一个特征向量。看看第一个特征向量的值:

    P = eigenvectors[0]
    P
    

    输出如下:

    array([0.98965371, -0.14347657])
    

我们已将数据集分解为主成分,并通过特征向量进一步降低了数据的维度。

注意

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

你也可以在线运行这个例子,访问packt.live/3f5Skrk

在后续示例中,我们将考虑 PCA,并将此技术应用于一个示例数据集。

PCA 过程

现在,我们已经准备好所有的部分来完成 PCA,以减少数据集中的维度。

完成 PCA 的整体算法如下:

  1. 导入所需的 Python 包(numpypandas)。

  2. 加载整个数据集。

  3. 从可用的数据中,选择您希望在降维中使用的特征。

    注意

    如果数据集中的特征之间存在显著的规模差异;例如,一个特征的值范围在 0 和 1 之间,另一个特征的值范围在 100 和 1000 之间,您可能需要对其中一个特征进行归一化,因为这种量级差异可能会消除较小特征的影响。在这种情况下,您可能需要将较大的特征除以其最大值。

    作为示例,看看这个:

    x1 = [0.1, 0.23, 0.54, 0.76, 0.78]

    x2 = [121, 125, 167, 104, 192]

    # 将 x2 归一化到 0 到 1 之间

    x2 = (x2-np.min(x2)) / (np.max(x2)-np.min(x2))

  4. 计算所选(并可能归一化后的)数据的协方差矩阵。

  5. 计算协方差矩阵的特征值和特征向量。

  6. 将特征值(及其对应的特征向量)从大到小排序。

  7. 计算特征值占数据集中总方差的百分比。

  8. 选择特征值和对应特征向量的数量。它们将被要求组成一个预定值的最小成分方差。

    注意

    在此阶段,排序后的特征值表示数据集总方差的百分比。因此,我们可以使用这些值来选择所需的特征向量数量,无论是用于解决问题,还是充分减少应用于模型的数据集的大小。例如,假设我们要求在 PCA 的输出中至少占有 90% 的方差。我们就会选择那些至少占有 90% 方差的特征值(及其对应的特征向量)。

  9. 将数据集与选定的特征向量相乘,您就完成了 PCA,从而减少了表示数据的特征数量。

  10. 绘制结果。

在进行下一个练习之前,请注意,转置是线性代数中的一个术语,指的是将矩阵的行和列互换。假设我们

有一个矩阵X=[1, 2, 3],那么,X的转置就是https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_Formula_03.png

练习 4.03:手动执行 PCA

对于本练习,我们将手动完成 PCA,再次使用 Seeds 数据集。对于这个例子,我们希望足够减少数据集中的维度,以包含至少 75% 的可用方差:

注意

该数据集来源于archive.ics.uci.edu/ml/datasets/seeds。 (UCI 机器学习数据集 [archive.ics.uci.edu/ml]。加利福尼亚大学,信息与计算机科学学院。)引用:贡献者感谢波兰科学院农物理研究所对其工作的支持。该数据集也可以从packt.live/2Xe7cxO下载。

  1. 导入pandasnumpy包:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    
  2. 加载数据集:

    df = pd.read_csv('../Seed_Data.csv')
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_16.jpg

    图 4.16:数据集的前五行

  3. 由于我们只需要ALK特征,因此删除其他列。在此示例中,我们没有对所选数据集进行归一化:

    df = df[['A', 'LK']]
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_17.jpg

    图 4.17:核特征的面积和长度

  4. 计算所选数据的协方差矩阵。请注意,我们需要对协方差矩阵进行转置,以确保其基于特征数量(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
    

    输出如下:

    array([[8.46635078, 1.22470367],
           [1.22470367, 0.19630525]])
    
  5. 计算协方差矩阵的特征向量和特征值。同样,使用full_matrices函数参数:

    eigenvectors, eigenvalues, _ = np.linalg\
                                   .svd(data, full_matrices=False)
    
  6. 特征值按从大到小排序返回:

    eigenvalues
    

    输出如下:

    array([8.64390408, 0.01875194])
    
  7. 特征向量作为矩阵返回:

    eigenvectors
    

    输出如下:

    array([[-0.98965371, -0.14347657],
           [-0.14347657,  0.98965371]])
    
  8. 计算数据集内方差占比的特征值:

    eigenvalues = np.cumsum(eigenvalues)
    eigenvalues /= eigenvalues.max()
    eigenvalues
    

    输出如下:

    array([0\. 99783531, 1\.        ])
    
  9. 根据练习的介绍,我们需要描述至少包含 75% 可用方差的数据。如步骤 7所述,主成分包含了 99% 的可用方差。因此,我们只需要数据集中的主成分。什么是主成分?让我们来看看:

    P = eigenvectors[0]
    P
    

    输出如下:

    array([-0.98965371, -0.14347657])
    

    现在,我们可以应用降维过程。执行主成分与数据集转置的矩阵乘法。

    注意

    降维过程是将所选特征向量与需要转换的数据进行矩阵乘法。

  10. 如果不对df.values矩阵进行转置,将无法进行乘法运算:

    x_t_p = P.dot(df.values.T)
    x_t_p
    

    输出的一部分如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_18.jpg

    图 4.18:矩阵乘法的结果

    注意

    为了执行矩阵乘法,需要对数据集进行转置,因为矩阵的内维度必须相同才能进行矩阵乘法。对于A(“A 点 B”)有效,A必须具有m x n的形状,B必须具有n x p的形状。在此示例中,AB的内维度均为n。得到的矩阵将具有m x p的维度。

    在以下示例中,PCA 的输出是一个单列的 210 样本数据集。因此,我们已将初始数据集的大小减少了一半,涵盖了大约 99%的数据方差。

  11. 绘制主成分的值:

    plt.figure(figsize=(10, 7))
    plt.plot(x_t_p)
    plt.title('Principal Component of Selected Seeds Dataset')
    plt.xlabel('Sample')
    plt.ylabel('Component Value')
    plt.show() 
    

    输出结果如下,显示了 210 样本数据集的新组件值,正如前一步中打印出来的所示:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_19.jpg

图 4.19:使用手动 PCA 转换的 Seeds 数据集

在本练习中,我们仅计算了数据集的协方差矩阵,而未事先对数据集进行任何变换。如果两个特征的均值和标准差大致相同,这是完全可以的。然而,如果一个特征的值远大于另一个特征(并且均值有所不同),那么在分解成组件时,该特征可能会主导另一个特征。这可能会导致小特征所提供的信息完全丢失。一个简单的归一化技术是在计算协方差矩阵之前从特征中减去各自的均值,从而使数据集以零为中心。我们将在练习 4.05使用手动 PCA 可视化方差减少中演示这一点。

注意

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

你也可以在packt.live/3iOvg2P上在线运行此示例。

练习 4.04:scikit-learn PCA

通常,我们不会手动完成 PCA,特别是当 scikit-learn 提供了一个优化过的 API,包含方便的方法,可以让我们轻松地将数据转换到降维空间并从中还原。在本练习中,我们将更详细地使用 scikit-learn 的 PCA 来处理 Seeds 数据集:

注意

该数据集来源于archive.ics.uci.edu/ml/datasets/seeds。(UCI 机器学习库[archive.ics.uci.edu/ml]。加利福尼亚大学欧文分校,信息与计算机科学学院。)引用:贡献者在此感谢波兰科学院农物理研究所(位于卢布林)对其工作的支持。该数据集也可以从packt.live/2Ri6VGk下载。

  1. sklearn包中导入pandasnumpyPCA模块:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.decomposition import PCA
    
  2. 加载数据集:

    df = pd.read_csv('../Seed_Data.csv')
    df.head()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_20.jpg

    图 4.20:数据集的前五行

  3. 再次说明,我们只需要ALK特征,因此需要删除其他列。在此示例中,我们不会对选择的数据集进行归一化:

    df = df[['A', 'LK']]
    df.head()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_21.jpg

    图 4.21:种子特征的面积和长度

  4. 将数据拟合到 scikit-learn 的 PCA 模型中,使用的是协方差数据。使用我们在此处的默认值,将为数据集生成最大数量的特征值和特征向量:

    model = PCA()
    model.fit(df.values)
    

    输出如下:

    PCA(copy=True, iterated_power='auto', n_components=None, 
        random_state=None, 
        svd_solver='auto', tol=0.0, whiten=False)
    

    这里,copy表示在应用任何计算之前,数据适配到模型中时会被复制。如果将copy设置为False,则传递给 PCA 的数据将被覆盖。iterated_power表示ALK特征是要保留的主成分的数量。默认值为None,它选择的组件数为样本数和特征数中较小值减一。random_state允许用户为 SVD 求解器使用的随机数生成器指定种子。svd_solver指定在 PCA 过程中使用的 SVD 求解器。tol是 SVD 求解器使用的容忍度值。使用whiten时,组件向量会乘以样本数的平方根。这会移除一些信息,但可以提高某些下游估计器的性能。

  5. 由组件(特征值)描述的方差百分比包含在explained_variance_ratio_属性中。显示explained_variance_ratio_的值:

    model.explained_variance_ratio_
    

    输出如下:

    array([0.99783531, 0.00216469])
    
  6. 通过components_属性显示特征向量:

    model.components_
    

    输出如下:

    array([[0.98965371, 0.14347657]])
    
  7. 在本次练习中,我们将仅使用主成分,因此我们将创建一个新的PCA模型,这次指定组件数(特征向量/特征值)为1

    model = PCA(n_components=1)
    
  8. 使用fit方法将covariance矩阵拟合到PCA模型中,并生成相应的特征值/特征向量:

    model.fit(df.values)
    

    输出如下:

    PCA(copy=True, iterated_power='auto', n_components=1, 
        random_state=None,
        svd_solver='auto', tol=0.0, whiten=False)
    

    该模型是使用多个默认参数拟合的,如前面输出所示。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可以在声明 PCA 对象时传递,例如model = PCA(n_components=1)

  9. 使用components_属性显示特征向量:

    model.components_
    

    输出如下:

    array([[0.98965371, 0.14347657]])
    
  10. 使用模型的fit_transform方法将 Seeds 数据集转换到低维空间。将转换后的值赋给data_t变量:

    data_t = model.fit_transform(df.values)
    
  11. 绘制转换后的值以可视化结果:

    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')
    plt.show()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_22.jpg

图 4.22:使用 scikit-learn PCA 转换后的 seeds 数据集

你刚刚使用手动 PCA 和 scikit-learn API 将 Seeds 数据集的维度降低了。但是,在我们过早庆祝之前,请比较图 4.19图 4.22;这两张图应该是相同的,对吧?我们使用了两种不同的方法在相同的数据集上完成 PCA,并且都选择了主成分。在接下来的活动中,我们将探讨为什么这两者之间会有差异。

注意

要访问此特定章节的源代码,请参考packt.live/2ZQV85c

你也可以在packt.live/2VSG99R上在线运行此示例。

活动 4.01:手动 PCA 与 scikit-learn

假设你被要求将一个旧应用程序中手动执行 PCA 的遗留代码移植到一个使用 scikit-learn 的新应用程序中。在移植过程中,你注意到手动 PCA 和移植后输出之间有一些差异。为什么我们的手动 PCA 和 scikit-learn 输出之间会有差异?比较两种方法在 Seeds 数据集上的结果。它们之间的差异是什么?

本活动的目的是通过从头开始实现 PCA,深入理解其工作原理,然后将你的实现与 scikit-learn 中包含的实现进行比较,以查看是否存在重大差异:

注意

该数据集来自archive.ics.uci.edu/ml/datasets/seeds。 (UCI 机器学习库[archive.ics.uci.edu/ml]。加利福尼亚大学欧文分校信息与计算机科学学院)。引用:贡献者感谢波兰科学院农物理研究所(位于卢布林)对他们工作的支持。该数据集也可以从packt.live/2JIH1qT下载。

  1. 导入pandasnumpymatplotlib绘图库,以及 scikit-learn 的PCA模型。

  2. 加载数据集,并根据之前的练习选择仅包含核心特征的数据。显示数据的前五行。

  3. 计算数据的covariance矩阵。

  4. 使用 scikit-learn API 并仅使用第一个主成分转换数据。将转换后的数据存储在sklearn_pca变量中。

  5. 使用手动 PCA 并仅使用第一个主成分来转换数据。将转换后的数据存储在manual_pca变量中。

  6. 注意

  7. 请注意,这两张图几乎看起来相同,但有一些关键区别。这些区别是什么?

  8. 查看是否可以修改手动 PCA 过程的输出,使其与 scikit-learn 版本一致。

    这张图将展示两种方法完成的降维结果实际上是相同的。

    提示:scikit-learn 的 API 在变换前会减去数据的均值。

sklearn_pcamanual_pca的值绘制在同一图表上,以可视化差异。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_23.jpg

注意到这两张图几乎完全相同,但有一些关键的区别。这些区别是什么?

图 4.23:预期的最终图

这张图将演示两种方法完成的降维结果实际上是相同的。

注意

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

恢复压缩后的数据集

现在我们已经涵盖了将数据集转换为低维空间的几个不同示例,我们应该考虑这种转换对数据产生了什么实际影响。使用 PCA 作为预处理步骤来减少数据中的特征数量将导致部分方差被丢弃。以下练习将引导我们完成这一过程,让我们看到转换丢弃了多少信息。

练习 4.05:使用手动 PCA 可视化方差减少

降维的一个最重要方面是理解由于降维过程从数据集中去除了多少信息。去除过多的信息将给后续处理带来额外的挑战,而去除的信息不足则违背了 PCA 或其他技术的目的。在本练习中,我们将可视化通过 PCA 从 Seeds 数据集中去除的信息量:

注意

图 4.24:核特征

  1. 导入pandasnumpymatplotlib绘图库:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    
  2. 从 Seeds 数据集中读取wheat kernel特征:

    df = pd.read_csv('../Seed_Data.csv')[['A', 'LK']]
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_24.jpg

    该数据集来源于archive.ics.uci.edu/ml/datasets/seeds。 (UCI 机器学习库[archive.ics.uci.edu/ml]。加利福尼亚大学尔湾分校信息与计算机科学学院。)引用:贡献者感谢波兰科学院农业物理研究所(位于卢布林)对他们工作的支持。该数据集也可以从packt.live/2RhnDFS下载。

    预期输出:在本活动结束时,您将使用手动 PCA 方法和 scikit-learn PCA 方法分别转换数据集。您将生成一个图表,展示这两个降维后的数据集实际上是相同的,并且您应该能够理解它们最初看起来非常不同的原因。最终图应类似于以下内容:

  3. 通过减去各自的均值,将数据集居中于零。

    means = np.mean(df.values, axis=0)
    means
    

    输出如下:

    array([14.84752381,  5.62853333])
    
  4. 要计算数据并打印结果,使用以下代码:

    data = df.values - means
    data
    

    输出的一部分如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_25.jpg

    图 4.25:输出的一部分

  5. 使用手动 PCA 对数据进行转换,基于第一个主成分:

    eigenvectors, eigenvalues, _ = np.linalg.svd(np.cov(data.T), \
                                   full_matrices=False)
    P = eigenvectors[0]
    P
    

    输出结果如下:

    array([-0.98965371, -0.14347657])
    
  6. 通过将之前的P与数据矩阵的转置版本进行点积,将数据转换为低维空间:

    data_transformed = P.dot(data.T)
    
  7. 重新塑造主成分以供后续使用:

    P = P.reshape((-1, 1))
    
  8. 要计算降维数据集的逆转换,我们需要将选定的特征向量恢复到高维空间。为此,我们将对矩阵进行求逆。矩阵求逆是另一种线性代数技术,我们将只简要介绍。一个方阵,A,如果存在另一个方阵B,并且AB=BA=I,其中I是一个特殊的矩阵,称为单位矩阵,它的对角线只有值1

    P_transformed = np.linalg.pinv(P)
    P_transformed
    

    输出结果如下:

    array([[-0.98965371, -0.14347657]])
    
  9. 准备转换后的数据以供矩阵乘法使用:

    data_transformed = data_transformed.reshape((-1, 1))
    
  10. 计算降维数据的逆转换并绘制结果,以可视化去除数据方差的效果:

    data_restored = data_transformed.dot(P_transformed)
    data_restored
    

    输出的一部分如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_26.jpg

    图 4.26:降维数据的逆转换

  11. means数组添加回转换后的数据:

    data_restored += means
    
  12. 通过绘制原始数据集和转换后数据集的图形来可视化结果:

    plt.figure(figsize=(10, 7))
    plt.plot(data_restored[:,0], data_restored[:,1], \
             linestyle=':', label='PCA restoration')
    plt.scatter(df['A'], df['LK'], marker='*', label='Original')
    plt.legend()
    plt.xlabel('Area of Kernel')
    plt.ylabel('Length of Kernel')
    plt.title('Inverse transform after removing variance')
    plt.show()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_27.jpg

    图 4.27:去除方差后的逆转换

  13. 该数据集只有两个变异成分。如果我们不移除任何成分,逆转换的结果会是什么?再次将数据转换为低维空间,但这次使用所有的特征向量:

    P = eigenvectors
    data_transformed = P.dot(data.T)
    
  14. 转置data_transformed,以便将其放入适合矩阵乘法的正确形状:

    data_transformed = data_transformed.T
    
  15. 现在,将数据恢复到高维空间:

    data_restored = data_transformed.dot(P)
    data_restored
    

    输出的一部分如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_28.jpg

    图 4.28:恢复的数据

  16. 将均值添加回恢复的数据:

    data_restored += means
    
  17. 在原始数据集的背景下可视化恢复的数据:

    plt.figure(figsize=(10, 7))
    plt.scatter(data_restored[:,0], data_restored[:,1], \
                marker='d', label='PCA restoration', c='k')
    plt.scatter(df['A'], df['LK'], marker='o', \
                label='Original', c='#1f77b4')
    plt.legend()
    plt.xlabel('Area of Kernel')
    plt.ylabel('Length of Kernel')
    plt.title('Inverse transform after removing variance')
    plt.show()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_29.jpg

图 4.29:去除方差后的逆转换

如果我们比较在本次练习中生成的两个图,我们可以看到 PCA 降维后的结果,恢复的数据集基本上是两组特征之间的负线性趋势线。我们可以将其与从所有可用成分恢复的数据集进行比较,在该数据集中,我们已经整体重建了原始数据集。

注意

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

你还可以在packt.live/3f8LDVC在线运行这个示例。

练习 4.06:使用 scikit-learn 可视化方差减少

在这个练习中,我们将再次可视化数据集降维的效果;然而,这次我们将使用 scikit-learn API。由于 scikit-learn 模型的强大和简便性,这是你在实际应用中常用的方法:

注意

该数据集来源于archive.ics.uci.edu/ml/datasets/seeds。 (UCI 机器学习库 [archive.ics.uci.edu/ml]。美国加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。) 引用:贡献者衷心感谢波兰科学院农业物理研究所(位于卢布林)对其工作的支持。该数据集也可以从packt.live/3bVlJm4下载。

  1. 导入 pandasnumpymatplotlib 绘图库和 scikit-learn 中的 PCA 模型:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.decomposition import PCA
    
  2. 从 Seeds 数据集中读取 Wheat Kernel 特征:

    df = pd.read_csv('../Seed_Data.csv')[['A', 'LK']]
    df.head()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_30.jpg

    图 4.30:来自 Seeds 数据集的小麦种子特征

  3. 使用 scikit-learn API 基于第一个主成分对数据进行变换:

    model = PCA(n_components=1)
    data_p = model.fit_transform(df.values)
    
  4. 计算降维数据的逆变换并绘制结果,以可视化从数据中去除方差的效果:

    data = model.inverse_transform(data_p)
    plt.figure(figsize=(10, 7))
    plt.plot(data[:,0], data[:,1], linestyle=':', \
             label='PCA restoration')
    plt.scatter(df['A'], df['LK'], marker='*', label='Original')
    plt.legend()
    plt.xlabel('Area of Kernel')
    plt.ylabel('Length of Kernel')
    plt.title('Inverse transform after removing variance')
    plt.show()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_31.jpg

    图 4.31:去除方差后的逆变换

  5. 该数据集中只有两个变异成分。如果我们不移除任何成分,那么逆变换的结果会是什么?我们通过计算逆变换并观察在不移除任何成分的情况下结果如何变化来找出答案:

    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['A'], df['LK'], marker='o', \
                label='Original', c='#1f77b4')
    plt.legend()
    plt.xlabel('Area of Kernel')
    plt.ylabel('Length of Kernel')
    plt.title('Inverse transform after removing variance')
    plt.show()
    

    输出如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_29.jpg

图 4.32:去除方差后的逆变换

正如我们在这里看到的,如果我们不移除 PCA 中的任何成分,进行逆变换时它将重建原始数据。我们已经演示了从数据集中去除信息的效果,并展示了使用所有可用特征向量重建原始数据的能力。

注意

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

你还可以在packt.live/3fdWYDU在线运行这个示例。

先前的练习指定了使用 PCA 将维度减少到两维,部分目的是为了便于结果的可视化。然而,我们可以使用 PCA 将维度减少到小于原始数据集的任何值。以下示例演示了如何使用 PCA 将数据集减少到三维,从而实现可视化。

练习 4.07:在 Matplotlib 中绘制 3D 图

matplotlib中创建 3D 散点图,遗憾的是不像提供一系列(x, y, z)坐标那样简单。在本练习中,我们将使用种子数据集进行一个简单的 3D 绘图示例:

注意

这个数据集来自于archive.ics.uci.edu/ml/datasets/seeds。 (UCI 机器学习库[archive.ics.uci.edu/ml]。美国加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。)

引用:贡献者感谢波兰科学院农物理研究所(位于卢布林)对他们工作的支持。该数据集也可以从packt.live/3c2tAhT下载。

  1. 导入pandasmatplotlib。为了启用 3D 绘图,您还需要导入Axes3D

    from mpl_toolkits.mplot3d import Axes3D
    import pandas as pd
    import matplotlib.pyplot as plt
    
  2. 读取数据集并选择ALKC列:

    df = pd.read_csv('../Seed_Data.csv')[['A', 'LK', 'C']]
    df.head()
    

    输出结果如下:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_33.jpg

    图 4.33:数据的前五行

  3. 在三维空间中绘制数据,并使用projection='3d'参数与add_subplot方法一起创建 3D 图:

    fig = plt.figure(figsize=(10, 7))
    # Where Axes3D is required
    ax = fig.add_subplot(111, projection='3d')
    ax.scatter(df['A'], df['LK'], df['C'])
    ax.set_xlabel('Area of Kernel')
    ax.set_ylabel('Length of Kernel')
    ax.set_zlabel('Compactness of Kernel')
    ax.set_title('Expanded Seeds Dataset')
    plt.show()
    

    绘图将如下所示:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_34.jpg

图 4.34:扩展的种子数据集

注意

尽管Axes3D库已导入但未直接使用,它对于配置三维绘图窗口是必需的。如果省略了Axes3D的导入,projection='3d'参数将会返回一个AttributeError异常。

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

您也可以在packt.live/2AFVXFr上在线运行此示例。

活动 4.02:使用扩展的种子数据集进行 PCA

在此活动中,我们将使用完整的种子数据集,观察在 PCA 分解中选择不同数量的组件的影响。此活动旨在模拟在实际问题中通常完成的过程,我们尝试确定选择的最佳组件数量,平衡维度减少和信息丢失的程度。因此,我们将使用 scikit-learn 的 PCA 模型:

注意

该数据集来自archive.ics.uci.edu/ml/datasets/seeds。 (UCI 机器学习库 [archive.ics.uci.edu/ml]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院。)引用:贡献者感激波兰科学院农业物理研究所(位于卢布林)对其工作的支持。该数据集也可以从packt.live/3aPY0nj下载。

以下步骤将帮助你完成该活动:

  1. 导入pandasmatplotlib。为了启用三维绘图,你还需要导入Axes3D

  2. 读取数据集并选择Area of KernelLength of KernelCompactness of Kernel列。

  3. 在三维中绘制数据。

  4. 创建一个不指定成分数的PCA模型。

  5. 将模型拟合到数据集。

  6. 显示特征值或explained_variance_ratio_

  7. 我们希望减少数据集的维度,但仍保持至少 90% 的方差。为了保持 90% 方差,所需的最小成分数是多少?

  8. 创建一个新的PCA模型,这次指定需要保持至少 90% 方差的成分数。

  9. 使用新模型转换数据。

  10. 绘制转换后的数据。

  11. 将转换后的数据恢复到原始数据空间。

  12. 在一个子图中绘制恢复后的三维数据,在第二个子图中绘制原始数据,以可视化去除部分方差的效果:

    fig = plt.figure(figsize=(10, 14))
    # Original Data
    ax = fig.add_subplot(211, projection='3d')
    # Transformed Data
    ax = fig.add_subplot(212, projection='3d')
    

预期输出:最终图将如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/unspr-lrn-ws/img/B15923_04_35.jpg

图 4.35:预期图

注意

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

总结

本章我们介绍了降维和 PCA 的过程。我们完成了一些练习,并掌握了通过提取数据中最重要的方差成分来减少数据集大小的技巧,使用了手动的 PCA 过程和 scikit-learn 提供的模型。在本章中,我们还将降维后的数据集恢复到原始数据空间,并观察去除方差对原始数据的影响。最后,我们讨论了 PCA 和其他降维过程的多种潜在应用。在下一章中,我们将介绍基于神经网络的自动编码器,并使用 Keras 包实现它们。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值