K-Means 算法

目标: 无监督的将数据分类

步骤:

  1. 确定 K 值(分成几类)
  2. 选择初始质心(质心指数据点的各个维度的平均值点),k 个质心
  3. 确定度量:距离,确定每个数据点到质心的距离最小,划分为该类
  4. 再将每个类的所有数据点求新质心,重复步骤 3
  5. 当质心不再发生变化时结束

缺点: K 值比较难确定、比较难发现特殊形状(任意形状)的簇分类、数据量越大,程序时间复杂度越高。


DBSCAN 算法

算法核心思想: DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法。

核心逻辑: 将高密度区域划分为簇,并识别低密度区域的噪声点。

关键优势: 无需指定簇数量、能发现任意形状的簇、自动处理噪声。

概念知识点

  • ε (eps): 邻域的半径(例如,以某个点为中心,半径 ε 的圆形区域)

  • 密度: 在指定 ε 邻域内,点的数量多少

  • 核心点: 如果这个点的密度达到算法设定的阈值(min_samples),则为核心点

  • 边界点: 如果一个点在核心点的邻域内,但这个点自己的邻域没有达到密度阈值(属于某个核心点的非核心点)

  • 直接密度可达: 如果 q 点在核心点 p 的邻域内,则 q-p 直接密度可达

  • 密度可达: 简单的说,核心点是一个传播站,一个核心点 q 如果在核心点 p₁ 内,而核心点 p₁ 又在核心点 p₂ 内,那么 q 点和核心点 p₂ 邻域内的点都是密度可达的。密度可达是单向的,非核心点只能接收密度可达关系(他是一个终点),不能传播。

    核心点 A → 核心点 B → 核心点 C → 点 D(非核心点)
    D 密度可达于 A,但 A 不密度可达于 D(方向性)
    
  • 密度相连: 如果从核心点 q 出发,a 和 b 都是密度可达的,则 a 和 b 就是密度相连的

  • 噪声点: 不属于任何一个类簇的点,从任何一个核心点出发都是密度不可达的

算法步骤

  1. 随机选择一个未访问的点
  2. 若它是核心点,创建一个新簇,并递归扩展所有密度可达的点
  3. 若它是噪声或边界点,标记为噪声,继续处理下一个点
  4. 重复直到所有点被访问

参数选择技巧

ε (eps):

  • 太小:所有点都是噪声
  • 太大:所有点被合并成一个簇
  • 经验方法:绘制”k-距离图”(k = min_samples),循环 1-10 fit,取 inertia_,选择拐点处的 ε 值

min_samples: 通常从 2 × 数据维度开始尝试(例如二维数据设为 4)

优点: 不需要指定分类个数、可以发现任意类型的簇

DBSCAN 代码示例

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_moons
from sklearn.preprocessing import StandardScaler

# === 1. 全局配置 ===
# 解决中文显示和Matplotlib警告
plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文字体
plt.rcParams['axes.unicode_minus'] = False    # 修复负号显示
import matplotlib
matplotlib.use('TkAgg')  # 非交互式后端,避免警告

# 生成数据:两个交错的月牙形 + 噪声
X, _ = make_moons(n_samples=500, noise=0.1, random_state=42)
noise = np.random.uniform(low=-1.5, high=2.5, size=(50, 2))  # 生成50个噪声点
X = np.vstack([X, noise])

# 标准化数据
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 训练DBSCAN模型(参数:eps=0.3, min_samples=5)
dbscan = DBSCAN(eps=0.3, min_samples=5)
labels = dbscan.fit_predict(X_scaled)

# 可视化
plt.figure(figsize=(10, 6))
unique_labels = set(labels)
colors = plt.cm.tab10(np.linspace(0, 1, len(unique_labels)))

for label, color in zip(unique_labels, colors):
    if label == -1:
        plt.scatter(X[labels == label][:, 0], X[labels == label][:, 1],
                    color='gray', s=10, alpha=0.6, label='Noise')
    else:
        plt.scatter(X[labels == label][:, 0], X[labels == label][:, 1],
                    color=color, s=30, label=f'Cluster {label}')

plt.title('DBSCAN Clustering (月牙形数据 + 噪声)')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.show()

K-Means代码

# 导入库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_blobs  # 生成模拟数据

# === 1. 全局配置 ===
# 解决中文显示和Matplotlib警告
plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文字体
plt.rcParams['axes.unicode_minus'] = False    # 修复负号显示
import matplotlib
matplotlib.use('TkAgg')  # 非交互式后端,避免警告

# 1. 生成模拟数据(实际项目中替换为真实数据)
# 生成500个样本,2个特征(年收入、年消费),3个簇,标准差1.5
X, y = make_blobs(n_samples=500, n_features=2, centers=3, cluster_std=1.5, random_state=42)

# 2. 数据标准化(消除量纲影响)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # 将数据转换为均值为0,方差为1的分布

# 3. 使用肘部法则选择最佳K值(可选步骤)
sse = []
for k in range(1, 5):
    kmeans = KMeans(n_clusters=k, random_state=42,n_init=10)
    kmeans.fit(X_scaled)
    sse.append(kmeans.inertia_)  # inertia_属性即为SSE

# 绘制肘部法则图
plt.plot(range(1, 5), sse, marker='o')
plt.xlabel('集群数 (K)')
plt.ylabel('SSE')
plt.title('Elbow Method for Optimal K')
plt.show()
plt.figure()
# 4. 训练K-Means模型(假设K=3)
kmeans = KMeans(n_clusters=3, n_init=10,random_state=42)
kmeans.fit(X_scaled)  # 拟合数据
labels = kmeans.predict(X_scaled)  # 预测每个样本的簇标签
centroids = kmeans.cluster_centers_  # 获取质心坐标(标准化后的)

# 5. 反标准化质心(还原到原始数据尺度)
centroids_original = scaler.inverse_transform(centroids)

# 6. 可视化结果
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', alpha=0.6)
plt.scatter(centroids_original[:, 0], centroids_original[:, 1], c='red', s=200, marker='X')
plt.xlabel('年收入(10k yuan)')
plt.ylabel('年消费 (10k yuan)')
plt.title('按 K-Means 进行客户细分')
plt.show()