集体智慧编程学习之聚类系统

欢迎关注我的个人博客blog.timene.com

有句话这么说“物以类聚,人以群分”,说的很有道理,有些人就完全融入不了一些团队,我觉得这里面不只是气场的问题,我也一直在苦苦思索这个问题,每个人有自己的生活习惯和思维习惯,相同生活习惯和思维习惯的人很容易聚在一起;反之则比较难了。

目标很明确,给你一堆电子书,怎么把其中一些书归为一类,其中另一些书归为另一类······,我会先看书的厚度,太厚的书(超过800页)我归为和我没什么关系这一类(偶尔会有例外),下来我会按照我的爱好细分,专业类、经济类、心理类、摄影类和其他类。如果书很多了,我一本一本的分类会很费时间和精力。

我有个朋友做数据挖掘的,他给我说用贝叶斯分类,我研究了一下,贝叶斯的确是个好东东,但有个问题,需要提前建立一个模型,我有建立这个模型的时间还不如自己一本一本分类来着。有没有不用先验经验就可以分类的办法呢?我查了一下还真有:无监督学习,贝叶斯这种需要事前建立一个模型的算法属于监督学习。

聚类,根据什么聚类呢?我的目的是根据书籍的内容来区分,书籍的内容怎么描述呢?书籍无非是一些词和词组(成语也算为词组吧?),根据词和词组怎么来区分书籍的分类呢?经验是,不同分类书籍常用词不同。什么意思,比方说经济类的书籍通常会出现“经济”,“增长”,“萧条”,“膨胀”,,,甚至“卡特尔”,“托拉斯”等等,摄影类的书籍通常会出现“光圈”,“快门”,“ISO”,“曝光”,“构图”等等。能不能根据词汇在书籍中出现的比例来判定这本书是那个分类的呢,好像是可以。于是有了下面的变量; 

books = {  
    'book1': {'word1': 2, 'word2': 3, 'word3': 3, 'word4': 12, 'word5': 13, 'word6': 12},  
    'book2': {'word1': 3, 'word2': 3, 'word3': 1, 'word4': 13, 'word5': 12, 'word6': 11},   
    'book3': {'word1': 1, 'word2': 10, 'word3': 3, 'word4': 5, 'word5': 11, 'word6': 13},  
    'book4': {'word1': 12, 'word2': 13, 'word3': 12, 'word4': 3, 'word5': 2, 'word6': 2},  
    'book5': {'word1': 13, 'word2': 12, 'word3': 11, 'word4': 1, 'word5': 3, 'word6': 3},   
    'book6': {'word1': 5, 'word2': 11, 'word3': 13, 'word4': 3, 'word5': 1, 'word6': 10}
}  
变量的含义很明确,数字表示该book中出现该word的次数。

为了实现方便,我简化该数据结构:

books = [  
    [2, 3, 3, 12, 13, 12],  
    [3, 3, 1, 13, 12, 11],   
    [1, 10, 3, 5, 11, 13],  
    [12, 13, 12, 3, 2, 2],  
    [13, 12, 11, 1, 3, 3],   
    [5, 11, 13, 3, 1, 10]
]

下面来分类,一种叫分级分类的算法,该算法通过连续不断的将最为相似的群组两两合并,

最终构造出一个群组的层级结构。相似度可以用上篇所说的皮尔逊相关度计算,注意的是最后一行,

皮尔逊公式计算出来的结果越相似值会越大,这里我想让越相似的值越小,

表示距离近,所以用1减了皮尔逊公式的返回值;

def pearson(v1,v2):
  sum1=sum(v1)
  sum2=sum(v2)
  
  sum1Sq=sum([pow(v,2) for v in v1])
  sum2Sq=sum([pow(v,2) for v in v2])	

  pSum=sum([v1[i]*v2[i] for i in range(len(v1))])
  
  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

分级分类算法先算一次所有book之间的相似度,把最相似的聚为一类,然后用他们的均值代表一个新值;

第二遍计算其他剩余书籍和新值组成的新数组之间两两相似度,把最相似的聚为一类,然后用他们的他两的均值代表一个新值;

。。。

直到最后就剩下一个元素,结束。

这里面有个可以优化的地方,我们看第一遍计算了所有元素之间的相似度,在第二遍时还要再计算,

所以我们可以把中间结果值缓存下来,来减少中间的计算。

我们先定义一个数据结构,代表聚类的一条数据:

class bicluster:
  def __init__(self,vec,left=None,right=None,distance=0.0,id=None):
    self.left=left
    self.right=right
    self.vec=vec
    self.id=id
    self.distance=distance


下面来看看分级分类算法的实现:

def hcluster(rows,distance=pearson):
  distances={}
  currentclustid=-1

  clust=[bicluster(rows[i],id=i) for i in range(len(rows))]

  while len(clust)>1:
    lowestpair=(0,1)
    closest=distance(clust[0].vec,clust[1].vec)

    for i in range(len(clust)):
      for j in range(i+1,len(clust)):
        if (clust[i].id,clust[j].id) not in distances: 
          distances[(clust[i].id,clust[j].id)]=distance(clust[i].vec,clust[j].vec)

        d=distances[(clust[i].id,clust[j].id)]

        if d<closest:
          closest=d
          lowestpair=(i,j)

    mergevec=[
    (clust[lowestpair[0]].vec[i]+clust[lowestpair[1]].vec[i])/2.0 
    for i in range(len(clust[0].vec))]

    newcluster=bicluster(mergevec,left=clust[lowestpair[0]],
                         right=clust[lowestpair[1]],
                         distance=closest,id=currentclustid)

    currentclustid-=1
    del clust[lowestpair[1]]
    del clust[lowestpair[0]]
    clust.append(newcluster)
    print len(clust)

  return clust[0]

中间的del只是删除了clust数组中的元素,也就是只删除了指针,实际元素并没有被删除。

这个算法好像没有解决我得问题,最后都聚为一类的,并没有我想要的聚为几类。

并且这个算法的计算量很惊人,因为我们必须计算每两个配对项之间的关系,

并且在合并项之后,这些关系还得重新再计算。


还有一种K-均值聚类的算法好像可以满足我的要求:

K-均值聚类算法首先会随即确定K个中心位置,然后将各个数据项分配给最接近的中心点。

分配完成后,聚类中心会移动到分配给该聚类的所有节点的平均位置处。然后整个分配过程重新开始。

这一过程会一直重复下去,直到分配过程不再产生变化为止。

下面代码实例:

import random

def kcluster(rows,distance=pearson,k=4):
  ranges=[(min([row[i] for row in rows]),max([row[i] for row in rows])) 
  for i in range(len(rows[0]))]

  clusters=[[random.random()*(ranges[i][1]-ranges[i][0])+ranges[i][0] 
  for i in range(len(rows[0]))] for j in range(k)]
  
  lastmatches=None
  for t in range(100):
    print 'Iteration %d' % t
    bestmatches=[[] for i in range(k)]
    
    for j in range(len(rows)):
      row=rows[j]
      bestmatch=0
      for i in range(k):
        d=distance(clusters[i],row)
        if d<distance(clusters[bestmatch],row): bestmatch=i
      bestmatches[bestmatch].append(j)

    if bestmatches==lastmatches: break
    lastmatches=bestmatches
    
    for i in range(k):
      avgs=[0.0]*len(rows[0])
      if len(bestmatches[i])>0:
        for rowid in bestmatches[i]:
          for m in range(len(rows[rowid])):
            avgs[m]+=rows[rowid][m]
        for j in range(len(avgs)):
          avgs[j]/=len(bestmatches[i])
        clusters[i]=avgs
      
  return bestmatches


对代码进行点解释:

ranges变量存储第一次随机生成中心点的值范围,也就是word出现频率的上下限

clusters变量就是随机生成的K个中心点

循环100次:

        定义bestmatches存放聚类到K个中心点的数组 

        对每一行:

                 计算每一行到K个中心点的距离,将改行放入最近中心点的数组bestmatches

        如果本次计算结果和上次计算结果相同,说明聚类完成,退出

        重新计算聚类中心,每个聚类中心为聚到该类的行的平均值


来看看我机器上的结果,效果还算不错吧

>>> clust = clusters.kcluster(books,clusters.pearson,2)
Iteration 0
Iteration 1
>>> print clust
[[3, 4, 5], [0, 1, 2]]





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值