数据挖掘笔记-聚类-DBSCAN-原理与简单实现

DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一个比较有代表性的基于密度的聚类算法。与划分和层次聚类方法不同,它将簇定义为密度相连的点的最大集合,能够把具有足够高密度的区域划分为簇,并可在噪声的空间数据库中发现任意形状的聚类。 该算法的目的在于过滤低密度区域,发现稠密度样本点,跟传统的基于层次聚类和划分聚类的凸形聚类簇不同,该算法可以发现任意形状的聚类簇,与传统的算法相比它有如下优缺点:
优点
1. 与K-means方法相比,DBSCAN不需要事先知道要形成的簇类的数量。
2. 与K-means方法相比,DBSCAN可以发现任意形状的簇类。
3. 同时,DBSCAN能够识别出噪声点。
4.DBSCAN对于数据库中样本的顺序不敏感,即Pattern的输入顺序对结果的影响不大。但是,对于处于簇类之间边界样本,可能会根据哪个簇类优先被探测到而其归属有所摆动。
缺点:
1. DBScan不能很好反映高尺寸数据。
2. DBScan不能很好反映数据集以变化的密度。


DBSCAN中的的几个定义:

Ε领域:给定对象半径为Ε内的区域称为该对象的Ε领域

核心对象:如果给定对象Ε领域内的样本点数大于等于MinPts,则称该对象为核心对象。

直接密度可达:对于样本集合D,如果样本点qpΕ领域内,并且p为核心对象,那么对象q从对象p直接密度可达。

密度可达:对于样本集合D,给定一串样本点p1,p2….pnp= p1,q= pn,假如对象pipi-1直接密度可达,那么对象q从对象p密度可达。

密度相连:对于样本集合D中的任意一点O,如果存在对象p到对象o密度可达,并且对象q到对象o密度可达,那么对象q到对象p密度相连。

可以发现,密度可达是直接密度可达的传递闭包,并且这种关系是非对称的。密度相连是对称关系。DBSCAN目的是找到密度相连对象的最大集合。

Eg: 假设半径Ε=3MinPts=3,点pE领域中有点{m,p,p1,p2,o}, 点mE领域中有点{m,q,p,m1,m2},qE领域中有点{q,m},oE领域中有点{o,p,s},sE领域中有点{o,s,s1}.

那么核心对象有p,m,o,s(q不是核心对象,因为它对应的E领域中点数量等于2,小于MinPts=3)

m从点p直接密度可达,因为mpE领域内,并且p为核心对象;

q从点p密度可达,因为点q从点m直接密度可达,并且点m从点p直接密度可达;

q到点s密度相连,因为点q从点p密度可达,并且s从点p密度可达。


DBSCAN算法描述:
输入: 包含n个对象的数据库,半径e,最少数目MinPts;
输出:所有生成的簇,达到密度要求。
(1)Repeat
(2)从数据库中抽出一个未处理的点;
(3)IF抽出的点是核心点 THEN 找出所有从该点密度可达的对象,形成一个簇;
(4)ELSE 抽出的点是边缘点(非核心对象),跳出本次循环,寻找下一个点;
(5)UNTIL 所有的点都被处理。
DBSCAN对用户定义的参数很敏感,细微的不同都可能导致差别很大的结果,而参数的选择无规律可循,只能靠经验确定。

具体描述:
(1)检测数据库中尚未检查过的对象p,如果p为被处理(归为某个簇或者标记为噪声),则检查其邻域,若包含的对象数不小于minPts,建立新簇C,将其中的所有点加入候选集N;
(2)对候选集N 中所有尚未被处理的对象q,检查其邻域,若至少包含minPts个对象,则将这些对象加入N;如果q 未归入任何一个簇,则将q 加入C;
(3)重复步骤2),继续检查N 中未处理的对象,当前候选集N为空;
(4)重复步骤1)~3),直到所有对象都归入了某个簇或标记为噪声。

下面用Java来简单实现算法

public class DBScanBuilder {
	
	//半径
	public static double Epislon = 2;
	//密度、最小点个数
	public static int MinPts = 5;
	
	public List<Point> initData() {
		List<Point> points = new ArrayList<Point>();
		InputStream in = null;
		BufferedReader br = null;
		try {
			in = DBScanBuilder.class.getClassLoader().getResourceAsStream("dbscan.txt");
			br = new BufferedReader(new InputStreamReader(in));
			String line = br.readLine();
			while (null != line && !"".equals(line)) {
				StringTokenizer tokenizer = new StringTokenizer(line);
				double x = Double.parseDouble(tokenizer.nextToken());
				double y = Double.parseDouble(tokenizer.nextToken());
				points.add(new Point(x , y));
				line = br.readLine();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(in);
			IOUtils.closeQuietly(br);
		}
		return points;
	}
	
	//计算两点之间的欧氏距离
	public double euclideanDistance(Point a, Point b) {
		double sum =  Math.pow(a.getX() - b.getX(), 2) + Math.pow(a.getY() - b.getY(), 2);
		return Math.sqrt(sum);
	}
	
	//获取当前点的邻居
	public List<Point> obtainNeighbors(Point current, List<Point> points) {
		List<Point> neighbors = new ArrayList<Point>();
		for (Point point : points) {
			double distance = euclideanDistance(current, point);
			if (distance < Epislon) {
				neighbors.add(point);
			}
		}
		return neighbors;
	}
	
	public void mergeCluster(Point point, List<Point> neighbors,
			int clusterId, List<Point> points) {
		point.setClusterId(clusterId);
		for (Point neighbor : neighbors) {
			//邻域点中未被访问的点先观察是否是核心对象
			//如果是核心对象,则其邻域范围内未被聚类的点归入当前聚类中
			if (!neighbor.isAccessed()) {
				neighbor.setAccessed(true);
				List<Point> nneighbors = obtainNeighbors(neighbor, points);
				if (nneighbors.size() > MinPts) {
					for (Point nneighbor : nneighbors) {
						if (nneighbor.getClusterId() <= 0) {
							nneighbor.setClusterId(clusterId);
						}
					}
				}
			}
			//未被聚类的点归入当前聚类中
			if (neighbor.getClusterId() <= 0) {
				neighbor.setClusterId(clusterId);
			}
		}
	}
	
	public void cluster(List<Point> points) {
		//clusterId初始为0表示未分类,分类后设置为一个正数,如果设置为-1表示噪声 
		int clusterId = 0;
		boolean flag = true;
		//所有点都被访问完成即停止遍历
		while (flag) {
			for (Point point : points) {
				if (point.isAccessed()) {
					continue;
				}
				point.setAccessed(true);
				flag = true;
				List<Point> neighbors = obtainNeighbors(point, points);
				if (neighbors.size() >= MinPts) {
					//满足核心对象条件的点创建一个新簇
					clusterId = point.getClusterId() <= 0 ? (++clusterId) : point.getClusterId();
					mergeCluster(point, neighbors, clusterId, points);
				} else {
					//未满足核心对象条件的点暂时当作噪声处理
					if(point.getClusterId() <= 0) {
						 point.setClusterId(-1);
					}
				}
				flag = false;
			}
		}
	}
	
	//打印结果
	public void print(List<Point> points) {
		Collections.sort(points, new Comparator<Point>() {
			@Override
			public int compare(Point o1, Point o2) {
				return Integer.valueOf(o1.getClusterId()).compareTo(o2.getClusterId());
			}
		});
		for (Point point : points) {
			System.out.println(point.getClusterId() + " - " + point);
		}
	}

	public void build() {
		List<Point> points = initData();
		cluster(points);
		print(points);
	}
	
	public static void main(String[] args) {
		DBScanBuilder builder = new DBScanBuilder();
		builder.build();
	}
}
代码托管:https://github.com/fighting-one-piece/repository-datamining.git


你可能感兴趣的:(算法,数据挖掘,聚类,DBSCAN,密度)