K-Means 算法聚类

K-Means 算法聚类

非监督学习: 从数据中发现隐含的关系 对数据进行聚类 cluster
监督学习: 根据已有的历史数据 对数据进行分类 classification

K-Means 算法

问题:如何对数据进行聚类?

  • 假设数据集T中, 由K类的数据, 但是如何确定这些数据之间存在关系
    损失函数: 平方误差函数 我们可以以它们之间距离度量确定数据之间存在关系,越是相似,那么距离度量就越近

算法思路:

确定K个质心, 将数据分为k类, 实质上质心的值就是数据集中所属于第k个质心的样本数据子集的均值

确定算法思路之后,尝试实现算法
已知条件: 数据集 损失函数 需要选择质心 遍历数据集与每个质心的距离 根据损失函数进行重新分配质心 遍历执行直到聚类效果达到最优
分解问题:

step1: 读取数据集
step2: 创建损失函数 SSE sum of Square
step3:  初始化质心选择  选择k个质心
step4:  k-means 算法实现

算法主体step4

step4 算法实现伪代码;
数据集维度为mxn
创建K个点作为启始质心
创建维度为(mx2)的矩阵clusterment 第一列记录分配的质心  第二列记录与该质心的损失函数度量
while True 循环 (有且仅当数据分配不改变时,停止循环)
    遍历数据集中的每一个点
        遍历每一个质心
            计算每一个点与每一个质心的损失函数大小
            比较大小,记录min_error and best_k  最小损失函数 以及对应的质心
        将数据点分配到距离最近的簇中
        并且检查数据分配是否仍然变动 没有变动了就停止循环
        同时更新每个质心的值
        每个质心的都是数据集中所属于该质心的样本数据集的均值 (因此才是k-Means算法)
返回 更新后的质心以及 clusterments

K-Means算法实现

import numpy as np


def load_data(filename):
    fr = open(filename)
    data_set = list()
    for lines in fr.readlines():
        line = lines.strip().split('\t')
        line_list = list()
        for i in line:
            line_list.append(float(i))
        data_set.append(line_list)
    data_set = np.mat(data_set)
    return data_set


def calc_dist_sse(data_set, centroids):
    """
    k-means 距离度量方式  sum of square error
    :param data_set:
    :param centroids:
    :return:
    """
    # print('data set', type(data_set))
    # print('centriods', type(centroids))
    return np.sqrt(np.sum(np.power(data_set - centroids, 2)))


def create_centroids(data_set, k):
    """
    k means 算法 随机选取质心
    :param data_set: 数据集
    :param k: k个质心
    :return
        centroids
    """
    m, n = np.shape(data_set)
    # print('shape data set', np.shape(data_set))
    centroids = np.mat(np.zeros((k, n)))
    # print('centroids', np.shape(centroids))
    for row in range(k):
        for index in range(n):
            range_min = np.min(data_set[:, index])
            range_of = np.max(data_set[:, index]) - range_min
            centroids[row, index] = range_min + np.random.uniform(range_of)
    # print('centroids\n', centroids)
    return centroids


def k_means(data_set, k, calc_dist, create_centroids=create_centroids):
    """
    k means 算法主体
    计算质心——分配——重新计算
    实现重点:
        part1: 通过对每个点遍历所有质心并计算点到每个置信的距离
        part2: 遍历所有质心并更新它们的取值
    :param data_set:数据集
    :param k:k个簇
    :param calc_dist: 指向计算距离的函数
    :param create_centroids: 指向创建质心的函数
    :return:
        centroids: 最终确定的质心
        cluster_ment: 聚类的情况(class, dist)  分配到哪个质心附近 以及dist距离
    """
    m, n = np.shape(data_set)
    cluster_ment = np.mat(np.zeros((m, 2)))
    centroids = create_centroids(data_set, k)
    print('init centriods', centroids)
    cluster_changed = True
    count = 0
    while cluster_changed:
        count += 1
        cluster_changed = False
        # step1 通过对每个点遍历所有质心并计算每个点到每个质心的距离
        for i in range(m):  # 循环每一个数据点并分配到最近的质心中去
            # print(np.inf, -1)
            min_dist = np.inf
            min_index = -1
            for j in range(k):
                dist_ji = calc_dist(data_set[i, :], centroids[j, :])     # 计算数据点到质心的最小距离
                if dist_ji < min_dist:      # 如果距离比最小距离还小, 就更新最小距离和最小质心的index
                    min_dist = dist_ji
                    min_index = j
            if cluster_ment[i, 0] != min_index:     # 簇分配结果改变
                cluster_changed = True
                cluster_ment[i, :] = min_index, min_dist**2     # 更新簇分配结果为最小质心的索引,最小距离
        # step2 遍历所有质心并更新它们的取值
        for cent in range(k):
            pts_in_cluster = data_set[np.nonzero(cluster_ment[:, 0].A == cent)[0]]   # 获取该簇中的所有点
            centroids[cent, :] = np.mean(pts_in_cluster, axis=0)        # 将质心修改为簇中所有点的平均值
            print('centriods changed', centroids)
    print('count', count)
    return centroids, cluster_ment

K-Means聚类算法缺陷

缺陷:当面对数据分布过于’散乱’的时候,以及初始质心选择不合理(靠的太近), k取值不合理(不好把握数据聚类中到底几类), 损失函数不合理
当出现上述情况的时候,k-means算法可能会陷入局部最优,而不是全局最优

解决问题的思路:

  • 什么时候才是全局最优?
  • 已知损失函数J(θ)=(y(i)y^)2
  • 有且仅当存在argminikNJ(θ)
  • 即: 所有样本所分配的质心与其损失函数之和 最小时才是达到了最优化

算法实现思路:

对生成后的簇进行处理
1.’大簇’,第k个质心点若是分配的样本数据点较多,就尝试将该簇分解为两个小簇并计算最大SSE 与没有改变之前相比较, 小就分解成功
2.对两个’小簇’进行合并,并计算SEE之和, 与未合并之前比较, 若是小于就合并成功,反之不需要分解

针对这一思路: 处理生成后的簇较为复杂,因此出现了K-Means算法的优化算法: b-k-means算法 二分k-Means算法

二分K-Means算法

解决问题: 为克服K-Means算法收敛于局部最小值的问题

优化思路;
在上述解决K-Means算法缺陷的思路中,我们是对应生成后的簇进行优化
那么如果在生成之前进行优化呢
即:算法一开始, 就将数据集看做一个大簇,然后再分解优化!!!
算法思路:
首先将所有点看做一个簇, 然后将该簇一分为二
之后选择其中的一个簇,继续划分
问题? 如果选择? 取决于对其划分是否可以最大程度的降低SSE的值
迭代上述步骤, 直到得到用户指定的簇数目

算法实现:

  • 以矩阵形式处理数据集 维度为mxn
  • 一开始将所有数据点看做一个簇, 因此初始化质心 该质心为所有数据点的均值
  • 创建维度为mx2的矩阵 记录分配的质心以及与相应质心的距离
  • 计算所有数据点到初始质心的距离平方误差

    当质心数量小于k时:
        遍历每一个质心
            调用K-Means算法 算法参数为(与该质心对应的样本数据集, k=2, 损失函数)====>二分K-Means算法
            返回本次二分的 质心与簇分配效果
            计算本次被二分的质心对于数据集的损失函数之和 与没有被二分的质心对应数据集的损失函数之和(2+1) ————————> 总的误差和越小,越相似, 效果越优化, 划分的效果更好
            记录本次优化的原质心index, 优化拆分出的质心集, 优化拆分出簇分配效果
        #更新最好的簇分配结果 调用K-means分配结构 最好的簇分配默认为0,1
        原本为1的更新为 len(centList)
        原本为0的更新为最佳质心
        # 更新质心列表
        更新原质心List中的第i个质心为使用二分K-Means算法优化后的第1个质心
        更新原质心List中的第2个质心为二分K-Means算法优化后的第2个质心
        重新分配最好簇下的数据(质心)以及SSE
    返回 质心 与 簇分配结果
    

算法实现 以及测试

def biKMeans(data_set, k, calc_dist=calc_dist_sse):
    m = np.shape(data_set)[0]
    clusterAssment = np.mat(np.zeros((m,2)))  # 保存每个数据点的簇分配结果和平方误差
    centroid0 = np.mean(data_set, axis=0).tolist()[0]  # 质心初始化为所有数据点的均值
    centList =[centroid0]  # 初始化只有 1 个质心的 list
    for j in range(m):  # 计算所有数据点到初始质心的距离平方误差
        clusterAssment[j,1] = calc_dist(np.mat(centroid0), data_set[j,:])**2
    while (len(centList) < k):  # 当质心数量小于 k 时
        lowestSSE = np.inf
        for i in range(len(centList)):  # 对每一个质心
            ptsInCurrCluster = data_set[np.nonzero(clusterAssment[:,0].A==i)[0],:] # 获取当前簇 i 下的所有数据点
            centroidMat, splitClustAss = k_means(ptsInCurrCluster, 2, calc_dist) # 将当前簇 i 进行二分 kMeans 处理
            sseSplit = sum(splitClustAss[:,1]) # 将二分 kMeans 结果中的平方和的距离进行求和
            sseNotSplit = sum(clusterAssment[np.nonzero(clusterAssment[:,0].A!=i)[0],1]) # 将未参与二分 kMeans 分配结果中的平方和的距离进行求和
            print("sseSplit, and notSplit: ",sseSplit,sseNotSplit)
            if (sseSplit + sseNotSplit) < lowestSSE: # 总的(未拆分和已拆分)误差和越小,越相似,效果越优化,划分的结果更好(注意:这里的理解很重要,不明白的地方可以和我们一起讨论)
                bestCentToSplit = i
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit
        # 找出最好的簇分配结果
        bestClustAss[np.nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) # 调用二分 kMeans 的结果,默认簇是 0,1. 当然也可以改成其它的数字
        bestClustAss[np.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] # 更新原质心 list 中的第 i 个质心为使用二分 kMeans 后 bestNewCents 的第一个质心
        centList.append(bestNewCents[1,:].tolist()[0]) # 添加 bestNewCents 的第二个质心
        clusterAssment[np.nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss # 重新分配最好簇下的数据(质心)以及SSE
    return np.mat(centList), clusterAssment


def main():
    filename = 'testSet.txt'
    data_set = load_data(filename)
    print('mean data set ', np.mean(data_set, axis=0).tolist()[0])
    # print('*****data set*****\n', data_set)
    # create_centroids(data_set, k=3)
    #
    # centriods, cluster_ment = k_means(data_set, k=3, calc_dist=calc_dist_sse, create_centroids=create_centroids)
    # print('result centriods,', centriods)
    # print("result cluster_ment", cluster_ment)
    centroids, cluster_ment = biKMeans(data_set, 3, calc_dist=calc_dist_sse)
    print('centroids', centroids)
    print('cluster ment', cluster_ment)

if __name__ == '__main__':
    main()

参考文献
《机器学习实战》

阅读更多
换一批

没有更多推荐了,返回首页