在学完python基本语法,学习西瓜书k-means聚类分析算法原理后,根据西瓜书算法原理及伪码(p203)上手编写代码,既是熟悉算法的过程,也是熟悉python编程的过程。此处为了方便差错和验证,使用了西瓜书教材的数据进行实验
前面先放初步编完大框架,记录一下调试代码的过程和遇到的困难以及解决方法。文末贴了调试完成后的最后可直接运行的代码及结果,需要的读者可以直接略过中间部分跳至文末。
初步解决了一些小的语法错误后,可以成功运行的代码:
import math
# 使用list里面存放tuple的方式来存放样本数据集
D = [(0.697,0.460), (0.774, 0.376), (0.634, 0.264), (0.609, 0.318), (0.556, 0.215), (0.403, 0.237), (0.481, 0.149), (0.437, 0.211), (0.666, 0.091), (0.243, 0.267), (0.245, 0.057), (0.343, 0.099), (0.639, 0.161), (0.657, 0.198), (0.360, 0.370), (0.593, 0.042), (0.719, 0.103), (0.359, 0.188), (0.339, 0.241), (0.282, 0.257), (0.748, 0.232), (0.714, 0.346), (0.483, 0.312), (0.478, 0.312), (0.478, 0.437), (0.525, 0.369), (0.751, 0.489), (0.532, 0.472), (0.473, 0.376), (0.725, 0.445), (0.446, 0.459)]
# 选取3个样本作为均值向量
u1 = [0.403, 0.237]
u2 = [0.343, 0.099]
u3 = [0.478, 0.437]
# 令两个簇C1、C2为空,定义簇为list类型
c1 = []
c2 = []
c3 = []
# 方法:计算样本xj与各均值向量的距离(此处使用闵可夫斯基距离,当n等于2时为欧式距离)
def getDistancein_x_u(xfirst, xsecond, ufirst, usecond):
distancin_x_u = math.sqrt(pow(abs(xfirst - ufirst), 2)+pow(abs(xsecond - usecond), 2))
return distancin_x_u
# 定义样本与均值向量的距离
distancein_x_u = 0.0
# 主函数
for x in range(1,100):
# 每次循环时将c1和c2清空
c1 = []
c2 = []
c3 = []
for element in D:
distancein_x_u1 = getDistancein_x_u(element[0], element[1], u1[0], u1[1])
distancein_x_u2 = getDistancein_x_u(element[0], element[1], u2[0], u2[1])
distancein_x_u3 = getDistancein_x_u(element[0], element[1], u3[0], u3[1])
# 根据最近的均值向量确定此样本的簇标记,并将该样本划入相应的簇。此处应使用一个排序算法,再改吧,先跑成再说
if distancein_x_u1 > distancein_x_u2 and distancein_x_u1 > distancein_x_u3:
c1.append((element[0], element[1]))
elif distancein_x_u2 > distancein_x_u1 and distancein_x_u2 > distancein_x_u3:
c2.append((element[0], element[1]))
else:
c3.append((element[0], element[1]))
#此处for循环执行完毕以后,c1和c2里都塞满了
#先保存当前均值向量,以便后面对比:
model_u1 = u1
model_u2 = u2
model_u3 = u3
#从c1、C2中求出新的均值向量,此处先定义变量为float类型
distance_sum_x_1 = 0.0
distance_sum_y_1 = 0.0
distance_sum_x_2 = 0.0
distance_sum_y_2 = 0.0
n=0
for newelement1 in c1:
distance_sum_x_1 = distance_sum_x_1 + newelement1[0]
distance_sum_y_1 = distance_sum_y_1 + newelement1[1]
n = n+1
u1 = [distance_sum_x_1/n, distance_sum_y_1/n]
n=0
for newelement2 in c2:
distance_sum_x_2 = distance_sum_x_2 + newelement2[0]
distance_sum_y_2 = distance_sum_y_2 + newelement2[1]
n = n+1
u2 = [distance_sum_x_1/n, distance_sum_y_1/n]
n=0
for newelement3 in c3:
distance_sum_x_3 = distance_sum_x_3 + newelement1[0]
distance_sum_y_3 = distance_sum_y_3 + newelement1[1]
n=n+1
u3 = [distance_sum_x_1/n, distance_sum_y_1/n]
if u1 == model_u1 and u2 == model_u2 and u3 == model_u3:
print("分类第一组:")
for element_final1 in c1:
print('('+element_final1[0]+','+element_final1[1])
print("分类第二组:")
for element_final2 in c2:
print('('+element_final2[0]+','+element_final2[1])
print("分类第三组:")
for element_final3 in c3:
print('(' + element_final3[0] + ',' + element_final3[1])
break
print("分类第一组:")
for element_final1 in c1:
print('(', element_final1[0], ',', element_final1[1], ')')
print("分类第二组:")
for element_final2 in c2:
print('(', element_final2[0], ',', element_final2[1], ')')
print("分类第三组:")
for element_final3 in c3:
print('(' + element_final3[0] + ',' + element_final3[1])
1.遇到ZeroDivisionError: float division by zero在51行,被除数不能为0
for newelement1 in c1:
distance_sum_x_1 = distance_sum_x_1 + newelement1[0]
distance_sum_y_1 = distance_sum_y_1 + newelement1[1]
n = n+1
u1 = [distance_sum_x_1/n, distance_sum_y_1/n]
第一个想法是此处c1可能在第一次分类结束后并未获得任何元素,那么newelement1中一个元素都没有,甚至无法遍历它,那么前面一定出了一些问题,发现在31行处大于小于号搞错了。
2.在测试不同的迭代次数算法的分类结果时,发现无论设置循环几次,结果都毫无变化,于是设立了一个参数用来监控算法的迭代次数,结果发现惊人的:
发现问题是这里的错误,很低级的错误
for x in range(1, 100):
x为空无法遍历x,还是对python的for循环不太熟悉,此处改为while循环:
while y<50:
依然只迭代了一次,看是不是第一轮便执行了两次前后对比均值向量的if语句直接break了
并没有。
问题出在这里
if u1 == model_u1 and u2 == model_u2 and u3 == model_u3:
print("分类第一组:")
for element_final1 in c1:
print('('+element_final1[0]+','+element_final1[1])
print("分类第二组:")
for element_final2 in c2:
print('('+element_final2[0]+','+element_final2[1])
print("分类第三组:")
for element_final3 in c3:
print('(' + element_final3[0] + ',' + element_final3[1])
print("执行了if语句,直接跳出")
break
break的位置错误,本来是要在if语句中break,现在直接在外层直接把本层循环跳出了
3.又遇到被除数为0的问题:
可以添加一个if-else判断来解决这个问题
if n == 0:
print("C1被除数不能为0")
else:
u1 = [distance_sum_x_1/n, distance_sum_y_1/n]
4.解决了上个错误后,分别测试1/2/3/4轮迭代结果,发现至第4轮后c2、c3簇完全没有了,全部到了c1簇里,和没有分一样。
单独测试了第一轮迭代没有问题,结果集和西瓜书一模一样
# 测试第一轮后遍历一下c1、c2、c3:
print('第一轮簇的测试')
print('测试C1')
for test1 in c1:
print(test1[0],',',test1[1])
print('测试C2')
for test2 in c2:
print(test2[0], ',', test2[1])
print('测试C3')
for test3 in c3:
print(test3[0], ',', test3[1])
在计算均值向量处测试了计算出的新的均值向量:
for newelement1 in c1:
n = n + 1
distance_sum_x_1 = distance_sum_x_1 + newelement1[0]
distance_sum_y_1 = distance_sum_y_1 + newelement1[1]
if n == 0:
print("C1被除数不能为0")
else:
u1 = [distance_sum_x_1/n, distance_sum_y_1/n]
# 输出一下当前轮的均值向量
print('第', y, '轮均值向量1:', u1[0], u1[1])
测试结果发现均值向量计算出的结果u2、u3偏差到不知道哪里了!
是计算方法的问题,明天下午继续更新
晚上躺下看代码找到了错误,u1数据正确,但是u2,u3偏差很大,看计算新的均值向量的代码部分
if n == 0:
print("C1被除数不能为0")
else:
u2 = [distance_sum_x_1/n, distance_sum_y_1/n]
应该是x_1,和y_1,这边直接复制的,低级错误
改正以后再跑结果正确,与西瓜书测试结果相符。
最后粘出最后运行代码,读者可以直接复制运行:
import math
# 使用list里面存放tuple的方式来存放样本数据集
D = [(0.697, 0.460), (0.774, 0.376), (0.634, 0.264), (0.609, 0.318), (0.556, 0.215), (0.403, 0.237), (0.481, 0.149), (0.437, 0.211), (0.666, 0.091), (0.243, 0.267), (0.245, 0.057), (0.343, 0.099), (0.639, 0.161), (0.657, 0.198), (0.360, 0.370), (0.593, 0.042), (0.719, 0.103), (0.359, 0.188), (0.339, 0.241), (0.282, 0.257), (0.748, 0.232), (0.714, 0.346), (0.483, 0.312), (0.478, 0.312), (0.478, 0.437), (0.525, 0.369), (0.751, 0.489), (0.532, 0.472), (0.473, 0.376), (0.725, 0.445), (0.446, 0.459)]
# 选取3个样本作为均值向量
u1 = [0.403, 0.237]
u2 = [0.343, 0.099]
u3 = [0.478, 0.437]
# 令两个簇C1、C2为空,定义簇为list类型
c1 = []
c2 = []
c3 = []
# 方法:计算样本xj与各均值向量的距离(此处使用闵可夫斯基距离,当n等于2时为欧式距离)
def getDistancein_x_u(xfirst, xsecond, ufirst, usecond):
distancin_x_u = math.sqrt(pow(abs(xfirst - ufirst), 2)+pow(abs(xsecond - usecond), 2))
return distancin_x_u
# 定义样本与均值向量的距离
distancein_x_u = 0.0
# 主函数
y = 0
while y < 20:
# 每次循环时将c1和c2清空
c1 = []
c2 = []
c3 = []
for element in D:
distancein_x_u1 = getDistancein_x_u(element[0], element[1], u1[0], u1[1])
distancein_x_u2 = getDistancein_x_u(element[0], element[1], u2[0], u2[1])
distancein_x_u3 = getDistancein_x_u(element[0], element[1], u3[0], u3[1])
# 根据最近的均值向量确定此样本的簇标记,并将该样本划入相应的簇。此处应使用一个排序算法,再改吧,先跑成再说
if distancein_x_u1 < distancein_x_u2 and distancein_x_u1 < distancein_x_u3:
c1.append((element[0], element[1]))
elif distancein_x_u2 < distancein_x_u1 and distancein_x_u2 < distancein_x_u3:
c2.append((element[0], element[1]))
else:
c3.append((element[0], element[1]))
# 此处for循环执行完毕以后,c1和c2里都塞满了
# 先保存当前均值向量,以便后面对比:
model_u1 = u1
model_u2 = u2
model_u3 = u3
# 从c1、C2中求出新的均值向量,先定义新的均值向量变量为float类型
distance_sum_x_1 = 0.0
distance_sum_y_1 = 0.0
distance_sum_x_2 = 0.0
distance_sum_y_2 = 0.0
distance_sum_x_3 = 0.0
distance_sum_y_3 = 0.0
n=0
for newelement1 in c1:
n = n + 1
distance_sum_x_1 = distance_sum_x_1 + newelement1[0]
distance_sum_y_1 = distance_sum_y_1 + newelement1[1]
if n == 0:
print("C1被除数不能为0")
else:
u1 = [distance_sum_x_1/n, distance_sum_y_1/n]
n=0
for newelement2 in c2:
n = n + 1
distance_sum_x_2 = distance_sum_x_2 + newelement2[0]
distance_sum_y_2 = distance_sum_y_2 + newelement2[1]
if n == 0:
print("C1被除数不能为0")
else:
u2 = [distance_sum_x_2/n, distance_sum_y_2/n]
n=0
for newelement3 in c3:
n = n + 1
distance_sum_x_3 = distance_sum_x_3 + newelement3[0]
distance_sum_y_3 = distance_sum_y_3 + newelement3[1]
if n == 0:
print("C1被除数不能为0")
else:
u3 = [distance_sum_x_3/n, distance_sum_y_3/n]
# 如果新求出的均值向量和上次的相同,那么下次分配簇时使用的与均值向量的距离算下和这次一样,就可以结束了
if u1 == model_u1 and u2 == model_u2 and u3 == model_u3:
print("分类第一组:")
for element_final1 in c1:
print('(', element_final1[0], ',', element_final1[1], ')')
print("分类第二组:")
for element_final2 in c2:
print('(', element_final2[0], ',', element_final2[1], ')')
print("分类第三组:")
for element_final3 in c3:
print('(', element_final3[0], ',', element_final3[1], ')')
break
y = y+1