目录
一、K-均值聚类算法
二、 使用后处理提高聚类性能
三、二分K-均值算法
k-均值聚类
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据上收敛较慢
适用数据类型:数值型数据
K-均值是发现给定数据集的k个簇的算法。簇个数是用户给定的,每一个簇通过其质心(即簇中所有的中心)来描述
K-均值算法的伪代码:
创建k个点作为起始质心(经常是随机选择)
当任意一个点的簇分配结果 发生改变时
对数据集中的每个数据点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每一个簇,计算簇中所有点的均值并将其均值作为质心
python实现K-均值算法,创建kMeans.py文件:
import numpy as np
# 生成数据集
def LoadDataSet(fileName):
dataMat = []
fr = open(fileName)
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)))
# 随机生成k个质心的集合
def randCent(dataset, k):
n = dataset.shape[1]
centroids = np.mat(np.zeros((k, n))) # k个簇
for j in range(n): # 生成簇 不过要在所有数据的区域内
rangeJ = float(max(dataset[:, j]) - min(dataset[:, j])) # 最大值与最小值的差距
centroids[:, j] = rangeJ * np.random.rand(k, 1) + min(dataset[:, j])
return centroids
datMat = np.mat(LoadDataSet('Ch10/testSet.txt'))
函数randCent()用于为给定数据集构建一个包含k个随机质心的集合。随机质心必须要在整个数据集的边界之内,可以通过找到数据集每一维的最小和最大值来完成。生成0到1.0之间的随机数并通过取值范围和最小值,以确保随机点在数据的边界之内。
import kMeans
from numpy import *
datMat = mat(kMeans.LoadDataSet('D:\\learning\\testSet.txt'))
result1 = min(datMat[:, 0])
result2 = min(datMat[:, 1])
result3 = max(datMat[:, 1])
result4 = max(datMat[:, 0])
result5 = kMeans.randCent(datMat, 2)
print(result1)
print(result2)
print(result3)
print(result4)
print(result5)
检验randCent()函数是否生成min到max之间的值,首先提出最小值与最大值,在输出经由randCent()函数处理的数据,可以看出函数可以按照预想的方式运行 。
K-均值聚类算法:
# 创建k个质心,将每个点分配到最近的质心,再重新计算质心
def kMeans(dataMat, k, distMeans=distEclud, createCent=randCent):
m = np.shape(dataMat)[0]
centroids = createCent(dataMat, k)
clusterAssment = np.mat(np.zeros((m, 2))) # 0列记录簇索引值,1列存储误差
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m):
minDist = float('inf');
minIndex = -1
for j in range(k):
distJI = distMeans(dataMat[i], centroids[j]) # 计算每个点到质心的距离
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 = dataMat[np.nonzero(clusterAssment[:, 0] == cent)[0]] # 找到同一个簇中的所有点
centroids[cent, :] = np.mean(ptsInClust, axis=0) # 重新计算簇的质心
return centroids, clusterAssment # 返回质心和点的分配结果
函数有4个输入参数。只有数据集及簇的数目是必选参数,用来计算距离和创建初始质心的函数都是可选的。kMeans()函数一开始确定数据集中数据点的总数,并创建一个矩阵来存储每个点的簇分配结果。矩阵clusterAssment一列记录簇索引值,第二列存储误差。误差为当前点到簇质心的距离。
通过计算质心-分配-重新计算反复迭代,直到所有数据点的簇分配结果不再改变为止。 clusterChanged用于判断是否继续迭代。
测试k-means的效果;
datMat = mat(kMeans.LoadDataSet('D:\\learning\\testSet.txt'))
myCentroids, clustAssing = kMeans.kMeans(datMat, 4)
结果如下:
缺点,K-均值聚类中簇的数目k是用户预先定义的,而用户并不能提前预知k的选择是否正确。同时点的簇分配结果值也没有那么准确,因为K-均值算法收敛到了局部最小值,而非全局最小值。
一种用于度量聚类效果的指标是SSE(误差平方和),SSE值越小表示数据点越接近它们的质心,同时聚类效果也越好。因为对误差取平方,因此更重视那些远离中心的点,一种降低SSE值方法是增加簇的个数,但这个违背聚类的目标。
可以通过对生成的簇进行后处理,一种是将具有最大SSE值的簇划分成两个簇,具体实现时将最大簇包含的点过滤出来并在这些点上运行K-均值算法,记k=2.
我们可以很容易的对二维数据上的聚类进行可视化,但面对40维数据时,就不是那么容易了,两种量化方法:合并最近的质心,或者合并两个使得SSE增幅最小的质心。
二分K-均值算法主要是为了克服K-均值算法收敛于局部最小值的问题。将所有点作为一个簇,将簇一分为二。选择其中一个继续划分,选择哪一 个簇进行划分取决于对其划分是否可以最大程度降低SSE的值。
二分K-均值算法的伪代码:
将所有点看成一个簇
当簇数目小于k时
对于每一个簇
计算总误差
在给定的簇上进行K-均值聚类(k=2)
计算将该簇一分为二之后的总误差
选择使得误差最小的那个簇进行划分操作
另一种做法是选择SSE最大的簇进行划分,直到簇数目达到用户指定的数目为止。
python实现二分K-均值聚类算法
def biKmeans(dataSet, k, distMeas=distEclud):
m = np.shape(dataSet)[0]
clusterAssment = np.mat(np.zeros((m, 2)))
centroid0 = np.mean(dataSet, axis=0).tolist()[0]
centList = [centroid0]
for j in range(m):
clusterAssment[j, 1] = distMeas(np.mat(centroid0), dataSet[j, :]) ** 2
while (len(centList) < k):
lowestSSE = float('inf')
for i in range(len(centList)):
ptsInCurrCluster = dataSet[np.nonzero(clusterAssment[:, 0].A == i)[0], :] # 每个簇看成一个小数据集,
centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) # 输入到kmeans算法中,计算k=2的质心簇和误差值
sseSplit = sum(splitClustAss[:, 1])
sseNotSplit = 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: ', bestCentToSplit)
print('the len of bestClustAss is: ', len(bestClustAss))
centList[bestCentToSplit] = bestNewCents[0, :].tolist()[0]
centList.append(bestNewCents[1, :].tolist()[0])
clusterAssment[np.nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0], :] = bestClustAss
return np.mat(centList), clusterAssment
创建矩阵存储数据集中的每一个点的簇分配结果以及平方误差,然后计算整个数据集的质心,使用列表保留所有的质心。得到质心后遍历数据集中所有的点以计算点到质心的误差值。
while循环用于划分簇,直到得到想要的簇数目。可以通过考察簇列表中的值得到当前簇的数目。通过遍历所有簇来决定最佳的簇的划分。对于每个簇将簇中的所有点看成是一个小的数据集,将其输入到kMeans()函数中进行处理,得到两个质心以及每个簇的误差值。误差与剩余数据的的误差之和作为本次划分的误差,当划分的SSE值很小保留本次划分。划分操作只需将划分的簇中所有点的簇分配结果进行修改就行了。新的簇分配后,新的质点会被添加到centLIst中。
实际效果如下:
datMat3 = mat(kMeans.LoadDataSet('D:\\learning\\testSet2.txt'))
centList, myNewAssment = kMeans.biKmeans(datMat3, 3)
print(centList)