机器学习之K均值聚类算法

262 阅读5分钟
参与拿奖:本文已参与「新人创作礼」活动,一起开启掘金创作之路
ps:代码文末自取
1.相关概念
聚类是一种无监督学习方法,几乎适用于所有对象
K均值聚类:将数据划分成K个簇,每个簇的中心采用该簇内均值计算而成
K均值聚类优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据上收敛较慢
适用数据类型:数值型数据

K均值伪代码

创建K个点为起始质心(通常随机选择)
对数据集中的每个数据点:
对于每个质心:
计算质心与数据点之间的距离
将数据点分配到距离其最近的簇中
对于每一个簇,计算簇中所有点的均值,并将均值作为质心
2.聚类案例

2.1 加载数据

# 加载数据集
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

2.2 计算误差平方和

# 计算误差平方和
def distEclud(vecA,vecB):
    return sqrt(sum(power((vecA-vecB),2)))

2.3 构建初始质心

# 构建初始簇质心
def randCent(dataSet,k):
    # 特征个数
    n=shape(dataSet)[1]
    # 构建划分为k*n的矩阵
    centroids=mat(zeros((k,n)))
    for j in range(n):
        # 获取特征J对应属性值取值步长(max-min)
        minJ=min(dataSet[:,j])
        rangJ=float(max(dataSet[:,j])-minJ)
        centroids[:,j]=minJ+rangJ*random.rand(k,1)
    return centroids

2.4 k-means聚类

# 输入参数分别为:数据集、K值、计算距离、创建初始质心
def KMeans(dataSet,k,distMeas=distEclud,createCent=randCent):
    # 获取数据点个数
    m=shape(dataSet)[0]
    # 记录每个点的分配结果,第一列存储簇索引、第二列存储误差(当前点到簇质心的距离)
    clusterAssment=mat(zeros((m,2)))
    # 初始质心
    centroids=createCent(dataSet,k)
    # 显示簇质心是否改变
    clusterChanged=True
    # 存储划分的结果
    clusterR=mat(zeros((m,3)))
    while clusterChanged:
        clusterChanged=False
        for i in range(m):
            # 初始化最小距离
            minDist=inf
            # 初始化最小索引
            minIndex=-1
            for j in range(k):
                # 计算第j行质心与第i行的数据距离,求出最近质心
                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
            clusterR[i,:]=dataSet[i,0],dataSet[i,1],minIndex
            # print(centroids)
        for cent in range(k):
            ptsInClust=dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]
            centroids[cent,:]=mean(ptsInClust,axis=0)
        # 返回所有的类质心与点分配结果
        return centroids,clusterAssment,clusterR

2.5 测试聚类效果

def kMeasPlot():
    dataMat=loadDataSet(r'data/testSet.txt')
    dataMat=mat(dataMat)
    # print(KMeans(dataMat,4))
    centeroids,clustrAssment,clusterR=KMeans(dataMat,4)
    fig=plt.figure()
    ax=fig.add_subplot(111)
    plt.scatter(centeroids[:,0].tolist(),centeroids[:,1].tolist(),color='red',marker='x')
    markers=['o','v','s','d']
    ms=[]
    for i in range(shape(dataMat)[0]):
        ms.append(clusterR[i,2])
    print(ms)
    plt.scatter(clusterR[:,0].tolist(),clusterR[:,1].tolist(),color="blue",marker=markers[int(ms[2])])
    plt.xlim(-6,6)
    plt.ylim(-6,6)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.show()

2.6 运行结果

image.png

由图可见数据比较分散,聚类效果并没有那么的好。

3.改进方法
使用后处理来提高聚类性能
K均值算法收敛但聚类效果差,是因为K均值算法收敛到了局部最小值,而非全局最小值
一种度量聚类效果的指标是SSE(误差平方和),SSE值越小说明其越接近质心,聚类效果越好
在簇数一定的限制条件下,可以通过后处理方法提高聚类性能
一种后处理方法:将SSE值较大的簇划分为两个簇,为了使得簇数不变可以将簇质心相距较小的两个簇进行合并

3.1 二分K均值算法步骤

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

3.2 代码

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
            centroidMat, splitClustAss,clusterR= KMeans(ptsInCurrCluster, 2, distMeas)
            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:
                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

3.3 测试代码与结果

def kBiMeasPlot():
    dataMat=loadDataSet(r'data/testSet2.txt')
    dataMat=mat(dataMat)
    # print(KMeans(dataMat,4))
    centList,myNewAssement=biKmeans(dataMat,3)
    fig=plt.figure()
    ax=fig.add_subplot(111)
    plt.scatter(centList[:,0].tolist(),centList[:,1].tolist(),color='red',marker='x')
    plt.scatter(dataMat[:,0].tolist(),dataMat[:,1].tolist(),color="blue",marker='v')
    plt.xlim(-6,6)
    plt.ylim(-6,6)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.show()

image.png

4.小结
聚类是一种无监督学习方法,无监督含义就是事先不知道需要寻找的内容,没有目标标量
聚类将数据点归类到多个簇中,其中相似数据在同一簇,可以使用多种方法计算相似度
K均值聚类:需要用户事先指定簇数,该方法以K个随机质心开始,计算每个点到质心的距离,每个点会被分到其距离最近的簇中,同时更新簇质心
二分K均值聚类:每次将目标数据划分为两个簇(直达划分K个簇为止),该方法基于贪心策略,是否划分依据为(SSE平方误差)
5.参考资料

[1] 机器学习实战

[2] 书籍源码

[3] jupyter版本

[4] 本节代码