【人工智能】4 聚类算法
聚类是一种无监督学习。无监督学习指的是,在我们缺乏足够的先验知识,难以人工标注类别的情况下,借助计算机来进行自动分类。
1. 聚类算法的分类
聚类是将数据对象的集合分成相似的对象类的过程,使得同一个簇中的个体间具有较高的相似性,不同簇间的对象具有较高的相异性。按照聚类的尺度,聚类算法可以被分为:
- 基于 距离 的聚类算法
- 基于 密度 的聚类算法
- 基于 互联性 的聚类算法
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()