分词,难在哪里?科普+解决方案!
题图:by Lucas Davies
一、前言
分词,我想是大多数大前端开发人员,都不会接触到的一个概念。这个不影响我们了解它,毕竟我们要多方向发展。今天就来简单介绍一些分词,我尽量用简介的语言来描述这个概念,并且最后再提供一个解决方案,希望对你有帮助。
分词简单来讲就是把一句话,按照词义,切分成一个个单独的词。这么说可能没什么感觉,先看看它适用的场景。分词是文本挖掘的基础,通常会用于自然语言处理、分词搜索、推荐等等领域。
二、分词的原理和算法
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 模型进行识别。
它提供三种分词模式:精准模式、全模式、搜索模式。全模式是找到所有可能的词语,搜索模式是在精确模式的基础上对长词进行切分,提高分割率。
在分词的速度上,精确模式能达到 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 =
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 设备上,例如对搜索词进行一个预处理,也是可以的。
jieba(java) 使用 maven 管理,所以需要 Gradle 简单配置一下,让其支持。
1. 配置 build.gradle
repositories {
google()
jcenter()
mavenCentral()
}
2. 引入依赖
api '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
http://www.infoq.com/cn/articles/nlp-word-segmentation
「联机圆桌」👈推荐我的知识星球,一年 50 个优质问题,上桌联机学习。
公众号后台回复成长『成长』,将会得到我准备的学习资料,也能回复『加群』,一起学习进步;你还能回复『提问』,向我发起提问。
推荐阅读:
写作是核心竞争力 | Google 工程师解密“猜画小歌” | 图解:HTTP 范围请求 | Android P 适配经验 | 技术创业选择清单 | HTTP传输编码 | 什么正在消耗你? | HTTP 内容编码 | 图解 HTTP 缓存 | 聊聊 HTTP 的 Cookie | 辅助模式实战 | Accessibility 辅助模式 | 小程序 Flex 布局 | 好的 PR 让你更靠谱 | 密码管理之道