简介
关于算法的原理,大家可以看这篇博客:聚类分析之模糊C均值算法核心思想,注意博客中有关隶属度计算的公式有点小问题,这里更正如下:
W
i
j
=
(
1
/
dist
(
x
i
,
c
j
)
2
)
1
p
−
1
/
∑
q
=
1
k
(
1
/
dist
(
x
i
,
c
q
)
2
)
1
p
−
1
W _ { i j } = ( 1 / \operatorname { dist } ( x _ { i } , c _ { j } ) ^ { 2 } ) ^ { \frac { 1 } { p - 1 } } / \sum _ { q = 1 } ^ { k } ( 1 / \operatorname { dist } ( x _ { i } , c _ { q } ) ^ { 2 )\frac { 1 } { p - 1 } }
Wij=(1/dist(xi,cj)2)p−11/q=1∑k(1/dist(xi,cq)2)p−11
K均值聚类与模糊C均值聚类
关于这两种算法的优劣好坏,网上教程大都是从数学的角度来进行解释的,对于我这种非数学专业的人来说,显得晦涩难懂,很难理解,所以这里我从一个非数学专业、数学知识有限的菜鸟的角度触发,记录一下我对模糊C均值算法的理解,当然不是为了说服各位看官,主要是为了说服我自己,不要在纠结这个问题:
K均值聚类在迭代聚类的过程中,对于某一个样本点属于哪一类是确定的,与哪一个聚类中心最近就属于哪一类,而模糊C均值聚类在迭代聚类的过程中,对于某一个样本点属于哪一类是不确定的,而是用了一个隶属度指标来衡量该样本点属于某一类别可能性,因此对某一样本点,其对应各类的隶属度之和为1。也就是说,在迭代聚类的过程中,该样本点可以属于任何一类,反过来讲就是任意一个聚类簇都包含了全体样本,只不过该样本附带了隶属度标签。直到迭代结束后,根据某一个样本对应各类的隶属度大小,取其最大值对应的类作为该样本的最终类别,也就是硬化过程。
从数学角度可能需要很深奥的知识来论证这两种算法孰优孰劣,但从现实世界的情况来看,模糊C均值算法要更符合客观实际。可以把迭代聚类的过程比喻为警察办案,在最终锁定凶手之前,每一个怀疑对象都有可能是凶手,警察需要不断地搜集更多的证据,来不断优化每一个怀疑对象属于凶手的隶属度,直到确定最后的真凶,在此之前不能贸然的认定任何一位怀疑对象就是凶手,否则就是冤假错案,在模糊C均值这里就错分。
代码逻辑
代码及结果
源代码
from sklearn import datasets
from sklearn import metrics
import numpy as np
import matplotlib.pyplot as plt
class FCM(object):
# data 原始数据
# c 类别数
# p 模糊权重
# k 最大迭代次数
# t 聚类中心变化阈值
def __init__(self,data,c,p,k,t) -> None:
super().__init__()
self.data=data
self.c=c
self.p=p
self.k=k
self.t=t
def classify(self):
nrow,ncol=self.data.shape
# 初始化隶属度
W=np.zeros((nrow,self.c))
for i in range(nrow):
for j in range(self.c):
W[i,j]=np.random.rand()
# 隶属度之和归一化
W=np.transpose(W.transpose()/np.sum(W,axis=1))
# 初始化聚类中心
C=np.zeros((self.c,ncol))
for i in range(self.c):
for j in range(nrow):
C[i]+=W[j,i]*self.data[j,:]
C[i]=C[i]/np.sum(W[:,i])
k=0 #当前迭代次数
t=self.t*2 #聚类中心变化初始值
while k<self.k and t>self.t:
# 更新隶属度
for i in range(nrow):
# 计算样本到每一个聚类中心的距离
dists=list()
for j in range(self.c):
dist=np.sum((self.data[i,:]-C[j,:])**2)
dists.append(dist)
# 计算隶属度
dists=np.array(dists)
dists=np.power(dists,1/(1-self.p))
dists=dists/np.sum(dists)
W[i,:]=dists
# 更心聚类中心
Ct=np.zeros((self.c,ncol))
for i in range(self.c):
for j in range(nrow):
Ct[i]+=self.data[j,:]*W[j,i]
Ct[i]=Ct[i]/np.sum(W[:,i])
t=np.max(np.abs(Ct-C))
k+=1
C=Ct
# 硬化,确定最终的聚类结果
result=np.zeros(nrow)
for i in range(nrow):
w=W[i,:]
wMax=np.max(w)
pos=np.where(w==wMax)
result[i]=pos[0][0]
self.result=result
return k
# 加载鸢尾花数据集 算法测试
data=datasets.load_iris().data
target=datasets.load_iris().target
fcm=FCM(data,3,2,1000,0.001)
k=fcm.classify()
print('最终迭代次数为{}'.format(k))
result=fcm.result
print(metrics.confusion_matrix(target,result))
print(metrics.classification_report(target,result))
# 运用PCA主成分分析对数据进行可视化处理
u,s,v=np.linalg.svd(data)
data=np.dot(data,v)[:,:2]
plt.figure(figsize=(20,10))
ax1=plt.subplot(1,2,1)
plt.scatter(data[target==0,0],data[target==0,1],marker='+')
plt.scatter(data[target==1,0],data[target==1,1],marker='o')
plt.scatter(data[target==2,0],data[target==2,1],marker='>')
ax2=plt.subplot(1,2,2)
plt.scatter(data[result==0,0],data[result==0,1],marker='+')
plt.scatter(data[result==1,0],data[result==1,1],marker='o')
plt.scatter(data[result==2,0],data[result==2,1],marker='>')
plt.show()
注意输出的类别编码与鸢尾花数据集本来的类别编码顺序可能不一致,导致输出的混淆矩阵看起来怪怪的,不过不影响算法本身分类的准确性,大家在看的时候,稍微注意一下就好了。此外在进行数据可视化的时候,首先运用了主成分分析对数据进行了降为处理,取了前两个维度,这样做主要是为了是的不同类别的鸢尾花在进行可视化时区别更加明显,不会对分类的过程及最终结果产生影响。
分类结果
类别数c=3,模糊参数p=2,最大迭代次数k=1000,聚类中心变化阈值t=0.001,经过30次迭代后,结果如下图所示:
左图为鸢尾花真实类别,有图为模糊C均值聚类结果,可见聚类精度还是比较高的。