k-means简介

k-means是无监督学习下的一种聚类算法,简单说就是不需要数据标签,仅靠特征值就可以将数据分为指定的几类。k-means算法的核心就是通过计算每个数据点与k个质心(或重心)之间的距离,找出与各质心距离最近的点,并将这些点分为该质心所在的簇,从而实现聚类的效果。

k-means具体步骤



1.指定要把数据聚为几类,确定k值;

2.从数据点中随机选择k个点,作为k个簇的初始质心;

3.计算数据点与各质心之间的距离,并将最近的质心所在的簇作为该数据点所属的簇;

4.计算每个簇的数据点的平均值,并将其作为新的质心;

5.重复步骤2-4,直到所有簇的质心不再发生变化,或达到最大迭代次数。

可以看出,k-means算法的步骤很清晰,而且易于实现。虽然这里的k值感觉像是拍脑袋得到的,但是后面会介绍Elbow方法,通过评估得到合理的k值。我们先来感受一下k-means的效果。

代码实现

  1. import random
  2. import math
  3. import matplotlib.pyplot as plt
  4. class KMeans:
  5. def __init__(self, n_clusters):
  6. self.n_clusters = n_clusters
  7. self.centroid_list = []
  8. self.predict = []
  9. def get_rand_centroid(self, X):
  10. # 随机取质心
  11. centroid_list = []
  12. while len(centroid_list) < self.n_clusters:
  13. d = int(random.random() * len(X))
  14. if X[d] not in centroid_list:
  15. centroid_list.append(X[d])
  16. return centroid_list
  17. @staticmethod
  18. def get_distance(point, C):
  19. # 计算两点间距离(欧式距离)
  20. return math.sqrt((point[0]-C[0])**2 + (point[1]-C[1])**2)
  21. def get_distributed(self, X):
  22. # 计算每个点距离最近的质心,并将该点划入该质心所在的簇
  23. dis_list = [[] for k in range(self.n_clusters)]
  24. for point in X:
  25. distance_list = []
  26. for C in self.centroid_list:
  27. distance_list.append(self.get_distance(point, C))
  28. min_index = distance_list.index(min(distance_list))
  29. dis_list[min_index].append(point)
  30. return dis_list
  31. def get_virtual_centroid(self, distributed):
  32. # 如果有空集,则取其他两个质心的坐标均值
  33. if [] in distributed:
  34. index = distributed.index([])
  35. v_centroid_list = self.centroid_list.copy()
  36. # 去除空集对应的质心
  37. v_centroid_list.pop(index)
  38. # 计算其余两个质心的坐标均值
  39. x = []
  40. y = []
  41. for C in v_centroid_list:
  42. x.append(C[0])
  43. y.append(C[1])
  44. v_centroid_list.insert(index, [round(sum(x) / len(x)), round(sum(y) / len(y))])
  45. else:
  46. # 计算每个簇所有点的坐标的算数平均,作为虚拟质心
  47. v_centroid_list = []
  48. for distribution in distributed:
  49. x = []
  50. y = []
  51. for point in distribution:
  52. x.append(point[0])
  53. y.append(point[1])
  54. v_centroid_list.append([sum(x)/len(x), sum(y)/len(y)])
  55. return v_centroid_list
  56. def fit_predict(self, X):
  57. self.centroid_list = self.get_rand_centroid(X)
  58. while True:
  59. # 聚类
  60. distributed = self.get_distributed(X)
  61. # 计算虚拟质心
  62. v_centroid_list = self.get_virtual_centroid(distributed)
  63. # 如果两次质心相同,说明聚类结果已定
  64. if sorted(v_centroid_list) == sorted(self.centroid_list):
  65. break
  66. # 否则继续训练
  67. self.centroid_list = v_centroid_list
  68. # 对结果按照数据集顺序进行分类
  69. predict = []
  70. for point in X:
  71. i = 0
  72. for dis in distributed:
  73. if point in dis:
  74. predict.append(i)
  75. i += 1
  76. self.predict = predict
  77. return predict
  78. def plot_clustering(self, X):
  79. x = []
  80. y = []
  81. for point in X:
  82. x.append(point[0])
  83. y.append(point[1])
  84. plt.scatter(x, y, c=self.predict, marker='x')
  85. plt.show()
  86. # 样本集合
  87. X = [[0.0888, 0.5885],
  88. [0.1399, 0.8291],
  89. [0.0747, 0.4974],
  90. [0.0983, 0.5772],
  91. [0.1276, 0.5703],
  92. [0.1671, 0.5835],
  93. [0.1306, 0.5276],
  94. [0.1061, 0.5523],
  95. [0.2446, 0.4007],
  96. [0.1670, 0.4770],
  97. [0.2485, 0.4313],
  98. [0.1227, 0.4909],
  99. [0.1240, 0.5668],
  100. [0.1461, 0.5113],
  101. [0.2315, 0.3788],
  102. [0.0494, 0.5590],
  103. [0.1107, 0.4799],
  104. [0.1121, 0.5735],
  105. [0.1007, 0.6318],
  106. [0.2567, 0.4326],
  107. [0.1956, 0.4280]
  108. ]
  109. # 初始化kmeans分类器
  110. km = KMeans(3)
  111. # 预测
  112. predict = km.fit_predict(X)
  113. print(predict)
  114. # 绘图
  115. km.plot_clustering(X)

聚类结果如下:



我们采用了几场篮球比赛的球员技术统计数据作为样本,x轴表示助攻数据,y轴表示得分数据,从初步的聚类中,我们可以看出,右下角的一类可看做是助攻很多,而得分较少的球员;左侧这类可看做是助攻较少,但得分相对多一些的球员;而最上面独立的那个类,可看做是得分最多,而且助攻也不少的球员,可理解成MVP球员。这样一来,我们就赋予了这些聚类实际的意义。感觉这个分类结果也挺不错的。

但多跑几次,会发现有时候的分类结果会不太一样,例如没有将MVP球员作为单独的一类:



可以看出,如果首次随机出的质心挨得太近,会导致分类结果不理想(蓝色向下的箭头表示随机的初始质心)。

k-means++

为了解决初始质心不合理而导致分类不准确的问题,我们可以优化质心初始化的步骤,使各个质心相距尽可能的远。k-means++ 算法就是一种为 k-means 寻找初始化质心的算法。它由David Arthur 和 Sergei Vassilvitskii 于2007年提出(论文地址),主要是对之前步骤1进行进一步优化,具体步骤如下:

1a.随机选出一个点作为质心;

1b.通过计算概率,得到新的质心。其中概率的计算方法如下:



1c.重复1b步,直到凑齐k个质心。

注:原作论文中并未提及如何根据概率值选择质心,我看网上有很多文章使用了“轮盘法”挑选质心,就是计算出各点的概率后,再计算出各点的累计概率值,然后随机一个概率值r,将r落入累计概率区间对应的点作为质心。这里就粗暴一点,直接选取概率最大的那个点(与当前质心最远的点)作为质心。

  1. def k_means_plus(self, X):
  2. centroid_list = []
  3. # 随机选出第一个点
  4. random_1 = int(random.random() * len(X))
  5. centroid_list.append(X[random_1])
  6. while len(centroid_list) < self.n_clusters:
  7. distance_list = []
  8. for x in X:
  9. tmp_list = []
  10. for C in centroid_list:
  11. tmp_list.append(pow(self.get_distance(x, C), 2))
  12. # 取距离当前质心最近的距离D(X)^2
  13. distance_list.append(min(tmp_list))
  14. # 选取D(X)^2最大的点作为下个质心(概率计算公式中分母相同,可略去)
  15. max_index = distance_list.index(max(distance_list))
  16. centroid_list.append(X[max_index])
  17. return centroid_list

直接拿这个函数替换之前的get_rand_centroid()方法即可。聚类效果如下:



三个初始质心都有较远的距离,从而保证了聚类的准确性。

WCSS - k-means算法的评估标准

由于k-means是一种无监督学习方法,没有一种严格的标准来衡量聚类结果的性能,大部分情况下都是根据人的经验来判断,但是如果数据超过三维,无法可视化的话,就比较尴尬了。所以还是需要有一种方法能够评估k-means的性能。之前不是通过计算距离选取质心吗,我们便可以用所有点到其所属质心的距离加和作为一种衡量方式,这就是WCSS方法(Within-Cluster Sum of Squares),WCSS方法可以将性能进行量化,对于相同的k,WCSS越小,代表总体性能越好。所以我们可以进行N轮训练,取WCSS最小的那个作为最终的聚类。

直接在fit_predict()方法中加入WCSS的计算即可:

  1. def fit_predict(self, X, N=10):
  2. # 进行N轮训练(默认进行10轮)
  3. train_num = 0
  4. # 存放质心与聚类结果
  5. WCSS_dict = {}
  6. while train_num < N:
  7. # self.centroid_list = self.get_rand_centroid(X)
  8. # 使用k-means++算法选出初始质心
  9. self.centroid_list = self.k_means_plus(X)
  10. # 记录初始质心
  11. self.initial_list = self.centroid_list
  12. while True:
  13. # 聚类
  14. distributed = self.get_distributed(X)
  15. # 计算虚拟质心
  16. v_centroid_list = self.get_virtual_centroid(distributed)
  17. # 如果两次质心相同,说明聚类结果已定
  18. if sorted(v_centroid_list) == sorted(self.centroid_list):
  19. break
  20. # 否则继续训练
  21. self.centroid_list = v_centroid_list
  22. # 对结果按照数据集顺序进行分类
  23. predict = []
  24. WCSS = 0
  25. for point in X:
  26. i = 0
  27. for dis in distributed:
  28. if point in dis:
  29. predict.append(i)
  30. # 计算当前点到其质心的距离,平方后再累加到WCSS中
  31. WCSS += pow(self.get_distance(point, self.centroid_list[i]), 2)
  32. i += 1
  33. WCSS_dict[WCSS] = [self.centroid_list, predict]
  34. print("第" + str(train_num+1) + "轮的WCSS为:" + str(WCSS))
  35. train_num += 1
  36. # 选出WCSS最小的那个作为最终的聚类
  37. min_WCSS = min(WCSS_dict.keys())
  38. last_predict = WCSS_dict[min_WCSS][1]
  39. self.predict = last_predict
  40. return last_predict, min_WCSS
  41. # 执行结果:
  42. 1轮的WCSS为:0.04830321200000001
  43. 2轮的WCSS为:0.04830321200000001
  44. 3轮的WCSS为:0.04830321200000001
  45. 4轮的WCSS为:0.04830321200000001
  46. 5轮的WCSS为:0.04751589380952381
  47. 6轮的WCSS为:0.04830321200000001
  48. 7轮的WCSS为:0.04751589380952381
  49. 8轮的WCSS为:0.04830321200000001
  50. 9轮的WCSS为:0.04751589380952381
  51. 10轮的WCSS为:0.04830321200000001



图中用红框标出的点,之前被归为得分较出色的球员,现在被归为了助攻较出色的球员。(红色的*点标注的是最终的质心)

Elbow方法 - 确定k的好办法

最后,我们再来讲一下关于k的取值问题。随着k的增大,即质心的增多,整体的WCSS是逐渐减小的(因为每个点能找到与其距离更近的质心的概率变大了),所以我们可以通过不断增大k,来观察整体的性能:

  1. def elbow(self, k_range=10):
  2. # 通过elbow方法选择最佳k值
  3. x = []
  4. y = []
  5. for k in range(1, k_range+1):
  6. self.n_clusters = k
  7. x.append(k)
  8. # 取出不同k值下的WCSS
  9. _, min_WCSS = self.fit_predict(X)
  10. y.append(min_WCSS)
  11. plt.plot(x, y)
  12. plt.xlabel('k')
  13. plt.ylabel('WCSS')
  14. plt.show()



从图中可以发现,k从1到4的过程中,聚类性能有着明显的提升,但是再继续增加聚类的个数,性能提升的幅度就没有之前那么明显了,而且,聚类个数太多,也会让数据集变得过于分散,所以,在本例中,k=4是较好的选择。说明一开始根据经验取的k=3并不是最优的。我们再跑一下k=4的聚类:



通过最终的聚类,我们发现,算法将之前得分较多的球员又细分为了两类,黄色这类的得分要比绿色这类更高,所以可看做是真正得分多的球员;而绿色这类的得分并不出众,助攻也不算高,可看做是表现较为一般的球员(毕竟不可能所有的球员都有好的表现)。这样的分类从整体来看也更加符合实际情况。

机器学习 - k-means聚类的更多相关文章

  1. 吴裕雄 python 机器学习——K均值聚类KMeans模型

    import numpy as np import matplotlib.pyplot as plt from sklearn import cluster from sklearn.metrics ...

  2. 机器学习算法与Python实践之(五)k均值聚类(k-means)

    机器学习算法与Python实践这个系列主要是参考<机器学习实战>这本书.因为自己想学习Python,然后也想对一些机器学习算法加深下了解,所以就想通过Python来实现几个比较常用的机器学 ...

  3. 机器学习算法与Python实践之(六)二分k均值聚类

    http://blog.csdn.net/zouxy09/article/details/17590137 机器学习算法与Python实践之(六)二分k均值聚类 zouxy09@qq.com http ...

  4. 机器学习实战5:k-means聚类:二分k均值聚类+地理位置聚簇实例

    k-均值聚类是非监督学习的一种,输入必须指定聚簇中心个数k.k均值是基于相似度的聚类,为没有标签的一簇实例分为一类. 一 经典的k-均值聚类 思路: 1 随机创建k个质心(k必须指定,二维的很容易确定 ...

  5. 机器学习之路:python k均值聚类 KMeans 手写数字

    python3 学习使用api 使用了网上的数据集,我把他下载到了本地 可以到我的git中下载数据集: https://github.com/linyi0604/MachineLearning 代码: ...

  6. 机器学习之K均值聚类

      聚类的核心概念是相似度或距离,有很多相似度或距离的方法,比如欧式距离.马氏距离.相关系数.余弦定理.层次聚类和K均值聚类等 1. K均值聚类思想   K均值聚类的基本思想是,通过迭代的方法寻找K个 ...

  7. 100天搞定机器学习|day44 k均值聚类数学推导与python实现

    [如何正确使用「K均值聚类」? 1.k均值聚类模型 给定样本,每个样本都是m为特征向量,模型目标是将n个样本分到k个不停的类或簇中,每个样本到其所属类的中心的距离最小,每个样本只能属于一个类.用C表示 ...

  8. 机器学习实战---K均值聚类算法

    一:一般K均值聚类算法实现 (一)导入数据 import numpy as np import matplotlib.pyplot as plt def loadDataSet(filename): ...

  9. 机器学习六--K-means聚类算法

    机器学习六--K-means聚类算法 想想常见的分类算法有决策树.Logistic回归.SVM.贝叶斯等.分类作为一种监督学习方法,要求必须事先明确知道各个类别的信息,并且断言所有待分类项都有一个类别 ...

  10. 机器学习理论与实战(十)K均值聚类和二分K均值聚类

    接下来就要说下无监督机器学习方法,所谓无监督机器学习前面也说过,就是没有标签的情况,对样本数据进行聚类分析.关联性分析等.主要包括K均值聚类(K-means clustering)和关联分析,这两大类 ...

随机推荐

  1. 我的新书《C++服务器开发精髓》终于出版啦

    一.千呼万唤始出来 亲爱的各位读者,我的新书<C++ 服务器开发精髓>终于终于终于与大家见面了,图书如下: 图书的封面设计很精美,当然内容一定不负众望.因出版社老师要求提供一张照片放到封面 ...

  2. Python常用数据结构(列表)

    Python中常用的数据结构有序列(如列表,元组,字符串),映射(如字典)以及集合(set),是主要的三类容器 内容 序列的基本概念 列表的概念和用法 元组的概念和用法 字典的概念和用法 各类型之间的 ...

  3. Swagger/OpenAPI By Swashbuckle在NetCore 3.1中较NetCore 2.2使用的注意事项及入门

    方案选择 使用Web API时,了解其各种方法对开发人员来说可能是一项挑战. Swagger也称为OpenAPI(Open Application Programming Interface,开放应用 ...

  4. explicit 关键字 禁止隐式转换

    explicit可以抑制内置类型隐式转换,所以在类的构造函数中,使用explicit关键字,防止不必要的隐式转换

  5. Docker:Docker部署postgresql数据库

    环境 Centos-7  Postgresql-10 docker-19 yum加载Postgresql yum install https://download.postgresql.org/pub ...

  6. Flyway:Spring Boot中使用Flyway来管理数据库版本

    Flyway简介 Flyway是一个简单开源数据库版本控制器(约定大于配置),主要提供migrate.clean.info.validate.baseline.repair等命令.它支持SQL(PL/ ...

  7. ESP32-OTA升级

    基于ESP-IDF4.1 1 #include <string.h> 2 #include "freertos/FreeRTOS.h" 3 #include " ...

  8. Python小白的数学建模课-B6. 新冠疫情 SEIR 改进模型

    传染病的数学模型是数学建模中的典型问题,常见的传染病模型有 SI.SIR.SIRS.SEIR 模型. SEIR 模型考虑存在易感者.暴露者.患病者和康复者四类人群,适用于具有潜伏期.治愈后获得终身免疫 ...

  9. TCP连接的11种状态

    传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的.可靠的.基于字节流的传输层通信协议.TCP协议主要针对三次握手建立连接和四次挥手断开连接,其中包括了 ...

  10. 中国剩余定理简析(python实现)

    中国剩余定理CRT 正整数m1,m2,...,mk两两互素,对b1,b2,...,bk的同余式组为 \[\begin{cases} x \equiv b_1\; mod \;m_1\\ x \equi ...