【机器学习实战】 利用K-均值聚类算法对未标注数据分组

转载请注明作者和出处: https://blog.csdn.net/weixin_37392582
代码地址: https://gitee.com/wuweijun
开发平台: Win10 + Python3.6 + Anaconda3
编  者: 无尾



一、前言

  聚类是一种无监督的学习,它将相似的对象归到同一簇中。它有点像全自动分类。聚类的方法几乎可以应用于所有对象,簇内的对象越相似,聚类的效果越好。本章要学习一种称为K-均值(K-means)聚类的孙发。之所以称之为K-均值,是因为它可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。
  簇识别。簇识别给出聚类结果的含义。假定有一些数据,现在将相似数据归到一起,簇识别会告诉我们这些簇到底都是些什么。聚类与分类的最大不同在与,分类的目标实现已知,而聚类则不一样。不易呀其产生的结果与分类相同,而只是类别没有预先定义,聚类有时也被称为无监督分类(unsupervised)。
    

二、K-均值聚类算法

  优点:容易实现
  缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢
  适用数据类型:数值型数据

1、工作流程  

  • 首先随机确定k个初始点作为质心
  • 为每个点找到距其最近的质心,并将其分配给该质心所对应的簇(打标签)
  • 将每个质心更新为该簇所有点的平均值
  • 重复前面两个步骤,知道所有点的簇类不再变化,即确定的质心为最优,即可完成聚类。

2、伪代码

创建k个点作为起始质心(通常是随机选择样本点)
当任意一个点的簇类(簇分配结果)发生改变时
    对数据集中的每个数据点
        对每个质心
            计算质心与数据点之间的距离
        将数据点分配到距其最近的簇
    对每一个簇,计算簇中所有点的均值并将均值作为质心

3、流程图

Created with Raphaël 2.1.2开始聚类随机确定k个初始点作为质心当任意一个点的簇类发生改变时对点寻找最近质心,确定簇类更新质心为该簇所有点的平均值结束yesno

4、核心代码解释

(1)euclidentDistance

  计算了各样本点到各质心距离,用距离差平方和表示,也称误差平方和。在主函数中,样本点会根据距离选取最新的质心作为簇类。

def euclidentDistance(vector1, vector2): #欧氏距离 Euclidean
    """
    Function: 欧式距离计算
    Input:两个座标(x0,x1),(x2, x3)
    Output: 两点的欧式距离
    """
    return np.sqrt(np.sum(np.power(vector2 - vector1,2)))

(2)initCentroids

  在样本点中,随机获取k个样本作为初始质心
  random.uniform(x1,x2) _在[x1,x2)内随机选取一个符合均匀分布的浮点数

def initCentroids(data, k):
    """
    Function: 随机获取k个质心
    Input:数据集,质心数量
    Output:k个质心的集合
    """
    numSamples, dim = data.shape
    centroids = np.zeros((k,dim))
    for i in range(k):
        #
        index = int(np.random.uniform(0, numSamples))
        centroids[i,:] = data[index,:]
    return centroids

(3)kmenas

   程序的主函数,通过迭代(划分样本-更新质心),实现聚类

def kmeans(dataSet, k):
    """
    Function:通过迭代,确定质心和样本类别
    Input   :数据集,质心数量
    Output  :质心座标 + [Ki,distance^2]
    """
    """
    伪代码:
        当质心还未更新完毕(各样本点的簇类值不再变化):
            1对每一个样本:
                对每一个质心:
                    计算样本到质心的距离
                    选取得到最小距离值时的质心,作为该样本的簇类
                判断该簇类是否是更新值,如果是:
                    将该样本的簇类和平方误差进行保存
            2由已分好的簇类中取均值,重新确立质心
        输出:质心 + 样本的簇类标签、           
    """
    numSamples = dataSet.shape[0] 
    #获取样本数 
    clusterAssment = np.mat((np.zeros((numSamples,2))))
    clusterChanged = True
    #初始化k个质心
    centroids = initCentroids(dataSet, k)
    #当聚类不在变化时,停止循环
    while clusterChanged:
        clusterChanged = False
        #遍历所有样本
        for i in range(numSamples):
            #对所有质心点,计算样本点到质心的距离,找到样本点到
            #一质心的最小距离,并取对该样本点进行划分
            minDist = float('inf')
            minIndex = 0
            for j in range(k):
                #计算点到质心的距离
                distance = euclidentDistance(centroids[j,:], dataSet[i,:])
                #取距点最近的质心为当前的簇类2
                if distance < minDist:
                    minDist = distance
                    minIndex = j
            #更新clusterChanged为True,将簇类和距离的平方保存
            if clusterAssment[i,0] != minIndex:
                clusterChanged = True
                clusterAssment[i,:] = minIndex, minDist**2
        #更新质心
        for j in range(k):
            pointsInCluster = dataSet[np.nonzero(clusterAssment[:,0] == j)[0]]
            centroids[j,:] = np.mean(pointsInCluster, axis = 0)
    print('完成聚类算法')
    return centroids, clusterAssment

三、二分K-均值算法

  在K-均值聚类中,点的簇分配结果值没有那么准确,K-均值算法收敛但是聚类效果较差。因为K-均值算法收敛到了局部最小值,而非全局最小值。因为初始的质心点是随机选取的,在对质心点优化过程的迭代,也是基于初始质心的位置而进行优化的,这样的优化结果只是这一局部的最优解,而非全局的。
  那么,我们引入一种用于度量聚类效果的指标:SSE(Sum of Squared Error,误差平方和)。SSE对应函数euclidentDistance的返回结果,SSE值越小,表明数据点越接近于它们的质心,质心位于数据点的中央,聚类效果也越好。因为我们对误差(点到质心的距离)取了平方,因此更加重视那些远离中心的点。
 因此,我们在每次划分簇的时候,对所有的划分结果用SSE进行比较,选取SSE值最小的划分方式进行聚类。这样避免了本应该划分的簇没有被划分,而点很集中的簇却被划分了的情况。
  

1、工作流程  

  • 首先将所有点作为一个簇,然后将该簇一分为二
  • 之后选取其中一个簇继续划分,划分哪一个簇取决于其划分是否可以最大程度降低SSE值
  • 上述基于SSE的划分过程不断重复,直到得到指定的簇数目为止。

2、伪代码

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

3、流程图

Created with Raphaël 2.1.2开始聚类,首先把左右点当作一个簇已划分的簇数量小于指定数量对当前已有的每一个簇,进行K-均值划分为两个簇比较几个划分方式的总SSE值,选取使SEE为最小的划分方式更新质心数目结束yesno

4、核心代码解释

def biKmeans(dataSet, k):
    """
    Function:不断二次划分最小误差数据集,得到质点和分类
    Input   :数据集,质心数量
    Output  :质心座标 + [Ki,distance^2]
    """
    """
    伪代码:
        初始化质心为所有样本点的均值
        计算样本点到初始质心的欧式距离,clusterAssment
        当确定的质心数量不足k个时:
            分类标签索引为质心的数量- len(centList)
            遍历每个质心:
                得到质心类别对应的数据样本
                将这些样本划分成2类,得到新的质心点和clusterAssment
                计算总体误差 - 参与划分的样本误差和没参与划分的样本误差之和
                当,当前误差值为最小误差时:
                    最佳分类标签(bestCentroidSplit)是当前的质心索引
                    最佳质心点(bestNewCentroids)是当前对应的质心点
                    bestClusterAssment为当前对应的clusterAssment
            选择一个类别的样本继续划分,类别为 len(cenList),另外一个样本标签为bestCentroidToSplit
            更新质心数组,最后一个质点更新为‘kmeans’中‘0’对应的质点,然后append进一个最新的质点

    """
    m = np.shape(dataSet)[0]
    clusterAssment = np.mat(np.zeros((m,2)))

    #初始质心,为所有样本点的均值
    centroid = np.mean(dataSet, axis = 0).tolist()[0] 
    centList = [centroid]
    #计算样本点到质心的距离
    for i in range(m):
        clusterAssment[i,1] = euclidentDistance(np.mat(centroid), dataSet[i,:]) ** 2

    #迭代收敛,分裂出新的质心,退出条件是分裂出的质心数量达到k
    while len(centList) < k:
        #定义误差值
        minSSE = float('inf') 
        #看上去是定义长度, 实则为每个质心打标签(0,1,2……)
        numCurrCluster = len(centList)
        #判断哪个质心好
        for i in range(numCurrCluster):
            #得到属于该质心的数据
            pointsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0] == i)[0],:]
            #得到划分成2类的【质心,分类+误差】
            centroids, splitClusterAssment = kmeans(pointsInCurrCluster, 2)
            #计算质心划分后的误差
            splitSSE = sum(splitClusterAssment[:,1])
            #计算没有参与划分的误差
            notSplitSSE = sum(clusterAssment[np.nonzero(clusterAssment[:,0] != i)[0],1]) 
            currSplitSSE = splitSSE + notSplitSSE
            #寻找最小的误差
            if currSplitSSE < minSSE:
                minSSE = currSplitSSE
                bestCentroidToSplit = i
                bestNewCentroids = centroids.copy()
                bestClusterAssment = splitClusterAssment.copy()\
        #修改集群索引以添加新集群, 类别1返回该质心原来的值, 0 更新为选择误差最小的质心再进行划分
        #给予新的分类新的标签,另外一个分类标签沿用原来的
        bestClusterAssment[np.nonzero(bestClusterAssment[:,0] == 1)[0],[0]] = numCurrCluster
        bestClusterAssment[np.nonzero(bestClusterAssment[:,0] == 0)[0],[0]] = bestCentroidToSplit

        #更新和添加新的2子簇的质心 
        centList[bestCentroidToSplit] = bestNewCentroids[0,:]
        centList.append(bestNewCentroids[1,:])

        #更新集群被更改的样本的索引和错误 - 在kmens中被贴上‘0’分类,在此处进行恢复 
        clusterAssment[np.nonzero(clusterAssment[:,0] == bestCentroidToSplit), :] = bestClusterAssment
    print('二分k-均值算法完成')
    return np.mat(centList), clusterAssment
发布了34 篇原创文章 · 获赞 23 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章