机器学习聚类算法二——DBSCAN(Density-Based Spatial Clustering of Applications with Noise)

一、算法原理

DBSCAN(Density-BasedSpatialClusteringofApplicationswithNoise)是一种基于密度的聚类算法,其核心思想是通过发现数据中的高密度区域来识别簇,并将低密度区域的数据点标记为噪声。

1、核心概率

核心点(CorePoint):

在半径为ε\varepsilonε的邻域内,若包含至少MinPts个数据点(包括自身),则该点为核心点。
ε\varepsilonε为邻域半径。

MinPts为最小样本数,为超参数。
边界点(BorderPoint):

在半径ε−\varepsilon-ε−的邻域内,点的数量少于

MinPts,但位于某个核心点的邻域内。
噪声点(NoisePoint):

既不是核心点,也不是边界点的点(即低密度区域的孤立点)。
密度直达(DirectlyDensity-Reachable):

若点p在点q的ε−\varepsilon-ε−邻域内,且q是核心点,则称p从q密度直达。
密度相连(Density-Connected):

若存在点o,使得p和q均从o密度可达,则称p和q密度相连。

2、算法步骤

(1)初始化

输入参数:ε\varepsilonε、MinPts(最小邻域点数)。

标记所有点为未访问(Unvisited)。

(2)遍历数据点:

随机选择一个未访问的点p。计算p的ε−\varepsilon-ε−邻域内的点数:若点数≥MinPts,则p是核心点,开始扩展(步骤3)。否则,标记p为噪声点(后续可能被重新分类为边界点)。

(3)扩展簇:

创建一个新簇,将p加入簇。遍历p的ε−\varepsilon-ε−邻域内的所有点q:若q未被访问:标记q为已访问。若q是核心点(其邻域点数≥MinPts),递归扩展q的邻域。若q不是核心点但位于某个核心点的邻域内,则q是边界点,将其加入当前簇。

(4)终止条件:

当前簇无法继续扩展时,选择下一个未访问的点重复步骤2-3,直到所有点被访问。

3、关键特性

抗噪声能力: 噪声点会被明确标记,不参与任何簇的形成。
形状适应性: 能发现任意形状的簇(如环形、非凸形),而无需预先指定簇的数量(如K-Means)。
参数敏感性: ε\varepsilonε过大:可能将不同簇合并;过小:可能将同一簇拆分。MinPts通常根据数据维度选择(如二维数据中 MinPts≥4)。

时间复杂度:最坏情况下为 O(n2n^2n2)(需计算所有点对的距离)。使用空间索引(如KD树)可优化至 O(nlognnlognnlogn)。

4、应用场景

地理空间数据聚类(如发现城市中的热点区域)。

异常检测(识别噪声点)。

图像分割(基于像素密度)。

二、参考代码

python 复制代码
from sklearn.neighbors import NearestNeighbors
from collections import deque
import numpy as np
from sklearn.datasets import make_moons, make_blobs,make_circles
import matplotlib.pyplot as plt

class DBSCAN:
    def __init__(self, eps=0.5, min_samples=5):
        """
        初始化DBSCAN参数
        
        参数:
            eps: 邻域半径
            min_samples: 核心点所需的最小邻域样本数
        """
        self.eps = eps
        self.min_samples = min_samples
        self.labels_ = None
    
    def fit(self, X):
        """
        对数据进行DBSCAN聚类
        
        参数:
            X: 输入数据 (n_samples, n_features)
        """
        n_samples = X.shape[0]
        self.labels_ = np.full(n_samples, -1, dtype=int)  # -1表示噪声点
        cluster_id = 0
        
        # 计算每个点的邻域
        neighbors_model = NearestNeighbors(radius=self.eps)
        neighbors_model.fit(X)
        neighborhoods = neighbors_model.radius_neighbors(X, return_distance=False)
        
        # 标记已访问的点
        visited = np.zeros(n_samples, dtype=bool)
        
        for i in range(n_samples):
            if not visited[i]:
                visited[i] = True
                neighbors = neighborhoods[i]
                
                if len(neighbors) >= self.min_samples:
                    # 发现新簇
                    self._expand_cluster(X, neighborhoods, visited, i, neighbors, cluster_id)
                    cluster_id += 1
                else:
                    # 标记为噪声点(可能在后续扩展中被重新分配)
                    self.labels_[i] = -1
        
        return self
    
    def _expand_cluster(self, X, neighborhoods, visited, index, neighbors, cluster_id):
        """
        扩展簇,将密度可达的点加入当前簇
        """
        queue = deque([index])
        self.labels_[index] = cluster_id
        
        while queue:
            current = queue.popleft()
            
            # 获取当前点的邻域
            current_neighbors = neighborhoods[current]
            
            if len(current_neighbors) >= self.min_samples:
                for neighbor in current_neighbors:
                    if not visited[neighbor]:
                        visited[neighbor] = True
                        if self.labels_[neighbor] == -1:
                            # 之前标记为噪声的点现在属于这个簇
                            self.labels_[neighbor] = cluster_id
                        elif self.labels_[neighbor] < 0:
                            # 未分类的点,加入队列
                            self.labels_[neighbor] = cluster_id
                            queue.append(neighbor)


    def generate_data(self,dataset_type='moons', n_samples=300, noise=0.05, random_state=42):
        """
        生成不同类型的测试数据集
        
        参数:
            dataset_type: 数据集类型 ('moons', 'blobs', 'circles')
            n_samples: 样本数量
            noise: 噪声水平
            random_state: 随机种子
            
        返回:
            X: 特征数据 (n_samples, 2)
            y: 真实标签 (用于参考)
        """
        if dataset_type == 'moons':
            X, y = make_moons(n_samples=n_samples, noise=noise, random_state=random_state)
        elif dataset_type == 'blobs':
            X, y = make_blobs(n_samples=n_samples, centers=3, 
                            cluster_std=[0.8, 0.5, 0.3], 
                            random_state=random_state)
        elif dataset_type == 'circles':
            X, y = make_circles(n_samples=n_samples, factor=0.5, 
                            noise=noise, random_state=random_state)
        else:
            raise ValueError("Error database type:choose 'moons', 'blobs' 或 'circles'")
        
        return X, y
    def plot_clusters(self,X, labels, title="DBSCAN  clustering result", centers=None):
        """
        可视化聚类结果
        
        参数:
            X: 输入数据
            labels: 聚类标签
            title: 图表标题
            centers: 簇中心(可选)
        """
        plt.figure(figsize=(10, 6))
        
        # 获取唯一标签(排除噪声点-1)
        unique_labels = set(labels)
        colors = [plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unique_labels))]
        
        # 绘制每个簇
        for k, col in zip(unique_labels, colors):
            if k == -1:
                # 噪声点显示为黑色
                col = [0, 0, 0, 1]
            
            class_member_mask = (labels == k)
            xy = X[class_member_mask]
            plt.scatter(xy[:, 0], xy[:, 1], c=[col], s=16, 
                    edgecolor='k', marker='o', label=f'clustering {k}' if k != -1 else 'noise')
        
        # 如果有中心点,绘制它们
        if centers is not None:
            plt.scatter(centers[:, 0], centers[:, 1], c='red', s=100, 
                    alpha=0.8, marker='X', label='center')
        
        plt.title(title)
        plt.xlabel('f 1')
        plt.ylabel('f 2')
        plt.legend()
        plt.grid(True)
        plt.show()

三、实验结果

1、实验参数

生成数据:n_samples=500, noise=0.08

模型参数:eps=0.2, min_samples=5

2、实验结果

自设计模型

标准模型:skDBSCAN(eps=0.2, min_samples=5)

由此可以看出,自设计模型和标准模型存在一定的差距。

3、模型改进

将扩展簇函数修改为,并去除调用时的多余参数。

python 复制代码
def _expand_cluster(self, neighborhoods, visited, index, cluster_id):
    """
    优化后的扩展簇实现
    """
    queue = deque([index])
    visited[index] = True
    self.labels_[index] = cluster_id
    
    while queue:
        current = queue.popleft()
        for neighbor in neighborhoods[current]:
            if not visited[neighbor]:
                visited[neighbor] = True
                # 如果是核心点,加入队列继续扩展
                if len(neighborhoods[neighbor]) >= self.min_samples:
                    queue.append(neighbor)
                # 无论是否核心点,均加入当前簇(覆盖噪声和未访问点)
                self.labels_[neighbor] = cluster_id

原本的扩展簇函数并没有涉及簇合并,所以结果簇有很多。修改后,

生成数据:n_samples=500, noise=0.08

模型参数:eps=0.2, min_samples=5

模型结果:

由此可见,自设计模型在聚类上和标准模型在"moons"类型的数据集上的聚类效果无明显差别,能正确地用来了解算法原理。

相关推荐
add45a2 小时前
C++中的原型模式
开发语言·c++·算法
bryant_meng2 小时前
【AI】《Explainable Machine Learning》
人工智能·深度学习·机器学习·计算机视觉·可解释性
就叫你天选之人啦2 小时前
GBDT系列八股(XGBoost、LightGBM)
人工智能·深度学习·学习·机器学习
CoderIsArt2 小时前
StarCoder-3B微调和RAG的技术原理
人工智能·深度学习·机器学习
2401_844221322 小时前
C++类型推导(auto/decltype)
开发语言·c++·算法
2201_753877792 小时前
高性能计算中的C++优化
开发语言·c++·算法
hans汉斯2 小时前
基于区块链和语义增强的科研诚信智能管控平台
人工智能·算法·yolo·数据挖掘·区块链·汉斯出版社
2501_945425152 小时前
分布式系统容错设计
开发语言·c++·算法
冷小鱼2 小时前
机器学习极简入门:从外卖预测到AI核心算法
人工智能·算法·机器学习