dbscan基于密度的空间聚类算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Androidlushangderen/article/details/44311309

参考文献:百度百科 http://baike.baidu.com

我的算法库:https://github.com/linyiqun/lyq-algorithms-lib 

算法介绍

说到聚类算法,大家如果有看过我写的一些关于机器学习的算法文章,一定都这类算法不会陌生,之前将的是划分算法(K均值算法)和层次聚类算法(BIRCH算法),各有优缺点和好坏。本文所述的算法是另外一类的聚类算法,他能够克服BIRCH算法对于形状的限制,因为BIRCH算法偏向于聚簇球形的聚类形成,而dbscan采用的是基于空间的密度的原理,所以可以适用于任何形状的数据聚类实现。

算法原理

在介绍算法原理之前,先介绍几个dbscan算法中的几个概念定义:

Ε领域:给定对象半径为Ε内的区域称为该对象的Ε领域;
核心对象:如果给定对象Ε领域内的样本点数大于等于MinPts,则称该对象为核心对象;
直接密度可达:对于样本集合D,如果样本点q在p的Ε领域内,并且p为核心对象,那么对象q从对象p直接密度可达。
密度可达:对于样本集合D,给定一串样本点p1,p2….pn,p= p1,q= pn,假如对象pi从pi-1直接密度可达,那么对象q从对象p密度可达。
密度相连:存在样本集合D中的一点o,如果对象o到对象p和对象q都是密度可达的,那么p和q密度相联。

下面是算法的过程(可能说的不是很清楚):

1、扫描原始数据,获取所有的数据点。

2、遍历数据点中的每个点,如果此点已经被访问(处理)过,则跳过,否则取出此点做聚类查找。

3、以步骤2中找到的点P为核心对象,找出在E领域内所有满足条件的点,如果个数大于等于MinPts,则此点为核心对象,加入到簇中。

4、再次P为核心对象的簇中的每个点,进行递归的扩增簇。如果P点的递归扩增结束,再次回到步骤2。

5、算法的终止条件为所有的点都被访问(处理过)。

算法可以理解为是一个DFS的深度优先扩展。

算法的实现

算法的输入Input(格式(x, y)):

2 2
3 1
3 4
3 14
5 3
8 3
8 6
9 8
10 4
10 7
10 10
10 14
11 13
12 8
12 15
14 7
14 9
14 15
15 8

坐标点类Point.java:

package DataMining_DBSCAN;

/**
 * 坐标点类
 * 
 * @author lyq
 * 
 */
public class Point {
	// 坐标点横坐标
	int x;
	// 坐标点纵坐标
	int y;
	// 此节点是否已经被访问过
	boolean isVisited;

	public Point(String x, String y) {
		this.x = (Integer.parseInt(x));
		this.y = (Integer.parseInt(y));
		this.isVisited = false;
	}

	/**
	 * 计算当前点与制定点之间的欧式距离
	 * 
	 * @param p
	 *            待计算聚类的p点
	 * @return
	 */
	public double ouDistance(Point p) {
		double distance = 0;

		distance = (this.x - p.x) * (this.x - p.x) + (this.y - p.y)
				* (this.y - p.y);
		distance = Math.sqrt(distance);

		return distance;
	}

	/**
	 * 判断2个坐标点是否为用个坐标点
	 * 
	 * @param p
	 *            待比较坐标点
	 * @return
	 */
	public boolean isTheSame(Point p) {
		boolean isSamed = false;

		if (this.x == p.x && this.y == p.y) {
			isSamed = true;
		}

		return isSamed;
	}
}

算法工具类DNSCANTool.java:

package DataMining_DBSCAN;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;

/**
 * DBSCAN基于密度聚类算法工具类
 * 
 * @author lyq
 * 
 */
public class DBSCANTool {
	// 测试数据文件地址
	private String filePath;
	// 簇扫描半径
	private double eps;
	// 最小包含点数阈值
	private int minPts;
	// 所有的数据坐标点
	private ArrayList<Point> totalPoints;
	// 聚簇结果
	private ArrayList<ArrayList<Point>> resultClusters;
	//噪声数据
	private ArrayList<Point> noisePoint;

	public DBSCANTool(String filePath, double eps, int minPts) {
		this.filePath = filePath;
		this.eps = eps;
		this.minPts = minPts;
		readDataFile();
	}

	/**
	 * 从文件中读取数据
	 */
	public void readDataFile() {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		Point p;
		totalPoints = new ArrayList<>();
		for (String[] array : dataArray) {
			p = new Point(array[0], array[1]);
			totalPoints.add(p);
		}
	}

	/**
	 * 递归的寻找聚簇
	 * 
	 * @param pointList
	 *            当前的点列表
	 * @param parentCluster
	 *            父聚簇
	 */
	private void recursiveCluster(Point point, ArrayList<Point> parentCluster) {
		double distance = 0;
		ArrayList<Point> cluster;

		// 如果已经访问过了,则跳过
		if (point.isVisited) {
			return;
		}

		point.isVisited = true;
		cluster = new ArrayList<>();
		for (Point p2 : totalPoints) {
			// 过滤掉自身的坐标点
			if (point.isTheSame(p2)) {
				continue;
			}

			distance = point.ouDistance(p2);
			if (distance <= eps) {
				// 如果聚类小于给定的半径,则加入簇中
				cluster.add(p2);
			}
		}

		if (cluster.size() >= minPts) {
			// 将自己也加入到聚簇中
			cluster.add(point);
			// 如果附近的节点个数超过最下值,则加入到父聚簇中,同时去除重复的点
			addCluster(parentCluster, cluster);

			for (Point p : cluster) {
				recursiveCluster(p, parentCluster);
			}
		}
	}

	/**
	 * 往父聚簇中添加局部簇坐标点
	 * 
	 * @param parentCluster
	 *            原始父聚簇坐标点
	 * @param cluster
	 *            待合并的聚簇
	 */
	private void addCluster(ArrayList<Point> parentCluster,
			ArrayList<Point> cluster) {
		boolean isCotained = false;
		ArrayList<Point> addPoints = new ArrayList<>();

		for (Point p : cluster) {
			isCotained = false;
			for (Point p2 : parentCluster) {
				if (p.isTheSame(p2)) {
					isCotained = true;
					break;
				}
			}

			if (!isCotained) {
				addPoints.add(p);
			}
		}

		parentCluster.addAll(addPoints);
	}

	/**
	 * dbScan算法基于密度的聚类
	 */
	public void dbScanCluster() {
		ArrayList<Point> cluster = null;
		resultClusters = new ArrayList<>();
		noisePoint = new ArrayList<>();
		
		for (Point p : totalPoints) {
			if(p.isVisited){
				continue;
			}
			
			cluster = new ArrayList<>();
			recursiveCluster(p, cluster);

			if (cluster.size() > 0) {
				resultClusters.add(cluster);
			}else{
				noisePoint.add(p);
			}
		}
		removeFalseNoise();
		
		printClusters();
	}
	
	/**
	 * 移除被错误分类的噪声点数据
	 */
	private void removeFalseNoise(){
		ArrayList<Point> totalCluster = new ArrayList<>();
		ArrayList<Point> deletePoints = new ArrayList<>();
		
		//将聚簇合并
		for(ArrayList<Point> list: resultClusters){
			totalCluster.addAll(list);
		} 
		
		for(Point p: noisePoint){
			for(Point p2: totalCluster){
				if(p2.isTheSame(p)){
					deletePoints.add(p);
				}
			}
		}
		
		noisePoint.removeAll(deletePoints);
	}

	/**
	 * 输出聚类结果
	 */
	private void printClusters() {
		int i = 1;
		for (ArrayList<Point> pList : resultClusters) {
			System.out.print("聚簇" + (i++) + ":");
			for (Point p : pList) {
				System.out.print(MessageFormat.format("({0},{1}) ", p.x, p.y));
			}
			System.out.println();
		}
		
		System.out.println();
		System.out.print("噪声数据:");
		for (Point p : noisePoint) {
			System.out.print(MessageFormat.format("({0},{1}) ", p.x, p.y));
		}
		System.out.println();
	}
}
测试类Client.java:

package DataMining_DBSCAN;

/**
 * Dbscan基于密度的聚类算法测试类
 * @author lyq
 *
 */
public class Client {
	public static void main(String[] args){
		String filePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";
		//簇扫描半径
		double eps = 3;
		//最小包含点数阈值
		int minPts = 3;
		
		DBSCANTool tool = new DBSCANTool(filePath, eps, minPts);
		tool.dbScanCluster();
	}
}
算法的输出:

聚簇1:(2,2) (3,4) (5,3) (3,1) (8,3) (8,6) (10,4) (9,8) (10,7) (10,10) (12,8) (14,7) (14,9) (15,8) 
聚簇2:(10,14) (11,13) (14,15) (12,15) 

噪声数据:(3,14) 
图示结果如下:

算法的缺点

dbscan虽说可以用于任何形状的聚类发现,但是对于密度分布不均衡的数据,变化比较大,分类的性能就不会特别好,还有1点是不能反映高尺寸数据。

没有更多推荐了,返回首页