机器学习实战笔记(4)
一、K-均值聚类算法
1、算法介绍
k均值聚类算法(k-means clustering algorithm)是一种理解和实现都比较简单的算法,属于无监督学习,主要实现聚类(Clustering),即将相似的数据归到同一簇,簇内对象越相似,聚类的效果越好。
k均值聚类算法是一种迭代求解的聚类分析算法,其步骤是,预将数据分为K组,则随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每次分配结束后,更新每个聚类中心。这个过程将不断重复直到满足某个终止条件:终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有聚类中心再发生变化,误差平方和局部最小。
2、算法实现
(1)辅助函数
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA - vecB, 2))) # 两数据点之间的距离计算
def randCent(dataSet, k):
'''随机初始化k个中心'''
n = shape(dataSet)[1]
centroids = mat(zeros((k,n))) # 创建中心矩阵
for j in range(n): # 在数据范围内,为每个维度生成随机值
minJ = min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j]) - minJ)
centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1))
return centroids
(2)K-均值聚类算法主体
输入参数有四个,分别为数据集、簇个数k、距离函数和初始化中心,其中数据集和k一定要指定。输出则为最终的k个簇中心和每个数据的簇分类情况。
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
'''K_均值算法主体部分'''
m = shape(dataSet)[0] # 数据总个数
clusterAssment = mat(zeros((m,2))) # 簇分配结果矩阵
# 第一列记录所属簇索引值,第二列存储误差
centroids = createCent(dataSet, k)
clusterChanged = True # 数据处理标志,当所有数据点簇分配结果不再改变则停止
while clusterChanged:
clusterChanged = False
for i in range(m): # 遍历所有数据点,将其”归到“最近的簇
minDist = inf; minIndex = -1
for j in range(k): # 计算到所有簇中心点的距离,选择最近的
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[nonzero(clusterAssment[:,0].A==cent)[0]] # 获取所有属于该簇的数据点
centroids[cent,:] = mean(ptsInClust, axis=0) # 更新均值
return centroids, clusterAssment
用两组简单的数据对该算法进行测试,结果如下图所示:
第一组:
第二组:
从两组结果图很容易看出,对第一组数据聚类情况看着还不错。而由于K均值算法中每次初始化的中心点均为随机生成的点,偶尔可能陷入局部最优解,而非全局最优解。具体情况类似与第二组数据的聚类情况,从图中很明显可以看出聚类结果并不理想。
对于类似情况可对该算法重复多次,避免陷入某些局部最优解,或者改进K-均值算法,如接下来介绍的二分K-均值算法。
二、二分K-均值算法
为克服K-均值算法收敛于局部最小值的问题,有人提出了另个称为二分K-均值(bisectingK-means)的算法。该算法首先将所有点作为个簇, 然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪个簇进行划分取决于对其划分是否可以最大程度降低SSE的值(Sum of Squared Error ,误差平方和,一种度量聚类效果的指标)。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。另一种做法则是选择SSE最大的簇进行划分,直到簇数目达到用户指定的数目为止。
(1)二分K-均值聚类算法
def biKmeans(dataSet, k, distMeas=distEclud):
'''二分K-均值算法'''
m = shape(dataSet)[0]
clusterAssment = mat(zeros((m,2)))
centroid0 = mean(dataSet, axis=0).tolist()[0] # 创建一个初始簇,仅一个中心
centList =[centroid0]
for j in range(m): # 计算距离(误差值)
clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2
while (len(centList) < k): # 当前簇个数尚未到达k时
lowestSSE = inf
for i in range(len(centList)): # 尝试划分每个簇,确定用于下次划分最好的簇
ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:] # 获取所有第i个簇的数据
centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) # 对第i个簇划分
sseSplit = sum(splitClustAss[:,1]) # 划分后簇的SSE
sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) # 未进行划分的其他簇的SSE
print("sseSplit, and notSplit: ",sseSplit,sseNotSplit)
if (sseSplit + sseNotSplit) < lowestSSE: # 保留划分后总体SSE最小的
bestCentToSplit = i
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy()
lowestSSE = sseSplit + sseNotSplit
bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) # 更新簇的分配结果,(0,1 ——> x,i)
bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0] # 更新存储簇中心的列表
centList.append(bestNewCents[1,:].tolist()[0])
clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss # 更新距离
return mat(centList), clusterAssment
-
nonzero函数是numpy中用于得到数组array中非零元素的位置(数组索引)的函数。它的返回值是一个长度为a.ndim(数组a的轴数)的元组,元组的每个元素都是一个整数数组,其值为非零元素的下标在对应轴上的值。
如:dataSet[nonzero(clusterAssment[:,0].Ai )[0],:] 中nonzero(clusterAssment[:,0].Ai[0]返回的即为满足clusterAssment[:,0].A==i条件(簇类别为i)的数据的二维索引元组。
二分K均值算法最终会收敛到全局最小值,对原始K均值聚类算法案例中出现局部最小值的数据,再次使用此算法进行聚类测试,结果如下图所示: