K-means聚类算法

来源:互联网 发布:python 人工智能 开发 编辑:程序博客网 时间:2024/05/16 06:49

聚类:是一种无监督学习,它将相似的对象归到同一个簇中。

聚类方法几乎可以应用于所有对象,簇内的对象越相似,聚类的效果越好。聚类分析试图将相似对象归入同一簇,将不相似对象归到不同簇。相似这一概念取决于所选择的相似度计算方法。

聚类和分类的最大区别:分类的目标事先已知,而聚类则不一样。

我们即将要介绍的K均值算法就是一种聚类算法,之所以称之为K--均值,是因为他可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。

K-均值是发现给定数据集的k个簇的算法。簇个数k是用户给定的,每一个簇通过其质心(centroid),即簇中所有点的中心来描述。

K-均值算法思想:首先,随机确定k个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。完成这一步后,每个簇的质心更新为该簇所有点的平均值。

K--均值算法的代码实现:

1、K-均值聚类支持函数

# -*- coding: cp936 -*-from numpy import *#K-均值聚类支持函数def loadDataSet(fileName):    dataMat = []    fr = open(fileName)    for line in fr.readlines():        curLine = line.strip().split('\t')        print(curLine)        fltLine = map(float,curLine)#将curLine中的数据转成float        print(fltLine)        dataMat.append(fltLine)    return dataMat#距离计算def distEclud(vecA, vecB):    return sqrt(sum(power(vecA - vecB, 2)))#为给定数据集构建一个包含k个随机质心的集合def randCent(dataSet, k):    n = shape(dataSet)[1]    centroids = mat(zeros((k,n)))#k行n列的mat    for j in range(n):        minJ = min(dataSet[:,j])        rangeJ = float(max(dataSet[:,j]) - minJ)        centroids[:,j] = minJ + rangeJ * random.rand(k,1)    return centroids

代码测试:

datMat = mat(Kmeans.loadDataSet('KtestSet.txt'))
myCentroids, clustAssing = Kmeans.kMeans(datMat,4)[[ 2.9332215   2.22861698] [ 1.69268674 -4.10293577] [ 0.91368859 -2.42097458] [ 3.3841354   1.52146566]][[ 0.16309271  3.17495486] [ 2.845833   -3.38251675] [-2.45947514 -2.32683068] [ 4.1436012  -0.2075728 ]][[-0.23940082  3.198336  ] [ 2.60401887 -3.15076575] [-3.35883359 -2.57409073] [ 3.53791189  0.64902844]][[-0.87618583  3.19731776] [ 2.54173689 -3.11892933] [-3.4967025  -2.70989515] [ 3.17716292  1.68927015]][[-1.94392522  2.96291883] [ 2.56468005 -2.8885874 ] [-3.53973889 -2.89384326] [ 2.91014783  2.71954072]][[-2.46154315  2.78737555] [ 2.65077367 -2.79019029] [-3.53973889 -2.89384326] [ 2.6265299   3.10868015]]

2、K-均值聚类算法

#K-均值聚类算法def kMeans(dataSet,k,distMeas=distEclud, createCent=randCent):    m = shape(dataSet)[0]    clusterAssment = mat(zeros((m,2)))    centroids = createCent(dataSet, k)    clusterChanged = True    while clusterChanged:        clusterChanged = False        for i in range(m):            minDist = inf;minIndex = -1            for j in range(k):                distJI = distMeas(centroids[j,:],dataSet[i,:])                if distJI < minDist:                    minDist = distJI;minIndex = j            if clusterAssment[i,0] != minIndex:                clusterChanged = True            clusterAssment[i,:] = minIndex,minDist**2 <span style="color:#ff0000;">#clusterAssment包含两列:一列记录簇索引值,第二列存储误差</span>        print(centroids)        for cent in range(k):            ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]<span style="color:#ff0000;">#将clusterAssment中第一列中等于cent的索引<span style="white-space:pre"></span>       #对应的dataSet中数提取出来,放到ptsInClust中</span>            #print(ptsInClust,"xiaobiao",nonzero(clusterAssment[:,0].A==cent))            #print(nonzero(clusterAssment[:,0].A==cent)[0])            centroids[cent,:] = mean(ptsInClust, axis=0)<span style="color:#ff0000;">#将这些点的平均点作为质心</span>    return centroids, clusterAssment

问题:上面的方法有一个问题,由于k是我们自己定义的,

问题1、那么用户如何知道才能知道k的选择是否正确呢?

问题2、我们怎么样才能知道生成的簇比较好呢?

对于问题1:我们可以用下面的二分k-均值聚类算法来解决。

对于问题2:我们用于度量聚类效果的指标是SSE(Sum of Squared Error,误差平方和)。

例如:对于clusterAssment矩阵的第一列之和。SSE值越小表示数据点越接近于他们的质心,聚类效果也越好。

我们可以对上面的方法进行改进,以此来降低SSE的大小。那么如何改进呢?

方法1:增加簇的个数肯定可以降低SSE的值,但是这违背了聚类的目标。聚类的目标是在保持簇不变的情况下提高簇的质量。

方法2:将具有最大SSE的簇生成两簇,但是为了保持总簇不变,可以将某两簇进行合并。合并最近的质心或者合并两个使得SSE增幅最小的质心(合并两个簇然后计算总的SSE值,所有的两个簇都得计算,知道找出合并最佳的簇)。


下面要说的二分k-均值聚类算法就用到了上述划分技术:

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

上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。

#二分K-均值聚类算法def biKmeans(dataSet, k, distMeas=distEclud):    m = shape(dataSet)[0]    clusterAssment = mat(zeros((m,2)))    centroid0 = mean(dataSet, axis=0).tolist()[0]    centList = [centroid0]<span style="color:#ff0000;">#用一个列表来保留所有的质心</span>    for j in range(m):<span style="color:#ff0000;">#计算每个点到质心的误差值,后面会用到这些误差值.</span>        clusterAssment[j,1] = distMeas(mat(centroid0),dataSet[j,:])**2    while (len(centList) < k):<span style="color:#ff0000;">#该循环会不停的对簇进行划分,直到得到想要的簇数目k为止</span>        lowestSSE = inf        for i in range(len(centList)):            ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]<span style="color:#ff0000;">#第i行的所有数据</span>            centroidMat, splitClustAss = kMeans(ptsInCurrCluster,2,distMeas)<span style="color:#ff0000;">#返回所有类的质心和点分配的结果</span>            sseSplit = sum(splitClustAss[:,1])<span style="color:#ff0000;">#SSE总和</span>            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])<span style="color:#ff0000;">#除i以外的所有的SSE的总和</span>            print("sseSplit, and notSplit:",sseSplit,sseNotSplit)            if (sseSplit + sseNotSplit) < lowestSSE:                bestCentToSplit = i                bestNewCents = centroidMat                bestClustAss = splitClustAss.copy()                lowestSSE = sseSplit + sseNotSplit        bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)        bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit        print("the bestCentToSplit is:",bestCentToSplit)        print("the len of bestClustAss is:",len(bestClustAss))        centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids         centList.append(bestNewCents[1,:].tolist()[0])        clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]=bestClustAss#将误差小的放到一个clusterAssment中,接着分,直到达while到要求    return mat(centList),clusterAssment

测试结果:

>>>myCentroids, clustAssing = Kmeans.biKmeans(datMat,4)>>>myCentroidsmatrix([[-3.38237045, -2.9473363 ],        [-2.46154315,  2.78737555],        [ 2.80293085, -2.7315146 ],        [ 2.6265299 ,  3.10868015]])


这个方法运行多次,聚类会收敛到全局最小值,而原始的kMeans函数可能会陷入局部最小值。
















0 0
原创粉丝点击