(1) 算法思路:
二分 k-means 算法,此算法不需要标签变量,在 k-means 算法的基础上需要通过四个特征变量将 Iris 进行聚类。目标:通过 Iris 的四个特征值进行聚类,得到每个聚类中的质心,并把聚类结果写入文件中。
(2) 算法原理基础:
在原理上跟 k-means 上差不多相同。
(3) 算法步骤:
把整个数据集看成一个簇,计算质心
将这个簇分成两个簇
选择满足条件的可以分解的簇,选择条件为簇元素的个数和 SSE 大小
使用 k-mean 算法将可分裂的簇分成两个簇
重复(2)(3)步,直到满足 k 值
(4)代码
from numpy import *
from matplotlib import pyplot as plt
def load_data_set(fileName):
dataSet = []
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split(',')
fltLine = list(map(float, curLine))#将列表里面的字符串变成float
dataSet.append(fltLine)
return dataSet
def distance_euclidean(vector1, vector2):
return sqrt(sum(power(vector1-vector2, 2)))
def rand_center(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
def k_means(dataSet, k, distMeas = distance_euclidean, creatCent = rand_center):
m = shape(dataSet)[0] # 行数
# 建立簇分配结果矩阵,第一列存放该数据所属中心点,第二列是该数据到中心点的距离
clusterAssment = mat(zeros((m, 2)))
centroids = creatCent(dataSet, k) # 质心,即聚类点
# 用来判定聚类是否收敛
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m): # 把每一个数据划分到离他最近的中心点
minDist = inf # 无穷大
minIndex = -1 #初始化
for j in range(k):
# 计算各点与新的聚类中心的距离
distJI = distMeas(centroids[j,:],dataSet[i,:])
if distJI < minDist:
# 如果第i个数据点到第j中心点更近,则将i归属为j
minDist = distJI
minIndex = j
# 如果分配发生变化,则需要继续迭代
if clusterAssment[i, 0] != minIndex:
clusterChanged = True
# 并将第i个数据点的分配情况存入字典
clusterAssment[i, :] = minIndex, minDist**2
for cent in range(k): # 重新计算中心点
# 去第一列等于cent的所有列
ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]
# 算出这些数据的中心点
centroids[cent, :] = mean(ptsInClust, axis=0)
return centroids, clusterAssment
def biKmeans(dataMat, k, distMeas=distance_euclidean):
"""二分k-means算法"""
m = shape(dataMat)[0] #获得数据集的样本数
clusterAssment = mat(zeros((m, 2))) #初始化一个元素全为0的(m,2)的矩阵
centroid0 = mean(dataMat, axis=0).tolist()[0] #获取数据每一列的均值,组成一个一维数组
centList = [centroid0] # 用一个列表来保留所有的质心
for j in range(m): #遍历数据中的每个数据集样本
# 计算当前聚类为一类时各个数据点距离质心的平方距离
clusterAssment[j, 1] = distMeas(mat(centroid0), dataMat[j, :]) ** 2
while (len(centList) < k):#循环,直到达到k类
lowestSSE = inf #将当前最小误差设置为正无穷大
for i in range(len(centList)):#遍历每个聚类
# 因此首先先比较clusterAssment[:,0].A==cent的真假,如果为真则记录了他所在的行,因此在用切片进行取值
ptsInCurrCluster = dataMat[nonzero(clusterAssment[:, 0].A == i)[0], :]
# 对该类利用二分k-均值算法进行划分,返回划分后结果,及误差
centroidMat, splitClustAss = k_means(ptsInCurrCluster, 2, distMeas)
# 划分数据的SSE与未划分的之和作为本次划分的总误差
sseSplit = sum(splitClustAss[:, 1]) #计算该划分后两个类的误差平方和
sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:, 0].A != i)[0], 1]) #计算数据集中不属于该类的数据的误差平方和
#划分第i类后总误差小于当前最小总误差
if (sseSplit + sseNotSplit) < lowestSSE:
bestCentToSplit = i # 第i类作为本次划分类
bestNewCents = centroidMat # 第i类划分后得到的两个质心向量
bestClustAss = splitClustAss.copy() # 复制第i类中数据点的聚类结果即误差值
lowestSSE = sseSplit + sseNotSplit # 将划分第i类后的总误差作为当前最小误差
# 数组过滤筛选出本次2-均值聚类划分后类编号为1数据点,将这些数据点类编号变为1
# 当前类个数+1,作为新的一个聚类
bestClustAss[nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)
# 同理,将划分数据集中类编号为0的数据点的类编号仍置为被划分的类编号,使类编号
# 连续不出现空缺
bestClustAss[nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSplit
# 更新质心列表中的变化后的质心向量
centList[bestCentToSplit] = bestNewCents[0, :].tolist()[0]
# 添加新的类的质心向量
centList.append(bestNewCents[1, :].tolist()[0])
# 重新分配最好簇下的数据(质心)以及SSE
clusterAssment[nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0], :] = bestClustAss
# 返回聚类结果
return mat(centList), clusterAssment
# 测试
datMat = mat(load_data_set('testSet.txt'))
centList, clusterAssment = biKmeans(datMat, 4)
print("质心结果:", centList)
print("聚类结果:", clusterAssment)
# 可视化
plt.scatter(array(datMat)[:, 0], array(datMat)[:, 1], c=array(clusterAssment)[:, 0].T)
plt.scatter(centList[:, 0].tolist(), centList[:, 1].tolist(), c="r")
plt.show()