聚类是的无监督学习一个例子。无监督学习与监督学习最显著的不同在于,无监督学习寻找的并不是一些什么数据,而是一种模式,或规律,或者,聚类。
比如,我们要对一系列博客进行聚类以从中发现一些规律(《Programming Collective Intelligence》),我们主要根据单词频度来对博客进行聚类。
构造数据
我们使用feedparser对博客的RSS订阅源进行解析,以得到相关的单词频度。
以下函数从一个订阅源得到一单词频度列表。
关于d.entries:
entries实际是有好些项的,所以这里可以写作d.entries[i]
注意这里的一句话:“This element always exists, although it may be an empty list.”。实际上,entries可能为空,所以在使用之前我们应该对其进行判断。判断方法有两种,一种是用len,一中是用not。另,e.summary和e.description是相等的。e.title和d.feed.title是不同的,前者是某条条目的title,即是某篇文章的标题,后者是这个博客的title。
所调用的getwords如下:
说明:1.关于html标记。(去掉的是那些html标记的符号,标记的具体内容并没有去掉)
2.关于re
main code:
这里的流程如下:
1.将所有url从文件feedlist.txt(文件名etc可换)读入到feedlist
2.解析每个url,将其名字和该博客中的单词计数列表存入tilte和wc中。将博客题目和其中所有不同单词出现的次数保存至wordcounts
3.统计出现某单词的博客数目,存入apcount(单词,出现于的博客数)中。前提是,如果该单词在该博客中出现过1次或一次以上,则将apcount[word]加1
4.因为很多单词,比如the,a之类的,几乎出现在每篇博文中,因此,我们对出现的单词进行频率限制。低于10%或高于50%的都舍弃。其余存入wordlist中。
5.新建文件blogdata.txt,写入相关数据。(包括,经频率限制处理过的单词,写入每个博客中所出现的经过频率限制处理的单词的次数)
所写blogdata.txt片段如下(数据经过裁剪,个数不一定相对应):
那么如何实现按主题分类博客呢?可以采用分级聚类的方法。
分级聚类通过连续不断的将最为相似(两元素之间的距离最近)的群组两两合并,构造一个群组的层级结构。分级结构最终形成树状图。树状图可以有效的确定图中个元素的相似程度。
因此在此例中,要实现按主题队博客分类,只需要构造博客的分级聚类,若构造成功,则可对博客进行聚类。
读入数据
首先从我们写成的blogdata.txt中读入数据。
要强烈的注意下blogdata的格式,如下:
第一行:Blog xxx xxx xxx xxx (xxx为出现在各博客中的合理单词)
第二行:blogname 000 000 000 000 (000为第一行中各个单词在博客blogname中出现的次数,可能为0次)
........ .......(同第二行)
函数readfile中的列标题即是那些合理单词;行名即是博客名字;data即是该单词出现在各博客中的次数。
聚类算法
算法hcluster完成对以上data的聚类功能。
初始时,每个单独的行均构成一个聚类,随之程序对每两行之间的距离进行计算并进行聚类操作,直至只有1行存在(while len(clust) > 1:),即只有一个聚类。
整个算法是个无敌的三重循环。。。
while中的双重for是遍历整个聚类寻找距离最小的二聚类。为加快运行,对两个聚类直接的距离值进行了缓存,因为,在这两类不合并或不和其他类合并之前,其离都是相等的,计算结果保存在distances中,其结果大致为:distances == {(聚类1,聚类2):距离},所以在存入distances或从中取值时,我们使用的key是元组,该元组是聚类的id(每个聚类均有唯一的id,这是clust = [bicluster(rows[i], id=i) for i in range(len(rows))]的功劳。。。)。
记住我们所计算的是什么的聚类?
我们的目标是,找出相似的博客。我们通过聚类来达到这一点的。我们计算的是,关于某博客中出现的所有单词的频度的聚类。以此我们将所有的博客根据其中出现的单词的情况进行聚类(做成树状图)。
在找到了两个聚类后,我们对其中的所有单词频度求平均,并以此作为新的聚类,参与下一次的寻找。
求距离我们使用经典的皮尔逊算法。
该算法返回的是0~1之间的数字。完全匹配返回1,否则返回0.即是说,相距越近,则返回的数字越大;但我们的算中是返回的:1.0-num/den,即相距越近则返回数字越小。这与我们的常识是相同的,注意对此算法hcluster中的判断条件是if d < closest,而不是>。
嗯,还可以。。。
把这棵树画出来看看?
树状图
使用PIL来画树状图。
参数选择很重要,尤其是当数据很多时,否则画出来的将会惨不忍睹。。。
上面这段画树的代码来自《Programming Collective Intelligence》。每个语言都有其相应的库/框架之类,但仿佛Python特别多,而且特别杂特别神,比如传说的用Python操作MS Office的xlrd,xlwt之类。但我们只是用而已,而且做树状图对聚类算法来说并非是必要的一步,因此对以上代码没有精研。。。(我该补一句:详情请参考用户手册。。。)
列聚类
我们上面提到过,说,我们所要进行的是,对博客,依据其中所使用的单词,进行聚类,这样就把相似的博客聚合在了一起。在上面的例子中,我们聚类所操作的,实际上是行,行即是博客的名字。
那列呢?列是什么?
列是单词。如果我们对单词进行聚类呢?那么,我们可以看出哪些单词可能被同时使用,哪些不太可能被同时使用,这就是列聚类(如果商店使用列聚类算法得出哪些商品用户经常同时购买,那么就能很方便的进行捆绑销售了。。。)。。。
我们上面所进行的是行聚类,在行聚类中,行是博客列是单词;如果要进行列聚类又要直接使用上面的算法,那么最好的办法(也是最简单的办法)就是,将上面的数据进行反置,使,行代表单词,列代表次数。
我们上面的格式是:
第一行:Blog xxx xxx xxx xxx (xxx为出现在各博客中的合理单词)
第二行:blogname 000 000 000 000 (000为第一行中各个单词在博客blogname中出现的次数,可能为0次)
........ .......(同第二行)
那么我们更改后的格式如:
第一行:Blog xxx xxx xxx xxx (xxx为出现在各博客)
第二行:单词 000 000 000 000 (000为该单词出现于列所代表的博客中的次数)
........ .......(同第二行)
总结行聚类和列聚类:
当数据项的数量比变量多的时候,出现无意义聚类的可能性就会增加。由于单词的数量比博客多得多,因此,在博客聚类中出现的模式要比在单词聚类中出现的更加合理。
总结分级聚类算法:
分级聚类将聚类表示成了直观的树状图。
两个缺点:
1.树状图并不会真正拆分数据,树状图只是表示出了该关系。(可以说,该关系并不能保存持久。。。)
2.结算量太过可观。。。