为什么二分K-均值比K-均值的聚类效果更好?

上一篇文章《K-均值算法的原理与实战》,讲到K均值算法只能收敛到局部最小值的问题,本文给大家带来了克服这个问题的二分K均值算法。

  K-均值算法因为受到初始簇质心的影响,可能只收敛到局部最小值,而非全局最小值。比如下图,使用K-均值算法划分为3个簇,预期的聚类结果应该是上方2个簇,下方1个簇。但因为初始化簇质心是随机选择的点,其中的2个质心被分配到下方的1个簇中,导致聚类结果并非最好的结果。
在这里插入图片描述
  二分K-均值(bisecting K-means)算法是对K-均值算法的改进,它可以很好的克服对初始簇质心比较敏感的问题。二分K-均值算法通过不断的使用2-means划分簇,保证每次分裂聚类效果最差的簇,克服初始化簇质心带来的问题,下面将详细讲解它的原理。

算法原理

  二分K-均值算法首先将所有点组成一个簇,然后使用k-means(k=2)算法将该簇分裂为两个簇。之后选择其中一个簇继续划分,选择哪一个簇划分取决于划分后是否可以最大程度的降低SSE(误差平方和)。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目 k k k为止。该过程的伪代码如下:


输入: 样本集 D D D = { x 1 , x 2 , . . . , x m x_1,x_2,...,x_m x1,x2,...,xm};
       聚类簇数 k k k
过程:

  1. D D D中所有样本组成初始簇 C = { C 1 } C=\{C_1\} C={C1}
  2. repeat
  3.   初始化最小误差为正无穷: S S E m i n = i n f SSE_{min}=inf SSEmin=inf
  4.   for i = 1 , 2 , . . . , ∣ C ∣ i = 1,2,...,|C| i=1,2,...,C do
  5.     在给定的簇 C i C_i Ci上面进行k=2的K-均值切分,得到两个新簇: C i a C_{i_a} Cia C i b C_{i_b} Cib
  6.     计算将该簇 C i C_i Ci一分为二之后的总误差:
           S S E s u m = ∑ 1 ≤ j ≤ ∣ C ∣ , j ≠ i S S E C j + S S E C i a + S S E C i b SSE_{sum}=\sum_{1\leq j\leq {|C|},j \neq i} SSE_{C_j}+SSE_{C_{i_a}}+SSE_{C_{i_b}} SSEsum=1jC,j=iSSECj+SSECia+SSECib
  7.     if S S E s u m < S S E m i n SSE_{sum} < SSE_{min} SSEsum<SSEmin then
  8.       更新当前最小误差: S S E m i n = S S E s u m SSE_{min}=SSE_{sum} SSEmin=SSEsum
  9.       指定划分簇标记: λ s p l i t = i \lambda_{split}=i λsplit=i
  10.       划分后的两个新簇: C λ a = C i a C_{\lambda_a}=C_{i_a} Cλa=Cia C λ b = C i b C_{\lambda_b}=C_{i_b} Cλb=Cib
  11.   end for
  12.   更新被划分的簇为划分后的一个簇: C λ s p l i t = C λ a C_{\lambda_{split}}=C_{\lambda_a} Cλsplit=Cλa
  13.   将划分后的另一个簇并入簇集合: C = C ⋃ { C λ b } C=C\bigcup \{C_{\lambda_b}\} C=C{Cλb}
  14. until 得到用户指定的簇数目 k k k

输出: 簇划分 C = { C 1 , C 2 , . . . , C k } C = \{C_1,C_2,...,C_k\} C={C1,C2,...,Ck}


编程实战

  首先从百度网盘下载测试数据集,下载链接:https://pan.baidu.com/s/1pOQ9qvWrYE4J3Kf-498HIw ,提取码:x8wd
。然后复制上篇文章《K-均值算法的原理与实战》的代码和本文中代码,粘贴到Jupyter Notebook中运行。

#二分K-均值算法
def biKMeans(dataSet, k, distMeas=distED):
    m = np.shape(dataSet)[0] 
    clusterAssment = np.mat(np.zeros((m,2)))  # 记录点的簇分配结果
    centroid0 = np.mean(dataSet, axis=0).tolist()[0] #创建一个初始簇
    centList=[centroid0]  #保存所有质心的列表
    for j in range(m):
        clusterAssment[j,1] = distMeas(np.mat(centroid0),dataSet[j,:])**2
    # 重复分裂簇,直到得到想要的簇数目
    while (len(centList) < k):
        lowestSSE = np.inf #初始化误差平方和(Sum of Squared Error),置为无穷大
        #尝试划分已有的每一个簇,寻找划分后使SSE最小的那一个簇,然后对其进行2-Means聚类划分。
        for i in range(len(centList)): 
            ptsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0].A==i)[0],:] #当前簇i下所有点
            centroidMat,splitClustAss=kMeans(ptsInCurrCluster, 2, distMeas)    #对簇i进行2-kmeans聚类划分
            sseSplit=np.sum(splitClustAss[:,1])    #当前簇i划分后的SSE值
            sseNotSplit=np.sum(clusterAssment[np.nonzero(clusterAssment[:,0].A!=i)[0],1]) #其他簇的SSE值
            # 选取簇划分后可以使总SSE值最小的簇
            if(sseSplit + sseNotSplit) < lowestSSE:
                bestCentToSplit = i
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit
        bestClustAss[np.nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)  #更新划分后的一个簇标记
        bestClustAss[np.nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit #更新划分后的另一个簇标记
        centList[bestCentToSplit] = bestNewCents[0,:].A[0]  #更新被划分簇的质心
        centList.append(bestNewCents[1,:].A[0])     #新增划分出的新簇质心
        clusterAssment[np.nonzero(clusterAssment[:,0].A==bestCentToSplit)[0],:] = bestClustAss #更新簇的分配结果
    return np.mat(centList), clusterAssment

#绘制数据点与质心
def drawPic(dataMat,centroids,clusterAssment):
    type1_x=[];type1_y=[];type2_x=[];type2_y=[];type3_x=[];type3_y=[]
    for i in range(datMat3.shape[0]):
        if myClusterAssments[i,0]==0:
            type1_x.append(datMat3[i,0])
            type1_y.append(datMat3[i,1])
        elif myClusterAssments[i,0]==1:
            type2_x.append(datMat3[i,0])
            type2_y.append(datMat3[i,1])
        elif myClusterAssments[i,0]==2:
            type3_x.append(datMat3[i,0])
            type3_y.append(datMat3[i,1])

    cent_x=myCentroids[:,0].T.tolist()[0]
    cent_y=myCentroids[:,1].T.tolist()[0]
    p1 = plt.scatter(type1_x,type1_y,s=30,marker='x')
    p2 = plt.scatter(type2_x,type2_y,s=30,marker='x')
    p3 = plt.scatter(type3_x,type3_y,s=30,marker='x')
    cent = plt.scatter(cent_x,cent_y,s=200,marker='o')
    plt.legend(["p1","p2","p3",'cent'],bbox_to_anchor=(1, 1))
    plt.show()

#加载数据集
datMat3=np.mat(loadDataSet('biKMeansTestSet.txt'))
#生成簇质心,簇分配结果
myCentroids, myClusterAssments=biKMeans(datMat3,3)
#绘制聚类结果图
drawPic(datMat3,myCentroids,myClusterAssments)

  最终的聚类结果如下图所示,其中p1-p3为划分为3种类别的簇,cent为簇质心。
在这里插入图片描述

结语

  二分K-均值算法是一种层次聚类方法,其实质就是在满足最小SSE(误差平方和)的情况下,不断的对选中的簇做k=2的k-means切分,直到聚类数等于用户指定的聚类数目k为止。
  我们使用SSE(误差平方和)衡量聚类效果的好坏,SSE越小,则数据点越接近它们的簇质心,聚类效果就越好。二分K均值算法选择要划分的簇,是可以最大程度降低SSE的值的簇,这意味着该簇的聚类效果不好,把多个簇当成一个簇了。所以应该选择该簇进行划分,如此可以收敛到全局最小值。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值