前面介绍了k-means算法,并列举了该算法的缺点。而K中心点算法(K-medoids)正好能解决k-means算法中的 “噪声”敏感这个问题。 如何解决的呢? 首先,我们得介绍下k-means算法为什么会对“噪声”敏感。还记得K-means寻找质点的过程吗?对某类簇中所有的样本点维度求平均值,即获得该类簇质点的维度。当聚类的样本点中有“噪声”(离群点)时,在计算类簇质点的过程中会受到噪声异常维度的干扰,造成所得质点和实际质点位置偏差过大,从而使类簇发生“畸变”。 Eg: 类簇C1中已经包含点A(1,1)、B(2,2)、 C(1,2)、 D(2,1), 假设N(100,100)为异常点,当它纳入类簇C1时,计算质点Centroid((1+2+1+2+100)/5,(1+2+2+1+100)/5)=centroid(21,21),此时可能造成了类簇C1质点的偏移,在下一轮迭代重新划分样本点的时候,将大量不属于类簇C1的样本点纳入,因此得到不准确的聚类结果。 为了解决该问题,K中心点算法(K-medoids)提出了新的质点选取方式,而不是简单像k-means算法采用均值计算法。在K中心点算法中,每次迭代后的质点都是从聚类的样本点中选取,而选取的标准就是当该样本点成为新的质点后能提高类簇的聚类质量,使得类簇更紧凑。该算法使用绝对误差标准来定义一个类簇的紧凑程度。 (p是空间中的样本点,Oj是类簇Cj的质点) 如果某样本点成为质点后,绝对误差能小于原质点所造成的绝对误差,那么K中心点算法认为该样本点是可以取代原质点的,在一次迭代重计算类簇质点的时候,我们选择绝对误差最小的那个样本点成为新的质点。 Eg:样本点A –>E1=10 样本点B –>E2=11 样本点C –>E3=12 原质点O–>E4=13,那我们选举A作为类簇的新质点。 与K-means算法一样,K-medoids也是采用欧几里得距离来衡量某个样本点到底是属于哪个类簇。终止条件是,当所有的类簇的质点都不在发生变化时,即认为聚类结束。
该算法除了改善
K-means
的“噪声”敏感以后,其他缺点和
K-means
一致,并且由于采用新的质点计算规则,也使得算法的时间复杂度上升:
O
(
k(n-k)2
)
Java实现代码如下: package com.kmedoids; import java.util.ArrayList; public class Cluster { private String clusterName; // 类簇名 private Medoid medoid; // 类簇的质点 private ArrayList dataPoints; // 类簇中各样本点 public Cluster(String clusterName) { this.clusterName = clusterName; this.medoid = null; // will be set by calling setCentroid() dataPoints = new ArrayList(); } public void setMedoid(Medoid c) { medoid = c; } public Medoid getMedoid() { return medoid; } public void addDataPoint(DataPoint dp) { // called from CAInstance dp.setCluster(this);// 标注该类簇属于某点,计算欧式距离 this.dataPoints.add(dp); } public void removeDataPoint(DataPoint dp) { this.dataPoints.remove(dp); } public int getNumDataPoints() { return this.dataPoints.size(); } public DataPoint getDataPoint(int pos) { return (DataPoint) this.dataPoints.get(pos); } public String getName() { return this.clusterName; } public ArrayList getDataPoints() { return this.dataPoints; } } ------------------------------------ package com.kmedoids; import java.util.ArrayList; public class DataPoint { private double dimension[]; //样本点的维度 private String pointName; //样本点名字 private Cluster cluster; //类簇 private double euDt;//样本点到质点的距离 public DataPoint(double dimension[], String pointName) { this.dimension = dimension; this.pointName = pointName; this.cluster = null; } public void setCluster(Cluster cluster) { this.cluster = cluster; } public double calEuclideanDistanceSum() { double sum=0.0; Cluster cluster=this.getCluster(); ArrayList dataPoints=cluster.getDataPoints(); for(int i=0;i double[] dims=dataPoints.get(i).getDimensioin(); for(int j=0;j double temp=Math.pow((dims[j]-this.dimension[j]),2); sum=sum+temp; } } return Math.sqrt(sum); } public double testEuclideanDistance(Medoid c) { double sum=0.0; double[] cDim=c.getDimensioin(); for(int i=0;i double temp=Math.pow((dimension[i]-cDim[i]),2); sum=sum+temp; } return Math.sqrt(sum); } public double[] getDimensioin() { return this.dimension; } public Cluster getCluster() { return this.cluster; } public double getCurrentEuDt() { return this.euDt; } public String getPointName() { return this.pointName; } } ------------------------------- package com.kmedoids; import java.util.ArrayList; public class Medoid{ private double dimension[]; // 质点的维度 private Cluster cluster; //所属类簇 private double etdDisSum;//Medoid到本类簇中所有的欧式距离之和 public Medoid(double dimension[]) { this.dimension = dimension; } public void setCluster(Cluster c) { this.cluster = c; } public double[] getDimensioin() { return this.dimension; } public Cluster getCluster() { return this.cluster; } public void calcMedoid() {// 取代价最小的点 calcEtdDisSum(); double minEucDisSum = this.etdDisSum; ArrayList dps = this.cluster.getDataPoints(); for (int i = 0; i < dps.size(); i++) { double tempeucDisSum = dps.get(i).calEuclideanDistanceSum(); if (tempeucDisSum < minEucDisSum) { dimension = dps.get(i).getDimensioin(); minEucDisSum=tempeucDisSum; } } } // 计算该Medoid到同类簇所有样本点的欧斯距离和 private void calcEtdDisSum() { double sum=0.0; Cluster cluster=this.getCluster(); ArrayList dataPoints=cluster.getDataPoints(); for(int i=0;i double[] dims=dataPoints.get(i).getDimensioin(); for(int j=0;j double temp=Math.abs(dims[j]-this.dimension[j]); sum=sum+temp; } } etdDisSum= sum; } } -------------------------- package com.kmedoids; import java.util.ArrayList; public class ClusterAnalysis { private Cluster[] clusters;// 所有类簇 private int miter;// 迭代次数 private ArrayList dataPoints = new ArrayList();// 所有样本点 private int dimNum;//维度 public ClusterAnalysis(int k, int iter, ArrayList dataPoints,int dimNum) { clusters = new Cluster[k];// 类簇种类数 for (int i = 0; i < k; i++) { clusters[i] = new Cluster("Cluster:" + i); } this.miter = iter; this.dataPoints = dataPoints; this.dimNum=dimNum; } public int getIterations() { return miter; } public ArrayList[] getClusterOutput() { ArrayList v[] = new ArrayList[clusters.length]; for (int i = 0; i < clusters.length; i++) { v[i] = clusters[i].getDataPoints(); } return v; } public void startAnalysis(double[][] medoids) { setInitialMedoids(medoids); double[][] newMedoids=medoids; double[][] oldMedoids=new double[medoids.length][this.dimNum]; while(!isEqual(oldMedoids,newMedoids)){ for(int m = 0; m < clusters.length; m++){//每次迭代开始情况各类簇的点 clusters[m].getDataPoints().clear(); } for (int j = 0; j < dataPoints.size(); j++) { int clusterIndex=0; double minDistance=Double.MAX_VALUE; for (int k = 0; k < clusters.length; k++) {//判断样本点属于哪个类簇 double eucDistance=dataPoints.get(j).testEuclideanDistance(clusters[k].getMedoid()); if(eucDistance minDistance=eucDistance; clusterIndex=k; } } //将该样本点添加到该类簇 clusters[clusterIndex].addDataPoint(dataPoints.get(j)); } for(int m = 0; m < clusters.length; m++){ clusters[m].getMedoid().calcMedoid();//重新计算各类簇的质点 } for(int i=0;i for(int j=0;j oldMedoids[i][j]=newMedoids[i][j]; } } for(int n=0;n newMedoids[n]=clusters[n].getMedoid().getDimensioin(); } this.miter++; } } private void setInitialMedoids(double[][] medoids) { for (int n = 0; n < clusters.length; n++) { Medoid medoid = new Medoid(medoids[n]); clusters[n].setMedoid(medoid); medoid.setCluster(clusters[n]); } } private boolean isEqual(double[][] oldMedoids,double[][] newMedoids){ boolean flag=false; for(int i=0;i for(int j=0;j if(oldMedoids[i][j]!=newMedoids[i][j]){ return flag; } } } flag=true; return flag; } } -------------------------------------------- package com.kmedoids; import java.util.ArrayList; import java.util.Iterator; public class TestMain { public static void main (String args[]){ ArrayList dataPoints = new ArrayList(); double[] a={2,3}; double[] b={2,4}; double[] c={1,4}; double[] d={1,3}; double[] e={2,2}; double[] f={3,2}; double[] g={8,7}; double[] h={8,6}; double[] i={7,7}; double[] j={7,6}; double[] k={8,5}; double[] l={100,2};//孤立点 double[] m={8,20}; double[] n={8,19}; double[] o={7,18}; double[] p={7,17}; double[] q={7,20}; dataPoints.add(new DataPoint(a,"a")); dataPoints.add(new DataPoint(b,"b")); dataPoints.add(new DataPoint(c,"c")); dataPoints.add(new DataPoint(d,"d")); dataPoints.add(new DataPoint(e,"e")); dataPoints.add(new DataPoint(f,"f")); dataPoints.add(new DataPoint(g,"g")); dataPoints.add(new DataPoint(h,"h")); dataPoints.add(new DataPoint(i,"i")); dataPoints.add(new DataPoint(j,"j")); dataPoints.add(new DataPoint(k,"k")); dataPoints.add(new DataPoint(l,"l")); dataPoints.add(new DataPoint(m,"m")); dataPoints.add(new DataPoint(n,"n")); dataPoints.add(new DataPoint(o,"o")); dataPoints.add(new DataPoint(p,"p")); dataPoints.add(new DataPoint(q,"q")); ClusterAnalysis ca=new ClusterAnalysis(3,0,dataPoints,2); double[][] cen={{8,7},{8,6},{7,7}}; ca.startAnalysis(cen); ArrayList[] v = ca.getClusterOutput(); for (int ii=0; ii ArrayList tempV = v[ii]; System.out.println("-----------Cluster"+ii+"---------"); Iterator iter = tempV.iterator(); while(iter.hasNext()){ DataPoint dpTemp = (DataPoint)iter.next(); System.out.println(dpTemp.getPointName()); } } } } 层次聚类算法: 前面介绍的K-means算法和K中心点算法都属于划分式(partitional)聚类算法。层次聚类算法是将所有的样本点自底向上合并组成一棵树或者自顶向下分裂成一棵树的过程,这两种方式分别称为凝聚和分裂。 凝聚层次算法: 初始阶段,将每个样本点分别当做其类簇,然后合并这些原子类簇直至达到预期的类簇数或者其他终止条件。 分裂层次算法: 初始阶段,将所有的样本点当做同一类簇,然后分裂这个大类簇直至达到预期的类簇数或者其他终止条件。 两种算法的代表: 传统的凝聚层次聚类算法有AGENES,初始时,AGENES将每个样本点自为一簇,然后这些簇根据某种准则逐渐合并,例如,如果簇C1中的一个样本点和簇C2中的一个样本点之间的距离是所有不同类簇的样本点间欧几里得距离最近的,则认为簇C1和簇C2是相似可合并的。 传统的分裂层次聚类算法有DIANA,初始时DIANA将所有样本点归为同一类簇,然后根据某种准则进行逐渐分裂,例如类簇C中两个样本点A和B之间的距离是类簇C中所有样本点间距离最远的一对,那么样本点A和B将分裂成两个簇C1和C2,并且先前类簇C中其他样本点根据与A和B之间的距离,分别纳入到簇C1和C2中,例如,类簇C中样本点O与样本点A的欧几里得距离为2,与样本点B的欧几里得距离为4,因为Distance(A,O)那么O将纳入到类簇C1中。 如图所示: 算法:AGENES。传统凝聚层次聚类算法 输入:K:目标类簇数 D:样本点集合 输出:K个类簇集合 方法:1) 将D中每个样本点当做其类簇; 2) repeat 3) 找到分属两个不同类簇,且距离最近的样本点对; 4) 将两个类簇合并; 5) util 类簇数=K 算法:DIANA。传统分裂层次聚类算法 输入:K:目标类簇数 D:样本点集合 输出:K个类簇集合 方法:1) 将D中所有样本点归并成类簇; 2) repeat 3) 在同类簇中找到距离最远的样本点对; 4) 以该样本点对为代表,将原类簇中的样本点重新分属到新类簇 5) util 类簇数=K 缺点: 传统的层次聚类算法的效率比较低O(tn2) t:迭代次数 n:样本点数,最明显的一个缺点是不具有再分配能力,即如果样本点A在某次迭代过程中已经划分给类簇C1,那么在后面的迭代过程中A将永远属于类簇C1,这将影响聚类结果的准确性。 改进: 一般情况下,层次聚类通常和划分式聚类算法组合,这样既可以解决算法效率的问题,又能解决样本点再分配的问题,在后面将介绍BIRCH算法。首先把邻近样本点划分到微簇(microcluseters)中,然后对这些微簇使用K-means算法。 ----------------贴上本人实现的AGENES算法,大家有兴趣可以把DIANA算法自己实现下--------------- package com.agenes; public class DataPoint { String dataPointName; // 样本点名 Cluster cluster; // 样本点所属类簇 private double dimensioin[]; // 样本点的维度 public DataPoint(){ } public DataPoint(double[] dimensioin,String dataPointName){ this.dataPointName=dataPointName; this.dimensioin=dimensioin; } public double[] getDimensioin() { return dimensioin; } public void setDimensioin(double[] dimensioin) { this.dimensioin = dimensioin; } public Cluster getCluster() { return cluster; } public void setCluster(Cluster cluster) { this.cluster = cluster; } public String getDataPointName() { return dataPointName; } public void setDataPointName(String dataPointName) { this.dataPointName = dataPointName; } } package com.agenes; import java.util.ArrayList; import java.util.List; public class Cluster { private List dataPoints = new ArrayList(); // 类簇中的样本点 private String clusterName; public List getDataPoints() { return dataPoints; } public void setDataPoints(List dataPoints) { this.dataPoints = dataPoints; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } } package com.agenes; import java.util.ArrayList; import java.util.List; public class ClusterAnalysis { public List startAnalysis(List dataPoints,int ClusterNum){ List finalClusters=new ArrayList(); List originalClusters=initialCluster(dataPoints); finalClusters=originalClusters; while(finalClusters.size()>ClusterNum){ double min=Double.MAX_VALUE; int mergeIndexA=0; int mergeIndexB=0; for(int i=0;i for(int j=0;j if(i!=j){ Cluster clusterA=finalClusters.get(i); Cluster clusterB=finalClusters.get(j); List dataPointsA=clusterA.getDataPoints(); List dataPointsB=clusterB.getDataPoints(); for(int m=0;m for(int n=0;n double tempDis=getDistance(dataPointsA.get(m),dataPointsB.get(n)); if(tempDis min=tempDis; mergeIndexA=i; mergeIndexB=j; } } } } } //end for j }// end for i //合并cluster[mergeIndexA]和cluster[mergeIndexB] finalClusters=mergeCluster(finalClusters,mergeIndexA,mergeIndexB); }//end while return finalClusters; } private List mergeCluster(List clusters,int mergeIndexA,int mergeIndexB){ if (mergeIndexA != mergeIndexB) { // 将cluster[mergeIndexB]中的DataPoint加入到 cluster[mergeIndexA] Cluster clusterA = clusters.get(mergeIndexA); Cluster clusterB = clusters.get(mergeIndexB); List dpA = clusterA.getDataPoints(); List dpB = clusterB.getDataPoints(); for (DataPoint dp : dpB) { DataPoint tempDp = new DataPoint(); tempDp.setDataPointName(dp.getDataPointName()); tempDp.setDimensioin(dp.getDimensioin()); tempDp.setCluster(clusterA); dpA.add(tempDp); } clusterA.setDataPoints(dpA); // List clusters中移除cluster[mergeIndexB] clusters.remove(mergeIndexB); } return clusters; } // 初始化类簇 private List initialCluster(List dataPoints){ List originalClusters=new ArrayList(); for(int i=0;i DataPoint tempDataPoint=dataPoints.get(i); List tempDataPoints=new ArrayList(); tempDataPoints.add(tempDataPoint); Cluster tempCluster=new Cluster(); tempCluster.setClusterName("Cluster "+String.valueOf(i)); tempCluster.setDataPoints(tempDataPoints); tempDataPoint.setCluster(tempCluster); originalClusters.add(tempCluster); } return originalClusters; } //计算两个样本点之间的欧几里得距离 private double getDistance(DataPoint dpA,DataPoint dpB){ double distance=0; double[] dimA = dpA.getDimensioin(); double[] dimB = dpB.getDimensioin(); if (dimA.length == dimB.length) { for (int i = 0; i < dimA.length; i++) { double temp=Math.pow((dimA[i]-dimB[i]),2); distance=distance+temp; } distance=Math.pow(distance, 0.5); } return distance; } public static void main(String[] args){ ArrayList dpoints = new ArrayList(); double[] a={2,3}; double[] b={2,4}; double[] c={1,4}; double[] d={1,3}; double[] e={2,2}; double[] f={3,2}; double[] g={8,7}; double[] h={8,6}; double[] i={7,7}; double[] j={7,6}; double[] k={8,5}; // double[] l={100,2};//孤立点 double[] m={8,20}; double[] n={8,19}; double[] o={7,18}; double[] p={7,17}; double[] q={8,20}; dpoints.add(new DataPoint(a,"a")); dpoints.add(new DataPoint(b,"b")); dpoints.add(new DataPoint(c,"c")); dpoints.add(new DataPoint(d,"d")); dpoints.add(new DataPoint(e,"e")); dpoints.add(new DataPoint(f,"f")); dpoints.add(new DataPoint(g,"g")); dpoints.add(new DataPoint(h,"h")); dpoints.add(new DataPoint(i,"i")); dpoints.add(new DataPoint(j,"j")); dpoints.add(new DataPoint(k,"k")); // dataPoints.add(new DataPoint(l,"l")); dpoints.add(new DataPoint(m,"m")); dpoints.add(new DataPoint(n,"n")); dpoints.add(new DataPoint(o,"o")); dpoints.add(new DataPoint(p,"p")); dpoints.add(new DataPoint(q,"q")); int clusterNum=3; //类簇数 ClusterAnalysis ca=new ClusterAnalysis(); List clusters=ca.startAnalysis(dpoints, clusterNum); for(Cluster cl:clusters){ System.out.println("------"+cl.getClusterName()+"------"); List tempDps=cl.getDataPoints(); for(DataPoint tempdp:tempDps){ System.out.println(tempdp.getDataPointName()); } } } } 基于密度的聚类算法OPTICS
1 什么是OPTICS算法 在前面介绍的DBSCAN算法中,有两个初始参数E(邻域半径)和minPts(E邻域最小点数)需要用户手动设置输入,并且聚类的类簇结果对这两个参数的取值非常敏感,不同的取值将产生不同的聚类结果,其实这也是大多数其他需要初始化参数聚类算法的弊端。 为了克服DBSCAN算法这一缺点,提出了OPTICS算法(Ordering Points to identify the clustering structure)。OPTICS并不显示的产生结果类簇,而是为聚类分析生成一个增广的簇排序(比如,以可达距离为纵轴,样本点输出次序为横轴的坐标图),这个排序代表了各样本点基于密度的聚类结构。它包含的信息等价于从一个广泛的参数设置所获得的基于密度的聚类,换句话说,从这个排序中可以得到基于任何参数E和minPts的DBSCAN算法的聚类结果。 2 OPTICS两个概念 核心距离: 对象p的核心距离是指是p成为核心对象的最小E’。如果p不是核心对象,那么p的核心距离没有任何意义。 可达距离: 对象q到对象p的可达距离是指p的核心距离和p与q之间欧几里得距离之间的较大值。如果p不是核心对象,p和q之间的可达距离没有意义。 例如:假设邻域半径E=2, minPts=3,存在点A(2,3),B(2,4),C(1,4),D(1,3),E(2,2),F(3,2) 点A为核心对象,在A的E领域中有点{A,B,C,D,E,F},其中A的核心距离为E’=1,因为在点A的E’邻域中有点{A,B,D,E}>3; 点F到核心对象点A的可达距离为 ,因为A到F的欧几里得距离 ,大于点A的核心距离1. 3 算法描述 OPTICS算法额外存储了每个对象的核心距离和可达距离。基于OPTICS产生的排序信息来提取类簇。 算法描述如下:
算法:OPTICS 输入:样本集D, 邻域半径E, 给定点在E领域内成为核心对象的最小领域点数MinPts 输出:具有可达距离信息的样本点输出排序 方法:1 创建两个队列,有序队列和结果队列。(有序队列用来存储核心对象及其该核心对 象的直接可达对象,并按可达距离升序排列;结果队列用来存储样本点的输出次 序); 2 如果所有样本集D中所有点都处理完毕,则算法结束。否则,选择一个未处理(即 不在结果队列中)且为核心对象的样本点,找到其所有直接密度可达样本点,如 过该样本点不存在于结果队列中,则将其放入有序队列中,并按可达距离排序; 3 如果有序队列为空,则跳至步骤2,否则,从有序队列中取出第一个样本点(即可 达距离最小的样本点)进行拓展,并将取出的样本点保存至结果队列中,如果它不 存在结果队列当中的话。 3.1 判断该拓展点是否是核心对象,如果不是,回到步骤3,否则找到该拓展点所 有的直接密度可达点; 3.2 判断该直接密度可达样本点是否已经存在结果队列,是则不处理,否则下一 步; 3.2 如果有序队列中已经存在该直接密度可达点,如果此时新的可达距离小于旧 的可达距离,则用新可达距离取代旧可达距离,有序队列重新排序; 3.3 如果有序队列中不存在该直接密度可达样本点,则插入该点,并对有序队列 重新排序; 4 算法结束,输出结果队列中的有序样本点。 |
大家或许会很疑惑,这里不也有输入参数E和MinPts吗?其实这里的E和MinPts只是起到算法辅助作用,也就是说E和MinPts的细微变化并不会影响到样本点的相对输出顺序,这对我们分析聚类结果是没有任何影响。 我们采用与先前DBSCAN相同的样本点集合, 对于样本点 a={2,3};b={2,4};c={1,4};d={1,3};e={2,2};f={3,2}; g={8,7};h={8,6};i={7,7};j={7,6};k={8,5}; l={100,2};//孤立点 m={8,20};n={8,19};o={7,18};p={7,17};q={8,21}; 并且使用相同的E=2 MinPts=4时,输出序列为 1->a:1.0 2->e:1.0 3->b:1.0 4->d:1.0 5->c:1.4142135623730951 6->f:1.4142135623730951 ------ 7->g:1.4142135623730951 8->j:1.4142135623730951 9->k:1.4142135623730951 10->i:1.4142135623730951 11->h:1.4142135623730951 ------ 12->n:2.0 13->q:2.0 14->o:2.0 15->m:2.0 如图,按照算法,分三个阶段输出了三波值 {a,e,b,d,c,f} ,{g,j,k,I,h},{n,q,o,m} 这和DBSCAN的类簇结果是一样的。不仅如此,我们通过分析有序图还能直接得到当参数E=1.5,minPts=4时DBSCAN的类簇结果,只要在坐标图中找到Y值小于1.5的样本点即可,只有两类{a,e,b,d,c,f} ,{g,j,k,I,h},其他点被认为是孤立点,和DBSCAN聚类算法取E=1.5,minPts=4时的结果一致。 所以说,这个OPTICS聚类算法所得的簇排序信息等价于一个广泛的参数设置所获得的基于密度的聚类结果。 具体实现算法如下: package com.optics; public class DataPoint { private String name; // 样本点名 private double dimensioin[]; // 样本点的维度 private double coreDistance; //核心距离,如果该点不是核心对象,则距离为-1 private double reachableDistance; //可达距离 public DataPoint(){ } public DataPoint(DataPoint e){ this.name=e.name; this.dimensioin=e.dimensioin; this.coreDistance=e.coreDistance; this.reachableDistance=e.reachableDistance; } public DataPoint(double dimensioin[],String name){ this.name=name; this.dimensioin=dimensioin; this.coreDistance=-1; this.reachableDistance=-1; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double[] getDimensioin() { return dimensioin; } public void setDimensioin(double[] dimensioin) { this.dimensioin = dimensioin; } public double getCoreDistance() { return coreDistance; } public void setCoreDistance(double coreDistance) { this.coreDistance = coreDistance; } public double getReachableDistance() { return reachableDistance; } public void setReachableDistance(double reachableDistance) { this.reachableDistance = reachableDistance; } } package com.optics; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class ClusterAnalysis { class ComparatorDp implements Comparator{ public int compare(DataPoint arg0, DataPoint arg1) { double temp=arg0.getReachableDistance()-arg1.getReachableDistance(); int a = 0; if (temp < 0) { a = -1; } else { a = 1; } return a; } } public List startAnalysis(List dataPoints, double radius, int ObjectNum) { List dpList = new ArrayList(); List dpQue = new ArrayList(); int total = 0; while (total < dataPoints.size()) { if (isContainedInList(dataPoints.get(total), dpList) == -1 ) { List tmpDpList = isKeyAndReturnObjects(dataPoints.get(total), dataPoints, radius, ObjectNum); if(tmpDpList != null && tmpDpList.size() > 0){ DataPoint newDataPoint=new DataPoint(dataPoints.get(total)); dpQue.add(newDataPoint); } } while (!dpQue.isEmpty()) { DataPoint tempDpfromQ = dpQue.remove(0); DataPoint newDataPoint=new DataPoint(tempDpfromQ); dpList.add(newDataPoint); List tempDpList = isKeyAndReturnObjects(tempDpfromQ, dataPoints, radius, ObjectNum); System.out.println(newDataPoint.getName()+":"+newDataPoint.getReachableDistance()); if (tempDpList != null && tempDpList.size() > 0) { for (int i = 0; i < tempDpList.size(); i++) { DataPoint tempDpfromList = tempDpList.get(i); int indexInList = isContainedInList(tempDpfromList, dpList); int indexInQ = isContainedInList(tempDpfromList, dpQue); if (indexInList == -1) { if (indexInQ > -1) { int index = -1; for (DataPoint dataPoint : dpQue) { index++; if (index == indexInQ) { if (dataPoint.getReachableDistance() > tempDpfromList .getReachableDistance()) { dataPoint .setReachableDistance(tempDpfromList .getReachableDistance()); } } } } else { dpQue.add(new DataPoint(tempDpfromList)); } } } // TODO:对Q进行重新排序 Collections.sort(dpQue, new ComparatorDp()); } } System.out.println("------"); total++; } return dpList; } public void displayDataPoints(List dps){ for(DataPoint dp: dps){ System.out.println(dp.getName()+":"+dp.getReachableDistance()); } } private int isContainedInList(DataPoint dp, List dpList) { int index = -1; for (DataPoint dataPoint : dpList) { index++; if (dataPoint.getName().equals(dp.getName())) { return index; } } return -1; } private List isKeyAndReturnObjects(DataPoint dataPoint,List dataPoints,double radius,int ObjectNum){ List arrivableObjects=new ArrayList(); //用来存储所有直接密度可达对象 List distances=new ArrayList(); //欧几里得距离 double coreDistance; //核心距离 for (int i = 0; i < dataPoints.size(); i++) { DataPoint dp = dataPoints.get(i); double distance = getDistance(dataPoint, dp); if (distance <= radius) { distances.add(distance); arrivableObjects.add(dp); } } if(arrivableObjects.size()>=ObjectNum){ List newDistances=new ArrayList(distances); Collections.sort(distances); coreDistance=distances.get(ObjectNum-1); for(int j=0;j if (coreDistance > newDistances.get(j)) { if(newDistances.get(j)==0){ dataPoint.setReachableDistance(coreDistance); } arrivableObjects.get(j).setReachableDistance(coreDistance); }else{ arrivableObjects.get(j).setReachableDistance(newDistances.get(j)); } } return arrivableObjects; } return null; } private double getDistance(DataPoint dp1,DataPoint dp2){ double distance=0.0; double[] dim1=dp1.getDimensioin(); double[] dim2=dp2.getDimensioin(); if(dim1.length==dim2.length){ for(int i=0;i double temp=Math.pow((dim1[i]-dim2[i]), 2); distance=distance+temp; } distance=Math.pow(distance, 0.5); return distance; } return distance; } public static void main(String[] args){ ArrayList dpoints = new ArrayList(); double[] a={2,3}; double[] b={2,4}; double[] c={1,4}; double[] d={1,3}; double[] e={2,2}; double[] f={3,2}; double[] g={8,7}; double[] h={8,6}; double[] i={7,7}; double[] j={7,6}; double[] k={8,5}; double[] l={100,2};//孤立点 double[] m={8,20}; double[] n={8,19}; double[] o={7,18}; double[] p={7,17}; double[] q={8,21}; dpoints.add(new DataPoint(a,"a")); dpoints.add(new DataPoint(b,"b")); dpoints.add(new DataPoint(c,"c")); dpoints.add(new DataPoint(d,"d")); dpoints.add(new DataPoint(e,"e")); dpoints.add(new DataPoint(f,"f")); dpoints.add(new DataPoint(g,"g")); dpoints.add(new DataPoint(h,"h")); dpoints.add(new DataPoint(i,"i")); dpoints.add(new DataPoint(j,"j")); dpoints.add(new DataPoint(k,"k")); dpoints.add(new DataPoint(l,"l")); dpoints.add(new DataPoint(m,"m")); dpoints.add(new DataPoint(n,"n")); dpoints.add(new DataPoint(o,"o")); dpoints.add(new DataPoint(p,"p")); dpoints.add(new DataPoint(q,"q")); ClusterAnalysis ca=new ClusterAnalysis(); List dps=ca.startAnalysis(dpoints, 2, 4); ca.displayDataPoints(dps); } } DBSCAN
一 什么是基于密度的聚类算法 由于层次聚类算法和划分式聚类算往往只能发现凸形的聚类簇。为了弥补这一缺陷,发现各种任意形状的聚类簇,开发出基于密度的聚类算法。这类算法认为,在整个样本空间点中,各目标类簇是由一群的稠密样本点组成的,而这些稠密样本点被低密度区域(噪声)分割,而算法的目的就是要过滤低密度区域,发现稠密样本点。 二 DBSCAN(Density-based Spatial Clustering of Applications with Noise) 是一种基于高密度联通区域的聚类算法,它将类簇定义为高密度相连点的最大集合。它本身对噪声不敏感,并且能发现任意形状的类簇。 DBSCAN中的的几个定义: Ε领域:给定对象半径为Ε内的区域称为该对象的Ε领域 核心对象:如果给定对象Ε领域内的样本点数大于等于MinPts,则称该对象为核心对象。 直接密度可达:对于样本集合D,如果样本点q在p的Ε领域内,并且p为核心对象,那么对象q从对象p直接密度可达。 密度可达:对于样本集合D,给定一串样本点p1,p2….pn,p= p1,q= pn,假如对象pi从pi-1直接密度可达,那么对象q从对象p密度可达。 密度相连:对于样本集合D中的任意一点O,如果存在对象p到对象o密度可达,并且对象q到对象o密度可达,那么对象q到对象p密度相连。 可以发现,密度可达是直接密度可达的传递闭包,并且这种关系是非对称的。密度相连是对称关系。DBSCAN目的是找到密度相连对象的最大集合。 Eg: 假设半径Ε=3,MinPts=3,点p的E领域中有点{m,p,p1,p2,o}, 点m的E领域中有点{m,q,p,m1,m2},点q的E领域中有点{q,m},点o的E领域中有点{o,p,s},点s的E领域中有点{o,s,s1}. 那么核心对象有p,m,o,s(q不是核心对象,因为它对应的E领域中点数量等于2,小于MinPts=3); 点m从点p直接密度可达,因为m在p的E领域内,并且p为核心对象; 点q从点p密度可达,因为点q从点m直接密度可达,并且点m从点p直接密度可达; 点q到点s密度相连,因为点q从点p密度可达,并且s从点p密度可达。
三 算法描述 算法:DBSCAN 输入:E — 半径 MinPts — 给定点在E领域内成为核心对象的最小领域点数 D — 集合 输出:目标类簇集合 方法:repeat 1) 判断输入点是否为核心对象 2) 找出核心对象的E领域中的所有直接密度可达点 util 所有输入点都判断完毕 repeat 针对所有核心对象的E领域所有直接密度可达点找到最大密度相连对象集合, 中间涉及到一些密度可达对象的合并。 Util 所有核心对象的E领域都遍历完毕
算法:DBSCAN 输入:E — 半径 MinPts — 给定点在E领域内成为核心对象的最小领域点数 D — 集合 输出:目标类簇集合 方法:repeat 1) 判断输入点是否为核心对象 2) 找出核心对象的E领域中的所有直接密度可达点 util 所有输入点都判断完毕 repeat 针对所有核心对象的E领域所有直接密度可达点找到最大密度相连对象集合, 中间涉及到一些密度可达对象的合并。 Util 所有核心对象的E领域都遍历完毕 |
四 算法实现 package com.dbscan; public class DataPoint { private String dataPointName; // 样本点名 private double dimensioin[]; // 样本点的维度 private boolean isKey; //是否是核心对象 public DataPoint(){ } public DataPoint(double[] dimensioin,String dataPointName,boolean isKey){ this.dataPointName=dataPointName; this.dimensioin=dimensioin; this.isKey=isKey; } } ------------ package com.dbscan; import java.util.ArrayList; import java.util.List; public class Cluster { private List dataPoints = new ArrayList(); // 类簇中的样本点 private String clusterName; //簇名 public List getDataPoints() { return dataPoints; } public void setDataPoints(List dataPoints) { this.dataPoints = dataPoints; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } } ------------ package com.dbscan; import java.util.ArrayList; import java.util.List; public class ClusterAnalysis { public List doDbscanAnalysis(List dataPoints, double radius, int ObjectNum) { List clusterList=new ArrayList(); for(int i=0; i DataPoint dp=dataPoints.get(i); List arrivableObjects=isKeyAndReturnObjects(dp,dataPoints,radius,ObjectNum); if(arrivableObjects!=null){ Cluster tempCluster=new Cluster(); tempCluster.setClusterName("Cluster "+i); tempCluster.setDataPoints(arrivableObjects); clusterList.add(tempCluster); } } for(int i=0;i for(int j=0;j if(i!=j){ Cluster clusterA=clusterList.get(i); Cluster clusterB=clusterList.get(j); List dpsA=clusterA.getDataPoints(); List dpsB=clusterB.getDataPoints(); boolean flag=mergeList(dpsA,dpsB); if(flag){ clusterList.set(j, new Cluster()); } } } } return clusterList; } public void displayCluster(List clusterList){ if(clusterList!=null){ for(Cluster tempCluster:clusterList){ if(tempCluster.getDataPoints()!=null&&tempCluster.getDataPoints().size()>0){ System.out.println("----------"+tempCluster.getClusterName()+"----------"); for(DataPoint dp:tempCluster.getDataPoints()){ System.out.println(dp.getDataPointName()); } } } } } private double getDistance(DataPoint dp1,DataPoint dp2){ double distance=0.0; double[] dim1=dp1.getDimensioin(); double[] dim2=dp2.getDimensioin(); if(dim1.length==dim2.length){ for(int i=0;i double temp=Math.pow((dim1[i]-dim2[i]), 2); distance=distance+temp; } distance=Math.pow(distance, 0.5); return distance; } return distance; } private List isKeyAndReturnObjects(DataPoint dataPoint,List dataPoints,double radius,int ObjectNum){ List arrivableObjects=new ArrayList(); //用来存储所有直接密度可达对象 for(DataPoint dp:dataPoints){ double distance=getDistance(dataPoint,dp); if(distance<=radius){ arrivableObjects.add(dp); } } if(arrivableObjects.size()>=ObjectNum){ dataPoint.setKey(true); return arrivableObjects; } return null; } private boolean isContain(DataPoint dp,List dps){ boolean flag=false; String name=dp.getDataPointName().trim(); for(DataPoint tempDp:dps){ String tempName=tempDp.getDataPointName().trim(); if(name.equals(tempName)){ flag=true; break; } } return flag; } private boolean mergeList(List dps1,List dps2){ boolean flag=false; if(dps1==null||dps2==null||dps1.size()==0||dps2.size()==0){ return flag; } for(DataPoint dp:dps2){ if(dp.isKey()&&isContain(dp,dps1)){ flag=true; break; } } if(flag){ for(DataPoint dp:dps2){ if(!isContain(dp,dps1)){ DataPoint tempDp=new DataPoint(dp.getDimensioin(),dp.getDataPointName(),dp.isKey()); dps1.add(tempDp); } } } return flag; } public static void main(String[] args){ ArrayList dpoints = new ArrayList(); double[] a={2,3}; double[] b={2,4}; double[] c={1,4}; double[] d={1,3}; double[] e={2,2}; double[] f={3,2}; double[] g={8,7}; double[] h={8,6}; double[] i={7,7}; double[] j={7,6}; double[] k={8,5}; double[] l={100,2};//孤立点 double[] m={8,20}; double[] n={8,19}; double[] o={7,18}; double[] p={7,17}; double[] q={8,21}; dpoints.add(new DataPoint(a,"a",false)); dpoints.add(new DataPoint(b,"b",false)); dpoints.add(new DataPoint(c,"c",false)); dpoints.add(new DataPoint(d,"d",false)); dpoints.add(new DataPoint(e,"e",false)); dpoints.add(new DataPoint(f,"f",false)); dpoints.add(new DataPoint(g,"g",false)); dpoints.add(new DataPoint(h,"h",false)); dpoints.add(new DataPoint(i,"i",false)); dpoints.add(new DataPoint(j,"j",false)); dpoints.add(new DataPoint(k,"k",false)); dpoints.add(new DataPoint(l,"l",false)); dpoints.add(new DataPoint(m,"m",false)); dpoints.add(new DataPoint(n,"n",false)); dpoints.add(new DataPoint(o,"o",false)); dpoints.add(new DataPoint(p,"p",false)); dpoints.add(new DataPoint(q,"q",false)); ClusterAnalysis ca=new ClusterAnalysis(); List clusterList=ca.doDbscanAnalysis(dpoints, 2, 4); ca.displayCluster(clusterList); } } } |
|
|