Introduction 最近在做数据分析相关研究时需要用到K-Medoids算法对数据进行聚类,然后在平时常用的Python的sklearn.cluster
库模块中没有找到这个算法函数,于是想着找一下别的库。首先是发现了一位研究数据分析的博主的一份笔记:K-medoids聚类算法原理简介&Python与R的实现 ,在文末评论里他提到博文中用的pyclust
模块不维护了,然后推荐了pyclustering
模块和其参考文档 。为加深自己的理解,特此对文档做一份学习笔记。
K-Medoids文档渣翻与应用 pyclustering.cluster.kmedoids.kmedoids :一个用于表征K-Medoids算法的类
公共类成员函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def __init__ (self, data, initial_index_medoids, tolerance=0.001 , ccore=True , kwargs )# K -Medoids 聚类算法的构造初始化 def process (self )# 根据K -Medoids 算法的规则进行聚类分析 def predict (self, points )# 对于每个点计算离它距离最近的簇 def get_clusters (self )# 返回已分配好群集的列表,每个群集包含数据列表中对象的索引 def get_medoids (self )# 返回由输入数据中的索引表示的已分配群集的medoid 列表 def get_cluster_encoding (self )# 返回指示如何对群集进行编码的群集结果表示类型
这里对每个函数的注释仅是简单翻译了一下官方文档里的那句话,对函数的详细使用和分析可结合下面详细说明中代码样例里我注释的笔记来理解。
详细说明 该算法与K-Means相比对异常值的敏感性较低。K-Medoids与K-Medians的主要区别在于,K-Medoids使用输入数据空间中的已有点作为每个簇的中心点,但K-Medians中的中心点可以是不真实的对象(而不是从输入数据空间中选取已有点)。
下面是官方给出的一个聚类样例,在代码中加入了一些本人做的笔记注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from pyclustering.cluster.kmedoids import kmedoidsfrom pyclustering.cluster import cluster_visualizerfrom pyclustering.utils import read_samplefrom pyclustering.samples.definitions import FCPS_SAMPLESsample = read_sample(FCPS_SAMPLES.SAMPLE_TWO_DIAMONDS) initial_medoids = [1 , 500 ] kmedoids_instance = kmedoids(sample, initial_medoids) kmedoids_instance.process() clusters = kmedoids_instance.get_clusters() print(clusters) medoids = kmedoids_instance.get_medoids() print(medoids) visualizer = cluster_visualizer() visualizer.append_clusters(clusters, sample) visualizer.show()
各点之间距离的计算方法可以由kmedoids类对象初始化里的参数“Metric”指定。需要注意的是kmedoids(metric=metric)
传入的metric是一个distance_metric类对象,而这个类对象是pyclustering
模块里的另一个定义的类(详细文档 ),调用的时候需要import一下。可选择的距离类型参考type_metric的文档 ,官方选用了MINKOWSKI距离作为示例:
1 2 3 4 5 6 7 8 9 10 11 12 from pyclustering.utils.metric import distance_metric, type_metricmetric = distance_metric(type_metric.MINKOWSKI, degree=2 ) kmedoids_instance = kmedoids(sample, initial_medoids, metric=metric) kmedoids_instance.process() clusters = kmedoids_instance.get_clusters()
下面这个就体现了K-Medoids算法相比于K-Means的一个优越性所在:可适用类别(categorical)类型的特征。因为K-Means每次计算质心取的均值可能不是一个真实的点,所以对原始数据有要求为数据点类型,能够映射到对应维度的一个空间里的具体位置中,即要求数据点处在一个欧氏空间之中。然而并不是所有的数据都能满足这样的要求,这里K-Medoids就解决了这个问题(参考文章 ),在算法里重新计算中心点时只需要点与点之间的距离即可,因此其适用范围可扩散到各种各样的数据,前提是只要你可以正确计算出每个元素之间的距离即可。pyclustering.cluster.kmedoids
里即可使用距离矩阵代替点序列来提高性能。为此,在创建K-Medoids类对象时要使用data_type这个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pyclustering.utils import calculate_distance_matrixsample = read_sample(FCPS_SAMPLES.SAMPLE_TWO_DIAMONDS) matrix = calculate_distance_matrix(sample) kmedoids_instance = kmedoids(matrix, initial_medoids, data_type='distance_matrix' ) kmedoids_instance.process() clusters = kmedoids_instance.get_clusters() medoids = kmedoids_instance.get_medoids()
构造和析构函数文档 1 2 3 4 5 6 7 8 def pyclustering .cluster .kmedoids .kmedoids .__init__ ( self, data, initial_index_medoids, tolerance = 0.001 , ccore = True , kwargs )
参数:
参数名
含义
data
(list型)输入数据,表征点(对象)的列表,每个点的表征格式都应为列表或元组。
initial_index_medoids
(list型)初始中心点的索引(输入数据中点的索引)。
tolerance
(double型)算法停止条件:如果在重新计算簇的中心点中,新旧中心点的距离变化的最大值小于这个tolerance,则算法停止运算。默认值为0.001。
ccore
(bool型)如果指定CCORE库(C++的pyclustering库)的话,则用于代替Python代码进行聚类。
**kwargs
任意关键字参数(可用参数:’metric’, ‘data_type’, ‘itermax’)。
关键字参数:
metric (distance_metric型): 用于计算两点之间距离的计算方法,默认为EUCLIDEAN_SQUARE。
data_type (string型): 输入样本数据的数据类型,有两种:’points’, ‘distance_matrix’,默认为’points’。
itermax (uint型): 聚类分析的最大迭代次数,默认为200。
成员函数文档 成员函数中get_cluster_encoding(),get_clusters(),get_medoids(),process()
都在前面示例代码里提到了,而且原文档中这里的介绍也比较简单就不再重复。在这里重点介绍一下predict()
函数:
1 2 3 4 def pyclustering .cluster .kmedoids .kmedoids .predict ( self, points )
用于计算对于每个点的最近聚类簇。
参数
[in]points(array_like型): 已计算好最近邻聚类簇的点集。
返回值
(list型) 对于每个点的最近邻聚类簇的列表形式。每个聚类簇用索引来表示。如果process()
方法在此之前未被调用的话会返回一个空集。
一个样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pyclustering.cluster.kmedoids import kmedoidsfrom pyclustering.samples.definitions import SIMPLE_SAMPLESfrom pyclustering.utils import read_samplesample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE3) initial_medoids = [4 , 12 , 25 , 37 ] kmedoids_instance = kmedoids(sample, initial_medoids) kmedoids_instance.process() points = [[0.35 , 0.5 ], [2.5 , 2.0 ]] closest_clusters = kmedoids_instance.predict(points) print(closest_clusters)
Elbow法则的应用 对k值的取值一直是K-Means、K-Medoids等划分类聚类算法的一个关键问题,而Elbow法则是最常用的一种方法。在研究pyclustering
文档时无意中发现里面还有pyclustering.cluster.elbow
这个模块,于是打算尝试一下把这个封装好的Elbow法则结合到上面的K-Medoids算法中。
pyclustering.cluster.elbow文档解析 具体的分析内容均写在下面的代码注释里了:
公共类成员函数 1 2 3 4 5 6 7 8 9 10 11 def __init__ (self, data, kmin, kmax, kwargs )# 构造Elbow 方法。 def process (self )# 执行分析以找出适当数量的群集。 def get_amount (self )# 返回由Elbow 法则得出的k 值。 def get_wce (self )# 返回一个列表,每个元素是(kmin, kmin + 1 , ..., kmax - 1 )这个范围里不同的k 取值所对应的误差函数的值。也就是可以绘制那条肘部法则折线图的取值。
详细说明与代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from pyclustering.cluster.kmeans import kmeans, kmeans_visualizerfrom pyclustering.cluster.center_initializer import kmeans_plusplus_initializerfrom pyclustering.cluster.elbow import elbow from pyclustering.utils import read_samplefrom pyclustering.samples.definitions import SIMPLE_SAMPLESsample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE3) kmin, kmax = 1 , 10 elbow_instance = elbow(sample, kmin, kmax) elbow_instance.process() amount_clusters = elbow_instance.get_amount() wce = elbow_instance.get_wce() centers = kmeans_plusplus_initializer(sample, amount_clusters, amount_candidates=kmeans_plusplus_initializer.FARTHEST_CENTER_CANDIDATE).initialize() kmeans_instance = kmeans(sample, centers) kmeans_instance.process() clusters = kmeans_instance.get_clusters() centers = kmeans_instance.get_centers() kmeans_visualizer.show_clusters(sample, clusters, centers)
把elbow模块应用到K-Medoids中 研究过elbow模块里的源代码可以发现elbow里的误差函数是由K-Means算法里的结果计算出来的,因此由此得出的K值应该是适用于K-Means里的K值,而并不一定适用于K-Medoids里。不过为了探索一下这个难得封装好的elbow法则在K-Medoids里的应用,我尝试了一下利用K-Means确定的K值来做K-Medoids聚类,代码及注释放在下面。
需要注意的一点是初始化中心点时,调用了pyclustering.cluster.center_initializer
模块中的K-Means++式初始化方法,而该模块中的initialize()
函数有一个bool型的参数return_index,若为True则返回初始化中心点的索引(可用于pyclustering
模块的K-Medoids算法中),否则默认为False,返回初始化中心点的数值数组(用于pyclustering
模块的K-Means算法中)。在下面的代码中我分别用centers_data和centers_index两个变量接收两个不同格式的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 from pyclustering.cluster.kmeans import kmeans, kmeans_visualizerfrom pyclustering.cluster.kmedoids import kmedoidsfrom pyclustering.cluster.center_initializer import kmeans_plusplus_initializerfrom pyclustering.cluster.elbow import elbowfrom pyclustering.utils import read_samplefrom pyclustering.samples.definitions import SIMPLE_SAMPLESimport matplotlib.pyplot as pltsample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE3) kmin, kmax = 1 , 10 elbow_instance = elbow(sample, kmin, kmax) elbow_instance.process() amount_clusters = elbow_instance.get_amount() wce = elbow_instance.get_wce() centers_data = kmeans_plusplus_initializer(sample, amount_clusters, amount_candidates=kmeans_plusplus_initializer.FARTHEST_CENTER_CANDIDATE).initialize() centers_index = kmeans_plusplus_initializer(sample, amount_clusters, amount_candidates=kmeans_plusplus_initializer.FARTHEST_CENTER_CANDIDATE).initialize(return_index=True ) kmeans_instance = kmeans(sample, centers_data) kmeans_instance.process() kmeans_clusters = kmeans_instance.get_clusters() kmeans_centers = kmeans_instance.get_centers() for row in kmeans_centers: print(row) print('*********K-Means结果*********' ) i=1 for cluster in kmeans_clusters: print("第%d类个数:%d" %(i,len (cluster))) i+=1 kmedoids_instance = kmedoids(sample, centers_index, itermax=10000 ) kmedoids_instance.process() kmedoids_clusters = kmedoids_instance.get_clusters() kmedoids_medoids = kmedoids_instance.get_medoids() for item in kmedoids_medoids: print(sample[item]) print('*********K-Medoids结果*********' ) i=1 for cluster in kmedoids_clusters: print("第%d类个数:%d" %(i,len (cluster))) i+=1 x=list (range (1 ,10 )) plt.plot(x,wce) plt.xlabel('K' ) plt.ylabel('error' ) plt.xticks(x) plt.grid() plt.show()
代码中的sample变量可更改为任意符合格式的数据。我用我自己的一些研究数据尝试了一下,总的来说从聚类后各类别的数量上来看,K-Means和K-Medoids聚类的结果相差并不大。因此从我自己数据的角度来看,把elbow模块用在K-Medoids算法之前帮忙确定K值这个做法不会产生太大问题,但毕竟这种做法在逻辑上严谨来说是说不通,需要具体数据具体分析。 在反复运行程序时出现变化的因素有:
在elbow_instance.process()
过程中由于K-Means算法本身的随机性,重复运行程序有可能会得出不同的K值。
在kmeans_plusplus_initializer().initialize()
初始化中心点时,由于K-Means++算法本身在概率上的随机性,重复运行程序有可能会得出不同的初始化中心点。
在kmeans_instance.process()
和kmedoids_instance.process()
过程中,由于算法本身的随机性,重复运行程序可能会出现每一类的数目有稍微变化的情况。然后针对前面“K-Means和K-Medoids聚类的结果相差并不大”,出现的特例在于有可能对于一个K值,K-Means(K-Medoids)的聚类结果是每个类别数目较为平均可观,但是K-Medoids(K-Means)的聚类结果中有某一类或数类里只有1个或几个元素(就是明显的无意义分类)。这种情况在反复运行程序过程中不会经常出现,但本人也碰到有出现过的情况,这个取决于数据本身的分布特征以及前面确定K值和初始化质心等工作的质量。
After 有意向进一步研究一下elbow模块的源代码,仿照一下写一个用K-Medoids算法得出误差函数的elbow模块,把elbow法则真正结合到K-Medoids模块算法中。