数据挖掘十大算法(十):K-means聚类算法
发布时间
阅读量:
阅读量
一,K-means算法原理
基本算法
K-means是一种广泛使用的聚类技术,在机器学习领域具有重要地位。该过程接受一个数据集合作为输入,并通过迭代优化的方式将其划分为若干类别。具体来说,在每一步骤中首先确定需要划分的类别数量k,并随机选择初始质心位置;接着建立组织结构,并将每个数据实例分配给离它最近的质心;随后重新计算所有质心的位置;最后检查质心位置是否有变化。若发生变动则继续迭代;否则结束过程。
初始中心点的选取
确定初始中心点的过程对于聚类结果具有重要影响。实验证明了不同的初始中心点可能导致不同的聚类效果。在选择合适的初始中心点时,请考虑以下原则:
初始中心点之间的间距应该较大。因此,可以采取的策略是:
第一步:计算所有样本点两两之间的距离,并选择距离最大的两个样本点对作为初始中心点。然后将这两个样本移除。
step2:如果初始中心点个数满足k个,则退出该过程;否则,在剩余样本中选择C3

这个问题属于双目标优化领域。通过约束其中一个变量或参数来实现,并使另一个指标达到其极值状态。从而可以选择一个合适的C3点作为第3个初始中心点。
如果要寻找第4个初始中心点,思路和寻找第3个初始中心点是相同的。
误差平方和(Sum of Squared Error)
误差平法和,SSE,用于评价聚类的结果的好坏,SSE的定义如下。

通常情况下,在数据集的不同类别中引入更多的划分线会减少Sum of Squared Errors(SSE)。当将类别数量设置为等于样本总数时(即每个样本单独成为一个类别),此时每个类别仅包含其自身的一个数据点作为中心点,则Sum of Squared Errors(SSE)达到最小值零。
k值的确定
通常来说,在2到10之间的范围内取值时不会导致过大的问题。进而可以通过绘制SSE-k曲线图来确定合适的k值位置。

可以看到,k=5之后,SSE下降的变得很缓慢了,因此最佳的k值为5。
二,基本原理的Python实现
# K-means Algorithm is a clustering algorithm
import numpy as np
import matplotlib.pyplot as plt
import random
def get_distance(p1, p2):
diff = [x - y for x, y in zip(p1, p2)]
distance = np.sqrt(sum(map(lambda x: x ** 2, diff)))
return distance
# 计算多个点的中心
def calc_center_point(cluster):
N = len(cluster)
m = np.array(cluster).transpose().tolist() # m的shape是(2, N)
center_point = [sum(x) / N for x in m] # 这里其实就是分别对x,y求平均
return center_point
# 检查两个点是否有差别
def check_center_diff(center, new_center):
n = len(center)
for c, nc in zip(center, new_center):
if c != nc:
return False
return True
# K-means算法的实现
def K_means(points, center_points):
N = len(points) # 样本个数
n = len(points[0]) # 单个样本的维度
k = len(center_points) # k值大小
tot = 0
while True: # 迭代
temp_center_points = [] # 记录中心点
clusters = [] # 记录聚类的结果
for c in range(0, k):
clusters.append([]) # 初始化
# 针对每个点,寻找距离其最近的中心点(寻找组织)
for i, data in enumerate(points):
distances = []
for center_point in center_points:
distances.append(get_distance(data, center_point))
index = distances.index(min(distances)) # 找到最小的距离的那个中心点的索引,
clusters[index].append(data) # 那么这个中心点代表的簇,里面增加一个样本(要理解这里)
tot += 1
print('Epoch:{} Clusters:{}'.format(tot, len(clusters)))
k = len(clusters)
colors = ['r.', 'g.', 'b.', 'k.', 'y.'] # 颜色和点的样式
for i, cluster in enumerate(clusters):
data = np.array(cluster)
data_x = [x[0] for x in data]
data_y = [x[1] for x in data]
plt.subplot(2, 3, tot)
plt.plot(data_x, data_y, colors[i])
plt.axis([0, 1000, 0, 1000])
# 重新计算中心点(该步骤可以与下面判断中心点是否发生变化这个步骤,调换顺序)
for cluster in clusters:
temp_center_points.append(calc_center_point(cluster))
# 在计算中心点的时候,需要将原来的中心点算进去
for j in range(0, k):
if len(clusters[j]) == 0: # 这里是说一旦某一个epoch中,某一个聚类中一个样本都没有
temp_center_points[j] = center_points[j]
# 判断中心点是否发生变化:即,判断聚类前后样本的类别是否发生变化
for c, nc in zip(center_points, temp_center_points):
if not check_center_diff(c, nc):
center_points = temp_center_points[:] # 复制一份
break
else: # 如果没有变化,那么退出迭代,聚类结束
break
plt.show()
return clusters, temp_center_points # 返回聚类的结果
# 随机获取一个样本集,用于测试K-means算法
def get_test_data():
N = 1000
# 产生点的区域
area_1 = [0, N / 4, N / 4, N / 2]
area_2 = [N / 2, 3 * N / 4, 0, N / 4]
area_3 = [N / 4, N / 2, N / 2, 3 * N / 4]
area_4 = [3 * N / 4, N, 3 * N / 4, N]
area_5 = [3 * N / 4, N, N / 4, N / 2]
areas = [area_1, area_2, area_3, area_4, area_5]
# 在各个区域内,随机产生一些点
points = []
for area in areas:
rnd_num_of_points = random.randint(50, 200)
for r in range(0, rnd_num_of_points):
rnd_add = random.randint(0, 100)
rnd_x = random.randint(area[0] + rnd_add, area[1] - rnd_add)
rnd_y = random.randint(area[2], area[3] - rnd_add)
points.append([rnd_x, rnd_y])
# 自定义中心点,目标聚类个数为5,因此选定5个中心点
center_points = [[0, 250], [500, 500], [500, 250], [500, 250], [500, 750]]
return points, center_points
if __name__ == '__main__':
points, center_points = get_test_data()
clusters, temp_center_points = K_means(points, center_points)
print('#######最终结果##########')
# for i, cluster in enumerate(clusters): # 打印所有点
# print('cluster ', i, ' ', cluster)
print('最后中心点为:')
print(temp_center_points)


Python实战
数据展示:
658985 4.285136
-3.453687 3.424321
4.838138 -1.151539
-5.379713 -3.362104
0.972564 2.924086
-3.567919 1.531611
0.450614 -3.302219
-3.487105 -1.724432
2.668759 1.594842
-3.156485 3.191137
3.165506 -3.999838
-2.786837 -3.099354
4.208187 2.984927
-2.123337 2.943366
0.704199 -0.479481
-0.392370 -3.963704
2.831667 1.574018
-0.790153 3.343144
2.943496 -3.357075
-3.195883 -2.283926
2.336445 2.875106
-1.786345 2.554248
2.190101 -1.906020
-3.403367 -2.778288
1.778124 3.880832
-1.688346 2.230267
2.592976 -2.054368
-4.007257 -3.207066
2.257734 3.387564
-2.679011 0.785119
0.939512 -4.023563
-3.674424 -2.261084
2.046259 2.735279
-3.189470 1.780269
4.372646 -0.822248
-2.579316 -3.497576
1.889034 5.190400
-0.798747 2.185588
2.836520 -2.658556
-3.837877 -3.253815
2.096701 3.886007
-2.709034 2.923887
3.367037 -3.184789
-2.121479 -4.232586
2.329546 3.179764
-3.284816 3.273099
3.091414 -3.815232
-3.762093 -2.432191
3.542056 2.778832
-1.736822 4.241041
2.127073 -2.983680
-4.323818 -3.938116
3.792121 5.135768
-4.786473 3.358547
2.624081 -3.260715
-4.009299 -2.978115
2.493525 1.963710
-2.513661 2.642162
1.864375 -3.176309
-3.171184 -3.572452
2.894220 2.489128
-2.562539 2.884438
3.491078 -3.947487
-2.565729 -2.012114
3.332948 3.983102
-1.616805 3.573188
2.280615 -2.559444
-2.651229 -3.103198
2.321395 3.154987
-1.685703 2.939697
3.031012 -3.620252
-4.599622 -2.185829
4.196223 1.126677
-2.133863 3.093686
4.668892 -2.562705
-2.793241 -2.149706
2.884105 3.043438
-2.967647 2.848696
4.479332 -1.764772
-4.905566 -2.911070
# coding:utf-8
import numpy as np
import matplotlib.pyplot as plt
def loadDataSet(fileName):
'''
加载测试数据集,返回一个列表,列表的元素是一个坐标
'''
dataList = []
with open(fileName) as fr:
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float, curLine))
dataList.append(fltLine)
return dataList
def randCent(dataSet, k):
'''
随机生成k个初始的质心
'''
n = np.shape(dataSet)[1] # n表示数据集的维度
centroids = np.mat(np.zeros((k, n)))
for j in range(n):
minJ = min(dataSet[:, j])
rangeJ = float(max(dataSet[:, j]) - minJ)
centroids[:, j] = np.mat(minJ + rangeJ * np.random.rand(k, 1))
return centroids
def kMeans(dataSet, k):
'''
KMeans算法,返回最终的质心坐标和每个点所在的簇
'''
m = np.shape(dataSet)[0] # m表示数据集的长度(个数)
clusterAssment = np.mat(np.zeros((m, 2))) # 这里存储的是(类别,distance)
# step:固定初始中心点
centroids = randCent(dataSet, k) # 保存k个初始质心的坐标
clusterChanged = True
iterIndex=1 # 迭代次数
while clusterChanged:
clusterChanged = False
# step2:找组织
for i in range(m): # 遍历每一个样本
minDist = np.inf
minIndex = -1
for j in range(k):
distJI = np.linalg.norm(np.array(centroids[j, :])-np.array(dataSet[i, :]))
if distJI < minDist:
minDist = distJI
minIndex = j
if clusterAssment[i, 0] != minIndex: # 判断该样本所属的类与之前是否发生变化,只要有一个样本发生变化,迭代继续
clusterChanged = True
clusterAssment[i, :] = minIndex, minDist**2
print("第%d次迭代后%d个质心的坐标:\n%s" % (iterIndex, k, centroids)) # 第一次迭代的质心坐标就是初始的质心坐标
iterIndex = iterIndex + 1
old_centroids = centroids.copy()
for cent in range(k):
ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]] # get all the point in this cluster
centroids[cent, :] = np.mean(ptsInClust, axis=0)
# 我这里采用双重判断
if (centroids == old_centroids).all():
pass
else:
clusterChanged = True
return centroids, clusterAssment
def showCluster(dataSet, k, centroids, clusterAssment):
'''
数据可视化,只能画二维的图(若是三维的坐标图则直接返回1)
这个画图方法不好,后期会更新
'''
numSamples, dim = dataSet.shape
if dim != 2:
return 1
mark = ['or', 'ob', 'og', 'ok', 'oy', 'om', 'oc', '^r', '+r', 'sr', 'dr', '<r', 'pr']
# draw all samples
for i in range(numSamples):
markIndex = int(clusterAssment[i, 0])
plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])
mark = ['Pr', 'Pb', 'Pg', 'Pk', 'Py', 'Pm', 'Pc', '^b', '+b', 'sb', 'db', '<b', 'pb']
# draw the centroids
for i in range(k):
plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize = 12)
plt.show()
if __name__ == '__main__':
dataMat = np.mat(loadDataSet('./testSet'))
k = 4 # 选定k值
cent, clust = kMeans(dataMat, k)
showCluster(dataMat, k, cent, clust)
输出结果:


三,sklearn实现
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from sklearn.cluster import KMeans
from scipy.spatial import Voronoi, voronoi_plot_2d
# 该数据集表示玩具制造商的产品数据:
#
# 第一个值代表一个玩具:
# 0-2: 人形公仔
# 3-5: 积木
# 6-8: 汽车
#
# 第二个值是购买玩具最多的年龄组:
# 0: 5 year-olds
# 1: 6 year-olds
# 2: 7 year-olds
# 3: 8 year-olds
# 4: 9 year-olds
# 5: 10 year-olds
x = np.array([[0, 4], [1, 3], [2, 5], [3, 2], [4, 0], [5, 1], [6, 4], [7, 5], [8, 3]])
# model
kmeans = KMeans(n_clusters=3, random_state=0).fit(x)
# Plot the data
sns.set_style("darkgrid")
plt.scatter(x[:, 0], x[:, 1], c=kmeans.labels_, cmap=plt.get_cmap("winter"))
# 以下为画分割线的方式
# Save the axes limits of the current figure
x_axis = plt.gca().get_xlim()
y_axis = plt.gca().get_ylim()
# Draw cluster boundaries and centers
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1], marker='x')
vor = Voronoi(centers)
voronoi_plot_2d(vor, ax=plt.gca(), show_points=False, show_vertices=False)
# Resize figure as needed
plt.gca().set_xlim(x_axis)
plt.gca().set_ylim(y_axis)
# Remove ticks from the plot
plt.xticks([])
plt.yticks([])
plt.tight_layout()
plt.show()

全部评论 (0)
还没有任何评论哟~
