前言
K_mean聚类可以算是聚类算法里面比较有代表性的算法之一了。其通俗易懂的原理和较好的效果让其被人们所认可接受。本文将简单的介绍一下原理和实现过程+代码的实现,并不适合想看严谨的介绍的读者。
原理
K_mean
基本理解
要说到k_mean 算法。当燃从其名字输入。这里的分成两个。一个是k,另一个是mean。
①k:选定有k个簇(类)。
②mean:指某一个簇中的均值(质心)。
算法流程
①对训练数据进行标准化,去异常值。
②给定一个k值,也就是选定要聚成几类,然后在训练数据中随机选取k个训练数据,将其作为k个均值点(质心)。
③对训练数据中每一个数据都与每一个均值点计算距离。每一个数据与某一个均值点最近,就认定其属于该均值点的簇。
④对得到的每一个簇,计算簇中的所有点的均值作为新的均值点(质心)。
⑤训练③、④步,直到均值点收敛不再改变。
两个小问题
①问题1:对于一些数据,我们不知道该聚成几类。也就是初始值k不知道如何确定。可以选择最小化损失函数来解决。也就是:
L
=
∑
j
=
1
m
∣
∣
x
j
−
μ
∣
∣
2
L=\sum\limits_{j=1}^m{||x_j-\mu||_2}
L=j=1∑m∣∣xj−μ∣∣2
上面的式子代表的就是某一个簇中的损失。其中,下面的2表示L2范数。我们就是要选定不同的k,来让这个损失函数最小便可。
②问题2:实际上,k_mean的随机初始化值的方式如果初始化的两个值位置很相近,会导致模型收敛的速度变慢。也会影响最终的模型聚类结果。其中,一个解决方案是k_mean++,它是k_mean的改进算法。就是为了解决这个问题的。
k_mean++
算法流程
①对训练数据进行标san准化,去异常值。
②给定一个k值,也就是选定要聚成几类,然后在训练数据中随机选取1个训练数据作为一个质心
③计算每一个点到已有的簇的距离,然后选取最小距离。最后将所有最小距离除以所有最小距离的总和,转化为概率,以该概率在原始数据中继续抽取1个质心。反复迭代③,直到抽取到k个质心。
④对训练数据中每一个数据都与每一个均值点计算距离。每一个数据与某一个均值点最近,就认定其属于该均值点的簇。
⑤对得到的每一个簇,计算簇中的所有点的均值作为新的均值点(质心)。
⑥训练③、④步,直到均值点收敛不再改变。
代码实现
k_mean
训练过程图如下所示。可以看到质心是一点点往各自的簇中心移动的。ps:代码使用欧氏距离
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import imageio
plt.ion()
images=[]
class K_Mean():
def __init__(self,x):
self.x=x
self.mean = np.mean(x, axis=0)
self.std = np.std(x, axis=0)
self.k_data=0
def fit(self,k):#k是簇
self.x=(self.x-self.mean)/self.std#标准化
#随机选取k个值(质心)
index=np.random.choice(self.x.shape[0],k,replace=False)
k_data=self.x[index]
self.k_data=k_data.copy()#为了将数据保存到模型中,预测的时候好调用。
distances=np.empty(shape=(self.x.shape[0],k))#对每一个k(质心)的值计算他和其他点的距离,结果按列保存到distances中
while True:#作循环,直到模型收敛
for j,each_k in enumerate(k_data):
#对每一个数据减去均值点然后求平方和,实际上就是欧氏距离
result=self.x-each_k
distance=np.linalg.norm(result,ord=2,axis=1).reshape(-1)
distances[:,j]=distance#将结果保存到distances中
#计算数据点和哪个簇近
a=np.argmin(distances,axis=1)
#对每一个簇重新计算均值点并更新
for each in range(k):
mean=np.mean(self.x[a==each],axis=0)
k_data[each,:]=mean
plot_figure(self.x, a,self.k_data)#绘图函数,与模型无关
if (self.k_data==k_data).all():#判定模型是否收敛,收敛则打破循环
#以下都是绘图和保存相关,与模型无关
imageio.mimsave("result.gif", images, duration=0.3)
plt.ioff()
plt.show()
#结束循环
break
else:
self.k_data=k_data.copy()
#绘图
plt.pause(0.5)
def predict(self,x): #预测函数,与fit函数差不多同理
x=(x-self.mean)/self.std #标准化
result=np.ones(shape=(x.shape[0],1),dtype=int) #生成用于储存结果的数组
for index,i in enumerate(x):
L2=np.linalg.norm(self.k_data-i,ord=2,axis=1)
classify=np.argmin(L2)
result[index,:]=classify
return result
def plot_figure(x,y,k_data):
'''绘图函数,与模型无关'''
map_color={0:"r",1:"g",2:"b"}
color_initial=[map_color[i] for i in y.squeeze()]
plt.cla()
plt.scatter(x[:,0],x[:,1],c=color_initial)
plt.scatter(k_data[:, 0], k_data[:, 1], c="y", marker="*")
plt.savefig("1.png")
images.append(imageio.v3.imread("1.png"))
plt.show()
def main():
#生成第一类数据g
x1=stats.norm.rvs(0,2,(100,2))
#生成第二类数据r
x2=stats.norm.rvs(6,2,(100,2))
#生成第三类数据b
x3=stats.norm.rvs(12,2,(100,2))
#合并数据
x=np.concatenate([x1,x2,x3],axis=0)
#初始化并训练模型参数
model=K_Mean(x)
model.fit(3)
#预测结果
result=model.predict(x2)
if __name__ == '__main__':
main()
k_mean++
可以看到k_mean++算法明显比k普通的能够更快收敛一些:
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import imageio
plt.ion()
images=[]
class K_Mean():
def __init__(self,x):
self.x=x
self.mean = np.mean(x, axis=0)
self.std = np.std(x, axis=0)
self.k_data=0
def fit(self,k):#k是簇
self.x=(self.x-self.mean)/self.std#标准化
#随机选取1个中心点(质心)
k_data= self.x[np.random.choice(self.x.shape[0], 1, replace=False)]
#dis用于储存距离
dis = np.empty(shape=(self.x.shape[0]), dtype=int)
# 循环选取其余的k均值点:
for j in range(1,k):
#循环每一个样本点,计算它们与最短均值点的距离并保存
for i in range(self.x.shape[0]):
result=k_data-self.x[i,:]
L2=np.linalg.norm(result,ord=2,axis=1)
dis[i]=np.min(L2)
#计算概率
p=dis/dis.sum()
#依据概率选取均值点
new_k_data=self.x[np.random.choice(self.x.shape[0],1,p=p)]
k_data=np.insert(k_data,j,new_k_data,axis=0)
self.k_data=k_data.copy()#为了将数据保存到模型中,预测的时候好调用。
distances=np.empty(shape=(self.x.shape[0],k))#对每一个k(质心)的值计算他和其他点的距离,结果按列保存到distances中
while True:#作循环,直到模型收敛
for index,each_k in enumerate(k_data):
#每一个样本与均值都做差然后求平方和,实际上就是欧氏距离
result=self.x-each_k
distance=np.linalg.norm(result,ord=2,axis=1).reshape(-1)
distances[:,index]=distance#将结果保存到distances中
#计算数据点和哪个簇近
a=np.argmin(distances,axis=1)
#对每一个簇重新计算均值点并更新
for each in range(k):
mean=np.mean(self.x[a==each],axis=0)
k_data[each,:]=mean
plot_figure(self.x, a,self.k_data)#绘图函数,与模型无关
if (self.k_data==k_data).all():#判定模型是否收敛,收敛则打破循环
#以下都是绘图和保存相关,与模型无关
imageio.mimsave("result1.gif", images, duration=0.3)
plt.ioff()
plt.show()
#结束循环
break
else:
self.k_data=k_data.copy()
#绘图
plt.pause(0.5)
def predict(self,x): #预测函数,与fit函数差不多同理
x=(x-self.mean)/self.std #标准化
result=np.ones(shape=(x.shape[0],1),dtype=int) #生成用于储存结果的数组
for index,i in enumerate(x):
L2=np.linalg.norm(self.k_data-i,ord=2,axis=1)
classify=np.argmin(L2)
result[index,:]=classify
return result
def plot_figure(x,y,k_data):
'''绘图函数,与模型无关'''
map_color={0:"r",1:"g",2:"b"}
color_initial=[map_color[i] for i in y.squeeze()]
plt.cla()
plt.scatter(x[:,0],x[:,1],c=color_initial)
plt.scatter(k_data[:, 0], k_data[:, 1], c="y", marker="*")
plt.savefig("1.png")
images.append(imageio.v3.imread("1.png"))
plt.show()
def main():
#生成第一类数据g
x1=stats.norm.rvs(0,2,(100,2))
#生成第二类数据r
x2=stats.norm.rvs(6,2,(100,2))
#生成第三类数据b
x3=stats.norm.rvs(12,2,(100,2))
#合并数据
x=np.concatenate([x1,x2,x3],axis=0)
#初始化并训练模型参数
model=K_Mean(x)
model.fit(3)
#预测结果
result=model.predict(x2)
if __name__ == '__main__':
main()
结束语
以上就是k_mean算法的简单介绍和代码实现啦。过程并不严谨,如有错误,还请指正,阿里嘎多。