机器学习-一篇入门

【代码】DBSCAN聚类-自实现代码

作者 : 老饼 发表日期 : 2022-06-26 03:34:19 更新日期 : 2024-10-08 15:32:58
本站原创文章,转载请说明来自《老饼讲解-机器学习》www.bbbdata.com



DBSCAN聚类全称为Density-Based Spatial Clustering of Applications with Noise,是基于密度的聚类方法

本文讲解DBSCAN聚类的基本思想,以及DBSCAN聚类算法流程,并根据算法流程自写代码实现一个DBSCAN聚类

通过本文可以快速了解DBSCAN的聚类机制和算法流程,以及如何不调函数自行编写代码实现DBSCAN聚类算法





     01. DBSCAN聚类-算法介绍     




本节介绍什么是DBSCAN聚类,以及一些相关术语




      DBSCAN聚类算法介绍       


通俗来说,DBSCAN就是从一个样本出发,依靠近邻找近邻,不断把附近的点纳入同一类别
由于DBSCAN是依靠近邻扫描进行聚类,因此DBSCAN的不受样本的形状约束,且具有极强的抗噪性
 为了更专业地描述DBSCAN聚类,下面先介绍一些定义
 DBSCAN有两个参数:半径 和 最小样本数,以及下述样本类型和关系:
 一、样本点的类型                                                                                                  
         DBSCAN聚类的样本点类型
                  核心样本点:如果样本的半径内的样本个数最小样本数,则称为核心点        
                                    核心样本点代表密度足够,核心样本点允许扩散,继续搜索它的近邻点
边界样本点:不是核心样本点,但在其它核心样本点的半径内                  
                                边界样本点可以被核心样本点搜索到,但不会进一步搜索它的近邻点
噪声样本点:既不是核心样本点,又不在其它核心样本点的半径内           
     噪声点不会被核心样本点搜索到,是较为孤立的点
 二、样本点的关系                                                                                                  
          DBSCAN聚类的样本点关系
密度直达   :p是核心点,q在p的范围内,则称p到q是密度直达的          
 密度直达代表可以通过p直接找到q            
        密度可达   :如果p到q可通过一连串的密度直达连接,则称p到q是密度可达的
 密度可达代表可以通过p可以找到q            
     密度相连   :如果o到p,q都是密度可达的,则称p,q密度相连                        
 密度相连代表p,q能同时被某个点o找到        
根据以上定义,DBSCAN的聚类描述如下:
 
 DBSCAN聚类是什么
DBSCAN聚类从一个样本点p出发(p必须是核心点,边界、噪声点根本出发不了)
通过密度直达不断传播,直到边界点样本点为止,本次找到的所有样本归为一个类别
 可知,所有与p密度可达的样本都会被找到,样本之间的允许的最大跨度是"密度相连"









      02. DBSCAN聚类算法流程      




本节介绍DBSCAN的具体聚类流程,以及展示算法流程图





     DBSCAN聚类流程      


    DBSCAN的算法流程如下:
一、初始化所有样本均未被访问过                                                         
二、对所有样本点进行历遍:                                                                
        1. 如果样本已被访问过:                                                                 
 继续下一个样本                                       
  2. 如果不是核心样本点:                                                           
 将样本标为噪声,并标记样本已被访问,继续下一个样本   
3. 如果是核心样本点:                                                            
 创建一个类别                                                                      
 标记该样本已被访问                                                            
 将样本的密度直达样本放到搜索队列                      
 历遍搜索队列样本 :                                                             
  样本还没有类别,则标上当前类别                    
如果样本被访问过,继续下一个样本                 
标记样本被访问                                   
     如果是核心点,将样本的密度直达样本放到搜索队列,并去重
          (备注:后期会有许多样本已被访问,因此在放上队列前可先作筛选)
 
三、输出最终每个样本的分类                                                                  





     DBSCAN聚类流程图     


DBSCAN聚类的算法流程图如下:
 DBSCAN聚类算法流程图 







     03. DBSCAN聚类-自实现代码     




本节展示如何使用代码自行实现DBSCAN聚类算法





      自写代码实现-DBSCAN聚类      


下面不用软件包,自行实现DBSCAN算法进行聚类
说明:本代码由笔者根据原理的理解编写,仅供参考与学习
 具体代码如下:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
# -------------成生数据
X1, y1=datasets.make_circles(n_samples=5000, factor=.5,noise=.05)
X2, y2 = datasets.make_blobs(n_samples=100
                             ,n_features=2
                             ,centers=[[2,2]]
                             ,cluster_std=[[.1]]
                             ,random_state=0) 
X = np.concatenate((X1, X2))                                                # 样本数据

							                               
esp = 0.15                                                                   # 设置范围
minP= 2                                                                      # 最小样本数
n_sample = X.shape[0]                                                        # 样本个数
C = np.zeros([n_sample,1])                                                   # 初始化类别
v = np.zeros([n_sample,1])                                                   # 初始化访问表
lable = -1                                                                   # 初始化当前类别标签
for i in range(n_sample):                                                    # 逐样本循环
    if (v[i]==1):                                                            # 如果样本已经访问过,则放弃
        continue                                                             # 直接进入下一次循环
    v[i] = 1                                                                 # 标记样本已被访问
    d = np.sqrt(((X[i]-X)*(X[i]-X)).sum(axis=1))                             # 计算距离 
    d[i] += esp +1                                                           # 为了屏蔽当前样本,将距离置为比eps大  
    if sum(d<esp)<minP:                                                      # 如果不是核心点
        C[i] = -1                                                            # 标为噪声样本
        continue                                                             # 继续下一个样本
    search_list= list(np.where(d<esp)[0])                                    # 找出直达样本作为搜索列表	
    lable +=1                                                                # 本次的类别标签
    C[i] = lable                                                             # 标记当前样本的类别
    while(len(search_list)>0):                                               # 如果搜索列表不为空,则进行搜索
        print(sum(v))                                                        
        cur_idx = search_list.pop(0)                                         # 从列表中弹出一个样本
        cur_x   =  X[cur_idx]                                                # 获取样本数据
        if(C[cur_idx]<=0):                                                   # 如果样本还没有类别                        
            C[cur_idx] = lable                                               # 则标示为本类别    
        if(v[cur_idx]==0):                                                   # 如果样本还没被访问过                           
            v[cur_idx] = 1                                                   # 标记样本已经被访问
            d = np.sqrt(((X[cur_idx]-X)*(X[cur_idx]-X)).sum(axis=1))         # 计算样本与其它样本的距离 
            d[i] += esp+1                                                    # 为了屏蔽当前样本,将距离置为比eps大  
            if sum(d<esp)>=minP:                                             # 如果是核心点
                new_p = np.where(d<esp)[0]                                   # 找出当前样本的直达样本
                noise_idx = np.where((v[new_p]==1)&(C[new_p]<=0))[0]         # 直达样本中之前被认为是噪声的样本
                C[noise_idx] = lable                                         # 将其标记本次类别
                valid_idx = np.where((v[new_p]==0))[0]                       # 直达样本中未被访问过的样本
                new_search =list(new_p[valid_idx])                           # 将其作为新增的搜索样本
                search_list= list(set(search_list + new_search))             # 添加到搜索列表
plt.scatter(X[:, 0], X[:, 1], c=C)                                           # 绘制聚类结果
plt.show()                                                                   # 显示
运行结果如下:
 DBSCAN聚类代码自实现的结果 
可以看到,自写的算法已经成功地把样本分为三类,说明算法是有效的










  End  







联系老饼