python实现NLP

Table of Contents

1 编程

1.1 字符编码

中文处理中步可必免要面对字符编码问题.(由于中文字符编码的特殊性,英文NLP 中使用UNIX命令进行简单字符串除理的办法基本失效.)最方便有效的办法是统一 使用unicode和utf-8编码.

  • python脚本编码设定,在文件开始加入
    # -*- coding:<encoding name> -*-
    

    或者

    # coding=<encoding name>
    
  • 中文字符的unicode范围是\u4E00-\u9FA5,这个范围包含所有正规的简繁体 中文字符.在这个范围之外有一些拟似中文字符.这些字符在网上字典中查 不到意思,怀疑是日本汉字.关于字符匹配,参见 结巴分词1,主包下的 __init__.py 文件
  • 编码的原理.在一个系统里储存了显示字符的方式,每个字符都被编号.现在 的系统为了支持多国语言,采用一个通用编码unicode.但是这种编码只确定 了字符的现示编码,没有确定如何储存.这样做的原因是对于latin语系,用 多个字节来表示字母是非常浪费储存空间的.所以储存编码和显示编码被分 开.utf-8,big-5,gbk,ascii,就是储存编码.英文字母由于历史原因,在所有 编码中都一样.但是对同一个汉字,不同储存编码的具体值就可能不一样.这 就是乱码的由来.python中,编码指的是把unicode转换成储存编码,解码指的 是把储存编码转换成unicode.一个str可以看成是储存编码,在写入文件前, 必需编码.而读入的str在打印和求长度等运算前,必需解码.在字符串前加u 等于解码.

2 分词

目前分词基本基于两类方法,一类是 词匹配 ,另一类是基于 Markov Random Field 模型。基于词语匹配的方法速度很快,但是对于歧义的处理不好,很依赖 词典;基于Markov Random Field (HMM,CEF, etc.)的模型在消除歧义方面有优势,不 过速度相对慢很多.不管那一种模型,良好的词典支持都是必要的.有人甚至认为: 算法本身不重要,重要的是词典.开源项目中可用的算法有很多,具体工程实现更 偏重于有自己的一套面向项目的词典

2.1 资源

2.1.1 语料资源

2.1.2 软件资源

  • Coreseek,中文检索搜索软件,开源2且免费.包含 MMSEG(词匹配分词算法),这个网站上还有CRF(分词算法)和Sphinx(搜索 软件包,多语言)的开源资源下载。
  • ik-analyzer3是一个开源的,Java实现的词匹配工具包.知乎 本身好像选用了这个方案.
  • Stanford NLP package, CRF 实现分词算法.
  • 结巴分词1,python实现,文档中说有HMM。在匹配(非训练)过程 中,个人只看到已经算好概率的词匹配.包中还有关键词提取和词性标注功能。
  • MaxEnt,张乐https://github.com/lzhang10/maxent. 基于C++,但是有 python接口,接口通过 SWIG 生成。
  • CRF++。基于C++,有python接口

2.2 实现

2.2.1 结巴分词

  1. 流水账

    结巴分词自带一个三十多万词的词典,词典中包含词的频率(多为很小的个位 数)。加载词典的时候会生成一个树,包含所有词的字符组合。根据这个树,可 以知道当一个词中第$i$个字符为C的时候,第$i+1$个字符有哪些可能。这个树本 身是不带频率或者概率统计的,所有的叶节点都为空字符串 =''= ,作为休止符 方便查找。

    首先,对于一个大字符串(可以是一整部小说),使用正则表达式将整块的中文 分割出来。基于中文的特性,所有的标点符号都是分割符, 分词中的句子对象 是分句,不是整句 。默认的正则表达式是 [\u4E00-\u9FA5a-zA-Z0-9+#&\._]+ ,这个表达式有一个问题,那就是遇到用 英文句号的文本会导致分句失败。

    对于目标句,先创建了一个DAG的数据结构,包含这个句子中所有可能的出现在词 典中的候选词(以字典的方式返回词在句中的位置, (i,j) 被表述为 {i:[j]} )。然后根据词频计算概率最大的分词组合。

    N = len(sentence)
    route[N] = (0.0,'')
    for idx in xrange(N-1,-1,-1):
        candidates = [ ( FREQ.get(sentence[idx:x+1],min_freq) + route[x+1][0],x ) for x in DAG[idx] ]
        route[idx] = max(candidates)
    

    对于连续的单字词,使用hmm来分词,将字母和数字抱团成为新词。这个hmm只关 注分词,采用4-tag为隐藏层状态。

    在关键词提取过程中,首先对文档进行分词(默认),然后计算词在文档中的频 率。之后和从idf.txt中读取的idf相乘,最后排序。

    词性标注的代码位于posseg包中,使用了HMM。先分词,这部分跟之前差不多,但 是多了对于数字的细化处理。=word_tag_tab=,对于登录词的tag采用查表的方式。 依靠超标来判断登录词的词性导致词义消岐不行。例句:“我花了一个小时吃饭” 和“我喜欢花”中的“花”词性不同。对于连续单字词,使用HMM重新划分和标注词类, (viterbi算法)。这里的HMM把分词标签和词性标签混合在一起作为hidden state,也就是说,每个hidden state实际上是一个joint state,具体的实现为 一个tuple。举个例子,transit probability的一个状态转换为从 (B,'n')(E,'n').在4-tag(B(egin),M(edia),E(nd),S(ingle))的情况下,所有可能的 状态为 \(4\times p\), \(p\) 为POS的标签种数。

    具体使用HMM实现分词和标注词性时的一个技巧是通过观测训练数据减少 emission probability的候选,同时记住每个字有限的可能状态,从而减少要计 算的状态数。如果出现完全不在训练数据中的emission情况,则用一个小值来做 平滑( 只有不存在任何记录状况时才做平滑 )。

3 语法标记

Footnotes:

Author: Xiao LIU

Created: 2014-10-29 Wed 18:06

Emacs 24.3.1 (Org mode 8.2.10)