「NLP」用python实现基于凝固度和自由度的新词发现程序
收藏

今天写篇技术硬文,看看大家喜不喜欢。互联网每年甚至每时刻都会催生很多新词(在新华词典里找不到的词语),比如"双一流"、"佛系"、"up主"、"大数据杀熟"等等,而做中文自然语言处理的基础是中文分词,中文分词的精准度是做自然语言处理的基础,比如(语音识别,文本倾向性分析,自动问答机器人,人工智能写自动文章等等)。


常用的中文分词方法是基于词库性质(比如结巴分词),如果一个词语没在这个词库里,那分词的时候,这个词语是肯定分不到一起的。要保证基于词库方式的分词效果好,就要持续的更新词库,也就是要有发现新词的能力。


这篇就来讨论发现新词的一种算法。


n-gram加词频
最原始的新词算法莫过于n-gram加词频了。简单来说就是,从大量语料中抽取连续的字的组合片段,这些字组合片段最多包含n个字,同时统计每个字组合的频率,按照词频并设置一个阈值来判断一个字组合片段是否为词汇。
该方法简单处理速度快,它的缺点也很明显,就是会把一些不是词汇但出现频率很高的字组合也当成词了。

凝固度和自由度
这个算法在文章《互联网时代的社会语言学:基于SNS的文本数据挖掘》 里有详细的阐述。
凝固度就是一个字组合片段里面字与字之间的紧密程度。比如“琉璃”、“榴莲”这样的词的凝固度就非常高,而“华为”、“组合”这样的词的凝固度就比较低。
自由度就是一个字组合片段能独立自由运用的程度。比如“巧克力”里面的“巧克”的凝固度就很高,和“巧克力”一样高,但是它自由运用的程度几乎为零,所以“巧克”不能单独成词。

Python实现
根据以上阐述,算法实现的步骤如下:

1. n-gram统计字组合的频率
如果文本量很小,可以直接用Python的dict来统计n-gram及其频率。一段文本n-gram出来的字组合的大小大约是原始文本的(1+n)*n/2倍,字组合的数量也非常惊人。比如,“中华人民共和国”的首字n-gram是(n=5):
中华中华人中华人民中华人民共

n-gram统计字组合频率的同时还要统计字组合的左右邻居,这个用来计算自由度。

如果文本量再大一些,Python的dict经常会碰到最好使用trie tree这样的数据结构。双数组Trie Tree有很多非常好的开源实现,比如,cedar、darts等等。Trie Tree使用的好处是,它天然包含了字组合的右邻居信息,因为这个数据结构一般是前缀树。要统计左邻居信息时,只需要把字组合倒序放入另外一个Trie Tree即可。

使用cedar Trie Tree的时候,5-gram统计30M的文本大约使用6GB左右的内存。
如果文本量更大,这就要借助硬盘了,可以使用leveldb这样的key-value数据库来实现。实验下来,trie tree统计30M的文本用几十秒,而同样的用leveldb统计却要6个多小时!!!应该还有比leveldb更合适的数据库来做这件事情,有时间再说。

当然,为了发现新词,几十MB的文本文本足够了。

2. 计算字组合的凝固度;
有了上述的统计结果,计算每个字组合的凝固度就比较简单了。
首先,把字组合切分成不同的组合对,比如’abcd’可以拆成(‘a’, ‘bcd’), (‘ab’, ‘cd’), (‘abc’, ‘d’),
然后,计算每个组合对的凝固度:D(s1, s2) = P(s1s2) / (P(s1) * P(s2))
最后,取这些组合对凝固度中最小的那个为整个字组合的凝固度。

3. 计算字组合的自由度;
分别计算它的左邻居信息熵和右邻居信息熵,取其中较小的为该组合的自由度。

4. 阈值的选择
整个过程涉及到三个阈值的选择:
组合的词频:频率很低的组合成词的可能性很小
组合的凝固度:凝固度越大成词的可能性越大
组合的自由度:自由度越大成词的可能性越大
经验值:30M文本,词频>200, 凝固度>10**(n-1), 自由度>1.5
小窍门:词频>30, 凝固度>20**(n-1)也能发现很多低频的词汇。


公众号