一、前言
分词,我想是大多数大前端开发人员,都不会接触到的一个概念。这个不影响我们了解它,毕竟我们要多方向发展。今天就来简单介绍一些分词,我尽量用简介的语言来描述这个概念,并且最后再提供一个解决方案,希望对你有帮助。
分词简单来讲就是把一句话,按照词义,切分成一个个单独的词。这么说可能没什么感觉,先看看它适用的场景,分词是文本挖掘的基础,通常会用于自然语言处理、分词搜索、推荐等等领域。
二、分词的原理和算法
2.1 什么是分词
先理解一下分词的概念。
分词就是将连续的字序列,按照一定的规范重新组合成词序列的过程。在英文中,单词之间会以空格作为分割符,将词与词之间进行分割,但是对于中文,没有一个显式的分割符。
正是因为缺乏这种显式的分割符,导致我们对中文中的词,进行分割的时候会出现很多的偏差。
2.2 分词的算法
中文分词有难度,不过也有成熟的解决方案。现有的分词算法,大概可分为三类:
基于字符串匹配的分词算法
基于理解的分词算法
基于统计的分词算法
1. 基于字符串匹配的分词算法
这种分词方法,又叫机械分词算法,它会提前维护一个大的字典,然后将句子和字典中的词进行匹配,若匹配成功,则可以进行分词处理。
当然,它实际上会更复杂一些,因为当字典足够大的时候,就又涉及到不同的匹配算法,这里就不展开讲了。通常会基于 Trie 树结构,来实现高效的词图扫描。
2. 基于理解的分词算法
这种分词方法,通过让计算机,模拟人对句子的理解,达到识别词组的效果。其基本思想是在分词的同时进行句法、语义的分析,利用句法和语义信息来处理歧义现象。
它通常会包含三部分:分词子系统、句法语义子系统、总控部分。在总控部分的协调下,分词子系统可以获得有关词、句子等的句法和语义信息,来对分词歧义进行判断,即它模拟了人对句子的理解过程。由于汉语语言知识的笼统、复杂性,难以将各种语言信息组织成机器可直接读取的形式,因此目前基于理解的分词系统还处在试验阶段。
3. 基于统计的分词算法
这种方法会给出大量已经分词的文本,利用统计机器学习模型,学习词语切分的规律(称为训练),从而实现对未知文本的切分。
随着大规模语料库的建立,统计机器学习方法的研究和发展,基于统计的中文分词方法渐渐成为了主流方法。
2.3 分词的诉求
虽然分词的算法,讲解起来很简单,但是从现有的经验来看,几乎是不存在通用且效果非常好的分词系统。
每个领域,都有其独特的词汇,这很难通过有限的训练数据,捕捉到所有的语言特征。例如:通过人民日报训练的分词系统,在网络玄幻小说上,分词的效果就不会好。
这是必然的,在分词系统中,没有银弹。
不同的场景,对分词的要求也差异很大,通常可以从两个维度进行区分:分词速度、分词准确性。
例如分词搜索,对速度要求就高于准确性的要求。而一些问答系统中,则需要对文本实现较深的理解,要求准确性高于速度要求。
不同的领域,不同的使用场景,对分词的要求是不同的,所以我们不能片面的去理解分词的准确率。并且随着新词的增加,训练数据的变化,分词的准确率也是在波动的。这也就是,现在吹嘘分词准确率的公司越来越少的原因。
2.4 分词的解决方案
分词是可以解决实际问题的功能,经过这么长时间的反复迭代更新,市面上一家产生了一批有特色的分词系统。例如:IK、Jieba、Ansj、Hanlp、Stanford分词 等等。
有兴趣可以一个个了解,接下来就其中的一个开源库 Jieba,进行讲解。
三、jieba
3.1 jieba 的优点
jieba 是开源的,号称是 Python 中,最好的中文分词组件。并且是基于 MIT 的协议,使用起来无后顾之忧。
jieba 使用起来也非常的简单,几行代码就可以实现分词调用和词性标注,而且速度还不错。
它内部维护了一个词典,是根据人民日报分析获得,在超出词典之外的新词,会基于 HMM 模型进行识别。
#p#分页标题#e#它提供三种分词模式:精准模式、全模式、搜索模式。全模式是找到所有可能的词语,搜索模式是在精确模式的基础上对长词进行切分,提高分割率。
在分词的速度上,精确模式能达到 400KB/s,全模式下能达到 1.5MB/s。同时除了 Python 版本之外,还有不同的人基于 Python 版的 jieba ,扩展出多种语言实现,包括:JavaScript、Java、Golang、R、PHP 等。
jieba 的使用
jieba 的代码对 Python 2/3 均兼容,在使用之前,需要通过命令 pip install jieba 或者 pip3 install jieba 进行安装。
具体 Api,就不展开讲了,有兴趣可以去查看 Github 上的文档(文末有地址)。
这里提供一个简单的代码示例,来感受一下 jieba 的方便与强大。
# encoding=utf-8
import jieba
seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
print("Full Mode: " + "/ ".join(seg_list)) # 全模式
seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list)) # 精确模式
seg_list = jieba.cut("他来到了网易杭研大厦") # 默认是精确模式
print(", ".join(seg_list))
seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造") # 搜索引擎模式
print(", ".join(seg_list))
输出的结果:
【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
【精确模式】: 我/ 来到/ 北京/ 清华大学
【新词识别】:他, 来到, 了, 网易, 杭研, 大厦 (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)
【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造
前面也提到,jieba 自身维护了一个词组的字典,如果自身需求上,有专有名词需要拆分,还可以通过 jieba.Tokenizer(dictionary=DEFAULT_DICT) 载入一个自定义字典。
3.2 jieba 的分词算法
匹配的算法,说起来就复杂了,这里就简单介绍一下 jiaba 分词匹配的原理。
首先,jieba 分词已经自带了一个 dict.txt 的词典,里面有 2w 多个词条,包括出现的次数和词性,这是作者自己基于人民日报为主的资料,训练的出来的。
jieba 会先将这个词典中的数据,放到一个 Trie 树中,Trie 树是有名的前缀树,当一个词语的前面几个字一样的时候,就标识他们具有相同的前缀,就可以使用 Trie 数来存储,具有查找速度快的优势。
其次,在需要对句子进行分词的时候,再根据前面生成的 Trie 数,生成有向无环图(DAG),这一步的意义在于,消除分词中的歧义,提高切分准确度,找出这句话中,所有可能的词。
到这一步,基本上就完成了,所有字典中记录的词,进行分词的过程。
但是如果你把 dict.txt 这个字典删除,jieba 依然可以进行分词,只是拆分出来的词,大部分的长度为 2。这是因为,对于未在字典中收录的词,基于隐马尔科夫模型(HMM)来预测分词,使用的是 Viterbi 算法。
HMM 模型中,将中文词汇按照 BEMS 四个状态来标记, B 是开始 begin 位置, E 是 end, 是结束位置, M 是 middle, 是中间位置, S 是 singgle, 单独成词的位置, 没有前, 也没有后. 也就是说, 他采用了状态为(B,E,M,S)这四种状态来标记中文词语, 比如北京可以标注为 BE, 即 北/B 京/E, 表示北是开始位置, 京是结束位置, 中华民族可以标注为 BMME , 就是开始, 中间, 中间, 结束.
作者通过对大量语料的训练,得到了 finalseg 目录下的训练结果,有兴趣可以自行研究。
到这里基本上就清晰了,jieba 分词的过程主要有以下三步:
加载 dict.txt 字典,生成 Trie 树。
对待分词的句子,通过 Trie 树,生成 DAG 图,匹配出所有可能的词。
再使用 HMM 模型,将字典中未收录的词,匹配出来。
这就是 jieba 分词的执行过程。
四、jieba(Java or Android)
4.1 Java 版的 jieba
jieba 发展到现在,已经支持众多的版本。Java 版并非原作者开编写,而是 hanban 参考原作者的分词原理,进行开发的。
不过 Java 版并没有原版 Python 版本那么强大,做了部分阉割,例如关键词提取就没有实现。
有兴趣可以直接去看 Github : https://github.com/huaban/jieba-analysis/
1. 引入依赖(稳定版)
<dependency>
<groupId>com.huaban</groupId>
<artifactId>jieba-analysis</artifactId>
<version>1.0.2</version>
</dependency>
2. 如何使用
@Test
public void testDemo() {
JiebaSegmenter segmenter = new JiebaSegmenter();
String[] sentences =
#p#分页标题#e#new String[] {"这是一个伸手不见五指的黑夜。我叫孙悟空,我爱北京,我爱Python和C++。", "我不喜欢日本和服。", "雷猴回归人间。",
"工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作", "结果婚的和尚未结过婚的"};
for (String sentence : sentences) {
System.out.println(segmenter.process(sentence, SegMode.INDEX).toString());
}
}
3. 性能评估
作者在测试机上进行测试,配置为:
Processor 2 Intel(R) Pentium(R) CPU G620 @ 2.60GHz
Memory:8GB
测试结果还算理想,单线程,对测试文本逐行分词,并循环调用上万次的效率分析。
循环调用一万次
第一次测试结果:
time elapsed:12373, rate:2486.986533kb/s, words:917319.94/s
第二次测试结果:
time elapsed:12284, rate:2505.005241kb/s, words:923966.10/s
第三次测试结果:
time elapsed:12336, rate:2494.445880kb/s, words:920071.30/s
循环调用2万次
第一次测试结果:
time elapsed:22237, rate:2767.593144kb/s, words:1020821.12/s
第二次测试结果:
time elapsed:22435, rate:2743.167762kb/s, words:1011811.87/s
第三次测试结果:
time elapsed:22102, rate:2784.497726kb/s, words:1027056.34/s
统计结果:词典加载时间1.8s左右,分词效率每秒2Mb多,近100万词。
2 Processor Intel(R) Core(TM) i3-2100 CPU @ 3.10GHz
12G 测试效果
time elapsed:19597, rate:3140.428063kb/s, words:1158340.52/s
time elapsed:20122, rate:3058.491639kb/s, words:1128118.44/s
4.2 在 Android 下使用 jieba
jieba(Java)版本,本身也是自带词典的,所以在 Android 下引入,会增大 Apk 的体积。同时因为设备的配置,还会影响到分词的效率。
不过如果非要使用在 Android 设备上,例如对搜索词进行一个预处理,也是可以的。Apk 增大的问题,其实可以通过安装后下载词典的方式规避。
jieba(java) 使用 maven 管理,所以需要 Gradle 简单配置一下,让其支持。
1. 配置 build.gradle
repositories {
google()
jcenter()
mavenCentral()
}
2. 引入依赖
implementation 'com.huaban:jieba-analysis:1.0.2'
引入之后,使用细节就没什么好说的了,和 Java 版本无差别。
参考:
https://github.com/fxsjy/jieba
https://github.com/huaban/jieba-analysis/
https://blog.csdn.net/John_xyz/article/details/54645527
【本文为51CTO专栏作者“张旸”的原创稿件,转载请通过微信公众号联系作者获取授权】