基于密度的聚类算法: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,如果对象o到对象p和对象q都是密度可达的,那么p和q密度相联。
可以发现,密度可达是直接密度可达的传递闭包,并且这种关系是非对称的。密度相连是对称关系。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算法描述:
输入: 包含n个对象的数据库,半径e,最少数目MinPts;
输出:所有生成的簇,达到密度要求。
(1)Repeat
(2)从数据库中抽出一个未处理的点;
(3)IF抽出的点是核心点 THEN 找出所有从该点密度可达的对象,形成一个簇;
(4)ELSE 抽出的点是边缘点(非核心对象),跳出本次循环,寻找下一个点;
(5)UNTIL 所有的点都被处理。
DBSCAN对用户定义的参数很敏感,细微的不同都可能导致差别很大的结果,而参数的选择无规律可循,只能靠经验确定。
 

好处

1. 与K-means方法相比,DBSCAN不需要事先知道要形成的簇类的数量。
2. 与K-means方法相比,DBSCAN可以发现任意形状的簇类。
3. 同时,DBSCAN能够识别出噪声点。
4.DBSCAN对于数据库中样本的顺序不敏感,即Pattern的输入顺序对结果的影响不大。但是,对于处于簇类之间边界样本,可能会根据哪个簇类优先被探测到而其归属有所摆动。

 

缺点

1. DBScan不能很好反映高维数据。
2. DBScan不能很好反映数据集以变化的密度。
 
代码
# coding=utf-8

from numpy import *
import matplotlib.pyplot as plt

from matplotlib.pyplot import *
from collections import defaultdict
import random


class DbScan(object):

    def show(self,data,color=None):
        if not color:
            color='green'
        group=self.createDataSet()
        fig = plt.figure(1)
        axes = fig.add_subplot(111)
        axes.scatter(group[:, 0], group[:, 1], s=40, c='red')
        axes.scatter(data[:, 0], data[:, 1], s=50, c=color)
        plt.show()

    def createDataSet(self):
        group = [[1.0, 1.1], [1.0, 1.0],
                       [0, 0], [0, 0.1],
                       [2, 1.0], [2.1, 0.9],
                       [0.3, 0.0], [1.1, 0.9],
                       [2.2, 1.0], [2.1, 0.8],
                       [3.3, 3.5], [2.1, 0.9],
                       [2, 1.0], [2.1, 0.9],
                       [3.5, 3.4], [3.6, 3.5]
                       ]
        return group

    def dist(self,p1, p2):
       return ((p1[0]-p2[0])**2+ (p1[1]-p2[1])**2)**(0.5)

    def db_scan(self):
        all_points = self.createDataSet()
        E = 0.3
        minPts = 2
        # find out the core points
        other_points = []
        core_points = []  # 核心点集合
        plotted_points = []  # 使用到的点   非噪声点
        for point in all_points:
            point.append(0)  # 在点的后面加上第三维度类别,初始类别为 0
            total = 0
            for otherPoint in all_points:
                distance = self.dist(otherPoint, point)  # 遍历其他点并计算距离
                if distance <= E:
                    total += 1  # 计算当前点的e领域内点的个数

            if total > minPts:
                core_points.append(point)  # 是核心点,添加到列表core
                plotted_points.append(point)  # 将核心点添加到列表 plotted
            else:
                other_points.append(point)  # 不是核心点,添加到其他点

                # find border points

        border_points = []
        for core in core_points:  # 遍历核心点
            for other in other_points:  # 遍历非核心点
                if self.dist(core, other) <= E:
                    border_points.append(other)  # 添加到非噪声点集合
                    plotted_points.append(other)  # 添加到非噪声点集合
                    # implement the algorithm
        cluster_label = 0

        for point in core_points:  # 遍历核心点
            if point[2] == 0:  # 核心点所属类别为0
                cluster_label += 1
                point[2] = cluster_label  # 每遍历一个核心点,类别栏就加1

            for point2 in plotted_points:  # 遍历非噪声点
                distance = self.dist(point2, point)
                if point2[2] == 0 and distance <= E:  # 非噪声点的类别为0 并且与核心点的距离小于e

                    point2[2] = point[2]  # 将核心点的类别赋值给非噪声点
                    # print point, point2

                    # after the points are asssigned correnponding labels, we group them
        cluster_list = defaultdict(lambda: [[], []])  # 定义一个字典,默认值是包含两个列表的列表
        for point in plotted_points:
            cluster_list[point[2]][0].append(point[0])  # 类别为键,值得第一个列表是非噪声点的x
            cluster_list[point[2]][1].append(point[1])  # 类别为键,值得第一个列表是非噪声点的y

        markers = ['+', '*', '.', 'd', '^', 'v', '>', '<', 'p']
        #
        # plotting the clusters
        i = 0
        print cluster_list
        for value in cluster_list:
            cluster = cluster_list[value]
            plot(cluster[0], cluster[1], markers[i])
            i = i % 10 + 1

        # plot the noise points as well
        noise_points = []
        for point in all_points:
            if not point in core_points and not point in border_points:
                noise_points.append(point)
        noisex = []
        noisey = []
        for point in noise_points:
            noisex.append(point[0])
            noisey.append(point[1])
        plot(noisex, noisey, "x")
        # /#
        # title(str(len(cluster_list)) + " clusters created with E =" + str(E) + " Min Points=" + str(
        #     minPts) + " total points=" + str(len(all_points)) + " noise Points = " + str(len(noise_points)))
        axis((-1, 5, -1, 5))
        show()

    def start(self):
        self.db_scan()

p=DbScan()
p.start()

  dbscan 在2维上效果要优于kmeans,不过各有利弊,一个是靠数据密度做输入,一个是靠分类类别做输入

 
 
 
posted @ 2016-11-22 17:26  GreadLoveJM  阅读(401)  评论(0编辑  收藏  举报