Kmeans算法

1. 聚类算法族谱

这篇文章主要介绍了:聚类算法的特点,样本间距离,簇之间距离的计算方法以及衡量聚类算法性能的算法。

2. Kmeans算法简介

KMeans算法是一种动态聚类方法,其基本思路为:

  1. 先粗略地进行预分类;
  2. 然后再逐步调整;
  3. 直到把类分得比较合理为止。

这种分类方法较之系统聚类法,具有计算量较小、占用计算机存贮单元少、方法简单等优点,所以更适用于大样本的聚类分析。 该算法常用于向量量化图像分隔等领域。

2.1 数据准备

假设有样本集数据 D = { x 1 , x 2 , . . . , x m } D=\{\boldsymbol{x_1}, \boldsymbol{x_2},...,\boldsymbol{x_m}\} D={x1,x2,...,xm},每个样本数据有 d d d个维度的向量。

2.2 评判标准

如果 ∣ C i ∣ |C_i| Ci是第 i i i聚类 C i C_i Ci的样本数量,则该类样本均值( μ i \boldsymbol{\mu_i} μi)有
μ i = 1 ∣ C i ∣ ∑ x ∈ C i x (2.1) \boldsymbol{\mu_i} = \frac{1}{|C_i|}\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x} \tag{2.1} μi=Ci1xCix(2.1)
在整个数据集上的误差(误差平方和)有:
E = ∑ i = 1 C ∑ x ∈ C i ( x − μ i ) (2.2) E = \sum_{i=1}^C\sum_{\boldsymbol{x} \in C_i}(\boldsymbol{x}-\boldsymbol{\mu_i}) \tag{2.2} E=i=1CxCi(xμi)(2.2)

2.3 求解思路

如果把某一样本 x ′ \boldsymbol{x'} x C i C_i Ci类移动到 C j C_j Cj类,则仅仅这两类发生了变化,而其他类不发生变化,所以,经过调整后, C i C_i Ci类移动前的均值( μ i \boldsymbol{\mu_i} μi)与移动后的均值( μ i ′ \boldsymbol{\mu_i'} μi)均值的变化如下所示:
{ μ i ′ = 1 ∣ C i ∣ − 1 ( ∑ x ∈ C i x − x ′ ) μ i = 1 ∣ C i ∣ ( ∑ x ∈ C i x ) (2.3) \begin{aligned} \left\{\begin{matrix} \boldsymbol{\mu_i'}=&\frac{1}{|C_i|-1}(\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}-\boldsymbol{x}') \\ \boldsymbol{\mu_i}=&\frac{1}{|C_i|}(\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}) \end{matrix}\right. \tag{2.3} \end{aligned} {μi=μi=Ci11(xCixx)Ci1(xCix)(2.3)
μ i ′ − μ i \boldsymbol{\mu_i'}-\boldsymbol{\mu_i} μiμi可得:
μ i ′ − μ i = ∑ x ∈ C i x − x ′ ∣ C i ∣ − 1 − ∑ x ∈ C i x ∣ C i ∣ = ∣ C i ∣ ∑ x ∈ C i x − ∣ C i ∣ x ′ − ∣ C i ∣ ∑ x ∈ C i x + ∑ x ∈ C i x ( ∣ C i ∣ − 1 ) ∣ C i ∣ = − ∣ C i ∣ x ′ + ∑ x ∈ C i x ( ∣ C i ∣ − 1 ) ∣ C i ∣ = − x ′ + μ i ∣ C i ∣ − 1 μ i ′ = μ i + − x ′ + μ i ∣ C i ∣ − 1 (2.4) \begin{aligned} \boldsymbol{\mu_i'}-\boldsymbol{\mu_i}=&\frac{\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}-\boldsymbol{x}'}{|C_i|-1} - \frac{\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}}{|C_i|} \\ =&\frac{|C_i|\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}-|C_i|\boldsymbol{x'}-|C_i|\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}+\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}}{(|C_i|-1)|C_i|}\\ =&\frac{-|C_i|\boldsymbol{x'}+\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}}{(|C_i|-1)|C_i|}\\ =&\frac{-\boldsymbol{x'}+\boldsymbol{\mu_i}}{|C_i|-1}\\ \boldsymbol{\mu_i'}=&\boldsymbol{\mu_i}+\frac{-\boldsymbol{x'}+\boldsymbol{\mu_i}}{|C_i|-1}\\ \tag{2.4} \end{aligned} μiμi====μi=Ci1xCixxCixCix(Ci1)CiCixCixCixCixCix+xCix(Ci1)CiCix+xCixCi1x+μiμi+Ci1x+μi(2.4)
同理,对于 C j C_j Cj类均值则变化为:
μ j ′ = μ j + x ′ − μ j ∣ C j ∣ + 1 (2.5) \begin{aligned} \boldsymbol{\mu_j'}=&\boldsymbol{\mu_j}+\frac{\boldsymbol{x'}-\boldsymbol{\mu_j}}{|C_j|+1} \tag{2.5} \end{aligned} μj=μj+Cj+1xμj(2.5)
两类各自的平方和发生的变化如下:
{ E i = ∑ x ∈ C i ∣ ∣ x − μ i ∣ ∣ 2 E i ′ = ∑ x ∈ C i ∣ ∣ x − μ i ′ ∣ ∣ 2 − ∣ ∣ x ′ − μ i ′ ∣ ∣ 2 (2.6) \begin{aligned} \left\{\begin{matrix} E_i=&\sum_{\boldsymbol{x} \in C_i}||\boldsymbol{x}-\boldsymbol{\mu_i}||^2 \\ E_i'=&\sum_{\boldsymbol{x} \in C_i}||\boldsymbol{x}-\boldsymbol{\mu_i'}||^2-||\boldsymbol{x'}-\boldsymbol{\mu_i'}||^2 \end{matrix}\right. \tag{2.6} \end{aligned} {Ei=Ei=xCixμi2xCixμi2xμi2(2.6)
得出
E i ′ = E i − ∣ C i ∣ ∣ C i ∣ − 1 ∣ ∣ x ′ − μ i ∣ ∣ 2 (2.7) \begin{aligned} E_i'=&E_i-\frac{|C_i|}{|C_i|-1}||\boldsymbol{x'}-\boldsymbol{\mu_i}||^2 \tag{2.7} \end{aligned} Ei=EiCi1Cixμi2(2.7)
同理
E j ′ = E j + ∣ C j ∣ ∣ C j ∣ + 1 ∣ ∣ x ′ − μ j ∣ ∣ 2 (2.8) \begin{aligned} E_j'=&E_j+\frac{|C_j|}{|C_j|+1}||\boldsymbol{x'}-\boldsymbol{\mu_j}||^2 \tag{2.8} \end{aligned} Ej=Ej+Cj+1Cjxμj2(2.8)
如果在调整的过程中, E i ′ > E j ′ E_i'>E_j' Ei>Ej,则说明该步骤有 利于减小整体误差。


3. 算法描述

3.1 算法流程

  • 初始化 k k k个聚类,计算各个类的均值( μ i , i = 1 , 2 , . . . , C \mu_i,i=1,2,...,C μi,i=1,2,...,C)以及总体的误差平方和( J c J_c Jc)。
  • 随机取一个样本 x ′ \boldsymbol{x'} x,其对应的分类为 C i ′ C_i' Ci
    • 如果
      • D   ∣ C i ′ ∣ = = 1 D\ |C_i'|==1 D Ci==1:返回上一步,重新选择样本;
    • 否则
      • 计算将 x ′ \boldsymbol{x'} x移出第 i i i类误差平方和的变化量 ρ i \rho_i ρi
               ρ i = ∣ C i ∣ ∣ C j − 1 ∣ ∣ ∣ x ′ − μ i ∣ ∣ 2 \rho_i = \frac{|C_i|}{|C_j-1|}||\boldsymbol{x'}-\boldsymbol{\mu_i}||^2 ρi=Cj1Cixμi2
      • 从所有分类中找出将样本 x ′ \boldsymbol{x'} x移入第 j j j类,误差最小的误差平方和 ρ j \rho_j ρj
               ρ j = ∣ C j ∣ ∣ C j + 1 ∣ ∣ ∣ x ′ − μ j ∣ ∣ 2 \rho_j = \frac{|C_j|}{|C_j+1|}||\boldsymbol{x'}-\boldsymbol{\mu_j}||^2 ρj=Cj+1Cjxμj2
      • 如果 ρ i > ρ j \rho_i > \rho_j ρi>ρj(移出成本大于移入成本):则把 x ′ \boldsymbol{x'} x移入到第 j j j类中。
      • 重新计算发生变化两类的均值以及总体误差平方和
  • 若连续 N N N总体误差平方和不改变,则停止,否则转向步骤2。

算法流程1中初始化 k k k个聚类的步骤

选择代表向量
  1. 经验选择法
  2. 所有样本随机分成 C C C类,计算每类重心。
  3. “密度”法选择
  4. 随机选择 C C C个样本作为代表向量
  5. ( c − 1 ) (c-1) (c1)聚类划分问题的解中产生 c c c聚类划分问题的代表点。思路如下:
    • 先把整个样本空间看成一个类,计算均值;
    • 选择最远的点作为第二类的代表向量;
    • 再计算 ∣ C ∣ − 1 |C|-1 C1个点的均值,依次类推选择第三类的代表向量。

一般选择方法2方法4

初始类划分
  1. 其余点离哪个代表点近则归为该类。
  2. 其余点离哪个代表点近则归为该类,然后重新计算该类的均值,直到归类完毕。
  3. 现将数据标准化,用 y i j y_{ij} yij表示标准化后第 i i i个样本的第 j j j个特征,令
    S U M ( i ) = ∑ j = 1 d y i j (3.1-1) SUM(i)=\sum_{j=1}^dy_{ij} \tag{3.1-1} SUM(i)=j=1dyij(3.1-1)
    M A = max ⁡ i S U M ( i ) (3.1-2) MA=\max_i SUM(i) \tag{3.1-2} MA=imaxSUM(i)(3.1-2)
    M I = min ⁡ i S U M ( i ) (3.1-3) MI=\min_i SUM(i) \tag{3.1-3} MI=iminSUM(i)(3.1-3)
    若将样本分为 C C C类,则对每个样本计算
    ( C − 1 ) [ S U M ( i ) − M I ] ( M A − M I ) + 1 \frac{(C-1)[SUM(i)-MI]}{(MA-MI)}+1 (MAMI)(C1)[SUM(i)MI]+1
    该值最接近的整数 k k k,则将第 i i i个样本归为第 k k k类。

4. 代码实现

代码地址
KMeans类实现

class KMeans(object):
    def __init__(self, cluster_num=5):
        '''
        初始化各簇的均值向量
        :param cluster_num: 簇的数量
        '''
        self.cluster_num = cluster_num
        self.nums = 0
        self.features = 0
        self.cluster = {}
        for i in range(self.cluster_num):
            self.cluster[i] = {
                'mean_vec': None,
                'data': None
            }

    def __initArgs__(self, data):
        '''
        随机选择cluster_num个样本值作为初始化的均值向量
        :param data: 数据集
        :return:
        '''
        self.nums, self.features = data.shape
        index = np.random.randint(1, self.nums, size=self.cluster_num)
        for key, value in enumerate(index):
            self.cluster[key]['mean_vec'] = data[value]
            self.cluster[key]['data'] = np.array([data[value]])

    def fit(self, data, iteration):
        # 1. 随机选取 cluster_num 个向量作为簇均值
        self.__initArgs__(data)
        for iters in range(iteration):
            # 2. 遍历样本空间,将每个样本进行归类
            for idata in data:
                min_dist = float(np.inf)
                cluster_name = 0
                # 选取一个最近的簇
                for icluster in self.cluster.keys():
                    dist = DistMinkov(self.cluster[icluster]['mean_vec'], idata, p=2)
                    if min_dist >= dist:
                        min_dist = dist
                        cluster_name = icluster
                # 将该条数据添加至最近的簇中
                self.cluster[cluster_name]['data'] = np.r_[
                    self.cluster[cluster_name]['data'],
                    np.array([idata])
                ]
            # 3. 更新簇均值向量
            stop = True
            for icluster in self.cluster.keys():
                # 计算新的簇心均值
                new_mean_vec = np.mean(
                    self.cluster[icluster]['data'], axis=0
                )
                # 重置数据
                self.cluster[icluster]['data'] = np.array([new_mean_vec])
                if ~(self.cluster[icluster]['mean_vec'] == new_mean_vec).all():
                    self.cluster[icluster]['mean_vec'] = new_mean_vec
                    stop = False
            if stop == True:
                break
        return self.cluster

读取数据

def load_data(filename):
    '''
    从文件中读取数据
    :param self:
    :param filename: 文件路径和文件名称
    :return: 返回读取的数据
    '''
    data = pd.read_csv(
        filename,
        sep='\t',
        header=None
    )
    return data

主函数调用

if __name__ == '__main__':
	import numpy as np
	import pandas as pd
	from Cluster.DistanceClass import DistMinkov
    filename = 'data/testSet.txt'
    data = load_data(filename)
    clf = KMeans(4)

    cluster = clf.fit(data.values, 100)
    for i in cluster.keys():
        print(cluster[i]['mean_vec'])

5. KMeans缺陷以及改进方法

5.1 缺陷

  1. 容易收敛到局部极大值。
  2. 分类的数量不易指定。

5.2 改进方法

5.2.1 针对分类的数量问题

5.2.1.1 合并簇

簇的数量越多,说明其分类种类越多,簇内部数据点也越接近其质心。因此,可以通过以下思路提高聚类效果:
预先生成多个簇,然后将相近的簇进行合并。

合并的标准为:

  1. 各簇簇心之间的距离小于某一正数。
  2. 合并两个簇,计算其合并后簇内平均距离,若合并后的簇内平均距离相比于合并前的距离增幅最小,则代表两个簇可合并。
5.2.1.2 拆分簇

在簇内平均距离最大的簇使用KMeans算法,指定簇的数量为2,然后再调用合并簇算法。

5.2.2 针对容易收敛到局部极大值的问题

采用二分KMeans算法,具体算法流程如下:

  • 输入数据集 D = { x 1 , x 2 , . . . , x N } D=\{x_1, x_2,..., x_N\} D={x1,x2,...,xN}
  • 初始化簇集合为 C = { C 1 } C=\{C_1\} C={C1}
  • 当集合 C C C的数量少于指定数量时:
    • 计算簇集合内每个簇的平均距离,记为 a v g ( D i ) avg(D_i) avg(Di)
    • 将每一个簇都使用KMeans算法,指定 K = 2 K=2 K=2,记划分后的两个簇为 D i 1 , D i 2 D_{i1}, D_{i2} Di1,Di2
    • 计算 D i 1 , D i 2 D_{i1}, D_{i2} Di1,Di2平均距离之和 a v g ( D i 1 ) + a v g ( D i 2 ) avg(D_{i1})+avg(D_{i2}) avg(Di1)+avg(Di2)
      • 如果: a v g ( D i 1 ) + a v g ( D i 2 ) < a v g ( D i ) avg(D_{i1})+avg(D_{i2}) < avg(D_i) avg(Di1)+avg(Di2)<avg(Di)
        • 划分成功,将 D i 1 , D i 2 D_{i1}, D_{i2} Di1,Di2对应的簇标签加入簇集合 C C C
      • 否则:
        • 不划分。
    • 如果簇集合 C C C的数量达到指定数量:
      • 返回划分结果
    • 否则:
      • 重复上述操作。

6. 参考文献

  • 《机器学习实战》
  • 《西瓜书》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值