美文网首页
K-means《机器学习实战》基础整理

K-means《机器学习实战》基础整理

作者: 御风而行carrie | 来源:发表于2018-05-31 16:15 被阅读0次

主要内容

一般K-means:

  • 簇数k是用户给定的,每个簇通过其质心(centroid),即簇中所有点的中心来描述。

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

  • 方法:(计算质心 - 分配 - 重新计算) - 反复迭代,直到所有数据点的簇分配结果不再改变为止

使用后处理提高聚类性能(《机器学习实战》 - P189):

  • 问题

    • 怎样知道k值的选择是否正确?怎样知道生成的簇比较好呢?
  • 怎样评价

    • 在包含簇分配结果的矩阵中包含着每个点的误差,即该点到簇质心的距离平方值,将以此来评价聚类质量
  • 原因:

    • K-Means算法收敛但聚类效果交叉的原因是:K-Means收敛到了局部最小值,而非全局最小值(局部最小值结果可以但并非最好结果,全局最小值是可能的最小结果)
  • 衡量指标:

    • 一种用于度量聚类效果的指标是SSE(Sum of Squared Error 误差平方和),即每个点距离质心距离的平方和。SSE越小表示数据点越接近质心,聚类效果也越好
      因为对误差取了平方,因此更加重视那些远离中心的点。增加簇个数能够降低SSE值,但这违背了聚类的目标:在保持簇数目不变情况下提高簇的质量
  • 一种方法

    • 对生成后的簇进行处理,将SSE值最大的簇划分为两个簇。具体实现时可以将最大簇包含的点过滤出来,并在这些点上运行k=2的K-means

二分K-均值算法

为了克服k-均值算法收敛于局部最小值的问题,有人提出了另一个称为二分k-均值(bisecting K-means)的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二,之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可可以最大程度降低SSE的值。上述基于SSE的划分过程不断重复,知道得到用户指定的簇数目为止

代码

代码整理自《机器学习实战》

# -*- coding: utf-8 -*-
from numpy import *
import matplotlib.pyplot as plt

def loadDataSet(fileName):
    """
    功能:读取文件,加载数据
    """
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('    ')
        # 注意: python2中map函数直接返回列表,不用list处理
        fltLine = list(map(float,curLine))
        dataMat.append(fltLine)
    return dataMat


def disEclud(vecA,vecB):
    """
    功能: 计算两个向量间的欧氏距离
    """
    return sqrt(sum(power(vecA - vecB, 2)))


def randCent(dataSet, k):
    """
    功能: 为给定数据集构建一个包含k个随机质心的集合(通过找到数据集每一维的最小和最大值来完成)
    """
    # array(dataSet)是自己加的,否则无法进行dataSet[:,j]操作,用mat转成矩阵也不能进行dataSet[:,j]操作
    dataSet = array(dataSet)
    n = shape(dataSet)[1]
    centroids = mat(zeros((k,n)))
    # 构建簇质心
    for j in range(n):
        # 第j维的最小值
        minJ = min(dataSet[:,j])
        # 第j维的最大最小值之差
        rangeJ = float(max(dataSet[:,j]) - minJ)
        # 原代码是: centroids[:,j] = minJ + rangeJ * random.rand(k,1), randoma.rand(k,1)是k行1列的ndarray,且随机产生的每个数字在0-1之间
        temp = random.rand(k,1)
        centroids[:,j] = minJ + rangeJ * temp
    return centroids

def kMeans(dataSet,k,distMeas = disEclud, createCent = randCent):
    """
    功能:kmearns聚类代码
    首先,随机确定k个初始点作为质心。然后,将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成后,每个簇的质心更新为该簇所有点的平均值。
    方法:(计算质心 - 分配 - 重新计算) - 反复迭代,直到所有数据点的簇分配结果不再改变为止
    param: dataSet: 数据计划
    param: k: 簇的数量
    param: distMeas: 距离计算方法
    param: createCent: 计算原始的质心
    return: centroids: 最终的质心
    return: clusterAssment: 簇分配结果矩阵,包含两列,一列记录簇索引值,第二列存储误差(指当前点到簇质心的距离,后续会用该误差评价聚类的效果)
    
    """
    m = shape(dataSet)[0]
    clusterAssment = mat(zeros((m,2)))
    centroids = createCent(dataSet,k)
    centroids = array(centroids)
    dataSet = array(dataSet)
    # 标识是否继续迭代(如果为True,则继续迭代)
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        # 遍历所有样本点,找到距离每个点最近的质心(通过对每个点遍历所有质心并计算点到每个质心的距离来完成)
        for i in range(m):
            minDist = inf
            minIndex = -1
            # 当前样本点便利便利所有的质心
            for j in range(k):
                # 计算第i个样本与第j个质心间的距离
                distJI = distMeas(centroids[j,:], dataSet[i,:])
                if distJI < minDist:
                    minDist = distJI
                    minIndex = j
            # 如果当前第i个点的分配结果发生了改变,则更新clusterChanged标识
            if clusterAssment[i,0] != minIndex:
                clusterChanged = True
            # clusterAssment的两列分别存储簇索引值和距离(第i个样本点到质心的距离)
            clusterAssment[i,:] = minIndex, minDist ** 2
        # print (centroids)
        # 遍历所有质心并更新它们的取值
        for cent in range(k):
            # 这一步操作每台明白?????
            pstInClust = dataSet[nonzero(clusterAssment[:,0].A == cent)[0]]
            # 在pstInclust的列上计算均值
            centroids[cent,:] = mean(pstInClust, axis = 0)
    return centroids, clusterAssment


def biKmeans(dataSet, k, distMeas = disEclud):
    """
    功能:二分K-均值算法 
    为了克服k-均值算法收敛于局部最小值的问题,有人提出了另一个称为二分k-均值(bisecting K-means)的算法。
    该算法首先将所有点作为一个簇,然后将该簇一分为二,之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于
    对其划分是否可可以最大程度降低SSE的值。上述基于SSE的划分过程不断重复,知道得到用户指定的簇数目为止
    param: dataSet: 数据计划
    param: k: 簇的数量
    param: distMeas: 距离计算方法
    return: centroids: 最终的质心
    return: clusterAssment: 簇分配结果矩阵,包含两列,一列记录簇索引值,第二列存储误差
    """
    dataSet = array(dataSet)
    m = shape(dataSet)[0]
    # 创建一个矩阵来存储数据集中每个点的簇分配结果及平方误差,然后计算整个数据集的质心,并使用一个列表来保留所有的质心
    # 得到上述质心后,可以遍历数据集中所有点来计算每个点到质心的误差,这些误差值都将在后续用到
    clusterAssment = mat(zeros((m,2)))
    centroid0 = mean(dataSet,axis=0).tolist()[0]
    cenList = [centroid0]
    for j in range(m):
        clusterAssment[j,1] = distMeas(mat(centroid0),dataSet[j,:]) ** 2
    # while循环汇总会不停地对簇进行划分,直到得到想要的簇数目位置
    while len(cenList) < k:
        # SSE初值设为无穷大
        lowestSSE = inf
        # 遍历簇列表centList中的每一个簇
        for i in range(len(cenList)):
            # 对每一个簇,将该簇中的所有点看称一个小的数据集pstIncurrCluster
            # QUESTION:为什么要选与i相等的???
            ptsIncurrCluster = dataSet[nonzero(clusterAssment[:,0].A == i)[0],:]
            # 将pstIncurrCluster输入到函数KMeans()中进行处理(k=2),然后得到2个质心(簇)和每个簇的误差值
            centroidMat, splitClustAss = kMeans(ptsIncurrCluster, 2, distMeas)
            # 上述得到的误差与剩余数据集的误差之和作为本次划分的误差
            sseSplit = sum(splitClustAss[:,1])
            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A != i)[0],1])
            print ("seeSplit, and notSplit: ", sseSplit, sseNotSplit)
            # 如果划分的SSE值最小,则本次划分被保存
            if (sseSplit + sseNotSplit) < lowestSSE:
                bestCentToSplit = i 
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSe = sseSplit + sseNotSplit
        """更新簇的分配结果"""
        bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(cenList)
        bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
        print ("the bestCentToSplit is : ", bestCentToSplit)
        print ("the len of bestClustAss is: ", len(bestClustAss))
        cenList[bestCentToSplit] = bestNewCents[0, :]
        cenList.append(bestNewCents[1,:])
        clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:] = bestClustAss
    return mat(cenList), clusterAssment


def show_pic(dataMat):
    dataArr = array(dataMat)
    # 作图方法一
    # plt.scatter(dataArr[:,0], dataArr[:,1], alpha=0.8)  # 绘制散点图,透明度为0.6(这样颜色浅一点,比较好看)
    # plt.show()  
    # 作图方法二
    fig = plt.figure()
    ax = plt.subplot()
    ax.scatter(dataArr[:,0], dataArr[:,1], s=30, alpha=0.8)  # 绘制散点图,面积随机
    plt.show()


if __name__ == '__main__':
    # dataMat = loadDataSet('KMeans_testSet.txt')
    # show_pic(dataMat)
    # centroids, clusterAssment = kMeans(dataMat,4)

    dataMat2 = loadDataSet('KMeans_testSet2.txt')
    # show_pic(dataMat2)
    cenList,myNewAssments = biKmeans(dataMat2,3)
    print (cenList)
    # print (myNewAssments)

相关文章

网友评论

      本文标题:K-means《机器学习实战》基础整理

      本文链接:https://www.haomeiwen.com/subject/aogqsftx.html