机械分词

前面在 分词是什么? 中,我们一起讨论了「分词」相关的方法和技巧. 这次我们从最简单的机械分词上手,来感受一下平常调个包就能解决事情,摸起来是什么样子的。

我们先用一句话来描述这次的任务:

从文本中匹配字典中的文字,记录词语的位置(起点,终点),最后以 / 切分。

我会在代码中尽量的加注释来说明,如下👇:

import re

# 语料词典:
char_dict = ["大家", "锅贴", "你", "你好", "我", "是", "什么", "洗", "摸", "杯", "张",
             "美丽", "人", "长得", "好看", "欢迎", "来到", "南京", "南京市", "市长",
             "长江大桥", "参观", ",", "。", "?", "!"]

# 待分词文本
input_char = "大家好,我是锅贴。什么是洗摸杯?张美丽人长得真好看!你好,欢迎来到南京市长江大桥参观。"

# 真拷贝,而不是变量的引用,不会随 input_char 的改变而改变
origin_intput_char = input_char[:]

# 遍历字典中的所有词,如果它存在于待分词文本中,就使用正则匹配它
l_postions = []
for char_item in char_dict:
    if char_item in input_char:
        # search 全局搜索,第一次就返回,所以使用这种方法会导致出现两次文本无法被匹配到,仅起到演示效果
        match_result = re.search(char_item, input_char)
        # 防御性判空
        if match_result:
            postion = match_result.span()
            # 保存分词位置
            l_postions.extend([p for p in postion])
            # 把匹配后的字符使用*替换,继续匹配
            input_char = input_char[:postion[0]] + "*"*len(char_item) + input_char[postion[1]:]

# 将得到的切分位置列表去重后排序
l_postions = list(sorted(set(l_postions)))
# 这里的位置倒排是一个trick,可以解决迭代带来位置的变化的影响
l_postions.reverse()

# 使用位置对原待分词文本进行切分
l_split_data = []
for index in l_postions:
    l_split_data.append(origin_intput_char[index:])
    origin_intput_char = origin_intput_char[:index]
l_split_data.reverse()

# 最后用分隔符 / 连接文本列表
result = " / ".join(l_split_data)
print(result)

打印的结果是:

大家 / 好 / , / 我 / 是 / 锅贴 / 。 / 什么 / 是 / 洗 / 摸 / 杯 / ? / 张 / 美丽 / 人 / 长得 / 真 / 好看 / ! / 你 / 好, / 欢迎 / 来到 / 南京 / 市长 / 江大桥 / 参观 / 。

问题

这里面有很多显而易见的问题:

  1. 洗 / 摸 / 杯

    这三个字是一个整词洗摸杯,解释一下这是一个零零后们的网络用语,意思为:“我手洗干净了能摸摸你的奖杯吗?”。 这种新词在词典中是没有的,这也是基于词典的机械切分最大的问题之一。 改进方法就是不断的丰富词典。

  2. 张 / 美丽

    如果我们人来分的话,肯定会知道这是个人名张美丽,而这里没有做上下文联系以及语义上的理解,导致了切分错误。 一个很好的改进方法是做NER,也就是命名实体识别,它会把一些人名、地名等识别出来。

  3. 你 / 好

    导致你好被分开的原因是我们的词典中,既有也有你好,如果先对进行的匹配,那么你好就会被切分。 常见的做法是对于字典中的词进行从长到短的排序,先用长的词匹配,然后再用短的词匹配。

  4. 好,

    这个没有分开是代码的问题,由于代码仅做演示用,所有就没有做多次匹配,也就是说这个没有被匹配到。

  5. 南京 / 市长 / 江大桥

    似乎写到这里终于点题了。 在我们的字典中,既有市长也有长江大桥,如果仅仅正向匹配,那么就会只有前面的词被匹配出来。 为了解决这个,可以采取双向匹配,先正向再逆向,这样很容易找出有歧义的地方。 但如何判断歧义的地方如何选择呢?简单来说就是对于两次的结果取得到分词数量少的那个。

局限性

机械分词没有使用文本的上下文进行语义层次的切分,具有很大的局限性。 在业界中一些公开评测中,机械分词的最高精度也只能在 80% 左右,而利用了语言模型的分词器可以做到 98% !

因此,大家有兴趣尝试一下机械分词,平时开发还是老老实实的调包吧~


改进版的代码见 Github