基于大规模语料的新词发现算法【转自matix67】
最近需要对商品中的特有的词识别,因此需新词发现算法,matrix的这篇算法很好。
对中文资料进行自然语言处理时,我们会遇到很多其他语言不会有的困难,例如分词——汉语的词与词之间没有空格,那计算机怎么才知道“已结婚的和尚未结婚的”究竟是“已/结婚/的/和/尚未/结婚/的”,还是“已/结婚/的/和尚/未/结婚/的”呢?
这就是所谓的分词歧义难题。不过,现在很多语言模型都已能比较漂亮地解决这一问题了。但在中文分词领域里,还有一个比分词歧义更令人头疼的东西—— 未登录词。中文没有首字母大写,专名号也被取消了,这叫计算机如何辨认人名地名之类的东西?更惨的则是机构名、品牌名、专业名词、缩略语、网络新词等,它 们的产生机制似乎完全无规律可寻。最近十年来,中文分词领域都在集中攻克这一难关,自动发现新词成为关键的环节。
挖掘新词的传统方法是,先对文本进行分词,然后猜测未能成功匹配的剩余片段就是新词。这似乎陷入了一个怪圈:分词的准确性本身就依赖于词库的完整性,如果词库中根本没有新词,我们又怎能信任分词结果呢?
此时,一种大胆的想法是,首先不依赖于任何已有的词库,仅仅根据词的共同特征,将一段大规模语料中可能成词的文本片段全部提取出来,不管它是新词还 是旧词。然后,再把所有抽出来的词和已有词库进行比较,不就能找出新词了吗?有了抽词算法后,我们还能以词为单位做更多有趣的数据挖掘工作。这里,我所选 用的语料是人人网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;但你只有1%的概率获取到这么大的信息量,其他情况下你只能得到-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,我们不应该把它看作一个能灵活运用的词。当然,一些文本片段的左邻字没啥问题,右邻字用例却非常贫乏,例如“交响”、“后遗”、“鹅卵”等,把它们看作单独的词似乎也不太合适。我们不妨就把一个文本片段的自由运用程度定义为它的左邻字信息熵和右邻字信息熵中的较小值。
在实际运用中你会发现,文本片段的凝固程度和自由程度,两种判断标准缺一不可。只看凝固程度 的话,程序会找出“巧克”、“俄罗”、“颜六色”、“柴可夫”等实际上是“半个词”的片段;只看自由程度的话,程序则会把“吃了一顿”、“看了一遍”、 “睡了一晚”、“去了一趟”中的“了一”提取出来,因为它的左右邻字都太丰富了。
图1 对“四是四十是十十四是十四 四十是四十”的所有后缀进行排序后的结果
抽词算法的实现
我们把文本中出现过的所有长度不超过d的子串都当作潜在的词(即候选词,其中d为自己设定的候选词长度上限,我设定的值为5),再为出现频数、凝固程度和自由程度各设定一个阈值,然后只需要提取出所有满足阈值要求的候选词即可。为了提高效率,我们可以把语料全文视作一整个字符串,并对该字符串的所有后缀按字典序排序。
这样的话,相同的候选词便都集中在了一起,从头到尾扫描一遍便能算出各个候选词的频数和右邻字信息熵。将整个语料逆序后重新排列现在所有的后缀,再 扫描一遍后便能统计出每个候选词在原文中所有左邻字的信息熵。另外,有了频数信息后,凝固程度也都很好计算了。这样,我们便得到了一个无须任何知识库的抽 词算法,输入一段充分长的文本,这个算法能以大致O(n logn)的效率提取出可能的词来。
实际上我们只需要在内存中储存这些后缀的前d+1个字,或者更好地,只储存它们在语料中的起始位置。
对不同的语料进行抽词,并且按这些词的频数从高到低排序。你会发现,不同文本的用词特征是非常明显的。
《西游记》上册:行者、师父、三藏、八戒、大圣、菩萨、悟空、怎么、和尚、唐僧……
《资本论》全文:商品、形式、货币、我们、过程、自己、机器、社会、部分、表现……
《圣经》全文:以色列、没有、自己、一切、面前、大卫、知道、什么、犹大、祭司……
《时间简史》全文:黑洞、必须、非常、任何、膨胀、科学、预言、太阳、观察、定律……
当然,我也没有忘记对人人网用户状态进行分析——人人网用户状态中最常出现的10个词是:哈哈、什么、今天、怎么、现在、可以、知道、喜欢、终于、这样。事实上,程序从人人网的状态数据中一共抽出了大约1200个词,里面大多数词也确实都是标准的现代汉语词汇。不过别忘了,我们的目标是新词抽取。将所有抽出来的词与已有词库作对比,于是得到了人人网特有的词汇(同样按频数从高到低排序):
尼玛、伤不起、给力、有木有、挂科、坑爹、神马、淡定、老爸、卧槽、牛逼、肿么、苦逼、无语、微博、六级、高数、选课、悲催、基友、蛋疼、很久、人人网、 情何以堪、童鞋、哇咔咔、脑残、吐槽、猥琐、奶茶、我勒个去、刷屏、妹纸、胃疼、飘过、考研、弱爆了、太准了、搞基、忽悠、羡慕嫉妒恨、手贱、柯南、狗 血、秒杀、装逼、真特么、碎觉、奥特曼、内牛满面、斗地主、腾讯、灰常、偶遇、拉拉、九把刀、高富帅、阿内尔卡、魔兽世界、线代、三国杀、林俊杰、速速、 臭美、花痴……
我还想到了更有意思的玩法。为什么不拿每天状态里的词去和前一天作对比,从而提取出这一天里特有的词呢?这样一来,我们就能从用户状态中提取出每日 热点了!事实上,有了抽词算法之后,我们就能够以词语为单位,在各个维度上挖掘人们在社交网络中的用词动向:今年网络上最流行的新词是什么?男生和女生都 喜欢说些什么?不同地方、不同年龄段的人都爱用什么样的词?这些问题曾经需要靠大规模语言统计调查才能得到一个满意的回答,而现在,借助互联网海量的用户 生成内容,我们可以轻而易举地得到答案,为社会语言学的研究提供真实可靠材料。受篇幅限制,我们不妨把这些激动人心的话题留到下一篇文章中去。
作者顾森,网名Matrix67,北京大学中文系应用语言学专业学生,数学爱好者。2005年开办数学博客http://www.matrix67.com,至今已积累上千篇文章,已有上万人订阅。
基于大规模语料的新词发现算法【转自matix67】的更多相关文章
- 基于SNMP的路由拓扑发现算法收集
一.三层(网络层)发现 算法来源:王娟娟.基于SNMP的网络拓扑发现算法研究.武汉科技大学硕士学位论文,2008 数据结构: 待检路由设备网关链表:存放指定深度内待检路由设备的网关信息,处理后删除. ...
- 模块度与Louvain社区发现算法
Louvain算法是基于模块度的社区发现算法,该算法在效率和效果上都表现较好,并且能够发现层次性的社区结构,其优化目标是最大化整个社区网络的模块度. 模块度(Modularity) 模块度是评估一个社 ...
- 社区发现算法 - Fast Unfolding(Louvian)算法初探
1. 社团划分 0x1:社区是什么 在社交网络中,用户相当于每一个点,用户之间通过互相的关注关系构成了整个网络的结构. 在这样的网络中,有的用户之间的连接较为紧密,有的用户之间的连接关系较为稀疏.其中 ...
- 【新词发现】基于SNS的文本数据挖掘、短语挖掘
互联网时代的社会语言学:基于SNS的文本数据挖掘 python实现 https://github.com/jtyoui/Jtyoui/tree/master/jtyoui/word 这是一个无监督训 ...
- 用python实现新词发现程序——基于凝固度和自由度
互联网时代,信息产生的数量和传递的速度非常快,语言文字也不断变化更新,新词层出不穷.一个好的新词发现程序对做NLP(自然预言处理)来说是非常重要的. N-Gram加词频 最原始的新词算法莫过于n-gr ...
- 一个基于特征向量的近似网页去重算法——term用SVM人工提取训练,基于term的特征向量,倒排索引查询相似文档,同时利用cos计算相似度
摘 要 在搜索引擎的检索结果页面中,用户经常会得到内容相似的重复页面,它们中大多是由于网站之间转载造成的.为提高检索效率和用户满意度,提出一种基于特征向量的大规模中文近似网页检测算法DDW(Det ...
- NLP︱词向量经验总结(功能作用、高维可视化、R语言实现、大规模语料、延伸拓展)
R语言由于效率问题,实现自然语言处理的分析会受到一定的影响,如何提高效率以及提升词向量的精度是在当前软件环境下,比较需要解决的问题. 笔者认为还存在的问题有: 1.如何在R语言环境下,大规模语料提高运 ...
- GNN 相关资料记录;GCN 与 graph embedding 相关调研;社区发现算法相关;异构信息网络相关;
最近做了一些和gnn相关的工作,经常听到GCN 和 embedding 相关技术,感觉很是困惑,所以写下此博客,对相关知识进行索引和记录: 参考链接: https://www.toutiao.com/ ...
- SVD++:推荐系统的基于矩阵分解的协同过滤算法的提高
1.背景知识 在讲SVD++之前,我还是想先回到基于物品相似的协同过滤算法.这个算法基本思想是找出一个用户有过正反馈的物品的相似的物品来给其作为推荐.其公式为:
随机推荐
- A solution for MySQL Assertion failure FIL_NULL
A solution for MySQL Assertion failure FIL_NULL http://michaelfranzl.com/2014/01/25/solution-mysql-a ...
- Java-idea-设置类头注释和方法注释
一.文件级别的注释 主要是通过File-->Setting-->Editor→File and Code Template中来设置 可以再右侧include中设置File ...
- HTML PX/EM换算工具 快捷键
换算工具:http://www.runoob.com/tags/ref-pxtoemconversion.html 快捷键:http://www.runoob.com/tags/html-keyboa ...
- Debugging golang programs
https://ttboj.wordpress.com/2016/02/15/debugging-golang-programs/ I’ve been writing a lot of golang ...
- [LeetCode] 207 Course Schedule_Medium tag: BFS, DFS
There are a total of n courses you have to take, labeled from 0 to n-1. Some courses may have prereq ...
- lambda函数和map函数
lambda函数,简化了函数定义的书写形式,使代码更为简洁,但是使用自定义函数的定义方式更为直观,易理解 g = lambda x:x+1 #上面的lambda表达式相当于下面的自定义函数 def g ...
- 【剑指Offer学习】【面试题3 :二维数组中的查找】
package 二维数组查找; public class Test03 { /** * 在一个二维数组中,每一行都按 package 二维数组查找; public class Test03 { /** ...
- discuz财付通也阵亡了
今日做交易部分,然后焦头烂额. 首先这积分,威望,金钱,什么鬼,乱七八糟的...... 然后这支付宝,啊,,,,,竟然停止个人接口了,不得已要使用财付通. %&……*&……&不 ...
- Python: ord()函数
ch() , unichr() , ord() ①chr()函数用一个范围在range(256)内的整数作参数,返回一个对应的字符. >>>chr(65) 'A' ②unichr() ...
- linux常用命令:tr 命令
tr 命令实现字符转换功能,其功能类似于 sed 命令,但是,tr 命令比 sed 命令简单.也就是说,tr 命令能实现的功能,sed 命令都可以实现.尽管如此,tr 命令依然是 Linux 系统下处 ...