Python 实战 | 进阶中文分词之 HanLP 词典分词(上)
Python教学专栏,旨在为初学者提供系统、全面的Python编程学习体验。通过逐步讲解Python基础语言和编程逻辑,结合实操案例,让小白也能轻松搞懂Python!
>>>点击此处查看往期Python教学内容本文目录
一、引言 二、加载 HanLP 词典 三、切分规则 四、实现 HanLP 词典分词 五、结束语 本文共9395个字,阅读大约需要24分钟,欢迎指正!
Part1引言
自然语言处理任务的层次可以分为词法分析、句法分析和语义分析,同时这也是从易到难的递进过程。对中文来说,词法分析(中文分词、词性标注、命名实体识别)是后续任务的基础,而中文分词又是其中最基本的任务。目前中文分词算法大致可以分为基于词典规则与基于机器学习两大派别,无论是哪个派别的算法总有各自的优缺点,我们在工作学习中应该选择最适合当前任务的算法。
本期将为大家介绍如何基于 HanLP 进行词典分词,词典分词是一种基于词典库的分词方法,它的原理是将待处理的文本与词典中的词语进行匹配,找出最长的匹配词并切分。另外,为什么我们先介绍 HanLP 词典分词呢?除了其原理相对容易理解,介绍该方法还有以下两点原因:
HanLP 支持自定义词典(同时也支持设置默认的词典词性),用户可以针对各自领域的内容自定义词典,以此提高分词的精度。 HanLP 开发者何晗(hancks)使用双数组字典树结构来存储字典,具有更小的空间复杂度和更高的匹配效率,在保证精度的同时,提高了分词的效率。
根据词典分词的定义,使用词典来分词其实仅需要一部词典和一套查找词典的规则即可,于是下面我们会先介绍在 HanLP 中如何加载词典,然后介绍三种常用的切分规则以及它们的效果,最后实现 HanLP 的词典分词。
文本基于 HanLP 1.8.4 版本书写。 本文中所有 Python 代码均在集成开发环境 Visual Studio Code (VScode) 中使用交互式开发环境 Jupyter Notebook 中编写。
Part2加载 HanLP 词典
实现词典分词的第一步,就是准备一部词典。网上已经有许多公开的中文词库了,比如 THUOCL(清华大学开放中文词库)[1]、中文维基百科抽取的词库[2]和何晗发布的千万级巨型汉语词库[3]等等,有需要的读者可以自行下载,供个人研究学习使用。
上期文章我们已经介绍了安装 HanLP 的方法,如果未安装的读者请参考Python 实战 | 文本分析工具之HanLP入门。在我们第一次运行时,HanLP 自带的数据包和字典就会自动下载到pyhanlp
的系统路径中,笔者的词典路径如下:
C:\Users\QIYAN_USER\miniconda3\Lib\site-packages\pyhanlp\static\data\dictionary
这里以 HanLP 自带的核心词典(上图 CoreNatureDictionary.txt)为例,这是一个“utf-8”
编码的纯文本文件,使用记事本打开格式如下:
可以看到,HanLP 词典是以空格作为分隔的表格形式,这三列分别为词语、词性和相应的词频(在某个语料库的统计结果),比如 “叙述” 这个词以动词的形式出现了 72 次、以动名词的形式出现了 18 次。
现在我们来看如何在 HanLP 中加载这份词典。代码如下:
需要注意一点,为了便于展示,本文将代码存放在 .ipynb
文件中。如果读者后续有调用该函数的需求,建议将代码存于扩展名为.py
的脚本文件(也称“模块”)中,然后在需要使用的模块中导入该模块,即可随时调用该模块中的函数。
from pyhanlp import *
# 参数 path:需要加载的词典路径
def load_dictionary(path):
IOUtil = JClass('com.hankcs.hanlp.corpus.io.IOUtil') # 1
dic = IOUtil.loadDictionary([path]) # 2
return set(dic.keySet()) # 返回 set 形式的词典
my_dict = load_dictionary(HanLP.Config.CoreDictionaryPath) # 传入核心词典路径
print(len(my_dict)) # 词典的词条数
# 运行结果
'''
153091
'''
loadDictionary
,该方法支持将多个文件读入同一个词典中,所以需要传入一个 list,返回值 dic 是一个 TreeMap,它的键是词语本身,值是一个包含词性和词频的结构(暂时不用管)。在中文分词中,我们更关心词语本身,所以函数只需返回 TreeMap 的键(通过dic.keySet()
)即可。然后我们将 HanLP 的配置项 Config 中的词典路径作为参数传入,得到了词典my_dic
,并且输出了词典的词条数量。至此,我们已经成功加载 HanLP 的核心词典,后续就可以基于 Python 代码使用该词典了。
my_dict = load_dictionary(HanLP.Config.CoreDictionaryPath.replace('.txt', '.mini.txt'))
Part3切分规则
准备好词典之后,下一步就需要确定查找词典的规则。常用的三种规则为正向最长匹配、逆向最长匹配和双向最长匹配,在具体了解这三种切分规则之前,首先需要对完全切分有一个认识,因为这是三种切分规则的基础。
完全切分指的是找出一段文本中所有的单词,请注意,这不是标准意义上的分词,完全切分做的就是遍历文本中的连续序列,并查询这个序列是否在词典中。根据这个思想,现在我们切分“完全切分过程是切分规则的基础”
这句话,代码如下:
def full_seg(text, dic):
seg_list = []
for i in range(len(text)): # i 从 0 遍历至 text 最后一个字的下标
for j in range(i+1, len(text)+1): # j 从 i 的后一个位置开始遍历
word = text[i:j] # 取出区间 [i, j) 对应的字符串
if word in dic: # 如果该字符串在词典中,则认为是一个词语
seg_list.append(word)
return seg_list
# 此处使用的词典,为上一步加载的 HanLP 核心词典
full_seg('完全切分过程是切分规则的基础', my_dict)
1正向最长匹配
“这项研究在中国人民大学进行”
这句话,代码如下:def fore_seg(text, dic):
seg_list = []
i = 0
while i < len(text):
longest_word = text[i] # 当前扫描位置对应的字符
for j in range(i+1, len(text)+1): # j 为结束为止,从 i 的下一位开始,遍历得到所有可能的词 [i,j)
word = text[i:j] # 得到区间 [i,j) 对应的字符串
if word in dic:
if len(word) > len(longest_word): # 该词在词典中 & 比longest_word更长,优先输出
longest_word = word
seg_list.append(longest_word) # 输出每一个起始位置 i 匹配的最长词
i += len(longest_word) # 从下一个字符开始匹配
return seg_list
fore_seg('这项研究在中国人民大学进行', my_dict)
# 输出结果
'''
['这项', '研究', '在', '中国人', '民', '大学', '进行']
'''
2逆向最长匹配
def back_seg(text, dic):
seg_list = []
j = len(text) - 1
while j >= 0: # 逆向扫描, 当前扫描位置为终点
longest_word = text[j] # 当前扫描位置对应的字符
for i in range(0, j): # i 为起始位置,从 0 开始遍历至 j 的前一个位置
word = text[i: j+1] # 得到区间 [i, j] 对应的字符串
if word in dic:
if len(word) > len(longest_word): # 该词在词典中 & 比longest_word更长,优先级更高
longest_word = word
seg_list.insert(0, longest_word) # 由于逆向扫描,查出的单词位置靠后
j -= len(longest_word)
return seg_list
back_seg('这项研究在中国人民大学进行', my_dict)
# 输出结果
'''
['这项', '研究', '在', '中国', '人民', '大学', '进行']
'''
可以看到,这句话的分词结果就是我们想要的,这可以说明逆向最长匹配的效果比正向最长匹配的效果更好吗?那么再看另一句话的分词结果,代码如下:
print(fore_seg('项目的研究目的值得人们的关注', my_dict))
print(back_seg('项目的研究目的值得人们的关注', my_dict))
# 输出结果
'''
['项目', '的', '研究', '目的', '值得', '人们', '的', '关注']
['项', '目的', '研究', '目的', '值得', '人们', '的', '关注']
'''
3双向最长匹配
于是有人就提出了综合这两个规则的一个切分规则——双向最长匹配,它的规则更加复杂一些:
同时进行正向、逆向最长匹配,如果两个分词结果词数不同,则返回词数更少的结果 如果两个分词结果词数相同,则返回两者中单字更少的结果 如果单字的数量也相同,优先返回逆向最长匹配的结果
💡 据 SunM.S. 和 Benjamin K.T.(1995)的研究表明,中文中 90.0% 左右的句子,正向最大匹配和逆向最大匹配完全重合且正确,只有大约9.0%的句子两种切分规则得到的结果不一样,但其中必有一个是正确的(歧义检测成功),只有不到1.0%的句子,是正向最大匹配和逆向最大匹配的切分虽重合却是错的,或者正向最大匹配法和逆向最大匹配法切分不同但两个都不对(歧义检测失败)。
def bidir_seg(text, dic):
fore = fore_seg(text, dic)
back = back_seg(text, dic)
bidir = fore if len(fore) < len(back) else back # 1.选择词数更少的结果
count_fore = len([word for word in fore if len(word) == 1]) # 正向匹配结果中单字的数量
count_back = len([word for word in back if len(word) == 1]) # 逆向匹配结果中单字的数量
bidir = fore if count_fore < count_back else back # 2.3.选择单字更少的结果,如果相等,优先逆向匹配
return bidir
print(bidir_seg('这项研究在中国人民大学进行', my_dict))
print(bidir_seg('项目的研究目的值得人们的关注', my_dict))
# 输出结果
'''
['这项', '研究', '在', '中国', '人民', '大学', '进行']
['项', '目的', '研究', '目的', '值得', '人们', '的', '关注']
'''
不难看出,词典分词的这三个切分规则都没有完美的效果,消歧效果不够好,无法确保规则正确理解每个词在特定上下文中的具体含义,分词结果很大程度上取决于词典的精确程度。当然,HanLP 的作者也指出词典分词的核心价值不在于精度,在于速度。
Part4实现 HanLP 词典分词
# 关闭词性标注(上期文章已介绍)
HanLP.Config.ShowTermNature = False
# 实例化
segment = DoubleArrayTrieSegment()
print(segment.seg("语料库规模决定实际效果,面向生产环境的语料库应当在千万字量级。"))
# 输出结果
'''
[语料库, 规模, 决定, 实际, 效果, ,, 面向, 生产, 环境, 的, 语料库, 应当, 在, 千, 万, 字, 量级, 。]
'''
全国地名大全.txt
和CustomDictionary.txt
的词典传入,代码如下:# 需要传入的词典路径
dict1 = HANLP_DATA_PATH + "/dictionary/custom/全国地名大全.txt ns"
dict2 = HANLP_DATA_PATH + "/dictionary/CoreNatureDictionary.mini.txt"
# 以 list 的形式,将多个词典传入
segment = DoubleArrayTrieSegment([dict1, dict2])
print(segment.seg('中国北京市海淀区中关村大街59号'))
# 输出结果
'''
[中国, 北京市, 海淀区, 中关村, 大街, 5, 9, 号]
'''
HANLP_DATA_PATH
指向 pyhanlp 的数据包路径,所以只需在后面添加词典路径即可;同时,分词器支持传入多个词典(以 list 形式)。另外,你可能注意到,dict1 的词典名为全国地名大全.txt ns
,这里的ns
指默认该词典中词语的词性为ns
(地名),这就是我们在引言中说过的“词典级默认词性”,这样就不需要在词典中对每一个词条指定词性了,并且,你也可以对词典中单独的词条设置词性,词条所指定的词性的优先级大于词典的词性。# 开启合并与词性标注功能
segment.enablePartOfSpeechTagging(True)
print(segment.seg('中国北京市海淀区中关村大街59号'))
# 输出结果
'''
[中国/ns, 北京市/ns, 海淀区/ns, 中关村/ns, 大街/n, 59/m, 号/q]
'''
Part5结束语
💡 分词是中文自然语言处理的基础,没有中文分词,我们难以对语言进行量化。基于词典的分词算法是一种常见的中文分词方法,本文着重介绍了三种切分规则,以及在高性能分词器 HanLP 中实现词典分词。分词结果已经得到了,怎么来计算分词的准确率呢?下期文章我们主要将介绍在中文分词领域使用的准确率评价指标。下期再见~
另外,如果您也有关于文本分析的实操经验,欢迎给我们留言交流您使用的方法或工具,让我们一起探索更多的技术!
如果你想学习各种 Python 编程技巧,提升个人竞争力,那就加入我们的数据 Seminar 交流群吧,欢迎大家在社群内交流、探索、学习,一起进步!同时您也可以分享通过数据 Seminar 学到的技能以及得到的成果。
参考资料
THUOCL(清华大学开放中文词库: https://github.com/thunlp/THUOCL
[2]中文维基百科抽取的词库: https://dumps.wikimedia.org/zhwiki/latest/
[3]千万级巨型汉语词库: https://www.hankcs.com/nlp/corpus/tens-of-millions-of-giant-chinese-word-library-share.html
Part6相关推荐
Python 教学
Python 实战
数据可视化
星标⭐我们不迷路!想要文章及时到,文末“在看”少不了!
点击搜索你感兴趣的内容吧
往期推荐
数据Seminar
这里是大数据、分析技术与学术研究的三叉路口
推荐 | 青酱
欢迎扫描👇二维码添加关注