欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

KMeans聚类算法

程序员文章站 2022-03-10 10:29:31
...

1、什么是聚类

    所谓聚类就是将一组对象按照特征划分不为的小组,使得组内的差异性尽可能的小,组间的差异尽可能的大。例如,粗粒度的分类,按照学校实力,分为985、211高校,普通一本高校,二本高校,三本高校。如果再更加细的分类,一个学校里面会按照所修的课程差异性分为不同学院,不同专业,这些同学院的专业课相差较小,不同的学院的课程相差就很大了。


2、聚类与分类的区别

    分类算法是已知数据的类别,通过对样本数据的学习建立分类模型,属于监督学习;而聚类算法是不知道数据的类别,由算法根据数据的分布进行分类,属于非监督学习模型。


3、Kmeans聚类

    Kmeans聚类属于划分聚类,由用户指定聚类簇的个数k,寻找k个簇中心,按照指定的相似度计算方法将样本划分到这K个聚类中心。


这里的相似度计算有很多种方法,常用的有

欧式距离:    KMeans聚类算法        


余弦相似度:KMeans聚类算法


下面全部采用欧式距离作为相似度度量方法

    算法流程:

    随机初始化k个聚类中心(第一次的聚类中心是随机选取的k个样本)

    

    对每个样本数据

        对每个聚类中心

            计算样本与聚类中心的相似度

        将数据点分配到相似度最高的样本中心

    对每个簇,使用簇内的样本数据特征均值更新聚类中心

   重复上述步骤,直至迭代次数达到用户指定的次数或者聚类中心基本没有改变。


    Kmeans聚类算法的优化目标函数:

                                           KMeans聚类算法

    上式的xi为第i个样本点,ci为第i个样本对应的聚类。对所有的样本,寻找c个中心点μ,使得每个样本距离其中心点的欧式距离之和最小。这个函数不是凸函数,无法对其求最优化,因此在聚类的过程中无法保证求得的聚类中心为全局最优的结果。但是可以确定的是t次迭代的过程,使得这个函数的函数值越来越小。其中隐含着EM算法的思想,将所有的样本分配到最近的聚类中心属于E环节求期望,对每个簇的中心进行修正属于M环节求最小化。为了避免局部最优,通常的做法是重复进行多次聚类,每次选择不同的初始聚类中心,比较选取最优的聚类结果。


4、Kmeans聚类算法的改进Kmedoids算法

    Kmeans聚类算法中心的更新采用均值,这种方法计算简单,但是也会带来问题,对异常点会比较敏感,计算平均值时会使聚类中心的位置偏向异常点。一种对Kmeans的改进是,对聚类中心的位置更改限制为样本点。即每次修改不是求样本的均值,而是将簇内的每一个样本点尝试作为这个簇的新中心,选择一个使得簇内差异最小的样本点作为新的聚类中心点,对每个簇进行同样的更新。这种方式对异常点不敏感,但是代价比较高,计算速度,收敛速度都会下降。 


5、Kmeans聚类算法的改进Kmeans++算法

    Kmeans聚类对初始点的选取非常敏感,在大数据集上,同一个算法不同的初始化点得到的结果往往不同。Kmeans++算法改进了Kmeans算法对初始聚类中心的选取方法,聚类过程和Kmeans是一样的。Kmeans++算法选取的方式是,首先从样本点中随机一个样本点作为第一个聚类中心点。计算训练样本距离各自簇中心的距离,将这个距离存储在一个dists数组中,并求距离总和sum,在0至sum区间产生一个随机数dist,使用这个随机数从i=0开始做运算dist-=dists[i],直至dist<0,此时的下标i对应的样本点作为下一个聚类中心点。重复上述步骤,直至选出k个聚类中心点。可以将距离理解为一节线段,数组dists存储了这个线段集合的每个线段长度,而sum则是将这些线段进行了一个拼接,在这个拼接的大线段上面随机选取一个点,那么这个点落在较长的小线段片段上几率更大。这种做法选取的新的中心点不是距离已有的中心点最远的点,而是偏向于较远的点。


6、实现

下面给出了Kmeans、Kmeans++和Kmoids算法的实现

#_*_encoding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt

class KMeans:
    def __init__(self,n_clusters=3,iterNum=200,type='kmeans',type2='random'):
        self.k=n_clusters
        self.iterNum=iterNum
        self.type=type
        self.type2=type2

    def distCal(self,x,y):                                             #计算欧式距离
        return np.sqrt(np.sum(np.power(x-y,2)))

    def randCent(self):                                                #随机初始化聚类中心点
        self.centroids=np.mat(np.zeros((self.k,self.n)))
        randIndex=np.random.randint(0,self.m,self.k)
        for i in range(self.k):
            self.centroids[i,:]=self.train[randIndex[i],:]

    def Kmeans_plus(self):                                            #Kmeans++初始化聚类中心点
        self.centroids=np.mat(np.zeros((self.k,self.n)))
        firstCent=np.random.randint(0,self.m)
        i=0
        self.centroids[i,:]=self.train[firstCent,:]
        while i<self.k-1:
            self.clusterAssment=self.calClusterAssment(self.train,self.centroids[0:i+1,:])
            distSum=sum(self.clusterAssment[:,1])
            randNum=np.random.randint(0,distSum)
            index=0
            while randNum>0:
                randNum-=self.clusterAssment[index,1]
                index=index+1
            i=i+1
            self.centroids[i,:]=self.train[index-1,:]

    def fit_predict(self,train):                                      #学习数据,返回聚类结果
        self.train=np.mat(train)
        self.m,self.n=np.shape(self.train)

        if self.type2=='random':
            self.randCent()
        else:
            self.Kmeans_plus()

        if self.type=='kmeans':
            self.KMeans_cluster()
        else:
            self.Kmedoids_cluster()

        return np.reshape(self.clusterAssment[:,0],(1,np.shape(self.clusterAssment[:,0])[0]))

    def KMeans_cluster(self):                                        #Kmneas聚类
        iter=0
        while  iter<self.iterNum:
            self.clusterAssment=self.calClusterAssment(self.train,self.centroids)
            for j in range(self.k):
                clust=self.train[np.nonzero(self.clusterAssment[:,0].A==j)[0]]
                self.centroids[j,:]=np.mean(clust,axis=0)
            iter=iter+1

    def calClusterAssment(self,train,centers):                    #分配
        clusterAssment = np.mat(np.zeros((self.m, 2)))
        for i in range(self.m):
            minDist = np.inf
            minIndex = -1
            for j in range(len(centers)):
                dist = self.distCal(self.train[i, :], centers[j, :])
                if dist < minDist:
                    minDist = dist
                    minIndex = j
            clusterAssment[i, 0] = int(minIndex)
            clusterAssment[i, 1] = minDist
        return clusterAssment

    def Kmedoids_cluster(self):                                      #Kmoids聚类
        iter=0
        distSum=np.zeros((self.m,1))
        while  iter<self.iterNum:
            self.clusterAssment=self.calClusterAssment(self.train,self.centroids)
            for i in range(self.k):
                clustIndex=np.nonzero(self.clusterAssment[:,0].A==i)[0]
                for j in clustIndex:
                    distSum[j]=self.calClusterDist(self.train[clustIndex,:],self.train[j,:])

                minDist=np.inf ; minIndex=-1
                for j in clustIndex:
                    if distSum[j]<minDist:
                        minDist=distSum[j]
                        minIndex=j
                self.centroids[i,:]=self.train[minIndex,:]
            iter+=1

    def calClusterDist(self,train,center):                  #计算簇内的距离和
        dists=0
        for i in range(np.shape(train)[0]):
            dists+=self.distCal(train[i,:],center)
        return dists

    def show(self,num=0):                                    #数据显示
        y=self.clusterAssment[:,0]
        fig=plt.figure(num)

        cValue = ['r', 'y', 'g', 'b', 'r', 'y', 'g', 'b', 'r']
        mValue = ['*', '+','s','^','D']
        for i in range(self.m):
            plt.scatter(self.train[i,1],self.train[i,2],marker=mValue[int(y[i,0])],c=cValue[int(y[i,0])])
        for i in range(self.k):
            plt.scatter(self.centroids[i,1],self.centroids[i,2],c='r',marker='D')
        plt.show()


from sklearn import datasets
iris=datasets.load_iris()
x=iris.data
x=np.mat(x)
#x=np.row_stack((x,[6,6,6,6]))
km=KMeans(n_clusters=4,iterNum=50,type='kmeans',type2='kmeans++')
km.fit_predict(x)
km.show()

  

上面采用的鸢尾花数据集加载于sklearn包中。

原始数据标签的分布如下图所示:

KMeans聚类算法


设置K=2,选择Kmeans算法进行聚类,结果如下:

KMeans聚类算法


设置k=4进行聚类:

KMeans聚类算法

可以看出不同的k,得到的聚类簇大小是不一样。在实际使用中选择合适的k值很重要,需要根据具体需求来进行选择(并不是K越大越好)。


选择K值为3,Kmeans的第一次结果:

KMeans聚类算法


Kmeans的第二次结果:

KMeans聚类算法,

从上面看到,这种随机初始化聚类中心点,多次运行结果明显不一样。


下面是加入异常点使用Kmeans++初始化聚类中心点效果:

KMeans聚类算法

异常点在右上侧,虽然不是很明显,但是依旧可以看出,聚类中心点相对于没有加入异常点向右发生偏移。


使用Kmedoids算法可以减少异常点对聚类中心的影响,下面给出使用Kmedoids算法的效果。

KMeans聚类算法


接下来在数据中加入一个异常点,来看看聚类中心是否发生偏移

KMeans聚类算法

可以看到上面两张图的显示除了新加入了一个样本点之外,聚类中心没有发现变化。也就是说,Kmoids算法对这种异常点并不敏感。


7、总结

    Kmeans算法是一种高效的聚类算法,易理解易实现,用于发现没有给出标签的数据内部的结构,属于无监督学习。时间复杂度和数据的规模m,迭代次数t,聚类个数k成正比,时间复杂度为O(kmt)。但是Kmeans算法要求数据必须是数值型数据,如果是其它类型数据则需要进行转换。Kmeans算法对异常点敏感,对初始聚类中心的选取敏感,因此有对应的改进算法Kmedoids和Kmeans++算法,这类算法都只适合于发现球型簇,不能用于发现非凸型簇。