热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

kmeans算法性能改进_kmeans++算法+kmeans++优化算法+距离计算优化

***题记:*我一直在路上,害怕停下在我的另一篇博客里《读sklearn源码学机器学习——kmeans聚类算法》我详细的阐述了kmeans算法的工作过程

***题记:*我一直在路上,害怕停下
在这里插入图片描述
在我的另一篇博客里《读sklearn源码学机器学习——kmeans聚类算法》我详细的阐述了kmeans算法的工作过程。截至目前为止,还没有深入的刨析kmeans算法的工作原理(会用和知道怎么用,跟理解背后深刻的数学原理是有本质区别的,我对此深感敬畏)。其实kmeas算法和高斯混合算法都是em算法的具体应用。今天站在工程应用的角度,刨析kmeans在工程中的应用和优化。主要是弥补我上篇博客中没有说明白的两个函数(elativeDist,squaredNorna)和一个初始化。

1、kmeans++算法

kmeans++是David Arthur and Sergei Vassilvitskii在一篇名为“k-means++: The Advantages of Careful Seeding(http://ilpubs.stanford.edu:8090/778/1/2006-13.pdf)文中中首次提及的,可以用来解决kmeans在初始化的时候,随机选取初始点带来的负面影响。算法的工作过程如下:
在这里插入图片描述
翻译成中文:
1a、随机的选取一个样本点作为一个一个聚类中心。
2b、在选择下一个聚类中心的时候,以到达现有聚类中心的距离为依据进行聚类中心得选择。离现在的聚类中心越远被选择的概率越大。
(一般通过轮盘赌选择,概率一般用欧式距离平方占比)
1c、重复上述的过程直到选出所有的聚类中心。
基本依据:在选择初始距离的时候,样本之间离得越远越好。
kmeans++实现过程:
数据通过矩阵的形式存在matrix_data中(个人觉得自己的实现过程还是优雅且美妙的)。
(可以仔细体会下对轮盘赌的实现过程以及numpy相应函数的应用,可以猜测np.searchsorted()的算法复杂度最高时log(n),比我们遍历选取要划算多了)

center=np.zeros((n_cluster,n_feature))
center[0]=matrix_data[np.random.randint(n_samples)]
for i in range(1,n_cluster):#计算每个样本点到已有中心点的距离distance_to_centers=euclideanDistance(matrix_data,center[[i for i in range(i)]],square=True)#选取据距离最小值closed_distance=np.min(distance_to_centers,axis=1)#轮盘赌denominator=closed_distance.sum()point=np.random.rand()*denominator#轮盘赌的指针be_choosed=np.searchsorted(np.cumsum(closed_distance),point)be_choosed=min(be_choosed,n_samples-1)#避免选的是最后一个而造成下标越界center[i]=matrix_data[be_choosed]

其中欧式距离的计算函数euclideanDistance实现原理在上一篇博客中证明过,过程如下:

import pandas as pd
import numpy as np
def rowNorms(X):#对行每个元素取平方加和return np.einsum("ij,ij->i",X,X)
def euclideanDistance(x,y,square=False):#x的每个样本与y之间的距离""":param x: 矩阵x:param y: 矩阵y:param squared: 表示是否返回二者欧式距离的平方值,很明显如果返回欧式距离的平方,计算量又小一些:return: 矩阵x中的每一个样本与y中样本之间的距离""""""对于这个操作的理解一定要理解下面这个操作np.array([[1],[2],[1]])+np.array([[1,2,3],[4,5,6],[7,8,9]])Out[28]:array([[ 2, 3, 4],[ 6, 7, 8],[ 8, 9, 10]])np.array([[ 2, 3, 4],[ 6, 7, 8],[ 8, 9, 10]])+np.array([1,2,3])Out[28]:array([[ 3, 5, 7],[ 7, 9, 11],[ 9, 11, 13]])P矩阵与C矩阵每一行之间的距离可以用公式[[p1^2],[p2^2],[...]]-2PC^T+[c1^2,c2^2,c3^2...](p1^2是p1行向量平方和,c1^2是c1行向量平方和)"""xx=rowNorms(x)[:,np.newaxis]#转化为列向量,便于让dot的每一行都相加同一个数yy=rowNorms(y)[np.newaxis,:]#与xx同理dot=np.dot(x,y.T)res = xx + yy - 2 * dotreturn res if square else np.sqrt(res)

kmeans++的优化

该方法David Arthur and Sergei Vassilvitskii没有发表出来,但是在机器学习开源社区上被用到,即在用kmeas++计算距离的过程中,每一轮再增加一个最大迭代次数,比如通过轮盘赌一次选取n_local_trials个,然后再依次选取能做到使距离平方和最小的聚类中心点(总距离平方和最小中的离现有中心点最大的点)。n_local_trals的选取原则为(2+log(聚类中心点数))。
实现过程如下(引用的sklearn开源社区源码(https://github.com/scikit-learn/scikit-learn)):

n_samples, n_features = X.shape
centers = np.empty((n_clusters, n_features), dtype=X.dtype)
n_local_trials = 2 + int(np.log(n_clusters))
center_id = random_state.randint(n_samples)
centers[0] = X[center_id]
closest_dist_sq = euclidean_distances(centers[0, np.newaxis], X, Y_norm_squared=x_squared_norms,squared=True)
current_pot = closest_dist_sq.sum()
for c in range(1, n_clusters):rand_vals = random_state.random_sample(n_local_trials) * current_potcandidate_ids = np.searchsorted(stable_cumsum(closest_dist_sq),rand_vals)np.clip(candidate_ids, None, closest_dist_sq.size - 1,out=candidate_ids)distance_to_candidates = euclidean_distances(X[candidate_ids], X, Y_norm_squared=x_squared_norms, squared=True)np.minimum(closest_dist_sq, distance_to_candidates,out=distance_to_candidates)candidates_pot = distance_to_candidates.sum(axis=1)best_candidate = np.argmin(candidates_pot)current_pot = candidates_pot[best_candidate]closest_dist_sq = distance_to_candidates[best_candidate]best_candidate = candidate_ids[best_candidate]centers[c] = X[best_candidate]return centers

距离计算的优化

假设有4个样本3个特征如下:

_f1f2f3
1123
2456
3789
4101112

再比如想要把样本聚类为2类,这样类的聚类中线为1和2。将上面数据存为矩阵:
x=np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
通过euclideanDistance计算所有样本点到两个聚类中心的距离,如下:

euclideanDistance(x,x[[0,1]])
Out[4]:
array([[ 0. , 5.19615242],[ 5.19615242, 0. ],[10.39230485, 5.19615242],[15.58845727, 10.39230485]])

结果显示1到样本1的距离0,到样本2的距离为5.19615242,2到样本1的距离为5.19615242到样本2的距离为0,依次类推我们就得到了各个样本到各个聚类中心的距离,并以此将各个样本贴上不同的类别标签上面的类别标签为[1,2,2,2]。
所以我们再选择的过程中直接比较的是行向量的相对大小,那如果我们在求||x-x[[0,1]]||的时候行向量同时减去一个||x||^2哪?分类的结果其实是不变的,但是对于这里的距离求取过程却可以简化为如下:

def relativeDist(x,y):#x的每个样本与y之间的相对距离"""我们知道,如果单纯选出x行向量与y之间最小的距离,完全可以同时减去xx也就是(x-y)^2-x^2"""yy=rowNorms(y)[np.newaxis,:]dot=np.dot(x,y.T)res=-2*dot+yyreturn res

该方法在计算距离的时候每次减少的计算量与x的大小成正比,减少了计算量。
上面例子用relativeDist求取的结果为:

relativeDist(x,x[[0,1]])
Out[5]:
array([[ -14, 13],[ -50, -77],[ -86, -167],[-122, -257]])

分类结果依然是[1,2,2,2]。


推荐阅读
author-avatar
雅白斋ab
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有