【人工智能】4 聚类算法



  聚类是一种无监督学习。无监督学习指的是,在我们缺乏足够的先验知识,难以人工标注类别的情况下,借助计算机来进行自动分类。

1. 聚类算法的分类

  聚类是将数据对象的集合分成相似的对象类的过程,使得同一个簇中的个体间具有较高的相似性,不同簇间的对象具有较高的相异性。按照聚类的尺度,聚类算法可以被分为:

  1. 基于 距离 的聚类算法
  2. 基于 密度 的聚类算法
  3. 基于 互联性 的聚类算法

2. K-means 聚类算法

2.1. 算法框架

\(k\)

  算法伪代码为:

创建 k 个点作为起始质心(随机选择)
根据 k 个质心创建 k 个簇
while True:
    for i in 所有点:
    	计算 点i 到所有质心的距离
        将 点i 归类到距离最近的质心的簇中
    for i in 所有簇:
    	计算 簇i 中所有点的平均值作为 簇i 的新质心
    if 所有簇的质心都保持不变:
    	聚类完成!
    	break

  然而,作为一个启发式的算法,初始质心的选择会影响算法的运行时间和最终效果,故而要使用初始质心的随机选择优化算法:

随机选择一个点作为质心
while True:
    for i in 所有点:
    	计算 点i 到已选择质心的距离
    选择新质心,到已选择质心的距离越大的点被选中的概率越大
    if 质心的数量达到 k :
    	break

2.2. 距离公式

\(m\) 维的点 \(X\) 和 \(Y\),它们的距离为 \(D\):

\[X=\left[
 \begin{matrix}
   x_1 & x_2 & x_3 &\cdots  & x_m\\
  \end{matrix}
  \right]\\
Y=\left[
 \begin{matrix}
   y_1 & y_2 & y_3 &\cdots  & y_m\\
  \end{matrix}
  \right]
\]

(1)欧氏距离

\[D=\sqrt{\sum_{i=1}^m (x_i-y_i)^2}
\]

(2)曼哈顿距离

\[D=\sum_{i=1}^m|x_i-y_i|
\]

(3)切比雪夫距离

\[D=max(|x_i-y_i|)\ ,i=1,2,3,...,m
\]

(4)闵可夫斯基距离

\[D=\sqrt[p]{\sum_{i=1}^m |x_i-y_i|^p} \]

  • \(p=1\),则为 曼哈顿距离
  • \(p=2\),则为 欧式距离
  • \(p→∞\),则为 切比雪夫距离

(5)标准化欧氏距离

  上面的距离计算方法都有个明显的缺点,那就是没有对数据进行量纲化预处理。标准化欧氏距离首先对数据进行 Z-Score 标准化,然后计算标准化后的数据的欧式距离。

  数据标准化的方法见上一篇文章:

【人工智能】2 数据预处理

(6)夹角余弦距离

\[D=\cos(\theta)=\frac{\sum_{i=1}^m x_iy_i}{\sqrt{\sum_{i=1}^m x_i^2 }\sqrt{\sum_{i=1}^m y_i^2}}
\]

取值范围:\(D∈[-1,1]\)。当 \(D=1\) 时,两个向量完全重合(两点最近);当 \(D=-1\)

2.3. 算法流程

  接下来将详细描述算法过程:

    • 假设有 \(n\) 个 \(m\) 维的数据样本 \(X\)
    \[X=\left[
     \begin{matrix}
       x_{1,1} & x_{1,2} & x_{1,3} &\cdots  & x_{1,m}\\
       x_{2,1} & x_{2,2} & x_{2,3} &\cdots  & x_{2,m}\\
       x_{3,1} & x_{3,2} & x_{3,3} &\cdots  & x_{3,m}\\
       \vdots & \vdots & \vdots &\ddots  & \vdots\\
       x_{n,1} & x_{n,2} & x_{n,3} &\cdots  & x_{n,m}\\
      \end{matrix}
      \right]_{n×m}
    \]
    • 先对每一维数据进行数据标准化。计算 平均值 \(\bar{X}\) 和 标准差 \(S\)
    \[\bar{X}=\left[
     \begin{matrix}
       \bar{x}_1 & \bar{x}_2 & \bar{x}_3 &\cdots  & \bar{x}_m\\
      \end{matrix}
      \right]_{1×m}\\
    \bar{x}_j=\frac{\sum_{i=1}^n x_{i,j}}{n}\\  
    S=\left[
     \begin{matrix}
       s_1 & s_2 & s_3 &\cdots  & s_m\\
      \end{matrix}
      \right]_{1×m}\\
    s_j=\sqrt{\frac{\sum_{i=1}^n (x_{i,j}-\bar{x}_j)}{n}}
    \]
    • 计算标准化后的数据 \(\hat{X}\):
    \[\hat{X}=\left[
     \begin{matrix}
       \hat{x}_{1,1} & \hat{x}_{1,2} & \hat{x}_{1,3} &\cdots  & \hat{x}_{1,m}\\
       \hat{x}_{2,1} & \hat{x}_{2,2} & \hat{x}_{2,3} &\cdots  & \hat{x}_{2,m}\\
       \hat{x}_{3,1} & \hat{x}_{3,2} & \hat{x}_{3,3} &\cdots  & \hat{x}_{3,m}\\
       \vdots & \vdots & \vdots &\ddots  & \vdots\\
       \hat{x}_{n,1} & \hat{x}_{n,2} & \hat{x}_{n,3} &\cdots  & \hat{x}_{n,m}\\
      \end{matrix}
      \right]_{n×m}\\
    \hat{x}_{i,j}=\frac{x_{i,j}-\bar{x}_j}{s_j}
    \]
    • 假设我们要将样本数据集分为 \(k\) 个簇。按照 2.1. 算法框架 中的伪代码思路即可得到。

    对应的 Python 代码为:

    # -*- coding: utf-8 -*-
    import matplotlib.pyplot as plt
    import numpy as np
    from numpy import random as nr
    
    
    ## k-means 类定义
    class kMeans:
        def __init__(self,X,k):
            self.k=k # 分类的簇数
            self.X=X # 数据样本
            self.size=self.X.shape[0] # 样本数量
            self.dimension=self.X.shape[1] # 单一样本维度
            self.labels=np.zeros((self.size,1), dtype = np.int) # 个体的类标签列表 0是无分类数据
            self.centerPoints=np.zeros((self.k,self.dimension), dtype = np.float) # 中心点列表
            self.diffValue=np.zeros((self.k,1), dtype = np.float) # 中心点更新差异值
            self.average=np.zeros((1,self.dimension), dtype = np.float) # 维度平均值
            self.std=np.zeros((1,self.dimension), dtype = np.float) # 维度标准差
            # 计算平均值
            for i in range(self.average.shape[1]):
                self.average[0][i]=np.mean(self.X[:,i])
            # 计算标准差
            for i in range(self.std.shape[1]):
                self.std[0][i]=np.std(X[:,i], ddof=1)        
                
        # 函数:进行 Z_Score 规范化(self,P_Z_Score):
        def Z_Score(self,P):
            P_Z_Score=np.zeros((1,self.dimension), dtype = np.float)
            for i in range(self.dimension):
                P_Z_Score[0][i]=(P[i]-self.average[0][i])/self.std[0][i]
            return P_Z_Score[0]
            
            
        # 函数:进行 Z_Score 逆规范化
        def Z_Score_Re(self,P_Z_Score):
            P=np.zeros((1,self.dimension), dtype = np.float)
            for i in range(self.dimension):
                P[0][i]=P_Z_Score[i]*self.std[0][i]+self.average[0][i]
            return P[0]
            
        # 函数:计算距离 P1=[,,,,]
        def D_calculate(self,P1,P2):
            P1=self.Z_Score(P1)
            P2=self.Z_Score(P2)
            sum=0
            for i in range(self.dimension):
                sum=sum+(P1[i]-P2[i])*(P1[i]-P2[i])
            return np.sqrt(sum)
    
        # 函数:生成随机质心
        def Generate_random_center_Point(self):
            first=nr.randint(0,self.size)
            self.centerPoints[0,:]=self.X[first,:]
            num=1
            Max=[0,0]
            current_D=0
            while True:
                Max=[0,0]
                for i in range(self.size): # 遍历所有点
                    current_D=0
                    for j in range(num): # 遍历所有中心点
                        # 计算该点到中心点距离
                        current_D=current_D+self.D_calculate(self.Z_Score(self.X[i,:]),
                                                             self.Z_Score(self.centerPoints[j,:]))
                    current_D=current_D/num
                
                    if current_D>Max[1]:# 求到所有中心点统一距离最大的点
                        Max[0]=i
                        Max[1]=current_D
                self.centerPoints[num,:]=self.X[Max[0],:]
                num=num+1
                if num==self.k:
                    break
            
        # 函数:分类函数
        def Run(self):
            self.Generate_random_center_Point()
            print("初始质心生成结束!")
            Min=[0,10]
            n=1
            # 循环更新质心
            while True:
                ## 为所有点归类
                for i in range(self.size): # 遍历所有点
                    Min=[0,10]
                    for j in range(self.k): # 遍历所有质心,计算该点到所有质心的距离
                        current_D=self.D_calculate(self.Z_Score(self.X[i,:]),
                                                   self.Z_Score(self.centerPoints[j,:]))
                        if current_D<Min[1]:
                            Min[0]=j
                            Min[1]=current_D
                    self.labels[i][0]=Min[0]+1 # 将该点归类到最近的簇中
                title='The '+str(n)+'  '+'Points Change Cluster'
                self.show_figure(title)
                #print(self.centerPoints)
                ## 制造新质心
                center_list=[]
                for i in range(self.k):
                    center_list.append([0,np.zeros((1,self.dimension), dtype = np.float)])
                for i in range(self.size): # 遍历所有点
                    center_list[self.labels[i][0]-1][1]=center_list[self.labels[i][0]-1][1]+self.X[i,:]
                    center_list[self.labels[i][0]-1][0]=center_list[self.labels[i][0]-1][0]+1
                    
                # 计算平均值
                for i in range(self.k):
                    center_list[i][1]=center_list[i][1]/center_list[i][0]
    
                ## 计算质心差异
                for i in range(self.k):
                    self.diffValue[i][0]=self.D_calculate(self.Z_Score(center_list[i][1][0,:]),
                                                          self.Z_Score(self.centerPoints[i,:]))
            
                ## 更新质心
                for i in range(self.k):
                    self.centerPoints[i,:]=center_list[i][1][0,:]
                title='The '+str(n)+'  '+'Center Points Renew'
                self.show_figure(title)
                
                
                
                
                #print(self.diffValue)
                
                n=n+1
                if np.sum(self.diffValue)==0:
                    break
            
            
    
            
            
            
        # 函数:在 figure 中显示分类情况,仅限于二维和三维数据
        def show_figure(self,title):
            if self.dimension==2:
                plt.figure(figsize=(8,8))
                X1=[] #第一维数据
                X2=[] #第二维数据
                for i in range(self.k+1):# 为列表设置k+1个簇空间,1个用来放置无类别点
                    X1.append([])
                    X2.append([])
                for i in range(self.size): # 遍历所有样本
                    X1[self.labels[:,0][i]].append(self.X[i][0])
                    X2[self.labels[:,0][i]].append(self.X[i][1])
                
                for i in range(self.k+1):
                    if i==0:
                        plt.plot(X1[i],X2[i],'o',label = 'None')
                    else:
                        plt.plot(X1[i],X2[i],'o',label = 'Cluster '+str(i))
                plt.plot(self.centerPoints[:,0],self.centerPoints[:,1],'x',color='black',label = 'Center Point')
                plt.legend(bbox_to_anchor=(1.05, 0), loc=3, borderaxespad=0)
                plt.title(title)
                plt.show()
            elif self.dimension==3:
                print('暂未开发')
            
            else:
                print("当前数据维度为 "+str(self.dimension)+",不显示非二维或三维的数据图形")
                
        # 函数:展示数据全貌
        def show_data(self):
            st='\t序号\t标签'
            for i in range(self.dimension):
                st=st+'\tD'+str(i+1)
            print(st)
            for i in range(self.size):
                st='\t'+str(i+1)+'\t'+str(self.labels[i])
                for j in range(self.dimension):
                        st=st+'\t'+str(self.X[:,j][i])
                print(st)
            
                
        
    def main():
        size=500
        x1=nr.randint(0,100,size = (size,1)) # 第一维
        x2=nr.randint(0,100,size = (size,1)) # 第一维
        X=np.hstack((x1,x2))
        # 生成 kMeans 对象
        k=7
        new_kmean=kMeans(X,k)
        #new_kmean.show_data()
        #new_kmean.show_figure()
        #new_kmean.centerPoints[:,1]
        new_kmean.show_figure('Initial')
        new_kmean.Run()
    
    
    
    
    if __name__ == '__main__':
      main()




    2019-03-18 15:09  _余沧海  阅读(639)  评论(0)  编辑  收藏  举报