概述
原型聚类是指聚类结构能通过一组原型刻画,原型是指样本空间中具有代表性的点。通常情况下,算法先对原型进行初始化,然后对原型进行迭代更新求解,下面是几种著名的原型聚类算法。
K均值算法
给定样本集合D,K均值算法针对聚类所得簇划分C,最小化平方误差
E=∑ki=1∑x∈Ci||x−μi||22
其中 μi=1|Ci|∑x∈Cix 是簇 Ci 的均值向量。直观来看,这个误差刻画了簇内样本围绕簇均值向量的紧密程度,E值越小则簇内样本相似度越高。
K均值法采用贪心策略,通过迭代近似求解上式,算法首先随机选择k个向量作为初始均值向量,然后是迭代过程,根据均值向量将样本划分到距离最近的均值向量所在的簇中,划分完成之后更新新的均值向量,直到迭代完成。
代码见最后
二分K均值法
K均值法依赖于初始均值向量的选择,导致算法可能收敛到局部最小值,一种改进的方法是二分K均值法,这里的二分不是指算法课程中的二分,而是一分为二的意思。
二分K均值法初始时将所有点看成一个簇,在簇的数量小于K的时候进行迭代,算法的核心是选择一个簇一分为二,这里一分为二的方法还是K均值法,只不过K变成了2。二分K均值依次计算每个簇一分为二后新的总平方误差,选择划分后总体平方误差最小的簇进行划分,代码见最后。
学习向量量化LVQ
与K均值法类似,学习向量量化也是试图找到一组原型向量来刻画聚类结构,但不同的是,LVQ假设数据样本带有类别信息,学习过程利用样本的这些监督信息来辅助聚类,可看作通过聚类来形成类别的子类结构,每个子类对应一个聚类簇。
给定样本集,原型向量个数,各原型向量的类别标记,学习率,
算法首先初始化一组原型向量,可以从该类别中随机选取一个向量作为原型向量。初始化完成之后,开始迭代过程,每次从样本集中选取一个样本 (x,y) ,将这个样本划分到距离它最近的原型向量 p 所在的簇中,如果这两个向量的类别标记相同,那么更新p为
p=p+η∗(x−p)
若类别标记不相同,更新p为
p=p−η∗(x−p)
直观上来看,若类别标记相同,原型向量向x的方向靠拢,否则远离。算法的停止条件是达到最大迭代轮数或者原型向量更新很小。
算法学习得到一组原型向量,对任意样本来说,它将被划分到与其最近的原型向量所代表的簇中,该划分通常称为“Voronoi”划分。
代码见最后
高斯混合聚类
与K均值,LVQ用原型向量刻画聚类结构不同,高斯混合聚类采用概率模型来表达聚类原型。
多元高斯分布的定义:对于n维样本空间中的随即向量 x ,若 x 服从高斯分布,其概率密度函数为
p(x)=1(2π)n/2|Σ|1/2e−1/2(x−μ)TΣ−1(x−μ)
可以看到,高斯分布完全由均值向量 μ 和协方差矩阵 Σ 确定,将概率密度函数记为 p(x|μ,Σ)
我们定义高斯混合分布为
pM(x)=∑ki=1αip(x|μi,Σi)
该分布共由k个混合成分构成,每个混合成分对应一个高斯分布,其中 μi,Σi 是第i个高斯混合成分的参数,而 αi>0 是相应的混合系数。
假设样本的生成过程由高斯分布给出,首先,根据 αi 定义的先验分布选择高斯混合成分,然后根据被选择的混合成分的概率密度函数进行采样。
若训练集D由上述过程生成,令随机变量 zj∈{1…k} 表示生成样本 xj 的高斯混合成分,其取值未知,显然, zj 的先验概率 P(zj=i)=αi 。根据贝叶斯定理, zj 的后验分布等于
pM(zj=i|xj)=P(zj=i)pM(xj|zj=i)pM(xj)=αipM(xj|μi,Σi)∑kl=1αlpM(xj|μl,Σl)
将这个后验概率记为 γji
当高斯混合分布已知,即所有参数都已知时,可以计算每个样本由第i个高斯混合成分生成的后验概率,取其中的最大值然后标记样本所属该簇。
因此,从原型聚类的角度来看,高斯混合聚类是采用概率模型对原型进行刻画,簇划分则由原型对应的后验概率确定。
现在的问题就是求解各混合成分的参数,这一步可以用极大似然估计。
对数似然为
LL(D)=ln(∏mj=1pM(xj))=∑mj=1ln(∑ki=1αip(xj|μi,Σi))
然后可以用EM算法进行迭代优化求解,具体见下一篇博客。
K均值法和二分K均值法代码
from numpy import *
#k均值算法
def loadData(filename):
dataMat = []
fr = open(filename)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float, curLine))
dataMat.append(fltLine)
return dataMat
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA-vecB, 2)))
def randCent(dataSet, k):
n = shape(dataSet)[1]
centroids = mat(zeros((k, n)))
for j in range(n):
minJ = min(dataSet[:, j])
rangeJ = float(max(dataSet[:, j])-minJ)
# 常数直接和数组相加结果是数组的每一项加上这个常数
centroids[:, j] = minJ + rangeJ*random.rand(k, 1)
return centroids
#k均值法
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
m, n = shape(dataSet)
centroids = createCent(dataSet, k)
# 每个样本被分到了到各类,以及到这个类质心的距离
clusterAssign = mat(-1*ones((m, 2)))
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m):
minDistance = inf; minIndex = -1
for j in range(k):
distanceIj = distMeas(dataSet[i, :], centroids[j, :])
if distanceIj < minDistance:
minDistance = distanceIj
minIndex = j
if minIndex != clusterAssign[i, 0]:
clusterChanged = True
clusterAssign[i, :] = minIndex, minDistance**2
for cent in range(k):
pointsInCluster = dataSet[nonzero(clusterAssign[:, 0] == cent)[0], :]
centroids[cent, :] = mean(pointsInCluster, axis=0)
return centroids, clusterAssign
# 二分k均值法
def biKmeans(dataSet, k, distMeas=distEclud):
m, n = shape(dataSet)
clusterAssign = mat(zeros((m, 2)))
centroid0 = mean(dataSet, axis=0).tolist()[0]
centList = [centroid0], numCents = 1
for j in range(m):
clusterAssign[j, 1] = distMeas(dataSet[j, :], mat(centroid0))**2
while numCents < k:
minSSE = inf
bestCentToSplit = -1
bestNewCents = centList
bestNewAssign = clusterAssign
for i in range(numCents):
pointsInCluster = dataSet[nonzeros(clusterAssign[:, 0] == i)[0], :]
splitCent, splitClusterAssign = kMeans(pointsInCluster, 2)
splitSSE = sum(splitClusterAssign[:, 1])
noSplitSSE = sum(clusterAssign[nonzeros(cluster[:, 0] != i)[0], 1])
currentSSE = splitSSE + noSplitSSE
if currentSSE < minSSE:
minSSE = currentSSE
bestCentToSplit = i
bestNewCents = splitCent
bestNewAssign = splitClusterAssign
# 更新簇的分配结果,将新得到的两个质点一个序号设为bestCentsToSplit,另一个序号设为numCents
centList[bestCentToSplit] = bestNewCents[0, :]
centList.append(bestNewCents[1, :])
bestNewAssign[nonzeros(bestNewAssign[:, 0] == 0)[0], 0] = bestCentToSplit
bestNewAssign[nonzeros(bestNewAssign[:, 0] == 1)[0], 0] = numCents
clusterAssign[nonzeros(clusterAssign[:, 0] == bestCentToSplit)[0], :] = bestNewAssign
numCents += 1
return mat(centList), clusterAssign
dataMat = mat(loadData('testSet2.txt'))
centroids, clusterAssign = kMeans(dataMat, 3)
print(centroids)
LVQ算法代码
from numpy import *
# LVQ算法
def loadData(filename):
dataSet = []
fr = open(filename)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float, curLine))
dataSet.append(fltLine)
return dataSet
def randomSelect(dataMat):
m = shape(dataMat)[0]
return dataMat[random.randint(0, m-1), :]
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA-vecB, 2)))
def LVQ(dataMat, q, labels, maxIter=500, eta=0.1, distMeas = distEclud):
m, n = shape(dataMat)
centroids = mat(zeros((q, n)))
# 初始化原型向量
for i in range(q):
curClassDataMat = dataMat[nonzero(dataMat[:, -1] == labels[i])[0], :]
centroids[i, :] = randomSelect(curClassDataMat)
for i in range(maxIter):
# 随机选择一个样本
sample = randomSelect(dataMat)
bestIndex = -1
minDistance = inf
# 选择距离这个样本最近的原型向量
for j in range(q):
curDistance = distMeas(sample[0, :n-1], centroids[j, :n-1])
if curDistance < minDistance:
minDistance = curDistance
bestIndex = j
# 若样本和这个原型向量类别相同
if sample[0, -1] == centroids[bestIndex, -1]:
centroids[bestIndex, :n-1] += eta*(sample[0, :n-1]-centroids[bestIndex, :n-1])
# 类别不同
else:
centroids[bestIndex, :n-1] -= eta*(sample[0, :n-1]-centroids[bestIndex, :n-1])
return centroids
dataMat = mat(loadData('waterMelon4.txt'))
centroids = LVQ(dataMat, 5, [1, 2, 2, 1, 1], 1000)
print(centroids)