在前面的章节中,我们已经看到了监督机器学习,其中我们知道目标变量或标签,并且我们尝试基于输入特征预测输出。无监督学习在某种意义上是不同的,即没有标记数据,我们也不会尝试预测任何输出;相反,我们试图找到有趣的模式,并在数据中提出组。相似的值组合在一起。 当我们加入一所新的学校或学院时,我们遇到了许多新面孔,每个人都看起来如此不同。我们几乎不认识研究所的任何人,最初没有任何小组。慢慢地,我们开始与其他人共度时光,团队开始发展。我们与很多不同的人互动,并弄清楚他们与我们有多么相似和不相似。几个月后,我们几乎在我们自己的朋友群中定居下来。组内的朋友/成员具有相似的属性/喜好/品味,因此保持在一起。聚类有点类似于这种基于定义组的属性组形成组的方法。
我们可以对任何类型的数据应用聚类,我们希望在这些数据中形成类似观察组并将其用于更好的决策。 在早期,客户细分过去是通过基于规则的方法完成的,这种方法大部分是手动操作,只能使用有限数量的变量。 例如,如果企业想要进行客户细分,他们会考虑最多10个变量,如年龄,性别,薪水,位置等,并创建仍然提供合理绩效的基于规则的细分市场; 但在今天的情况下会变得非常无效。 一个原因是数据可用性很丰富,另一个原因是动态的客户行为。 有数以千计的其他变量可以被认为是这些机器学习驱动的细分市场,它们更加丰富和有意义。
当我们开始聚类时,每个观察都是不同的,并且不属于任何组,而是基于每个观察的属性有多相似。 我们将它们分组,使得每个组包含最相似的记录,并且任何两个组之间存在尽可能多的差异。 那么,我们如何衡量两个观察结果是相似还是不同?
有多种方法可以计算任何两个观测值之间的距离。 我们主要表示任何观察都是一种矢量形式,其中包含该观测值(A)的值,如下所示。
Age | Salary | Weight | Height |
---|---|---|---|
32 | 8 | 65 | 6 |
现在,假设我们想要计算此观察/记录与任何其他观察(B)的距离,其中也包含如下所示的类似属性。
Age | Salary | Weight | Height |
---|---|---|---|
40 | 15 | 90 | 5 |
我们可以使用Euclidean方法
测量距离,这很简单。
它也被称为笛卡尔距离
。 我们试图计算任意两点之间的直线距离; 如果它们之间的距离很小,它们更可能相似,而如果距离很大,它们彼此不相似,如图8-1所示。
可以使用以下公式计算任意两点之间的欧几里德距离:
# 伪代码
dist = math.sqrt(sum(math.pow(diff1,2),
math.pow(diff2,2),
math.pow(diff3,2),
math.pow(diff4,2)))
因此,观测A和B之间的欧几里德距离是27.18。 计算观测距离的其他技术如下:
- 曼哈顿距离
- 马哈拉诺比斯距离
- Minkowski距离
- 切比雪夫距离
- 余弦距离
聚类的目的是使簇内距离最小化,并且最大聚簇间差异。 我们最终可以使用我们用于进行聚类的距离方法的不同组,因此确保选择与业务问题一致的正确距离度量是至关重要的。 在进入不同的集群技术之前,让我们快速浏览一些集群应用程序。
如今,群集用于各种用例,从客户细分到异常检测。 企业广泛使用机器学习驱动的集群来分析客户和细分,以围绕这些结果创建市场策略。 群集通过在一个群集中查找相似的对象以及彼此远离的不同对象来驱动大量搜索引擎的结果。 它根据搜索查询推荐最接近的类似结果。
聚类可以基于数据类型和业务需求以多种方式完成群集。 最常用的是K-means
和Hierarchical clustering(层次聚类)
。
'K'
代表我们想要在给定数据集中形成的许多聚类或组。 这种类型的聚类涉及预先确定簇的数量。 在研究K-means
聚类的工作原理之前,让我们首先熟悉几个术语。
- 质心
- 差异
质心是指集群或组中心的中心数据点。 它也是群集中最具代表性的点,因为它是群集中其他点的最等距数据点。 三个随机簇的质心(用十字表示)如图8-2所示。
每个群集或组包含最接近群集质心的不同数量的数据点。 一旦各个数据点更改了群集,群集的质心值也会发生变化。 组内的中心位置会发生变化,从而产生一个新的质心,如图8-3所示。
聚类的整个想法是最小化集群内距离,即数据点与集群质心的内部距离,并最大化集群间距离,即两个不同集群的质心之间。
方差是该群集中质心和数据点之间的群内距离的总和,如图8-4所示。 随着集群数量的增加,方差继续减小。 群集越多,每个群集内的数据点数量越少,因此可变性越小。
K均值聚类由总共四个步骤组成,以形成数据集中的内部组。 我们将考虑一个样本数据集来理解K-means聚类算法的工作原理。 数据集包含一些用户的年龄和体重值,如表8-1所示。 现在我们将使用K-means聚类来提出有意义的聚类并理解算法。
它首先决定簇的数量(K的值)。 大多数情况下,我们一开始并不确定组的数量是否合适,但我们可以使用基于可变性的称为Elbow方法的方法找到最佳簇数。 对于这个例子,让我们从K = 2开始,以保持简单。 因此,我们在此示例数据中寻找两个集群。
下一步是随机地将任意两个点视为新集群的质心。 这些可以随机选择,因此我们选择用户编号5和用户编号10作为新集群上的两个质心,如表8-2所示。
在这一步中,我们计算每个点与质心的距离。 在这个例子中,我们从两个质心点计算每个用户的欧几里德平方距离。 基于距离值,我们继续并决定用户属于哪个特定群集(1或2)。 用户所在的质心(较小的距离)将成为该群集的一部分。 计算表8-3中显示的每个用户的欧几里德平方距离。 用户5和用户10的距离从各个质心为零,因为它们是与质心相同的点。
Table 8-3. Cluster Assignment Based on Distance from Centroids
User ID | Age | Weight | ED* from Centroid 1 | ED* from Centroid 2 | Cluster |
---|---|---|---|---|---|
1 | 18 | 80 | 48 | 78 | 1 |
2 | 40 | 60 | 60 | 51 | 2 |
3 | 35 | 100 | 22 | 74 | 1 |
4 | 20 | 45 | 79 | 70 | 2 |
5 | 45 | 120 | 0 | 83 | 1 |
6 | 32 | 65 | 57 | 60 | 1 |
7 | 17 | 50 | 75 | 73 | 2 |
8 | 55 | 55 | 66 | 35 | 2 |
9 | 60 | 90 | 34 | 50 | 1 |
10 | 90 | 50 | 83 | 0 | 2 |
因此,根据与质心的距离,我们已将每个用户分配到群集1或群集2.群集1包含五个用户,群集2也包含五个用户。 初始集群如图8-7所示。
如前所述,在群集中包含或排除新数据点后,群集的质心必然会发生变化。 由于较早的质心(C1,C2)不再位于聚类的中心,我们将在下一步计算新的质心。
K-means聚类的最后一步是计算聚类的新质心,并根据与新质心的距离将聚类重新分配给每个值。 让我们计算集群1和集群2的新质心。为了计算集群1的质心,我们只取这些属于集群1的值的年龄和权重的平均值,如表8-4所示。
Table 8-4. New Centroid Calculation of Cluster 1
User ID | Age | Weight |
---|---|---|
1 | 18 | 80 |
3 | 35 | 100 |
5 | 45 | 120 |
6 | 32 | 65 |
9 | 60 | 90 |
Mean Value | 38 | 91 |
群集2的质心计算也以类似的方式完成,如表8-5所示。
Table 8-5. New Centroid calculation of Cluster 2
User ID | Age | Weight |
---|---|---|
2 | 40 | 60 |
4 | 20 | 45 |
7 | 17 | 50 |
8 | 55 | 55 |
10 | 90 | 50 |
Mean Value | 44.4 | 52 |
现在我们为每个由十字形表示的簇提供了新的质心值,如图8-8所示。 箭头表示集群内质心的移动。
对于每个聚类的质心,我们重复步骤3,计算每个用户从新质心的欧几里德平方距离,并找出最近的质心。 然后,我们根据与质心的距离将用户重新分配给Cluster 1或Cluster 2。 在这种情况下,只有一个值(用户6)将其簇从1更改为2,如表8-6所示。
Table 8-6. Reallcoation of Clusters
User ID | Age | Weight | ED* from Centroid 1 | ED* from Centroid 2 | Cluster |
---|---|---|---|---|---|
1 | 18 | 80 | 23 | 38 | 1 |
2 | 40 | 60 | 31 | 9 | 2 |
3 | 35 | 100 | 9 | 49 | 1 |
4 | 20 | 45 | 49 | 25 | 2 |
5 | 45 | 120 | 30 | 68 | 1 |
6 | 32 | 65 | 27 | 18 | 2 |
7 | 17 | 50 | 46 | 27 | 2 |
8 | 55 | 55 | 40 | 11 | 2 |
9 | 60 | 90 | 22 | 41 | 1 |
10 | 90 | 50 | 66 | 46 | 2 |
现在,群集1只剩下四个用户,群集2包含六个用户,基于每个群集质心的距离,如图8-9所示。
我们不断重复上述步骤,直到群集分配不再发生变化为止。 新簇的质心如表8-7所示。
Table 8-7. Calculation of Centroids
User ID | Age | Weight |
---|---|---|
1 | 18 | 80 |
3 | 35 | 100 |
5 | 45 | 120 |
9 | 60 | 90 |
Mean Value | 39.5 | 97.5 |
User ID | Age | Weight |
2 | 40 | 60 |
4 | 20 | 45 |
6 | 32 | 65 |
7 | 17 | 50 |
8 | 55 | 55 |
10 | 90 | 50 |
Mean Value | 42.33 | 54.17 |
当我们完成这些步骤时,质心运动变得越来越小,这些值几乎成为该特定集群的一部分,如图8-10所示。
正如我们所观察到的,即使在质心发生变化之后,点也没有更多的变化,这完成了K均值聚类。 结果可以根据第一组随机质心而变化。 为了重现结果,我们也可以自己设定起点。 具有值的最终聚类如图8-11所示。
群集1包含高度属性上的平均值但在权重变量上看起来非常高的用户,而群集2似乎将那些高于平均值的用户组合在一起,但非常注意它们的重量,如图8-12所示。
由于我们需要深入了解数据集和业务问题的上下文,因此在大多数情况下选择最佳数量的集群非常棘手。另外,在无监督学习方面没有正确或错误的答案。与另一种方法相比,一种方法可能导致不同数量的集群。我们必须尝试找出哪种方法效果最好,以及创建的集群是否足以用于决策。每个群集都可以用一些重要的属性来表示,这些属性表示或提供有关该特定群集的信息。但是,有一种方法可以使用数据集选择尽可能多的聚类。这种方法称为肘部法则。
肘部法则有助于我们使用多个聚类来测量数据中的总方差。群集数量越多,方差就越小。如果我们对数据集中的记录数具有相同数量的聚类,则可变性将为零,因为每个点与其自身的距离为零。可变性或SSE(平方误差之和)以及'K'值如图8-13所示。
正如我们所观察到的,K值为3和4之间存在一种肘部形成。总方差突然减少(群内差异),之后方差类型下降非常缓慢。 事实上,它在K = 9值之后变平。 因此,如果我们采用肘法,K = 3的值最有意义,因为它可以捕获具有较少数量聚类的最大可变性。
这是另一种无监督机器学习技术,与K-means的不同之处在于我们不必事先知道簇的数量。 有两种类型的分层聚类。
- 凝聚聚类(自下而上的方法)
- 分裂聚类(自上而下方法)
我们将讨论凝聚聚类,因为它是主要类型。 这首先假设每个数据点是一个单独的集群,并逐渐将最近的值组合到相同的集群中,直到所有值都成为一个集群的一部分。 这是一种自下而上的方法,用于计算每个群集之间的距离,并将两个最接近的群集合并为一个群集。 让我们在可视化的帮助下理解凝聚聚类。 假设我们最初有七个数据点(A1-A7),它们需要使用凝聚聚类分组成包含相似值的聚类,如图8-14所示。
在初始阶段(步骤1),将每个点视为单个群集。 在下一步中,计算每个点之间的距离,并将最近的点组合在一起成为一个簇。在这个例子中,A1和A2,A5和A6彼此最接近,因此形成一个簇,如图8-15所示。
在使用分层聚类时确定最佳数量的聚类可以通过多种方式完成。 一种方法是使用弯头方法本身,另一种方法是使用称为树状图的方法。 它用于可视化聚类之间的可变性(欧几里德距离)。 在树形图中,垂直线的高度表示点或簇之间的距离以及沿底部列出的数据点。 每个点绘制在X轴上,距离表示在Y轴(长度)上。 它是数据点的分层表示。 在此示例中,步骤2中的树形图类似于图8-16中所示的树形图。
在步骤3中,重复计算簇之间距离的练习,并将最近的簇组合成单个簇。 此时A3与(A1,A2)合并,A4与(A5,A6)合并,如图8-17所示。
在步骤4中,计算唯一剩余点A7之间的距离,并且发现它更靠近Cluster(A4,A5,A6)。 它与同一个集群合并,如图8-19所示。
在最后阶段(步骤5),所有点组合成一个簇(A1,A2,A3,A4,A5,A6,A7),如图8-20所示。
有时很难通过树形图识别正确数量的聚类,因为它可能变得非常复杂并且难以解释,这取决于用于聚类的数据集。 与K-means相比,分层聚类在大型数据集上不能很好地工作。
聚类对数据点的规模也非常敏感,因此建议在聚类之前进行数据扩展。 还有其他类型的群集可用于将类似的数据点组合在一起,如下所示:
- 高斯混合模型聚类
- 模糊C均值聚类
但上述方法超出了本书的范围。 我们现在开始使用数据集在PySpark
中使用K-means
构建集群。
创建SparkSession
[In]: from pyspark.sql import SparkSession
[In]: spark=SparkSession.builder.appName('K_means').getOrCreate()
[In]:df=spark.read.csv('iris_dataset.csv',
inferSchema=True,header=True)
探索性数据分析
[In]:print((df.count(), len(df.columns)))
[Out]: (150,3)
[In]: df.printSchema()
[Out]: root
|-- sepal_length: double (nullable = true)
|-- sepal_width: double (nullable = true)
|-- petal_length: double (nullable = true)
|-- petal_width: double (nullable = true)
|-- species: string (nullable = true)
[In]: from pyspark.sql.functions import rand
[In]: df.orderBy(rand()).show(10,False)
[Out]:
+------------+-----------+------------+-----------+----------+
|sepal_length|sepal_width|petal_length|petal_width|species |
+------------+-----------+------------+-----------+----------+
|5.5 |2.6 |4.4 |1.2 |versicolor|
|4.5 |2.3 |1.3 |0.3 |setosa |
|5.1 |3.7 |1.5 |0.4 |setosa |
|7.7 |3.0 |6.1 |2.3 |virginica |
|5.5 |2.5 |4.0 |1.3 |versicolor|
|6.3 |2.3 |4.4 |1.3 |versicolor|
|6.2 |2.9 |4.3 |1.3 |versicolor|
|6.3 |2.5 |4.9 |1.5 |versicolor|
|4.7 |3.2 |1.3 |0.2 |setosa |
|6.1 |2.8 |4.0 |1.3 |versicolor|
+------------+-----------+------------+-----------+----------+
[In]: df.groupBy('species').count().orderBy('count').show(10,False)
[Out]:
+----------+-----+
|species |count|
+----------+-----+
|virginica |50 |
|setosa |50 |
|versicolor|50 |
+----------+-----+
这是我们使用Spark
的VectorAssembler
创建整合所有输入特征的单个向量的部分。 它只创建一个捕获该特定行的输入值的功能。 因此,代替四个输入列(我们不考虑标签列,因为它是无监督的机器学习技术),它实质上将其转换为具有列表形式的四个输入值的单个列。
[In]: from pyspark.ml.linalg import Vector
[In]: from pyspark.ml.feature import VectorAssembler
[In]: input_cols=['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
[In]: vec_assembler = VectorAssembler(inputCols = input_cols,outputCol='features')
[In]: final_data = vec_assembler.transform(df)
最终数据包含可用于运行K均值聚类的输入向量。 由于我们需要在使用K-means
之前预先声明**'K'的值,我们可以使用elbow方法找出'K'的正确值。 为了使用肘部法则**,我们对不同的'K'值运行K-means
聚类。 首先,我们从PySpark
库导入K-means
并创建一个空列表,该列表将捕获K的每个值的可变性或SSE(在簇距离内)。
[In]:from pyspark.ml.clustering import KMeans
[In]:errors=[]
[In]:
for k in range(2,10):
kmeans = KMeans(featuresCol='features',k=k)
model = kmeans.fit(final_data)
intra_distance = model.computeCost(final_data)
errors.append(intra_distance)
现在,我们可以使用numpy
和matplotlib
绘制簇内距离与簇的数量。
[In]: import pandas as pd
[In]: import numpy as np
[In]: import matplotlib.pyplot as plt
[In]: cluster_number = range(2,10)
[In]: plt.xlabel('Number of Clusters (K)')
[In]: plt.ylabel('SSE')
[In]: plt.scatter(cluster_number,errors)
[In]: plt.show()
在这种情况下,k = 3似乎是最好的簇数,因为我们可以看到三到四个值之间的一种肘形成。 我们使用k = 3构建最终聚类。
[In]: kmeans = KMeans(featuresCol='features',k=3)
[In]: model = kmeans.fit(final_data)
[In]: model.transform(final_data).groupBy('prediction').count().show()
[Out]:
+----------+-----+
|prediction|count|
+----------+-----+
| 1 | 50 |
| 2 | 38 |
| 0 | 62 |
+----------+-----+
K-Means聚类
为我们提供了三个基于IRIS数据集的不同聚类。 我们肯定会使一些分配错误,因为只有一个类别在该组中有50条记录,其余类别则混淆了。 我们可以使用transform
函数将簇编号分配给原始数据集,并使用groupBy
函数验证分组。
[In]: predictions=model.transform(final_data)
[In]: predictions.groupBy('species','prediction').count().show()
[Out]:
+----------+----------+-----+
| species |prediction|count|
+----------+----------+-----+
| virginica| 2 | 14 |
| setosa | 0 | 50 |
| virginica| 1 | 36 |
|versicolor| 1 | 3 |
|versicolor| 2 | 47 |
+----------+----------+-----+
可以观察到,setosa物种
与杂色一起被完美地分组,几乎被捕获在同一群集中,但是verginica
似乎属于两个不同的群体。 K-means
每次都会产生不同的结果,因为它每次都会随机选择起点(质心)。 因此,除非我们使用种子来重现结果,否则您在K-means
聚类中可能得到的结果可能与这些结果完全不同。 种子确保分裂,并且初始质心值在整个分析过程中保持一致。
在最后一步中,我们可以借助Python
的matplotlib
库可视化新的集群。 为此,我们首先将Spark
数据帧转换为Pandas数据帧。
[In]: pandas_df = predictions.toPandas()
[In]: pandas_df.head()
[In]: from mpl_toolkits.mplot3d import Axes3D
[In]: cluster_vis = plt.figure(figsize=(12,10)).gca(projection='3d')
[In]: cluster_vis.scatter(pandas_df.sepal_length,
pandas_df.sepal_width,
pandas_df.petal_length,
c=pandas_df.prediction,
depthshade=False)
[In]: plt.show()
在本章中,我们讨论了不同类型的无监督机器学习技术,并使用PySpark
中的K-means算法
构建了集群。 K-Means使用随机质心初始化对数据点进行分组,而分层聚类则侧重于将整个数据点合并到单个群集中。 我们还介绍了各种技术来确定最佳簇数,例如Elbow
方法和Dendrogram
,它们在对数据点进行分组时使用方差优化。