DBSCAB算法介绍(Python3实现)

news2024/10/6 12:35:23

一、DBSCAB算法简介

1、DBSCAN算法

基于密度的空间聚类的应用(Density-based spatial clustering of applications with noise,DBSCAN)算法是由Martin Ester, Hans-Peter Kriegel, Jörg Sander和Xiaowei Xu于1996年提出的一种聚类分析算法。
其原始论文是在1996年的KDD会议(Knowledge Discovery and Data Mining)上发表的,论文名称为"A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise"。
该算法是一种基于密度的聚类算法,该类算法用样本点密度表示相似度,因此这类算法可以适用于任何类型的数据集。

2、DBSCAN算法基本思想

DBSCAN 算法的基本思想是通过密度可达关系获得最大密度相连的一个簇,该算法具有较强的抗噪性,但是需要手动设置最小样本数和邻域半径作为参数,且聚类结果收到参数影响较大,所以需要选取合适的参数。

DBSCAN算法主要有两个参数:
Eps:指定邻域半径;
MinPts:密度阈值。

DBSCAN算法的基本概念定义如下:
Eps邻域(Eps-neighborhood of a point):给定对象半径Eps内的邻域,用NEps(p)表示点p的Eps半径内的点的集合。

  • 核心对象 (core points):若某个点的Eps邻域内的样本点数(密度)达到算法设定的阈值MinPts,则其为核心点。(即Eps领域内的样本数量不小于MinPts)
  • 直接密度可达(directly density-reachable):若某点p在点q的Eps邻域内,且q是核心点,则称对象p从核心对象q是直接密度可达。
  • 密度可达density-reachable):若有一个点的序列q0,q1,…,qk,对任意qi到qi+1是直接密度可达的,则称从q0到qk密度可达,这实际上是直接密度可达的“传播”。
  • 密度相连(density-connected):若从某核心点p出发,点q和点k都是密度可达的,则称点q和点k是密度相连的。
  • 边界点:属于某一个类的非核心点,不能发展下线了。
  • 噪声点(noise):不属于任何一个类簇的点,从任何一个核心点出发都是密度不可达的。

3、DBSCAN算法步骤

Step1:对一个未访问过的点P,先标记它为已访问。
Step2:如果点P的Eps邻域(即以P为中心,Eps为半径的圆)内的数据点大于等于MinPts阈值,则创建一个新的簇C,并把P加入C。
Step3:对P的Eps邻域内的每个点P’,如果P’未被访问,标记P’为已访问,并且如果P’的Eps邻域内有足够多的点,则将这些点也加入到簇C。
Step4:如果P’不属于任何簇,将P’加入到簇C。
Step5:重复Step2-4,直到所有的点都被访问过。

4、DBSCAN算法伪代码

DBSCAN(D, eps, MinPts) {
   C = 0
   for each unvisited point P in dataset D {
      mark P as visited
      NeighborPts = regionQuery(P, eps)
      if sizeof(NeighborPts) < MinPts
         mark P as NOISE
      else {
         C = next cluster
         expandCluster(P, NeighborPts, C, eps, MinPts)
      }
   }
}

expandCluster(P, NeighborPts, C, eps, MinPts) {
   add P to cluster C
   for each point P' in NeighborPts { 
      if P' is not visited {
         mark P' as visited
         NeighborPts' = regionQuery(P', eps)
         if sizeof(NeighborPts') >= MinPts
            NeighborPts = NeighborPts joined with NeighborPts'
      }
      if P' is not yet member of any cluster
         add P' to cluster C
   }
}

regionQuery(P, eps)
   return all points within P's eps-neighborhood (including P)

5、DBSCAN算法时间复杂度分析

DBSCAN 的基本时间复杂度是 O(n * 找出 Eps 邻域中的点所需要的时间),其中 n 是点的个数。在最坏的情况下,时间复杂度是 O(n^2)。然而,在低维空间,有一些数据结构,如 kd 树,可以有效地检索特定点给定距离内的所有点,时间复杂度可以降低到O(nlogn)。

6、DBSCAN算法优缺点

DBSCAN算法优点:

  1. 能够处理任何形状的簇;
  2. 能够处理噪声和异常值;
  3. 不需要提前指定簇的数量。

DBSCAN算法缺点:

  1. 对高维数据效果不好;
  2. 对于密度不均匀的数据,聚类效果较差;
  3. 对参数敏感,需要选择合适的密度参数,如果Eps、MinPts参数选取不当对结果影响较大。

二、DBSCAB算法实现(Python3)

本文使用的数据集为UCI数据集和人工数据集,分别使用鸢尾花数据集Iris、葡萄酒数据集Wine,和spiral 数据集进行测试,本文从UCI官网上将这三个数据集下载下来,并放入和python文件同一个文件夹内即可。同时由于程序需要,将数据集的列的位置做出了略微改动。数据集具体信息如下表:

数据集样本数属性维度类别个数
aggregation24022
jain37322
spiral31223

数据集在我主页资源里有,免积分下载,如果无法下载,可以私信我。

1、Python3代码实现

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.metrics import normalized_mutual_info_score  # NMI
from sklearn.metrics import rand_score  # RI
from sklearn.metrics import accuracy_score  # ACC
from sklearn.metrics import f1_score  # F-measure
from sklearn.metrics import adjusted_rand_score  # ARI
# from sklearn.cluster import DBSCAN
from sklearn.decomposition import PCA
# from sklearn.preprocessing import LabelEncoder

# 数据保存在.csv文件中
# iris = pd.read_csv("dataset/iris.csv")  # 鸢尾花数据集 Iris  class=3
# wine = pd.read_csv("dataset/wine.csv")  # 葡萄酒数据集 Wine  class=3
# seeds = pd.read_csv("dataset/seeds.csv")  # 小麦种子数据集 seeds  class=3
# wdbc = pd.read_csv("dataset/wdbc.csv")  # 威斯康星州乳腺癌数据集 Breast Cancer Wisconsin Diagnostic class=2
# glass = pd.read_csv("dataset/glass.csv")  # 玻璃辨识数据集 Glass Identification  class=6
# aggregation = pd.read_csv("dataset/aggregation.csv")  # 人工数据集 Eps=0.18 MinPts=4
# flame = pd.read_csv("dataset/flame.csv")  # 人工数据集 Eps=0.28 MinPts=4
# jain = pd.read_csv("dataset/jain.csv")  # 人工数据集 Eps = 0.315 MinPts = 4
spiral = pd.read_csv("dataset/spiral.csv")  # 人工数据集 Eps=0.45 MinPts=4
df = spiral  # 设置要读取的数据集
# print(df)
columns = list(df.columns)  # 获取数据集的第一行,第一行通常为特征名,所以先取出
features = columns[:len(columns) - 1]  # 数据集的特征名(去除了最后一列,因为最后一列存放的是标签,不是数据)
dataset = df[features]  # 预处理之后的数据,去除掉了第一行的数据(因为其为特征名,如果数据第一行不是特征名,可跳过这一步)
attributes = len(df.columns) - 1  # 属性数量(数据集维度)
original_labels = list(df[columns[-1]])  # 原始标签

UNCLASSIFIED = 0
NOISE = -1


# 计算数据点两两之间的距离
def getDistanceMatrix(datas):
    N, D = np.shape(datas)
    dists = np.zeros([N, N])

    for i in range(N):
        for j in range(N):
            vi = datas[i, :]
            vj = datas[j, :]
            dists[i, j] = np.sqrt(np.dot((vi - vj), (vi - vj)))
    return dists


#  寻找以点cluster_id 为中心,eps 为半径的圆内的所有点的id
def find_points_in_eps(point_id, eps, dists):
    index = (dists[point_id] <= eps)
    return np.where(index == True)[0].tolist()


# 聚类扩展
# dists : 所有数据两两之间的距离  N x N
# labs :   所有数据的标签 labs N,
# cluster_id : 一个簇的标号
# eps : 密度评估半径
# seeds: 用来进行簇扩展的点
# min_points: 半径内最少的点数
def expand_cluster(dists, labs, cluster_id, seeds, eps, min_points):
    i = 0
    while i < len(seeds):
        # 获取一个临近点
        Pn = seeds[i]
        # 如果该点被标记为NOISE 则重新标记
        if labs[Pn] == NOISE:
            labs[Pn] = cluster_id
        # 如果该点没有被标记过
        elif labs[Pn] == UNCLASSIFIED:
            # 进行标记,并计算它的临近点 new_seeds
            labs[Pn] = cluster_id
            new_seeds = find_points_in_eps(Pn, eps, dists)

            # 如果 new_seeds 足够长则把它加入到seed 队列中
            if len(new_seeds) >= min_points:
                seeds = seeds + new_seeds

        i = i + 1


def dbscan(datas, Eps, MinPts):
    # 计算 所有点之间的距离
    dists = getDistanceMatrix(datas)

    # 将所有点的标签初始化为UNCLASSIFIED
    n_points = datas.shape[0]
    labs = [UNCLASSIFIED] * n_points

    cluster_id = 0
    # 遍历所有点
    for point_id in range(0, n_points):
        # 如果当前点已经处理过了
        if not (labs[point_id] == UNCLASSIFIED):
            continue

        # 没有处理过则计算临近点
        seeds = find_points_in_eps(point_id, Eps, dists)

        # 如果临近点数量过少则标记为 NOISE
        if len(seeds) < MinPts:
            labs[point_id] = NOISE
        else:
            # 否则就开启一轮簇的扩张
            cluster_id = cluster_id + 1
            # 标记当前点
            labs[point_id] = cluster_id
            expand_cluster(dists, labs, cluster_id, seeds, Eps, MinPts)
    return labs, cluster_id


# 计算聚类指标
def clustering_indicators(labels_true, labels_pred):
    if type(labels_true[0]) != int:
        labels_true = LabelEncoder().fit_transform(df[columns[len(columns) - 1]])  # 如果标签为文本类型,把文本标签转换为数字标签
    f_measure = f1_score(labels_true, labels_pred, average='macro')  # F值
    accuracy = accuracy_score(labels_true, labels_pred)  # ACC
    normalized_mutual_information = normalized_mutual_info_score(labels_true, labels_pred)  # NMI
    rand_index = rand_score(labels_true, labels_pred)  # RI
    ARI = adjusted_rand_score(labels_true, labels_pred)
    return f_measure, accuracy, normalized_mutual_information, rand_index, ARI


# 绘图    
def draw_cluster(datas, labs, n_cluster):
    plt.cla()
    colors = np.array(["red", "blue", "green", "orange", "purple", "cyan", "magenta", "beige", "hotpink", "#88c999"])
    if attributes > 2:
        datas = PCA(n_components=2).fit_transform(datas)  # 如果属性数量大于2,降维
    # plt.scatter(datas[:, 0], datas[:, 1], s=7., color="black")
    # plt.show()
    for i, lab in enumerate(labs):
        if lab == NOISE:
            plt.scatter(datas[i, 0], datas[i, 1], s=7., color=(0, 0, 0))
        else:
            plt.scatter(datas[i, 0], datas[i, 1], s=7., color=colors[lab - 1])

    plt.show()


if __name__ == "__main__":
    # 设置DBSCAN参数
    Eps = 0.45
    MinPts = 4

    datas = np.array(dataset).astype(np.float32)
    # 数据正则化
    datas = StandardScaler().fit_transform(datas)
    label, cluster_id = dbscan(datas, Eps=Eps, MinPts=MinPts)  # 执行DBSCAN
    print(original_labels)  # 输出原始标签
    print("label of my dbscan")
    print(label)  # 输出聚类结果
    F_measure, ACC, NMI, RI, ARI = clustering_indicators(original_labels, label)  # 计算聚类指标
    print("F_measure:", F_measure, "ACC:", ACC, "NMI", NMI, "RI", RI, "ARI", ARI)
    draw_cluster(datas, label, cluster_id)  # 画散点图
    # db = DBSCAN(Eps=Eps, MinPts=MinPts).fit(datas)  # 调用sk的dbscan
    # skl_labels = db.labels_
    # print("label of sk-DBSCAN")
    # print(skl_labels)
    # draw_cluster(db, skl_labels, cluster_id)


2、聚类结果分析

本文选择了F值(F-measure,FM)、准确率(Accuracy,ACC)、标准互信息(Normalized Mutual Information,NMI)和兰德指数(Rand Index,RI)作为评估指标,其值域为[0,1],取值越大说明聚类结果越符合预期。

F值结合了精度(Precision)与召回率(Recall)两种指标,它的值为精度与召回率的调和平均,其计算公式见公式:

P r e c i s i o n = T P T P + F P Precision=\frac{TP}{TP+FP} Precision=TP+FPTP

R e c a l l = T P T P + F N Recall=\frac{TP}{TP+FN} Recall=TP+FNTP

F − m e a s u r e = 2 R e c a l l × P r e c i s i o n R e c a l l + P r e c i s i o n F-measure=\frac{2Recall \times Precision}{Recall+Precision} Fmeasure=Recall+Precision2Recall×Precision

ACC是被正确分类的样本数与数据集总样本数的比值,计算公式如下:

A C C = T P + T N T P + T N + F P + F N ACC=\frac{TP+TN}{TP+TN+FP+FN} ACC=TP+TN+FP+FNTP+TN

其中,TP(True Positive)表示将正类预测为正类数的样本个数,TN (True Negative)表示将负类预测为负类数的样本个数,FP(False Positive)表示将负类预测为正类数误报的样本个数,FN(False Negative)表示将正类预测为负类数的样本个数。

NMI用于量化聚类结果和已知类别标签的匹配程度,相比于ACC,NMI的值不会受到族类标签排列的影响。计算公式如下:

N M I = I ( U , V ) H ( U ) H ( V ) NMI=\frac{I\left(U,V\right)}{\sqrt{H\left(U\right)H\left(V\right)}} NMI=H(U)H(V) I(U,V)

其中H(U)代表正确分类的熵,H(V)分别代表通过算法得到的结果的熵。

其具体实现代吗如下:
由于数据集中给定的正确标签可能为文本类型而不是数字标签,所以在计算前先判断数据集的标签是否为数字类型,如果不是,则转化为数字类型

def clustering_indicators(labels_true, labels_pred):
    if type(labels_true[0]) != int:
        labels_true = LabelEncoder().fit_transform(df[columns[len(columns) - 1]])  # 如果标签为文本类型,把文本标签转换为数字标签
    f_measure = f1_score(labels_true, labels_pred, average='macro')  # F值
    accuracy = accuracy_score(labels_true, labels_pred)  # ACC
    normalized_mutual_information = normalized_mutual_info_score(labels_true, labels_pred)  # NMI
    rand_index = rand_score(labels_true, labels_pred)  # RI
    return f_measure, accuracy, normalized_mutual_information, rand_index


F_measure, ACC, NMI, RI = clustering_indicators(labels_number, labels)
print("F_measure:", F_measure, "ACC:", ACC, "NMI", NMI, "RI", RI)

如果需要计算出聚类分析指标,只要将以上代码插入实现代码中即可。

3、聚类结果散点图

  1. aggregation数据集
    原图:
    在这里插入图片描述

聚类效果图(Eps=0.18,MinPts=4):
在这里插入图片描述

  1. jain数据集
    原图:
    在这里插入图片描述

聚类效果图(Eps=0.315,MinPts=4):
在这里插入图片描述

  1. Spiral数据集
    原图:
    在这里插入图片描述
    聚类效果图(Eps=0.45,MinPts=4):
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1627429.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASP.NET某企业信息管理系统的设计与实现

摘 要 信息管理系统就是我们常说的MIS(Management Information System),它是一个计算机软硬件资源以及数据库的人-机系统。经过对题目和内容的分析,选用了Microsoft公司的ASP.NET开发工具,由于它提供了用于从数据库中访问数据的强大工具集,使用它可以建立开发比较完善的数据库…

untiy avpro播放超过8K视频的解决方案

安转LAV Filters解码器&#xff0c;然后指定Avpro使用这个解码器播放即可 第一步 安装解码器 下载链接 第二步 AVPro设置 MediaPlayer脚本中一共两处

计算完美数

一、概要 完全数&#xff08;Perfect number&#xff09;&#xff0c;又称完美数或完备数&#xff0c;是一些特殊的自然数。 它所有的真因子&#xff08;即除了自身以外的约数&#xff09;的和&#xff08;即因子函数&#xff09;&#xff0c;恰好等于它本身。 例如&#xf…

排序算法-计数排序

一、计数排序 这种排序算法 是利用数组下标来确定元素的正确位置的。 如果数组中有20个随机整数&#xff0c;取值范围为0~10&#xff0c;要求用最快的速度把这20个整数从小到大进行排序。 很大的情况下&#xff0c;它的性能甚至快过那些时间复杂度为O(nlogn&#xff09;的排序。…

API和微服务设计的优化方式有哪些?

在构建响应迅速、用户体验良好的应用程序中&#xff0c;API性能的优化至关重要。在构建高性能的API时&#xff0c;采取综合策略是至关重要的。通过采用一系列策略&#xff0c;我们可以确保API在处理请求时高效运行&#xff0c;提供流畅的服务。 一、API和微服务设计的优化可以…

Linux系统安全及应用(1)

目录 一.账号安全控制 系统账号清理 二.密码安全控制 密码安全控制 三.命令历史限制 命令历史限制 四.限制su切换用户 1&#xff09;将信任的用户加入到wheel组中 2&#xff09;修改su的PAM认证配置文件 ​编辑五.PAM认证的构成 六.使用sudo机制提升权限…

机器人系统ros2-开发实践03-监听节点的参数变化(C++)

背景&#xff1a; 通常&#xff0c;节点需要响应其自身参数或另一个节点参数的更改。 ParameterEventHandler 类可以轻松侦听参数更改&#xff0c;以便您的代码可以响应它们。本教程将向您展示如何使用 ParameterEventHandler 类的 C 版本来监视节点自身参数的更改以及另一个节…

深入了解Semaphore、CountDownLatch等实用工具的用法

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

Android使用ProtoBuf 适配 gradle7.5 gradle8.0

ProtoBuf 适配 Gradle7.5 gradle-wrapper.properties 配置 distributionUrlhttps\://services.gradle.org/distributions/gradle-7.5-bin.zipProject&#xff1a;build.gradle: plugins {id com.android.application version 7.4.2 apply falseid com.android.library versio…

【智能算法】囊状虫群算法(TSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2020年&#xff0c;S Kaur等人受到囊状虫群自然行为启发&#xff0c;提出了囊状虫群算法&#xff08;Tunicate Swarm Algorithm, TSA&#xff09;。 2.算法原理 2.1算法思想 TSA模拟了囊状虫群在导…

2分钟自己写小游戏:使用js和css编写石头剪刀布小游戏、扫雷小游戏、五子棋小游戏。新手老手毕业论文都能用。

系列文章目录 【复制就能用1】2分钟玩转轮播图,unslider的详细用法 【复制就能用2】css实现转动的大风车&#xff0c;效果很不错。 【复制就能用3】2分钟自己写小游戏&#xff1a;剪刀石头布小游戏、扫雷游戏、五子棋小游戏 【复制就能用4】2024最新智慧医疗智慧医院大数据…

【介绍下OneFlow概念清单】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

61、回溯-分割回文串

思路&#xff1a; 还是全排列的思路&#xff0c;列出每一种组合&#xff0c;然后验证是否是回文&#xff0c;如果是子串放入path中&#xff0c;在验证其他元素是否也是回文。代码如下&#xff1a; class Solution {// 主方法&#xff0c;用于接收一个字符串s并返回所有可能的…

【GO】命令行解析 os 与 flag

目录 OS解析命令 简单用法 进阶用法 flag命令解析 基础实例 1. 自定义数据类型 2. 创建多个 FlagSet 3. 整合环境变量和配置文件 os与flag 关键点解析 程序的作用 示例命令行调用 在 Go 语言中&#xff0c;命令行解析是一项基本且常用的功能&#xff0c;它允许开发者…

玄子Share-Shell编程之条件语句

玄子Share-Shell编程之条件语句 条件测试操作 test命令 测试表达式是否成立&#xff0c;若成立返回0&#xff0c;否则返回其他数值 格式1&#xff1a;test 条件表达式 格式2&#xff1a;[ 条件表达式 ] # 至少应有一个空格文件测试 [ 操作符 文件或目录 ][rootlocal…

[Algorithm][模拟][替换所有问号][提莫攻击][N字形变换][外观数列][数青蛙] + 模拟原理详细讲解

目录 0.原理讲解1.替换所有的问号1.题目链接2.代码实现 2.提莫攻击1.题目链接2.算法原理详解3.代码实现 3.N 字形变换1.题目链接2.算法原理详解3.代码实现 4.外观数列1.题目链接2.算法原理详解3.代码实现 5.数青蛙1.题目链接2.算法原理详解3.代码实现 0.原理讲解 模拟&#xf…

实现SpringMVC底层机制(一)

文章目录 1.环境配置1.创建maven项目2.创建文件目录3.导入jar包 2.开发核心控制器文件目录1.流程图2.编写核心控制器SunDispatcherServlet.java3.类路径下编写spring配置文件sunspringmvc.xml4.配置中央控制器web.xml5.配置tomcat&#xff0c;完成测试1.配置发布方式2.配置热加…

Vue入门篇:生命周期,钩子函数,工程化开发Vue(脚手架安装),组件化开发(全局注册,局部注册)

目录 1.Vue生命周期和生命周期的四个阶段2.Vue生命周期函数&#xff08;钩子函数)3.工程化开发&脚手架Vue CLI1.在powershell管理员权限下打开命令行安装脚手架&#xff1a;2.查看vue版本&#xff1a;3.创建项目架子4.运行项目 4.组件化开发&根组件1.App.vue文件&#…

JavaSE字节缓冲流

欢迎来到 请回答1024 的博客 &#x1f353;&#x1f353;&#x1f353;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的延伸的后端开发者。 博客特色&#xff1a; 在我的博客中&a…

解决minIO 文件上传回显报 403 问题

一、问题描述&#xff1a; minIO 上传文件回显时 提示 403 Forbidden 二、问题原因&#xff1a; minIO 中文件相对应的 buckets 权限问题造成 三、解决办法&#xff1a; 进入 minIO 控制台&#xff0c;将 buckets 中 access Policy 改为 public