K-means聚类算法(事先数据并没有类别之分!所有的数据都是一样的)
1、概述
K-means算法是集简单和经典于一身的基于距离的聚类算法,采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。
该算法认为类簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。
2、核心思想
通过迭代寻找k个类簇的一种划分方案,使得用这k个类簇的均值来代表相应各类样本时所得的总体误差最小。
k个聚类具有以下特点:各聚类本身尽可能的紧凑,而各聚类之间尽可能的分开。
k-means算法的基础是最小误差平方和准则,
其代价函数是:
$J(c,\mu)=\sum\limits_{i=1}^{k}\|x^{(i)}-\mu_{c^{(i)}}\|^2$
式中,$`表示第i个聚类的均值。
各类簇内的样本越相似,其与该类均值间的误差平方越小,对所有类所得到的误差平方求和,即可验证分为k类时,各聚类是否是最优的。
上式的代价函数无法用解析的方法最小化,只能有迭代的方法。
3、算法步骤图解
下图展示了对n个样本点进行K-means聚类的效果,这里k取2。
![kmeans][base64str]
4、算法实现步骤
k-means算法是将样本聚类成 k个簇(cluster),其中k是用户给定的,其求解过程非常直观简单,具体算法描述如下:
- 随机选取 k个聚类质心点
- 重复下面过程直到收敛 {
对于每一个样例 i,计算其应该属于的类:$c^{(i)}:=\underset{j}{\arg\min}\Vert x^{(i)}-\mu_{j}\Vert ^2$
对于每一个类 j,重新计算该类的质心:
}
其伪代码如下:
创建k个点作为初始的质心点(随机选择)
当任意一个点的簇分配结果发生改变时
KaTeX parse error: Can't use function '$' in math mode at position 6: \quad$̲`对数据集中的每一个数据点 …\qquad$对每一个质心 $$\qquad\qquad$
计算质心与数据点的距离
KaTeX parse error: Can't use function '$' in math mode at position 7: \qquad$̲`将数据点分配到距离最近的簇 …\quad$`对每一个簇,计算簇中所有点的均值,并将均值作为质心
5、K-means聚类算法python实战
需求:对给定的数据集进行聚类
本案例采用二维数据集,共80个样本,有4个类。
testSet.txt
-0.017612 14.053064 0
-1.395634 4.662541 1
-0.752157 6.538620 0
-1.322371 7.152853 0
0.423363 11.054677 0
0.406704 7.067335 1
0.667394 12.741452 0
-2.460150 6.866805 1
0.569411 9.548755 0
-0.026632 10.427743 0
0.850433 6.920334 1
1.347183 13.175500 0
1.176813 3.167020 1
-1.781871 9.097953 0
-0.566606 5.749003 1
0.931635 1.589505 1
-0.024205 6.151823 1
-0.036453 2.690988 1
-0.196949 0.444165 1
1.014459 5.754399 1
1.985298 3.230619 1
-1.693453 -0.557540 1
-0.576525 11.778922 0
-0.346811 -1.678730 1
-2.124484 2.672471 1
1.217916 9.597015 0
-0.733928 9.098687 0
-3.642001 -1.618087 1
0.315985 3.523953 1
1.416614 9.619232 0
-0.386323 3.989286 1
0.556921 8.294984 1
1.224863 11.587360 0
-1.347803 -2.406051 1
1.196604 4.951851 1
0.275221 9.543647 0
0.470575 9.332488 0
-1.889567 9.542662 0
-1.527893 12.150579 0
-1.185247 11.309318 0
-0.445678 3.297303 1
1.042222 6.105155 1
-0.618787 10.320986 0
1.152083 0.548467 1
0.828534 2.676045 1
-1.237728 10.549033 0
-0.683565 -2.166125 1
0.229456 5.921938 1
-0.959885 11.555336 0
0.492911 10.993324 0
0.184992 8.721488 0
-0.355715 10.325976 0
-0.397822 8.058397 0
0.824839 13.730343 0
1.507278 5.027866 1
0.099671 6.835839 1
-0.344008 10.717485 0
1.785928 7.718645 1
-0.918801 11.560217 0
-0.364009 4.747300 1
-0.841722 4.119083 1
0.490426 1.960539 1
-0.007194 9.075792 0
0.356107 12.447863 0
0.342578 12.281162 0
-0.810823 -1.466018 1
2.530777 6.476801 1
1.296683 11.607559 0
0.475487 12.040035 0
-0.783277 11.009725 0
0.074798 11.023650 0
-1.337472 0.468339 1
-0.102781 13.763651 0
-0.147324 2.874846 1
0.518389 9.887035 0
1.015399 7.571882 0
-1.658086 -0.027255 1
1.319944 2.171228 1
2.056216 5.019981 1
-0.851633 4.375691 1
-1.510047 6.061992 0
-1.076637 -3.181888 1
1.821096 10.283990 0
3.010150 8.401766 1
-1.099458 1.688274 1
-0.834872 -1.733869 1
-0.846637 3.849075 1
1.400102 12.628781 0
1.752842 5.468166 1
0.078557 0.059736 1
0.089392 -0.715300 1
1.825662 12.693808 0
0.197445 9.744638 0
0.126117 0.922311 1
-0.679797 1.220530 1
0.677983 2.556666 1
0.761349 10.693862 0
-2.168791 0.143632 1
1.388610 9.341997 0
0.317029 14.739025 0
代码如下:
#!/usr/bin/python
# coding=utf-8
import copy
import numpy as np
# 加载数据
def loadDataSet(fileName): # 解析文件,按tab分割字段,得到一个浮点数字类型的矩阵
dataMat = [] # 文件的最后一个字段是类别标签
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float, curLine)) # 将每个元素转成float类型
dataMat.append(fltLine)
return np.array(dataMat)
# 计算欧几里得距离
def distEclud(vecA, vecB):
return np.sqrt(np.sum(np.power(vecA - vecB, 2))) # 求两个向量之间的距离
# 构建聚簇中心,取k个(此例中为4)随机质心
def randCent(dataSet, k):
centroid = np.array(np.zeros((k,2))) # 每个质心有n个坐标值,总共要k个质心
for j in range(2):
minJ = min(dataSet[:,j])
maxJ = max(dataSet[:,j])
rangeJ = float(maxJ - minJ)
centroid[:,j] = minJ + rangeJ * np.random.rand(k)
return centroid
# k-means 聚类算法
def kMeans(dataSet, k, distMeans =distEclud, createCent = randCent):
m = dataSet.shape[0];counts=[]
clusterAssments=[];centroids=[]
clusterAssment = np.array(np.zeros((m,2))) # 用于存放该样本属于哪类及质心距离
# clusterAssment第一列存放该数据所属的中心点,第二列是该数据到中心点的距离
centroid = createCent(dataSet, k)
clusterChanged = True # 用来判断聚类是否已经收敛
while clusterChanged:
clusterChanged = False
count=0
for i in range(m): # 把每一个数据点划分到离它最近的中心点
minDist = np.inf; minIndex = -1;
for j in range(k):
distJI = distMeans(centroid[j,:], dataSet[i,:-1])
if distJI < minDist:
minDist = distJI; minIndex = j # 如果第i个数据点到第j个中心点更近,则将i归属为j
if clusterAssment[i,0] != minIndex:
clusterChanged = True;count+=1 # 如果分配发生变化,则需要继续迭代
#print(clusterAssment[i,0],'-->',minIndex)
clusterAssment[i,:] = minIndex,minDist**2 # 并将第i个数据点的分配情况存入字典
for cent in range(k): # 重新计算中心点
ptsInClust = dataSet[clusterAssment[:,0] == cent][:,:-1] # 去第一列等于cent的所有列
centroid[cent,:] = np.mean(ptsInClust, axis = 0) # 算出这些数据的中心点
#此处为坑
# centroids.append(centroid)
# clusterAssments.append(clusterAssment)
if clusterChanged==True:
centroids.append(copy.copy(centroid))
clusterAssments.append(copy.copy(clusterAssment))
counts.append(count)
return centroids, clusterAssments,counts
# --------------------测试----------------------------------------------------
# 用测试数据及测试kmeans算法
datMat=loadDataSet('f:/testSet.txt')
myCentroids,clustAssings,counts = kMeans(datMat,4)
print(myCentroids)
len(clustAssings)
counts # [58, 23, 3, 1]
运行结果:
![kmean2][base64str2]
画出每步迭代的图:
import matplotlib.pyplot as plt
fig1=plt.figure(1,figsize=(15,20))
for i in range(4):
ax=fig1.add_subplot(32*10+i+1)
s=clustAssings[i][:,0]+30
c=clustAssings[i][:,0]+20
ax.scatter(datMat[:,0],datMat[:,1],s,c)
ax.scatter(myCentroids[i][:,0],myCentroids[i][:,1],s=150,c='r',marker='+')
plt.show()
结果共迭代了4步,每一步变动的点数分别为58, 23, 3, 1,画图如下:
6、K-means算法补充
K-means算法的缺点及改进方法
(1)值的选择是用户指定的,不同的k得到的结果会有挺大的不同,如下图所示,上边是k=3的结果,这个就太稀疏了,蓝色的那个簇其实是可以再划分成两个簇的。而下图是k=5的结果,可以看到紫色和黄色这两个簇应该是可以合并成一个簇的:
![kmeans4][base64str4] | ![kmeans5][base64str5] |
改进:
对k的选择可以先用一些算法分析数据的分布,如重心和密度等,然后选择合适的k
(2)对k个初始质心的选择比较敏感,容易陷入局部最小值。例如,我们上面的算法运行的时候,有可能会得到不同的结果,如下面这两种情况。K-means也是收敛了,只是收敛到了局部最小值.
改进:
有人提出了另一个成为二分k均值(bisecting k-means)算法,它对初始的k个质心的选择就不太敏感.
(3)存在局限性,如下面这种非球状的数据分布就搞不定了:
(4)数据集比较大的时候,收敛会比较慢.