1 缘起
其实开展文本主题特征抽取这个方面的工作,已经近一个多月了。在此之前,部门内部对于数据挖掘、机器学习这块的积累还是比较薄弱的。
经过一个多月在这方面的实践、与行业内相关同行的交流以及经历接触的一些东西,我发现还是有些东西可以拿出来做分享的。
虽然有些东西看起来并不是很高端的东西,但是,从工程化的角度来说,个人认为还是值得借鉴参考的。
2 文本主题特征抽取实践
2.1 主题特征抽取能干嘛?
在当前个性化推荐大行其道的时候,那就不得不提用户画像。关于用户画像,不久之前跟一个朋友聊过这方面的话题,他有个观点我很认可。
类似CSDN这种以内容驱动的社区网站,想直接做用户画像是比较困难的。首先这种网站用户的粘度不大,大部分都是游客;其次用户本身的固有属性很难获取到,或者说即使获取到了,也是不够准确的。
那OK,如何解决这个问题呢?那我们只能从“用户到底干了啥”这个角度,尝试去描述一个用户了。那么,我们想要知道一个用户“干了啥”,那么我们首先需要确定“他干的事到底是啥”。
换言之,我们需要知道他写的文章是什么文章,他看的文章是什么文章。然也,我们先得清楚文章的主题,我们得清楚文章的标签。
文章的标签准了,那么通过用户对标签的行为,那么,试想一下,用户的标签还不容易吗?
所以,与天猫、京东类似的电商不一样(他们的物品已经高度分类以及标签化,并且用户数据详细而准确),以内容为王的技术社区想要做好用户画像,那么,物品画像的构建是重中之重。
而对于物品画像的构建,标签系统又是重中之重。对于标签系统的建立,那么,自然少不了文本主题特征的抽取。
2.2 简单说一说算法实践吧!
2.2.1 TF-IDF模型
说到文本主题特征抽取,则不得不说的TF-IDF模型,这是一种最常见也是最通用的文本关键词提取模型。OK,我们来客观的描述一下,在一个文本中,什么样的词才能算的上是关键词呢?也就是能够体现主题的关键词。
答案显然是否定的,因为这种词结结实实地对我们的主题描述毫无作用,所以它有一个比较专业的名称,那就是“停用词”。通常,我们在处理文本的时候,首先需要过滤掉的就是停用词。
OK,抛开停用词不说,我们以CSDN技术博文为例,其实很多词例如“JAVA”、“开发”、“程序”、“函数”等出现的频率相当的高。
他们不属于停用词,但总体来说,它们依然很难作为某一篇具体技术文章的主题词,因为描述的太宽泛,不够准确,甚至是偏离主题的。通常这种词,我们习惯性的称之为“公共词”,即在很多地方都经常出现的词。
但我们仔细分析每一个文本,特别是那种富文本(即比较长的文本),会发现,能够体现文章主题的词的确是容易反复被提起的。
根据上面的分析,我们来总结一下,什么样的词更有可能是主题关键词:首先它必须不能是大众词,即尽量的在少数的文本中出现;其次它在该文本中出现的次数必须足够的多。
有了分析结果,那么剩下的事情就好办多了,根据如上两个规律,我们来设计模型。
我们通过TF,也就是某个词在文本中出现的频度,来提升这个词在主题中的权重,然后我们通过IDF值,即逆向文档频来降低公共词的主题权重。TF*IDF也就得到了我们要的主题词权重,然后根据权重值排序,截取N个我们要的词作为文本的主题词。
可能大家对TF好理解点,对IDF依然不甚理解。我们来看一下它具体的计算方式,我们就明白了。
计算过程:
词频(TF)=某个词在文本中出现的次数/该文本中总词数
或者有另一种变种的计算方法。
词频(TF)=某个词在文本中出现的次数/该文本中出现次数最多的词其出现次数
我们再来计算逆向文档频(IDF)。
逆向文档频(IDF)=log(语料库中所有文档总数/(包含某词的文档数+1))
//这里需要注意的有三点:
(1)一是在我们的词表与语料库没有直接关系时,分子中+1就很有必要了,因为很难保证词表中的某个词在语料库中一定会出现,反之,如果词表是通过语料库整理而出,则没有这个问题;
(2)这里取log对数,我们可以取log10,log2甚至是log e,根据实际效果调整即可;
(3)第三,这里所指的词个数,一定是刨除了停用词的计算,甚至是专门的词表;
最后,我们计算TF-IDF,只需要将计算的TF值与IDF值累乘就得到了我们需要的权重值,然后经过再次排序,就能知道词的重要程度了。
无聊的理论过后,我们来说说实践吧:
我们接着来说一说实际的实践吧。其实在此之前,部门内部已经有一定的累积了,最直接的体现就是有几个不同筛选程度的词表,例如通用8W词词表,技术技能8000词的专用词表等等。
说到这,我就不得不说一说词表的重要性了。由于某种特定业务的需要,我们需要对于CSDN中特定部分技术文章进行主题词抽取。
本着前人栽树后人乘凉的原则,我们直接拿起之前整理的通用词表,很快乐的进行文本去噪后抽词,然后TF-IDF模型自然是没有什么难度的。
然后抽取结果抽样进行评测,结果显然是差强人意的,不然就不会有后面的故事了。这使得我一度质疑TF-IDF模型的权威性。
不说准确度多少吧,好歹你别漏这么多关键词啊,而且权重排序好歹靠谱点啊。在与一个朋友(好吧,其实这货就是微博@祝威廉二世,很流弊的一只人)交流过后,经他提醒,我意识到一个很重要的问题:我们的词表有问题。
构建一个符合当前业务场景的专用词表非常重要!
我们使用CSDN内部通用8W词表去做特殊业务场景的主题抽取,能够好才怪呢!于是,我竭力说服众人去一个事:为这个项目整理一个专门的词表(至于说为何要说服他们,嘿嘿,不说服他们,谁帮我进行人工整理O(∩_∩)O~)。
于是,我在100G的技术博文中,过滤出针对这个业务场景的76W篇文章,然后进行常规的分词处理,最终处理出一份近8W词并且带文档频的词表。
其实,在特定的业务场景下,其总词数也是远远低于通用总词数的。随后,我把任务下发到8个人手里,哈哈,没人领了1W词回去(当然也包括我),进行人工筛词。
在众人看得眼花缭乱之后,终于整理出一份5W词左右的词表。然后替换词表之后,重新进行结果测试。
从个人抽样结果来看,效果真心好了不止一筹。其实我们仔细分析一下会发现,替换一个符合业务场景的词表主要体现在以下两个方面:一是筛除了很多的干扰词;二是TF-IDF模型中,符合业务的真实词分布相当重要。
除此之外,需要提一下的就是,我们在进行人工筛词的时候,适当的看中名词与动词,而形容词与副词之类的,其实大部分情况下是可以筛除掉的。
然后,在考虑词上下文的时候,由于CSDN技术博文很难像正规论文那样,通过类似文章头部尾部来体现部分词的加权,但是例如文章标题其实是可以考虑加权的。
不管文章是不是一篇正规文章,标题大部分时候都是主题高度凝练的体现。
2.2.2 TextRank算法
关于TextRank算法,可能了解的人稍微的少一点。它是一种非监督式的主题抽取算法,即它可以不依赖于其他语料,直接从文本中抽取主题词。
说到TextRank算法,我们先来说一说PageRank算法。PageRank算法想必不少人都熟悉,它是一种很有名的网页排名算法。
PageRank的核心思想:一个网页的重要程度取决于链接到它的网页的数量以及这些网页的重要程度。即每一个网页的重要程度都是通过其他网页来体现的,而它的重要程度又反过来影响它外链中的网页重要程度。
而TextRank算法则是借用了PageRank的核心思想,其把词看做是一个网页,而词与词之间的联系则通过词之间的距离来表示,任何一个词与其左右为5的词都是类似外链关系的。通过不断的权重计算,并且移动词排序,重新迭代计算权重,最终达到一定的收敛即停止。
算法实践:
我在MapReduce中跑了一批数据,其实其效果,我个人认为还行,特别是使用专用词表进行杂词过滤之后,效果还是可以的。
这是一种比较容易实现,并且相对比较简单的主题抽取算法。所以,如果在主题抽取方面不愿意投入过多资源的,可以适当的考虑。
但是,我们需要注意的是,由于它的实现机制,一方面它如TF-IDF算法那样,在富文本领域表现较佳,并且词频对于其结果影响较大;另外一方面就是由于每次它都需要进行迭代计算,所以效率上会有所降低,特别是迭代次数越多的时候。
可以作为一种备用选择吧。
2.2.3 LDA主题模型
先简单的说一下LDA的核心思想:我们认为每一个文档Doc都是由多个主题Topic组成,而每一个主题Topic由多个词Word组成。
通过对语料库D中所有的文档d进行分词或者抽词处理之后,通过模型训练(具体的训练过程我就不写了,有点复杂,而且我不一定能够写清楚),我们得到两个概率矩阵:一是每一个Doc对应K个Topic的概率;二是每一个Topic对应N个词组成的词表的概率。
有了这两个概率矩阵,那么,我们需要的是每一个Doc对应的Word概率,然后找M个概率最大的词作为关键词。有Topic作为中间桥梁,那还有什么问题么?
并且,从这里,我们会发现,通过LDA模型进行关键词查找,其关键词不再依赖于词是否出现在该文档中,这点与传统的基于统计模型的主题抽取方式是不一样的。
算法实践:
在写这篇文章的时候,我们刚把LDA结果跑出来,词表大约5W左右,K值设置的大概是200,迭代次数为100左右。出来的结果简直惨不忍睹。
具体的方式也可以稍微的说一说,我们使用了Spark MLlib上的LDA实现。
(1)DistributedLDAModel类中有describeTopics方法,可以获取到每一个Topic对应的词Word概率矩阵;
(2)DistributedLDAModel类的topTopicsPerDocument方法可以获取到每一个Doc对应的K个Topic概率矩阵;
(3)有了如上两个矩阵,求Doc到Word的概率矩阵不是手到擒来?
(4)对于新文本,DistributedLDAModel类同样提供了topicDistributions方法进行新文档对应于K个Topic的概率预测;
参数肯定还是会继续调整的,但从我个人的角度上来说,我们目前的业务场景并不是很适合LDA算法模型,首先个人认为K值会比较大,因为K值的设置需要尽量的跟实际情况吻合,而我们的业务场景恰巧是主题分散的类型。
其次,LDA主题模型是三层概率模型,通过中间层进行桥接,概率映射的精度会进一步下降,所以,个人认为在富文本的场景下,直接的统计模型不会输于LDA。
再其次感觉就是LDA对干扰词相当的敏感,当然,也或许是我们没有用好。总之,我们会继续的进行探索。
2.2.4 TF-IDF与TextRank的融合
我个人还尝试使用TF-IDF的值对TextRank权重进行修正,目前最简单的尝试就是直接类似TF与IDF的融合,直接进行累乘,其实效果还OK啦。
不过,通过与祝威廉的交流,他认为一个比较理性的融合方式是,如果我们有足够的样本,我们可以通过类似的线性回归的方式,来确定两种算法的结果权重,来计算最终的词权重值。
这个有机会,有人力物力的话,真心可以考虑试试。
3 一些文本主题抽取方面的构想
3.1 社会化词表机制
社会化标签或许不少人听过,但社会化词表是什么东西?
先别着急,我们先来说一说社会化标签。据个人所知,豆瓣是社会化标签的重度使用者。我们来看一下社会化标签的运作。
网站为用户呈现物品时,呈现的是社会化标签,而每一个人都可以为物品打上若干标签,但物品只是动态的显示TopN个最热标签。
随着参与打标签的用户越来越多,物品的标签主题描述就会越来越趋近于准确,而相应的,利用标签做的一些推荐或者个性化定位也会随之越来越准确。
在之前的实践中,我们很明确的了解到,一个适合的词表对于主题抽取的准确率有多大的影响。
在当前词表的基础上,我所说的合适词表有两层意思:一是进一步筛除干扰词,二是随着业务变更,增加新词。
在之前的实践中,我并没有提及太多对词操作的事,例如分词、新词发现之类的。其实针对于一些新词,我们是有一些方法进行判断是否为新词的。
例如,我们在判断一个字典中没有的词是否能够成为一个新词时,我们可以通过语料,计算该词与其左右两边词的信息熵。
信息熵的意义是,随着熵值越大,代表该词与左右词的组合信息越混乱,那就是说把他们组合在一起使用是不合理的。
经典实例如“被子”,我们可以找到诸如“叠被子”、“盖被子”、“加被子”、“新被子”、“掀被子”、“收被子”、“薄被子”、“踢被子”、“抢被子”等100多种组合,计算“被子”与左组合词的信息熵,我们会发现很大。
这说明本身“被子”一词不适合和作词组合成一个单独的词,他应该可以算一个单独的词来使用。
反之,我们会发现,一些词,例如“辈子”这个词,与他组合的词,诸如“一辈子”、“下辈子”、“上辈子”、“半辈子”、“八辈子”、“几辈子”、“哪辈子”等。其实不算很多,而且词频也不会太大。
计算“辈子”与左边组合词的信息熵,相比于“被子”一词,则不一样,其信息熵很小。也就意味着“辈子”与左边的词组合信息混乱度很小,这意味着,它与左侧词组合,形成一个组合词更有可能是一个单独词。
除了计算词左右组合词的信息熵,当然也还有其他办法来判断一个词是否作为新词去处理。
但在我们的实践里,我没有在新词发现与分词上做过多的事情。个人的观点是,在分词上投入过多,其实效果是很小的。因为这个领域已经属于基础领域中的基础领域,除非你愿意投入相当大的精力,不然效果是很低很低的,从工程化的角度来说,得不偿失。
同理,在新词发现领域,也一样,属于复杂度更高的研究,并且不容易出效果。
那么,有没有便捷的方法去解决词表更新的问题呢?
答案是显然的,我们可以依靠众人的力量去更新词表,这比少数几个专业人士的效率更高。
我们主题特征的一个重要应用就是标签系统,我们可以模仿社会化标签操作的手法。我们给用户推荐物品的标签,同时允许用户在推荐标签的基础上进行增删去留。
当用户的操作量累积到一定程度的时候,比如,用户会添加一些新词进去,又诸如,一些干扰词,我们可以通过用户经常删除该推荐词的操作得以发现。
所以,如果这个机制做好了,这就是一个良性的循环过程。其次,还有一个我一直很坚持的观点就是:算法真心没有想象中流弊,我们需要结合用户的力量对算法效果进行修正。
3.2 一种Word2vec边权改进的TextRank算法
说这个之前,简单说一说这个点子的来源,其实包括上面提到的TextRank与TF-IDF算法融合思想,都是通过与一位朋友交流得来的。
12月12号的时候,在2015大数据大会BDTC的活动中,有一个分场,那就是“IBM算法马拉松挑战赛年度总决赛”,我作为大赛评委参与了大赛的评判工作。
算法挑战赛的比赛题目就是“文本主题特征词抽取”,是不是有点巧。其实真的不巧,因为这题目是我出的。
当然,这不是重点,重点是比赛选手的作品情况。其实总体来说,比赛的结果不甚理想,不过想想也是,把一个研究多年课题,要在六七个小时内把它实际工程化,的确是挺难的。
不过,有些点子还是不错的,比如一等奖的获奖团队他们的想法就很好,就是通过语料计算词的IDF,然后通过IDF进行词过滤,在词过滤的基础上改进TextRank效果。
大赛过后,我联系了他们的领队,进行了进一步沟通交流,他提出来Word2Vec与TextRank结合的观点,即下面一些内容就是在此基础上一些发散(这进一步说明了技术交流的重要呐)。
对于TextRank算法过程,上面我们已经大概说过了。并且在实际的实践中,我也尝试使用TF-IDF算法模型结果对TextRank结果进行融合,但效果不会太好。
关于Word2vec,简单的说一下。它是一种以三层神经网络为底层支撑的词向量表征模型。具体的算法模型过程,我就不做过多的篇幅描述了,有兴趣的可以去查找一下资料。
我们在word2vec上最直接的一个应用就是,通过语料训练,我们可以获取到词表中任何一个词对应于其他词的相似度。
OK,我们再回到TextRank,它在计算词的重要程度时,针对于每一个词,只把左右邻居中5个词作为参考对象,并且参考度,也就是词与词的边权都是1。
我们有了Word2vec中词向量,我们是否能在计算词与词参考度上进行优化呢?比如TextRank中只参考词左右N个词作为“外链词”,并且词与词之间的“外链权重”都是1,其实这是有很大优化空间的。
比如,我们可以根据词与词之间的距离的增加,减小词与词之间的边权,然后边权的计算也不单纯依赖于距离,我们使用Word2Vec计算出的词与词的相似度作为边权。
这个思考是一个构想,没有付诸具体的实践,所以也没法保证结果。但之后有时间一定要实践一下,预计结果会好一些。