【新词发现】基于SNS的文本数据挖掘、短语挖掘

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

python实现 https://github.com/jtyoui/Jtyoui/tree/master/jtyoui/word  这是一个无监督训练文本词库与分词 (转载)java

java实现 https://gitee.com/tyoui/jsns 这个速度要快一点。逻辑比较清楚些(转载)python

 更多实现见文章末尾:c++

实现原理以下:(转载)http://www.matrix67.com/blog/archives/5044git

    今年上半年,我在人人网实习了一段时间,期间获得了不少宝贵的数据,并作了一些还算有意义的事情,在这里和你们一起分享。感谢人人网提供的数据与工做环境,感谢赵继承博士、詹卫东老师的支持和建议。在这项工做中,我获得了不少与众人交流的机会,特别感谢 OpenParty 、 TEDxBeijing 提供的平台。本文已发表在了《程序员》杂志,分上下两部分刊于 2012 年 7 月刊和 8 月刊,在此感谢卢鸫翔编辑的辛勤工做。因为众所周知的缘由,《程序员》刊出的文章被和谐过(看到后面你们就自动地知道被和谐的内容是什么了),于是我决定把完整版发在 Blog 上,同时与更多的人一同分享。对此感兴趣的朋友能够给我发邮件继续交流。好了,开始说正文吧。程序员

    做为中文系应用语言学专业的学生以及一名数学 Geek ,我很是热衷于用计算的方法去分析汉语资料。汉语是一种独特而神奇的语言。对汉语资料进行天然语言处理时,咱们会遇到不少其余语言不会有的困难,好比分词——汉语的词与词之间没有空格,那计算机怎么才知道,“已结婚的和还没有结婚的青年都要实行计划生育”究竟说的是“已/结婚/的/和/还没有/结婚/的/青年”,仍是“已/结婚/的/和尚/未/结婚/的/青年”呢?这就是所谓的分词歧义难题。不过,如今不少语言模型已经能比较漂亮地解决这一问题了。但在中文分词领域里,还有一个比分词歧义更使人头疼的东西——未登陆词。中文没有首字母大写,专名号也被取消了,这叫计算机如何辨认人名地名之类的东西?更惨的则是机构名、品牌名、专业名词、缩略语、网络新词等等,它们的产生机制彷佛彻底无规律可寻。最近十年来,中文分词领域都在集中攻克这一难关。自动发现新词成为了关键的环节。github

    挖掘新词的传统方法是,先对文本进行分词,而后猜想未能成功匹配的剩余片断就是新词。这彷佛陷入了一个怪圈:分词的准确性自己就依赖于词库的完整性,若是词库中根本没有新词,咱们又怎么能信任分词结果呢?此时,一种大胆的想法是,首先不依赖于任何已有的词库,仅仅根据词的共同特征,将一段大规模语料中可能成词的文本片断所有提取出来,无论它是新词仍是旧词。而后,再把全部抽出来的词和已有词库进行比较,不就能找出新词了吗?有了抽词算法后,咱们还能以词为单位作更多有趣的数据挖掘工做。这里,我所选用的语料是人人网 2011 年 12 月前半个月部分用户的状态。很是感谢人人网提供这份极具价值的网络语料。算法


 
 
    要想从一段文本中抽出词来,咱们的第一个问题就是,怎样的文本片断才算一个词?你们想到的第一个标准或许是,看这个文本片断出现的次数是否足够多。咱们能够把全部出现频数超过某个阈值的片断提取出来,做为该语料中的词汇输出。不过,光是出现频数高还不够,一个常常出现的文本片断有可能不是一个词,而是多个词构成的词组。在人人网用户状态中,“的电影”出现了 389 次,“电影院”只出现了 175 次,然而咱们却更倾向于把“电影院”看成一个词,由于直觉上看,“电影”和“院”凝固得更紧一些。
网络

    为了证实“电影院”一词的内部凝固程度确实很高,咱们能够计算一下,若是“电影”和“院”真的是各自独立地在文本中随机出现,它俩正好拼到一块儿的几率会有多小。在整个 2400 万字的数据中,“电影”一共出现了 2774 次,出现的几率约为 0.000113 。“院”字则出现了 4797 次,出现的几率约为 0.0001969 。若是二者之间真的毫无关系,它们刚好拼在了一块儿的几率就应该是 0.000113 × 0.0001969 ,约为 2.223 × 10-8 次方。但事实上,“电影院”在语料中一共出现了 175 次,出现几率约为 7.183 × 10-6 次方,是预测值的 300 多倍。相似地,统计可得“的”字的出现几率约为 0.0166 ,于是“的”和“电影”随机组合到了一块儿的理论几率值为 0.0166 × 0.000113 ,约为 1.875 × 10-6 ,这与“的电影”出现的真实几率很接近——真实几率约为 1.6 × 10-5 次方,是预测值的 8.5 倍。计算结果代表,“电影院”更多是一个有意义的搭配,而“的电影”则更像是“的”和“电影”这两个成分偶然拼到一块儿的。测试

    固然,做为一个无知识库的抽词程序,咱们并不知道“电影院”是“电影”加“院”得来的,也并不知道“的电影”是“的”加上“电影”得来的。错误的切分方法会太高地估计该片断的凝合程度。若是咱们把“电影院”看做是“电”加“影院”所得,由此获得的凝合程度会更高一些。所以,为了算出一个文本片断的凝合程度,咱们须要枚举它的凝合方式——这个文本片断是由哪两部分组合而来的。令 p(x) 为文本片断 x 在整个语料中出现的几率,那么咱们定义“电影院”的凝合程度就是 p(电影院) 与 p(电) · p(影院) 比值和 p(电影院) 与 p(电影) · p(院) 的比值中的较小值,“的电影”的凝合程度则是 p(的电影) 分别除以 p(的) · p(电影) 和 p(的电) · p(影) 所得的熵的较小值。优化

    能够想到,凝合程度最高的文本片断就是诸如“蝙蝠”、“蜘蛛”、“彷徨”、“忐忑”、“玫瑰”之类的词了,这些词里的每个字几乎老是会和另外一个字同时出现,从不在其余场合中使用

 
    光看文本片断内部的凝合程度还不够,咱们还须要从总体来看它在外部的表现。考虑“被子”和“辈子”这两个片断。咱们能够说“买被子”、“盖被子”、“进被子”、“好被子”、“这被子”等等,在“被子”前面加各类字;但“辈子”的用法却很是固定,除了“一生”、“这辈子”、“上辈子”、“下辈子”,基本上“辈子”前面不能加别的字了。“辈子”这个文本片断左边能够出现的字太有限,以致于直觉上咱们可能会认为,“辈子”并不单独成词,真正成词的实际上是“一生”、“这辈子”之类的总体。可见,文本片断的自由运用程度也是判断它是否成词的重要标准。若是一个文本片断可以算做一个词的话,它应该可以灵活地出如今各类不一样的环境中,具备很是丰富的左邻字集合和右邻字集合。

    “信息熵”是一个很是神奇的概念,它可以反映知道一个事件的结果后平均会给你带来多大的信息量。若是某个结果的发生几率为 p ,当你知道它确实发生了,你获得的信息量就被定义为 – log(p) 。 p 越小,你获得的信息量就越大。若是一颗骰子的六个面分别是 1 、 1 、 1 、 2 、 2 、 3 ,那么你知道了投掷的结果是 1 时可能并不会那么吃惊,它给你带来的信息量是 – log(1/2) ,约为 0.693 。知道投掷结果是 2 ,给你带来的信息量则是 – log(1/3) ≈ 1.0986 。知道投掷结果是 3 ,给你带来的信息量则有 – log(1/6) ≈ 1.79 。可是,你只有 1/2 的机会获得 0.693 的信息量,只有 1/3 的机会获得 1.0986 的信息量,只有 1/6 的机会获得 1.79 的信息量,于是平均状况下你会获得 0.693/2 + 1.0986/3 + 1.79/6 ≈ 1.0114 的信息量。这个 1.0114 就是那颗骰子的信息熵。如今,假如某颗骰子有 100 个面,其中 99 个面都是 1 ,只有一个面上写的 2 。知道骰子的抛掷结果是 2 会给你带来一个巨大无比的信息量,它等于 – log(1/100) ,约为 4.605 ;但你只有百分之一的几率获取到这么大的信息量,其余状况下你只能获得 – log(99/100) ≈ 0.01005 的信息量。平均状况下,你只能得到 0.056 的信息量,这就是这颗骰子的信息熵。再考虑一个最极端的状况:若是一颗骰子的六个面都是 1 ,投掷它不会给你带来任何信息,它的信息熵为 – log(1) = 0 。何时信息熵会更大呢?换句话说,发生了怎样的事件以后,你最想问一下它的结果如何?直觉上看,固然就是那些结果最不肯定的事件。没错,信息熵直观地反映了一个事件的结果有多么的随机。

    咱们用信息熵来衡量一个文本片断的左邻字集合和右邻字集合有多随机。考虑这么一句话“吃葡萄不吐葡萄皮不吃葡萄倒吐葡萄皮”,“葡萄”一词出现了四次,其中左邻字分别为 {吃, 吐, 吃, 吐} ,右邻字分别为 {不, 皮, 倒, 皮} 。根据公式,“葡萄”一词的左邻字的信息熵为 – (1/2) · log(1/2) – (1/2) · log(1/2) ≈ 0.693 ,它的右邻字的信息熵则为 – (1/2) · log(1/2) – (1/4) · log(1/4) – (1/4) · log(1/4) ≈ 1.04 。可见,在这个句子中,“葡萄”一词的右邻字更加丰富一些。

    在人人网用户状态中,“被子”一词一共出现了 956 次,“辈子”一词一共出现了 2330 次,二者的右邻字集合的信息熵分别为 3.87404 和 4.11644 ,数值上很是接近。但“被子”的左邻字用例很是丰富:用得最多的是“晒被子”,它一共出现了 162 次;其次是“的被子”,出现了 85 次;接下来分别是“条被子”、“在被子”、“床被子”,分别出现了 69 次、 64 次和 52 次;固然,还有“叠被子”、“盖被子”、“加被子”、“新被子”、“掀被子”、“收被子”、“薄被子”、“踢被子”、“抢被子”等 100 多种不一样的用法构成的长尾⋯⋯全部左邻字的信息熵为 3.67453 。但“辈子”的左邻字就很可怜了, 2330 个“辈子”中有 1276 个是“一生”,有 596 个“这辈子”,有 235 个“下辈子”,有 149 个“上辈子”,有 32 个“半辈子”,有 10 个“八辈子”,有 7 个“几辈子”,有 6 个“哪辈子”,以及“n 辈子”、“两辈子”等 13 种更罕见的用法。全部左邻字的信息熵仅为 1.25963 。于是,“辈子”可否成词,明显就有争议了。“下子”则是更典型的例子, 310 个“下子”的用例中有 294 个出自“一会儿”, 5 个出自“两下子”, 5 个出自“这下子”,其他的都是只出现过一次的罕见用法。事实上,“下子”的左邻字信息熵仅为 0.294421 ,咱们不该该把它看做一个能灵活运用的词。固然,一些文本片断的左邻字没啥问题,右邻字用例却很是贫乏,例如“交响”、“后遗”、“鹅卵”等,把它们看做单独的词彷佛也不太合适。咱们不妨就把一个文本片断的自由运用程度定义为它的左邻字信息熵和右邻字信息熵中的较小值

 
    在实际运用中你会发现,文本片断的凝固程度和自由程度,两种判断标准缺一不可。只看凝固程度的话,程序会找出“巧克”、“俄罗”、“颜六色”、“柴可夫”等其实是“半个词”的片断;只看自由程度的话,程序则会把“吃了一顿”、“看了一遍”、“睡了一晚”、“去了一趟”中的“了一”提取出来,由于它的左右邻字都太丰富了。

 
 
    咱们把文本中出现过的全部长度不超过 d 的子串都看成潜在的词(即候选词,其中 d 为本身设定的候选词长度上限,我设定的值为 5 ),再为出现频数、凝固程度和自由程度各设定一个阈值,而后只须要提取出全部知足阈值要求的候选词便可。为了提升效率,咱们能够把语料全文视做一整个字符串,并对该字符串的全部后缀按字典序排序。下表就是对“四是四十是十十四是十四四十是四十”的全部后缀进行排序后的结果。实际上咱们只须要在内存中存储这些后缀的前 d + 1 个字,或者更好地,只储存它们在语料中的起始位置。

 

更多见原文 http://www.matrix67.com/blog/archives/5044

 

更多相关实现:

 

我把楼主的算法用python实现,并进行一些必要的优化,并加入到个人分词库里。开源的分词库地址:https://github.com/jannson/yaha
实如今 yaha/wordmaker.py里
实用示例在 tests/test_cuttor.py里
欢迎你们进行测试。6M如下的文本问题不大,如若要分析更大的文本,后续会添加一个c++实现的版本,测试发现比python快 10倍
https://github.com/sing1ee/dict_build 这个简单用java实现了,看效果还不错。挺好玩的。
C++11/14实现了一个,写得渣勿喷。。 https://github.com/zouyxdut/new-words-discoverer
成词条件
互信息
左右熵
位置成词几率
ngram 频率


自动构建中文词库:http://www.matrix67.com/blog/archives/5044
python 3 實現:
https://github.com/yanghanxy/New-Word-Detection
Python 3 实现了一个,(新)



Neologism

https://github.com/jtyoui/Jtyoui/tree/master/jtyoui/word
相关文章
相关标签/搜索