KMeans算法
1.概述
K-means聚类算法也称k均值聚类算法,是集简单和经典于一身的基于距离的聚类算法。它采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。该算法认为类簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。
2.核心思想
K-means聚类算法是一种迭代求解的聚类分析算法,其步骤是随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
3.实现步骤
1、首先确定一个k值,即我们希望将数据集经过聚类得到k个集合。
2、从数据集中随机选择k个数据点作为质心。
3、对数据集中每一个点,计算其与每一个质心的距离,离哪个质心近,就划分到那个质心所属的集合。
4、把所有数据归好集合后,一共有k个集合。然后重新计算每个集合的质心。
5、如果新计算出来的质心和原来的质心之间的距离小于某一个设置的阈值(表示重新计算的质心的位置变化不大,趋于稳定,或者说收敛),我们可以认为聚类已经达到期望的结果,算法终止。
6、如果新质心和原质心距离变化很大,需要迭代3~5步骤。
4.距离公式
公式不好写,就采用截图的方式
5.Python 实现KMeans算法
- 实现生成初始聚类中心函数
# 输入:dot数据集, 聚类个数
# 输出:k个随机质心的矩阵
def setCentroid(k, dot):
# 列的个数
dots = np.shape(dot)[1]
# 质心的存储形式
centroids = np.mat(np.zeros([k, dots]))
# 遍历所有列
for j in range(dots):
minJ = min(dot[:, j])
maxJ = max(dot[:, j])
centroids[:, j] = minJ + (maxJ - minJ) * np.random.random([k, 1])
# 填充质心矩阵的第i列
# dots次循环完毕,质心矩阵填充完成
return centroids
- 实现计算距离函数(我采用的是欧式距离公式,其他计算距离的公式也可以)
# 欧式距离公式
def twoDistance(valA, valB):
return np.sqrt(np.sum(np.power(valA - valB, 2)))
- 实现KMeans算法的具体函数(核心)
def kmeans(k, dot):
# 样本个数
m = np.shape(dot)[0]
# 保存每个样本的聚类情况,第一列表示该样本属于某一类,第二列是与聚类中心的距离
cluster = np.mat(np.zeros([m, 2]))
# 调用函数产生随机质心
centroids = setCentroid(k, dot)
b = True
while b:
b = False
# 遍历样本
for i in range(m):
# 每个样本与每个质心计算距离
# 采用一趟冒泡排序找出最小的距离,并找出对应的类
# 计算与质心的距离时,刚开始需要比较,记为无穷大
mindist = np.inf
minj = -1
# 遍历类
for j in range(k):
distj = twoDistance(dot[i, :], centroids[j, :])
if distj < mindist:
mindist = distj
minj = j
# 遍历完k个类,本次样本已聚类
# 判断本次聚类结果和上一次是否一致
if cluster[i, 0] != minj:
# 只要有一个聚类结果改变,就重新迭代
b = True
# 类别,与距离
cluster[i, :] = minj , np.power(mindist, 2)
# 外层循环结束,每一个样本都有了聚类结果
# 更新质心
for cent in range(k):
# 获得距离同一个质心最近的所有点的下标,即同一簇的坐标
data_cent = dot[np.nonzero(cluster[:, 0].A == cent)[0]]
# 求同一簇的坐标平均值,axis=0表示按列求均值
centroids[cent, :] = np.mean(data_cent, axis=0)
return centroids, cluster
- 实现数据可视化函数
def show(dot, k, cluster, centroids):
plt.figure()
plt.title("K-means")
data = []
# 提取出每个簇的数据
for cent in range(k):
# 获得属于cent簇的数据
ptsInClust = dot[np.nonzero(cluster[:, 0].A == cent)[0]]
data.append(ptsInClust)
for cent, c, marker in zip(range(k), ['r','g', 'b', 'y'], ['^', 'o', '*', 's']):
# 数据集的行
X, Y = getXY(data[cent])
plt.scatter(X, Y, s=80, c=c, marker=marker)
centroidsX, centroidsY = getXY(centroids)
# 画出质心点
plt.scatter(centroidsX, centroidsY, s=1000, c='black', marker='+', alpha=1)
plt.show()
- 实现获取散点图的坐标
def getXY(dot):
m = np.shape(dot)[0]
X = []
Y = []
for i in range(m):
X.append(dot[i, 0])
Y.append(dot[i, 1])
xaixa = np.array(X)
yaixa = np.array(Y)
return xaixa, yaixa
- 完整代码
import random
import numpy as np
import matplotlib.pyplot as plt
import operator
import time
# 输入:数据集, 聚类个数
# 输出:k个随机质心的矩阵
def setCentroid(k, dot):
# 列的个数
dots = np.shape(dot)[1]
# 质心的存储形式
centroids = np.mat(np.zeros([k, dots]))
for j in range(dots):
minJ = min(dot[:, j])
maxJ = max(dot[:, j])
centroids[:, j] = minJ + (maxJ - minJ) * np.random.random([k, 1])
# 填充质心矩阵的第i列
# dots次循环完毕,质心矩阵填充完成
return centroids
# 欧式距离公式
def twoDistance(valA, valB):
return np.sqrt(np.sum(np.power(valA - valB, 2)))
def kmeans(k, dot):
# 样本个数
m = np.shape(dot)[0]
# 保存每个样本的聚类情况,第一列表示该样本属于某一类,第二列是与聚类中心的距离
cluster = np.mat(np.zeros([m, 2]))
# 调用函数产生随机质心
centroids = setCentroid(k, dot)
b = True
while b:
b = False
# 遍历样本
for i in range(m):
# 每个样本与每个质心计算距离
# 采用一趟冒泡排序找出最小的距离,并找出对应的类
# 计算与质心的距离时,刚开始需要比较,记为无穷大
mindist = np.inf
minj = -1
# 遍历类
for j in range(k):
distj = twoDistance(dot[i, :], centroids[j, :])
if distj < mindist:
mindist = distj
minj = j
# 遍历完k个类,本次样本已聚类
# 判断本次聚类结果和上一次是否一致
if cluster[i, 0] != minj:
# 只要有一个聚类结果改变,就重新迭代
b = True
# 类别,与距离
cluster[i, :] = minj , np.power(mindist, 2)
# 外层循环结束,每一个样本都有了聚类结果
# 更新质心
for cent in range(k):
# 获得距离同一个质心最近的所有点的下标,即同一簇的坐标
data_cent = dot[np.nonzero(cluster[:, 0].A == cent)[0]]
# 求同一簇的坐标平均值,axis=0表示按列求均值
centroids[cent, :] = np.mean(data_cent, axis=0)
return centroids, cluster
def getXY(dot):
m = np.shape(dot)[0]
X = []
Y = []
for i in range(m):
X.append(dot[i, 0])
Y.append(dot[i, 1])
xaixa = np.array(X)
yaixa = np.array(Y)
return xaixa, yaixa
def show(dot, k, cluster, centroids):
plt.figure()
plt.title("K-means")
data = []
# 提取出每个簇的数据
for cent in range(k):
# 获得属于cent簇的数据
ptsInClust = dot[np.nonzero(cluster[:, 0].A == cent)[0]]
data.append(ptsInClust)
for cent, c, marker in zip(range(k), ['r','g', 'b', 'y'], ['^', 'o', '*', 's']):
# 数据集的行
X, Y = getXY(data[cent])
plt.scatter(X, Y, s=80, c=c, marker=marker)
centroidsX, centroidsY = getXY(centroids)
# 画出质心点
plt.scatter(centroidsX, centroidsY, s=1000, c='black', marker='+', alpha=1)
plt.show()
if __name__ == "__main__":
k = 3
dot = np.random.randint(1, 20 ,[200, 2])
centroids, cluster = kmeans(k, dot)
show(dot, k, cluster, centroids)
6.运行结果展示
7.总结
- KMeans的缺点:
1 k的选取不好把握;
2 对于不是凸的数据集比较难以收敛;
3.如果数据的类型不平衡,比如数据量严重失衡或者类别的方差不同,则聚类效果不佳;
4.采用的是迭代的方法,只能得到局部最优解;
5.对于噪音和异常点比较敏感。 - KMeans的优点:
1.原理简单,实现方便,收敛速度快;
2.聚类效果较优;
3.模型的可解释性较强;
4.调参只需要簇数k; - 自我总结:
在学习之前,我只是大概知道kmeans算法的简单步骤,感觉是挺简单的,但是当我用代码实现的时候,才发现需要注意许多问题,比如参数的类型,还有如何实现这个流程才能让他更加优化,运行速度才会更快。
本代码还有需要改进的地方,例如在可视化的时候,如何的到更多的颜色,我这里定义了4个,可是更多的时候怎么解决呢?这些都是需要深入的去学习的。