一.二分KMeans:
算法的主要思想是:首先将所有点作为一个簇,然后将该簇一分为二。之后选择能最大限度降低聚类代价函数(也就是误差平方和)的簇划分为两个簇。以此进行下去,直到簇的数目等于用户给定的数目k为止。以上隐含的一个原则就是:因为聚类的误差平方和能够衡量聚类性能,该值越小表示数据点越接近于他们的质心,聚类效果就越好。所以我们就需要对误差平方和最大的簇进行再一次划分,因为误差平方和越大,表示该簇聚类效果越不好,越有可能是多个簇被当成了一个簇,所以我们首先需要对这个簇进行划分。
伪代码:
初始化簇表,使之包含由所有的点组成的簇。
repeat
{对选定的簇进行多次二分试验}
for i=1 to 试验次数 do
使用基本k均值,二分选定的簇。
endfor
从二分试验中选择具有最小误差的两个簇。
将这两个簇添加到簇表中。
until 簇表中包含k个簇
1.k值与整体误差平方和的关系:
k值越大,划分越细,(x-x1)越小,则整体误差平方和越小;
所以,k值固定时,“误差平方和”可以量化聚类效果;
轮廓系数可以比较不同k值下划分效果,ai表示簇内距离,bi表示簇外距离,
一般情况下,k值增大(=2,3,4,5...)会使轮廓系数,先增大,后减小,慢慢接近0;不同情况下,临界点不同;
2.二分kmeans算法过程中,可以k=1,2,3....的结果,
所以,当你不知道分成几个簇的时候,可以使用二分kmeans算法;
算法意图在于得到k=2,3...时,整体SSE(簇内误差平方和)最小的结果;
3.每次选择要划分的簇时,简单的操作:(1)选“SSE最大的簇“(2)选簇内点最多的簇,然后原始的K-means,一分为二;
复杂有目的性的操作:对所有簇尝试原始K-Means划分,然后选使k=k(前)+1时整体SSE最小的那个簇,直接保留划分结果;
讨论:选SSE最大簇就是使得划分后整体SSE最小的簇;
(这个可以在实验中观察)
实验结论:SSE最大簇就是使得划分后整体SSE最小的簇,只有0.05左右概率出现意外,而且是分割的数据集较小的时候;
本人认为可能是二分时使用k-means的随机选初始点的影响;
所以,用“选SSE最大簇”代替“选使整体SSE最小的簇”,时间复杂度减少很多;
4.SSE(簇内误差平方和)的因素有:(1)数量 (2)点的松散程度
所以我觉得用3中简单操作(2)不太靠谱,有的分类可能数量虽多,分布却很紧密;
5.时间复杂度分析:
k-means:D*K*N ;D为迭代次数
基于“使整体SSE最小”两种方式的二分K均值:每次二分都需要D*2*N/x*x , x是暂时分割的簇数;经过k-1次,计算得:
D*2*(k-1)*N; 但是数据集分割得越来越小,所以每次的D越来越小,所以大约为k-means时间的1.5倍左右;
基于“使整体SSE最小”两种方式的二分K均值,如下代码所示:
from numpy import * from pandas import * # 计算欧几里得距离 def distEclud(vecA, vecB): return sqrt(sum(power(vecA - vecB, 2))) # 求两个向量之间的距离 def testBestSplit(cents,ClustDist): max = 0 i = 0 for x in cents: a = ClustDist[ClustDist[:, 0]== x][:,1] lossSum = sum(multiply(a,a)) if lossSum>max: max = lossSum i = x return i def Two_Kmeans(dataSet, k): m = shape(dataSet)[0] # 初始化第一个聚类中心: 每一列的均值 centroid0 = mean(dataSet, axis=0).tolist() cents = [0] #簇的index列表 centList =[centroid0] # 把均值聚类中心加入中心表中 # 初始化聚类距离表,距离方差 # 列1:数据集对应的聚类中心,列2:数据集行向量到聚类中心距离的平方 ClustDist = zeros((m, 2)) for j in range(m): ClustDist[j,1] = distEclud(centroid0,dataSet[j,:])**2 # 依次生成k个聚类中心 while (len(centList) < k): lowestSSE = inf bestCentToSplit = 0 bestClustAss = [] #最优分割 for i in cents: #当前簇dataSet ptsInCurrCluster = dataSet[ClustDist[:, 0]== i] #print(len(ptsInCurrCluster[:, 0])) #划分 centroidMat, splitClustAss = my_kMeans(ptsInCurrCluster, 2) #上一章 centroidMat = centroidMat.getA() #当前簇划分后的误差平方和 sseSplit = sum(multiply(splitClustAss[:, 1], splitClustAss[:, 1])) #没划分的簇的误差平方和 ClustDist_nosplit = ClustDist[ClustDist[:, 0]!= i] sseNotSplit = sum(multiply(ClustDist_nosplit[:,1],ClustDist_nosplit[:,1])) if (sseSplit + sseNotSplit) < lowestSSE: lowestSSE = sseSplit + sseNotSplit bestCentToSplit = i # 确定聚类中心的最优分隔点 #bestNewCents = centroidMat # 用新的聚类中心更新最优聚类中心 bestClustAss = splitClustAss.copy() # 深拷贝聚类距离表为最优聚类距离表 #检测一下: testi = testBestSplit(cents,ClustDist) print(testi==bestCentToSplit) #验证了:SSE最大簇就是使得划分后整体SSE最小的簇(在绝大部分情况下) #保留分割结果: length = len(cents) cents.insert(cents.index(bestCentToSplit)+1,length) centList[bestCentToSplit] = centroidMat[0].tolist() centList.append(centroidMat[1].tolist()) for x in bestClustAss: if x[0] == 0: x[0] = bestCentToSplit else: x[0] = length ClustDist[ClustDist[:, 0]==bestCentToSplit] = bestClustAss #k个簇后: return centList,ClustDist
二.K-menas++:
为了解决因为初始化的问题带来K-Means算法的问题,改进的K-Means算法,即K-Means++算法被提出,K-Means++算法主要是为了能够在聚类中心的选择过程中选择较优的聚类中心。
即每次选下一个中心点的策略,使得它离那些确定下来的中心点都比较远;
算法流程:
算法的缺点:这K个点是迭代产生,第M个点的好坏依赖前M-1个点;而且第一个点是随机产生的;
这K个点相对分散,比原始的k-means好很多,但也不是完美的;
Python实现:(初始化中心点)
轮盘法:
举例说明,三个事件概率分别为A:3/10,B:2/10,C:5/10,现取[0,10]之间随机浮点数,是均匀分布;
当取到[0,3]时,为A事件,取到[3,5]时,为B事件,取到[5,10]时,为C事件;符合各事件概率;
于是,让这个浮点数依次减3,2,5,结果为负数,即为该事件;
此法称为轮盘法;
from random import random
FLOAT_MAX = 1e100 # 设置一个较大的值作为初始化的最小的距离
# 计算欧几里得距离
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA - vecB, 2))) # 求两个向量之间的距离
#返回欧几里得距离的平方
def nearest(point, cluster_centers):
min_dist = FLOAT_MAX
m = np.shape(cluster_centers)[0] #当前已经初始化的聚类中心的个数
for i in range(m):
# 计算point与每个聚类中心之间的距离
d = distEclud(point, cluster_centers[i])**2
# 选择最短距离
if min_dist > d:
min_dist = d
return min_dist
def get_centroids(points, k):
m, n = np.shape(points)
cluster_centers = np.mat(np.zeros((k , n)))
# 1、随机选择一个样本点为第一个聚类中心
index = np.random.randint(0, m)
cluster_centers[0] = np.copy(points[index])
# 2、初始化一个距离的序列
d = [0.0 for _ in range(m)]
for i in range(1, k):
sum_all = 0
for j in range(m):
# 3、对每一个样本找到最近的聚类中心点
d[j] = nearest(points[j], cluster_centers[0:i])
# 4、将所有的最短距离相加
sum_all += d[j]
# 5、取得sum_all之间的随机值
sum_all *= random.random()
# 6、获得距离最远的样本点作为聚类中心点
for j, di in enumerate(d):
sum_all -= di
if sum_all > 0:
continue
cluster_centers[i] = np.copy(points[j])
break
return cluster_centers
from numpy import * from pandas import * # 计算欧几里得距离 def distEclud(vecA, vecB): return sqrt(sum(power(vecA - vecB, 2))) # 求两个向量之间的距离 def testBestSplit(cents,ClustDist): max = 0 i = 0 for x in cents: a = ClustDist[ClustDist[:, 0]== x][:,1] lossSum = sum(multiply(a,a)) if lossSum>max: max = lossSum i = x return i def Two_Kmeans(dataSet, k): m = shape(dataSet)[0] # 初始化第一个聚类中心: 每一列的均值 centroid0 = mean(dataSet, axis=0).tolist() cents = [0] #簇的index列表 centList =[centroid0] # 把均值聚类中心加入中心表中 # 初始化聚类距离表,距离方差 # 列1:数据集对应的聚类中心,列2:数据集行向量到聚类中心距离的平方 ClustDist = zeros((m, 2)) for j in range(m): ClustDist[j,1] = distEclud(centroid0,dataSet[j,:])**2 # 依次生成k个聚类中心 while (len(centList) < k): lowestSSE = inf bestCentToSplit = 0 bestClustAss = [] #最优分割 for i in cents: #当前簇dataSet ptsInCurrCluster = dataSet[ClustDist[:, 0]== i] #print(len(ptsInCurrCluster[:, 0])) #划分 centroidMat, splitClustAss = my_kMeans(ptsInCurrCluster, 2) #上一章 centroidMat = centroidMat.getA() #当前簇划分后的误差平方和 sseSplit = sum(multiply(splitClustAss[:, 1], splitClustAss[:, 1])) #没划分的簇的误差平方和 ClustDist_nosplit = ClustDist[ClustDist[:, 0]!= i] sseNotSplit = sum(multiply(ClustDist_nosplit[:,1],ClustDist_nosplit[:,1])) if (sseSplit + sseNotSplit) < lowestSSE: lowestSSE = sseSplit + sseNotSplit bestCentToSplit = i # 确定聚类中心的最优分隔点 #bestNewCents = centroidMat # 用新的聚类中心更新最优聚类中心 bestClustAss = splitClustAss.copy() # 深拷贝聚类距离表为最优聚类距离表 #检测一下: testi = testBestSplit(cents,ClustDist) print(testi==bestCentToSplit) #验证了:SSE最大簇就是使得划分后整体SSE最小的簇(在绝大部分情况下) #保留分割结果: length = len(cents) cents.insert(cents.index(bestCentToSplit)+1,length) centList[bestCentToSplit] = centroidMat[0].tolist() centList.append(centroidMat[1].tolist()) for x in bestClustAss: if x[0] == 0: x[0] = bestCentToSplit else: x[0] = length ClustDist[ClustDist[:, 0]==bestCentToSplit] = bestClustAss #k个簇后: return centList,ClustDist