西瓜书第十章——聚类

请添加图片描述

聚类算法

聚类是一种经典的无监督学习方法,无监督学习的目标是通过对无标记训练样本的学习,发掘和揭示数据集本身潜在的结构与规律,即不依赖于训练数据集的类标记信息。聚类则是试图将数据集的样本划分为若干个互不相交的类簇,从而每个簇对应一个潜在的类别。

聚类算法全面解析

1.距离度量基础

1.1 闵可夫斯基距离族

通用公式:

d i s t m k ( x i , x j ) = ( ∑ u = 1 n x i u − x j u p ) 1 / p dist_{mk}(x_i,x_j) = (\sum_{u=1}^n x_{iu}-x_{ju} ^p)^{1/p} distmk(xi,xj)=(u=1nxiuxjup)1/p
常见变体:

  • 曼哈顿距离(p=1): d i s t = ∑ x i − y i dist = ∑x_i - y_i dist=xiyi

  • 欧氏距离(p=2): d i s t = √ ( ∑ ( x i − y i ) 2 ) dist = √(∑(x_i - y_i)²) dist=((xiyi)2)

1.2 属性类型处理

由于属性分为连续属性与离散属性。连续属性可以直接使用距离公式计算,对于离散属性而言还要再分为有序属性和无序属性,有序属性可以连续化然后通过距离公式计算,而无需属性则需要使用one-hot编码再使用VDM距离法。

  • 有序属性 直接参与计算 身高:高(1)→中(0.5)→矮(0)
  • 无序属性 VDM距离法 性别:(男→[1,0], 女→[0,1])

VDM距离公式:

聚类性能评估

2.1 外部指标

由于无监督,因此不能对比标签来看聚类模型得准确率。因此此时找到一个外部得参考模型得输出作为伪标签,然后评价聚类效果
在这里插入图片描述

2.2 内部指标

基于聚类结果自身特性进行评估
在这里插入图片描述

原型聚类算法

3.1 K-Means

K-Means的思想十分简单,首先随机指定类中心,根据样本与类中心的远近划分类簇,接着重新计算类中心,迭代直至收敛。但是其中迭代的过程并不是主观地想象得出,事实上,若将样本的类别看做为“隐变量”(latent variable),类中心看作样本的分布参数,这一过程正是通过 EM算法的两步走 策略而计算出,其根本的目的是为了最小化平方误差函数E:

E = ∑ i = 1 k ∑ x ∈ C i ∣ x − μ i ∣ 2 E=\sum_{i=1}^k \sum_{x\in C_i} |x-\mu_i|^2 E=i=1kxCixμi2
在这里插入图片描述

Kmeans++算法:更优的初始化
1.随机选择​​第一个​​中心点作为第一个类别
2.计算每个点到已有中心的​​最短距离​​D(x)(如果有多个中心的话就选择最短距离作为D(x))
3.按概率 P = D ( x ) 2 / ∑ D ( x ) 2 P= D(x)^2/ \sum D(x)^2 P=D(x)2/D(x)2选择下一个中心作为下一个类别标记
4.直到选满K个类别


选择下一个中心

3.2 学习向量量化(LVQ)

首先面对标注真实标签的数据,是一个半监督算法。从每个类别中挑选出一个作为圆形特征向量。然后再从样本集中随机挑选样本 x i x_i xi来计算 x i x_i xi与每个原型特征的距离,选择最近的距离作为 x i x_i xi的预测标签。如果分类正确,则原型向量靠近样本向量,如果分类不正确则远离。

通过**"奖励-惩罚"机制更新原型向量**

更新规则:
{ p ′ = p + η ( x − p ) 分类正确 p ′ = p − η ( x − p ) 分类错误 \begin{cases} p' = p + \eta(x-p) & \text{分类正确} \\ p' = p - \eta(x-p) & \text{分类错误} \end{cases} {p=p+η(xp)p=pη(xp)分类正确分类错误
在这里插入图片描述

3.3 高斯混合聚类

在这里插入图片描述

概率模型:
p ( x ) = ∑ i = 1 k α i ⋅ N ( x ∣ μ i , Σ i ) p(x) = \sum_{i=1}^k \alpha_i \cdot N(x|\mu_i,\Sigma_i) p(x)=i=1kαiN(xμi,Σi)
EM算法步骤:

  • E步:计算后验概率γ

  • M步:更新参数:

μ i = ∑ γ j i x j ∑ γ j i \mu_i = \frac{\sum γ_{ji}x_j}{\sum γ_{ji}} μi=γjiγjixj
密度聚类(DBSCAN)

🌟 DBSCAN算法完全解析:从背景到实践

🔍 算法提出的背景

DBSCAN (Density-Based Spatial Clustering of Applications with Noise) 诞生于解决传统聚类算法三大局限:

  1. 形状限制:K-Means等算法只能识别凸形簇,无法处理复杂形状
  2. 噪声敏感:传统算法对噪声点缺乏鲁棒性
  3. 密度差异:无法自动适应不同区域的数据密度

举个例子🌰:假设地图上有这些位置:

  • 密集居民区(高密度点集)
  • 零散便利店(中等密度点集)
  • 郊区独立屋(低密度点集)
  • 偏远加油站(孤立噪声点)

传统聚类会将郊区住宅误认为噪声,而DBSCAN能精准识别所有区域!


📊 算法操作的数据特点

DBSCAN处理的数据需满足:

  1. 可测距性:能计算任意两点间距离
    • 欧氏距离: d i s t = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 dist = \sqrt{ (x₁-x₂)² + (y₁-y₂)² } dist=(x1x2)2+(y1y2)2
    • 余弦相似度:适合文本数据
  2. 特征向量表示:每个数据点为向量形式
    • 房价数据:[面积, 房间数, 经度, 纬度]
    • 用户画像:[活跃度, 消费金额, 年龄]
  3. 维度无限制:适合高维空间(但需要调整参数)
# 典型数据集格式
import numpy as np
data = np.array([
    [35.2, -118.3],   # 洛杉矶
    [40.7, -74.0],    # 纽约
    [51.5, -0.1],     # 伦敦
    [48.9, 2.3],      # 巴黎郊区
    [48.8, 2.4]       # 巴黎郊区
])

关键概念

在这里插入图片描述

🔧 算法流程详解

DBSCAN通过两个核心参数工作:

  • ε (eps):邻域半径

  • MinPts:核心点阈值

算法分为两个步骤:1.标记所有核心对象(满足len<MinPts)2.将所有密度可直达的点划分为一个cluster
在这里插入图片描述

在这里插入图片描述

层次聚类(AGNES)

非常像哈夫曼树呢怎么??

🧠 AGNES算法提出的背景

层次聚类诞生的核心动机是解决"数据层次关系可视化"问题。不同于K-Means等划分式聚类,AGNES (AGglomerative NESting) 的独特价值在于:

  1. 生物分类学启发
    受生物分类系统(界-门-纲-目…)启发,适用于需要多层级分析的场景:
  • 基因家族进化树
  • 文档主题分层体系
  • 企业组织架构
  1. 应对现实数据的层次性
    当数据存在自然嵌套结构时(如:动物 → 哺乳类 → 猫科 → 家猫),
    扁平聚类会丢失层次化的嵌套结构关系

  2. 无需预设簇数

    传统方法需提前指定k值(如K-Means),而AGNES通过树状图(Dendrogram) 展示所有可能划分
    简言之:AGNES像"数据显微镜",能观察样本在不同粒度下的聚类关系

🔧 算法流程详解(结合您提供的伪代码)

AGNES采用自底向上策略,流程分为三阶段:

阶段1:初始化(对应伪代码1-9行)

每个样本单独归为一个簇。n个样本n个簇,并且构建n×n的对称距离矩阵
距离的计算采用三种链接之一
在这里插入图片描述

阶段2:迭代合并(对应伪代码10-23行)

取出距离矩阵中最小的两个簇合并。去掉这两个簇,并且将合并的新簇添加进入队列,并且更新距离矩阵。

在这里插入图片描述

很容易看出:单链接的包容性极强,稍微有点暧昧就当做是自己人了,全链接则是坚持到底,只要存在缺点就坚决不合并,均连接则是从全局出发顾全大局。

头歌实战

距离计算

#encoding=utf8    
import numpy as np

def distance(x,y,p=2):
    '''
    input:x(ndarray):第一个样本的坐标
          y(ndarray):第二个样本的坐标
          p(int):等于1时为曼哈顿距离,等于2时为欧氏距离
    output:distance(float):x到y的距离      
    ''' 

    #********* Begin *********#
    #一定注意x,y都是多维空间的
    if p == 1:
          return np.sum(np.abs(x-y))
    else:
          return np.sum(np.abs(x-y)**p)**(1.0/p)
    #********* End *********#

计算质心距离并且排序

看清楚怎么使用sort()的

#encoding=utf8
import numpy as np

# 计算样本间距离
def distance(x, y, p=2):
    '''
    input:x(ndarray):第一个样本的坐标
          y(ndarray):第二个样本的坐标
          p(int):等于1时为曼哈顿距离,等于2时为欧氏距离
    output:distance(float):x到y的距离      
    '''
    #********* Begin *********#
    # 计算x和y之间差的绝对值的p次方  
    dis = np.sum(np.abs(x - y) ** p) ** (1.0 / p)
    # 计算最终的距离值 

    #********* End *********#
    return dis
    
# 计算质心
def cal_Cmass(data):
    '''
    input:data(ndarray):数据样本
    output:mass(ndarray):数据样本质心
    '''
    #********* Begin *********#
    # 计算数据样本的质心(每个维度的平均值)
    # 使用axis=0确保正确处理多维数组
    Cmass = np.mean(data, axis=0)
    #********* End *********#
    return Cmass

# 计算每个样本到质心的距离,并按照从小到大的顺序排列
def sorted_list(data, Cmass):
    '''
    input:data(ndarray):数据样本
          Cmass(ndarray):数据样本质心
    output:dis_list(list):排好序的样本到质心距离
    '''
    #********* Begin *********#
    # 初始化一个空列表,用于存储距离  
    dist_list = []
    # 遍历数据样本中的每个样本 
    for x in data:
        dist_list.append(distance(x, Cmass))
    
    # 对距离列表进行排序
    dist_list.sort()
    
    # 转换为NumPy数组
    #********* End *********#
    return dist_list

K-means

K-means:
两个关键变量

  • 样本集X(sample_num,featrues)
  • clusters,K行,每行若干个索引——代表X样本的分类结果
  • centroids(K,featrues),K个类别中心

#encoding=utf8
import numpy as np

# 计算一个样本与数据集中所有样本的欧氏距离的平方
def euclidean_distance(one_sample, X):
    one_sample = one_sample.reshape(1, -1)
    distances = np.power(np.tile(one_sample, (X.shape[0], 1)) - X, 2).sum(axis=1)
    return distances

class Kmeans():
    """Kmeans聚类算法.
    Parameters:
    -----------
    k: int
        聚类的数目.
    max_iterations: int
        最大迭代次数. 
    varepsilon: float
        判断是否收敛, 如果上一次的所有k个聚类中心与本次的所有k个聚类中心的差都小于varepsilon, 
        则说明算法已经收敛
    """
    def __init__(self, k=2, max_iterations=500, varepsilon=0.0001):
        self.k = k
        self.max_iterations = max_iterations
        self.varepsilon = varepsilon
        np.random.seed(1)
    #********* Begin *********#

    #clusters,K行,每行若干个索引
    #centroids(K,featrues),K个类别中心

    # 从所有样本中随机选取self.k样本作为初始的聚类中心
    def init_random_centroids(self, X):
        #生成从0到样本数量的k个随机不重复索引np.random.choice,replace=Falsed代表无放回
        random_indices = np.random.choice(X.shape[0],self.k,replace=False)
        #选择出的索引可能为[1,3,24,54]
        return X[random_indices]


    # 返回距离该样本最近的一个中心索引[0, self.k)
    def _closest_centroid(self, sample, centroids):
        #广播机制的应用
        # distance = np.sum((sample-centroids)**2)**(1.0/2)错误,直接使用np.sum会将所有维度的差值平方嘉禾成一个标量值。
        #应该是用np.sum(,axis=1)得到一个形状为 (k,) 的距离向量
        distance = np.sum((sample-centroids)**2,axis=1)**(1.0/2)
        #返回该样本属于哪个中心的中心索引
        return np.argmin(distance)


    # 将所有样本进行归类,归类规则就是将该样本归类到与其最近的中心
    def create_clusters(self, centroids, X):
        clusters = [[] for _ in range(self.k)]#二维数组,k行代表一共k个簇,每行存储对应的样本
        #遍历所有样本
        for i, sample in enumerate(X):
            centroid_index=self._closest_centroid(sample,centroids)
            clusters[centroid_index].append(i)

        return clusters
    #cluster为[k],每行保存X的对应索引

    # 对中心进行更新
    def update_centroids(self, clusters, X):
        #初始化新的聚类中心数组(K,featrues),老的已经分完类的二维数组clusters存储了每个类的数组,cluster 中包含有若干个索引
        centroids = np.zeros((self.k,X.shape[1]))
        #遍历每个簇
        for i,cluster in enumerate(clusters):
            if cluster:
                centroids[i]=np.mean(X[cluster],axis=0)#得到(,features)
            else:#簇为空
                centroids[i]=X[np.random.choice(X.shape[0],1,replace=False)].flatten()#X[random]需要展平得到一维向量
        return centroids


    # 将所有样本进行归类,其所在的类别的索引就是其类别标签
    def get_cluster_labels(self, clusters, X):
        #初始化标签数组,大小为样本数
        labels=np.empty(X.shape[0])

        #为每个簇的样本分配标签
        for i,cluster in enumerate(clusters):
            for sample_index in cluster:
                labels[sample_index]=i

        return labels
    # 对整个数据集X进行Kmeans聚类,返回其聚类的标签
    def predict(self, X):
        # 从所有样本中随机选取self.k样本作为初始的聚类中心
        centroids= self.init_random_centroids(X)
        # 迭代,直到算法收敛(上一次的聚类中心和这一次的聚类中心几乎重合)或者达到最大迭代次数
        for _ in range(self.max_iterations):
            #保存上一代的中心
            pre_cen = centroids.copy()

            # 将所有X进行归类,归类规则就是将该样本归类到与其最近的中心
            clusters = self.create_clusters(centroids,X)
            # 计算新的聚类中心
            centroids = self.update_centroids(clusters,X)

            # 如果聚类中心几乎没有变化,说明算法已经收敛,退出迭代
            distances=np.sum((pre_cen-centroids)**2,axis=1)**(1.0/2)

            if np.all(distances<self.varepsilon):
                break;
        
        labels = self.get_cluster_labels(clusters,X)
        return labels
    #********* End *********#

sklearn实战K-means

#encoding=utf8
from sklearn.cluster import KMeans

def kmeans_cluster(data):
    '''
    input:data(ndarray):样本数据
    output:result(ndarray):聚类结果
    '''
    #********* Begin *********#
    Kmeans=KMeans(n_clusters=3,random_state=10)

    Kmeans.fit(data)

    result=Kmeans.labels_
    #********* End *********# 
    return result



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值