【机器学习实战-python3】K-均值聚类算法

来源:互联网 发布:淘宝宝贝推荐模板 编辑:程序博客网 时间:2024/05/20 07:15

本篇的数据和代码参见:https://github.com/stonycat/ML-in-Action

一、K-均值聚类算法
聚类是一种无监督的学习,它将相似的对象归到同一簇中,类似全自动分类。簇内的对象越相似,聚类的效果越好。K-均值聚类是每个类别簇都是采用簇中所含值的均值计算而成。聚类与分类的区别在于分类前目标已知,而聚类为无监督分类。
K-均值算法的伪代码如下:

创建k个点作为起始质心(通常随机选择)

   当任意一个点的簇分配结果发生改变时:        对数据集中的每个点:                对每个质心:                计算质心与数据点之间的距离         将数据点分配到距离其最近的簇    对每一个簇,计算簇中所有点的均值并将均值作为质心。

基本功能函数:加载数据、计算距离、初始化k个中心三个函数。

#coding=utf-8from numpy import *#load datadef loadDataSet(fileName):    dataMat = []    fr = open(fileName)    for line in fr.readlines(): #for each line        curLine = line.strip().split('\t')        fltLine = list(map(float,curLine)) #这里和书中不同 和上一章一样修改        dataMat.append(fltLine)    return dataMat#distance funcdef distEclud(vecA,vecB):    return sqrt(sum(power(vecA - vecB, 2)))  # la.norm(vecA-vecB) 向量AB的欧式距离#init K points randomlydef randCent(dataSet, k):    n = shape(dataSet)[1]    centroids = mat(zeros((k,n)))#create centroid mat    for j in range(n):#create random cluster centers, within bounds of each dimension        minJ = min(dataSet[:,j])        rangeJ = float(max(dataSet[:,j]) - minJ)        centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1))    return centroids

K-均值聚类算法接收4个参数,两个必要参数为数据集和k的值,另外两个为距离计算函数和初始化函数(可修改)。算法采用计算质心-分配-重新计算质心反复迭代的方式,直到所有点的分配结果不再改变。设置flag为clusterChange=True。

#K-均值算法:def kMeans(dataSet,k,distMeas=distEclud,createCent=randCent):    #参数:dataset,num of cluster,distance func,initCen    m=shape(dataSet)[0]    clusterAssment=mat(zeros((m,2)))#store the result matrix,2 cols for index and error    centroids=createCent(dataSet,k)    clusterChanged=True    while clusterChanged:        clusterChanged=False        for i in range(m):#for every points            minDist = inf;minIndex = -1#init            for j in range(k):#for every k centers,find the nearest center                distJI=distMeas(centroids[j,:],dataSet[i,:])                if distJI<minDist:#if distance is shorter than minDist                    minDist=distJI;minIndex=j# update distance and index(类别)            if clusterAssment[i,0] != minIndex:                clusterChanged = True                #此处判断数据点所属类别与之前是否相同(是否变化,只要有一个点变化就重设为True,再次迭代)            clusterAssment[i,:] = minIndex,minDist**2        print(centroids)        # update k center        for cent in range(k):            ptsInClust=dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]            centroids[cent,:]=mean(ptsInClust,axis=0)    return centroids,clusterAssment

二、用后处理来提高聚类性能
聚类算法中,k的值是由用户初始定义的,如何才能判断k值定义是否合适,就需要用误差来评价聚类效果的好坏,误差是各个点与其所属类别质心的距离决定的。K-均值聚类的方法效果较差的原因是会收敛到局部最小值,而且全局最小。一种评价聚类效果的方法是SSE(Sum of Squared Error)误差平方和的方法,取平方的结果是使得远离中心的点变得更加突出。
一种降低SSE的方法是增加簇的个数,即提高k值,但是违背了聚类的目标,聚类的目标是在不改变簇数目的前提下提高簇的质量。可选的改进的方法是对生成的簇进行后处理,将最大SSE值的簇划分成两个(K=2的K-均值算法),然后再进行相邻的簇合并。具体方法有两种:1、合并最近的两个质心(合并使得SSE增幅最小的两个质心)2、遍历簇 合并两个然后计算SSE的值,找到使得SSE最小的情况。

下面将使用上述技术得到更好的聚类结果方法。

三、二分K-均值算法
二分K-均值类似后处理的切分思想,初始状态所有数据点属于一个大簇,之后每次选择一个簇切分成两个簇,这个切分满足使SSE值最大程度降低,直到簇数目达到k。另一种思路是每次选择SSE值最大的一个簇进行切分。
满足使SSE值最大程度降低伪代码如下:

将所有点看成一个簇

   当簇数目小于k时        对于每一个簇:            计算总误差            在给定的簇上面进行K-均值聚类(k=2)    计算将该簇一分为二后的总误差    选择使得误差最小的那个簇进行划分操作

函数biKmeans是上面二分K-均值聚类算法的实现,首先创建clusterAssment储存数据集中每个点的分类结果和平方误差,用centList保存所有已经划分的簇,初始状态为整个数据集。while循环不停对簇进行划分,寻找使得SSE值最大程度减小的簇并更新,添加新的簇到centList中。

#二分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]  # create a list with one centroid    for j in range(m):  # calc initial Error for each point        clusterAssment[j, 1] = distMeas(mat(centroid0), dataSet[j, :]) ** 2    while (len(centList) < k):        lowestSSE = inf #init SSE        for i in range(len(centList)):#for every centroid            ptsInCurrCluster = dataSet[nonzero(clusterAssment[:, 0].A == i)[0],:]  # get the data points currently in cluster i            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)# k=2,kMeans            sseSplit = sum(splitClustAss[:, 1])  # compare the SSE to the currrent minimum            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:, 0].A != i)[0], 1])            print("sseSplit, and notSplit: ", sseSplit, sseNotSplit)            if (sseSplit + sseNotSplit) < lowestSSE: #judge the error                bestCentToSplit = i                bestNewCents = centroidMat                bestClustAss = splitClustAss.copy()                lowestSSE = sseSplit + sseNotSplit        #new cluster and split cluster        bestClustAss[nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)  # change 1 to 3,4, or whatever        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  # reassign new clusters, and SSE    return mat(centList), clusterAssment

四、示例:对地图上的点聚类
书上给出的yahooAPI的baseurl已经改变,github上有oauth2供python使用,但是yahoo的BOOS GEO好像OAuth2验证出了问题,虽然写了新的placeFinder调用api的代码,仍然会有403错误。参考:http://blog.forec.cn/2016/02/21/machinelearning10/

好在随书代码中已经给出place.txt,所以直接调用,这里略过获取数据的步骤。

#draw functionimport matplotlibimport matplotlib.pyplot as pltdef clusterClubs(numClust=5):#参数:希望得到的簇数目    datList = []    for line in open('places.txt').readlines():#获取地图数据        lineArr = line.split('\t')        datList.append([float(lineArr[4]), float(lineArr[3])])#逐个获取第四列和第五列的经纬度信息    datMat = mat(datList)    myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC)    #draw    fig = plt.figure()    rect=[0.1,0.1,0.8,0.8]#创建矩形    #创建不同标记图案    scatterMarkers=['s', 'o', '^', '8', 'p', \                    'd', 'v', 'h', '>', '<']    axprops = dict(xticks=[], yticks=[])    ax0=fig.add_axes(rect, label='ax0', **axprops)    imgP = plt.imread('Portland.png')#导入地图    ax0.imshow(imgP)    ax1=fig.add_axes(rect, label='ax1', frameon=False)    for i in range(numClust):        ptsInCurrCluster = datMat[nonzero(clustAssing[:,0].A==i)[0],:]        markerStyle = scatterMarkers[i % len(scatterMarkers)]        ax1.scatter(ptsInCurrCluster[:,0].flatten().A[0], ptsInCurrCluster[:,1].flatten().A[0], marker=markerStyle, s=90)    ax1.scatter(myCentroids[:,0].flatten().A[0], myCentroids[:,1].flatten().A[0], marker='+', s=300)    plt.show()

测试:k=3
这里写图片描述
k=5
这里写图片描述
k=7
这里写图片描述

小结
聚类是一种无监督聚类算法,无监督指的是事先不知道所需要查找的内容(无目标变量)。聚类将数据点归入多个簇中,相似的数据点归入到同一个簇。有很多不同的方法来计算相似性。广泛使用的是K-均值算法:通过指定k值,随机分配k个质心,然后计算每个数据点到各个质心的距离,将点分配到距离最近的质心,重新计算每个簇的均值更新质心,反复迭代直到质心不在变化。(算法有效但初始k值不容易确定)
另一种是二分K-均值算法:首先将所有点作为一个簇,然后采用k=2的K-均值算法进行划分,下一次迭代时选择两个簇中SSE(平方误差)最大的簇进行再次划分,直到簇数目达到给定的k值。二分K-均值的算法要优于K-均值算法,不容易收敛到局部最小。

0 0