DBSCAN 算法详解 + 代码实现 + 参数的选择

news2024/10/7 16:19:09

基于密度的噪声应用空间聚类(DBSCAN)是一种无监督聚类算法,它可以替代KMeans和层次聚类等流行的聚类算法。

KMeans 的缺点

  • 容易受到异常值的影响,离群值对质心的移动方式有显著的影响。
  • 在集群大小和密度不同的情况下存在数据精确聚类的问题。
  • 只能应用于球形簇,如果数据不是球形的,它的准确性就会受到影响。
  • KMeans 要求我们首先选择希望找到的集群的数量,无法自动判断集群的类别。

针对这些缺点,人们提出了 DBSCAN 算法
在这里插入图片描述

1. 算法流程

首先该算法用到两个参数:

  • eps:领域半径
  • min_samples:领域半径内的最少点数

还有一些基本概念:

  • 核心点:算法会遍历每一个点,并统计以该点为圆心,半径为 eps 的圆内点的数量,如果大于等于 min_samples,那么该店就是核心点
  • 密度直达:如果点 P P P 为核心点,点 Q Q Q P P P 的邻域内,也就是它们间距离小于 eps,那么 P P P Q Q Q 密度直达。注意,密度直达不具有对称性,也就是 P P P Q Q Q 密度直达, Q Q Q P P P 不一定密度直达,因为 Q Q Q 可能不是核心点。
  • 密度可达:如果有这么一组核心点: { P i   ∣   0 ≤ i ≤ n } \{P_i~|~0\le i\le n\} {Pi  0in},并且 P i P_i Pi P i + 1 P_{i+1} Pi+1 密度直达, P n P_n Pn Q Q Q 密度直达且 P 1 P_1 P1 Q Q Q 不是密度直达,那么 P 1 P_1 P1 Q Q Q 密度可达。密度可达也不具有对称性
  • 密度相连:如果存在核心点 S S S,使得 S S S P P P Q Q Q 都密度可达,则 P P P Q Q Q 密度相连。密度相连具有对称性,如果 P P P Q Q Q 密度相连,那么 Q Q Q P P P 也一定密度相连。密度相连的两个点属于同一个聚类簇。

在这里插入图片描述

看完上面的这些概念,对于这个算法的流程可能就已经有些眉目了,该算法的步骤可以大概分为两部分:

  1. 遍历没有访问过的点,并且找到一个核心点 P P P,归为类别 i i i
  2. 从这个核心点开始往外扩展:把它的密度直达点放到集合 Q = { q j } Q=\{q_j\} Q={qj},依次遍历集合内所有点 q j q_j qj,统统归为类别 i i i,再判断一下 q j q_j qj 是不是核心点,如果是的话继续从 q j q_j qj 开始重复第二步,直到没有新的点加入集合 Q Q Q,并且这个集合内的点全被访问过。这样集合 Q Q Q 内的点全部都是密度相连的,并且它们统统都是类别 i i i,因此该算法是成立的。

2. 代码实现

又是喜闻乐见的展示代码环节

class DBSCAN:
    def __init__(self, eps, min_samples):
        self.distance = None
        self.visit = None
        self.label = None
        self.eps = eps
        self.min_samples = min_samples

    def fit(self, X):
        """
        分类 id 从 1 开始,0 为噪声
        """
        self._distance(X)

        n_samples = X.shape[0]
        self.visit = np.zeros(n_samples)
        self.label = np.zeros(n_samples)

        clusterId = 1
        for i in range(n_samples):
            if self.visit[i] == 1:
                continue
            self.visit[i] = 1

            neighbors = self._region_query(i)
            if len(neighbors) < self.min_samples:
                self.label[i] = 0  # 标记为噪声点
            else:
                self.label[i] = clusterId
                self._expand_cluster(neighbors, clusterId)
                clusterId += 1
        self.label = self.label.astype(int)
        return self.label


    def _distance(self, X):
        self.distance = np.linalg.norm(X[:, np.newaxis] - X, axis=-1)


    def _region_query(self, i):
        dists = self.distance[i]
        neighbors = np.argwhere(dists <= self.eps).squeeze(axis=1)
        return neighbors


    def _expand_cluster(self, neighbors, clusterId):
        j = 0
        while j < len(neighbors):
            neighbor = neighbors[j]
            if self.visit[neighbor] == 0:
                self.visit[neighbor] = 1
                self.label[neighbor] = clusterId
                new_neighbors = self._region_query(neighbor)
                if len(new_neighbors) >= self.min_samples:
                    neighbors = np.concatenate((neighbors, new_neighbors), axis=0)
            j += 1

测试代码

import numpy as np
import matplotlib.pyplot as plt
from draw_point import map_label_to_color


def generate_dataset(num_clusters, num_points_per_cluster, noise_ratio):
    # 确定数据集的范围
    x_range = (-10, 10)
    y_range = (-10, 10)

    # 生成聚类中心点
    cluster_centers = []
    for _ in range(num_clusters):
        center_x = np.random.uniform(*x_range)
        center_y = np.random.uniform(*y_range)
        cluster_centers.append((center_x, center_y))

    # 生成数据点
    dataset = []
    for center in cluster_centers:
        points = []
        for _ in range(num_points_per_cluster):
            offset_x = np.random.uniform(-1, 1)
            offset_y = np.random.uniform(-1, 1)
            point_x = center[0] + offset_x
            point_y = center[1] + offset_y
            points.append((point_x, point_y))
        dataset.extend(points)

    # 生成噪声点
    num_noise_points = int(len(dataset) * noise_ratio)
    noise_points = []
    for _ in range(num_noise_points):
        noise_x = np.random.uniform(*x_range)
        noise_y = np.random.uniform(*y_range)
        noise_points.append((noise_x, noise_y))
    dataset.extend(noise_points)

    return np.array(dataset)


colorLst = [
    (1, 0, 0),  # 红色
    (0, 1, 0),  # 绿色
    (0, 0, 1),  # 蓝色
    (1, 1, 0),  # 黄色
    (1, 0, 1),  # 品红色
    (0, 1, 1),  # 青色
    (0.5, 0, 0),  # 深红色
    (0, 0.5, 0),  # 深绿色
    (0, 0, 0.5),  # 深蓝色
    (0.5, 0.5, 0.5),  # 灰色
    (0, 0, 0)  # 白色
]


# 生成数据集
num_clusters = 4
num_points_per_cluster = 50
noise_ratio = 0.1
dataset = generate_dataset(num_clusters, num_points_per_cluster, noise_ratio)

eps = 1
min_samples = 4
dbscan = DBSCAN(eps, min_samples)
labels = dbscan.fit(dataset)
labels = map_label_to_color(labels)

plt.scatter(dataset[:, 0], dataset[:, 1], s=5, c=[colorLst[i] for i in labels])
plt.title('DBSCAN Test Dataset')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

效果如下:

在这里插入图片描述

3. 确定参数

还记得 epsmin_samples 这两个参数吗,我们要想让算法有不错的效果,这个两个的值必须合适才可以。

  • min_samples 的取值:很简单,2 * 维度 即可
  • eps 的取值:
    • 首先要选取一个 k 值,一般为 2 * 维度 - 1
    • 计算并绘制 k-distance 图。即计算出每个点到距其第 k 近的点的距离,然后将这些距离排序后进行绘图。
    • 找到拐点位置的距离,即为 eps 的值。

这里我用的 KDTree 来快速找到第 k 近的点,代码如下,KDTree 的实现可以看我之前的博客:

import os
import numpy as np
import pandas as pd
from tqdm import tqdm

from model.kdTree import KDTree
from tools.visualization import draw_plots

csvPath = "./data/radar_3"
files = os.listdir(csvPath)

totDistLst = []
for i in tqdm(files):
    df = pd.read_csv(os.path.join(csvPath, i))
    points = np.array([df['u'].tolist(), df['v'].tolist(), df['z'].tolist()]).T
    kdt = KDTree(points, 4)

    for j in range(points.shape[0]):
        points, dist = kdt.search_nearest(j, isInner=True)
        totDist = -dist[0]
        totDistLst.append(totDist)

totDistLst = sorted(totDistLst)
draw_plots("k-distance", totDistLst)

从下图可以看出,如果点的数量很多的话,拐点还是不太好确定的,可以通过分段的方式画出多个区间的图,再去具体的判断 eps 取值,比如在这个整体的图上看感觉拐点是在 250 的位置,但是如果把所有的点分成几部分分开来看的话,会发现取 50 左右才是最合适的值。

在这里插入图片描述

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

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

相关文章

2023-07-23 LeetCode每日一题(接雨水)

2023-07-23每日一题 一、题目编号 42. 接雨水二、题目链接 点击跳转到题目位置 三、题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 提示&#xff1a; n height.length1 < n < 2 …

ChatGPT 4.0 —— Code Interpreter

&#x1f4ce;产品销售数据集.csv 选取以上的数据集作为输入&#xff0c;对Code Interpreter 进行测试 1.输入指定数据集&#xff0c;要求给出该数据集的概貌 2.请分析销售的总金额和其他变量的关系 Python Script: # Import required libraries import matplotlib.pyplot a…

UE5 用DLL文件制作第三方插件

本篇博文介绍了&#xff0c;如果在UE 中如何使用第三方库&#xff0c;及制作成插件的方法。 DLL 文件是上篇文章中创键的具体的方法见上篇文章。下面开始介绍方法 首先&#xff0c;创建一个空白的 UE5 C 项目&#xff0c;然后再创建一个空白内容的插件&#xff0c;如下图所示 …

转义字符\

转义字符就是反斜杠想要实现的转义功能首字母。 为什么需要转义字符&#xff1f; 当字符串中包含反斜杠、单引号和双引号等有特殊用途的字符时&#xff0c;必须使用反斜杠对这些字符进行转义&#xff08;转换一个含义&#xff09; 反斜杠&#xff1a;\ 单引号&#xff1a;’ 双…

HCIA动态路由基础实验(eNSP)

实验题目及要求&#xff1a; IP配置&#xff1a; R1: <Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]sysname r1 [r1]int GigabitEthernet 0/0/0 [r1-GigabitEthernet0/0/0]ip add 192.168.1.1 30 Jul 22 2023 13:07:24-08:00 r1 %%01IFNET/4/…

构建Web3生态系统:区块链技术与数字管理的融合

Web3技术是一种基于区块链技术的下一代互联网技术&#xff0c;它致力于实现去中心化、安全和透明的互联网世界。在Web3生态系统中&#xff0c;区块链技术是基础设施&#xff0c;而浏览器和数字管理是主要的应用场景。 区块链技术是Web3的核心&#xff0c;它是一种去中心化的分…

Spring更简单读取和存储对象

目录 前言 注解 存储Bean 通过类注解 配置扫描路径 添加类注解存储Bean对象 Controller(控制器存储) Service(服务存储) Repository(仓库存储) Component(组件存储) Configuration(配置存储) 类注解之间的关系 Bean的命名规则 通过方法注解 重命名Bean 方式一 方式…

【【51单片机 --秒表--定时器扫描按键数码管】】

轻松做秒表&#xff0c;谁用谁知道 我们在Key 和Nixie 内部都写一个函数这个是main 中中断函数的调用 因为中断是有优先级的&#xff0c;假设有多个中断&#xff0c;那么总是优先级高的在进行&#xff0c;如果我们安排多个中断的话&#xff0c;整体设计就会变得很麻烦 我们放在…

K8s系列---【K8s如何配置优雅停机?】

K8s如何配置优雅停机&#xff1f; 应用部署在k8s中&#xff0c;需要设置pod的优雅停机时间(terminationGracePeriodSeconds)&#xff0c;一般大于应用程序中spring.lifecycle.timeout-per-shutdown-phase设置的超时时间&#xff1b;设置之后服务更新或者重启时k8s会捕获到1号进…

2 push方法的使用(相当于python的append方法)

push方法相当于python的append方法&#xff0c;用来添加数组元素。 另外&#xff0c;数组取元素也是使用data[i]的格式。 例子&#xff1a; <script>var dataList [[1,2,3,4,5,6],[7,8,9,1,2,3]];var x dataList[0];console.log(x);dataList.push([1,1,2,3,4,5]);cons…

leetcode 47. 全排列 II

2023.7.23 这道题是上一题全排列 的一个升级版。 唯一区别就是需要增加一个树层去重的操作&#xff0c;因为数组nums中允许有重复的元素了&#xff0c;而上一题没有重复元素。 下面看代码&#xff1a; class Solution { public:vector<vector<int>> ans;vector<…

MySQL存储过程——概念及基本语法

1.什么时存储过程 2.存储过程操作语法 2.1 创建和调用 2.2 查看和删除 show create procedure p1;删除存储过程 drop procedure if exists p1;

Linux6.11 Docker 网络

文章目录 计算机系统5G云计算第四章 LINUX Docker 网络及Cgroup资源限制一、Docker 网络实现原理二、Docker 的网络模式1.网络模式详解1&#xff09;host模式2&#xff09;container模式3&#xff09;none模式4&#xff09;bridge模式5&#xff09;自定义网络 三、资源控制1.CP…

5.2 Bootstrap 过渡效果(Transition)插件

文章目录 Bootstrap 过渡效果&#xff08;Transition&#xff09;插件使用案例 Bootstrap 过渡效果&#xff08;Transition&#xff09;插件 过渡效果&#xff08;Transition&#xff09;插件提供了简单的过渡效果。 注意&#xff1a;如果您想要单独引用该插件的功能&#xff0…

如何在 Linux 中创建和使用别名命令

动动发财的小手&#xff0c;点个赞吧&#xff01; Linux 用户经常需要反复使用一个命令。一遍又一遍地键入或复制相同的命令会降低您的工作效率并分散您对应该做的事情的注意力。 您可以通过为最常用的命令创建别名来节省一些时间。别名就像自定义快捷方式&#xff0c;代表可以…

FM算法介绍

文章目录 1. 逻辑回归模型的不足2. POLY2模型——特征交叉的开始3. FM模型——隐向量特征交叉4. FFM模型——引入特征域的概念5. 从POLY2到FFM的模型演化过程6. 参考书籍 1. 逻辑回归模型的不足 逻辑回归作为一个基础模型&#xff0c;显然有其简单、直观、易用的特点。 但其局…

第二讲:MySQL安装及启动 Windows

目录 1.MySQL安装及启动2.MySQL常用命令&#xff1a; 1.MySQL安装及启动 1、官网下载 官网入口&#xff1a;官网 点击下载。 2、双击打开安装包 3、安装 4.设置密码&#xff08;随便设置能记住就行&#xff09; 5.配置环境变量 6、找到安装的MySQL路径&#xff0c;复制到上方…

经典CNN(二):ResNet50V2算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 1 论文解读 在《Identity Mappings in Deep Residual Networks》中&#xff0c;作者何凯明先生提出了一种新的残差单元&#xff0c;为区别原始…

MOS,PCB如何添加散热孔、过孔

一、什么是 PCB 散热孔&#xff1f; 散热孔是利用贯通PCB板的通道&#xff08;过孔&#xff09;使热量传导到背面来散热的手法&#xff0c;配置在发热体的正下方或尽可能靠近发热体。 散热孔是利用PCB板来提高表面贴装部件散热效果的一种方法&#xff0c;在结构上是在PCB板上…

element-ui里的el-table在grid布局下切换数据有滚动条时不断增加?

今天在项目里面遇到了这个问题&#xff0c;相当炸裂&#xff0c;看了半天都没有看出什么问题&#xff0c;很是逆天&#xff0c;记录一下 下面使用代码情景复现一下&#xff1a;el-table 是在 grid 布局下面的&#xff0c;不是子层级&#xff0c;中间还有一层 content 的元素包…