k-Means聚类

1.基本原理

k-均值聚类实在给定数据集的k个簇的算法。簇的个数是由用户自己定义的,每个簇通过其质心,即簇中所有点的中心。具体工作流程如下:

首先随机确定k个初始点作为质心。然后将数据集中每个点分配到一个簇中,即选择距离该点最近簇中,若簇中有新加入的数据点,则从新计算簇中心。

优点:容易实现

缺点:可能收敛到局部最小值,在大规模数据集中收敛较慢

 2.算法实现:

 1 def loadDataSet(filename):
 2     dataMat=[]
 3     fr=open(filename)
 4     for line in fr.readlines():
 5         curline=line.strip().split('\t')
 6         fltline=map(float,curline)
 7         dataMat.append(list(fltline))
 8     return dataMat
 9 
10 #计算两个向量距离
11 def distE(vecA,vecB):
12     return sqrt(sum(power(vecA-vecB,2)))
13 
14 #随机选择中心点
15 def randCent(dataSet,k):
16     n=shape(dataSet)[1]#获取数组列
17     centriods=mat(zeros((k,n)))
18     for j in range(n):
19         minJ=min(dataSet[:,j])#获取每一列数据中最小值
20         rangeJ=float(max(array(dataSet)[:,j])-minJ)
21         centriods[:,j]=minJ+rangeJ*random.rand(k,1)#随机生成k个数据中心
22     return centriods
View Code

k-Means核心代码

 1 #dataSet:待聚类数据集
 2 #k待聚类类别
 3 #disMea:距离计算函数
 4 #createCent:随机生成的初始聚类中心
 5 def kmeans(dataSet,k,disMea=distE,createCent=randCent):
 6     m=shape(dataSet)[0]
 7     clusterA=mat(zeros((m,2)))#用于存储数据集dataSet中属于k个类别中的哪一类和距该类的距离
 8     centriods=createCent(dataSet,k)
 9     clusterC=True
10     while clusterC:
11         clusterC=False
12         for i in range(m):
13             minDist=inf
14             minIndex=-1
15             for j in range(k):
16                 distJI=disMea(centriods[j,:],dataSet[i,:])#第i个数据集到第j个数据中心距离
17                 if distJI<minDist:
18                     minDist=distJI;minIndex=j
19             if clusterA[i,0]!=minIndex:
20                 clusterC=True
21             clusterA[i,:]=minIndex,minDist**2
22         print(centriods)
23         #遍历聚类类别,跟新聚类中心
24         for cent in range(k):
25             #nonzero(clusterA[:,0].A==cent获取指定中心的下标,再通过dataSet找到对应的数据
26             ptsInClust=dataSet[nonzero(clusterA[:,0].A==cent)[0]]
27             centriods[cent,:]=mean(ptsInClust,axis=0)#跟新聚类中心距离
28     return centriods,clusterA
View Code

可视化

 1 def show(dataSat,k,centriods,clusterA):
 2     import matplotlib.pyplot as plt
 3     numSamples,dim=shape(dataSat)
 4     mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
 5     for i in range(numSamples):
 6         markIndex=int(clusterA[i,0])
 7         plt.plot(dataSat[i,0],dataSat[i,1],mark[markIndex])
 8 
 9     mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
10     for i in range(k):
11         plt.plot(centriods[i,0],centriods[i,1],mark[i],markersize=12)
12     plt.show()
13 
14 def main():
15     dataMat=mat(loadDataSet('testSet2.txt'))
16     myCentroids,clustering=kmeans(dataMat,4)
17     print(myCentroids)
18     show(dataMat,4,myCentroids,clustering)
19 
20 if __name__=='__main__':
21     main()
View Code

3.一种改进的k均值算法----二分k-均值算法

为了克服k均值算法局部收敛问题,有人提出了二分k-均值算法,具体实现:

该算法首先将所有点看作一个簇,然后将该簇一分为二,之后选择其中一个簇继续划分,选择哪一个簇继续划分取决于对其划分是否可以最大程度降低SSE值。

 核心算法如下:

#dataSet:待分类数据集
#k:待分类别
#distMeans:分类距离计算公式
def biKmeans(dataSet,k,distMeans=distE):
    m=shape(dataSet)[0]
    clusterAssment=mat(zeros((m,2)))
    centroid0=mean(dataSet,axis=0).tolist()#按列计算每一列的均值,
    centList=[centroid0]#用于存储每个类中心
    #初始化,将每个数据都看为一个类
    for j in range(m):
        clusterAssment[j,1]=distMeans(mat(centroid0),dataSet[j,:])**2
    while (len(centList)<k):
        lowestSSE=inf
        for i in range(len(centList)):
            ptsInCurrCluster=dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]#计算被划分为i类中dataSet所有数据
            centroidMat,splitClustAss=kmeans(ptsInCurrCluster,2,disMea=distMeans)#将ptsInCurrCluster划分为两类
            # print('c',centroidMat)
            sseSplit=sum(splitClustAss[:,1])#计算划分后的均方根(SSE)误差
            sseNotSplit=sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])#计算没有划分数据集均方根误差
            print('sseSplit, and notSplit: ',sseSplit,sseNotSplit)
            #选择误差最小的i类中数据进行划分
            if (sseSplit+sseNotSplit)<lowestSSE:
                bestCentToSplit=i
                bestNewCents=centroidMat
                bestClustAss=splitClustAss.copy()
                lowestSSE=sseNotSplit+sseSplit
        #简单理解就是,我们选择了第i类划分为两类,那么有一类用原来的类编号i,而令一类用centList长度,即将类别加一
        #将二分类数据中划分为1的子类类别变化为centList长度
        print('b',bestClustAss)
        bestClustAss[nonzero(bestClustAss[:,0].A==1)[0],0]=len(centList)
        print('b1',bestClustAss)
        # 将二分类数据中划分为0的子类类别变化为bestCentToSplit
        bestClustAss[nonzero(bestClustAss[:,0].A==0)[0],0]=bestCentToSplit
        print('the bestCentTosplit is :',bestCentToSplit)
        print('the len of bestClustAss is :',len(bestClustAss))
        print('cent',centList)
        #更新用原理类别编号的数据距离
        centList[bestCentToSplit]=bestNewCents[0,:].tolist()
        #新加类别编号len(centList)的距离
        centList.append(bestNewCents[1,:].tolist())
        #将原先分类i中clusterAssment全部数据都变化为bestClustAss
        clusterAssment[nonzero(clusterAssment[:,0].A==bestCentToSplit)[0],:]=bestClustAss
    return centList,clusterAssment
View Code

 

 

posted @ 2020-05-23 13:53  阿贝尔  阅读(212)  评论(0)    收藏  举报