密度聚类知识介绍:参见了这篇文章http://blog.csdn.net/uestcfrog/article/details/6876360
定义:
1. 对于空间中的一个对象,如果它在给定半径e的邻域中的对象个数大于密度阀值MinPts,则该对象被称为核心对象,否则称为边界对象。
2. 如果p是一个核心对象,q属于p的邻域,那么称p直接密度可达q。
3. 如果存在一条链<p1,p2,…..,pi>,满足p1=p,pi=q,pi直接密度可达pi+1,则称p密度可达q。
4. 如果存在o,o密度可达q和p,则称p和q是密度连通的
5. 由一个核心对象和其密度可达的所有对象构成一个聚类。
a为核心对象,b为边界对象,且a直接密度可达b,
但b不直接密度可达a,因为b不是一个核心对象
c直接密度可达a,a直接密度可达b,所以c密度可达b,
同理b不密度可达c,但b和c密度连通
DBSCAN从任一对象p开始,根据参数e和MinPts提取所有从p密度可达对象,得到一个聚类。
1. 从任一对象p开始。
a) 如果p是核心对象,则p和p直接密度可达的所有对象被标记为类i。递归p直接密度可达的所有对象qi(即用qi代替p回到第一步)。
b) 如果p是一个边界对象,那么p被标记为噪声。
2. i++
3. 如果还有没被标记的对象,则从中任选一个作为p,回到第一步。
![]()
得到一个类,同样我们可以得到另一个类
优点:
1. 对噪声不敏感。
2. 能发现任意形状的聚类。
缺点:
1. 聚类的结果与参数有很大的关系。
2. DBSCAN用固定参数识别聚类,但当聚类的稀疏程度不同时,相同的判定标准可能会破坏聚类的自然结构,即较稀的聚类会被划分为多个类或密度较大且离得较近的类会被合并成一个聚类
''' 本章主要介绍密度聚类: 簇:密度相连的点的最大集合 优点: 1、能够将具有高密度的区域划分为簇 2、能发现任意形状的簇 基本概念: 对象的ε-邻域:给定对象在半径ε内的区域 核心对象:一个对象的邻域中至少包含最小数目x个对象的 直接密度可达:给定对象集合D,如果p是在q的邻域内,q又是核心对象,则表明从核心对象q直接密度可达对象p 密度可达(可以认为是基于传递性):存在对象链p1,p2,...,pn,p1=q,pn=p,对于pi属于D(1<=i<=n),pi+1是从pi (认为是核心对象)关于邻域和x直接密度可达的,则对象p是从对象q关于邻域和x密度可达的 密度相连:如果对象集合D中存在一个对象o,使得对象p和q从o关于邻域和x密度可达的,那么对象p和q是关于邻域和 x密度相连的 噪声:基于密度的簇是基于密度可达性最大的密度相连对象的集合。不包含在任何簇中的对象时噪声。 DBSCAN密度聚类的过程 检查数据集中每个对象的邻域来寻找聚类。如果一个点p的邻域内包含多于x个对象,则创建一个p作为核心对象 的新簇。然后,DBSCAN反复寻找从核心对象直接密度可达的对象,过程可能涉及一些密度可达簇的合并。 当没有新的点可以被添加到任何簇时,该过程结束。 优点:发现任意形状的聚类,对噪声不敏感 缺点:复杂度大,需要建立空间索引来降低计算量,最少需要的点的个数难易确定,密度不均匀或距离相差较大,聚类质量下降 时间复杂度:O(N*找出邻域中的点所需要的时间),N四点的个数,最坏时间复杂度O(N*N) 空间复杂度:找到核心对象需要以该对象为中心向外扩展,过程中核心对象增多,要较大内存,空间为O(N) '''
具体实现参考了以下两篇文章
介绍DBSCAN聚类的PPT:
http://wenku.baidu.com/link?url=DhMmqbWfjI54MmapGdKVlygrzOtoMDF7OTi-WN493cikpCdr7SCrYMYgWvG0vPeO6nZc8yKiUCXgu2S4uADfIVG2ZEXw_ujUh9k5llD2mb_
不进行簇合并的密度聚类实现:
http://blog.csdn.net/cang_sheng_ta_ge/article/details/50137667
本文实现的DBSCAN聚类可以进行簇的合并,以及进行密度相连,下面附上python3.4实现的代码
# -*- coding: utf-8 -*- import sys, os import random from collections import defaultdict from matplotlib import pyplot as plt # def getData(): p1 = [2,1] p2 = [5,1] p3 = [1,2] p4 = [2,2] p5 = [3,2] p6 = [4,2] p7 = [5,2] p8 = [6,2] p9 = [1,3] p10 = [2,3] p11 = [5,3] p12 = [2,4] points = [] points.append(p1) points.append(p2) points.append(p3) points.append(p4) points.append(p5) points.append(p6) points.append(p7) points.append(p8) points.append(p9) points.append(p10) points.append(p11) points.append(p12) return points # 随机生成指定个数的点集 def getRandomData(minNum, maxNum, pointCount): if pointCount <= 0: pointCount = 50 if minNum > maxNum: minNum, maxNum = maxNum, minNum if minNum == maxNum and minNum != 0: minNum = maxNum / 2; allPoints = [] i = 0 while i < pointCount: #这里封装的每一个点其实是一个数组 tmpPoint = [random.randint(minNum, maxNum), random.randint(minNum, maxNum)] if tmpPoint not in allPoints: allPoints.append(tmpPoint) i += 1 return allPoints #计算两个点之间的距离, **表示乘方 def distance(vec1, vec2): return ((vec1[0] - vec2[0]) ** 2 + (vec1[1] - vec2[1]) ** 2) ** 0.5 def isSame(point1 , point2): if point1[0] == point2[0] and point1[1] == point2[1]: return True else: return False def isCorePoint(points , minDistance , minPointNum , point): neighbourPoints = [] count = 0 for point2 in points: ''' flag = isSame(point , point2) if flag: continue ''' if distance(point, point2) <= minDistance: count += 1 neighbourPoints.append(point2) if count >= (minPointNum): return True , neighbourPoints return False , neighbourPoints def getNeighbourPoints(points , minDistance , point): #为点添加聚类的类别标签,默认为0 neighbourPoints = [] for point2 in points: ''' flag = isSame(point , point2) if flag: continue ''' if distance(point, point2) <= minDistance: neighbourPoints.append(point2) return neighbourPoints def isFinish(points): for point in points: if point[-1] == 0: return False return True ''' DBSCAN算法过程: 输入:数据集D,参数MinPts, ε 输出:簇集合 (1) 首先将数据集D中的所有对象标记unvisited ; (2) do (3) 从D中随机选取一个unvisited对象p,并将p标记为visited ; if p的 ε 邻域 包含的对象数至少为MinPts个 创建新簇C ,并把p添加到c中; 令N为 p的 ε 邻域 中对象的集合; (7) for N 中每个点pi if pi 是unvisited 标记pi 为visited; if pi 的ε 邻域 至少有MinPts个 对象,把这些对象添加到N ; if pi 还不是任何簇的对象。将 pi 添加到 簇C中 ; (12) end for (13) 输出C (14) Else 标记p 为噪声 (15) Untill 没有标记为unvisited 的对象 ''' def myDBSCAN(points , minDistance , minPointNum): #设置访问标记为0,表示初始化为未访问,-1:表示已经访问,-2表示噪声,>=1时表示点所属于的类别 for point in points: point.append(0) cluster = defaultdict(lambda: [[],[]]) label = 0 corePoints = [] while True: #如果没有标记为未访问的对象,则表示完成,退出 isOk = isFinish(points) if isOk: break #从数据集中随机选取一个未访问过的对象p p = None for point in points: if point[-1] == 0: #当为-1的时候表示已经访问 point[-1] = -1 p = point break if p == None: break flag , neighbourPoints = isCorePoint(points , minDistance , minPointNum , p) #如果是核心点,那么创建簇C if flag: label += 1 cluster[label][0].append(p[0]) cluster[label][1].append(p[1]) #设置当前点位核心点 p[-1] = label corePoints.append(p) # 寻找当前核心点的邻域N N = getNeighbourPoints(points , minDistance , p) #遍历领域中每个点pi,如果pi未访问过,则设置为访问过, for pi in N: if pi[-1] == 0: pi[-1] = -1 flag2 , neighbourPoints2= isCorePoint(points , minDistance , minPointNum , pi) #如果pi的领域含有至少指定个数的点,则把这些对象添加到N if flag2: #这边拓展之后会动态发生变化,需要处理 N.extend(neighbourPoints2) #如果pi不是任何簇的对象(已经访问过,且不为噪声),将pi添加到簇C中 if pi[-1] <= 0: pi[-1] = label cluster[label][0].append(pi[0]) cluster[label][1].append(pi[1]) else: #标记pi为噪声 p[-1] = -2 print(cluster) return cluster def showResult(cluster): #画出密度聚类后的结果 mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr'] num = len(mark) count = 0 for index, points in cluster.items(): #判断如果是噪声点则过滤 #if index <= 1: if index < 1: continue color = mark[count % num] count += 1 xArr = points[0] xLen = len(xArr) yArr = points[1] for i in range(0 , xLen): plt.plot(xArr[i], yArr[i], color) print("密度聚类最终聚为:%d类" % count ) plt.show() def testExtend(): arr = [1,2,3] count = 0 for val in arr: count += 1 if count <= 2: tempArr = [count] arr.extend(tempArr) print(val) if __name__ == "__main__": allPoints = getRandomData(1, 50, 100) allPoints = getData() minDistance = 1 minPointNum = 4 cluster = myDBSCAN(allPoints, minDistance, minPointNum) #cluster = myDBSCAN(allPoints, 8, 8) showResult(cluster) #testExtend()
附上运行效果图:
为了便于理解,下面附上数据集的本来面目以及便于观察的聚类结果,这个结果与上面运行出来的结果是一样的