logo

NLP-分词

王哲峰 / 2022-04-05


目录

中文分词

在语言理解中, 词是最小的能够独立活动的有意义的语言成分. 将词确定下来是理解自然语言的第一步, 只有跨越了这一步, 中文才能像英文那样过渡到短语划分、概念抽取以及主题分析, 以致自然语言理解, 最终达到智能计算的最高境界.

的概念一直是汉语言语言学界纠缠不清而又绕不开的问题. 主要难点在于汉语结构与印欧体系语种差异甚大, 对词的构成边界方面很难进行界定

自中文自动分词被提出以来, 历经将近 30 年的探索, 提出了很多方法, 可主要归纳为:

规则分词

基于规则的分词是一种机械的分词方法, 主要通过维护词典, 在切分语句时, 将语句的每个字符串与词汇表中的词逐一进行匹配, 找到则切分, 否则不予切分.

按照匹配切分的方式, 主要有:

基于规则的分词, 一般都较为简单高效, 但是词典的维护是一个很庞大的工程。 在网络发达的今天, 网络新词层出不穷, 很难通过词典覆盖到所有词。

正向最大匹配法

正向最大匹配法思想:

正向最大匹配(Maximum Match Method, MM 法)的基本思想为:假定分词词典中的最长词有 $i$ 个汉字字符, 则用被处理文档的当前字符串中的前 $i$ 个字符作为匹配字段, 查找字典

如此进行下去, 直到匹配成功, 即切分出词或剩余字符串的长度为零为止。这样就完成了一轮匹配, 然后取下一个 $i$ 字字符串进行匹配处理, 直到文档被扫描完为止。

正向最大匹配算法描述如下:

  1. 从左向右取待切分汉语句的 $m$ 个字符作为匹配字段, $m$ 为机器词典中最长词条的字符数
  2. 查找机器词典并进行匹配
    • 若匹配成功, 则将这个匹配字段作为一个词切分出来
    • 若匹配不成功, 则将这个匹配字段的最后一个字去掉, 剩下的字符串作为新的匹配字段, 进行再次匹配, 重复以上过程, 直到切分出所有词为止

正向最大匹配法示例:

class MM(object):
    """
    正向最大匹配
    """
    def __init__(self):
        self.window_size = 3

    def cut(self, text):
        result = []
        index = 0
        text_length = len(text)
        dic = ["研究", "研究生", "生命", "命", "的", "起源"]
        while text_length > index:
            for size in range(self.window_size + index, index, -1):
                piece = text[index:size]
                if piece in dic:
                    index = size - 1
                    break
            index = index + 1
            result.append(piece + "----")
        print(result)

if __name__ == "__main__":
    text = "研究生命的起源"
    tokenizer = MM()
    print(tokenizer.cut(text))

逆向最大匹配法

逆向最大匹配法思想:

逆向最大匹配法示例:

class RMM(object):
    """
    逆向最大匹配法
    """
    def __init__(self):
        self.window_size = 3

    def cut(self, text):
        result = []
        index = len(text)
        dic = ["研究", "研究生", "生命", "命", "的", "起源"]
        while index > 0:
                for size in range(index - self.window_size, index):
                piece = text[size:index]
                if piece in dic:
                    index = size + 1
                    break
                index = index - 1
                result.append(piece + "----")
        result.reverse()
        print(result)

if __name__ == "__main__":
    text = "研究生命的起源"
    RMM_tokenizer = RMM()
    print(RMM_tokenizer.cut(text))

双向最大匹配法

双向最大匹配法思想:

双向最大匹配的规则是:

双向最大匹配法示例:

class BMM(object):
    """
    双向最大匹配法
    """
    def __init__(self):
        pass

    def cut(self, text):
        pass

if __init__ == "__main__":
    text = "研究生命的起源"
    BMM_tokenizer = BMM()
    print(BMM_tokenizer.cut(text))

统计分词

随着大规模语料库的建立, 统计机器学习方法的研究和发展, 基于统计的中文分词算法逐渐成为主流。

语言模型

语言模型在信息检索、机器翻译、语音识别中承担着重要的任务。 用概率论的专业术语描述语言模型就是:

为长度为 $m$ 的字符串确定其概率分布 $P(\omega_{1}, \omega_{2}, \cdot, \omega_{m})$, 其中 $\omega_{1}$$\omega_{m}$ 依次表示文本中的各个词语

一般采用链式法计算其概率值:

$$P(\omega_{1}, \omega_{2}, \cdots, \omega_{m})= P(\omega_{1})P(\omega_{2}|\omega_{1})P(\omega_{3}|\omega_{1}, \omega_{2}) \cdots P(\omega_{i}|\omega_{1}, \omega_{2}, \cdots, \omega_{i-1}) \cdots P(\omega_{m}|\omega_{1}, \omega_{2}, \cdots, \omega_{m-1})$$

当文本过长时, 公式右部从第三项起的每一项计算难度都很大。为了解决该问题, 有人提出了 $n$ 元模型(n-gram model) 降低该计算难度。 所谓 $n$ 元模型就是在估算条件概率时, 忽略距离大与等于 $n$ 的上下文词的影响, 因此:

$$P(\omega_{i}|\omega_{1}, \omega_{2}, \cdots, \omega_{i-1}) = P(\omega_{i}|\omega_{i-(n-1)}, \omega_{i-(n-2)}, \cdots, \omega_{i-1})$$

$n=1$ 时, 称为一元模型(unigram model), 此时整个句子的概率可以表示为:

$$P(\omega_{1}, \omega_{2}, \cdots, \omega_{m}) = P(\omega_{1})P(\omega_{2}) \cdots P(\omega_{m})$$

在一元模型中, 整个句子的概率等于各个词语概率的乘积, 即各个词之间都是相互独立的, 这无疑是完全损失了句中的词序信息, 所以一元模型的效果并不理想

$n=2$ 时, 称为二元模型(bigram model), 概率的计算变为:

$$P(\omega_{i}|\omega_{1}, \omega_{2}, \cdots, \omega_{i-1}) = P(\omega_{i}|\omega_{i-1})$$

$n=3$ 时, 称为三元模型(trigram model), 概率的计算变为:

$$P(\omega_{i}|\omega_{1}, \omega_{2}, \cdots, \omega_{i-1}) = P(\omega_{i}|\omega_{i-2},\omega_{i-1})$$

$n \geq 2$ 时, 该模型是可以保留一定的词序信息的, 而且 $n$ 越大, 保留的词序信息越丰富, 但计算成本也呈指数级增长

一般使用频率计数的比例来计算 $n$ 元条件概率:

$$P(\omega_{i}|\omega_{i-(n-1)}, \omega_{i-(n-2)}, \cdots, \omega_{i-1}) = \frac{count(\omega_{i-(n-1)}, \cdots, \omega_{i-1},\omega_{i})}{count(\omega_{i-(n-1)}, \cdots, \omega_{i-1})}$$

其中 $count(\omega_{i-(n-1)}, \cdots, \omega_{i-1})$ 表示词语 $\omega_{i-(n-1)}, \cdots, \omega_{i-1}$ 在语料库中出现的总次数

综上, 当 $n$ 越大时, 模型包含的词序信息越丰富, 同时计算量随之增大。 与此同时, 长度越长的文本序列出现的次数也会越少, 这样, 按照上式估计 $n$ 元条件概率时, 就会出现分子、分母为零的情况。因此, 一般在 $n$ 元模型中需要配合相应的平滑算法解决该问题, 如拉普拉斯平滑算法等

HMM 模型

隐马尔科夫模型(HMM)是将分词作为字在字符串中的序列标注任务来实现的。

隐马尔科夫模型的基本思路是: 每个字在构造一个特定的词语时都占据着一个确定的构词位置(即词位), 现规定每个字最多只有四个构词位置:

用数学抽象表示如下:

$\lambda = \lambda_{1}\lambda_{2}\lambda_{n}$ 代表输入的句子, $n$ 为句子长度,
$\lambda_{i}$ 表示字, $o=o_{1}o_{2} \cdots o_{n}$ 代表输出的标签, 那么理想的输出即为:

$$max = max P(o_{1}o_{2} \cdots o_{n}|\lambda_{1}\lambda_{2} \cdots \lambda_{n})$$

在分词任务上, $o$ 即为 B、M、E、S 这四种标记, $\lambda$ 为诸如 “中”、“文” 等句子中的每个字(包括标点等非中文字符). 需要注意的是, $P(o|\lambda)$ 是关于 2n 个变量的条件概率, 且 n 不固定。因此, 几乎无法对 $P(o|\lambda)$ 进行精确计算。 这里引入观测独立性假设, 即每个字的输出仅仅与当前字有关, 于是就能得到下式:

$$P(o_{1}o_{2} \cdots o_{n}|\lambda_{1}\lambda_{2} \cdots \lambda_{n}) = p(o_{1}|\lambda_{1})p(o_{2}|\lambda_{2}) \cdots p(o_{n}|\lambda_{n})$$

示例: 下面句子(1)的分词结果就可以直接表示成如(2)所示的逐字标注形式:

其他统计分词算法

混合分词

事实上, 目前不管是基于规则的算法、基于 HMM、CRF 或者 deep learning 等的方法, 其分词效果在具体任务中, 其实差距并没有那么明显。

在实际工程应用中, 多是基于一种分词算法, 然后用其他分词算法加以辅助。 最常用的方式就是先基于词典的方式进行分词, 然后再用统计方法进行辅助。 如此, 能在保证词典分词准确率的基础上, 对未登录词和歧义词有较好的识别。

jieba 分词工具就是基于这种方法的实现。

外文分词

jieba 分词

安装

$ pip install paddlepaddle-tiny=1.6.1 # Python3.7
$ pip install jieba

特点、算法

特点: 支持四种分词模式:

算法:

分词

API:

添加自定义词典

开发者可以指定自己自定义的词典, 以便包含 jieba 词库里没有的词。 虽然 jieba 有新词识别能力, 但是自行添加新词可以保证更高的正确率。

用法:

API:

示例:

import sys
import jieba
import jieba.posseg as pseg
sys.path.append("./util_data")
jieba.load_userdict("./util_data/userdict.txt")

jieba.add_word("石墨烯")
jieba.add_word("凱特琳")
jieba.add_word("自定义词")

test_sent = (
    "李小福是创新办主任也是云计算方面的专家; 什么是八一双鹿\n"
    "例如我输入一个带“韩玉赏鉴”的标题, 在自定义词库中也增加了此词为N类\n"
    "「台中」正確應該不會被切開。mac上可分出「石墨烯」; 此時又可以分出來凱特琳了。"
)
words = jieba.cut(test_sent)
print(" ".join(words))

调整词典

API:

示例:

string = "如果放到旧字典中将出错。"
seg_list = jieba.cut(string, HMM = False)
print(" ".join(seg_list))
jieba.suggest_freq(segment = ("中", "将"), tune = True)
seg_list_tuned = jieba.cut(string, HMM = False)
print(" ".join(seg_list_tuned))

关键词提取

基于 TF-IDF 算法的关键词提取

API:

用法:

基于 TextRank 算法的关键词提取

API:

算法论文:

基本思想:

使用示例:

词性标注

并行分词

Tokenize:返回词语在原文的起止位置

ChineseAnalyzer for Whoosh 搜索引擎

API:

示例:

命令行分词

语法:

$ python -m jieba [option] filename

示例:

$ python -m jieba news.txt > cut_result.txt

命令行选项

其他分词