加载HanLP附带的迷你核心词典
from pyhanlp import *
def load_dictionary():
"""
加载HanLP中的mini词库
:return: 一个set形式的词库
"""
#JClass 函数是连通Java和Python的桥梁,用来根据Java路径名得到--个Python类
#利用Jclass取得了HanLP中的IOUtil工具类
IOUtil = JClass('com.hankcs.hanlp.corpus.io.IOUtil')
#取得HanLP的配置项config中的词典路径,我们写在配置文件中的条目最终会被读入这个结构中
#比如配置文件写作CoreDictionaryPath=data/dictionary/CorelNatureDictionary.txt,该配置将被读人HanLP.Config.CoreDictionaryPath。
#这里我们想要加载mini词典,因为其体积更小,加载起来更快
#将这个路径替换为mini词典的路径
path = HanLP.Config.CoreDictionaryPath.replace('.txt', '.mini.txt')
#像对待普通Python 工具类一样调用了工outil的静态方法loadDictionary
#该方法支持将多个文件读人同一个词典中,因此需要传入一个list。
#它返回一个Java Map对象
dic = IOUtil.loadDictionary([path])
#只取它的键keyset,并将其转换为一个 Python 原生的set对象
return set(dic.keySet())
if __name__ == '__main__':
dic = load_dictionary()
print(len(dic))#词典大小
print(list(dic)[0])#取词典第一个词
85584
悲痛
def fully_segment(text, dic):
word_list = []
for i in range(len(text)): # i 从 0 到text的最后一个字的下标遍历
for j in range(i + 1, len(text) + 1): # j 遍历[i + 1, len(text)]区间
word = text[i:j] # 取出连续区间[i, j]对应的字符串
if word in dic: # 如果在词典中,则认为是一个词
word_list.append(word)
return word_list
if __name__ == '__main__':
dic = load_dictionary()
print(fully_segment('商品和服务', dic))#由于词库中含有单字,所以结果中出现了一些单字
['商', '商品', '品', '和', '和服', '服', '服务', '务']
完全切分的输出并不是中文分词,我们更需要那种有意义的词语序列,而不是所有出现在词典中的单词所构成的链表。为了达到这个目的,需要完善一下我们的规则,考虑到越长的单词表达的意义越丰富,于是我们定义单词越长优先级越高。
最长匹配算法:以某个下标为起点递增查词的过程中,优先输出更长的单词
正向最长匹配:在最长匹配算法的基础上从前往后匹配
def forward_segment(text, dic):
word_list = []
i = 0
while i < len(text):
longest_word = text[i] # 当前扫描位置的单字
for j in range(i + 1, len(text) + 1): # 所有可能的结尾
word = text[i:j] # 从当前位置到结尾的连续字符串
if word in dic: # 在词典中
if len(word) > len(longest_word): # 并且更长
longest_word = word # 则更优先输出
word_list.append(longest_word) # 输出最长词
i += len(longest_word) # 正向扫描
return word_list
if __name__ == '__main__':
dic = load_dictionary()
print(forward_segment('就读北京大学', dic))
print(forward_segment('研究生命起源', dic))
['就读', '北京大学']
['研究生', '命', '起源']
正向最长匹配:在最长匹配算法的基础上从前往后匹配
def backward_segment(text, dic):
word_list = []
i = len(text) - 1
while i >= 0: # 扫描位置作为终点
longest_word = text[i] # 扫描位置的单字
for j in range(0, i): # 遍历[0, i]区间作为待查询词语的起点
word = text[j: i + 1] # 取出[j, i]区间作为待查询单词
if word in dic:
if len(word) > len(longest_word): # 越长优先级越高
longest_word = word
break
word_list.insert(0, longest_word) # 逆向扫描,所以越先查出的单词在位置上越靠后
i -= len(longest_word)
return word_list
if __name__ == '__main__':
dic = load_dictionary()
print(forward_segment('就读北京大学', dic))
print(forward_segment('研究生命起源', dic))
['就读', '北京大学']
['研究生', '命', '起源']
正向/逆向最长匹配歧义对比
由上图可以看出正向和逆向匹配都存在无法消除歧义的情况
启发式算法:在搜索最优解的过程中利用到原来搜索过程中得到的信息,且这个信息会改进我们的搜索过程。
双向最长匹配:
一种融合两种匹配方法的复杂规则集,流程如下。
def count_single_char(word_list: list): # 统计单字成词的个数
return sum(1 for word in word_list if len(word) == 1)
def bidirectional_segment(text, dic):
f = forward_segment(text, dic)
b = backward_segment(text, dic)
if len(f) < len(b): # 词数更少优先级更高
return f
elif len(f) > len(b):
return b
else:
if count_single_char(f) < count_single_char(b): # 单字更少优先级更高
return f
else:
return b # 都相等时逆向匹配优先级更高
if __name__ == '__main__':
dic = load_dictionary()
print(bidirectional_segment('研究生命起源', dic))
['研究', '生命', '起源']