手写K-means及K-means++经典算法及实战
前段时间在学校,看了一篇关于K-means-u的聚类论文,当时对聚类只是听过,但对许多经典算法和练习都不够,所以今天专门记录一下,当然也查阅了网上许多资料,如果本文哪有纰漏,欢迎各位的批评和建议
关于K-means和K-means++的算法流程,我这里就不细讲了,之前做过一个PPT,点击下方链接即可查看
https://slides.com/huozhang/clusting/fullscreen
K-means
# 手写k-means算法
# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
def distance(dec1, dec2) -> float:
return np.sqrt(np.sum(np.square(dec1 - dec2))) # 计算欧式距离
def K_Means(data, k):
K = np.random.uniform(0, 10, (k, data.shape[1])) # 初始化中心点位矩阵
ret = np.zeros([data.shape[0], data.shape[1]]) # 构造一个答案矩阵
flag = True # 定义标记变量
count = 1
while flag:
flag = False
for i in range(data.shape[0]):
minIndex = -1 # 定义得到最短距离的时候的临时中心点位
minDis = np.inf # 定义最短距离
for j in range(K.shape[0]):
dis = distance(data[i], K[j]) # 计算距离
if dis < minDis:
minDis = dis
minIndex = j
# 计算完成后 将所属距离点位填入答案矩阵
ret[i][0] = minDis
ret[i][1] = minIndex
# 计算好位置了,现在开始重新计算均值,查询中心点
for i in range(k):
cluster = data[ret[:, 1] == i]
# print("中心点位——————", cluster)
if len(cluster) == 0:
pass
else:
center = np.mean(cluster, axis=0)
# print("center 为", center)
# print("K[i] 为", K[i])
if (center == K[i]).all(): # 这里必须用all()因为 ndarry()的性质 具体大家可以百度
pass
else:
K[i] = center
flag = True
_X = K[:, 0]
_Y = K[:, 1]
plt.scatter(_x, _y)
plt.scatter(_X, _Y, marker='X', color='r')
plt.title("%d of convergence" % count)
count += 1
plt.show()
if __name__ == '__main__':
# 构造三簇数据
data1 = np.random.uniform(0, 2, (10, 2))
data2 = np.random.uniform(3, 6, (10, 2))
data3 = np.random.uniform(8, 10, (10, 2))
data = np.r_[data1, data2, data3] # 按列上下合并数据 np.c_[]是按行 左右合并
_x = data[:, 0]
_y = data[:, 1]
plt.rcParams['font.sans-serif'] = 'SimHei'
# plt.scatter(_x, _y, marker="o")
# plt.show()
K_Means(data, 3) # 自定义定义3个中心
代码进行了可视化展示,可以看看每次收敛效果
K-means++
主要解决K-means初始点位选择问题,k-means++即基本解决了这一问题,点位选择完成过后,进行相应的收敛和迭代,以减少迭代次数和SSE(最大平方误差和)
# 手写k-means++算法
# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
import random
import math
def euler_distance(point1, point2) -> float:
"""
计算两点之间的欧拉距离,支持多维
"""
distance = 0.0
for a, b in zip(point1, point2): # 将x,y对应成元组进行计算
distance += math.pow(a - b, 2)
return math.sqrt(distance)
def distance(point, cluster) -> float:
min_dist = math.inf
for i, centroid in enumerate(cluster):
dist = euler_distance(centroid, point) # 计算每一个点到每个簇的距离
if dist < min_dist:
min_dist = dist
return min_dist # 返回离簇最近的距离
def KMeansplus(data, k) -> list:
"""
K-means++ 主要解决K-means的初始点位选择问题,返回点位后,再进行收敛,此函数仅完成簇点选取
:param data: 数据集(测试集)
:param k: 簇个数
:return: 返回簇x,y列表
"""
cluster_list = []
cluster_list.append(random.choice(data).tolist())
ret = [0 for _ in range(len(data))] # 构造距离空列表
# print(ret)
for _ in range(1, k): # 这里从第二个点开始,因为第一个点是随机的
sum_dis = 0
for i, point in enumerate(data):
ret[i] = distance(point, cluster_list)
sum_dis += ret[i] # 累加距离
sum_dis *= random.random() # 利用轮盘法*[0~1]里面的随机数
for i, point_dis in enumerate(ret):
sum_dis -= point_dis # 依次减距离
if sum_dis <= 0:
cluster_list.append(data[i].tolist()) # 直到sum_dis为0时 将此点作为第二个点
break
return cluster_list
if __name__ == '__main__':
# 构造三簇数据
data1 = np.random.uniform(0, 2, (20, 2))
data2 = np.random.uniform(3, 6, (20, 2))
data3 = np.random.uniform(8, 10, (20, 2))
data = np.r_[data1, data2, data3] # 按列上下合并数据 np.c_[]是按行 左右合并
np.random.shuffle(data)
_x = data[:, 0]
_y = data[:, 1]
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.scatter(_x, _y, marker="o")
center = KMeansplus(data, 3) # 自定义定义3个中心
# print("k-means++ 的中心为\n", center)
center = np.array(center)
center_x = center[:, 0]
center_y = center[:, 1]
plt.scatter(center_x, center_y, marker='X', c='r')
plt.show()
可以看见在点位选择上基本和以完成收敛的点位非常接近,之后再通过几次收敛达到最佳