五、基于密度的算法
5.1 DBSCAN 算法
import sys # 导入 sys 模块,用于访问系统相关的参数和功能
import os # 导入 os 模块,用于处理文件和目录
import math # 导入 math 模块,用于进行数学运算
import random # 导入 random 模块,用于生成随机数
from sklearn import datasets # 导入 sklearn 的 datasets 模块,用于加载数据集
import numpy as np # 导入 numpy 模块,用于进行科学计算,简写为 np
# Import helper functions
dir_path = os.path.dirname(os.path.realpath(__file__)) # 获取当前文件的绝对路径
sys.path.insert(0, dir_path + "/../utils") # 将 utils 目录添加到系统路径中,方便导入其中的模块
from utils.data_manipulation import normalize # 从 utils.data_manipulation 模块中导入 normalize 函数,用于对数据进行归一化处理
from utils.data_operation import euclidean_distance # 从 utils.data_operation 模块中导入 euclidean_distance 函数,用于计算欧几里得距离
sys.path.insert(0, dir_path + "/../unsupervised_learning/") # 将 unsupervised_learning 目录添加到系统路径中,方便导入其中的模块
from principal_component_analysis import PCA # 从 principal_component_analysis 模块中导入 PCA 类,用于进行主成分分析
# 定义 DBSCAN 类,用于实现 DBSCAN 算法
class DBSCAN():
# 初始化方法,接受两个参数:eps 和 min_samples
def __init__(self, eps=1, min_samples=5):
self.eps = eps # eps 表示邻域半径,用于判断两个样本是否为邻居
self.min_samples = min_samples # min_samples 表示核心点的最小邻居数,用于判断一个样本是否为核心点
# List of arrays (clusters) containing sample indices
self.clusters = [] # clusters 表示聚类结果,是一个列表,每个元素是一个数组,表示一个簇,包含了样本的索引
self.visited_samples = [] # visited_samples 表示已经访问过的样本的索引,是一个列表
# Hashmap {"sample_index": [neighbor1, neighbor2, ...]}
self.neighbors = {} # neighbors 表示每个样本的邻居,是一个字典,键是样本的索引,值是一个数组,表示该样本的邻居的索引
self.X = None # Dataset # X 表示数据集,是一个二维数组,每一行是一个样本,每一列是一个特征
# 定义一个私有方法,用于获取一个样本的邻居,接受一个参数:sample_i,表示样本的索引
def _get_neighbors(self, sample_i):
neighbors = [] # 定义一个空列表,用于存储邻居的索引
for _sample_i, _sample in enumerate(self.X): # 遍历数据集中的每个样本及其索引
if _sample_i != sample_i and euclidean_distance( # 如果样本的索引不等于 sample_i,且样本与 sample_i 对应的样本的欧几里得距离小于 eps
self.X[sample_i], _sample) < self.eps:
neighbors.append(_sample_i) # 则将该样本的索引添加到邻居列表中
return np.array(neighbors) # 返回邻居列表,转换为 numpy 数组
# 定义一个私有方法,用于扩展一个簇,接受两个参数:sample_i 和 neighbors,分别表示样本的索引和邻居
def _expand_cluster(self, sample_i, neighbors):
cluster = [sample_i] # 定义一个列表,用于存储簇中的样本的索引,初始值为 sample_i
# Iterate through neighbors
for neighbor_i in neighbors: # 遍历邻居中的每个样本的索引
if not neighbor_i in self.visited_samples: # 如果该样本的索引不在已访问过的样本列表中
self.visited_samples.append(neighbor_i) # 则将该样本的索引添加到已访问过的样本列表中
# Fetch the samples distant neighbors
self.neighbors[neighbor_i] = self._get_neighbors(neighbor_i) # 获取该样本的邻居,并存储到 neighbors 字典中
# Make sure the neighbors neighbors are more than min_samples
if len(self.neighbors[neighbor_i]) >= self.min_samples: # 如果该样本的邻居数大于等于 min_samples,即该样本是一个核心点
# Choose neighbors of neighbor except for sample
distant_neighbors = self.neighbors[neighbor_i][ # 选择该样本的邻居中除了 sample_i 之外的样本,作为远邻
np.where(self.neighbors[neighbor_i] != sample_i)]
# Add the neighbors neighbors as neighbors of sample
self.neighbors[sample_i] = np.concatenate( # 将远邻添加到 sample_i 的邻居中
(self.neighbors[sample_i], distant_neighbors))
# Expand the cluster from the neighbor
expanded_cluster = self._expand_cluster( # 从该样本开始递归地扩展簇