4.2 在主题空间比较相似度
要构建像前图那样的词语小插图,主题本身就很有用处。这些可视化图形可以在较大的文档集合中为人们指引方向。事实上,它们已经被用于此处了。
然而,主题经常会被当做是一种实现另外一个目标的中间工具。既然对每篇文档我们都预估了它来自于每个主题的可能性,那么可以在主题空间中比较两篇文档。这意味着,我们要根据两个文档是否描述相同主题来判断它们是否相似,而不是通过词与词的比较。
这是很有威力的,因为两个只有少量公共词语的文本文档实际上可能是在说同一个主题。只是用了不同的阐述方式(例如,一个人在说美国总统,而其他人使用了巴拉克•奥巴马这个名字)。
提示 主题模型本身对可视化和数据探索都很有用。在其他一些任务中,它们也充当着非常有用的中间步骤。
在这里,我们重新做一次前章中所做过的练习,通过主题来寻找最相似的帖子。鉴于之前我们通过词向量比较了两个文档,现在我们再通过主题向量来比较一下这两个文档。
对此,我们把文档映射到主题空间。这是说,我们要构造一个主题向量来概括这个文档。由于主题的个数(100)比可能的词语的个数要小,所以我们已经把维度降低了。不过如何进行通用的数据降维本身就是一个重要问题。我们会用一整章的内容来介绍它。除了降低了维度以外,它在计算上还有一个优势,那就是,比较100维的主题权重向量,要比比较词表大小的向量快得多(词表中包含成千上万的词语)。
我们通过gensim 可以看到之前语料中所有文档的主题是如何计算的:
>>> topics = [model[c] for c in corpus] >>> print topics[0] [(3, 0.023607255776894751), (13, 0.11679936618551275), (19, 0.075935855202707139), (92, 0.10781541687001292)]
我们用NumPy的数组来存储所有的主题统计数据,并计算两两之间的距离:
>>> dense = np.zeros( (len(topics), 100), float) >>> for ti,t in enumerate(topics): … for tj,v in t: … dense[ti,tj] = v
这里,dense 是一个主题的矩阵。我们用SciPy中的pdist 函数来计算两两之间的距离。这是说,通过调用一个函数,我们就可以计算出所有sum((dense[ti] – dense[tj])**2) 的值:
>>> from scipy.spatial import distance >>> pairwise = distance.squareform(distance.pdist(dense))
现在,采用最后一个小技巧,把距离矩阵对角线上的元素都设成较大的值(只要比矩阵中其他值都大就可以):
>>> largest = pairwise.max() >>> for ti in range(len(topics)): pairwise[ti,ti] = largest+1
完成了!针对每一篇文档,我们轻而易举就可以找到最接近的一个元素:
>>> def closest_to(doc_id): return pairwise[doc_id].argmin()
注意 如果我们没有将矩阵对角线上的元素设置成较大的值,前面这段代码将不会工作;这个函数总会返回相同的元素,这是由于跟它最相似的文档就是它自己(除非出现一种很诡异的情况,那就是两个元素具有完全相同的主题分布,但很少发生,除非它们两个是完全一样的文档)。
例如,这里是第二个文档(第一个文档没有多大意义,因为系统返回了一个跟它非常相似的文档):
From: geb@cs.pitt.edu (Gordon Banks) Subject: Re: request for information on "essential tremor" and Indrol? In article <1q1tbnINNnfn@life.ai.mit.edu> sundar@ai.mit.edu writes: Essential tremor is a progressive hereditary tremor that gets worse when the patient tries tuse the effected member. All limbs, vocal cords, and head can be involved. Inderal is a beta-blocker and is usually effective in diminishing the tremor. Alcohol and mysoline are alseffective, but alcohol is totoxic tuse as a treatment. ----------------------------------------------------------------Gordon Banks N3JXP | "Skepticism is the chastity of the intellect, and geb@cadre.dsl.pitt.edu | it is shameful tsurrender it tosoon." ----------------------------------------------------------------
如果寻找最相似的文档closest_to(1) ,那么我们将得到如下文档:
From: geb@cs.pitt.edu (Gordon Banks) Subject: Re: High Prolactin In article <93088.112203JER4@psuvm.psu.edu> JER4@psuvm.psu.edu (John E. Rodway) writes: >Any comments on the use of the drug Parlodel for high prolactin in the blood? >It can suppress secretion of prolactin. Is useful in cases of galactorrhea. Some adenomas of the pituitary secret tomuch. ------------------------------------------------------------------ Gordon Banks N3JXP | "Skepticism is the chastity of the intellect, and geb@cadre.dsl.pitt.edu | it is shameful tsurrender it tosoon." ----------------------------------------------------------------
我们得到的是一个相同作者写的关于药物治疗的帖子。
对整个维基百科建模
最初的LDA实现可能运行得有些缓慢,而现代系统需要对很大的数据集进行运算。在下面的gensim 文档中,我们要为整个英语版的维基百科(Wikipedia)构建一个主题模型。它的运行时间需要几个小时,不过即使使用不是很强大的机器也可以完成。如果使用计算机集群,我们就可以大大加快运算速度,后面会有一章详细介绍这个处理过程。
首先,我们从http://dumps.wikimedia.org 下载整个维基百科。这是一个很大的文件(现在已经超过9 GB),所以需要花费一定时间,除非你的网速非常快。然后,我们用一个gensim 工具对它建立索引:
python -m gensim.scripts.make_wiki enwiki-latest-pages-articles.xml.bz2 wiki_en_output
在命令行中(而不是Python交互窗口)运行上面这个命令。索引过程经过几个小时就会完成。最后,我们可以去构建最终的主题模型了。这一步正如我们在小规模AP数据集上所做的那样。首先引入一些程序库:
>>> import logging, gensim >>> logging.basicConfig( format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
现在,我们将预处理好的数据读入:
>>> id2word = gensim.corpora.Dictionary.load_from_text('wiki_en_output_wordids.txt') >>> mm = gensim.corpora.MmCorpus('wiki_en_output_tfidf.mm')
最后,像以前那样构建出LDA模型:
>>> model = gensim.models.ldamodel.LdaModel( corpus=mm, id2word=id2word, num_topics=100, update_every=1, chunksize=10000, passes=1)
这还会耗费几个小时。(可以在控制台上观察到这个过程,它会给出一个提示,告诉你还需要等待多少时间。)一旦完成,你可以把结果保存到文件里,以后无需再重复做这件事:
>>> model.save('wiki_lda.pkl')
如果退出会话,然后过会儿再回来,你还可以把模型读出来:
>>> model = gensim.models.ldamodel.LdaModel.load('wiki_lda.pkl')
让我们探究一下其中的一些topics :
>>> topics = [] >>> for doc in mm: topics.append(model[doc])
可以看到它依然是一个稀疏的模型,即使比以前拥有更多的文档(在撰写到这里的时候,已经多于400万):
>>> import numpy as np >>> lens = np.array([len(t) for t in topics]) >>> print np.mean(lens) 6.55842326445 >>> print np.mean(lens <= 10) 0.932382190219
所以,平均下来每个文档只涉及6.5个主题,其中93%的文档涉及的主题数小于等于10。
注意 如果你之前没看到过这些习语,可能会对计算一个比较运算的均值感到奇怪。但这是计算所占比例的直接方法。
np.mean(lens<=10) 计算了一个布尔数组的均值。用数字来解释的话,这些布尔值就是一些0和一些1。因此,计算结果的值域在0到1之间,它是1所占的比例。在这里,它就是lens 数组中小于等于10的元素所占的比例。
我们还可以查询出维基百科中最常谈论的主题有哪些。首先收集一些主题使用情况的统计信息:
>>> counts = np.zeros(100) >>> for doc_top in topics: … for ti,_ in doc_top: … counts[ti] += 1 >>> words = model.show_topic(counts.argmax(), 64)
通过之前使用的可视化工具,我们可以看到最常谈论的主题是小说和故事,还有书籍和电影。考虑到多样性,我们选择了不同的配色方案。维基百科中多达25%的页面都与这个主题有部分关联(换句话说,5%的词语来自于这个主题):
注意 这些图和数字是2013年年初作者撰写本书时得到的。但是由于维基百科一直在变化,你得到的结果可能会有所不同。特别是,最不相关的主题可能已经变了,但和前述主题很接近的主题很可能依然在列表中排名很高(即使它并不是最重要的主题)。
或者,我们来看下最不常讨论的主题:
>>> words = model.show_topic(counts.argmin(), 64)
最不常讨论的主题是前法属中非殖民地。只有1.5%的文档涉及了这个主题,0.08%的词语来自于这个主题。或许,如果在法语维基百科上进行测试,我们将会得到一个完全不同的结果。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论