前段时间,因为项目需求, 开始接触了NLP,有感自己不是科班出身,很多东西理解不深,于是花时间再读了一些NLP的经典教程的部分章节,这里是第一部分,主要包括三小块:中文分词、词向量、词性标注, 这三块是前段时间项目上有用到过,所以稍做总结与大家分享下,只有更极致地深入了解才能学习得更多。
分词分词可能是自然语言处理中最基本的问题,在英文中,天然地使用空格来对句子做分词工作,而中文就不行了,没有特点符号来标志某个词的开始或者结尾,而分词通常对语义的理解是特别重要的,这里举个栗子:
下雨天留客天留我不留==>下雨天 留客天 留我不留 ==>下雨 天留客 天留我不留不同的分词,会造成完全不同的语义理解,其重要性不明而喻,那么如何把词从句子中正确地切分出来呢?
我爱北京天安门分成我 爱 北京天安门 而不是 我爱 北 京天安门? 对于计算机而已,天安门和京天安门都是二进制存储在硬盘或者内存中,没有其他差别,那么我们如何让计算机知道切分为天安门而不是京天安门呢? 这里我们需要提到词典的帮助,做过NLP的小伙伴通常都知道在一些基础任务上,词典的好坏决定最后的性能指标,那么词典是如何对分词起作用的呢?
分词词典最简单的一个想法,是构造一个常用词的候选集合,如我、爱、天安门、北京这些词,然后从句子头到尾遍历,如何词在候选集合中出现过则切分该词,那么很容易将我爱天安门分词为我 爱 天安门,这样的逻辑很容易理解,所以接下来就是如何去设计这个候选集合的数据结构,常用的list,当然是可以的,但是很明显,将一个海量词的词典载入,词典元素的查找还有存储,如果使用list必然会存在很严重的性能问题,如果高效地存储词典,还有高效地查询词或者短语在词典中,是这部分分词最重要的工作,Trie树在自然语言处理词库的存储和查找上使用的比较普遍。
Trie树存储及最长匹配法Wikipedia上对于Trie树是这样解释的:在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
图中主要包括三种节点:开始节点、中间节点、和结束节点,利用Trie树存储后,根据一条路径上来存储一个词典的词如上海大学、当然中间节点也可以做为一个词的结尾来保存如上海,常用的中文字不到5000,大概只需要一个一层分支为2^12的Trie树来保存所有的中文词库信息,树形的结构,保证了高效的存储和查找方法,遍历sentence时,只需要依次向树下一层访问,如果无法访问到下一节点,则切分,如到叶子节点,也切分即可,这就是基于Tire树的最长匹配法,分词性能的好坏完全依赖于词库。 具体的实现可以读下cppjieba的MPSEGMENT的部分https://github.com/yanyiwu/cppjieba/blob/master/include/cppjieba/MPSegment.hpp ,这里主要关注下Calc和CutByDag即可比较好的理解。 Trie树的更高效的实现方式包括三数组Trie和二数组Trie,三数组Trie结构包括三个数组结:base,next和check;二数组Trie包含base和check两个数组,base的每个元素表示一个Trie节点,而check数组表示某个状态的前驱状态,高效Trie树的实现,大家有兴趣可以拿源码来读读,这里我先略过。
基于HMM的分词方法基于Trie Tree的分词方法,主要依赖词典,通常能满足大部分场景,但是很多时候也会效果不好,通常会引入概率模型来做分词,隐性马尔科夫模型通过引入状态见的概率转换,来提高分词的效果,尤其是对未登录词效果要好很多。 相信大家在很多场景下听过HMM,HMM的基本部分包括状态值集合、观察值集合、状态转移矩阵、条件概率矩阵、初始化概率。
这里稍微解释下这五个术语在分词中是啥意思:
状态值序列,这里一般有四种状态:B:Begin, M:Middel, E:End, S:single,对于一个待分词序列:大家都爱北京天安门对应的状态序列为BESSBEBME,这样就很容易切分为:BE S S BE BME。
观察值序列,指的就是待切分的词,如:我爱北京天安门;
初始化概率,指的是B\M\E\S这四种状态在第一个字的概率分布情况;