数据挖掘 K-means聚类实现实例
本学期恰好选修了数据挖掘课程。本次作业要求我们编写代码完成两个典型实例:其中一个是编写代码完成waveform.data文件的数据聚类任务;另一个则是采用K-means算法进行图像的聚类分割处理。下面我会分别详细阐述这两个项目的开发过程和成果。
首先介绍一下K-means实现原理这一主题。文中有一篇非常易于理解的文章值得参考;具体链接如下
http://www.aboutyun.com/thread-18178-1-1.html
废话不多说直接上干货啦!K-means算法主要是通过设定聚类数量来进行操作的。它会先随机初始化选择若干个聚类中心点,在此基础上对剩下的所有数据进行距离计算,并找出最近的距离范围后将这些数据归入对应的那个类别中。接着系统会计算每个类别内所有数据的平均值,并把得到的平均值作为新的聚类中心继续重复上述步骤直到最终不再产生新的类别为止。
优点:算法简单,当结果簇是密集的且区别明显,效果较好。
缺点:仅当cluster mean存在时才可应用, 这种方法可能只适用于那些数据不具备分类属性的情况.
需要预先指定顶点数k值,并且该方法不具备消除噪声数据与异常点(outliers)的能力。它不适宜用于识别具有非凸形状(non-convex shapes)特征的数据集。
下方面向读者编写代码时,请首先编写与waveform.data相关的聚类代码部分。在此过程中,请注意在这里设置了三个不同的聚类类别,并请具体说明可以根据实际需求进行调整
import random
import pandas as pd
import numpy as np
import tool
#设置精度小数点后两位
np.set_printoptions(precision=2)
#读取文件
df=pd.read_csv("waveform.data",header = None)
#随机选择三个中心点作为初始质心
i1 = random.randint(0,21) #列标0-21
j1 = random.randint(0,4998) #行标0-4999
cent1 = df[i1][j1]
i2 = random.randint(0,21)
j2 = random.randint(0,4998)
cent2 = df[i2][j2]
i3 = random.randint(0,21) #列标0-21
j3 = random.randint(0,4998) #行标0-4999
cent3 = df[i3][j3]
#聚类存储列表
list1 = []
list2 = []
list3 = []
result = 1
while result:
list1.clear
list2.clear
list3.clear
result = tool.circle (df,cent1,cent2,cent3,list1,list2,list3,result)
print("聚类1:前100个数:",list1[:100],'\n')
print("聚类2:前100个数:",list2[:100],'\n')
print("聚类2:前100个数:",list3[:100],'\n')
代码解读
waveform.data的数据格式较为简单,在实际应用中通常将其视为一个一维数组;在计算过程中仅需计算两个点之间的欧氏距离的绝对值即可完成相关运算
我想说点什么吧。回想起来,在学习编程的时候就不太擅长转变思维方式。起初接触Python的时候不适应感明显——毕竟习惯了C++那种严格的数据类型和明确的语法结构嘛。在尝试使用pandas库进行数据遍历时想要实现一个类似于for循环的功能时(本以为应该可以直接调用相关的接口),结果发现只能通过for循环的方式来进行操作。如果有经验之谈或者解决办法的话,请一定告知小弟一声。
本人封装了一个circle接口,并用于循环计算各点的聚类距离并进行分类;最终会根据结果判断是否生成新的聚类点以决定是否继续循环;具体的接口代码以及waveform数据文件已提供,请根据需求自行下载。
接着是对图像分割的代码实现。
这里本人分割的图像为一个较为简单的图像,原图和分割图像如下:
原图:

本人将其划分成四个聚类,分别是天空,森林,地面,狗狗,分割图如下:




可以观察到图像分割效果明显欠佳,在地面与天空区域中存在较多混杂的像素点。狗和树林中也存在混杂的像素点。而且这个最终结果是本人进行了多次测试后得到的。
先上主代码:
import random
import pandas as pd
import numpy as np
import tool
from PIL import Image
#图像大小为98*69
#print(im.size,im.format,im.mode) 显示图片信息
img = Image.open("dog.jpg","r")
img_array=img.load()
#随机选择四个中心点作为初始质心
i1 = random.randint(0,97)
j1 = random.randint(0,68)
cent1 = img_array[i1,j1]
i2 = random.randint(0,97)
j2 = random.randint(0,68)
cent2 = img_array[i2,j2]
i3 = random.randint(0,97)
j3 = random.randint(0,68)
cent3 = img_array[i3,j3]
i4 = random.randint(0,97)
j4 = random.randint(0,68)
cent4 = img_array[i4,j4]
print(i1,j1,"\n",i2,j2,"\n",i3,j3,"\n",i4,j4)
#聚类存储列表
list1 = []
list2 = []
list3 = []
list4 = []
result = 1
while result:
list1.clear
list2.clear
list3.clear
list4.clear
result = tool.pic(img,img_array,cent1,cent2,cent3,cent4,list1,list2,list3,list4,result)
imgnew1 = Image.new("RGB",(img.size[0],img.size[1]))
imgnew2 = Image.new("RGB",(img.size[0],img.size[1]))
imgnew3 = Image.new("RGB",(img.size[0],img.size[1]))
imgnew4 = Image.new("RGB",(img.size[0],img.size[1]))
#三个参数依次为R,G,B,A R:红 G:绿 B:蓝 A:透明度
#白色(225,255,255) 黑色(0,0,0)
pixTuple = (255,255,255)
for i in range(img.size[0]):
for j in range(img.size[1]):
if img_array[i,j] in list1:
imgnew1.putpixel((i,j),img_array[i,j])
else:
imgnew1.putpixel((i,j),pixTuple)
if img_array[i,j] in list2:
imgnew2.putpixel((i,j),img_array[i,j])
else:
imgnew2.putpixel((i,j),pixTuple)
if img_array[i,j] in list3:
imgnew3.putpixel((i,j),img_array[i,j])
else:
imgnew3.putpixel((i,j),pixTuple)
if img_array[i,j] in list4:
imgnew4.putpixel((i,j),img_array[i,j])
else:
imgnew4.putpixel((i,j),pixTuple)
imgnew1.save('list1.jpg')
imgnew2.save('list2.jpg')
imgnew3.save('list3.jpg')
imgnew4.save('list4.jpg')
print("打印图片\n")
代码解读
探讨影响聚类效果的因素时会发现其主要原因在于数据特征的选择问题。具体而言,在随机数生成过程中可能会出现不同位置的像素可能对结果产生影响的情况;此外因为图片质量较低可能导致某些像素点与周围区域差异较大从而造成较大的偏差;针对这种情况建议采用K-medoids算法作为优化方案以提升聚类效果
编程思想:图像可被视为由RGB分量构成的三维像素矩阵或高维列表。同样地,在此方法中继续采用平方欧式距离这一指标来衡量每个像素与聚类中心之间的差异程度。具体而言,在此过程中首先计算每个像素点与聚类中心之间平方欧式距离;其次评估所有像素点到各个中心的距离大小;再次基于距离远近进行分组;最后重复上述步骤直至不再产生新的中心位置为止。
本人遇到了一个算法问题,即聚类之后如何计算各类的新聚类点。我的思路是将局针转置,因为需要计算每个像素点的R、G、B通道的平均值[[[R1,G1,B1],[R2,G2,B2],[R3,G3,B3],[R4,G4,B4]...]...[Rn,Gn,Bn]]。观察到将该多维列表进行一次转置后变成如下形式:[[[R1,R2,R3,...,Rn],[G1,G2,G3,...,Gn],[B1,B2,B3,...,Bn]]]这大大简化了问题。转制后可以直接使用numpy库中的mean函数来计算每一行的平均值即得到新的聚类中心RGB三个通道的平均值。
最终通过判断,按照需要对其进行图形分割即可。
代码下载地址:<>
