k - 均值聚类算法

对聚类得到的簇进行后处理

二分k -均值聚类算法

聚类是一种无监督学习,聚类分析试图将相似的对象归入同一簇,将不相同的对象归到不同簇中。相似这一概念取决于所选的相似度计算方法。

K-均值聚类算法

优点:易于使用 

缺点:收敛于局部极小值(converge at local minima);大规模数据集上运行很慢 

适用范围:数值 

k-均值 是在给定数据集中发现 k 个簇的算法 。簇个数k 是用户给定的,每一个簇通过其质心,即簇中的所有点的中心来描述。

k-均值算法的工作流程是这样的。首先,随机确定k 个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心。并将其分配给该质心所对应的簇。这一步完成后,每个簇的质心更新为该簇所有点的平均值。

伪代码 : 

创建k个初始中心 (随机)
当任意样本点的簇类别改变时:
遍历数据集:
遍历簇中心:
计算簇中心与样本点距离
将样本点分配距离最近的簇类别
遍历所有簇:
计算各簇均值,将簇均值分配给簇中心

k -均值聚类支持函数

import numpy as np


def loadDataSet(filename) :
dataMat = []

with open(filename,"r") as fr:
for line in fr.readlines() :
curline = line.strip().split('\t')
fltLine = list(map(float,curline))
dataMat.append(fltLine)
return dataMat


def distEclud(vecA,vecB) :
# 计算距离
return np.sqrt(np.sum(np.power(vecA-vecB,2)))

def randCent(dataSet,k):
# 该函数为给定的数据集构建一个包含k个随机质心的集合
# 随机质心必须要在整个数据集的边界之内
n = np.shape(dataSet)[1]
centroids = np.matrix(np.zeros((k, n)))

for j in range(n):
# create cluster centroids
minJ = np.min(dataSet[:, j])
rangeJ = float(np.max(dataSet[:, j]) - minJ)
centroids[:, j] = minJ + rangeJ * np.random.rand(k, 1)

return centroids

randCent 函数用来为给定的数据集构建一个包含k个随机质心的集合 

datMat = np.matrix(loadDataSet("./testSet.txt"))
# 最大、最小值
print(np.min(datMat[:, 0]))
print(np.min(datMat[:, 1]))
print(np.max(datMat[:, 0]))
print(np.max(datMat[:, 1]))

# 随机中心
print(randCent(datMat, 2))

# 距离测度
print(distEclud(datMat[0], datMat[1]))

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111)
ax.scatter(datMat[:, 0].A.ravel(),
datMat[:, 1].A.ravel(),
label="samples")
plt.legend(loc="best")
plt.show()
-5.379713
-4.232586
4.838138
5.1904
[[ 2.56673435 3.53457907]
[-2.95255221 4.86765517]]
5.184632816681332

机器学习实战 -- K-均值聚类算法_数据集

k - 均值聚类算法

# kMeans.py
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):

m = np.shape(dataSet)[0]
# cluster assignment, col_0: cluster index, col_1: dist^2
clusterAssment = np.matrix(np.zeros((m, 2)))
centroids = randCent(dataSet, k)
clusterChanged = True

while clusterChanged:
clusterChanged = False
# 对于每一个样本
for i in range(m):
minDist = np.inf
minIndex = -1

# 寻找最近的质心
# 枚举k 个簇
for j in range(k):
# disJI 簇中心到 样本点的距离
distJI = distMeas(centroids[j, :], dataSet[i, :])
if distJI < minDist:
minDist = distJI
minIndex = j

if clusterAssment[i, 0] != minIndex:
clusterChanged = True
clusterAssment[i, :] = minIndex, minDist ** 2

print(centroids)

# 更新质心的位置
for cent in range(k):
ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
centroids[cent, :] = np.mean(ptsInClust, axis=0)

return centroids, clusterAssment

簇分配结果矩阵 clusterAssment 包含两列: 一列记录簇索引值, 第二列存储误差。这里的误差是指当前点到簇中心的距离。

通过遍历所有数据点,找到其最近的簇, 最后遍历所有的质心并更新它们的取值。

接下来看看运行的效果 : 

myCentroids , clustAssing = kMeans(datMat,4)
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
ax.scatter(datMat[:,0].A.ravel(),
datMat[:,1].A.ravel(),
c=clustAssing[:,0].A.ravel(),
label ="samples")
ax.scatter(myCentroids[:,0].A.ravel(),
myCentroids[:,1].A.ravel(),
s = 62 , marker="x",c="red",
label="centroids")
plt.legend(loc="best")
plt.show()

机器学习实战 -- K-均值聚类算法_聚类算法_02

>>> datMat = np.matrix(kMeans.loadDataSet('testSet.txt'))
>>> myCentroids ,clustAssing = kMeans.kMeans(datMat,4)
>>> clustAssing
matrix([[ 2. , 16.25637055],
[ 1. , 14.38650203],
[ 3. , 2.9633119 ],
[ 0. , 16.73946162],
[ 2. , 7.6955159 ],
[ 1. , 3.59955386],
[ 3. , 7.14464147],
[ 0. , 2.64654613],
[ 2. , 2.69095875],
.. .....

上面代码中 clustAssing 第一列是 索引 , 0 表示 样本点在第0 个簇 , 1 表示样本点在第一个簇 .... 第二列是误差,即样本点与簇中心的距离(欧式距离)。

使用后处理来提高聚类的性能

前面提到,在k-均值聚类中簇的数目是已知的,那么用户如何才能知道k的选择是否正确? 如何才能生成的簇比较好呢?在包含簇分配结果的矩阵中保存着每个点的误差,即该店到簇质心的距离平方值。

机器学习实战 -- K-均值聚类算法_数据集_03

  考虑上图中的聚类的结果,这是一个在包含三个簇的数据集上运行k-均值算法之后的结果,但是点的簇分配结果并没有那么准确。 k-均值算法收敛但聚类效果较差的原因是 ,k-均值算法收敛到了局部最小值,而非全局最小值。

  一种用来度量聚类效果的指标是SSE( sum of Squared Error ) 误差平方和,SSE 值越小表示数据点越接近他们的质心,聚类效果也越好。因为对误差取平方,因此更加重视那些远离中心的点。一种降低 SSE的做法是增加簇的个数,但是这违背了聚类的目标。聚类的目标是在保存簇个数不变的情况下提高簇的质量。

  为了保持簇的总数不变,可以将两个簇进行合并。

有两种可以量化的办法 : 合并最近的质心,或者合并两个使得SSE增幅最小的质心。第一种思路通过计算所有质心之间的距离,然后合并距离最近的两个。第二种方法是需要合并两个簇然后计算SSE值。必须在所有可能的两个簇上重复上述处理过程,直到合并最佳的两个簇为止。

 

二分 K-均值算法

为了克服k-均值算法收敛于局部最小值的问题,有人提出了另一个称为二分k-均值(bisecting K-mean) 的算法。该算法首先将所有的点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可以最大程度降低 SSE 的值。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。

二分K-均值算法的伪代码 : 

初始化,将所有样本点聚类到同一簇
当簇总数小于k时:
遍历簇:
计算总误差
在给定的簇上面进行k-均值聚类
计算该簇一分为二后的总误差
选择使SSE最小的簇分裂

另一种做法就是选择SSE最大的簇进行划分,直到簇数量达到用户指定的数目为止。

#kMeans.py
def biKmeans(dataSet,k,disMeas=distEclud):
m = np.shape(dataSet)[0]
# m 行 2 列的簇分配结果矩阵
clusterAssment = np.matrix(np.zeros((m,2)))
# 创建一个初始簇
centroid0 = np.mean(dataSet,axis=0).tolist()[0]
centList = [centroid0]
# 求出所有样本点到 初始簇的距离(误差)
for j in range(m):
clusterAssment[j,1] = distEclud(np.matrix(centroid0),dataSet[j,:])**2
while len(centList) <k :
lowestSSE = np.inf
# 遍历簇列表中的每一个簇
for i in range(len(centList)) :
# 找到所有属于 第 i 个簇的数据点
ptsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0].A == i)[0] ,:]
# 划分为2个簇
centroidMat ,splitClustAss = kMeans(ptsInCurrCluster,2,disMeas)
# 求出划分出的簇的误差(样本点到质心的距离)
sseSplit = np.sum(splitClustAss[:,1])
# 剩余数据集的误差
sseNotSplit = np.sum(clusterAssment[np.nonzero(clusterAssment[:,0].A !=i)[0],1])

print("sseSplit , and notSplit: ",sseSplit,sseNotSplit)

if sseSplit + sseNotSplit <lowestSSE :
bestCentToSplit = i
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy()
lowestSSE = sseSplit + sseNotSplit

bestClustAss[np.nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)
bestClustAss[np.nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSplit
print("the bestCentToSplit is: {}".format(bestCentToSplit))
print("the len of bestClustAss is: {}".format(len(bestClustAss)))

print(centList)
centList[bestCentToSplit] = bestNewCents[0, :].A.ravel()
centList.append(bestNewCents[1, :].A.ravel())
clusterAssment[np.nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0], :] = bestClustAss

return np.matrix(centList), clusterAssment

看看用二分 k- 均值 聚类算法的效果 : 

datMat3 = np.matrix(loadDataSet('testSet2.txt'))
centList, myNewAssments = biKmeans(datMat3, 3)

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111)
ax.scatter(datMat3[:, 0].A.ravel(),
datMat3[:, 1].A.ravel(),
c=myNewAssments[:, 0].A.ravel(),
label="samples")
ax.scatter(centList[:, 0].A.ravel(),
centList[:, 1].A.ravel(),
s=80,
marker="x",
c="red",
label="centroids")
plt.legend(loc="best")
plt.show()

 

机器学习实战 -- K-均值聚类算法_聚类算法_04