用C均值聚类算法在Excel中轻松处理和分类数据(详解+Python代码示例)
模式识别学习,课程实例分享。
文章目录
- 第一,实验步骤描述
- 第二,聚类数变化对聚类性能的影响
- 第三,不同初始聚类中心对分类速度的影响
- 第四,代码实现
第一,实验步骤描述
本次实验主要使用208个学生的身高、体重、50m成绩数据作为基础,使用C均值算法对这些三维特征点进行聚类。其中,C均值算法聚类准则是以最小欧式距离方法,将每个点分配到最近的聚类中来实现的。
本节主要分成两个测试小节来描述C均值算法的特性,并用三维图像模型对聚类效果进行直观展示。第一,将目标聚类数c从0至20进行变化时,观测每个聚类数c对应的最后稳定准则函数值J变化趋势。第二,介绍并对比两种不同的初始聚类中心点选取方法,对准则函数值J收敛速度的影响。
第二,聚类数变化对聚类性能的影响
本次测试方法为:对208个学生的身高、体重、50m成绩三维特征点按照c个随机分布聚类中心点进行聚类,其中c的变化范围为1至20,
观察准则函数值J即最后各个聚类集方差的收敛程度和变化趋势(聚类数值c的变化范围应该是1至208,但是在实际测试中准则函数值J从c=20开始,
随着c的增长J值的变化很小,所以在此将不讨论c>20后的J值变化情况)。
由上述测试方法,可以得到上表一的测试数据和图1的准则函数值J遂聚类数c增加而变化趋势图。由数据可以看出c∈[1,16]范围时,J值迅速收敛,即当将数据特征点分别分为1类、2类……16类时,各个类别里面的特征点方差快速减小,这些类别的点也越集中。可以预见,当c∈[17,208]时,J值减小速度明显放缓,乃至忽略。
由表一数据结合图1曲线,当将所有特征点数据分成6类时,即c=6、J=0.83时,为聚类最优选择。因为随着将特征点分的类别越多,每个聚类里面能够分到的特征点必定减少即方差变小但运算时间和成本也会增多,当将有限特征点分成足够多的类时每个类都会近似为每个特征点本身失去实际分类意义,所以将数据分为6类时最能够兼顾分类效果和运算成本的分类方法,其具体分类效果如上图2所示。
第三,不同初始聚类中心对分类速度的影响
由图2结合图3所示的Result聚类效果进行对比,不难得出初始聚类中心的选取对最终聚类结果有很大影响的结论。此外,在实践过程中我们发现,不同的初始聚类中心除了会影响最终聚类结果以外,还会影响准则函数值J的稳定速度。为此,除了随机选取特征点作为聚类中心的方法外,我们还设计了另外一种初始聚类中心点选取方法进行比较。
主特征值聚类中心选取法,对由208个人每人3个特征数据的208×3矩阵提取特征值,选取其中最大特征值所在的矩阵列数据(即3个特征中某个特征的208个数据)作由大到小排序得到208×1的数据M。此时,若需要N个初始聚类中心,则需对M进行N-1等分,再将各等份的边界点作为初始聚类中心点即可。
测试方法:对随机取点法和主特征取点法分别做10次6分类的测试,每次测试会得到该次分类准则函数值J从减小到稳定不变的循环调整次数,并
记录该次测试J稳定时的值。最后分别得到两种方法的J稳定平衡环调整次数与J稳定平均值。
对表二进行结果分析可以得到如下结论。特征法比随机法运算速度更快更稳定,但是随机法的平均聚类效果比特征法要好,即平均J值更小。
对于主特征取点法,由于在不改变原始数据矩阵的前提下,其主特征值所对应特征列是不变的,所以如果不改变选取点的个数,无论运行多少次
都会得到唯一的初始聚类中心选择结果。对于身高、体重、50m成绩特征下的6分类结果,其准则函数值J为0.89,通过循环10次即可使J保持不变。
对于随机取点法,由于聚类中心点时随机选取的,所以有概率循环最低的次数得到最小的J,使得算法可以用最快的速度得到最紧凑的分类效果,如第4次测试时,仅循环4次即可使J值稳定且达到最小的0.8。可是该随机方法也会带来计算速度不稳定或者陷入到最差解情况的问题。
第四,代码实现
* 测试
import numpy as np
import os
import openpyxl
import random
import matplotlib.pyplot as plt
#获取N个随机分类中心
def C_Center(Data, Lable, N, Func):
Center_Data = []
Center_Lable = []
Center_Position = []
Lenth = np.shape(Data)[0]
if Func:
Ranlist = random.sample(range(0, Lenth), N)
Center_Position = Ranlist
for i in range(N):
Center_Data.append(Data[Ranlist[i]])
Center_Lable.append(Lable[Ranlist[i]])
return Center_Data, Center_Lable, Center_Position
#计算两点的欧氏距离
def Eucldist(coords1, coords2):
coords1 = np.array(coords1)
coords2 = np.array(coords2)
err = coords1 - coords2
return np.sqrt(np.sum((coords1 - coords2)**2))
#更新各个点到中心点的欧氏距离
def CentDis(Center, Data, N):
Long = np.shape(Data)[0]
CenterDis = [[0 for i in range(N)] for i in range(Long)]
for i in range(Long):
for j in range(N):
CenterDis[i][j] = Eucldist(Data[i],Center[j])
return CenterDis
#得到列表中最小值对应的序号
def MinOlder(Data):
return sorted(range(len(Data)), key=lambda k: Data[k], reverse=True)[len(Data) - 1]
def MaxOlder(Data):
return sorted(range(len(Data)), key=lambda k: Data[k], reverse=True)[1]
#计算类样本点平均值
def ClassMean(Data, ClassNum):
Mean = [[0 for i in range(np.shape(Data[0])[1])] for i in range(ClassNum)]
for i in range(ClassNum):
m, n = np.shape(Data[i])
for j in range(n):
sum = 0
for k in range(m):
sum = sum + Data[i][k][j]
Mean[i][j] = sum/m
return Mean
# 画出 特征点
def plot_dataset(X, y, V, str1, str2, str3):
show = plt.figure().add_subplot(111, projection='3d')
PointShape = ["^", "o", "+", "*", "p", "x", "s", "h"]
PointColor = ['red', 'green', 'blue', 'firebrick', 'black', 'sage', 'deeppink', 'orange']
if V > 8:
V = 8
# 生成散点图
for i in range(V):
show.scatter(X[:, 0][y == i], X[:, 1][y == i], X[:, 2][y == i], \
c=PointColor[i], marker=PointShape[i])
# 设置坐标轴名称
show.set_xlabel(str1)
show.set_ylabel(str2)
show.set_zlabel(str3)
def PltShow(Data, Center_Num):
ShowData = []
ShowLabel = []
for i in range(Center_Num):
for j in range(np.shape(Data[i])[0]):
ShowData.append(Data[i][j])
ShowLabel.append(i)
plot_dataset(np.array(ShowData), np.array(ShowLabel), 4, "X", "Y", "Z")
def main():
Data_Num = 10 # 提取 208 个人信息
Data = [[163, 51, 7.5],
[171, 64, 7.5],
[182, 68, 7.8],
[172, 66, 8.2],
[185, 80, 8.5],
[164, 47, 9 ],
[160, 46, 9 ],
[170, 46, 7 ],
[178, 60, 7 ],
[180, 71, 7.5]]
Lable = [1,1,1,1,1,0,0,1,1,1]
# 设聚类中心4个
Center_Num = 2
# 设定不同类别的点池
ClassData = [[] for i in range(Center_Num)]
# 使用方法0,得到初始聚类中心4个
Center_Data, Center_Lable, Center_Position = C_Center(Data, Lable, Center_Num, 1)
Distance = CentDis(Center_Data, Data, Center_Num) # 更新各点与新中心的距离
MinDistance = [MinOlder(Distance[i]) for i in range(Data_Num)]#得到每点所属类别
# 将每个点分到所属的类里面
for i in range(Data_Num):
ClassData[MinDistance[i]].append(Data[i])
print(random.randint(0,10))
PltShow(ClassData, Center_Num)
#plt.show()
if __name__ == '__main__':
main()
* 全部
import numpy as np
import os
import openpyxl
import random
import matplotlib.pyplot as plt
#输入表格名file_data
# 要读该列的行数从1到row_end
def ReadInData(file_data, LableClassNum, ClassNum, row_end):
LableClassNum = LableClassNum+1
row_end = row_end+2
Data = [[i for i in range(len(ClassNum))] for i in range(row_end-2)]
Lable = []
ClassLenth = len(ClassNum)
Data = np.array(Data, dtype='float32')
for i in range(2, row_end):
Lable.append(file_data.cell(i, LableClassNum).value)
for j in range(ClassLenth):
Data[i-2][j] = file_data.cell(i,ClassNum[j]+1).value
# 读入数据归一化处理
Mean = []
for i in range(row_end - 2):
sum = 0
k = 0
for j in range(ClassLenth):
sum = sum + Data[i][j]
k = k + 1
Mean.append(sum / k)
for i in range(row_end - 2):
for j in range(ClassLenth):
Data[i][j] = Data[i][j] / Mean[j]
return Data,np.array(Lable)
# Func = 1 获取N个随机分类中心
# Func = 0 以样本矩阵主特征值所在列对应的特征作为一维轴,将数值由大到小排序,进行N等分得到N+1个中心点
def C_Center(Data, Lable, N, Func):
Center_Data = []
Center_Lable = []
Center_Position = []
Lenth = np.shape(Data)[0]
SufN = np.shape(Data)[1]
if Func:
Ranlist = random.sample(range(0, Lenth), N)
Center_Position = Ranlist
for i in range(N):
Center_Data.append(Data[Ranlist[i]].tolist())
Center_Lable.append(Lable[Ranlist[i]].tolist())
else:
Data_Scale = np.zeros((Lenth, SufN))
for i in range(SufN):
SufN_Max = np.max(Data[:, i])
SufN_Min = np.min(Data[:, i])
SufN_Mean = np.mean(Data[:, i])
for j in range(Lenth):
Data_Scale[j][i] = round((Data[j][i] - SufN_Mean) / (SufN_Max - SufN_Min) + 0.5, 1)
EigValue, EigVector = np.linalg.eig(np.cov(np.transpose(Data_Scale)))
Max_EigValue = sorted(range(len(EigValue)), key=lambda k: EigValue[k], reverse=True)
Center = Data_Scale[:, Max_EigValue[0]]
Max_CenterData = sorted(range(len(Center)), key=lambda k: Center[k], reverse=True)
if N-1 == 0:
Scale = 0
else:
Scale = len(Max_CenterData)/(N-1)
Temp = 0
for i in range(N):
if i <1:
where = Max_CenterData[0]
else:
where = Max_CenterData[int(Temp)-1]
Center_Position.append(where)
Center_Data.append(Data[where].tolist())
Center_Lable.append(Lable[where])
if i<N-1:
Temp = np.ceil(Temp+Scale)
else:
Temp = Scale*(N-1)-1
return Center_Data, Center_Lable, Center_Position
#计算两点的欧氏距离
def Eucldist(coords1, coords2):
coords1 = np.array(coords1)
coords2 = np.array(coords2)
return np.sqrt(np.sum((coords1 - coords2)**2))
#更新各个点到中心点的欧氏距离
def CentDis(Center, Data, N):
Long = np.shape(Data)[0]
CenterDis = [[0 for i in range(N)] for i in range(Long)]
for i in range(Long):
for j in range(N):
CenterDis[i][j] = Eucldist(Data[i],Center[j])
return CenterDis
#得到列表中最小值对应的序号
def MinOlder(Data):
Length = len(Data)
if Length <= 1:
return 0
return sorted(range(Length), key=lambda k: Data[k], reverse=True)[Length - 1]
#得到列表中随机序号
def RandOlder(Data):
Length = len(Data)
if Length <= 1:
return 0
return random.randint(0,Length-1)
#计算类样本点平均值
def ClassMean(Data, ClassNum):
Mean = [[0 for i in range(np.shape(Data[0])[1])] for i in range(ClassNum)]
for i in range(ClassNum):
if np.shape(Data[i])[0] == 0:
Mean[i][0] = 0
continue
m, n = np.shape(Data[i])
for j in range(n):
sum = 0
for k in range(m):
sum = sum + Data[i][k][j]
Mean[i][j] = sum/m
return Mean
# 更新准则函数的值,看是否继续缩小
def Je(ClassData,Center_Num,NewCenter):
sum2 = 0
for i in range(Center_Num):
Class = ClassData[i]
sum1 = 0
for j in range(np.shape(Class)[0]):
sum1 = sum1 + pow(np.array(Class[j])-np.array(NewCenter[i]),2)
sum2 = sum2 + sum1
return sum(sum2)
# 计算每一点与其他类别中心的距离,如果比该点和原类别中心的距离短,
# 则移动该点至新的类别
def PointMoving(ClassData,Center_Num,Center):
for i in range(Center_Num):
for j in range(np.shape(ClassData[i])[0]):
if j < np.shape(ClassData[i])[0]:
ClassOrder = [i for i in range(Center_Num)]
ClassOrder.remove(i) # 得到除特征点所属类别之外的类别序号
Nk = np.shape(ClassData[i])[0] # 原类别特征值个数
# 重新计算原类别代价函数
Pk = 0
if Nk - 1 == 0:
Pk = 0
else:
F = sum(pow(np.array(ClassData[i][j]) - np.array(Center[i]), 2)) # 计算二范数
Pk = Nk / (Nk - 1) * F
Pj = [i for i in range(Center_Num - 1)]
for k in range(Center_Num-1):
Nj = np.shape(ClassData[ClassOrder[k]])[0] #新类别特征值个数
F = sum(pow(np.array(ClassData[i][j]) - np.array(Center[ClassOrder[k]]), 2)) # 计算二范数
#重新计算新类别代价函数
Pj[k] = Nj/(Nj+1)*F
# 得到该特征点除原类别外,在移动该点后与其他最近的类别是哪个即Pj最小
PjMinNum = MinOlder(Pj)
MiniPj = ClassOrder[PjMinNum]
if Pj[PjMinNum] < Pk:
ClassData[MiniPj].append(ClassData[i][j]) # 添加该特征点至新类别
ClassData[i].remove(ClassData[i][j]) # 在原类别里删除该特征点
#print(i,j," ",np.shape(ClassData[0])[0],np.shape(ClassData[1])[0],np.shape(ClassData[2])[0],np.shape(ClassData[3])[0])
#print(Pk-Pj[PjMinNum])
return ClassData
def main():
# 数据打开文件路径
Tain_set = openpyxl.load_workbook(os.path.abspath('data.xlsx'))
# 训练数据读取
Tain_sheet = Tain_set["Sheet1"]
# 1性别 2籍贯 3身高 4体重 5鞋码 6(50米成绩) 7肺活量 8喜欢颜色 9喜欢运动 10喜欢文学
Data_Num = 200 # 提取 208 个人信息
WhichClass = [3, 4, 6]
Data, Lable = ReadInData(Tain_sheet, 1, WhichClass, Data_Num)
X = []
Y = []
for k in range(20):
print(k)
################################<C均值分类算法>####################################
# 设聚类中心4个
Center_Num = k+1
# 设定不同类别的点池
ClassData = [[] for i in range(Center_Num)]
# 使用方法0,得到初始聚类中心
Center_Data, Center_Lable, Center_Position = C_Center(Data, Lable, Center_Num,1)
# 得到各点与中心点的距离
Distance = CentDis(Center_Data, Data, Center_Num)
# 得到每点所属类别
#MinDistance = [RandOlder(Distance[i]) for i in range(Data_Num)] # 初始随机分类
MinDistance = [MinOlder(Distance[i]) for i in range(Data_Num)] # 初始最小距离分类
# 将每个点分到所属的类里面
for i in range(Data_Num):
ClassData[MinDistance[i]].append(Data[i].tolist())
if k < 1:
sum2 = 0
for i in range(Center_Num):
Class = ClassData[i]
sum1 = 0
for j in range(np.shape(Class)[0]):
sum1 = sum1 + pow(np.array(Class[j]) - np.array(Center_Data[i]), 2)
sum2 = sum2 + sum1
X.append(1)
Y.append(sum(sum2))
else:
# 类别特征点初始化
NewClassData = ClassData
# 初始化准则函数值
OldJe = 3
NewJe = 2
RunTime = 0
# 开始循环使准则函数Je最小
while OldJe - NewJe > 0.0000001:
# for j in range(10):
# 得到新的类别中心
NewCenter = ClassMean(NewClassData, Center_Num)
# 更新准则函数值
if RunTime > 0:
OldJe = NewJe
NewJe = Je(NewClassData, Center_Num, NewCenter)
# 更新类的点
NewClassData = PointMoving(NewClassData, Center_Num, NewCenter)
RunTime = RunTime + 1 # 调制次数统计
X.append(k)
Y.append(NewJe)
print(Y)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(X, Y)
ax.set_title("Je Trend Line")
ax.set_xlabel("ClassNum - C")
ax.set_ylabel("Je")
plt.show()
if __name__ == '__main__':
main()
上一篇: 单细胞分析初探:数据获取入门指南
下一篇: [温习]jqgrid 前后端交互实例