1.CURE聚类概述
绝大多数聚类算法或者擅长处理球形和相似大小的聚类.或者在存在孤立点时变得比较脆弱。CURE采用了一种新颖的层次聚类算法.该算法选择基于质心和基于代表对象方法之间的中间策略。它不同于单个质心或对象来代表一个类,而是选择数据空间中固定数目的具有代表性的点。一个类的代表点通过如下方式产生:首先选择类中分散的对象,然后根据一个特定的分数或收缩因子“收缩”或移动它们。在算法的每一步,有最近距离的代表点对(每个点来自于一个不同的类)的两个类被合并。
每个类有多于一个的代表点使得CURE可以适应非球形的几何形状。类的收缩或凝聚可以有助于控制孤立点的影响。因此,CURE对孤立点的处理更加健壮,而且能够识别非球形和大小变化比较大的类。针对大型数据库,CURE采用随机取样和划分两种方法组合:一个随机样本首先被划分,每个划分被部分聚类。
2 算法步骤
- 从源数据对象中抽取一个随机样本S;
- 将样本S分割成一组划分;
- 对每个划分局部的聚类;
- 通过随机样本剔除孤立点。如果一个类增长缓慢就去除它;
- 对局部的类进行聚类,落在每个新形成的类中的代表点根据用户定义的一个收缩因子收缩或向类中心移动。这些点代表和捕捉到了类的形状。
- 用相应的类标签来标记数据。
3.CURE 聚类特点
CURE,Clustering Using Representative 算法的特点如下:
- (1)属于凝聚层次聚类
CURE 算法首先把每个数据点看成一个簇,然后将距离最近的簇 结合,一直到簇的个数达到要求的 K 个为止。
- (2)适应非球形的几何形状
不同于一个质心或者单个点来代表一个类,CURE 算法中每个簇有多个代表点,这使得 CURE算法可以适应非球形的几何形状。
代表点的选取:
首先选择簇中距离质心最远的点做为第一个点,然后依次选择距离已选到的点最远的点,直到选到c 个点为止,这些点尽量得分散,捕获了簇的形状和大小。
- (3)对孤立点的处理更加健壮
另外,CURE 算法还通过“收缩因子”减少离群点对聚类效果的影响。
代表点的收缩或凝聚:
将上面选取到的代表点根据固定的参数α(0≤α≤1 )向该簇的质心收缩,距离质心越远的点(例如离群点)的收缩程度越大,因此CURE对离群点是不太敏感的,这种方法可以有效的降低离群点带来的不利影响。收缩系数α的取值不同,聚类结果也相应不同。
当 α 趋于 0 时,所有的“代表点”都汇聚到质心,算法退化为基于“质心”的聚类;
当 α 趋于 1 时,“代表点”完全没有收缩,算法退化为基于“全连接”的聚类,
因此 α 值需要根据数据特征灵活选取,才能得到更好的聚类结果
在得到收缩后的代表点后,两个簇之间的距离就可以定义为这两个簇中距离最近的两个代表点之间的距离
- (4)识别异常值/离群点
CURE 算法分两个阶段消除异常值的影响。
第一个阶段、是在聚类算法执行到某一阶段(或称当前的簇总数减小到某个值)时,根据簇的增长速度和簇的大小对离群点进行一次识别。
第一阶段,将聚类过程中类成员增长非常缓慢的类作为异常值剔除;
CURE算法由于异常值同其他对象的距离更大,所以其所在类中对象数目的增大就会非常缓慢,甚至不增长。
需要注意的是:
如果这个阶段选择的较早(簇总数过大)的话,会将一部分本应被合并的簇识别为离群点;
如果这个阶段选择的较晚(簇总数过少)的话,离群点很可能在被识别之前就已经合并到某些簇中;
因此原文推荐当前簇的总数为数据集大小的1/3时,进行离群点的识别。
第一阶段有一个很明显的问题,就是当随机采样到的离群点分布的比较近时(即使可能性比较小),这些点会被合并为一个簇,而导致无法将他们识别出来,这时就需要第二阶段的来进行处理。
第二阶段,是指在聚类的最后阶段,将非常小的簇删除;
由于离群点占的比重很小,而在层次聚类的最后几步中,每个正常簇的粒度都是非常高的,因此很容易将他们识别出来,一般当簇的总数缩减到大约为 k 时,进行第二阶段的识别。
- (5)适应大规模数据
为了适应大型数据,CURE算法采用了随机抽样和分割相结合的手段。
随机抽样将原始数据集中的部分点提取出来,然后试图在这些点上实施CURE层次聚类算法,采样形成的数据子集要适应内存的需要并且与原始数据集相比要足够小。因此,这种随机采样的方法会大大提升CURE的执行速度,并且由于采样过程会对离群点进行过滤因而可以提高聚类质量。
另外,CURE算法还引入了分割的手段,即样本分割成几个部门,然后针对各个部分中的对象进行局部聚类,形成子类,再针对子类进行聚类,新出新的类。
4.优缺点
优点
1)可以发现复杂空间的簇
2)受噪点影响小
缺点
1)参数较多,包括采样的大小、聚类的个数、收缩的比例等;
2) 抽样有误差;
3)难以发现形状非常复杂的空间簇(如中空形状),对空间数据密度差异敏感
4)虽然 CURE 聚类是针对大规模数据库设计的算法,但是当数据量剧增时,效率仍然不能满足需求
5.Python实现
# -*- coding: utf-8 -*-###########################################################################################
# Implementation of CURE (Clustering Using Representatives) Clustering Algorithm
# Author for codes: Chu Kun(kun_chu@outlook.com)
# Paper: https://www.sciencedirect.com/science/article/pii/S0306437901000084
# Reference: https://github.com/Kchu/CURE-cluster-python
###########################################################################################import numpy as np
import scipy.spatial.distance as distance
import sys# Returns the distance between two vectors
def dist(vecA, vecB):return np.sqrt(np.power(vecA - vecB, 2).sum())# This class describes the data structure and method of operation for CURE clustering.
class CureCluster:def __init__(self, id__, center__):self.points = center__self.repPoints = center__self.center = center__self.index = [id__]def __repr__(self):return "Cluster " + " Size: " + str(len(self.points))# Computes and stores the centroid of this cluster, based on its pointsdef computeCentroid(self, clust):totalPoints_1 = len(self.index)totalPoints_2 = len(clust.index)self.center = (self.center*totalPoints_1 + clust.center*totalPoints_2) / (totalPoints_1 + totalPoints_2)# Computes and stores representative points for this clusterdef generateRepPoints(self, numRepPoints, alpha):tempSet = Nonefor i in range(1, numRepPoints+1):maxDist = 0maxPoint = Nonefor p in range(0, len(self.index)):if i == 1:minDist = dist(self.points[p,:], self.center)else:X = np.vstack([tempSet, self.points[p, :]])tmpDist = distance.pdist(X)minDist = tmpDist.min()if minDist >= maxDist:maxDist = minDistmaxPoint = self.points[p,:]if tempSet is None:tempSet = maxPointelse:tempSet = np.vstack((tempSet, maxPoint))for j in range(len(tempSet)):if self.repPoints is None:self.repPoints = tempSet[j,:] + alpha * (self.center - tempSet[j,:])else:self.repPoints = np.vstack((self.repPoints, tempSet[j,:] + alpha * (self.center - tempSet[j,:])))# Computes and stores distance between this cluster and the other one.def distRep(self, clust):distRep = float('inf')for repA in self.repPoints:if type(clust.repPoints[0]) != list:repB = clust.repPointsdistTemp = dist(repA, repB)if distTemp < distRep:distRep = distTempelse:for repB in clust.repPoints:distTemp = dist(repA, repB)if distTemp < distRep:distRep = distTempreturn distRep# Merges this cluster with the given cluster, recomputing the centroid and the representative points.def mergeWithCluster(self, clust, numRepPoints, alpha):self.computeCentroid(clust)self.points = np.vstack((self.points, clust.points))self.index = np.append(self.index, clust.index)self.repPoints = Noneself.generateRepPoints(numRepPoints, alpha)# Describe the process of the CURE algorithm
def runCURE(data, numRepPoints, alpha, numDesCluster):# InitializationClusters = []numCluster = len(data)numPts = len(data)distCluster = np.ones([len(data), len(data)])distCluster = distCluster * float('inf')for idPoint in range(len(data)):newClust = CureCluster(idPoint, data[idPoint,:])Clusters.append(newClust)for row in range(0, numPts):for col in range(0, row):distCluster[row][col] = dist(Clusters[row].center, Clusters[col].center)while numCluster > numDesCluster:if np.mod(numCluster, 50) == 0:print('Cluster count:', numCluster)# Find a pair of closet clustersminIndex = np.where(distCluster == np.min(distCluster))minIndex1 = minIndex[0][0]minIndex2 = minIndex[1][0]# MergeClusters[minIndex1].mergeWithCluster(Clusters[minIndex2], numRepPoints, alpha)# Update the distCluster matrixfor i in range(0, minIndex1):distCluster[minIndex1, i] = Clusters[minIndex1].distRep(Clusters[i])for i in range(minIndex1+1, numCluster):distCluster[i, minIndex1] = Clusters[minIndex1].distRep(Clusters[i])# Delete the merged cluster and its disCluster vector.distCluster = np.delete(distCluster, minIndex2, axis=0)distCluster = np.delete(distCluster, minIndex2, axis=1)del Clusters[minIndex2]numCluster = numCluster - 1print('Cluster count:', numCluster)# Generate sample labelsLabel = [0] * numPtsfor i in range(0, len(Clusters)):for j in range(0, len(Clusters[i].index)):Label[Clusters[i].index[j]] = i + 1return Label