跳转至

分词

1. 中文分词原理

1.1. 概述

中文分词指的是将连续的中文汉字序列,按照一定的规范,切分成一个一个单独的、有意义的词语的过程。

1.2 中文分词方法

常用的分词方法可分为两大类:基于词典(字符串匹配)的分词方法基于基于统计的分词的方法

1.2.1 基于词典(字符串匹配)的分词方法

原理:建立一个包含尽可能多词语的词典。对待分词的文本,按照一定的扫描方向(正向或反向)和匹配规则(如最长匹配),逐个与词典中的词进行匹配。如果能在词典中找到对应的词,就将其切分出来。

优点

  • 速度快效率高
  • 对于词典内已有的词效果很好

缺点

  • 未登录词处理能力弱:完全依赖预设词典,无法识别新词、专有名词(如人名、地名)或领域术语
  • 歧义消解能力有限:仅能通过简单规则(如词数、单字数量)优化,难以处理复杂语义歧义
  • 词典维护成本高:需持续更新词典以覆盖新词汇,尤其在网络文本快速迭代的场景下

常用算法

  • 正向最大匹配法(Forward Maximum Matching, FMM):从句子的最左边开始,尽可能多地匹配词典中最长的词。
  • 逆向最大匹配法(Backward Maximum Matching, BMM):从句子的最右边开始,尽可能多地匹配词典中最长的词。

    统计数据表明,逆向最大匹配算法切分的正确率高于正向最大匹配算法。

  • 双向最大匹配法:分别使用正向最大匹配法和逆向最大匹配法对句子进行切分,结果遵循以下规则:

    1. 切分结果完全相同时,准确率最大
    2. 切分结果不同时,切分词数目小的作为将结果
    3. 切分结果不同,且切分词数目相同,取逆向最大匹配算法结果

1.2.2 基于统计的分词方法

原理:在给定大规模语料库中已经分词的文本的前提下,利用字与字、词与词之间共同出现的统计规律,训练统计机器学习模型,学习词语切分的规律,来量化“词语”的成词可能性,并以此为依据,寻找全局最优的切分方案。

主要模型

  • n-gram语言模型

    将分词转化为选择概率最大的词序列问题,使用n-gram计算不同切分组合的流畅度(概率)。

    能较好地解决组合型歧义,但严重依赖候选切分集的质量,且存在数据稀疏问题。

  • 隐马尔科夫模型(Hidden Markov Model, HMM)

    通过“状态转移”和“观测发射”两种概率,计算哪个标签序列最可能生成当前的汉字序列。

    采用四个隐含状态,分别表示为单字成词,词组的开头,词组的中间,词组的结尾。通过标注好的分词训练集,可以得到 HMM 的各个参数,然后使用 Viterbi 算法来解释测试集,得到分词结果。

    是一种生成式模型,比词典法强大,但其“观测独立性”的强假设不符合语言事实,灵活性差。

  • 最大熵模型(maximum entropy model, MaxEnt)

    利用多种上下文特征(如前后字)进行概率估计的分类模型,遵循“在已知约束下选择最不确定分布”的原则。

    特征灵活,打破了HMM的独立性假设,但作为局部分类器,存在标记偏置问题,未能实现全局最优。

  • 条件随机场(Conditional Random Fields, CRF)

    一种判别式模型,能够定义并综合利用各种上下文特征,通过全局归一化寻找最优标签序列。

    克服了HMM的假设缺陷和最大熵的标记偏置问题,是经典统计方法的集大成者,效果优异。

具体原理此处不作详细说明,后续根据实际需求可分别单独撰写文档

2. 中文分词工具——jieba

2.1 介绍

jieba 分词是一个强大的、使用最广泛的中文分词库。

Github开源仓库:https://github.com/fxsjy/jieba

基本原理

  1. 基于前缀词典(Trie 树)进行词图扫描

    jieba 内置了一个庞大的词典,包含了常见的词语及其词频。它利用这个词典构建一个前缀树,快速识别句子中所有可能出现的词。

  2. 基于动态规划查找最大概率路径

    在识别出所有可能的词之后,会形成一个有向无环图(DAG)。然后使用 Viterbi 算法,找到一条基于词频(概率)的路径,使得整个句子的概率最大。这条路径就是最终的分词结果。

  3. 对于未登录词(OOV)

    未登录词是指不在词典中的新词,比如人名、技术名词、网络新词等。jieba 采用了基于汉字成词能力的HMM 模型来识别这些新词。

具体原理此处不作详细说明,后续根据实际需求可分别单独撰写文档

2.2 分词

2.2.1 默认分词

目前支持3种分词模式

模式 特点 适用场景
精确模式 将句子最精确地切开,不存在冗余词汇,结果准确。 文本分析
全模式 扫描出句子中所有可以成词的词语,速度非常快。但会产生大量的冗余词汇。 快速分词
搜索引擎模式 在精确模式的基础上,对长词再次切分,提高召回率。既兼顾了准确性,又能找到更细粒度的词语。 搜索引擎场景

代码实现

import jieba
text = "我来到北京清华大学"

1 . 精确模式

seg_list = jieba.cut(text, cut_all=False)
print("精确模式: " + "/ ".join(seg_list))
    精确模式: 我/ 来到/ 北京/ 清华大学

2 . 全模式

seg_list_all = jieba.cut(text, cut_all=True)
print("全模式: " + "/".join(seg_list_all))
    全模式: 我/来到/北京/清华/清华大学/华大/大学

3 . 搜索引擎模式

seg_list_search = jieba.cut_for_search(text)
print("全模式: " + "/".join(seg_list_search))
    全模式: 我/来到/北京/清华/华大/大学/清华大学

使用 隐马尔可夫(HMM) 模型:

HMM在jieba分词中主要用于解决未登录词(OOV)的识别问题。

以句子"他来到了网易杭研大厦"为例,HMM模型可以识别出“杭研”这个新的词

# 未启用 HMM
seg_list = jieba.cut("他来到了网易杭研大厦", HMM=False) #默认精确模式和不启用 HMM
print("未启用 HMM:" + "/ ".join(seg_list))
未启用 HMM:他/ 来到/ 了/ 网易/ 杭/ 研/ 大厦
# 启用 HMM
# 可识别到新词
seg_list = jieba.cut("他来到了网易杭研大厦") #默认精确模式和启用 HMM
print("启用 HMM:" + "/ ".join(seg_list)) 
启用 HMM:他/ 来到/ 了/ 网易/ 杭研/ 大厦

返回值说明:

  • jieba.cut() / jieba.cut_for_search() :返回生成器(generator)对象,需要遍历或转换为列表使用。

  • jieba.lcut() / jieba.lcut_for_search() :直接返回列表(list),方便直接使用。

下面是jieba.lcut()在精确模式下的使用效果:

seg_list = jieba.lcut(text, cut_all=False)
print(seg_list)
['我', '来到', '北京', '清华大学']

2.2.2 添加自定义词典

作用: 添加自定义词典后,jieba能够准确识别词典中出现的词汇,避免将专业术语、专有名词等拆分成单个字或无关词汇。

格式要求: 每一行包含三部分,用空格分隔,顺序不可颠倒

  • 词语(必填):需要识别的词汇
  • 词频(可选):词汇出现的频率,不填则由jieba自动计算
  • 词性(可选):词汇的词性标注,不填则由jieba自动判断

  • 示例:

    黑马程序员 5 n
    传智教育 6 n
    人工智能 7 nz
    学习 3      # 省略词性
    上市        # 省略词频和词性
    

使用效果:

import jieba
text1 = "传智教育是一家上市公司,旗下有黑马程序员品牌。我是在黑马这里学习人工智能"

1 . 不使用自定义词典

# 未加载词典
jieba.initialize()
seg_list1 = jieba.cut(text1, cut_all=False)
print("未使用自定义词典:" + '/ '.join(seg_list1))
未使用自定义词典:传智/教育/是/一家/上市公司/,/旗下/有/黑马/程序员/品牌/。/我/是/在/黑马/这里/学习/人工智能

2 . 使用自定义词典

# 载入词典
jieba.load_userdict("userdict.txt")
seg_list2 = jieba.cut(text1, cut_all=False)
print("未使用自定义词典:" + '/ '.join(seg_list2))
未使用自定义词典:传智教育/是/一家/上市公司/,/旗下/有/黑马程序员/品牌/。/我/是/在/黑马/这里/学习/人工智能

使用词典前后对比: | 场景 | 结果差异 | | :----: | ---- | | 未使用自定义词典 | 传智、教育、黑马、程序员 | | 使用自定义词典 | 传智教育、黑马程序员 |

2.2.3 jieba词典增加、删除词

可以通过添加或删除单个词来改变分词结果。增加或删除词只能比原来切分的长度要长才起效果

import jieba
sentence="我来到杭州浙江工业大学"
seg_list=jieba.cut(sentence, cut_all = False)   
print("默认效果:" + "/".join(seg_list))
    默认效果:我/来到/杭州/浙江工业大学

1 . 增加更长的词

jieba.add_word("我来到")
seg_list=jieba.cut(sentence, cut_all = False)   
print("加入“我来到”:" + "/".join(seg_list))
    加入“我来到”:我来到/杭州/浙江工业大学

2 . 增加比原来分的更短的词

jieba.add_word("浙")
seg_list=jieba.cut(sentence, cut_all = False)   
print("加入“浙”:" + "/".join(seg_list))
加入“浙”:我来到/杭州/浙江工业大学

可以看到增加比原来切分的长度短的词不起效果

3 . 删除词

jieba.del_word("浙江工业大学")
seg_list=jieba.cut(sentence, cut_all = False)   
print("删除“浙江工业大学”:" + "/".join(seg_list))
删除“浙江工业大学”:我来到/杭州/浙江/工业/大学

3.补充内容

3.1 基于词典的分词方法简单实现

Dictionary = ["我","来到","杭州","浙江","工业","大学","选工","大有","底气","工大","有底气","业大"] 
Sentence = "我来到杭州浙江工业大学,选工大有底气"

1 . 正向最大匹配法(Forward Maximum Matching, FMM)

def FMM(sentence, dic):
    result = []
    #获取字典中最长的词语长度
    max_word_length = len(max(dic, key=len))
    while sentence:
        # 当前匹配窗口大小
        match_size = min(len(sentence), max_word_length)
        matched = False
        # 尝试从最大长度开始匹配
        for length in range(match_size, 0, -1):
            word = sentence[:length]
            if word in dic:
                result.append(word)
                sentence = sentence[length:]
                matched = True
                break
        # 如果没有匹配到任何词,则切分单个字符
        if not matched:
            result.append(sentence[0])
            sentence = sentence[1:]
    return result

FFM_result = FMM(Sentence, Dictionary)
print("FMM切分结果:", FFM_result)
FMM切分结果: ['我', '来到', '杭州', '浙江', '工业', '大学', ',', '选工', '大有', '底气']

2 . 逆向最大匹配法(Backward Maximum Matching, BMM)

def BMM(sentence, dic):
    result = []
    #获取字典中最长的词语长度
    max_word_length = len(max(dic, key=len))
    while sentence:
        # 当前匹配长度大小
        match_size = min(len(sentence), max_word_length)
        matched = False
        # 尝试从最大长度开始匹配
        for length in range(match_size, 0, -1):
            word = sentence[-length:]
            if word in dic:
                result.insert(0, word) #在开头插入
                sentence = sentence[:-length]
                matched = True
                break
        # 如果没有匹配到任何词,则切分单个字符
        if not matched:
            result.insert(0, sentence[-1])
            sentence = sentence[:-1]
    return result

BMM_result = BMM(Sentence, Dictionary)
print("BMM切分结果:", BMM_result)
BMM切分结果: ['我', '来到', '杭州', '浙江', '工业', '大学', ',', '选', '工大', '有底气']

把上面两个结果放在一起,可以看出区别

print("字典:", Dictionary)
print("句子:", Sentence)
FFM_result = FMM(Sentence, Dictionary)
print("FMM切分结果:", FFM_result)
BMM_result = BMM(Sentence, Dictionary)
print("BMM切分结果:", BMM_result)
字典: ['我', '来到', '杭州', '浙江', '工业', '大学', '选工', '大有', '底气', '工大', '有底气', '业大']
句子: 我来到杭州浙江工业大学,选工大有底气
FMM切分结果: ['我', '来到', '杭州', '浙江', '工业', '大学', ',', '选工', '大有', '底气']
BMM切分结果: ['我', '来到', '杭州', '浙江', '工业', '大学', ',', '选', '工大', '有底气']

评论