欢迎关注我的个人博客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]
这个算法好像没有解决我得问题,最后都聚为一类的,并没有我想要的聚为几类。
并且这个算法的计算量很惊人,因为我们必须计算每两个配对项之间的关系,
并且在合并项之后,这些关系还得重新再计算。
还有一种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]]