Fork me on GitHub

NLP札记2-3种匹配方式

本文重点介绍了3种匹配方式

  • 正向最长匹配
  • 逆向最长匹配
  • 双向最长匹配

词典分词

中文分词:指的是将原文的一段段文本拆分成一个个单词的过程,这些单词顺序拼接后组成原文本。分为两个方法:基于词典规则和基于机器学习

词典分词:最常见的分词算法,一套词典和一套查词典的规则即可。

词语指的是具备独立意义的最小单位。词典中的字符串就是词。词的性质满足齐夫定律:一个单词的频率和它的词频排名成反比。

词典

HanLP词典

词典格式是空格为分隔符的表格形式

  • 第一列是单词本身
  • 第二列和第三列是词性和相应的词频

如果单词本身就有空格,使用英文逗号分隔的.csv文件

词典加载

利用Python进行加载

1
2
3
4
def load_dictionary():
IOUtil = JClass('com.hankcs.hanlp.corpus.io.IOUtil') # JClass连接Java和Python的桥梁,根据Java路径得到一个Python类
path = HanLP.Config.CoreDictionaryPath.replace('.txt', '.mini.txt') # 取得了HanLP的配置项Config中的词典路径,并且替换成mini词典的路径
dic = IOUtil.loadDictionary([path]) # 调用loadDictionary静态方法,该方法支持多个文件读入同一个词典中,需要传入list

切分算法

常用的切分规则

  • 正向最长匹配
  • 逆向最长匹配
  • 双向最长匹配

它们都是基于完全切分过程。完全切分过程指的是找出一段文本中的全部单词。

朴素完全切分

遍历文本中的连续序列,查询该序列中是否在词典中即可。

1
2
3
4
5
6
7
8
9
10
11
def fully_segment(text, dic):   # 需要遍历的文本和对照的词典
word_list = [] # 空单词列表,用于存放新的单词
for i in range(len(text)): # 遍历从0到len(text)的最后下标
for j in range(i+1, len(text)+1): # j遍历[i+1, len(text)]区间,i之后的所有元素
word = text[i:j] # 取出连续区间[i,j)之间的所有元素
if word in dic: # 如果在字典中,认为是一个单词,加入空列表中,最后返回空列表
word_list.append(word)
return word_list

dic = load_dictionary()
print(fully_segment("商品和服务", dic))

正向最长匹配

  • 越长的单词表达的意义越丰富,定义单词越长优先级越高
  • 以某个下标为起点的递增查词的过程中,优先输出更长的单词,这种规则成为最长匹配算法
    • 下标的顺序是从前往后,称之为正向最长匹配
    • 如果是从后往前,则称之为逆向最长匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
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 ): # 所有可能的结尾,比如:“欢迎报考美丽的北京大学的电子与信息专业”,此时如果当前位置 i 是"北",那么需要遍历之后的全部情况,所以下标从[i+1, len(text) + 1),才能把"北京大学"匹配出来
word = text[i:j] # 通过切片取出[i,j)之间的全部单词,看其是否在词典中
if word in dic:
if len(word) > len(longest_word): # 单词在词典中,且长度大于设定的len(longest_word)
longest_word = word # 将找到的真正最长单词 word 赋值给longest_word
word_list.append(longest_word) # 全部遍历完成之后,最长单词追加到空列表中
i += len(longest_word) # 正向扫描,主要是对这句话起作用word = text[i:j],将i不断的右移,不断地找出右边范围的最长的单词
return word_list

逆向扫描

从后往前扫描的过程中,保留最长单词。

1
2
3
4
5
6
7
8
9
10
11
12
13
def backward_segment(text, dic):  # 需要扫描的文本和对比的词典
word_list = [] # 空列表,用于存放查到的最长单词
i = len(text) - 1 # 遍历初始条件
while i > 0: # 遍历结束条件
longest_word = text[i] # 假定当前位置为扫描的最长单词
for j in range(0, i): # 所有可能的前面部分,比如文本是"研究生命起源",如果初始位置 i 是"命",则前面的部分都要遍历,所以下标从[0, i]。从后往前遍历
word = text[j: i+1] # 通过切片取出[j:i+1]之间的(需要包含i)之间的全部单词
if word in dic:
if len(word) > len(longest_word): # 单词在词典中,且单词的长度大于设定的len(longest_word)
longest_word = word # 将找到的真正最长单词 word 赋值给longest_word
word_list.insert(0, longest_word) # 全部遍历完成之后,最长单词追加到空列表中
i -= len(longest_word) # 逆向扫描,主要是对这句话起作用: word = text[j: i+1],将i的范围不断左移,在左边更小的范围找出单词
return word_list

双向最长匹配

双向最长匹配的规则如下

  • 同时执行正向和逆向最长匹配,如果两者的次数不同,则返回词数更少的那个
  • 否则,返回的是两者中单字更少的那个。当单字数也相同,优先返回逆向最长匹配的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def count_single_char(word_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

本文标题:NLP札记2-3种匹配方式

发布时间:2019年12月14日 - 00:12

原始链接:http://www.renpeter.cn/2019/12/14/NLP%E6%9C%AD%E8%AE%B02-3%E7%A7%8D%E5%8C%B9%E9%85%8D%E6%96%B9%E5%BC%8F.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Coffee or Tea