开源中文分词工具探析(三):Ansj
Ansj是由孙健(ansjsun)开源的一个中文分词器,为ICTLAS的Java版本,也采用了Bigram + HMM分词模型(可参考我之前写的文章):在Bigram分词的基础上,识别未登录词,以提高分词准确度。虽然基本分词原理与ICTLAS的一样,但是Ansj做了一些工程上的优化,比如:用DAT高效地实现检索词典、邻接表实现分词DAG、支持自定义词典与自定义消歧义规则等。
【开源中文分词工具探析】系列:
- 开源中文分词工具探析(一):ICTCLAS (NLPIR)
- 开源中文分词工具探析(二):Jieba
- 开源中文分词工具探析(三):Ansj
- 开源中文分词工具探析(四):THULAC
- 开源中文分词工具探析(五):FNLP
- 开源中文分词工具探析(六):Stanford CoreNLP
- 开源中文分词工具探析(七):LTP
1. 前言
Ansj支持多种分词方式,其中ToAnalysis为店长推荐款:
它在易用性,稳定性.准确性.以及分词效率上.都取得了一个不错的平衡.如果你初次尝试Ansj如果你想开箱即用.那么就用这个分词方式是不会错的.
因此,本文将主要分析ToAnalysis的分词实现(基于ansj-5.1.0版本)。
ToAnalysis继承自抽象类org.ansj.splitWord.Analysis,重写了抽象方法getResult。其中,分词方法的依赖关系:ToAnalysis::parse -> Analysis::parseStr -> Analysis::analysisStr。analysisStr方法就干了两件事:
- 按照消歧义规则分词;
- 在此基础上,按照核心词典分词;
analysisStr方法的最后调用了抽象方法getResult,用于对分词DAG的再处理;所有的分词类均重写这个方法。为了便于理解ToAnalysis分词,我用Scala 2.11重写了:
import java.util
import org.ansj.domain.{Result, Term}
import org.ansj.recognition.arrimpl.{AsianPersonRecognition, ForeignPersonRecognition, NumRecognition, UserDefineRecognition}
import org.ansj.splitWord.Analysis
import org.ansj.util.TermUtil.InsertTermType
import org.ansj.util.{Graph, NameFix}
/**
* @author rain
*/
object ToAnalysis extends Analysis {
def parse(sentence: String): Result = {
parseStr(sentence)
}
override def getResult(graph: Graph): util.List[Term] = {
// bigram分词
graph.walkPath()
// 数字发现
if (isNumRecognition && graph.hasNum)
new NumRecognition().recognition(graph.terms)
// 人名识别
if (graph.hasPerson && isNameRecognition) {
// 亚洲人名识别
new AsianPersonRecognition().recognition(graph.terms)
// 词黏结后修正打分, 求取最优路径
graph.walkPathByScore()
NameFix.nameAmbiguity(graph.terms)
// 外国人名识别
new ForeignPersonRecognition().recognition(graph.terms)
graph.walkPathByScore()
}
// 用户自定义词典的识别
new UserDefineRecognition(InsertTermType.SKIP, forests: _*).recognition(graph.terms)
graph.rmLittlePath()
graph.walkPathByScore()
import scala.collection.JavaConversions._
val terms = graph.terms
new util.ArrayList[Term](terms.take(terms.length - 1).filter(t => t != null).toSeq)
}
}
如果没看懂,没关系,且看下小节分解。
2. 分解
分词DAG
分词DAG是由类org.ansj.util.Graph实现的,主要的字段terms为org.ansj.domain.Term数组。其中,类Term为DAG的节点,字段包括:offe首字符在句子中的位置、name为词,next具有相同首字符的节点、from前驱节点、score打分。仔细看源码容易发现DAG是用邻接表(array + linked-list)方式所实现的。
Bigram模型
Bigram模型对应于一阶Markov假设,词只与其前面一个词相关,其对应的分词模型:
\begin{equation}
\arg \max \prod_{i=1}^m P(w_{i} | w_{i-1}) = \arg \min - \sum_{i=1}^m \log P(w_{i} | w_{i-1})
\label{eq:bigram}
\end{equation}
对应的词典为bigramdict.dic,格式如下:
始##始@和 11
和@尚 1
和@尚未 1
世纪@末##末 3
...
初始状态\(w_0\)对应于Bigram词典中的“始##始”,\(w_{m+1}\)对应于“末##末”。Bigram分词的实现为Graph::walkPath函数:
/**
* bigram分词
* @param relationMap 干涉性打分, key为"first_word \tab second_word", value为干涉性score值
*/
public void walkPath(Map<String, Double> relationMap) {
Term term = null;
// 给terms[0] bigram打分, 且前驱节点为root_term "始##始"
merger(root, 0, relationMap);
// 从第一个字符开始往后更新打分
for (int i = 0; i < terms.length; i++) {
term = terms[i];
while (term != null && term.from() != null && term != end) {
int to = term.toValue();
// 给terms[to] bigram打分, 更新最小非零score值对应的term为前驱
merger(term, to, relationMap);
term = term.next();
}
}
// 求解最短路径
optimalRoot();
}
对条件概率\(P(w_{i} | w_{i-1})\)做如下的平滑处理:
- \log P(w_{i} | w_{i-1}) & \approx - \log \left[ aP(w_{i-1}) + (1-a) P(w_{i}|w_{i-1}) \right] \\
& \approx - \log \left[ a\frac{f(w_{i-1})}{N} + (1-a) \left( \frac{(1-\lambda)f(w_{i-1},w_i)}{f(w_{i-1})} + \lambda \right) \right]
\end{aligned}
\]
其中,\(a=0.1\)为平滑因子,\(N=2079997\)为训练语料中的总词数,\(\lambda = \frac{1}{N}\)。上述平滑处理实现函数为MathUtil.compuScore。
求解式子\eqref{eq:bigram}的最优解等价于求解分词DAG的最短路径。Ansj采用了类似于Dijkstra的动态规划算法(作者称之为Viterbi算法)来求解最短路径。记\(G=(V,E)\)为分词DAG,其中边\((u,v) \in E\)满足如下性质:
\]
即DAG顶点的序号的顺序与图流向是一致的。这个重要的性质确保了(按Graph.terms[]的index依次递增)用动态规划求解最短路径的正确性。用\(d_i\)标记源节点到节点\(i\)的最短距离,则有递推式:
\]
其中,\(b_{(j,i)}\)为两个相邻词的条件概率的负log值-$ \log P(w_{i} | w_{j})$。上述实现请参照源码Graph::walkPath与Graph::optimalRoot。
自定义词典
Ansj支持自定义词典分词,是通过词黏结的方式——如果相邻的词黏结后正好为自定义词典中的词,则可以被分词——实现的。换句话说,如果自定义的词未能完全覆盖相邻词,则不能被分词。举个例子:
import scala.collection.JavaConversions._
val sentence = "倒模,替身算什么?钟汉良、ab《孤芳不自赏》抠图来充数"
println(ToAnalysis.parse(sentence).mkString(" "))
// 倒/v 模/ng ,/w 替身/n 算/v 什么/r ?/w 钟汉良/nr 、/w ab/en 《/w 孤芳/nr 不/d 自赏/v 》/w 抠/v 图/n 来/v 充数/v
DicLibrary.insert(DicLibrary.DEFAULT, "身算")
DicLibrary.insert(DicLibrary.DEFAULT, "抠图")
println(ToAnalysis.parse(sentence).mkString(" "))
// 倒/v 模/ng ,/w 替身/n 算/v 什么/r ?/w 钟汉良/nr 、/w ab/en 《/w 孤芳/nr 不/d 自赏/v 》/w 抠图/userDefine 来/v 充数/v
3. 参考资料
[1] goofyan, ansj词典加载及简要分词过程.
开源中文分词工具探析(三):Ansj的更多相关文章
- 开源中文分词工具探析(四):THULAC
THULAC是一款相当不错的中文分词工具,准确率高.分词速度蛮快的:并且在工程上做了很多优化,比如:用DAT存储训练特征(压缩训练模型),加入了标点符号的特征(提高分词准确率)等. 1. 前言 THU ...
- 开源中文分词工具探析(五):FNLP
FNLP是由Fudan NLP实验室的邱锡鹏老师开源的一套Java写就的中文NLP工具包,提供诸如分词.词性标注.文本分类.依存句法分析等功能. [开源中文分词工具探析]系列: 中文分词工具探析(一) ...
- 开源中文分词工具探析(五):Stanford CoreNLP
CoreNLP是由斯坦福大学开源的一套Java NLP工具,提供诸如:词性标注(part-of-speech (POS) tagger).命名实体识别(named entity recognizer ...
- 开源中文分词工具探析(七):LTP
LTP是哈工大开源的一套中文语言处理系统,涵盖了基本功能:分词.词性标注.命名实体识别.依存句法分析.语义角色标注.语义依存分析等. [开源中文分词工具探析]系列: 开源中文分词工具探析(一):ICT ...
- 开源中文分词工具探析(六):Stanford CoreNLP
CoreNLP是由斯坦福大学开源的一套Java NLP工具,提供诸如:词性标注(part-of-speech (POS) tagger).命名实体识别(named entity recognizer ...
- 中文分词工具探析(二):Jieba
1. 前言 Jieba是由fxsjy大神开源的一款中文分词工具,一款属于工业界的分词工具--模型易用简单.代码清晰可读,推荐有志学习NLP或Python的读一下源码.与采用分词模型Bigram + H ...
- 中文分词工具探析(一):ICTCLAS (NLPIR)
1. 前言 ICTCLAS是张华平在2000年推出的中文分词系统,于2009年更名为NLPIR.ICTCLAS是中文分词界元老级工具了,作者开放出了free版本的源代码(1.0整理版本在此). 作者在 ...
- 基于开源中文分词工具pkuseg-python,我用张小龙的3万字演讲做了测试
做过搜索的同学都知道,分词的好坏直接决定了搜索的质量,在英文中分词比中文要简单,因为英文是一个个单词通过空格来划分每个词的,而中文都一个个句子,单独一个汉字没有任何意义,必须联系前后文字才能正确表达它 ...
- matplotlib工具栏源码探析三(添加、删除自定义工具项)
转: matplotlib工具栏源码探析三(添加.删除自定义工具项) matplotlib工具栏源码探析二(添加.删除内置工具项)探讨了工具栏内置工具项的管理,除了内置工具项,很多场景中需要自定义工具 ...
随机推荐
- mongodb group包(最具体的、最受欢迎、最容易理解的解释)
和数据库一样group经常常使用于统计.MongoDB的group还有非常多限制,如:返回结果集不能超过16M, group操作不会处理超过10000个唯一键.好像还不能利用索引[不非常确定]. Gr ...
- JSON解析之Gson
1.Gson简介 Gson是一个将Java对象转为JSON表示的开源类库,由Google提供,并且也可以讲JSON字符串转为对应的Java对象.虽然有一些其他的开源项目也支持将Java对象转为JSON ...
- leetcode[67] Plus One
题目:对一个用vector存的数字进行加1,然后返回加1后的值. 一次就在oj上通过了. 就是进位加上当前位如果大于9,那就当前位等于0: 随后进位还为1的话就是在数组前面插入一个1: class S ...
- OWIN产生的背景以及简单介绍
OWIN产生的背景以及简单介绍 随着VS2013的发布,微软在Asp.Net中引入了很多新的特性,比如使用新的权限验证模块Identity, 使用Async来提高Web服务器的吞吐量和效率等.其中一个 ...
- 【转】Android 图层引导帮助界面制作
2012-11-02 10:31 1979人阅读 评论(0) 收藏 举报 原文:http://www.cnblogs.com/beenupper/archive/2012/07/18/2597504. ...
- 【C#版本详情回顾】C#3.0主要功能列表
隐式类型的本地变量和数组 在与本地变量一起使用时,var 关键字指示编译器根据初始化语句右侧的表达式推断变量或数组元素的类型 对象初始值设定项 支持无需显式调用构造函数即可进行对象初始化 集合初始值设 ...
- 尝试使用Memcached
尝试使用Memcached遇到的狗血问题 乘着有时间,尝试下利用Memcached进行分布式缓存,其中遇到了不少问题及狗血的事情,开篇记录下,希望对您有帮助. 我之前的项目为:Asp.Net MV ...
- 【转】几点 iOS 开发技巧
[译] 几点 iOS 开发技巧 原文:iOS Programming Architecture and Design Guidelines 原文来自破船的分享 原文作者是开发界中知晓度相当高的 Mug ...
- C#线程池用法
C#线程池用法 在C#编程语言中,使用线程池可以并行地处理工作,当强制线程和更新进度条时,会使用内建架构的ThreadPool类,为批处理使用多核结构,这里我们来看在C#编程语言中一些关于来自Syst ...
- [实验]通过内核Patch去掉iOS-v4.3.3的沙盒特性
环境: 1.Mac OS X 10.9.2 2.xcode 5.1.1 3.gcc 4.8 4.redsn0w 0.9.15b3 前提: 1.获取 iOS 4.3.3 的kernelcache,并解密 ...