机器学习算法--K-means聚类

以《机器学习实战》为参考,实现K-means聚类算法

1.K-means聚类算法

数据示例:  每一个样本特征是二维向量

读取数据文件,加载数据集:

def loadDataSet(fileName):      #general function to parse tab -delimited floats
    dataMat = []                #assume last column is target value
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float,curLine)) #map all elements to float()
        dataMat.append(fltLine)
    return dataMat

初始化K个初始质心:

def randCent(dataSet, k):
    n = shape(dataSet)[1]
	#k个均值向量  每个向量n个特征
    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)
		#返回一个kx1维数组  用0,1随机数填充
        centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1))
    return centroids

对于每一个特征来说,求出最小值和最大值,然后产生之间的一个随机数,其中random.rank(m,n)函数返回一个m*n的数组,数组中每一个元素均用(0,1)随机数来填充。

距离计算,采用欧式距离:

def distEclud(vecA, vecB):
    return sqrt(sum(power(vecA - vecB, 2))) #la.norm(vecA-vecB)

K-means算法:

def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
    m = shape(dataSet)[0]
	#存储每个样本的簇记号以及与均值向量的距离
    clusterAssment = mat(zeros((m,2)))#create mat to assign data points 
                                      #to a centroid, also holds SE of each point
    #随机产生k个质心
	centroids = createCent(dataSet, k)
	#用来标记在一次迭代中,均值向量是否改变  作为终止条件
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
		#计算每个样本与均值的距离,选择最近的均值向量索引以及最近距离存储
        for i in range(m):#for each data point assign it to the closest centroid
            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
        print (centroids)
		#计算新的均值向量
        for cent in range(k):#recalculate centroids
		    #返回属于第cent个均值的样本集合数据
            ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]#get all the point in this cluster
			#按列求均值
            centroids[cent,:] = mean(ptsInClust, axis=0) #assign centroid to mean 
    return centroids, clusterAssment

随机产生K个质心  计算每个样本到每个均值向量的距离,将样本划分到最近的均值向量中,计算新的均值向量,如此迭代,终止条件是均值向量不再改变。

返回的4个质心为:

扫描二维码关注公众号,回复: 3866333 查看本文章

画出数据集以及质心分布:

def plotarr(arr, k):
    import matplotlib.pyplot as plt
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(arr[:,0].flatten().A[0], arr[:,1].flatten().A[0])
    ax.scatter(k[:,0].flatten().A[0], k[:,1].flatten().A[0], c='r', marker='p')
    #ax.scatter(arr[:,0], arr[:,1])
    plt.show()

arr数据集矩阵  k质心矩阵   

scatter函数   第一维数据  第二维数据    颜色c   形状marker

K-means均值算法与初始均值向量选择有关,容易陷入到局部最优值,可以采用二分K-均值算法来处理,避免陷入局部最优值。

2.二分K-均值算法

聚类效果的度量SSE(误差平方和):对应于clusterAssment矩阵的第一列之和

算法大致如下:

将所有点看成一个簇:

当簇数小于K时:

       对于每一个簇

             计算总误差

             在给定簇上面进行2-means均值聚类

             计算将簇一分为二的总误差

       选择使得误差最小的簇进行划分操作

代码如下:

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
        clusterAssment[j,1] = distMeas(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],:]#get the data points currently in cluster i
			#将该簇进行2-means聚类
            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
			#聚类之后的sse
            sseSplit = sum(splitClustAss[:,1])#compare the SSE to the currrent minimum
			#没有聚类之前的sse
            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])
            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) #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

centList存放  均值向量列表

clusterAssment  存放每个样本的均值向量索引   以及每个样本到均值向量的距离大小

centroidMat  将某一簇进行二划分的  均值向量矩阵 2Xm   共两个均值向量 索引依次为0,1

sliptClustAss  将某一簇进行二划分之后  该簇元素的均值向量centroidMat  索引(0或1)以及到均值向量的距离

bestClusterAss  最优划分之后的某一簇的 均值向量索引以及到均值向量的距离

进行簇划分时,将bestClusterAss的均值向量索引0改成被划分簇的索引bestCentToSplit以及均值向量索引1变为新增簇的索引len(centList)

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

centList中bestCentToSplit位置改为bestClusterAss对应0的均值向量,在len(centList)位置添加bestClusterAss对应1的均值向量,

centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids 
centList.append(bestNewCents[1,:].tolist()[0])

最后改变clusterAssment中bestCentToSplit簇的相应距离向量为bestClusterAss

加载数据集测试:可以发现数据集约可以聚成3类

 

运行K-means聚类算法:  可知聚类效果不好   陷入了局部最优值

     

运行二分kMeans聚类算法: 质心位置很好的聚类了数据

3.对于地理坐标进行聚类

数据集文件:  第四列与第五列分别为经纬度

距离计算,球面上两点距离计算,可以参考

https://www.cnblogs.com/zhoug2020/p/7634151.html

球面两点间距离公式: 
r*acos(cos(wa)*cos(wb)*cos(jb-ja) + sin(wa)*sin(wb)) 

def distSLC(vecA, vecB):#Spherical Law of Cosines
    a = sin(vecA[0,1]*pi/180) * sin(vecB[0,1]*pi/180)
    b = cos(vecA[0,1]*pi/180) * cos(vecB[0,1]*pi/180) * \
                      cos(pi * (vecB[0,0]-vecA[0,0]) /180)
    return arccos(a + b)*6371.0 #pi is imported with numpy

进行二分均值聚类以及显示:

import matplotlib
import matplotlib.pyplot as plt
def 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)
    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=5   k=10

       

如何选取K的值:

轮廓系数  用于评估聚类的效果,结合凝聚度与分离度。该值处于-1~1之间,值越大,表示聚类效果越好。

A(i)簇内凝聚度:样本点i与簇内其他样本距离的平均值

B(i)簇间分离度:计算样本i与其他各个簇的平均值的最小值

轮廓系数S(i)=(B(i)-A(i))/max{A(i),B(i)}

K值的选取:

1.多次测试取轮廓系数最大的K

2.(Calinski-Harabasz准则)

   

其中SSB是类间方差, ,m为所有点的中心点,mi为某类的中心点;

SSW是类内方差,

(N-k)/(k-1)是复杂度;

比率越大,数据分离度越大.

猜你喜欢

转载自blog.csdn.net/u014106644/article/details/83576653