聚类-附kmeans代码

目录

1、聚类任务

2、性能度量

外部指标

内部指标

3、原型聚类

k均值聚类

学习向量化

4、密度聚类

5、层次聚类


1、聚类任务

目标:通过对无标记训练样本的学习来揭示数据的内在性质和规律,为进一步数据分析提供基础。

聚类试图将数据集中的样本划分为若干个通常是不相交的子集,每个子集称为一个“簇”,每个簇可能对应于一些潜在的概念(类别),这些概念对聚类算法而言事先是未知的

形式化来说,设样本集D=\{\boldsymbol{x}_1,\boldsymbol{x}_2,\cdots,\boldsymbol{x}_m\}包含m个无标记样本,每个样本\boldsymbol{x}_i=\{x_{i1};x_{i2};\cdots;x_{in}\}是一个n维特征向量。

则聚类算法将样本集D划分为k个不相交的簇\{C_l|l=1,2,\cdots,k\},其中C_l \cap_{l' \neq l}C_l = \varnothingD= \cup_{l=1}^kC_l

相应的,用\lambda_j \in \{1,2,\cdots,k\}表示样本\boldsymbol{x}_j的“簇标记”,即\boldsymbol{x}_j \in C_{\lambda_j},于是聚类的结果可用包含m个元素的簇标记向量\boldsymbol{\lambda} = (\lambda_1; \lambda_2;\cdots;\lambda_m )表示。

2、性能度量

性能度量又称聚类“有效性指标”,(1)需要某种性能度量来评估聚类结果的好坏;(2)可直接将其作为聚类过程的优化目标。

我们期望聚类的结果“簇内相似度”高且“簇间相似度”低。

外部指标

对数据集D=\{\boldsymbol{x}_1,\boldsymbol{x}_2,\cdots,\boldsymbol{x}_m\},假定通过聚类给出的簇划分为C = \{C_1, C_2, \cdots, C_k\},参考模型给出的簇划分C^* = \{C_1^*, C_2^*, \cdots, C_k^*\},相应的,令\boldsymbol{\lambda}\boldsymbol{\lambda}^*分别表示CC^*对应的簇标记向量,将样本两两配对考虑(共有m(m-1)/2个pair对,即a + b + c+ d= m(m-1)/2),定义

        \begin{aligned} &a = |SS|,SS=\{(\boldsymbol{x}_i,\boldsymbol{x}_j)| \lambda_i=\lambda_j,\lambda_i^* = \lambda_j^*, i < j\} \\ &b = |SD|,SS=\{(\boldsymbol{x}_i,\boldsymbol{x}_j)| \lambda_i=\lambda_j,\lambda_i^* \neq \lambda_j^*, i < j\} \\ &c = |DS|,SS=\{(\boldsymbol{x}_i,\boldsymbol{x}_j)| \lambda_i\neq \lambda_j,\lambda_i^* = \lambda_j^*, i < j\} \\ &d = |DD|,SS=\{(\boldsymbol{x}_i,\boldsymbol{x}_j)| \lambda_i\neq \lambda_j,\lambda_i^* \neq \lambda_j^*, i < j\} \\ \end{aligned}

其中,我们发现,相对于参考模型,a是必定正确的(集合SS包含pair对在两个模型均在相同簇),b(集合SD包含pair对在C中属于相同簇,在C^*中属于不同簇)和c(集合DS包含pair对在C中属于不同簇,在C^*中属于相同簇)是必定错误的,d(集合DD包含pair对在两个模型均在不同簇)是待定的(无法判断对错)。

Jaccard系数(JC):

        JC=\frac{a}{a+b+c}

FM指数(FMI):

       FMI=\sqrt{\frac{a}{a+b}\cdot \frac{a}{a+c}}

Rand指数(RI):

       RI=\frac{2(a+d)}{m(m-1)}

显然,上述性能度量(JC、FMI和RI)的结果值均在[0,1]区间,值越大越好。

内部指标

考虑聚类结果的簇划分C = \{C_1, C_2, \cdots, C_k\},定义

       \begin{aligned} &avg(C) = \frac{2}{|C|(|C|-1)}\sum_{1 \leqslant i < j \leqslant |C|}dist(\boldsymbol{x}_i, \boldsymbol{x}_j) \\ &diam(C)=max_{1 \leqslant i < j \leqslant |C|}dist(\boldsymbol{x}_i, \boldsymbol{x}_j) \\ &d_{min}(C_i, C_j)=min_{\boldsymbol{x}_i \in C_i, \boldsymbol{x}_j \in C_j}dist(\boldsymbol{x}_i, \boldsymbol{x}_j) \\ &d_{cen}(C_i, C_j)=dist(\boldsymbol{\mu}_i, \boldsymbol{\mu}_j) \end{aligned}

其中,dist(\cdot, \cdot)用于计算两个样本之间的距离;\boldsymbol{\mu}代表簇C的中心点\boldsymbol{\mu}=\frac{1}{|C|}\sum_{1\leqslant i \leqslant|C|}\boldsymbol{x}_i。显然,avg(C)对应于簇C内样本间的平均距离,diam(C)对应于簇C内样本间的最远距离,d_{min}(C_i, C_j)对应于簇C_i与簇C_j最近的样本间的距离,d_{cen}(C_i, C_j)对应于簇C_i与簇C_j中心点的距离。

DB指数(DBI):

      DBI = \frac{1}{k}\sum_{i=1}^k\max_{j \neq i}(\frac{avg(C_i)+avg(C_j)}{d_{cen}(\boldsymbol{\mu}_i,\boldsymbol{\mu}_j)})

Dunn指数(DI)

     DI = \min_{1 \leqslant i\leqslant k}\{\min_{j \neq i}(\frac{d_{min}(C_i,C_j)}{\max_{1 \leqslant l \leqslant k}diam(C_l)})\}  

显然,DBI的值越小越好,而DI则相反,值越大越好。

3、原型聚类

原型聚类也称作“基于原型的聚类”,此类算法假设聚类结构可以通过一组原型刻画,在现实任务中极为常见。(“原型”是指样本空间中具有代表性的点)通常情况下,算法先对原型进行初始化,然后对原型进行迭代更新求解。采用不同的原型表示、不同的求解方式,将产生不同的算法,下面介绍几种著名的原型聚类算法。

k均值聚类

给定样本集D=\{\boldsymbol{x}_1,\boldsymbol{x}_2,\cdots, \boldsymbol{x}_m\},“k均值”算法针对聚类所得簇划分C = \{C_1, C_2, \cdots, C_k\}最小化平方误差

         E = \sum_{i=1}^k\sum_{\boldsymbol{x} \in C_i}||\boldsymbol{x}-\boldsymbol{\mu}_i||_2^2 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (1)

其中\boldsymbol{\mu}_i=\frac{1}{|C|}\sum_{\boldsymbol{x} \in C_i} \boldsymbol{x}是簇C_i的均值向量。式(1)在一定程度上刻画了簇内样本围绕簇均值向量的紧密程度,E值越小则簇内样本相似度越高。要想直接找到式(1)的最优解不太容易,因为需要考察样本集D所有可能的簇划分,这是一个NP难问题。因此k均值法采用了贪心策略,通过迭代优化来近似求解上式。

       

过程:

  • 初始化k个均值向量\{\boldsymbol{\mu}_1,\boldsymbol{\mu}_2,\cdots, \boldsymbol{\mu}_k\},即k个簇的中心
  • 循环迭代(直到满足最大的运算轮数或调整幅度小于阈值)。
    • 将m个样本分配到距离最近的簇(到簇中心的距离最小)
    • 依据新划分的簇计算新的均值向量

k均值算法的缺点:

  1. 需要事先确定簇的个数即k值(是一个超参数)
  2. 对噪声和离群点敏感
  3. 结果不一定是全局最优,只能保证局部最优
  4. 相较于其他算法,kmeans只能处理数值型的,对于其他类型的数据算法会受到限制。

如何初始化簇中心:

  1. 随机初始化
  2. 选择彼此距离尽可能远的K个点。首先随机选择一个点作为第一个初始类簇中心点,然后选择距离该点最远的那个点作为第二个初始簇中心点,然后再选择距离前两个点的最近距离最大的点作为第三个初始簇中心点,一次类推,直到选出k个初始中心点。
  3. 先对数据用层次聚类算法或Canopy算法进行聚类,得到k个簇之后,从每个簇中选择一个点,该点可以是该簇的中心点,或者是距离类簇中心最近的那个点。

附代码

from random import random
import numpy as np
from matplotlib import pyplot as plt


class KMeans:
    def __init__(self):
        self._centor = None

    def fit(self, X_train, k, n_iters=1e2):
        def init(k, X_train, shuffled_indexes):
            for i in range(k):
                for j in range(X_train.shape[1]):
                    self._centor[i, j] = X_train[shuffled_indexes[i], j]

        # 随机选取k个点作为初始的质心
        shuffled_indexes = np.random.permutation(X_train.shape[0])
        self._centor = np.zeros(shape=(k,X_train.shape[1]))
        init(k, X_train, shuffled_indexes)

        for epoch in range(n_iters):
            # 将样本配到离他最近的质心
            label = [[] for _ in range(k)]
            for i in range(X_train.shape[0]):
                min_dis, idx_j = float("inf"), 0
                for j in range(k):
                    dis = np.sum(np.square(X_train[i, :] - self._centor[j, :]))
                    if min_dis > dis:
                        min_dis, idx_j = dis, j
                label[idx_j].append(i)

            # 更新质心
            errors = 0
            for j in range(k):
                old_center = self._centor[j, :].copy()
                self._centor[j, :] = np.mean(X_train[label[j]], axis=0)
                errors += np.sum(np.abs(old_center - self._centor[j, :]))

            if errors < 1e-4:
                print("stoped at epoch : {}, errors = {}".format(epoch, errors))
                return self._centor
        return self._centor

    def predict(self, X_test):
        label = np.zeros(shape=X_test.shape[0], dtype='int')
        for i in range(X_test.shape[0]):
            min_dis = float("inf")
            for j in range(self._centor.shape[0]):
                dis = np.sum(np.square(X_test[i, :] - self._centor[j, :]))
                if min_dis > dis:
                    min_dis, label[i] = dis, j
        return label

简单的测试案例, 随机生成了100个点,k=3

if __name__ == "__main__":
    X = [[0] * 2 for _ in range(100)]
    for i in range(len(X)):
        X[i][0] = 2 + random() * 2
        X[i][1] = 1 + random() * 2

    X = np.array(X)

    k_means = KMeans()
    centor = k_means.fit(X, 3, 10)
    print("质心:\n{}".format(centor))
    label = k_means.predict(X)
    print("\n标签:\n{}".format(label))

    plt.scatter(X[label == 0, 0] , X[label == 0, 1], s=10, c="blue")
    plt.scatter(X[label == 1, 0], X[label == 1, 1], s=10, c='green')
    plt.scatter(X[label == 2, 0], X[label == 2, 1], s=10, c='yellow')
    plt.scatter(centor[0, 0], centor[0, 1], c='blue', marker="v", s=300)
    plt.scatter(centor[1, 0], centor[1, 1], c='green', marker="v", s=300)
    plt.scatter(centor[2, 0], centor[2, 1], c='yellow', marker="v", s=300)
    plt.show()

结果如下:

stoped at epoch : 9, errors = 0.0
质心:
[[2.68613685 1.56171796]
 [2.68554185 2.6062735 ]
 [3.59284737 2.0891164 ]]

标签:
[1 0 1 1 1 0 1 1 2 1 1 0 1 0 1 1 0 0 2 2 0 1 1 0 1 1 0 0 1 2 0 0 2 1 0 2 2
 1 0 2 1 1 0 1 2 1 2 0 1 2 1 2 1 2 0 1 2 1 2 1 1 1 0 1 1 1 1 2 0 0 1 1 2 1
 0 2 1 1 0 0 2 2 0 2 2 1 2 1 2 0 2 0 1 1 1 0 2 1 1 1]

学习向量化

“学习向量化”(LVQ):假设样本带有类别标记,学习过程利用样本的这些监督信息来辅助聚类。

给定样本集D=\{\boldsymbol{x}_1,\boldsymbol{x}_2,\cdots, \boldsymbol{x}_m\},每个样本\boldsymbol{x}_i=\{x_{j1};x_{j2};\cdots;x_{jn}\}是一个n维特征向量,y_j \in \gamma是样本\boldsymbol{x}_j的类别标记。

目标:学得一组n维原型向量\{\boldsymbol{p}_1,\boldsymbol{p}_2,\cdots, \boldsymbol{p}_q\},每个原型向量代表一个聚类簇,簇标记t_i \in \gamma

       

过程:

  • 初始化一组原型向量\{\boldsymbol{p}_1,\boldsymbol{p}_2,\cdots, \boldsymbol{p}_q\}
  • 循环迭代(直到满足最大的运算轮数或调整幅度小于阈值)。
    • 随机选择一个样本,找出距离该样本最近的一个原型向量,依据两者类别的标记是否一致来更新原型向量

原型向量的更新:

  • 若最近的原型向量\boldsymbol{p}_i^*\boldsymbol{x}_j的类别标记相同,则令\boldsymbol{p}_i^*\boldsymbol{x}_j的方向靠拢,此时原型向量:
    •  \boldsymbol{p}'= \boldsymbol{p}_i^* + \eta \cdot (\boldsymbol{x}_j - \boldsymbol{p}_i^*)
    • \boldsymbol{p}_i^*\boldsymbol{x}_j之间的距离
    • \begin{aligned} ||\boldsymbol{p}' - \boldsymbol{x}_j||_2&=||\boldsymbol{p}_i^* + \eta \cdot (\boldsymbol{x}_j-\boldsymbol{p}_i^*) -\boldsymbol{x}_j||_2 \\ &=(1-\eta) \cdot ||\boldsymbol{p}_i^*-\boldsymbol{x}_j||_2 \end{aligned}
    • 令学习率\eta \in (0,1),则原型向量\boldsymbol{p}_i^*在更新为\boldsymbol{p}'之后将更接近\boldsymbol{x}_j
  • 若最近的原型向量\boldsymbol{p}_i^*\boldsymbol{x}_j的类别标记不同,则令\boldsymbol{p}_i^*远离\boldsymbol{x}_j的方向,此时原型向量:
    •  \boldsymbol{p}'= \boldsymbol{p}_i^* - \eta \cdot (\boldsymbol{x}_j - \boldsymbol{p}_i^*)
    • \boldsymbol{p}_i^*\boldsymbol{x}_j之间的距离
    • \begin{aligned} ||\boldsymbol{p}' - \boldsymbol{x}_j||_2&=(1+\eta) \cdot ||\boldsymbol{p}_i^*-\boldsymbol{x}_j||_2 \end{aligned}
    • 令学习率\eta \in (0,1),则原型向量\boldsymbol{p}_i^*在更新为\boldsymbol{p}'之后将更远离\boldsymbol{x}_j

高斯混合聚类

kmeans、LVQ用原型向量刻画聚类结构不同,高斯混合聚类采用概率模型来表达聚类原型。

多元高斯分布,对n维样本空间\chi中的随机向量\boldsymbol{x},若\boldsymbol{x}服从高斯分布,其概率密度函数:

        p(\boldsymbol{x})= \frac{1}{(2\pi )^{\frac{n}{2}}|\boldsymbol{\Sigma}|^{\frac{1}{2}}}e^{-\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^T\boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (2)

其中,\boldsymbol{\mu}谁n维均值向量,\boldsymbol{\Sigma}n \times n的协方差矩阵,高斯分布完全由均值向量\boldsymbol{\mu}和协方差矩阵\boldsymbol{\Sigma}这两个参数确定。为明确显示高斯分布与相应参数的依赖关系,将概率密度函数记为p(\boldsymbol{x}|\boldsymbol{\mu},\boldsymbol{\Sigma}),这边只是一个记号,并不少是条件概率的意思,等价于(2)式中的p(\boldsymbol{x})

定义高斯混合分布

      p_\textit{M}(\boldsymbol{x}) = \sum_{i=1}^k\alpha_i \cdot p(\boldsymbol{x}|\boldsymbol{\mu},\boldsymbol{\Sigma })

该分布共由k个混合成分组成,每个混合成分对应一个高斯分布,其中\boldsymbol{\mu}_i\boldsymbol{\Sigma }_i是第i个高斯混合成分的参数,而\alpha_i > 0为相应的“混合系数”,\sum_{i=1}^k \alpha_i=1

待续 

4、密度聚类

密度聚类亦称“基于密度的聚类”,此类算法假设聚类结构能通过样本分布的紧密程度确定。通常性情下,密度聚类算法从样本密度的角度来考察样本之间的可连接性,并基于可连接样本不断扩展聚类簇以获得最终的聚类结果

DBSCAN是一种著名的密度聚类算法,它基于一组“邻域”参数( \epsilon ,MinPts)来刻画样本分布的紧密程度。给定数据集D=\{\boldsymbol{x}_1,\boldsymbol{x}_2,\cdots, \boldsymbol{x}_m\},定义

  • \epsilon-邻域:对\boldsymbol{x}_j \in D,其\epsilon-邻域包含样本集D中与\boldsymbol{x}_j的距离不大于\epsilon的样本,即N_\epsilon(\boldsymbol{x}_j) = \{ \boldsymbol{x}_i \in D| dixt(\boldsymbol{x}_i,\boldsymbol{x}_j) \leqslant \epsilon\}
  • 核心对象:若\boldsymbol{x}_j\epsilon-邻域至少包含MinPts个样本,即|N_\epsilon(\boldsymbol{x})_j| \geqslant MinPts,则\boldsymbol{x}_j是一个核心对象
  • 密度直达:若\boldsymbol{x}_j位于\boldsymbol{x}_i\epsilon-邻域中,且\boldsymbol{x}_i是核心对象,则称\boldsymbol{x}_j\boldsymbol{x}_i密度直达(密度直达通常不满足对称性,因为x必须有核心对象直达)
  • 密度可达:对\boldsymbol{x}_i\boldsymbol{x}_j,若存在样本序列\boldsymbol{p}_1,\boldsymbol{p}_2,\cdots, \boldsymbol{p}_n,其中\boldsymbol{p}_1 = \boldsymbol{x}_i, \boldsymbol{p}_n = \boldsymbol{x}_j\boldsymbol{p}_{i+1}\boldsymbol{p}_{i}密度直达,则称\boldsymbol{x}_j\boldsymbol{x}_i密度可达(密度可达满足直递性,不一定满足对称性,且密度可达还借助了密度直达的概念)
  • 密度相连:对\boldsymbol{x}_i\boldsymbol{x}_j,若存在\boldsymbol{x}_k使得\boldsymbol{x}_i\boldsymbol{x}_j均由\boldsymbol{x}_k密度可达,则称\boldsymbol{x}_i\boldsymbol{x}_j密度相连(密度相连满足对称性)

基于这些概念,DBSCAN将“簇”定义为:由密度可达关系导出的最大的密度相连样本集合。形式化的说,给定邻域参数( \epsilon ,MinPts),簇C\subseteq D是满足以下性质的非空样本子集:

  • 连接性:\boldsymbol{x}_i \in C,\boldsymbol{x}_j \in C \Rightarrow \boldsymbol{x}_i\boldsymbol{x}_j密度相连
  • 最大性:\boldsymbol{x}_i \in C,\boldsymbol{x}_j\boldsymbol{x}_i密度可达\Rightarrow \boldsymbol{x}_j \in C

那么如何从D中找出满足以上性质的聚类簇呢?实际上,若\boldsymbol{x}为核心对象,由\boldsymbol{x}密度可达的所有样本组成的集合记为X = \{\boldsymbol{x}' \in D|\boldsymbol{x}'\boldsymbol{x}密度可达\},则不难证明X即为满足连接性和最大性的簇。

    

过程:

  • 找出样本中的所有核心对象集合\Omega
  • 初始化现有聚类簇数k=0,初始化为访问样本集合为整个样本\Gamma = D
  • 循环迭代直至满足条件(直到访问过所有的核心对象)
    • 随机选取一个核心对象并入队
      • 当队列不为空,弹出队首元素,并寻找其\epsilon-邻域中所有未访问过的样本\bigtriangleup,若其中有核心对象就入队并且更新未访问过的样本\Gamma(将\bigtriangleup从为访问过的样本中剔除)
    • 生成第k个聚类簇(两个集合的差集即使聚类簇,\Gamma_{old}上一轮未访问样本集合,\Gamma这一轮未访问样本集合),更新现有聚类簇数k=k+1
    • 更新未访问过的核心对象集合\Omega(将已访问的剔除)

5、层次聚类

层次聚类试图在不同层次对数据集进行划分,从而形成树形的聚类结构,数据集的划分可采用“自底向上”的聚合策略,也可采用“自顶向下”的分拆策略。即凝聚式层次聚类:在初始阶段将每个店都视为一个簇,之后合并两个最接近的簇,直到最后只剩一个簇;分裂式层次聚类:在初始阶段将所有点视为一个点,之后每分裂出一个簇,直到最后剩下单个点的簇。

AGNES是一种采用自底向上聚合策略的层次聚类算法,他先将数据集中的每个样本看做一个初始聚类簇,然后在算法运行的每一步找出距离最近的两个聚类簇进行合并,该过程不断重复,直到达到预设的聚类簇个数。这里关键是素和计算聚类簇之间得距离,实际上,每个簇是一个样本集合,因此,只需要采用关于集合的某种距离即可。

         

     

⚠️:感觉图中17和18行之间缺少了一个外层循环,距离矩阵更新应该两层循环,然后还有既然用了对称性,内层循环j只要从i+1开始就可以了。

过程:

  • 初始化,将每个样本当作一个初始聚类簇,并对相应的距离矩阵进行初始化
  • 循环迭代直至满足退出条件(达到预设的聚类簇数)
    • 找出距离最近的两个聚类簇C_{i^*}C_{j^*},将这两个聚类簇合并成C_{i^*}
    • 因为少了一个聚类簇,故距离矩阵需要删掉第j^*行和第j^*列,并且将聚类簇的编号重编(j^*之后的因为删掉了j^*,所以均往前挪一个)
    • 对现在的q-1个聚类簇,重新计算距离矩阵。

 

 

 

 

 

 

参考:机器学习,周志华

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值