Lesson 7.1 无监督学习算法与 K-Means 快速聚类

news2024/11/28 6:43:45

文章目录

  • 一、聚类算法与无监督学习
  • 二、K-Means 快速聚类的算法原理
    • 1. K-Means 快速聚类的基本执行流程
    • 2. K-Means 快速聚类的背后的数学意义
  • 三、K-Means 快速聚类的 sklearn 实现方法
    • 1. sklearn 中实现 K-Means 快速快速聚类
    • 2. 轮廓系数基本概念与 sklearn 中实现方法

  • 从现在开始,我们将学习无监督学习领域内最重要的一类算法——聚类算法。
# 科学计算模块
import numpy as np
import pandas as pd
​
# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt
​
# 自定义模块
from ML_basic_function import *

一、聚类算法与无监督学习

  • 在此前的学习中,无论是回归问题还是分类问题,本质上其实都属于有监督学习范畴:即算法的学习是在标签的监督下进行有选择规律学习,也就是学习那些能够对标签分类或者数值预测起作用的规律。
  • 而无监督学习,则是在没有标签的数据集中进行规律挖掘,既然没有标签,自然也就没有了规律是否对预测结果有效一说,也就失去了对挖掘规律的“监督”过程,这也就是无监督算法的由来。
  • 而如果一个数据集没有标签,我们就只能围绕特征矩阵进行规律挖掘,更具体的来说,面对没有标签的数据集,我们只能去尽可能的探索特征矩阵中的数值分布规律,当然这些规律肯定是需要符合一定的业务场景、拥有一定的现实意义。
  • 而在所有的无监督学习算法中,最著名的两类算法就是聚类算法和关联规则算法。
  • 其中聚类算法是去探索特征矩阵中那些样本更加相似、更有可能是同一类(注意不是更加接近),并据此对数据集中的样本进行分类(当然有时我们也会针对数据集的列进聚类),著名的如 RFM 用户价值划分,就是通过三个维度的评估对不同类型用户进行价值划分的聚类过程。

在这里插入图片描述

  • 而关联规则算法则更加聚焦于一个具体的业务场景——即针对一个购物篮数据进行频繁项的挖掘,并据此进一步探索不同数据之间是否存在一定的关联性,也就是所谓的关联规则,典型的如啤酒和尿布(尽管已经被证伪),就是在进行关联性的挖掘。
  • 当然,相比之下,聚类算法的使用场景会更多。

在这里插入图片描述

  • 当然我们也可以换个角度来理解,那就是如果数据没有标签,那么我们就只能从数据内部结构入手,探索数据的分布规律、并对其进行类别的划分。
  • 总的来说,我们可以将聚类算法的使用场景划分成两类,其一是独力解决一个无监督问题,如上对客户价值进行划分,或者,有时我们也会利用聚类算法来辅助有监督学习的过程、
  • 通常来说是辅助进行特征工程方面的工作,如进行样本的合并、进行特征的合并等。此外,在极少情况下,我们会利用聚类算法去解决有监督学习问题。
  • 当然,围绕样本进行分群的聚类算法并不是一个算法,而是一类算法。
  • 所谓无监督学习最核心的算法类,当前流行的聚类算法也有数十种之多,而不同的聚类算法在进行分群的过程中实际效果也各不相同,在 sklearn 中就有一个著名的、不同聚类算法的效果比较图:

在这里插入图片描述

  • 其中就列举了 10 种不同的聚类针对不同分布形态的数据最终的聚类效果。能够看出,尽管聚类是尝试对数据进行分群,但聚类算法(计算流程)不同,分群效果也是截然不同的。
  • 不过尽管聚类算法看起来数量不少,但实际上对聚类算法的掌握难度其实要远远低于有监督学习算法,由于没有规律的选择环节,因此无监督学习算法并不存在类似模型泛化能力评估以及模型调参的过程。
  • 另外,在实际解决问题的过程中,机器学习类算法的主流应用还是在于预测,因此聚类算法的实际使用场景也要远少于有监督类算法。
  • 正是因为上述种种,我们将挑选最为常用三类聚类算法来进行学习,即 K-Means 快速聚类、小批量快速聚类(Mini Batch K-Means)以及 BDSCAN 基于密度的聚类,并据此深入学习聚类算法的算法共性与使用共性。

二、K-Means 快速聚类的算法原理

  • 首先是 K-Means 快速聚类,这是一种能够对数据集进行指定类别数量分群的聚类算法,同时也是目前最常用的一类聚类算法。
  • 我们此前说到,聚类算法其实就是对数据进行分群,而不同聚类算法流程不同、对应的分群规则也有所不同,对聚类算法流程的掌握,实际上也就是对数据分群规则的掌握。
  • 而对于 K-Means 快速聚类来说,我们可以通过如下实例来介绍该算法的聚类流程。

1. K-Means 快速聚类的基本执行流程

  • 首先进行数据准备,我们借助此前定义的 arrayGenCla 函数创建一组数据,注意,无监督算法的执行流程不需要标签,也不会从标签中提取任何信息,因此其实我们核心是需要 arrayGenCla 函数所创建的特征矩阵。‘而为了更好的展示聚类算法对特征矩阵的分群功能,我们创建一组包含两个特征的数据,在二维特征空间内对其进行聚类:
np.random.seed(23)
X, y = arrayGenCla(num_examples = 20, num_inputs = 2, num_class = 2, deg_dispersion = [2, 0.5])

plt.scatter(X[:, 0],X[:, 1],c=y)

在这里插入图片描述

  • 聚类的类别数量
  • 接下来,围绕已经生成 X,我们尝试对其进行聚类。
  • 对于 K-Means 来说,首先需要确定的是需要将分成几个群,尽管我们从样本的分布来看分成两个群更加合适,但实际上 K-Means 聚类的类别数量根本上由实际业务来决定,并且由于没有标签的引导。
  • 例如上述在围绕用户价值进行分群时,将用户分为高价值、低价值两类还是分成高、中、低价值三类,实际上是由业务端来决定。
  • 从算法原理层面来说,由于聚类算法缺少了标签的指引,所以分成几类其实也没有非常严谨的数值指标进行引导。此处我们假定需要对上述数据聚成两类,然后执行后续的操作。
  • 此处有两个点需要进行拓展讨论:
  • (1) 首先,尽管一般来说没有非常严谨的指标来指导 K-Means 应该聚成几类,但却有很多用于评估聚类结果的指标,有的时候,我们也可以从这些指标中反推应该聚成几类更加合适。
  • (2) 其次,并非所有的聚类算法都需要在聚类开始前设置聚类的类别数量,如后续将要介绍的 DBSCAN。
  • 需要知道的是,聚成几类,实际上就是 K-Means 中的 K。
  • 创建初始中心点
  • 在确定了聚类的类别数量(也就是两类)之后,接下来,我们需要在特征空间中随机生成两个点,作为初始中心点。
  • 这里需要注意,在 K-Means 快速聚类过程中,这类中心点其实起到了至关重要的作用,中心点会随着迭代逐步发生变化,而每个点应该属于哪一类,其实也都是由这些中心点决定的。
  • 这里的相关概念我们可以类比此前的内容进行理解,中心点可以类比于此前逻辑回归/线性回归中的参数,刚开始给予一组初始随机值,并且 K-Means 的计算过程实际上也是一轮一轮进行迭代的,并且每一轮迭代的过程都会修改中心点的位置。
  • 这就类似于梯度下降的计算过程中,通过一轮一轮的迭代来不断的修改参数。
  • 无论如何,我们先在特征空间中创建两个点作为初始中心点,相关过程如下:
np.random.seed(23)
center = np.random.randn(2, 2)
center
#array([[ 0.66698806,  0.02581308],
#       [-0.77761941,  0.94863382]])

plt.scatter(X[:, 0],X[:, 1])
plt.plot(center[0, 0], center[0, 1], 'o', c='red')         # 令第一个点为红色
plt.plot(center[1, 0], center[1, 1], 'o', c='cyan')        # 令第二个点为蓝色

在这里插入图片描述

  • 依据中心点,对数据进行类别划分
  • 首先给出聚类算法分群结束后分得的每个群的定义,为了区分分类算法分类别这一概念,我们成聚类算法分出来的“群”为一个簇。
  • 在给出两个中心点之后,我们就能够依据这两个中心点将数据分成两个簇,划分的过程也非常简单。
  • 计算每个点到两个中心点的距离,如果距离红色中心点更近,则该点属于红色点代表的簇(以下简称红色点簇),而如果该点距离蓝色中心点更近,则应该属于蓝色点代表的簇(以下简称蓝色点簇)。
  • 当然,关于距离的计算其实有很多种,我们在 Lesson 4.1 中曾介绍了多种距离的计算方法,此处我们以欧式距离为例,来进行距离计算,相关计算过程可以由如下代码实现:
  • 欧式距离计算公式如下: d ( x , y ) = ∑ i = 1 n ( x i − y i ) 2 d(x, y) = \sqrt{\sum_{i = 1}^{n}(x_i-y_i)^2} d(x,y)=i=1n(xiyi)2
center
#array([[ 0.66698806,  0.02581308],
#       [-0.77761941,  0.94863382]])

# 计算每条样本距离红色中心点距离
np.power((X - center[0]), 2).sum(1)
#array([2.80418598, 4.53045247, 4.13895531, 5.96788755, 5.43938343,
#       3.865367  , 3.25889336, 7.13376456, 2.30412925, 8.92312473,
#       3.37863704, 2.33823889, 4.64766873, 3.16445859, 5.17815242,
#       1.55545174, 1.69836706, 5.6316741 , 9.43394061, 2.93573695,
#       2.1952354 , 1.68759248, 0.99370862, 1.5880287 , 0.50250126,
#       1.21754799, 1.4285248 , 0.052919  , 0.64128333, 2.52979672,
#       0.53203112, 1.17477822, 2.55720201, 7.04514128, 1.0019926 ,
#       2.98793165, 3.17855917, 0.55893052, 1.00322863, 0.55888205])

# 对比距离中心点远近情况
res_bool = np.power((X - center[0]), 2).sum(1) < np.power((X - center[1]), 2).sum(1)
res_bool
#array([ True, False,  True,  True, False, False,  True, False, False,
#       False, False,  True,  True, False,  True,  True, False, False,
#       False,  True,  True,  True,  True,  True,  True,  True,  True,
#        True,  True,  True,  True,  True,  True,  True,  True,  True,
#        True,  True,  True,  True])

res = res_bool*1
res
#array([1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1,
#       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
  • 根据代码可知,1 代表该样本距离红色中心点更近,应该属于红色点簇,而 0 则代表该样本距离蓝色中心点更近,应该属于蓝色点簇。
  • 我们可以通过可视化的方式进行呈现,令红色点簇中的点都着上红色,令蓝色点簇中的点都着上蓝色:
# 选取红色点簇
X_red = X[(res_bool)]
X_red
#array([[-0.66650597, -0.98709346],
#       [-0.6491641 , -1.52554078],
#       [-1.18377406, -1.56872985],
#       [-0.83886424, -0.96982853],
#       [-0.73091902, -0.59394067],
#       [-0.87944685, -1.47625477],
#       [-0.91318318, -1.61162739],
#       [-0.29234001, -0.77114451],
#       [-0.69690225, -1.01126945],
#       [ 1.00671113,  1.46797245],
#       [ 1.21031133,  1.20580982],
#       [ 0.96433804,  0.97728121],
#       [ 1.52044299,  0.95298263],
#       [ 0.78957802,  0.72400572],
#       [ 0.93945123,  1.09507068],
#       [ 1.2560687 ,  1.06576923],
#       [ 0.83419144,  0.18380686],
#       [ 1.30955704,  0.50371311],
#       [ 0.91932681,  1.59620217],
#       [ 1.12536828,  0.59319188],
#       [ 1.35181182,  0.86592893],
#       [ 0.75872026,  1.62230524],
#       [ 1.33843006,  2.59375134],
#       [ 0.45971762,  1.00511465],
#       [ 1.21891484,  1.66389381],
#       [ 0.87442748,  1.79655531],
#       [ 1.08540885,  0.64537305],
#       [ 0.9334877 ,  0.99132153],
#       [ 0.94945354,  0.71798019]])

# 选取蓝色点簇
X_blue = X[(~res_bool)]
X_blue
#array([[-1.38880971, -0.52568309],
#       [-1.66107376, -0.11387086],
#       [-1.1737295 , -0.66492992],
#       [-1.521725  , -1.50497094],
#       [-0.77913181, -0.43556157],
#       [-1.91903388, -1.46938432],
#       [-1.10092026, -0.47731436],
#       [-1.06813338, -0.3663759 ],
#       [-0.63556208, -0.01578263],
#       [-1.273894  , -1.33970914],
#       [-2.25311516, -0.92651975]])

plt.plot(X_red[:, 0],X_red[:, 1], 'o', c='lightcoral')
plt.plot(center[0, 0], center[0, 1], 'o', c='red')
plt.plot(X_blue[:, 0],X_blue[:, 1], 'o', c='c')
plt.plot(center[1, 0], center[1, 1], 'o', c='cyan')

在这里插入图片描述

  • 至此,我们就根据两个中心点,对上述数据集进行了两个簇的划分。由此我们也可得知,只要给出一组中心点,就能够对数据集进行一次分群。
  • 重新计算中心点
  • 尽管上述过程确实将所有的点分成了两个簇,但划分结果却并不理想。
  • 对于 K-Means 来说,其聚类的核心目的是将类似的划分成一类,而上面的划分结果很明显无法满足要求。
  • 比如对于左下方的一些点来说,明明彼此距离更近,却有些点和右上方的点属于同一类,这并不符合挨得越近越有可能属于同一类的初衷。
  • 因此我们还需要进行进一步的计算,也就是换中心点,再进行簇的划分。
  • 而新的中心点应该如何计算?对于 K-Means 来说,我们会根据上述划分结果,通过计算不同簇的质心来重新计算中心点。
  • 此时我们将红色点簇的中心点改为红色点簇的质心,而蓝色点簇的中心点改为蓝色点簇的质心。
  • 注意,在上述表述中,中心点表示 K-Means 的建模含义,即据此划分数据集,而质心其实表示的中心点的计算方法。
  • 对于利用欧式距离进行计算的 K-Means 快速聚类来说,质心是采用均值来进行计算的: x c e n = x 1 + x 2 + . . . + x n n x_{cen} = \frac{x_1+x_2+...+x_n}{n} xcen=nx1+x2+...+xn y c e n = y 1 + y 2 + . . . + y n n y_{cen} = \frac{y_1+y_2+...+y_n}{n} ycen=ny1+y2+...+yn
  • 为什么说利用欧式距离进行 K-Means 的聚类算法才是用均值计算质心,稍后在解释 K-Means 的数学原理时会详细探讨。
  • 而对于上述点簇,我们可以通过如下方法计算各簇的质心:
X_red.mean(0)
#array([0.48257302, 0.40526208])

X_blue.mean(0)
#array([-1.3431935 , -0.71273659])
  • 而这两个点,就构成了新的中心点,其中第一个点就是红色点簇新的中心点、第二个点就是蓝色点簇新的中心点。接下来围绕这两个点再来进行下一轮划分:
center = np.array([X_red.mean(0), X_blue.mean(0)])
center
#array([[ 0.48257302,  0.40526208],
#       [-1.3431935 , -0.71273659]])

plt.plot(X_red[:, 0],X_red[:, 1], 'o', c='lightcoral')
plt.plot(center[0, 0], center[0, 1], 'o', c='red')
plt.plot(X_blue[:, 0],X_blue[:, 1], 'o', c='c')
plt.plot(center[1, 0], center[1, 1], 'o', c='cyan')

在这里插入图片描述

  • 第二次迭代
  • 接下来,重复上述依据中心点划分数据集的方法,根据新的中心点对数据集进行再一次的划分:
# 距离计算结果
res_bool = np.power((X - center[0]), 2).sum(1) < np.power((X - center[1]), 2).sum(1)
res = res_bool*1

# 新的簇的划分
X_red = X[(res_bool)]
X_blue = X[(~res_bool)]

# 展示划分结果
plt.plot(X_red[:, 0],X_red[:, 1], 'o', c='lightcoral')
plt.plot(center[0, 0], center[0, 1], 'o', c='red')
plt.plot(X_blue[:, 0],X_blue[:, 1], 'o', c='c')
plt.plot(center[1, 0], center[1, 1], 'o', c='cyan')

在这里插入图片描述

  • 在本轮计算结束后,我们发现聚类的结果已经更贴近于 K-Means 所要求的距离更近更有可能属于同一个簇的目标。
  • 当然,前面所有的计算我们可以将其视为两轮运算,每一轮计算中都包含以下三步:
  • (1) 确定中心点,第一轮是随机生成的,其他情况都是通过质心计算得到;
  • (2) 根据中心点,计算每个点到中心点的距离;
  • (3) 根据距离计算结果,对数据集进行划分。
  • 并且后一轮的中心点的位置,实际上是由前一轮计算结果(也就是数据集的划分结果)决定的,也就是当前计算条件其实是上一轮的计算结果(所决定)。
  • 因此上述过程本质上也是在迭代,即整个 K-Means 的计算过程实际上是迭代计算过程。
  • K-Means 迭代停止条件
  • 那何时迭代停止呢?一般来说有两个等价的条件:
  • (1) 相邻两次迭代过程中质心位置不发生变化;
  • (2) 相邻两次迭代过程中各点所属类别不发生变化;
  • 注意,这两个条件是等价的,如果质心位置不变,则数据集划分情况就不会发生变化,而如果数据集划分情况不发生变化,则质心也就不变。
  • 当然,迭代停止也就等价于模型收敛了,因此对于 K-Means 来说,和梯度下降求解参数一样,都存在模型收敛这一说法。
  • 接下来,我们就尝试继续进行计算,看下数据集划分情况或者中心点是否发生变化。首先继续计算心的质心:
center = np.array([X_red.mean(0), X_blue.mean(0)])
plt.plot(X_red[:, 0],X_red[:, 1], 'o', c='lightcoral')
plt.plot(center[0, 0], center[0, 1], 'o', c='red')
plt.plot(X_blue[:, 0],X_blue[:, 1], 'o', c='c')
plt.plot(center[1, 0], center[1, 1], 'o', c='cyan')

在这里插入图片描述

  • 然后进行数据集的重新划分:
# 距离计算结果
res_bool = np.power((X - center[0]), 2).sum(1) < np.power((X - center[1]), 2).sum(1)
res = res_bool*1

# 新的簇的划分
X_red = X[(res_bool)]
X_blue = X[(~res_bool)]

# 展示划分结果
plt.plot(X_red[:, 0],X_red[:, 1], 'o', c='lightcoral')
plt.plot(center[0, 0], center[0, 1], 'o', c='red')
plt.plot(X_blue[:, 0],X_blue[:, 1], 'o', c='c')
plt.plot(center[1, 0], center[1, 1], 'o', c='cyan')

在这里插入图片描述

  • 我们发现数据集划分方式并为发生变化,说明已经达到 K-Means 模型收敛条件,模型将不再进行迭代,而上述过程就是一整个 K-Means 快速聚类的过程。
  • 当然,如果将上述代码封装为一个完整函数,该函数就是 K-Means 快速聚类的手动实现方法。

2. K-Means 快速聚类的背后的数学意义

  • 不难看出,相比很多有监督学习算法,K-Means 的计算过程相对简单,并且稍后在进行 sklearn 的实现过程中,我们也会发现 K-Means 评估器的参数也不算多。
  • 不过尽管如此,我们还是需要稍微深入点儿来讨论 K-Means 快速聚类过程背后的数学意义,在这个过程中,我们会发现 K-Means 作为一个无监督学习算法,其背后有非常多的原理都和我们此前介绍的一系列算法的原理是相通的。
  • K-Means 建模目标的数学等价表示
  • 尽管我们此前一直说,K-Means 快速聚类的目标是让更接近的点划分为同一个簇,以达到“物以类聚、人以群分”的效果,但实际上这一目标背后其实有更加严谨的数学表示,那就是在给定 K(簇的个数)的情况下,找到一种最优的划分情况,使得组内误差平方和尽可能的小。
  • 这里所谓的组内误差平方和,指的是每个点到当前簇的中心点的距离的平方和,我们可以通过如下数学计算符号来进行表示:
NameDescription
x x x单独样本
C i C_i Ci第i个簇
c i c_i ci C i C_i Ci的质心
c c c所有点的质心
m i m_i mi第i个簇中数据个数
m m m数据集总个数
K K K簇的个数
  • 则组内误差为: ∑ i = 1 K ∑ x ∈ C i ( c i − x ) 2 \sum^K_{i=1}\sum_{x\in C_i}(c_i-x)^2 i=1KxCi(cix)2
  • 这点其实不难理解,如果每个点都距离各自中心点更近,肯定聚类效果会更好。而由此,则可进一步衍生出原型和质心计算公式的数学意义这两个至关重要的点。
  • 原型与 SSE
  • 在 K-Means 快速聚类中,中心点、质心其实还有另一个叫法:原型,即当前簇中所有点的原型。
  • 换而言之,很多时候我们在进行聚类分析时,最后实际上是要利用这个中心点、质心或者原型来代表一个簇的数据的。
  • 例如此前介绍的 RFM 客户价值划分模型中,如果我们将客户划分成高、中、低价值三类,最终我们还是需要从这三类中找到具有代表性的“典型”,才能为后续的诸如产品设计环节提供数据支持。
  • 也就是说,我们其实是希望通过原型来“代表”一个簇中的点,也就是说,我们希望通过原型来预测这个簇中数据的表现。
  • 那既然是预测,就肯定会有误差,而这里用原型预测一个簇中其他所有点的误差,就是上述的组内平方和误差,也就是簇内所有点到这个原型之间距离的平方和。
  • 而在线性回归中我们曾介绍,预测值和真实值之间的距离,被称为 SSE,因此对于 K-Means 快速聚类来说,其组内误差平方和也就是 SSE: S S E = ∑ i = 1 K ∑ x ∈ C i ( c i − x ) 2 SSE = \sum^K_{i=1}\sum_{x\in C_i}(c_i-x)^2 SSE=i=1KxCi(cix)2
  • 质心的最佳计算公式
  • 当然,上述 SSE 其实就是对当前聚类状况的一个评估,SSE 越大,则说明当前聚类效果较差,而 SSE 较小,则说明当前聚类效果较好。回顾上述聚类过程,能够明显看到 SSE 下降趋势:
# 第一轮SSE
# 确定中心点
np.random.seed(23)
center = np.random.randn(2, 2)# 计算距离
res_bool = np.power((X - center[0]), 2).sum(1) < np.power((X - center[1]), 2).sum(1)
res = res_bool*1# 划分数据集
X_red = X[(res_bool)]
X_blue = X[(~res_bool)]# 计算SSE
np.power((X_red - center[0]), 2).sum()
#66.26098649489376

# 第二轮SSE
# 确定中心点
center = np.array([X_red.mean(0), X_blue.mean(0)])# 计算距离
res_bool = np.power((X - center[0]), 2).sum(1) < np.power((X - center[1]), 2).sum(1)
res = res_bool*1# 划分数据集
X_red = X[(res_bool)]
X_blue = X[(~res_bool)]# 计算SSE
np.power((X_red - center[0]), 2).sum()
#23.256673490142994
  • 而更进一步,可以借助数学证明,选取质心作为中心点,实际上是有利于让 SSE 下降速度最快的迭代方法。
  • 在梯度下降中我们曾提到,令损失函数导函数取值为 0 的方向,就是损失函数值下降最快的方向,此处也类似,由于原型概念的引入,使得我们可以将 K-Means 视作预测模型,而上述 SSE 就是其损失函数,并且该损失函数中变量为 c k c_k ck ,也就是质心,对其求导可得: ∂ ∂ c k S S E = ∑ i = 1 K ∑ x ∈ C i ∂ ∂ c i ( c i − x ) 2 = ∑ i = 1 K ∑ x ∈ C i 2 ( c i − x ) = 0 \begin{aligned} \frac{\partial}{\partial c_k}SSE &= \sum^K_{i=1}\sum_{x\in C_i}\frac{\partial}{\partial c_i}(c_i-x)^2 \\ &=\sum^K_{i=1}\sum_{x\in C_i}2(c_i-x) = 0 \end{aligned} ckSSE=i=1KxCici(cix)2=i=1KxCi2(cix)=0
  • 因此,对于给定的 i,上式的必要条件是: ∑ x ∈ C i ( c i − x ) = 0 \sum_{x\in C_i}(c_i-x) = 0 xCi(cix)=0
  • 由该式可以进一步推导得出: ∑ x ∈ C i x = m i c i \sum_{x \in C_i}x = m_ic_i xCix=mici
  • c i = 1 m i ∑ x ∈ C i x c_i = \frac{1}{m_i}\sum_{x \in C_i}x ci=mi1xCix
  • 即中心是由质心计算得出。
  • 换而言之,中心点采用每个点各维度均值的计算方式,能够让 SSE 下降速度最快。当然如果样本距离的计算方式发生变化,则质心对应的计算方式也必须发生变化,例如,如果采用曼哈顿距离进行距离计算,则需要采用中位数作为质心的计算方法。

三、K-Means 快速聚类的 sklearn 实现方法

  • 接下来,我们尝试在 sklearn 中进行 K-Means 快速聚类,并尝补充讲 解K-Means 聚类算法在使用过程中的注意事项,同时补充介绍关于 Mini Batch K-Means 的相关内容 。

1. sklearn 中实现 K-Means 快速快速聚类

  • 首先,作为聚类的评估器,K-Means 在 sklearn.cluster 模块下,通过如下方式进行导入,并查看 K-Means 的超参数。
from sklearn.cluster import KMeans

KMeans?
  • 除了通用的 verbose、random_state 和 copy_x 外,我们重点介绍其他各参数:
NameDescription
n_clusters聚类类别总数
init初始中心点创建方法
n_init初始化几次中心点
max_iter最大迭代次数
tol收敛条件
precompute_distances是否提前预计算距离
algorithm优化距离计算的方法选取
  • 围绕上述参数,需要重点解释的是关于 K-Means 迭代不平稳的问题。
  • 尽管此前例子中 K-Means 的迭代过程快速高效,但实际上,当面对复杂数据集时,K-Measn 很有可能陷入“局部最小值陷进”或者“震荡收敛”。
  • 所谓落入局部最小值陷进,指的是尽管可能有更好的划分数据集的方法(SSE 取值更小),但根据 K-Means 的收敛条件却无法达到,算法会在另外一种划分情况时停止迭代。
  • 而所谓“震荡收敛”,指的是算法会在两种不同的划分方法中来回震荡(尽管 SSE 取值可能有差别)。
  • 前种情况非常类似于参数进行梯度下降求解过程中,如果采用 BGD,并且参数在一个局部最小值点附近,则最终参数会收敛到局部最小值点类似,而后面一种情况则非常类似于学习率过大导致无法收敛、一直处于震荡状态。
  • 而出现这种问题的根本原因,其实在于初始中心点的随机选取。
  • 因此 sklearn 中其实集成了两种技术手段来避免上述两种问题的出现。
  • 其一是采用 k-means++ 算法来计算初始中心点,经过这种算法生成的中心点,能够大概率在后续的迭代过程中让模型保持平稳。
  • 而无论 k-means++ 是否生效,为了保险起见,sklearn 中都采用了多次初始化中心点、多次训练模型、然后找到最优数据集划分的方法,这就是 n_init 参数的意义。
  • 在这双重保证下,sklearn 的 K-means 快速聚类能够整体保持非常平稳的状态。
  • 接下来,尝试调用sklearn中快速聚类方法对数据集进行聚类:
km = KMeans(n_clusters=2)
km.fit(X)
#KMeans(n_clusters=2)
  • 注意,对于无监督学习算法,只需要带入特征矩阵进行计算即可。
  • 在训练完成后,我们即可调用评估器的相关属性来查看聚类结果:
# 查看中心点
km.cluster_centers_
#array([[-1.08131141, -0.91777659],
#       [ 1.04228586,  1.11340149]])

# 查看每条数据属于哪一类
km.labels_
#array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
#       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], dtype=int32)

# 借助图像进行验证
plt.scatter(X[:, 0], X[:, 1], c=km.labels_)
plt.plot(km.cluster_centers_[0, 0], km.cluster_centers_[0, 1], 'o', c='red')
plt.plot(km.cluster_centers_[1, 0], km.cluster_centers_[1, 1], 'o', c='cyan')

在这里插入图片描述

# 收敛时SSE
km.inertia_
#16.352095518854334
  • 注意,此处和手动实现过程 SSE 计算结果不同,原因是手动实现时 SSE 是上一轮迭代完的中心点,若在手动实现部分将中心点改为两次迭代后的质心,则计算结果相同。
# 迭代次数
km.n_iter_
#3
  • 能够发现,和此前手动实现结果一致。
  • 此外,K-Means 评估器也支持 predict 方法,对于新的数据,K-Means 模型能够依据其距离各中心点的远近来对其类别所属情况进行判别:
X_new = np.random.randn(2, 2)
km.predict(X_new)
#array([0, 0], dtype=int32)
  • 当然,正因如此,我们可以绘制 K-Means 的决策边界,类似于有监督学习算法,决策边界的形状其实一定程度将决定聚类算法针对不同分布的数据集时聚类的“性能”。
plot_decision_boundary(X, km.labels_, km)

在这里插入图片描述

2. 轮廓系数基本概念与 sklearn 中实现方法

  • 轮廓系数基本概念
  • 尽管我们可以通过 SSE 来表示当前 K-Means 聚类模型效果好坏(甚至作为损失函数),但 SSE 却不能作为模型超参数(K)的选取依据。
  • 其实我们不难发现,伴随 K 增加,模型整体 SSE 将会逐渐下降。不过,尽管如此,其实 K-Means 快速聚类中,还是有部分指标可以一定程度上给出聚成几类的指导意见,其中最有名的就是轮廓系数(silhouette coefficient,简称 sc)。
  • 注意,对于 K-Means 来说,这些指标只能参考,最终聚成几类,还应该主要参考模型的业务背景。
  • 轮廓系数的计算过程如下:
  • (1) 对于第 i 条数据(以下简称 i),计算该对象到所属簇的平均距离,记为 a i a_i ai
  • (2) 如果还存在其他簇(不包含第 i 个对象的簇,如 A、B 两个簇),分不同的簇,计算该对象到这些簇的所有点的平均距离(例如计算 i 到 A 簇中所有点的平均距离,以及计算 i 到 B 簇中所有点的平均距离),并在这些距离中找到最小值记为 b i b_i bi
  • (3) 则对于 i,轮廓系数计算结果为: s i = b i − a i m a x ( a i , b i ) s_i=\frac{b_i-a_i}{max(a_i, b_i)} si=max(ai,bi)biai
  • (4) 而对于聚类中的所有 N 条数据,最终轮廓系数为单个 s i s_i si 的均值,即 s = m e a n ( s i ) s=mean(s_i) s=mean(si)
  • 尽管轮廓系数可以在 [-1, 1] 区间内取值,但我们并不希望轮廓系数出现负值,此时代表组内的平均距离要大于组外平均距离的最小值,此时说明聚类算法无效。
  • 我们希望 b i > a i b_i>a_i bi>ai,并且希望 a i a_i ai 尽可能的小,此时 s i s_i si 也就趋近于 1,而当轮廓系数趋于 0 时,则说明各簇重叠现象明显。
  • 并且,非常重要的一点是,轮廓系数取值的大小一定程度上能够给 K 的取值提供建议,当轮廓系数比较大时,往往说明数据在特征空间中本身的分布情况就和聚类的类别数量相同。
  • 和 SSE 不同,轮廓系数受到K的影响相对较小,这也是轮廓系数相对可靠的原因之一。
  • 轮廓系数的 sklearn 中实现方法
  • 当然,我们也可以借助 sklearn 中 metrics 模块下的 silhouette_score 函数来进行轮廓系数的计算:
from sklearn.metrics import silhouette_score

silhouette_score(X, km.labels_)
#0.7241755028408805
  • 而更进一步的,轮廓系数如何指导 K 值的选取,我们可以通过如下实例来进行说明。此处手动生成一组三分类明显的数据集,观察 K 取值不同时轮廓系数的变化情况。
np.random.seed(23)
X, y = arrayGenCla(num_examples = 50, num_inputs = 2, num_class = 3, deg_dispersion = [2, 0.5])
plt.scatter(X[:, 0], X[:, 1], c=y)

在这里插入图片描述

ss = []for i in range(2, 12):
    km = KMeans(n_clusters=i).fit(X)
    ss.append(silhouette_score(X, km.labels_))

ss
#[0.5917969390803755,
# 0.6753180189915984,
# 0.580872808406484,
# 0.47687683047050644,
# 0.3685113521594094,
# 0.3653820829962011,
# 0.3639995054273048,
# 0.3524637605205039,
# 0.3579462241667135,
# 0.3682450219445942]
  • 能够发现,当 K 取值为 3 时轮廓系数取值最高,也就是说明从特征空间的数据分布来看,整体呈现聚成三类的趋势。当然,这个我们创建数据集时赋予的规律一致。
  • 不过,仍然需要强调的是,除非特征矩阵在特征空间的“分界”非常明显,才能在轮廓系数上有明显差异。而聚类算法在分类上的性能,其实也远远弱于有监督学习算法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/345181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【基于jeeSite框架】SpringBoot+poi+Layui自定义列表导出

文章目录功能效果思路代码前台后台easyPoi,easyExcel&#xff0c;poi三者的区别poipoi依赖导出ExcelHSSF方式导出XSSF方式导出SXSSF方式导出导入excelHSSF方式导入XSSF方式导入SXSSF方式导入easyPoi依赖包采用注解导出导入easyExcel依赖采用注解导出导入API文档easyPoi操作文档…

内网渗透(二十五)之Windows协议认证和密码抓取-使用Hashcat和在线工具破解NTLM Hash

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

说说Knife4j

Knife4j是一款基于Swagger2的在线API文档框架使用Knife4j, 需要 添加Knife4j的依赖当前建议使用的Knife4j版本, 只适用于Spring Boot2.6以下版本, 不含Spring Boot2.6 在主配置文件(application.yml)中开启Knife4j的增强模式必须在主配置文件中进行配置, 不要配置在个性化配置文…

Java 代理模式详解

1、代理模式 代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问&#xff0c;这样就可以在不修改原目标对象的前提下&#xff0c;提供额外的功能操作&#xff0c;扩展目标对象的功能。 代理模式的主要作用是扩展目标对象…

C++——运算符重载

1、运算符重载的概念 运算符重载&#xff0c;就是对已有的运算符重新进行定义&#xff0c;赋予其另一种功能&#xff0c;以适应不同的数据类型。运算符重载的目的是让语法更加简洁运算符重载不能改变本来寓意&#xff0c;不能改变基础类型寓意运算符重载的本质是另一种函数调用…

【半监督医学图像分割 2021 TMI】SimCVD 论文翻译

文章目录【半监督医学图像分割 2021 TMI】SimCVD 论文翻译摘要1. 介绍2. 相关工作3. 方法3.1 总览3.2 任务制定3.3 基础体系结构3.4 边缘对比蒸馏4. 实验4.1 数据集和预处理4.2 实现细节5. 结论5.1 实验&#xff1a;LA5.2 实验&#xff1a;Pancreas6. 消融研究6.1 边界感知对比…

嵌入式ARM工业边缘计算机BL302的CAN总线接口如何设置?

CAN 接口如图所示&#xff0c;输入如下命令&#xff1a; ifconfig -a //查看所有网卡 如果 FlexCAN 驱动工作正常的话就会看到 CAN 对应的网卡接口&#xff0c;如图。从图中可 以看出&#xff0c;有一个名为“can0”的网卡&#xff0c;这个就是 BL302 板上的 CAN1 接口对应的 c…

借助亚马逊云科技,Early Data完成数据云上安家、实现降本增效

数字经济时代&#xff0c;伴随着大数据应用的不断深入&#xff0c;企业对用户及市场发展动向的判断正变得愈加精准。数据资产不再是虚无缥缈的东西&#xff0c;而是可以帮助企业切切实实找到业务增长点&#xff0c;洞悉潜在商机&#xff0c;拥有巨大潜力的“宝藏”。IDC数据显示…

【 mybatis的工作流程】

目录一.mybatis执行流程二.使用工具类简化项目&#xff08;反射的体现&#xff09;2.1 Sqlsession工厂对像2.2 工具类&#xff08;可直接放在工具类使用&#xff09;一.mybatis执行流程 1.1 读取主配置文件mybatis-config.xml&#xff0c;获得运行环境和数据库连接。 1.2 加载映…

对象之间的关系

目录1. 依赖2. 关联3. 聚合4. 组合Java的对象/类之间有四种关系&#xff1a;依赖、关联、组合、聚合。 1. 依赖 依赖&#xff08;Dependency&#xff09;&#xff1a; 一个对象的功能依赖于另一个对象。 类比&#xff1a;人类生存依赖食物和空气 体现&#xff1a;被依赖者体…

Ethercat系列(5)TWcat3激活过程的协议分析(续1)

顺序写系统时间偏移从-》主顺序写时间延迟主-》从从-》主顺序写分布式时钟启动主-》从从-》主读多重写系统时间主-》从从-》主顺序写应用层控制主-》从从-》主顺序读错误计数器主-》从从-》主顺序读应用层状态主-》从从-》主顺序读应用层&#xff0c;广播写错误计数器主-》从从…

2023 年首轮土地销售活动来了 与 The Sandbox 一起体验「体素狂热」!

2 月 14 日晚上 11 点&#xff0c;开始你的体素冒险。 The Sandbox 很高兴推出 2023 年的第一次土地销售活动。欢迎来到「体素狂热 (Voxel Madness)」&#xff01; 简要概括 土地销售抽奖活动将于北京时间 2 月 14 日星期二晚上 11 点开始 「体素狂热」 土地销售活动将于 2 月…

企业公众号菜单添加视频的完整教程(组图)

公众号实用教程&#xff0c;公众号运营教程。 有公众号的朋友&#xff0c;看过来&#xff0c;今天来教教大家&#xff0c;在企业微信公众号中如何调用视频列表。先上一个效果吧&#xff1a; 最终效果的截图 -关注酷播公众号&#xff0c;查看实际的应用效果&#xff08;在本公众…

【shell 编程大全】脚本交互 以及表达式

脚本交互 以及表达式 1. 概述 嗨&#xff0c;我又来继续分享了。今天分享的内容是脚本交互&#xff0c;再开始接下来的分享开始之前&#xff0c;让我们来回顾下上一次(【shell 编程大全】内容格式化以及多样化输出)的内容&#xff1a; 内容格式化 重定向管道符后台执行信息符号…

C语言学习_DAY_3_基本数据类型_运算符与表达式【C语言学习笔记】

目录 I. 基本数据类型 II. 复杂的输出和输入语句编写 III. 运算符与表达式 III.I 算术运算符 III.II 关系运算符 III.III 逻辑运算符 III.IV 位运算符 III.V 三目运算符 III.VI 逗号运算符 高质量博主&#xff0c;点个关注不迷路&#x1f338;&#x1f338;&#x1f3…

电脑怎么备份文件?含泪分享3种免费的方法

电脑里面保存着我们很多文件数据&#xff0c;比如工作文档、珍贵的照片、喜欢的音乐或者视频等。你很可能会遇到下面的原因而丢失数据&#xff1a;清空回收站&#xff0c;或者使用永久删除文件的方法&#xff1b;电脑出现故障&#xff0c;比如硬盘故障、系统崩溃&#xff1b;病…

Python实现基于openCV+百度智能云平台实现《1:N人脸考勤机》文章最后附带源码!

目录 一、 项目介绍 1.1 项目名称 1.2 项目简介 1.3 项目物料 1.4 技术栈 二、 项目架构 三、项目细节 3.1 环境搭建 3.2 利用opencv实现摄像头调取及相关图像的采集 3.3 利用aips上传图像和结果返回 3.4 结果优化和处理 3.5 可扩展性 3.6 遗留问题和…

中睿天下获评2022年度中关村网信联盟工业互联网标准工作特别贡献奖

近日&#xff0c;中关村网络安全与信息化产业联盟&#xff08;简称“CCAIA”&#xff09;于线上召开联盟第二届第五次会员大会&#xff0c;来自全国的联盟成员代表、行业专家等100余人出席会议&#xff0c;共同听取了联盟2022年工作总结与2023年工作计划&#xff0c;大会设置了…

春招Leetcode刷题日记-D2-贪心算法-训练专题

D2-贪心算法-训练专题贪心算法使用总结力扣605. 种花问题思路代码力扣763. 划分字母区间思路代码另一种思路贪心算法使用总结 当我们意识到&#xff0c;满足贪心算法三要素时候&#xff0c;解决贪心算法问题的时候&#xff0c;一定要遵循的步骤&#xff1a; 1、读懂题目限制条…

数据结构与算法:7种必须会的排序以及3种非基于比较排序

1.什么是排序 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序…