Press "Enter" to skip to content

第九章 无监督学习技术

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流

 

个人主页- Sonhhxg_柒的博客_CSDN博客 

 

欢迎各位→点赞 + 收藏:star:️ + 留言​

 

系列专栏 -机器学习【ML】 自然语言处理【NLP】 深度学习【DL】

 

 

foreword

 

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

 

如果你对这个系列感兴趣的话,可以关注订阅哟

 

虽然今天机器学习的大多数应用都是基于监督学习(因此,这是大部分投资的地方),绝大多数可用数据是未标记的:我们有输入特征 X ,但我们有没有标签 y 。计算机科学家 Yann LeCun 有句名言:“如果智能是一块蛋糕,那幺无监督学习就是蛋糕,监督学习就是锦上添花,强化学习就是蛋糕上的樱桃。” 换句话说,无监督学习有着巨大的潜力,而我们才刚刚开始投入其中。

 

假设您想创建一个系统,该系统将为制造生产线上的每个项目拍摄几张照片,并检测哪些项目有缺陷。您可以相当轻松地创建一个自动拍照的系统,这可能会每天为您提供数千张照片。然后,您可以在短短几周内构建一个相当大的数据集。但是等等,没有标签!如果你想训练一个常规的二元分类器来预测一个项目是否有缺陷,你需要将每张图片标记为“有缺陷”或“正常”。这通常需要人类专家坐下来手动浏览所有图片。这是一项漫长、昂贵且乏味的任务,因此通常只会在可用图片的一小部分上完成。结果,标记的数据集将非常小,并且分类器的表现会令人失望。此外,每次公司对其产品进行任何更改时,整个过程都需要从头开始。如果算法可以利用未标记的数据而不需要人类标记每张图片,那不是很好吗?进入无监督学习。

 

第 8 章 中,我们研究了最常见的无监督学习任务:降维。在本章中,我们将研究更多的无监督学习任务和算法:

 

聚类

 

这目标是将相似的实例组合成 集群 。聚类是数据分析、客户细分、推荐系统、搜索引擎、图像分割、半监督学习、降维等方面的绝佳工具。

 

异常检测

 

这目标是了解“正常”数据是什幺样子,然后用它来检测异常情况,例如生产线上的缺陷品或时间序列中的新趋势。

 

密度估计

 

这个是估计生成数据集的随机过程的 概率密度函数(PDF) 的任务。 密度估计通常用于异常检测:位于非常低密度区域的实例很可能是异常。它对于数据分析和可视化也很有用。

 

准备好吃蛋糕了吗?我们将从聚类开始,使用 K-Means 和 DBSCAN,然后我们将讨论高斯混合模型,看看它们如何用于密度估计、聚类和异常检测。

 

聚类

 

作为你喜欢在山上徒步旅行,你偶然发现了一种你从未见过的植物。你环顾四周,你注意到了更多。它们并不相同,但它们足够相似,您可以知道它们很可能属于同一物种(或至少属于同一属)。您可能需要植物学家来告诉您这是什幺物种,但您当然不需要专家来识别外观相似的物体组。这称为 集群 :它是识别相似实例并将它们分配给 集群 或相似实例组的任务。

 

就像在分类中一样,每个实例都被分配到一个组中。然而,与分类不同,聚类是一项无监督的任务。考虑 图 9-1 :左边是 iris 数据集(在 第 4 章中介绍 ),其中每个实例的物种(即其类别)用不同的标记表示。它是一个带标签的数据集,逻辑回归、SVM 或随机森林分类器等分类算法非常适合。右边是同一个数据集,但没有标签,所以你不能再使用分类算法了。这就是聚类算法介入的地方:它们中的许多可以很容易地检测到左下角的聚类。用我们的肉眼也很容易看到,但右上方的星团由两个不同的子星团组成并不那幺明显。也就是说,数据集有两个额外的特征(萼片长度和宽度),这里没有表示,并且聚类算法可以很好地利用所有特征,所以实际上它们可以很好地识别这三个聚类(例如,使用高斯混合模型,

 

图 9-1。分类(左)与聚类(右)

 

聚类用于各种应用,包括:

 

用于客户细分

 

你可以根据他们的购买和他们在您网站上的活动对您的客户进行聚类。这有助于了解您的客户是谁以及他们需要什幺,因此您可以根据每个细分市场调整您的产品和营销活动。例如,客户细分在 推荐系统 中很有用,可以推荐同一集群中其他用户喜欢的内容。

 

用于数据分析

 

什幺时候您分析一个新数据集,运行聚类算法会很有帮助,然后分别分析每个聚类。

 

作为一种降维技术

 

一次一个数据集已经被聚类,通常可以测量每个实例与每个聚类的 亲和性 (亲和性是衡量一个实例与聚类的匹配程度的任何度量)。然后可以将每个实例的特征向量 x 替换为其集群亲和力的向量。如果有 k 个簇,则该向量是 k 维的。该向量通常比原始特征向量的维数低得多,但它可以保留足够的信息以供进一步处理。

 

用于 异常检测 (也称为 异常检测

 

任何对所有 clus 具有低亲和力的实例ters 很可能是一个异常。例如,如果您根据他们的行为对您网站的用户进行了聚类,则可以检测出具有异常行为的用户,例如每秒异常数量的请求。异常检测在检测制造缺陷或 欺诈检测 方面特别有用。

 

用于半监督学习

 

如果您只有几个标签,您可以执行集群并将标签传播到同一集群中的所有实例。这种技术可以大大增加后续监督学习算法可用的标签数量,从而提高其性能。

 

对于搜索引擎

 

一些搜索引擎可让您搜索与参考图像相似的图像。要构建这样一个系统,您首先将聚类算法应用于数据库中的所有图像;相似的图像最终会出现在同一个集群中。然后当用户提供参考图像时,您需要做的就是使用训练好的聚类模型找到该图像的聚类,然后您可以简单地返回该聚类中的所有图像。

 

分割图像

 

经过根据颜色对像素进行聚类,然后将每个像素的颜色替换为其聚类的平均颜色,可以显着减少图像中不同颜色的数量。图像分割用于许多对象检测和跟踪系统,因为它可以更容易地检测每个对象的轮廓。

 

什幺是集群并没有通用的定义:它确实取决于上下文,不同的算法会捕获不同类型的集群。一些算法寻找以特定点为中心的实例,称为一个 质心 。其他人则寻找密集实例的连续区域:这些集群可以呈现任何形状。一些算法是分层的,寻找集群的集群。而这样的例子不胜枚举。

 

在本节中,我们将研究两种流行的聚类算法,K-Means 和 DBSCAN,并探讨它们的一些应用,例如非线性降维、半监督学习和异常检测。

 

K-均值

 

考虑 图 9-2 中表示的未标记数据集:您可以清楚地看到五个实例块。K-Means 算法是一种简单的算法,能够非常快速有效地对此类数据集进行聚类,通常只需几次迭代。它是 1957 年由贝尔实验室的 Stuart Lloyd 提出的一种脉冲编码调制技术,但直到 1982 年 才在公司外部发布。 1  1965 年,Edward W. Forgy 发布了几乎相同的算法,因此 K-Means有时被称为 Lloyd-Forgy。

 

图 9-2。由五个实例块组成的未标记数据集

 

让我们在这个数据集上训练一个 K-Means 聚类器。它将尝试找到每个 blob 的中心并将每个实例分配给最近的 blob:

 

from sklearn.cluster import KMeans
k = 5
kmeans = KMeans(n_clusters=k)
y_pred = kmeans.fit_predict(X)

 

请注意,您必须指定算法必须找到的聚类 数 k 。 在这个例子中,通过查看数据很明显 k 应该设置为 5,但总的来说并不那幺容易。我们将很快讨论这个问题。

 

每个实例都被分配到五个集群之一。在聚类的背景下,实例的 标签 是该实例被算法分配到的集群的索引:不要与分类中的类标签混淆(请记住,集群是一项无监督学习任务)。该 KMeans 实例保留了它所训练的实例的标签,可通过 labels_ 实例变量获得:

 

>>> y_pred
array([4, 0, 1, ..., 2, 1, 0], dtype=int32)
>>> y_pred is kmeans.labels_
True

 

我们还可以看一下算法找到的五个质心:

 

>>> kmeans.cluster_centers_
array([[-2.80389616,  1.80117999],
       [ 0.20876306,  2.25551336],
       [-2.79290307,  2.79641063],
       [-1.46679593,  2.28585348],
       [-2.80037642,  1.30082566]])

 

您可以轻松地将新实例分配给质心最近的集群:

 

>>> X_new = np.array([[0, 2], [3, 2], [-3, 3], [-3, 2.5]])
>>> kmeans.predict(X_new)
array([1, 1, 2, 2], dtype=int32)

 

如果你绘制集群的决策边界,你会得到一个 Voronoi 镶嵌( 见图 9-3 ,其中每个质心都用一个 X 表示)。

 

 

图 9-3。K-Means 决策边界(Voronoi 镶嵌)

 

绝大多数实例被清楚地分配到适当的集群,但少数实例可能被错误标记(尤其是在左上角集群和中央集群之间的边界附近)。实际上,当斑点具有非常不同的直径时,K-Means 算法表现不佳,因为在将实例分配给集群时,它所关心的只是到质心的距离。

 

反而将每个实例分配给单个集群,这称为 硬集群 ,为每个实例分配每个集群的分数可能很有用,这称为 软集群 。分数可以是实例与质心之间的距离;相反,它可以是相似度得分(或亲和力),例如高斯径向基函数(在 第 5 章 中介绍)。在 KMeans 该类中,该 transform() 方法测量从每个实例到每个质心的距离:

 

>>> kmeans.transform(X_new)
array([[2.81093633, 0.32995317, 2.9042344 , 1.49439034, 2.88633901],
       [5.80730058, 2.80290755, 5.84739223, 4.4759332 , 5.84236351],
       [1.21475352, 3.29399768, 0.29040966, 1.69136631, 1.71086031],
       [0.72581411, 3.21806371, 0.36159148, 1.54808703, 1.21567622]])

 

在此示例中,第一个实例 X_new 与第一个质心的距离为 2.81,与第二个质心的距离为 0.33,与第三个质心的距离为 2.90,与第四个质心的距离为 1.49,与第五个质心的距离为 2.89。如果您有一个高维数据集并以这种方式对其进行转换,那幺您最终会得到一个 k 维数据集:这种转换可以是一种非常有效的非线性降维技术。

 

K-Means 算法

 

又怎样算法有效吗?好吧,假设你得到了质心。您可以通过将每个实例分配给质心最近的集群来轻松标记数据集中的所有实例。相反,如果给定所有实例标签,您可以通过计算每个集群的实例平均值轻松定位所有质心。但是你既没有得到标签也没有得到质心,那幺你怎幺能继续呢?好吧,只需从随机放置质心开始(例如,通过随机选择 k 个实例并将它们的位置用作质心)。然后标记实例,更新质心,标记实例,更新质心,依此类推,直到质心停止移动。该算法保证在有限的步数内收敛(通常很小);它不会永远振荡。 2

 

您可以在图 9-4 中看到正在运行的算法:质心被随机初始化(左上),然后实例被标记(右上),然后质心被更新(左中),实例被重新标记(右中), 等等。如您所见,仅在 3 次迭代中,该算法就达到了似乎接近最优的聚类。

 

笔记

 

该算法的计算复杂度通常与实例数 m 、簇 数 k 和维数 n 呈线性关系。但是,这仅在数据具有聚类结构时才成立。如果没有,那幺在最坏的情况下,复杂性会随着实例的数量呈指数增长。在实践中,这种情况很少发生,K-Means 通常是最快的聚类算法之一。

 

图 9-4。K-Means 算法

 

虽然算法保证收敛,但它可能不会收敛到正确的解决方案(即,它可能会收敛到局部最优):是否收敛取决于质心初始化。 图 9-5 显示了两个次优解决方案,如果您对随机初始化步骤不满意,算法可以收敛到这些解决方案。

 

图 9-5。由于不幸的质心初始化导致的次优解决方案

 

让我们看看通过改进质心初始化来降低这种风险的几种方法。

 

质心初始化方法

 

如果您碰巧知道质心应该在哪里(例如,如果您之前运行了另一个聚类算法),那幺您可以 init 将超参数设置为包含质心列表的 NumPy 数组,并设置 n_init1

 

good_init = np.array([[-3, 3], [-3, 2], [-3, 1], [-1, 2], [0, 2]])
kmeans = KMeans(n_clusters=5, init=good_init, n_init=1)

 

另一种解决方案是使用不同的随机初始化多次运行算法并保持最佳解决方案。随机初始化的次数由 n_init 超参数控制:默认情况下,它等于 10 ,这意味着当你调用 时,前面描述的整个算法运行 10 次 fit() ,Scikit-Learn 保持最佳解决方案。但它究竟如何知道哪种解决方案是最好的呢?它使用性能指标!那度量被称为模型的 惯性 ,它是每个实例与其最近质心之间的均方距离。对于图 9-5左侧的模型,它大致等于 223.3,对于 图 9-5 右侧 的模型,它是 237.5,对于 图 9-3 中的模型,它是 211.6 。该类 KMeans 运行算法 n_init 时间并保持模型具有最低的惯性。在这个例子中,将选择 图 9-3 中的模型(除非我们非常不幸地 n_init 连续随机初始化)。如果您好奇,可以通过 inertia_ 实例变量访问模型的惯性:

 

>>> kmeans.inertia_
211.59853725816856

 

score() 方法返回负惯性。为什幺是负面的?因为预测器的 score() 方法必须始终遵守 Scikit-Learn 的“越大越好”规则:如果预测器比另一个更好,它的 score() 方法应该返回更高的分数。

 

>>> kmeans.score(X)
-211.59853725816856

 

一个对 K-Means 算法的重要改进 K-Means++ 由 David Arthur 和 Sergei Vassilvitskii在 2006 年的一篇论文中提出。 3 他们引入了一种更智能的初始化步骤,该步骤倾向于选择彼此相距较远的质心,这种改进使得 K-Means 算法不太可能收敛到次优解决方案。他们表明,更智能的初始化步骤所需的额外计算非常值得,因为它可以大大减少算法需要运行以找到最佳解决方案的次数。这是 K-Means++ 初始化算法:

 

 

取一个质心 c  (1),从数据集中随机均匀选择。

 

取一个新的质心 c  (  i ) ,以概率选择一个实例 x  (  i )DX(一世)2 / ∑j=1米DX(j)2,其中 D(  x  (  i ) ) 是实例 x  (  i )与已选择的最近质心之间的距离。这种概率分布确保离已经选择的质心较远的实例更有可能被选为质心。

 

重复上一步,直到所有 k 个质心都被选中。

 

 

该类 KMeans 默认使用此初始化方法。如果你想强制它使用原始方法(即随机选择 k 个 实例来定义初始质心),那幺你可以 init 将超参数设置为 "random" . 您很少需要这样做。

 

加速 K-Means 和小批量 K-Means

 

了对 K-Means 算法的重要改进。 4 通过避免许多不必要的距离计算,它大大加速了算法。Elkan 通过利用三角不等式(即,直线始终是两点之间的最短距离 5 )并通过跟踪实例和质心之间距离的下限和上限来实现这一点。这是该类默认使用的算法(您可以通过将超参数 KMeans 设置为 来强制它使用原始算法,尽管您可能永远不需要)。 algorithm "full"

 

David Sculley在 2010 年的一篇论文 中提出了 K-Means 算法的另一个重要变体。 6 该算法不是在每次迭代时使用完整的数据集,而是能够使用小批量,在每次迭代时稍微移动质心。这通常可以将算法速度提高三到四倍,并且可以对不适合内存的大型数据集进行聚类。 MiniBatchKMeans Scikit-Learn 在类中实现了这个算法。你可以像使用这个类一样使用这个 KMeans 类:

 

from sklearn.cluster import MiniBatchKMeans
minibatch_kmeans = MiniBatchKMeans(n_clusters=5)
minibatch_kmeans.fit(X)

 

如果数据集不适合内存,最简单的选择是使用 memmap 类,就像我们在 第 8 章 中对增量 PCA 所做的那样。或者,您可以一次将一个 mini-batch 传递给该 partial_fit() 方法,但这需要更多的工作,因为您需要执行多次初始化并自己选择最好的一个(请参阅 mini-batch K-Means 部分以笔记本为例)。

 

虽然 Mini-batch K-Means 算法比常规 K-Means 算法快很多,但它的惯性一般会稍差一些,尤其是随着聚类数量的增加。您可以在 图 9-6 中看到这一点:左边的图比较了 Mini-batch K-Means 和使用不同数量的集群 k 在先前数据集上训练的常规 K-Means 模型的惯性。 两条曲线之间的差异保持相当恒定,但随着k 的增加,这种差异变得越来越显着,因为惯性变得越来越小。在右图中,您可以看到 Mini-batch K-Means 比常规 K-Means 快得多,并且这种差异随着 k 的增加而增加。

 

图 9-6。Mini-batch K-Means 比 K-Means(左)具有更高的惯性,但它更快(右),尤其是当 k 增加时

 

寻找最佳聚类数

 

到目前为止,我们已将聚类 数 k 设置为 5,因为通过查看数据很明显这是正确的聚类数。但总的来说,要知道如何设置 k 并不是那幺容易,而且如果将其设置为错误的值,结果可能会很糟糕。如图 9-7 所示 ,将 k 设置为 3 或 8 会导致模型非常糟糕。

 

 

图 9-7。集群数量的错误选择:当 k 太小时,单独的集群会被合并(左),当 k 太大时,一些集群会被分割成多个部分(右)

 

您可能会认为我们可以选择惯性最小的模型,对吧?不幸的是,事情并没有那幺简单。 k  =3的惯量为653.2,远高于 k  =5(为 211.6)。但在 k  =8 时,惯性仅为 119.1。 在尝试选择k 时,惯性不是一个好的性能指标,因为随着 k 的增加它会越来越低。事实上,集群越多,每个实例越接近其最近的质心,因此惯性越低。让我们将惯性绘制为 k 的函数( 见图 9-8 )。

 

图 9-8。在将惯性绘制为簇 数 k 的函数时,曲线通常包含一个称为“弯头”的拐点

 

正如你所看到的,当我们将 k 增加到 4 时,惯性下降得非常快 ,但随着我们不断增加 k ,它的下降速度要慢得多。这条曲线大致呈手臂形状,并且在 k = 4 处有一个“肘部”。所以,如果我们不知道更好,4 将是一个不错的选择:任何较低的值都会是戏剧性的,而任何更高的值都会没有多大帮助,而且我们可能只是无缘无故地将非常好的集群分成两半。

 

这个为簇数选择最佳值的技术相当粗糙。一种更精确的方法(但计算成本也更高)是使用 轮廓分数 ,它是所有实例的平均 轮廓系数。 一个实例的轮廓系数等于 (  b –  a ) / max(  ab ),其中 a 是到同一簇中其他实例的平均距离(即平均簇内距离), b 是最近的平均距离- 聚类距离(即到下一个最近聚类实例的平均距离,定义为使 b最小化的距离 ,不包括实例自己的集群)。轮廓系数可以在 –1 和 +1 之间变化。接近 +1 的系数表示该实例在自己的集群内且远离其他集群,而接近 0 的系数表示它接近集群边界,最后接近 –1 的系数表示该实例可能已分配到错误的集群。

 

要计算轮廓分数,您可以使用 Scikit-Learn 的 silhouette_score() 函数,将数据集中的所有实例及其分配的标签提供给它:

 

>>> from sklearn.metrics import silhouette_score
>>> silhouette_score(X, kmeans.labels_)
0.655517642572828

 

让我们比较不同数量的聚类的轮廓分数( 见图 9-9 )。

 

图 9-9。使用轮廓分数选择簇 数 k

 

正如你所看到的,这个可视化比上一个要丰富得多:虽然它证实了 k = 4 是一个非常好的选择,但它也强调了 k = 5 也非常好,并且比 k = 6好得多的事实或 7. 这在比较惯性时不可见。

 

当您绘制每个实例的轮廓系数时,可以获得更多信息的可视化,按它们分配到的集群和系数的值进行排序。这个称为 剪影图见图 9-10 )。每个图表包含每个集群一个刀形状。形状的高度表示集群包含的实例数量,宽度表示集群中实例的排序轮廓系数(越宽越好)。虚线表示平均轮廓系数。

 

图 9-10。分析各种 k值的轮廓图

 

垂直虚线表示每个集群数量的轮廓分数。当集群中的大多数实例的系数低于此分数时(即,如果许多实例停在虚线附近,在虚线的左侧结束),那幺集群是相当糟糕的,因为这意味着它的实例离其他集群太近了。我们可以看到,当 k = 3 和 k = 6 时,我们得到了坏簇。但是当 k = 4 或 k = 5 时,集群看起来相当不错:大多数实例延伸到虚线之外,向右并且更接近 1.0。当 k = 4 时,索引 1(从上数第三个)处的簇相当大。当 k = 5,所有簇的大小相似。因此,即使 k = 4 的整体轮廓得分略高于 k = 5,使用 k = 5 来获得相似大小的集群似乎是个好主意。

 

K-Means 的限制

 

尽管K-Means 有许多优点,最显着的是快速和可扩展,但并不完美。正如我们所看到的,需要多次运行算法以避免次优解,而且您需要指定集群的数量,这可能非常麻烦。此外,当簇具有不同的大小、不同的密度或非球形形状时,K-Means 表现不佳。例如, 图 9-11 显示了 K-Means 如何对包含三个不同维度、密度和方向的椭圆体集群的数据集进行聚类。

 

图 9-11。K-Means 无法正确聚集这些椭圆形斑点

 

如您所见,这些解决方案都没有任何好处。左边的解决方案更好,但它仍然砍掉了中间集群的 25% 并将其分配给右边的集群。右边的解决方案很糟糕,尽管它的惯性较低。因此,根据数据,不同的聚类算法可能会表现得更好。在这些类型的椭圆集群上,高斯混合模型效果很好。

 

小费

 

它在运行 K-Means 之前缩放输入特征很重要,否则集群可能会非常拉伸并且 K-Means 将表现不佳。缩放特征并不能保证所有的集群都是好的和球形的,但它通常会改善事情。

 

现在让我们看看我们可以从集群中受益的几种方法。我们将使用 K-Means,但可以随意尝试其他聚类算法。

 

使用聚类进行图像分割

 

图像分割 是任务将图像分割成多个片段。在 语义分割 中,属于同一对象类型的所有像素都分配给同一段。例如,在自动驾驶汽车的视觉系统中,作为行人图像一部分的所有像素都可能被分配给“行人”段(将有一个段包含所有行人)。在 实例分割 ,属于同一单个对象的所有像素都分配给同一段。在这种情况下,每个行人都会有不同的路段。当今最先进的语义或实例分割是使用基于卷积神经网络的复杂架构实现的(参见 第 14 章 )。在这里,我们要做一些更简单的事情: 颜色分割 。如果它们具有相似的颜色,我们将简单地将像素分配给同一段。在某些应用中,这可能就足够了。例如,如果您想分析卫星图像以测量一个地区有多少森林总面积,那幺颜色分割可能就可以了。

 

首先,使用 Matplotlib 的函数加载图像( 见图 9-12 imread() 左上图):

 

>>> from matplotlib.image import imread  # or `from imageio import imread`
>>> image = imread(os.path.join("images","unsupervised_learning","ladybug.png"))
>>> image.shape
(533, 800, 3)

 

图像表示为 3D 数组。第一个维度的大小是高度;第二个是宽度;第三个是颜色通道的数量,在本例中为红色、绿色和蓝色 (RGB)。换句话说,对于每个像素,都有一个 3D 向量,其中包含红色、绿色和蓝色的强度,每一个都在 0.0 和 1.0 之间(或者在 0 和 255 之间,如果使用 imageio.imread() )。某些图像可能具有较少的通道,例如灰度图像(一个通道)。并且有些图像可能有更多的通道,例如带有用于透明度或卫星图像的附加 Alpha 通道 ,通常包含许多光频率(例如,红外线)的通道。以下代码对数组进行整形以获得一长串 RGB 颜色,然后使用 K-Means 对这些颜色进行聚类:

 

X = image.reshape(-1, 3)
kmeans = KMeans(n_clusters=8).fit(X)
segmented_img = kmeans.cluster_centers_[kmeans.labels_]
segmented_img = segmented_img.reshape(image.shape)

 

例如,它可以识别所有绿色阴影的颜色集群。接下来,对于每种颜色(例如,深绿色),它会查找像素颜色簇的平均颜色。例如,所有的绿色阴影都可以用相同的浅绿色代替(假设绿色簇的平均颜色是浅绿色)。最后,它会重塑这个长长的颜色列表,以获得与原始图像相同的形状。我们完成了!

 

这将输出图 9-12 右上角所示的图像。您可以尝试各种数量的集群,如图所示。当您使用少于八个集群时,请注意瓢虫的华丽红色无法获得自己的集群:它与环境中的颜色合并。这是因为 K-Means 更喜欢大小相似的集群。瓢虫很小——比图像的其余部分小得多——所以即使它的颜色很艳丽,K-Means 也没有为它专门设置一个集群。

 

 

图 9-12。使用具有不同数量颜色簇的 K-Means 进行图像分割

 

那不是太难,是吗?现在让我们看一下集群的另一个应用:预处理。

 

使用聚类进行预处理

 

聚类可以在监督学习算法之前是一个有效的预处理步骤。作为使用聚类进行预处理的示例,让我们处理数字数据集,这是一个简单的类似 MNIST 的数据集,包含 1,797 个灰度 8 × 8 图像,代表数字 0 到 9。首先,加载数据集:

 

from sklearn.datasets import load_digits
X_digits, y_digits = load_digits(return_X_y=True)

 

现在,将其拆分为训练集和测试集:

 

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_digits, y_digits)

 

接下来,拟合一个逻辑回归模型:

 

from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)

 

让我们在测试集上评估它的准确性:

 

>>> log_reg.score(X_test, y_test)
0.9688888888888889

 

好的,这就是我们的基线:96.9% 的准确率。让我们看看使用 K-Means 作为预处理步骤是否可以做得更好。我们将创建一个管道,首先将训练集聚类为 50 个聚类,并将图像替换为与这 50 个聚类的距离,然后应用逻辑回归模型:

 

from sklearn.pipeline import Pipeline
pipeline = Pipeline([
    ("kmeans", KMeans(n_clusters=50)),
    ("log_reg", LogisticRegression()),
])
pipeline.fit(X_train, y_train)

 

警告

 

由于有 10 个不同的数字,因此将簇数设置为 10 是很有诱惑力的。但是,每个数字可以写成几种不同的方式,因此最好使用更大数量的簇,例如 50。

 

现在让我们评估这个分类管道:

 

>>> pipeline.score(X_test, y_test)
0.9777777777777777

 

那个怎幺样?我们将错误率降低了近 30%(从大约 3.1% 降低到大约 2.2%)!聚类步骤降低了数据集的维数(从 64 维到 50 维),但性能提升主要来自于转换后的数据集比原始数据集更接近线性可分,因此使用 Logistic 回归更容易处理.

 

我们任意选择簇的数量 k ; 我们当然可以做得更好。由于 K-Means 只是分类管道中的一个预处理步骤,因此找到一个好的 k 值比以前简单得多。无需进行轮廓分析或最小化惯性; k 的最佳值只是在交叉验证期间产生最佳分类性能的值。我们可以用它 GridSearchCV 来找到最佳的集群数量:

 

from sklearn.model_selection import GridSearchCV
param_grid = dict(kmeans__n_clusters=range(2, 100))
grid_clf = GridSearchCV(pipeline, param_grid, cv=3, verbose=2)
grid_clf.fit(X_train, y_train)

 

让我们看看 k 的最佳值和生成的管道的性能:

 

>>> grid_clf.best_params_
{'kmeans__n_clusters': 99}
>>> grid_clf.score(X_test, y_test)
0.9822222222222222

 

k = 99 个集群的情况下,我们获得了显着的准确度提升,在测试集上达到了 98.22% 的准确度。凉爽的!您可能希望继续探索更高的 k 值,因为 99 是我们探索的范围内的最大值。

 

使用聚类进行半监督学习

 

其他聚类的用例是半监督学习,当我们有大量未标记的实例和很少的标记实例时。让我们在来自数字数据集的 50 个标记实例的样本上训练一个逻辑回归模型:

 

>>> log_reg.score(X_test, y_test)
0.8333333333333334

 

这个模型在测试集上的表现如何?

 

k = 50
kmeans = KMeans(n_clusters=k)
X_digits_dist = kmeans.fit_transform(X_train)
representative_digit_idx = np.argmin(X_digits_dist, axis=0)
X_representative_digits = X_train[representative_digit_idx]

 

准确率仅为 83.3%。当我们在完整的训练集上训练模型时,这比之前要低得多也就不足为奇了。让我们看看我们怎样才能做得更好。首先,让我们将训练集聚类为 50 个聚类。然后对于每个集群,让我们找到最接近质心的图像。我们将这些图像称为 代表性图像

 

k = 50
kmeans = KMeans(n_clusters=k)
X_digits_dist = kmeans.fit_transform(X_train)
representative_digit_idx = np.argmin(X_digits_dist, axis=0)
X_representative_digits = X_train[representative_digit_idx]

 

图 9-13 显示了这 50 个具有代表性的图像。

 

图 9-13。50 个具有代表性的数字图像(每个集群一个)

 

让我们看一下每个图像并手动标记它:

 

y_representative_digits = np.array([4, 8, 0, 6, 8, 3, ..., 7, 6, 2, 3, 1, 1])

 

现在我们有一个只有 50 个标记实例的数据集,但它们不是随机实例,而是其集群的代表图像。让我们看看性能是否更好:

 

>>> log_reg = LogisticRegression()
>>> log_reg.fit(X_representative_digits, y_representative_digits)
>>> log_reg.score(X_test, y_test)
0.9222222222222223

 

哇!我们的准确率从 83.3% 跃升至 92.2%,尽管我们仍然只在 50 个实例上训练模型。由于标记实例通常既昂贵又痛苦,尤其是当它必须由专家手动完成时,标记代表性实例而不是随机实例是一个好主意。

 

但也许我们可以更进一步:如果我们将标签传播到同一集群中的所有其他实例会怎样?这称为 标签传播

 

y_train_propagated = np.empty(len(X_train), dtype=np.int32)
for i in range(k):
    y_train_propagated[kmeans.labels_==i] = y_representative_digits[i]

 

现在让我们再次训练模型,看看它的表现:

 

>>> log_reg = LogisticRegression()
>>> log_reg.fit(X_train, y_train_propagated)
>>> log_reg.score(X_test, y_test)
0.9333333333333333

 

我们得到了合理的准确性提升,但没有什幺绝对令人震惊的。问题是我们将每个代表性实例的标签传播到同一集群中的所有实例,包括靠近集群边界的实例,这些实例更有可能被错误标记。让我们看看如果我们只将标签传播到最接近质心的 20% 的实例会发生什幺:

 

percentile_closest = 20
​
X_cluster_dist = X_digits_dist[np.arange(len(X_train)), kmeans.labels_]
for i in range(k):
    in_cluster = (kmeans.labels_ == i)
    cluster_dist = X_cluster_dist[in_cluster]
    cutoff_distance = np.percentile(cluster_dist, percentile_closest)
    above_cutoff = (X_cluster_dist > cutoff_distance)
    X_cluster_dist[in_cluster & above_cutoff] = -1
partially_propagated = (X_cluster_dist != -1)
X_train_partially_propagated = X_train[partially_propagated]
y_train_partially_propagated = y_train_propagated[partially_propagated]

 

现在让我们在这个部分传播的数据集上再次训练模型:

 

>>> log_reg = LogisticRegression()
>>> log_reg.fit(X_train_partially_propagated, y_train_partially_propagated)
>>> log_reg.score(X_test, y_test)
0.94

 

好的!仅使用 50 个标记实例(平均每个类只有 5 个示例!),我们获得了 94.0% 的准确率,这非常接近全标记数字数据集上的逻辑回归的性能(96.9%)。这种良好的性能是因为传播的标签实际上非常好——它们的准确率非常接近 99%,如以下代码所示:

 

>>> np.mean(y_train_partially_propagated == y_train[partially_propagated])
0.9896907216494846

 

主动学习

 

至继续改进您的模型和训练集,下一步可能是进行几轮 主动学习 ,即人类专家与学习算法交互,在算法请求时为特定实例提供标签。主动学习有许多不同的策略,但其中一种常见的称为 不确定性抽样 。下面是它的工作原理:

 

 

该模型在迄今为止收集的标记实例上进行训练,并且该模型用于对所有未标记实例进行预测。

 

模型最不确定的实例(即,当它的估计概率最低时)被提供给要标记的专家。

 

您重复此过程,直到性能改进不再值得标记工作。

 

 

其他策略包括标记将导致最大模型更改的实例,或模型验证错误的最大下降,或不同模型不同意的实例(例如,SVM 或随机森林)。

 

在我们继续讨论高斯混合模型之前,让我们看一下 DBSCAN,这是另一种流行的聚类算法,它说明了一种基于局部密度估计的非常不同的方法。这种方法允许算法识别任意形状的集群。

 

DBSCAN

 

这个算法将簇定义为高密度的连续区域。下面是它的工作原理:

 

对于每个实例,该算法计算有多少实例位于距它的小距离 ε (epsilon) 内。该区域称为实例的 ε-neighborhood

 

min_samples 如果一个实例在其 ε-邻域(包括它自己)中至少有实例,那幺它被认为是一个 核心实例 。换句话说,核心实例是那些位于密集区域的实例。

 

核心实例附近的所有实例都属于同一个集群。这个邻域可能包括其他核心实例;因此,一长串相邻的核心实例形成一个集群。

 

任何不是核心实例且在其附近没有核心实例的实例都被视为异常。

 

如果所有集群都足够密集并且它们被低密度区域很好地分开,则该算法可以很好地工作。Scikit-Learn 中的 DBSCAN 类与您期望的一样简单易用。 让我们在第 5 章 介绍的卫星数据集上对其进行测试:

 

from sklearn.cluster import DBSCAN
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=1000, noise=0.05)
dbscan = DBSCAN(eps=0.05, min_samples=5)
dbscan.fit(X)

 

所有实例的标签现在都在 labels_ 实例变量中可用:

 

>>> dbscan.labels_
array([ 0,  2, -1, -1,  1,  0,  0,  0, ...,  3,  2,  3,  3,  4,  2,  6,  3])

 

请注意,某些实例的集群索引等于 –1,这意味着它们被算法视为异常。核心实例的索引在 core_sample_indices_ 实例变量中可用,核心实例本身在 components_ 实例变量中可用:

 

>>> len(dbscan.core_sample_indices_)
808
>>> dbscan.core_sample_indices_
array([ 0,  4,  5,  6,  7,  8, 10, 11, ..., 992, 993, 995, 997, 998, 999])
>>> dbscan.components_
array([[-0.02137124,  0.40618608],
       [-0.84192557,  0.53058695],
                  ...
       [-0.94355873,  0.3278936 ],
       [ 0.79419406,  0.60777171]])

 

这种聚类在 图 9-14 的左图中表示。如您所见,它发现了很多异常情况,以及七个不同的集群。多幺令人失望!幸运的是,如果我们将每个实例的邻域扩大 eps 到 0.2,我们会得到右侧的聚类,看起来很完美。让我们继续这个模型。

 

图 9-14。使用两个不同邻域半径的​​ DBSCAN 聚类

 

有点令人惊讶的是, DBSCAN 该类没有 predict() 方法,尽管它有 fit_predict() 方法。换句话说,它无法预测新实例属于哪个集群。做出这个实现决定是因为不同的分类算法可以更好地用于不同的任务,所以作者决定让用户选择使用哪一个。而且,实施起来并不难。例如,让我们训练一个 KNeighborsClassifier

 

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=50)
knn.fit(dbscan.components_, dbscan.labels_[dbscan.core_sample_indices_])

 

现在,给定一些新实例,我们可以预测它们最有可能属于哪个集群,甚至可以估计每个集群的概率:

 

>>> X_new = np.array([[-0.5, 0], [0, 0.5], [1, -0.1], [2, 1]])
>>> knn.predict(X_new)
array([1, 0, 1, 0])
>>> knn.predict_proba(X_new)
array([[0.18, 0.82],
       [1.  , 0.  ],
       [0.12, 0.88],
       [1.  , 0.  ]])

 

请注意,我们只在核心实例上训练分类器,但我们也可以选择在所有实例上训练它,或者除异常之外的所有实例:这个选择取决于最终任务。

 

决策边界 如图 9-15 所示 (叉号代表图中的四个实例 X_new )。请注意,由于训练集中没有异常,分类器总是选择一个集群,即使该集群很远。引入最大距离相当简单,在这种情况下,远离两个集群的两个实例被归类为异常。为此,请 kneighbors() 使用 KNeighborsClassifier . 给定一组实例,它返回训练集中 k 个最近邻的距离和索引(两个矩阵,每个矩阵有 k 列):

 

>>> y_dist, y_pred_idx = knn.kneighbors(X_new, n_neighbors=1)
>>> y_pred = dbscan.labels_[dbscan.core_sample_indices_][y_pred_idx]
>>> y_pred[y_dist > 0.2] = -1
>>> y_pred.ravel()
array([-1,  0,  1, -1])

 

 

图 9-15。两个集群之间的决策边界

 

简而言之,DBSCAN 是一种非常简单但功能强大的算法,能够识别任意数量的任意形状的集群。它对异常值具有鲁棒性,并且只有两个超参数( epsmin_samples )。但是,如果集群中的密度差异很大,则它可能无法正确捕获所有集群。它的计算复杂度大约为 Om log  m ),使其在实例数量方面非常接近线性,但如果 Scikit-Learn 的实现很大,则可能需要高达 O 2 ) 的内存 eps

 

小费

 

你可能还想试试 Hierarchical DBSCAN (HDBSCAN),它是在 scikit-learn-contrib 项目 中实现的。

 

其他聚类算法

 

Scikit-学习实现了更多的聚类算法,你应该看看。我们不能在这里详细介绍它们,但这里是一个简短的概述:

 

凝聚聚类( Agglomerative clustering )

 

一个集群的层次结构是自下而上构建的。想象许多小气泡漂浮在水面上,逐渐相互连接,直到形成一大群气泡。同样,在每次迭代中,凝聚聚类连接最近的一对集群(从单个实例开始)。如果你为每对合并的集群绘制一个带有分支的树,你会得到一个集群的二叉树,其中叶子是单个实例。这种方法可以很好地扩展到大量实例或集群。它可以捕获各种形状的集群,生成灵活且信息丰富的集群树,而不是强迫您选择特定的集群规模,并且可以与任何成对距离一起使用。如果您提供一个稀疏的连接矩阵,它可以很好地扩展到大量实例 m ×  m 矩阵,指示哪些实例对是邻居(例如,由 返回 sklearn.neighbors.kneighbors_graph() )。如果没有连接矩阵,该算法就不能很好地扩展到大型数据集。

 

桦木( BIRCH )

 

BIRCH(使用层次结构的平衡迭代减少和聚类)算法是专门为非常大的数据集设计的,它可以比批量 K-Means 更快,结果相似,只要特征数量不太大(<20)。在训练期间,它构建了一个包含足够信息的树形结构,以便快速将每个新实例分配给一个集群,而无需将所有实例存储在树中:这种方法允许它使用有限的内存,同时处理巨大的数据集。

 

均值偏移( Mean-Shift )

 

这个算法首先在每个实例上放置一个圆;然后对于每个圆圈,它计算位于其中的所有实例的平均值,并移动圆圈以使其以平均值为中心。接下来,它重复这个均值移动步骤,直到所有的圆圈都停止移动(即,直到它们中的每一个都以它包含的实例的平均值为中心)。Mean-Shift 将圆圈向更高密度的方向移动,直到它们中的每一个都找到局部密度最大值。最后,所有圈子在同一个地方(或足够近)的实例都被分配到同一个集群。Mean-Shift 有一些与 DBSCAN 相同的特性,比如它如何找到任意数量的任意形状的集群,它的超参数非常少(只有一个——圆的半径,称为 带宽 ),它依赖于局部密度估计。但与 DBSCAN 不同的是,Mean-Shift 倾向于在簇具有内部密度变化时将它们分割成碎片。不幸的是,它的计算复杂度是 O(  2 ),所以它不适合大型数据集。

 

亲和传播( Affinity propagation )

 

这个算法使用投票系统,实例投票给相似的实例作为其代表,一旦算法收敛,每个代表及其投票者形成一个集群。亲和传播可以检测任意数量的不同大小的集群。不幸的是,该算法的计算复杂度为 O(  2 ),因此也不适合大型数据集。

 

光谱聚类( Spectral clustering )

 

这个算法采用实例之间的相似性矩阵并从中创建一个低维嵌入(即,它降低了它的维数),然后它在这个低维空间中使用另一种聚类算法(Scikit-Learn 的实现使用 K-Means。)聚类可以捕获复杂的聚类结构,也可以用于切割图(例如,识别社交网络上的朋友聚类)。它不能很好地扩展到大量实例,并且当集群具有非常不同的大小时表现不佳。

 

现在让我们深入研究高斯混合模型,它可用于密度估计、聚类和异常检测。

 

高斯混合

 

高斯混合模型 ( GMM) 是一个概率模型,假设实例是从几个参数未知的高斯分布的混合中生成的。从单个高斯分布生成的所有实例形成一个通常看起来像椭圆体的集群。每个簇可以有不同的椭圆形状、大小、密度和方向, 如图 9-11 所示 。当你观察一个实例时,你知道它是从一个高斯分布中生成的,但你不知道是哪一个,你也不知道这些分布的参数是什幺。

 

那里是几个 GMM 变体。在 GaussianMixture 课堂上实现的最简单的变体中,您必须事先知道高斯分布的数量 k 。假设数据集 X 是通过以下概率过程生成的:

 

对于每个实例,从k 个集群中随机挑选一个集群。 选择第j 个集群的概率由集群的权重 φ j )定义。 7 为第i 个实例选择的集群索引记为 i )。

 

如果 i ) =  j ,表示第 i 个实例已分配到第 j 个簇,则此实例的位置 x  (  i )从均值为 μ  (  j )和协方差矩阵 Σ  (  j 的高斯分布中随机采样) . 注意到这一点훍X(一世)∼풩(米(j),小号(j)).

 

这个生成过程可以表示为图形模型。 图 9-16 表示随机变量之间的条件依赖的结构。

 

图 9-16。高斯混合模型的图形表示,包括其参数(正方形)、随机变量(圆圈)及其条件依赖关系(实线箭头)

 

以下是如何解释该图: 8

 

圆圈代表随机变量。

 

方块代表固定值(即模型的参数)。

 

大矩形称为 。他们表明他们的内容被重复了好几次。

 

每个板块右下角的数字表示其内容重复了多少次。因此,有 m 个随机变量 i )(从 (1)到 m ))和 m 个随机变量 x  (  i )。还有 k 个均值 μ  (  j )和 k 个协方差矩阵 Σ  (  j )。最后,只有一个权重向量 φ (包含所有权重 φ  (1)为 φ k ) )。

 

绘制每个变量 i )来自权重为 φ的 分类分布 。每个变量 x  (  i )均来自正态分布,均值和协方差矩阵由其簇 i )定义。

 

实线箭头表示条件依赖。例如,每个随机变量 i )的概率分布取决于权重向量 φ 。请注意,当箭头穿过板块边界时,意味着它适用于该板块的所有重复。例如,权重向量 φ 调节所有随机变量 x  (1)到 x  (  m )的概率分布。

 

从z i )到 x  (  i )的波浪形箭头表示一个开关:根据 i )的值,实例 x  (  i )将从不同的高斯分布中采样。例如,如果 i ) =  j ,那幺 훍X(一世)∼풩(米(j),小号(j)).

 

阴影节点表示该值是已知的。因此,在这种情况下,只有随机变量 x  (  i )具有已知值:它们称为 观察变量 。这未知的随机变量 i )称为 潜在变量

 

那幺,你能用这样的模型做什幺呢?好吧,给定数据集 X ,您通常希望从估计权重 φ 和所有分布参数 μ  (1)到 μ  (  k )和 Σ  (1)到 Σ  (  k )开始。Scikit-Learn 的 GaussianMixture 课程让这变得超级简单:

 

from sklearn.mixture import GaussianMixture
gm = GaussianMixture(n_components=3, n_init=10)
gm.fit(X)

 

让我们看一下算法估计的参数:

 

>>> gm.weights_
array([0.20965228, 0.4000662 , 0.39028152])
>>> gm.means_
array([[ 3.39909717,  1.05933727],
       [-1.40763984,  1.42710194],
       [ 0.05135313,  0.07524095]])
>>> gm.covariances_
array([[[ 1.14807234, -0.03270354],
        [-0.03270354,  0.95496237]],
       [[ 0.63478101,  0.72969804],
        [ 0.72969804,  1.1609872 ]],
       [[ 0.68809572,  0.79608475],
        [ 0.79608475,  1.21234145]]])

 

太好了,工作正常!实际上,用于生成数据的权重是 0.2、0.4 和 0.4;同样,均值和协方差矩阵与算法找到的非常接近。但是怎幺做?这个类依赖于 Expectation-Maximization (EM)算法,它与K-Means算法有很多相似之处:它也是随机初始化簇参数,然后重复两步直到收敛,首先将实例分配给集群(这称为 期望步骤 ),然后更新集群(这称为 最大化步骤 )。听起来很熟悉,对吧?在聚类的上下文中,您可以将 EM 视为 K-Means 的推广,它不仅可以找到聚类中心 (  μ  (1)到 μ  (  k ) ),而且还可以找到它们的大小、形状和方向 (  Σ  (1 ) )到 Σ  (  k ) ),以及它们的相对权重 (  ϕ  (1)到 ϕ k ))。然而,与 K-Means 不同的是,EM 使用软聚类分配,而不是硬分配。对于每个实例,在期望步骤中,算法估计它属于每个集群的概率(基于当前集群参数)。然后,在最大化步骤中,使用数据集中的 所有 实例更新每个集群,每个实例由它属于该集群的估计概率加权。这些概率称为集群对实例的 责任。 在最大化步骤中,每个集群的更新将主要受到它最负责的实例的影响。

 

警告

 

不幸的是,就像 K-Means 一样,EM 最终会收敛到较差的解决方案,因此需要运行多次,只保留最佳解决方案。这就是我们设置 n_init10 . 注意:默认 n_init 设置为 1 .

 

您可以检查算法是否收敛以及它进行了多少次迭代:

 

>>> gm.converged_
True
>>> gm.n_iter_
3

 

现在您已经估计了每个集群的位置、大小、形状、方向和相对权重,模型可以轻松地将每个实例分配给最可能的集群(硬集群)或估计它属于特定集群的概率(软聚类)。只需使用 predict() 硬聚类的 predict_proba() 方法,或软聚类的方法:

 

>>> gm.predict(X)
array([2, 2, 1, ..., 0, 0, 0])
>>> gm.predict_proba(X)
array([[2.32389467e-02, 6.77397850e-07, 9.76760376e-01],
       [1.64685609e-02, 6.75361303e-04, 9.82856078e-01],
       [2.01535333e-06, 9.99923053e-01, 7.49319577e-05],
       ...,
       [9.99999571e-01, 2.13946075e-26, 4.28788333e-07],
       [1.00000000e+00, 1.46454409e-41, 5.12459171e-16],
       [1.00000000e+00, 8.02006365e-41, 2.27626238e-15]])

 

一个高斯混合模型是一个 生成模型 ,这意味着您可以从中采样新实例(注意它们是按集群索引排序的):

 

>>> X_new, y_new = gm.sample(6)
>>> X_new
array([[ 2.95400315,  2.63680992],
       [-1.16654575,  1.62792705],
       [-1.39477712, -1.48511338],
       [ 0.27221525,  0.690366  ],
       [ 0.54095936,  0.48591934],
       [ 0.38064009, -0.56240465]])
>>> y_new
array([0, 1, 2, 2, 2, 2])

 

也可以估计任何给定位置的模型密度。这是使用 score_samples() 方法实现的:对于给定的每个实例,此方法估计该位置的 概率密度函数 (PDF) 的对数。分数越大,密度越高:

 

>>> gm.score_samples(X)
array([-2.60782346, -3.57106041, -3.33003479, ..., -3.51352783,
       -4.39802535, -3.80743859])

 

如果您计算这些分数的指数,您将获得给定实例所在位置的 PDF 值。这些不是概率,而是概率 密度 :它们可以取任何正值,而不仅仅是 0 到 1 之间的值。要估计实例落在特定区域内的概率,您必须在该区域上集成 PDF(如果您在可能的实例位置的整个空间,结果将是 1)。

 

图 9-17 显示了该模型的聚类均值、决策边界(虚线)和密度等值线。

 

图 9-17。训练的高斯混合模型的聚类均值、决策边界和密度等值线

 

好的!该算法显然找到了一个很好的解决方案。当然,我们通过使用一组 2D 高斯分布(不幸的是,现实生活中的数据并不总是如此高斯和低维)生成数据,从而简化了任务。我们还为算法提供了正确的簇数。当有许多维度、许多集群或少数实例时,EM 可能难以收敛到最优解。您可能需要通过限制算法必须学习的参数数量来降低任务的难度。一种方法是限制集群可以具有的形状和方向的范围。这可以通过对协方差矩阵施加约束来实现。为此,请 covariance_type 将超参数设置为以下值之一:

 

"spherical"

 

所有簇必须是球形的,但它们可以具有不同的直径(即不同的方差)。

 

"diag"

 

簇可以呈现任何大小的任何椭圆体形状,但椭圆体的轴必须平行于坐标轴(即协方差矩阵必须是对角线)。

 

"tied"

 

所有集群必须具有相同的椭圆体形状、大小和方向(即,所有集群共享相同的协方差矩阵)。

 

默认情况下, covariance_type 等于 "full" ,这意味着每个集群可以采用任何形状、大小和方向(它有自己的无约束协方差矩阵)。 图 9-18 绘制了 EM 算法在 covariance_type 设置为 "tied" 或 “时找到的解 spherical 。”

 

图 9-18。捆绑星团(左)和球形星团(右)的高斯混合

 

笔记

 

训练模型的计算复杂度 GaussianMixture 取决于实例数量 m 、维度数量 n 、聚类数量 k 以及协方差矩阵的约束。如果 covariance_type"spherical"diag" ,则为 Okmn ),假设数据具有聚类结构。如果 covariance_type"tied""full" ,则为 Okmn  2 +  kn  3 ),因此它不会扩展到大量特征。

 

高斯混合模型也可用于异常检测。让我们看看如何。

 

使用高斯混合进行异常检测

 

异常检测 (也称为 异常检测 )是检测严重偏离规范的实例的任务。这些实例称为 异常异常值 ,而正常实例称为内部 。异常检测在各种应用中都很有用,例如欺诈检测、检测制造中的缺陷产品或在训练另一个模型之前从数据集中删除异常值(这可以显着提高结果模型的性能)。

 

使用高斯混合模型进行异常检测非常简单:位于低密度区域的任何实例都可以被视为异常。您必须定义要使用的密度阈值。例如,在试图检测缺陷产品的制造公司中,缺陷产品的比率通常是众所周知的。假设它等于 4%。然后,您将密度阈值设置为使 4% 的实例位于低于该阈值密度的区域的值。如果您注意到您得到太多误报(即,被标记为有缺陷的完美产品),您可以降低阈值。相反,如果您有太多的漏报(即系统未标记为有缺陷的缺陷产品),您可以提高阈值。这是通常的精度/召回率权衡(参见 第 3 章 )。以下是使用第四个百分位最低密度作为阈值来识别异常值的方法(即,大约 4% 的实例将被标记为异常):

 

densities = gm.score_samples(X)
density_threshold = np.percentile(densities, 4)
anomalies = X[densities < density_threshold]

 

图 9-19 将这些异常表示为星星。

 

图 9-19。使用高斯混合模型进行异常检测

 

一个密切相关的任务是 新奇检测 :它与异常检测的不同之处在于,假设算法在“干净​​”数据集上进行训练,不受异常值的污染,而异常检测不做这个假设。实际上,异常值检测通常用于清理数据集。

 

小费

 

高斯混合模型试图拟合所有数据,包括异常值,所以如果你有太多的数据,这会使模型对“正常”的看法产生偏差,一些异常值可能会被错误地认为是正常的。如果发生这种情况,您可以尝试一次拟合模型,使用它来检测并移除最极端的异常值,然后在清理后的数据集上再次拟合模型。另一种方法是使用稳健的协方差估计方法(参见 EllipticEnvelope 课程)。

 

就像 K-Means 一样,该 GaussianMixture 算法要求您指定集群的数量。那幺,你怎幺能找到它呢?

 

选择簇数

 

和K-Means,您可以使用惯性或轮廓分数来选择适当数量的集群。但是对于高斯混合,不可能使用这些度量,因为当集群不是球形或具有不同大小时,它们是不可靠的。相反,您可以尝试找到最小化 理论信息准则 的模型,例如 公式 9-1中定义的 贝叶斯信息准则 (BIC) 或 Akaike 信息准则 (AIC) 。

 

公式 9-1。贝叶斯信息准则(BIC)和赤池信息准则(AIC)

 

在这些等式中:

 

m 是实例的数量,一如既往。

 

p 是模型学习的参数数量。

 

L^是个 模型的似然函数 的最大值。

 

BIC 和 AIC 都惩罚具有更多要学习的参数(例如,更多集群)的模型,并奖励能够很好地拟合数据的模型。他们通常最终选择相同的模型。当它们不同时,BIC 选择的模型往往比 AIC 选择的模型更简单(参数更少),但往往不能很好地拟合数据(对于较大的数据集尤其如此)。

 

似然函数

 

“概率”和“可能性”这两个术语在英语中经常互换使用,但它们在统计学中的含义却截然不同。给定一个带有一些参数 θ 的统计模型,“概率”一词用于描述未来结果 x 的可信度(知道参数值 θ ),而“可能性”一词用于描述一组特定参数的可信度值 θ 是,在结果 x 已知之后。

 

考虑以 –4 和 +1 为中心的两个高斯分布的一维混合模型。为简单起见,这个玩具模型有一个参数 θ 来控制两个分布的标准差。 图 9-20 中左上角的等高线图显示了整个模型 f  (  x  ;  θ  ) 作为 xθ 的函数。要估计未来结果 x 的概率分布,您需要设置模型参数 θ 。例如,如果将 θ 设置为 1.3(水平线),则得到概率密度函数 f  (  x  ;  θ =1.3) 显示在左下方的图中。假设您要估计 x 介于 –2 和 +2 之间的概率。您必须计算 PDF 在此范围(即阴影区域的表面)上的积分。但是,如果您不知道 θ ,而是观察到单个实例 x  =2.5(左上图中的垂直线)怎幺办?在这种情况下,您会得到似然函数 ℒ(  θ  |  x  =2.5)=f(  x  =2.5;  θ  ),如右上图所示。

 

图 9-20。模型的参数函数(左上)和一些派生函数:PDF(左下)、似然函数(右上)和对数似然函数(右下)

 

简而言之,PDF 是 x 的函数( θ 固定),而似然函数是 θ 的函数( x 固定)。重要的是要理解似然函数 不是概率分布:如果你对 x 的所有可能值进行概率分布积分,你总是得到 1; 但是,如果您对θ 的所有可能值进行似然函数积分,则结果可以是任何正值。

 

给定一个数据集 X ,一个常见的任务是尝试估计模型参数的最可能值。为此,您必须在给定 X 的情况下找到使似然函数最大化的值。在这个例子中,如果您观察到单个实例 x =2.5, θ的 最大似然估计 (MLE)为훉一世^=1.5。如果存在关于 θ 的先验概率分布 g ,则可以通过最大化 ℒ(  θx )g(  θ ) 而不仅仅是最大化 ℒ(  θx ) 来考虑它。这个称为 最大后验 (MAP)估计。由于 MAP 约束参数值,您可以将其视为 MLE 的正则化版本。

 

请注意,最大化似然函数等同于最大化其对数(在图 9-20 的右下方图中表示)。事实上,对数是严格递增的函数,所以如果 θ 最大化对数似然,它也最大化似然。事实证明,最大化对数似然通常更容易。例如,如果您观察到多个独立实例 (1)到 m ),则需要找到 θ的值 最大化个体似然函数的乘积。但是,最大化对数似然函数的和(而不是乘积)是等效的,并且更简单,这要归功于将乘积转换为和的对数的魔力:log(  ab )=log(  a )+log(  b )。

 

一旦你估计훉一世^,使似然函数最大化的 θ值,那幺您就可以计算 훉大号^=大号(一世^,X),这是用于计算 AIC 和 BIC 的值;您可以将其视为模型与数据拟合程度的衡量标准。

 

要计算 BIC 和 AIC,请调用 bic()aic() 方法:

 

>>> gm.bic(X)
8189.74345832983
>>> gm.aic(X)
8102.518178214792

 

图 9-21 显示了不同数量的集群 k 的 BIC 。如您所见,当 k =3 时,BIC 和 AIC 都最低,因此它很可能是最佳选择。请注意,我们还可以搜索 covariance_type 超参数的最佳值。例如,如果它是 "spherical" 而不是 "full" ,那幺模型要学习的参数要少得多,但它也不适合数据。

 

 

图 9-21。不同数量的集群 k的 AIC 和 BIC

 

贝叶斯高斯混合模型

 

相当与手动搜索最佳集群数相比,您可以使用 BayesianGaussianMixture 该类,该类能够将权重等于(或接近)零分配给不必要的集群。将聚类数设置为 n_components 您有充分理由相信大于最佳聚类数的值(这假设对手头的问题有一些最少的了解),算法将自动消除不必要的聚类。例如,让我们将集群的数量设置为 10,看看会发生什幺:

 

>>> from sklearn.mixture import BayesianGaussianMixture
>>> bgm = BayesianGaussianMixture(n_components=10, n_init=10)
>>> bgm.fit(X)
>>> np.round(bgm.weights_, 2)
array([0.4 , 0.21, 0.4 , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  ])

 

完美:算法自动检测到只需要三个聚类,生成的聚类与 图 9-17 中的聚类几乎相同。

 

在这个模型中,集群参数(包括权重、均值和协方差矩阵)不再被视为固定的模型参数,而是作为潜在随机变量,就像集群分配一样( 见图 9-22 )。所以 z 现在包括集群参数和集群分配。

 

Beta 分布通常用于对值在固定范围内的随机变量进行建模。在这种情况下,范围是从 0 到 1。最好通过一个例子来解释 Stick-Breaking Process (SBP):假设 Φ=[0.3, 0.6, 0.5,…],那幺 30% 的实例将分配给集群 0,然后 60% 的剩余实例将分配给集群 1,然后 50% 的剩余实例将分配给集群 2,依此类推。这个过程是一个很好的数据集模型,其中新实例比小集群更有可能加入大集群(例如,人们更有可能搬到大城市)。如果浓度 α 很高,那幺 Φ 值可能会接近 0,并且 SBP 会生成许多簇。相反,如果浓度较低,则 Φ 值可能会接近 1,并且集群将很少。最后,

 

图 9-22。贝叶斯高斯混合模型

 

事先的关于潜在变量 z 的知识可以编码在称为 先验的概率分布 p  (  z  )中。例如,我们可能有一个先验信念,即集群可能很少(低浓度),或者相反,它们可能很丰富(高浓度)。这种关于集群数量的先验信念可以使用超参数进行调整。将其设置为 0.01 或 10,000 会产生非常不同的聚类( 见图 9-23 )。然而,我们拥有的数据越多,先验的重要性就越小。事实上,要绘制具有如此大差异的图表,您必须使用非常强大的先验和很少的数据。 weight_concentration_prior

 

图 9-23。对相同数据使用不同的浓度先验会导致不同数量的聚类

 

贝叶斯定理( 方程 9-2 )告诉我们如何在观察到一些数据 X 后更新潜在变量的概率分布。它计算 后验 分布 p (  z |  X ),它是给定 X的 z 的条件概率。

 

公式 9-2。贝叶斯定理

 

不幸的是,在高斯混合模型(和许多其他问题)中,分母 p (  x ) 是难以处理的,因为它需要对 z 的所有可能值进行积分( 方程 9-3 ),这需要考虑所有可能的集群组合参数和集群分配。

 

公式 9-3。证据 p (  X ) 通常难以处理

 

这个难处理性是贝叶斯统计的核心问题之一,有几种方法可以解决它。其中之一是 变分推理 ,它选择具有自己的 变分参数 λ (lambda) 的分布族 q (  z ;  λ ),然后优化这些参数以使 q (  z ) 成为 p (  z |  X )的良好近似。这是通过找到最小化从 q (  z ) 到 p (  z |  X 的 KL 散度的 λ值来实现的 ),记为 D KL (  q ‖  p )。KL 散度方程如 方程 9-4 所示,它可以重写为证据的对数 (log  p (  X )) 减去 证据下限 (ELBO)。由于证据的对数不依赖于 q ,它是一个常数项,因此最小化 KL 散度只需要最大化 ELBO。

 

公式 9-4。 从q (  z ) 到 p (  z |  X )的KL 散度

 

在实践中,有不同的技巧可以最大化 ELBO。在 平均场变分推断 中,有必要非常仔细地选择分布族 q (  z ;  λ ) 和先验 pz ),以确保 ELBO 的方程简化为可以计算的形式。不幸的是,没有通用的方法来做到这一点。选择正确的分布族和正确的先验取决于任务并且需要一些数学技能。 BayesianGaussianMixture 例如,Scikit-Learn课程中使用的分布和下界方程在 文档中都有介绍 . 从这些方程可以推导出集群参数和分配变量的更新方程:这些方程的使用非常类似于期望最大化算法。实际上,该类的计算复杂度与 BayesianGaussianMixture 该类的计算复杂度相似 GaussianMixture (但通常要慢得多)。一种更简单的方法来最大化ELBO 被称为 黑盒随机变分推断 (BBSVI):在每次迭代中,从 q 中抽取几个样本,它们用于估计 ELBO 关于变分参数 λ 的梯度,然后将其用于梯度上升步骤。这种方法可以将贝叶斯推理用于任何类型的模型(只要它是可微的),甚至是深度神经网络;使用深度神经网络进行贝叶斯推理称为贝叶斯深度学习。

 

小费

 

如果您想深入了解贝叶斯统计,请查看Andrew Gelman 等人的《 贝叶斯数据分析》一书。 (查普曼和霍尔)。

 

高斯混合模型在椭圆形的簇上效果很好,但如果你尝试拟合不同形状的数据集,你可能会遇到不好的意外。例如,让我们看看如果我们使用贝叶斯高斯混合模型来聚类卫星数据集会发生什幺( 见图 9-24 )。

 

图 9-24。将高斯混合拟合到非椭圆体簇

 

哎呀!该算法拼命搜索椭球,因此它找到了八个不同的集群而不是两个。密度估计还不错,所以这个模型或许可以用于异常检测,但它未能识别出两颗卫星。现在让我们看一些能够处理任意形状的聚类的聚类算法。

 

其他异常和新奇检测算法

 

Scikit-Learn 工具其他专用于异常检测或新奇检测的算法:

 

PCA(和其他降维技术的 inverse_transform() 方法)

 

如果您将正常实例的重建误差与异常的重建误差进行比较,后者通常会大得多。这是一种简单且通常非常有效的异常检测方法(有关该方法的应用,请参见本章的练习)。

 

Fast-MCD(最小协方差行列式)

 

实施的就 EllipticEnvelope 类而言,该算法对于异常值检测很有用,特别是对于清理数据集。它假设正常实例(内点)是从单个高斯分布(不是混合)生成的。它还假设数据集被非高斯分布产生的异常值污染。当算法估计高斯分布的参数(即,内点周围的椭圆包络的形状)时,它会小心地忽略最可能是异常值的实例。这种技术可以更好地估计椭圆包络,从而使算法更好地识别异常值。

 

隔离森林

 

这个是一种有效的异常值检测算法,尤其是在高维数据集中。该算法构建了一个随机森林,其中每个决策树都是随机生长的:在每个节点上,它随机选择一个特征,然后选择一个随机阈值(在最小值和最大值之间)将数据集一分为二。数据集以这种方式逐渐被分割成碎片,直到所有实例最终与其他实例隔离。异常通常与其他实例相距甚远,因此平均而言(在所有决策树中)它们往往比正常实例以更少的步骤被隔离。

 

局部异常因子 (LOF)

 

这个算法也适用于异常值检测。它将给定实例周围的实例密度与其邻居周围的密度进行比较。一个异常通常比它的 k 个最近邻更孤立。

 

一类SVM

 

这个算法更适合新颖性检测。回想一下,核化 SVM 分类器通过首先(隐式)将所有实例映射到高维空间来分离两个类,然后在这个高维空间内使用线性 SVM 分类器分离这两个类(参见 第 5 章) )。由于我们只有一类实例,因此一类 SVM 算法尝试将高维空间中的实例与原点分开。在原始空间中,这将对应于找到一个包含所有实例的小区域。如果新实例不在此区域内,则为异常。有一些超参数需要调整:核化 SVM 的常用超参数,加上一个边距超参数,该超参数对应于一个新实例被错误地认为是新实例的概率,而实际上它是正常的。它工作得很好,尤其是对于高维数据集,但与所有 SVM 一样,它不能扩展到大型数据集。

 

练习

 

 

您如何定义聚类?你能说出一些聚类算法吗?

 

聚类算法的一些主要应用是什幺?

 

描述在使用K-Means时选择正确数量的集群的两种技术。

 

什幺是标签传播?你为什幺要实施它,以及如何实施?

 

你能说出两种可以扩展到大型数据集的聚类算法吗?还有两个寻找高密度区域?

 

你能想出一个主动学习有用的用例吗?你将如何实施它?

 

异常检测和新奇检测有什幺区别?

 

什幺是高斯混合?您可以将它用于哪些任务?

 

当使用高斯混合模型时,你能说出两种技术来找到正确数量的集群吗?

 

经典的 Olivetti 人脸数据集包含 400 张灰度 64 × 64 像素的人脸图像。每个图像都被展平为大小为 4,096 的一维向量。拍摄了 40 个不同的人(每个人 10 次),通常的任务是训练一个模型,该模型可以预测每张照片中代表的人。使用函数加载数据集 sklearn.datasets.fetch_olivetti_faces() ,然后将其拆分为训练集、验证集和测试集(请注意,数据集已经在 0 和 1 之间缩放)。由于数据集非常小,您可能希望使用分层抽样来确保每组中每个人的图像数量相同。接下来,使用 K-Means 对图像进行聚类,并确保您拥有大量的聚类(使用本章讨论的技术之一)。可视化集群:您在每个集群中看到相似的面孔吗?

 

继续使用 Olivetti 人脸数据集,训练分类器来预测每张图片中代表的人,并在验证集上对其进行评估。接下来,使用 K-Means 作为降维工具,并在降维集上训练分类器。搜索允许分类器获得最佳性能的集群数量:你能达到什幺性能?如果您将缩减集的特征附加到原始特征中(同样,搜索最佳聚类数)怎幺办?

 

在 Olivetti 人脸数据集上训练高斯混合模型。为了加快算法速度,您可能应该减少数据集的维度(例如,使用 PCA,保留 99% 的方差)。使用模型生成一些新面孔(使用 sample() 方法),并将它们可视化(如果使用 PCA,则需要使用它的 inverse_transform() 方法)。尝试修改一些图像(例如,旋转、翻转、变暗)并查看模型是否可以检测到异常(即,比较 score_samples() 正常图像和异常的方法的输出)。

 

一些降维技术也可以用于异常检测。例如,采用 Olivetti 人脸数据集并使用 PCA 对其进行缩减,保留 99% 的方差。然后计算每个图像的重建误差。接下来,取一些您在上一个练习中构建的修改后的图像,并查看它们的重建误差:注意重建误差有多大。如果你绘制一张重建的图像,你会明白为什幺:它试图重建一张正常的脸。

 

 

附录 A 中提供了这些练习的解决方案。

 

1 Stuart P. Lloyd,“PCM 中的最小二乘量化”, IEEE Transactions on Information Theory 28,不。2 (1982): 129–137。

 

2 这是因为实例与其最近的质心之间的均方距离只能在每一步下降。

 

3 David Arthur 和 Sergei Vassilvitskii,“k-Means ++:小心播种的优势”  ,第 18 届 ACM-SIAM 离散算法年度研讨会论文集 (2007 年):1027–1035。

 

4 Charles Elkan,“使用三角不等式加速 k-Means”  ,第 20 届机器学习国际会议论文集 (2003 年):147-153。

 

5 三角不等式为 AC ≤ AB + BC,其中 A、B 和 C 是三个点,AB、AC 和 BC 是这些点之间的距离。

 

6 David Sculley,“Web-Scale K-Means Clustering”  ,第 19 届万维网国际会议论文集 (2010 年):1177-1178。

 

7 Phi (  φφ ) 是希腊字母表的第 21 个字母。

 

8 这些符号中的大多数是标准的,但一些额外的符号取自维基百科关于 车牌符号 的文章。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注