一.二分KMeans:    

        算法的主要思想是:首先将所有点作为一个簇,然后将该簇一分为二。之后选择能最大限度降低聚类代价函数(也就是误差平方和)的簇划分为两个簇。以此进行下去,直到簇的数目等于用户给定的数目k为止。以上隐含的一个原则就是:因为聚类的误差平方和能够衡量聚类性能,该值越小表示数据点越接近于他们的质心,聚类效果就越好。所以我们就需要对误差平方和最大的簇进行再一次划分,因为误差平方和越大,表示该簇聚类效果越不好,越有可能是多个簇被当成了一个簇,所以我们首先需要对这个簇进行划分。


伪代码:

初始化簇表,使之包含由所有的点组成的簇。
repeat
   {对选定的簇进行多次二分试验}
   for i=1 to 试验次数 do
       使用基本k均值,二分选定的簇。
   endfor
   从二分试验中选择具有最小误差的两个簇。
   将这两个簇添加到簇表中。
until 簇表中包含k个簇


1.k值与整体误差平方和的关系:

    k值越大,划分越细,(x-x1)越小,则整体误差平方和越小;

    所以,k值固定时,“误差平方和”可以量化聚类效果;

    轮廓系数可以比较不同k值下划分效果,ai表示簇内距离,bi表示簇外距离,

    一般情况下,k值增大(=2,3,4,5...)会使轮廓系数,先增大,后减小,慢慢接近0;不同情况下,临界点不同;

2.二分kmeans算法过程中,可以k=1,2,3....的结果,

所以,当你不知道分成几个簇的时候,可以使用二分kmeans算法;

算法意图在于得到k=2,3...时,整体SSE(簇内误差平方和)最小的结果;


3.每次选择要划分的簇时,简单的操作:(1)选“SSE最大的簇“(2)选簇内点最多的簇,然后原始的K-means,一分为二;

复杂有目的性的操作:对所有簇尝试原始K-Means划分,然后选使k=k(前)+1时整体SSE最小的那个簇,直接保留划分结果;

讨论:选SSE最大簇就是使得划分后整体SSE最小的簇;

    (这个可以在实验中观察)

    实验结论:SSE最大簇就是使得划分后整体SSE最小的簇,只有0.05左右概率出现意外,而且是分割的数据集较小的时候;

        本人认为可能是二分时使用k-means的随机选初始点的影响;

        所以,用“选SSE最大簇”代替“选使整体SSE最小的簇”,时间复杂度减少很多;


4.SSE(簇内误差平方和)的因素有:(1)数量  (2)点的松散程度

    所以我觉得用3中简单操作(2)不太靠谱,有的分类可能数量虽多,分布却很紧密;


5.时间复杂度分析:

    k-means:D*K*N ;D为迭代次数

    基于“使整体SSE最小”两种方式的二分K均值:每次二分都需要D*2*N/x*x , x是暂时分割的簇数;经过k-1次,计算得:

        D*2*(k-1)*N;  但是数据集分割得越来越小,所以每次的D越来越小,所以大约为k-means时间的1.5倍左右;


基于“使整体SSE最小”两种方式的二分K均值,如下代码所示:


from numpy import *
from pandas import *


# 计算欧几里得距离
def distEclud(vecA, vecB):
     return sqrt(sum(power(vecA - vecB, 2))) # 求两个向量之间的距离


def testBestSplit(cents,ClustDist):
    max = 0
    i = 0
    for x in cents:
        a = ClustDist[ClustDist[:, 0]== x][:,1]
        lossSum = sum(multiply(a,a))
        if lossSum>max:
            max = lossSum
            i = x
    return i
    
def Two_Kmeans(dataSet, k):
    m = shape(dataSet)[0]  


    # 初始化第一个聚类中心: 每一列的均值
    centroid0 = mean(dataSet, axis=0).tolist()
    cents = [0] #簇的index列表
    centList =[centroid0] # 把均值聚类中心加入中心表中
    # 初始化聚类距离表,距离方差
    # 列1:数据集对应的聚类中心,列2:数据集行向量到聚类中心距离的平方
    ClustDist = zeros((m, 2))
    for j in range(m):
        ClustDist[j,1] = distEclud(centroid0,dataSet[j,:])**2


    # 依次生成k个聚类中心
    while (len(centList) < k):
        lowestSSE = inf 
        
        bestCentToSplit = 0
        
        bestClustAss = []
        
        #最优分割
        for i in cents:
            #当前簇dataSet
            ptsInCurrCluster = dataSet[ClustDist[:, 0]== i]
            #print(len(ptsInCurrCluster[:, 0]))
            #划分
            centroidMat, splitClustAss = my_kMeans(ptsInCurrCluster, 2)  #上一章
            centroidMat = centroidMat.getA()
            #当前簇划分后的误差平方和
            sseSplit = sum(multiply(splitClustAss[:, 1], splitClustAss[:, 1]))  
            #没划分的簇的误差平方和
            ClustDist_nosplit = ClustDist[ClustDist[:, 0]!= i]
            sseNotSplit = sum(multiply(ClustDist_nosplit[:,1],ClustDist_nosplit[:,1]))
            
            if (sseSplit + sseNotSplit) < lowestSSE: 
                lowestSSE = sseSplit + sseNotSplit 
                bestCentToSplit = i                 # 确定聚类中心的最优分隔点
                #bestNewCents = centroidMat          # 用新的聚类中心更新最优聚类中心
                bestClustAss = splitClustAss.copy() # 深拷贝聚类距离表为最优聚类距离表
        
        #检测一下:
        testi = testBestSplit(cents,ClustDist)
        print(testi==bestCentToSplit)
        #验证了:SSE最大簇就是使得划分后整体SSE最小的簇(在绝大部分情况下)
        
        
        #保留分割结果:
        length = len(cents) 
        cents.insert(cents.index(bestCentToSplit)+1,length)
        centList[bestCentToSplit] = centroidMat[0].tolist()
        centList.append(centroidMat[1].tolist())
        
        for x in bestClustAss:
            if x[0] == 0:
                x[0] = bestCentToSplit
            else:
                x[0] = length
        ClustDist[ClustDist[:, 0]==bestCentToSplit] = bestClustAss


        
        
    #k个簇后:
    return centList,ClustDist


二.K-menas++:

为了解决因为初始化的问题带来K-Means算法的问题,改进的K-Means算法,即K-Means++算法被提出,K-Means++算法主要是为了能够在聚类中心的选择过程中选择较优的聚类中心。

即每次选下一个中心点的策略,使得它离那些确定下来的中心点都比较远;

算法流程:

二分的kmeans、Kmeans++、

算法的缺点:这K个点是迭代产生,第M个点的好坏依赖前M-1个点;而且第一个点是随机产生的;

            这K个点相对分散,比原始的k-means好很多,但也不是完美的;


Python实现:(初始化中心点)

        轮盘法:

        举例说明,三个事件概率分别为A:3/10,B:2/10,C:5/10,现取[0,10]之间随机浮点数,是均匀分布;

            当取到[0,3]时,为A事件,取到[3,5]时,为B事件,取到[5,10]时,为C事件;符合各事件概率;

            于是,让这个浮点数依次减3,2,5,结果为负数,即为该事件;

        此法称为轮盘法;

import numpy as np
from random import random

FLOAT_MAX = 1e100 # 设置一个较大的值作为初始化的最小的距离

# 计算欧几里得距离
def distEclud(vecA, vecB):
     return sqrt(sum(power(vecA - vecB, 2))) # 求两个向量之间的距离

#返回欧几里得距离的平方
def nearest(point, cluster_centers):
    min_dist = FLOAT_MAX
    m = np.shape(cluster_centers)[0]  #当前已经初始化的聚类中心的个数
    for i in range(m):
        # 计算point与每个聚类中心之间的距离
        d = distEclud(point, cluster_centers[i])**2
        # 选择最短距离
        if min_dist > d:
            min_dist = d
    return min_dist

def get_centroids(points, k):
    m, n = np.shape(points)
    cluster_centers = np.mat(np.zeros((k , n)))
    # 1、随机选择一个样本点为第一个聚类中心
    index = np.random.randint(0, m)
    cluster_centers[0] = np.copy(points[index])
    # 2、初始化一个距离的序列
    d = [0.0 for _ in range(m)]

    for i in range(1, k):
        sum_all = 0
        for j in range(m):
            # 3、对每一个样本找到最近的聚类中心点
            d[j] = nearest(points[j], cluster_centers[0:i])
            # 4、将所有的最短距离相加
            sum_all += d[j]
        # 5、取得sum_all之间的随机值
        sum_all *= random.random()
        # 6、获得距离最远的样本点作为聚类中心点
        for j, di in enumerate(d):
            sum_all -= di
            if sum_all > 0:
                continue
            cluster_centers[i] = np.copy(points[j])
            break
    return cluster_centers


from numpy import *
from pandas import *


# 计算欧几里得距离
def distEclud(vecA, vecB):
     return sqrt(sum(power(vecA - vecB, 2))) # 求两个向量之间的距离


def testBestSplit(cents,ClustDist):
    max = 0
    i = 0
    for x in cents:
        a = ClustDist[ClustDist[:, 0]== x][:,1]
        lossSum = sum(multiply(a,a))
        if lossSum>max:
            max = lossSum
            i = x
    return i
    
def Two_Kmeans(dataSet, k):
    m = shape(dataSet)[0]  


    # 初始化第一个聚类中心: 每一列的均值
    centroid0 = mean(dataSet, axis=0).tolist()
    cents = [0] #簇的index列表
    centList =[centroid0] # 把均值聚类中心加入中心表中
    # 初始化聚类距离表,距离方差
    # 列1:数据集对应的聚类中心,列2:数据集行向量到聚类中心距离的平方
    ClustDist = zeros((m, 2))
    for j in range(m):
        ClustDist[j,1] = distEclud(centroid0,dataSet[j,:])**2


    # 依次生成k个聚类中心
    while (len(centList) < k):
        lowestSSE = inf 
        
        bestCentToSplit = 0
        
        bestClustAss = []
        
        #最优分割
        for i in cents:
            #当前簇dataSet
            ptsInCurrCluster = dataSet[ClustDist[:, 0]== i]
            #print(len(ptsInCurrCluster[:, 0]))
            #划分
            centroidMat, splitClustAss = my_kMeans(ptsInCurrCluster, 2)  #上一章
            centroidMat = centroidMat.getA()
            #当前簇划分后的误差平方和
            sseSplit = sum(multiply(splitClustAss[:, 1], splitClustAss[:, 1]))  
            #没划分的簇的误差平方和
            ClustDist_nosplit = ClustDist[ClustDist[:, 0]!= i]
            sseNotSplit = sum(multiply(ClustDist_nosplit[:,1],ClustDist_nosplit[:,1]))
            
            if (sseSplit + sseNotSplit) < lowestSSE: 
                lowestSSE = sseSplit + sseNotSplit 
                bestCentToSplit = i                 # 确定聚类中心的最优分隔点
                #bestNewCents = centroidMat          # 用新的聚类中心更新最优聚类中心
                bestClustAss = splitClustAss.copy() # 深拷贝聚类距离表为最优聚类距离表
        
        #检测一下:
        testi = testBestSplit(cents,ClustDist)
        print(testi==bestCentToSplit)
        #验证了:SSE最大簇就是使得划分后整体SSE最小的簇(在绝大部分情况下)
        
        
        #保留分割结果:
        length = len(cents) 
        cents.insert(cents.index(bestCentToSplit)+1,length)
        centList[bestCentToSplit] = centroidMat[0].tolist()
        centList.append(centroidMat[1].tolist())
        
        for x in bestClustAss:
            if x[0] == 0:
                x[0] = bestCentToSplit
            else:
                x[0] = length
        ClustDist[ClustDist[:, 0]==bestCentToSplit] = bestClustAss


        
        
    #k个簇后:
    return centList,ClustDist

相关文章: