正在加载
请稍等

菜单

Home 项目 基于凝聚度和自由度的非监督词库生成
Home 项目 基于凝聚度和自由度的非监督词库生成

基于凝聚度和自由度的非监督词库生成

by   阅读量 7,329

中文分词是中文文本自然语言处理的第一步,然而分词效果的好坏取决于所使用的语料词库和分词模型。主流的分词模型比较固定,参见中文分词一些思路的总结,而好的语料词库往往很难获得,并且大多需要人工标注。这里介绍一种基于词频、凝聚度和自由度的非监督词库生成方法,什么是非监督呢?输入一大段文本,通过定义好的模型和算法,即可自动生成词库,不需要更多的工作,听起来是不是还不错?

参考文章:互联网时代的社会语言学:基于SNS的文本数据挖掘

1 获取所有的备选词语

假设对于一段很长的文本,例如《西游记》的全文,这里提供了西游记utf-8西游记gbk两个版本,我在mac上进行处理,因此使用的是utf-8版本,我关注的最大词语长度为5,因此可以使用正则匹配出全部的单个汉字、双汉字、三汉字、四汉字、五汉字,作为可能的备选词语。

由于python中的re模块进行的是非重叠匹配,因此在匹配多汉字词语时返回的数量会有遗漏,以下是python的re模块官方文档中的说明。

所以我用的是python的regex模块,可以进行多汉字的重叠匹配。

因此使用正则表达式,可以很方便地提取出全部可能的备选词语。

2 词频

一个很直观的认识是,真正的词语应该至少出现一定次数,而那些局部切割出来的碎片出现次数则较少,因此可以统计一下以上全部可能备选词语的词频,作为我们判断是否成词的第一个标准。

对于《西游记》而言,一共出现了4459个汉字,而长度不超过5个汉字的全部可能备选词语共824567个。为了得到这些词语的词频,我写了一个循环,挨个在《西游记》中查找每一个词的词频。但是事实证明这样相当浪费时间,因为这个循环需要进行824567次!所以更好的方法是,同样还是使用regex匹配单汉字、双汉字、三汉字、四汉字和五汉字词语,只不过不进行set、list的去重操作,这样返回的匹配结果中便包含了全部备选词语的词频,而且一共只需执行五次正则匹配,所需时间会少很多。

3 聚合度

我们已经得到了全部可能备选词语的词频了,但这并不是判断成词的全部标准。自然语言处理中有“停用词”的概念,也就是那些使用频繁,但是不包含有用信息的词语,如“的”、“了”、“着”等,因此还需要计算更多用于判断是否成词的标准。

聚合度的概念很好理解,例如“齐天”会和“大圣”出现在一起,因为“齐天大圣”这个词的聚合度很高。同样,我们还能想到类似“葡萄”、“蝙蝠”、“师父”这样的词,这些词往往作为一个整体而出现,说明它们确实是有意义的词语。

那么如何计算词语的聚合度呢?假设该词语为S,首先计算该词语出现的概率P(S),然后尝试S的所有可能的二切分,即分为左半部分sl和右半部分sr并计算P(sl)和P(sr),例如双汉字词语存在一种二切分、三汉字词语存在两种二切分。接下来计算所有二切分方案中,P(S)/(P(sl)×P(sr))的最小值,取对数之后即可作为聚合度的衡量。

以双汉字词语为例,可以想象到,如果该词语的聚合度很低,说明其第一个字和第二个字的相关性很弱,甚至是不相关的,那么P(S)和P(sl)×P(sr)将处于同一个数量级。相反,如果该词语的聚合度很高,“齐天”、“大圣”和“齐天大圣”三者的概率都很接近,因此P(S)/(P(sl)×P(sr))将是一个远大于1的数值。

取对数有三个好处:

  • 避免概率过低造成下溢出;
  • 将取值范围映射到更平滑的区间中;
  • 当P(S)和P(sl)×P(sr)处于同一个数量级时,P(S)/(P(sl)×P(sr))接近1,取对数后为0,对应一个很低的聚合度。

通过以上方式,我们可以计算全部可能备选词语的聚合度。有意思的是,《西游记》的824567个备选词中,对应的聚合度范围为-6至20。为什么会出现负数呢?说明P(S)比P(sl)×P(sr)更小,即sl和sr同时出现的可能性更低,因此别提聚合,可能还存在某些排斥。

在具体实现上,由于上一步中已经计算了全部可能备选词语的词频,当然也包括全部的二切分词,因此计算词语的概率以及聚合度都是很方便的。

4 自由度

有了聚合度的概念,我们可以自动识别出类似“齐天大圣”这样的词语。那么问题来了,“天大圣”是一个有效词语吗?“齐天大”呢?可以推测到,这两个词语的聚合度也很高,但是它们却不应该成为有效的词语。

一个有效的词语,应该能够被灵活地运用到各种语句中,其上下文搭配应当是丰富的,这便是自由度的概念。“天大圣”的左边绝大多数都是“齐”,“齐天大”的右边绝大多数都是“圣”,因此它们的自由度很低。

可以用熵来衡量一个词语的自由度。假设一个词语一共出现了N次,其左边共出现过n个汉字,每个汉字依次出现N1,N2,……,Nn次,则满足N = N1 + N2 + …… + Nn,因此可以计算该词语左边各个汉字出现的概率,并根据熵公式计算左邻熵。熵越小则自由度越低,例如“天大圣”的左邻熵接近于0,因为“齐”字的概率几乎为1;熵越大则自由度越高,表示用词搭配越混乱、越自由、越多样。因为“天大圣”的左邻熵很小,而右邻熵则相对较大,因此我们将一个词语左邻熵和右邻熵中较小者作为最终的自由度。

通过以上方式,我们可以计算出全部可能备选词语的自由度,《西游记》的824567个备选词中,对应的自由度范围为0至8,这和熵的非负性相吻合。

在具体实现中,一开始我也是写了个循环,对《西游记》的824567个词语各写一个正则,匹配出其左右可能搭配的汉字,结果依旧非常耗时。更好更快的解决方案是,依旧使用regex只写五次正则,分别处理单汉字、双汉字、三汉字、四汉字和五汉字,只不过在原来的基础上在两边各加一个字符,然后将全部的匹配结果映射到对应的词语中即可。

5 综合起来

有了频数、聚合度和自由度,就可以对全部可能的备选词语进行筛选了。例如,对频率、聚合度和自由度分别设置阈值,仅保留三项指标都超过阈值的词语。对于筛选过后的词语,可以考虑使用频率,或者频率、聚合度、自由度三者的乘积,作为最终输出的排序指标。基于以上方法,能够很好地去除停用词,并自动生成有意义和有代表性的词语。

《西游记》输出的词语包括行者、师父、八戒、唐僧、菩萨、大圣、和尚、三藏、妖精、沙僧、老孙等,还是很有代表性的。

6 实现

这里提供一个我的corpus.py,里面的CorpusGenerator类构造函数的参数包括待分析的文本content、最大词长maxlen、返回得分靠前的词数量topK、频率阈值tfreq、凝聚度阈值tDOA和自由度阈值tDOF,还提供了以下几个函数:

  • get_characters(),返回一个list,对应全部可能的单字;
  • get_possible_words(),返回一个list,对应长度不超过maxlen的全部可能备选词语;
  • get_frequency(),计算全部可能备选词语的词频,存储在self.result[key][‘freq’]中;
  • get_doa(),计算全部可能备选词语的凝聚度,存储在self.result[key][‘doa’]中;
  • get_dof(),计算全部可能备选词语的自由度,存储在self.result[key][‘dof’]中;
  • get_score(),基于频率、凝聚度、自由度计算全部可能备选词语的得分,存储在self.result[key][‘score’]中;
  • generate(),依次执行以上四个计算函数,并根据得分和topK返回一个list,每个元素即为一个经过筛选的备选词语;
  • distribution(),在执行generate()之后可调用,根据self.result绘制全部可能备选词语的频率、凝聚度、自由度的散点图矩阵,以便查看各参数分布情况。

7 进一步的工作

我们建立了一个基于凝聚度和自由度的非监督词库生成模型,输入一大段文本,可以自动输出符合要求的词语。那么除此之外,我们还可以做些什么?

假设我们的输入不是类似《西游记》这样的大段文本,而是具有时间戳的互联网语料,例如每天发布的用户微博和评论等内容。通过对每一天的UGC(User Generated Content,用户产生内容)进行成词,我们可以发现每天的热点词语有哪些。如果使用更短的时间间隔,则可以进一步发现更为实时的热词结果。

通过对子类别分别进行成词,可以做一些群体画像的工作。例如分别对男性和女性的UGC成词,可以发现男女用词的特点和不同;分别对不同城市和地区的UGC成词,可以发现语言用词的地域变化;分别对不同年龄段的UGC成词,可以发现70后、80后、90后和00后们分别在关注什么。如果文本语料有足够的额外信息,能够进一步做的工作是相当丰富的。

14 2016-08

3条评论

  1. 匿名说道:

    你的dof函数实现是不是有点问题
    self.result[r[1]][‘left’].append(r[0])
    self.result[r[1]][‘right’].append(r[2])
    上面应该是r[1:]
    下面应该是self.result[r[:-1]][‘right’].append(r[-1])
    还是我理解的问题

发表评论