kmeans 聚类 --- (代码为: 博客数据聚类) (python )

时间:2021-12-04 23:44:05

kmeans聚类  迭代时间远比层次聚类的要少,处理大数据,kmeans优势极为突出.。

对博客数据进行聚类,实验测试了: 层次聚类的列聚类(单词聚类)几乎要上1小时,而kmeans对列聚类只需要迭代4次!! 快速极多。

如图:包含两个聚类的kmean聚类过程:

kmeans 聚类 --- (代码为: 博客数据聚类) (python )

总思路:

将所有要聚类的博客,全部用word表示成一个向量,即每篇博客都是由单词组成的,然后形成了一个单词-博客 的矩阵,矩阵里的权重值就是单词在当前博客出现的总次数。

这样kmeans就是要将这些词频矩阵进行聚类。其实kmeans这里用到的距离相似度是用pearson。

 

 

聚类之前,先读取数据文件blogdata.txt.文本如下:

kmeans 聚类 --- (代码为: 博客数据聚类) (python )

源文件第一列是博客名。第一行从第二列起,是这些博客的单词列表的所有单词。所以这里便有个wordlist。

这里的博客内容全部用单词表示,从第二行开始,每一行都是表示一篇博客。

 

def readfile(filename):
#取得文件的所有内容,用数组存文件的每行数据
lines = [line for line in file(filename)]

#获取矩阵的第一行数据,用数组列表columnames存储 所有列名
columnnames = []
columnnames
= lines[0].strip().split('\t')[1:] #从数组下标为1的开始取,不要 下标为0的,因为下标为0,是“Blog”,删去,返回的是数组列表

rownames
= []
data
= []
splitwords
= []
datatemp
= []
for line in lines[1:]: #从下标为1的line数组里取各行,即从矩阵第二行开始去
splitwords = line.strip().split('\t')
#每行的第一列是行名 ,rownames存的是所有行名
rownames.append(splitwords[0])
#剩下部分是该行对应的数据

# data.append(splitwords[1:]) #这里数据虽然是数字,但添加的是string类型,但是应该改成添加float类型
datatemp = [float(x) for x in splitwords[1:]] #即,datatemp存的是所有x的数组:[x]。 等式右边的一开始[]号不能省略啊
data.append(datatemp) # 最终data里的每个元素都是一个datatemp数组,也就是data每个元素就存着 矩阵一行,在本例,data每个元素就是存着一个博客的数据
return rownames,columnnames,rowsdata #rownames是博客名,方便以后将结果树状打印

 

 这里的rownames将是wordnames:

kmeans 聚类 --- (代码为: 博客数据聚类) (python )

 rowsdata就是:

kmeans 聚类 --- (代码为: 博客数据聚类) (python )

所以kmeans就是对上面这一串数字rowdata进行聚类的。

 

 

kmeans聚类步骤:

def kmeanscluster(rowsdata,distance = pearson , finalclusternumberK = 4):

 

1.随机创建k个中心点(对应博客聚类,则是创k个中心类似博客,但是这k个中心类似博客基本不会命中原来的博客,这k个中心点是新创建的

  

    # 中心值博客的所有列的值(即单词的值)就是最小最大博客的平均值。 
例如: 所有行里,对所有列 求出每列的最小大值,然后将两者平均,则中心博客 的所有列就是这些平均值

如:原数据为: 每行表示一个博客,列表示单词。数字表示单词在博客出现的次数。
0 0
2 1 ...
2 3 4 10 ...
4 6 0 0 ...

则对每列进行最小最大值,求出最大最小值博客:
最小值: 0 0 0 0
 最大值:
4 6 4 10
则中心博客的各列是 最小值博客 和最大值博客的 平均:
2 3 2 5
 代码中,rangeslistofAllWords存储则会是:[ (0,4) ,(0,6) ,(0,4),(0,10)...... ] 所以[2,3 , 2, 5....] 就设为中心点

'''
但在下面代码实现中,求中心博客,并不是简单求平均值,而是引用了随机数!!即如radom()*(max-min) + min
若radom()是0.
2,则
最终中心博客是;
(
0.4 0.6 0.4 1 ) + (0 0 0 0)
为:
0.4 0.6 0.4 1
'''
 
 
 
 
代码如下:
    #确定每个点的最小、大值,即确定一个单词 出现次数的最大最小值。即创2行单词数列 的二维数组,定义如下:
rangeslistofAllWords=[]
for i in range(len(rowsdata[0])): #对所有列进行遍历,每一列的最大最小值找出来,也就是每一个单词的最大最小出现次数找出来
minvalue = min(line[i] for line in rowsdata)
maxvalue
= max(line[i] for line in rowsdata)
Elementofcolumn
=(minvalue,maxvalue)
rangeslistofAllWords.append(Elementofcolumn)
#rangslistofallwords 每个元素里是一个元组
  #随机创建k个中心点(对应博客聚类,则是创k个中心类似博客)。
meancluster =[ [(rangeslistofAllWords[j][1] - rangeslistofAllWords[j][0]) * random.random() + rangeslistofAllWords[j][0]
for j in range(len(rowsdata[0]))]
for i in range(0,finalclusternumberK)] # 创建i行 j列二维数组,i*j

 

 

 

2. 遍历数据所有行(即遍历所有博客),为每行找到匹配的中心点。 计算每行与每个中心点的距离(这里用的pearson距离),
对每行,匹配中心点,找出与该行距离最小的中心点,从而归入到该中心点的簇去,即放进lowestDistanceMatches[]去。记住放进去的是ID,而不是这些行本身。这样会简洁很多

 #设置匹配列表   (如果用字典表示,怎样实现呢???? 
lowestDistanceMatches =[[]for i in range (finalclusternumberK)] #初始化:二维数组,列表套列表 = ,并定义它列表的长度

#遍历数据的所有行,为每行找到匹配的中心点 (即距离该行最小的中心点)
for j in range(len(rowsdata)): #遍历所有行
row = rowsdata[j] #方便记住row该行对应的id :j
IndexofBestMatchmeancluster = 0
lowestdistance
= distance(row,meancluster[0]) #初始化最小距离

for i in range(finalclusternumberK): #遍历所有的中心点
if distance(row,meancluster[i])< lowestdistance:
lowestdistance
= distance(row,meancluster[i])
IndexofBestMatchmeancluster
= i # 只传下标ID, 跟选择排序一样。
lowestDistanceMatches[IndexofBestMatchmeancluster].append(j) # j是该行的下标,IndexofBestMatchmeancluster 为该行匹配的中心点的下标

3.把中心点变成其所有成员的平均位置处

      #中心点重新分布,中心点为所在簇的平均值
for i in range(finalclusternumberK): #遍历所有的中心点
newmeanrowforallWords = [0.0]*len(rowsdata[0])#很重要!! 初始化了数组列表,并且定义 数组列表的长度 ,为什么不[for i range(len(rowsdata))]呢??

#遍历同一个簇下的所有行,从而求得 平均值的行
if len(lowestDistanceMatches[i] )>0: #有一些中心点是聚不到元素
for rowid in lowestDistanceMatches[i]: #lowestDistanceMatches[i] 存储着那些 聚敛在一起的博客行 。相当与二维数组[i][rowid]
# print 'one '
for colid in range(len(rowsdata[0])): # 遍历中心点的所有列
newmeanrowforallWords[colid]+= rowsdata[rowid][colid]

for j in range(len(newmeanrowforallWords)): #求平均
newmeanrowforallWords[j]/=len(lowestDistanceMatches[i])
# print newmeanrowforallWords
meancluster[i]=newmeanrowforallWords

 

4. 迭代第2,3步,直到 lowestDistanceMatches[]里的元素不再变化。(要比较是否变化,就要多设一个以前匹配列表lastmatches来比较)

 

lastmatches = None
for times in range(100):
print '迭代 第%d 次:' % times

#在这里插入第2,3 步的 代码。第2,3步代码都与上一句print对齐,同属for times的内容。 最好是将下面终止条件一段代码,放在第2,3步代码 之间,可以早点break迭代

 

     #终止条件: lowestDistanceMatches 匹配结果没变过,则说明聚类完成,结束   
if lowestDistanceMatches == lastmatches:
break
lastmatches
= lowestDistanceMatches

 

最终的目的就是求出lowestDistancematches[]

return lowestDistanceMatches

 

 

到此,kmean函数定义完成。


 

定义pearson距离 函数:

def pearson(v1,v2):
# Simple sums
sum1=sum(v1)
sum2
=sum(v2)

# Sums of the squares
sum1Sq=sum([pow(v,2) for v in v1])
sum2Sq
=sum([pow(v,2) for v in v2])

# Sum of the products
pSum=sum([v1[i]*v2[i] for i in range(len(v1))])

# Calculate r (Pearson score)
num=pSum-(sum1*sum2/len(v1))
den
=sqrt((sum1Sq-pow(sum1,2)/len(v1))*(sum2Sq-pow(sum2,2)/len(v1)))
if den==0: return 0

return 1.0-num/den

 

对列聚类,定义转置矩阵 rotatematrix 即可。

 

 

最后,运行kmeans函数:

 
blognames ,wordnames, rowsdata = HierarchicalCluster.readfile("blogdata.txt")

choice
= int (raw_input(' 请输入:1表示行聚类:博客聚类; 输入2表示列聚类,单词kmeans聚类: '))
if choice == 1 : #为什么加了一个if ,运行就有问题???
print '行聚类:博客聚类 开始:'
clusters
= kmeanscluster(rowsdata, finalclusternumberK=4)
#遍历结果,输出结果
for i in range(len(clusters)):
print [ blognames[j] for j in clusters[i]] #一句输出等于下面三句!
# for j in clusters[i]:
#
print '%s ,'% blognames[j],
#
print '\n'

else:
print '列聚类(单词kmeans聚类)如下:'
colsdata
= rotatematrix(rowsdata)
clusters
= kmeanscluster(colsdata, finalclusternumberK=10)
#遍历结果,输出结果
for i in range(len(clusters)):
print [ wordnames[j] for j in clusters[i]] #一句输出等于下面三句!

 

 

运行结果:

kmeans 聚类 --- (代码为: 博客数据聚类) (python )

 

 

缺点:

结果并不能如层次聚类那样图形化展示出来,还没找到这样第三方库,

树状图展示结果,效果会更好

 

 


 

附层次聚类代码:

def hcluster(rowsdata , distance = pearson):
'''层次聚类:所有原始数据项当做一组聚类,最终所有组至底向上地聚类成一个聚类。
这里对 矩阵行 (行表示博客) 进行聚类,每一行数据当成一组。即对博客进行聚类,相似的博客聚在一起
循环体遍历计算:
每一组可能的配对 并计算它们的相关度closest,以此找到最佳配对。 相关度 用pearson距离来衡量
最佳匹配的两个聚类合并成一个聚类, 新生成的聚类所包含数据 等于 两个旧聚类的数据 求均值 的结果
一直循环直到只剩下一个聚类cluster[0]为止

变量定义:
distances是一个字典,存储所有距离的计算值
id =i 表示第i个博客,
lowestDistacePair =(i,j)表示一个元组,当前最小距离的一对,这一对簇即将合并成一个簇。
rowsdata[i]和vec存的内容都是:[0.0, 1.0, 0.0, 3.0,
'''
currentclustid
= -1
distancesDic
= {} #distances是一个字典,存储所有距离的计算值 。如果是行聚类,存储距离的字典不会很大,因为是博客作为key.而如果是列聚类,字典会很大,因为单词word作为key.所以列聚类就不存储 这些距离了。直接比较 。

#最开始的聚类是数据集的行
'''所有聚类都放在一个数组cluster里,数组rowsdata会传到bicluster类的vec参数去。
clust是一个列表数组,该数组每个元素就是存着rowsdata一个元素,而rowsdata存的是矩阵一行的元素,
总之,clust数组存的是博客数组,一个元素存的是一个博客。clust数组一个元素就是矩阵一行
'''
clust
= [bicluster(rowsdata[i],id = i) for i in range(len(rowsdata))]
'''id =i 这一步很重要 ,第几个博客,则id为几。 等于第i行数据
clust存的是矩阵的每行。rowsdata每个元素赋给vec。
rowsdata[i]和vec存的内容都是:[0.0, 1.0, 0.0, 3.0,
'''

while len(clust)>1: #当聚类还剩最后一个,则跳出循环
lowestDistacePair = (0,1)
closestness
= distance(clust[0].vec , clust[1].vec) #先对矩阵第一行和第二行聚类,即对第一个博客、第二个博客聚类.

#遍历每一个配对,寻找最小距离的一对,即找 lowestDistancePair
for i in range(len(clust)): #一个clust[i]就代表一个博客
for j in range(i+1,len(clust)): #range(i,)添加 i很重要,这样就不用重复计算以计算好的距离,因为计算<i,j>距离和<j,i>距离是一样的,所以添加一个范围(i,)就省下极多时间

'''上面的字典distancesDic 那五句代码,可以不用,只用下面一句代码代替即可,只用下面
一句代码意思是,不需要存储距离
'''
d
= distance(clust[i].vec ,clust[j].vec)

if d<closestness :
closestness
= d
lowestDistacePair
= (i,j)

#计算两个聚类的平均值 (在找到了最小距离的一对lowerstDistancePair后),作为新合并簇的博客值 vec
mergevec = [] #mergevec是数组
mergevec = [
(clust[lowestDistacePair[0]].vec[i]
+ clust[lowestDistacePair[1]].vec[i]) /2.0
for i in range(len(clust[0].vec))
]
#vec内容是:[0.0, 1.0, 0.0, 3.0,.....所以vec也是一个数组

#建立新的聚类簇,更新该簇信息
newcluster = bicluster( mergevec,left = clust[lowestDistacePair[0]],
right
= clust[lowestDistacePair[1]],
distance
= closestness, id = currentclustid
)
#新合并的簇的id都是负数,currentclustid都是负数

#不在原始集合中的聚类(即新合并的聚类),其id为负数。
currentclustid -=1
#删去在clust的已经被合并的最小聚类。每次都删去最小的一对,以找下一对。。
#del数组是先要del[1],再del[0].顺序不能反。
#不然出错IndexError: list assignment index out of range:引用超过list最大索引。删除一定要先删除下标最大的,再删除下标最小的??是吗?
del clust[lowestDistacePair[1]]
del clust[lowestDistacePair[0]]
clust.append(newcluster)
#最终是将clust数组里所有元素都删掉,直到clust元素还剩下一个,即clust[0].
return clust[0]