• 中文分词概述及结巴分词原理


    词是中文表达语义的最小单位,自然语言处理的基础步骤就是分词,分词的结果对中文信息处理至为关键。

    本文先对中文分词方法进行一下概述,然后简单讲解一下结巴分词背后的原理。

    中文分词概述

    简单来说,中文分词根据实现特点大致可分为两个类别:

    基于词典的分词方法、基于统计的分词方法。

    基于词典的分词方法

    基于词典的分词方法首先会建立一个充分大的词典,然后依据一定的策略扫描句子,若句子中的某个子串与词典中的某个词匹配,则分词成功。

    常见的扫描策略有:正向最大匹配、逆向最大匹配、双向最大匹配和最少词数分词。

    正向最大匹配

    对输入的句子从左至右,以贪心的方式切分出当前位置上长度最大的词,组不了词的字单独划开。其分词原理是:词的颗粒度越大,所能表示的含义越精确。

    逆向最大匹配

    原理与正向最大匹配相同,但顺序不是从首字开始,而是从末字开始,而且它使用的分词词典是逆序词典,其中每个词条都按逆序方式存放。在实际处理时,先将句子进行倒排处理,生成逆序句子,然后根据逆序词典,对逆序句子用正向最大匹配。

    双向最大匹配

    将正向最大匹配与逆向最大匹配组合起来,对句子使用这两种方式进行扫描切分,如果两种分词方法得到的匹配结果相同,则认为分词正确,否则,按最小集处理。

    最少词数分词

    即一句话应该分成数量最少的词串,该方法首先会查找词典中最长的词,看是不是所要分词的句子的子串,如果是则切分,然后不断迭代以上步骤,每次都会在剩余的字符串中取最长的词进行分词,最后就可以得到最少的词数。

    总结:基于词典的分词方法简单、速度快,效果也还可以,但对歧义和新词的处理不是很好,对词典中未登录的词没法进行处理。

    基于统计的分词方法

    基于统计的分词方法是从大量已经分词的文本中,利用统计学习方法来学习词的切分规律,从而实现对未知文本的切分。随着大规模语料库的建立,基于统计的分词方法不断受到研究和发展,渐渐成为了主流。

    常用的统计学习方法有:隐马尔可夫模型(HMM)、条件随机场(CRF)和基于深度学习的方法。

    HMM和CRF

    这两种方法实质上是对序列进行标注,将分词问题转化为字的分类问题,每个字有4种词位(类别):词首(B)、词中(M)、词尾(E)和单字成词(S)。由字构词的方法并不依赖于事先编制好的词典,只需对分好词的语料进行训练即可。当模型训练好后,就可对新句子进行预测,预测时会针对每个字生成不同的词位。其中HMM属于生成式模型,CRF属于判别式模型。

    基于深度学习的方法

    神经网络的序列标注算法在词性标注、命名实体识别等问题上取得了优秀的进展,这些端到端的方法也可以迁移到分词问题上。与所有深度学习的方法一样,该方法需要较大的训练语料才能体现优势,代表为BiLSTM-CRF。

    总结:基于统计的分词方法能很好地处理歧义和新词问题,效果比基于词典的要好,但该方法需要有大量人工标注分好词的语料作为支撑,训练开销大,就分词速度而言不如前一种。

    在实际应用中一般是将词典与统计学习方法结合起来,既发挥词典分词切分速度快的特点,又利用了统计分词结合上下文识别生词、自动消除歧义的优点。结巴分词正是这一类的代表,下面简要介绍一下它的实现算法。

    结巴分词原理

    官方Github上对所用算法的描述为:

    基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图 (DAG);

    采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合;

    对于未登录词,采用了基于汉字成词能力的 HMM 模型,使用了 Viterbi 算法。

    下面逐一介绍:

    构造前缀词典

    结巴分词首先会依照统计词典dict.txt构造前缀词典。dict.txt含有近35万的词条,每个词条占用一行,其中每一行有3列,第一列为词条,第二列为对应的词频,第三列为词性,构造前缀词典需要用到前两列。

    具体做法为:首先定义一个空的python字典,然后遍历dict.txt的每一行,取词条作为字典的键,词频作为对应的键值,然后遍历该词条的前缀,如果前缀对应的键不在字典里,就把该前缀设为字典新的键,对应的键值设为0,如果前缀在字典里,则什么都不做。

    这样等遍历完dict.txt后,前缀词典就构造好了。在构造前缀词典时,会对统计词典里所有词条的词频做一下累加,累加值等计算最大概率路径时会用到。

    生成有向无环图(DAG)

    用正则表达式分割句子后,对每一个单独的子句会生成一个有向无环图。

    具体方式为:先定义一个空的python字典,然后遍历子句,当前子句元素的索引会作为字典的一个键,对应的键值为一个python列表(初始为空),然后会以当前索引作为子串的起始索引,不断向后遍历生成不同的子串,如果子串在前缀词典里且键值不为0的话,则把子串的终止索引添加到列表中。

    这样等遍历完子句的所有字后,对应的DAG就生成好了。(子串的键值如果是0,则说明它不是一个词条)

    计算最大概率路径

    DAG的起点到终点会有很多路径,需要找到一条概率最大的路径,然后据此进行分词。可以采用动态规划来求解最大概率路径。

    具体来说就是:从子句的最后一个字开始,倒序遍历子句的每个字,取当前字对应索引在DAG字典中的键值(一个python列表),然后遍历该列表,当前字会和列表中每个字两两组合成一个词条,然后基于词频计算出当前字到句尾的概率,以python元组的方式保存最大概率,元祖第一个元素是最大概率的对数,第二个元素为最大概率对应词条的终止索引。

    词频可看作DAG中边的权重,之所以取概率的对数是为了防止数值下溢。有了最大概率路径,分词结果也就随之确定。

    对未登录词采用HMM模型进行分词

    当出现没有在前缀词典里收录的词时,会采用HMM模型进行分词。HMM模型有5个基本组成:观测序列、状态序列、状态初始概率、状态转移概率和状态发射概率。分词属于HMM的预测问题,即已知观测序列、状态初始概率、状态转移概率和状态发射概率的条件下,求状态序列。结巴分词已经内置了训练好的状态初始概率、状态转移概率和状态发射概率。

    句子会作为观测序列,当有新句子进来时,具体做法为:先通过Viterbi算法求出概率最大的状态序列,然后基于状态序列输出分词结果(每个字的状态为B、M、E、S之一)。

    至此,结巴分词的原理就简单介绍完了。

    最后举一个简单的例子:

    假如待分词的句子为: “这几天都在学自然语言处理”。

    首先依据前缀词典生成DAG:

    {  0: [0],

       1: [1, 2],

       2: [2, 3],

       3: [3],

       4: [4],

       5: [5],

       6: [6, 7, 9],

       7: [7],

       8: [8, 9],

       9: [9],

      10: [10, 11],

      11: [11]  }

    句子元素对应的索引会作为字典的键,对应键值的第一项与当前索引相同,其余项会与当前索引组成词条,这个词条在前缀词典里且对应键值不为0。

    生成的DAG如下:

     

    然后采用动态规划求出的最大概率路径为:

    {12: (0, 0),

     11: (-9.073726763747516, 11),

     10: (-8.620554852761583, 11),

      9: (-17.35315508178225, 9),

      8: (-17.590039287472578, 9),

      7: (-27.280113467960604, 7),

      6: (-22.70346658402771, 9),

      5: (-30.846092652642497, 5),

      4: (-35.25970621827743, 4),

      3: (-40.95138241952608, 3),

      2: (-48.372244833381465, 2),

      1: (-50.4870755319817, 2),

      0: (-55.92332690525722, 0)}

    最大概率路径按句子索引倒序排列,但分词时会从索引0开始顺序遍历句子。

    具体做法为:

    首先遍历0,0对应的键值最后一项为0,即词的长度为1,遇到长度为1的词时(即单字)先不分,继续往后看,然后遍历1,1对应的键值最后一项为2,即词的长度为2,这时会把索引为0的单字作为词分割出来,然后接着把索引1、2对应的词分割出来,然后遍历3,3对应的键值最后一项为3,属于单字,先不分,索引4、5同理,然后遍历6,6对应的键值最后一项为9,即词的长度为4,注意,这里索引3、4、5对应的单字序列(即“都在学”)如果不在前缀词典中或者在前缀词典中但键值为0,则会对单字序列采用HMM模型进行分词,否则的话,会对单字序列每个字进行分词,分好之后把索引6、7、8、9对应的词分割出去,然后遍历10,10对应的键值最后一项为11,即词的长度为2,直接把索引10、11对应的词分割出去,至此分词结束。

    总结一下:

    在遇到长度>=2的词之前会把它前面出现的所有单字保存下来。

    如果保存下来的单字序列长度为0,则直接把当前词分割出去;

    如果保存下来的单字序列长度为1,则直接把单字作为词分割出去,然后把后面词分割出去;

    如果保存下来的单字序列长度>1,会分两种情况:假如单字序列不在前缀词典中或者在前缀词典中但键值为0,则会对单字序列采用HMM模型进行分词,否则的话,会对单字序列每个字进行分词。

    最后分好的词为:['这',  '几天',  '都',  '在',  '学',  '自然语言',  '处理']。

  • 相关阅读:
    C# macro function via #define __FILE__ __LINE__ ___FUNCTION__ __DATE__ __TIME__
    3
    2月23号
    3月26
    impala故障
    2月3号日更
    HDFS某个节点的磁盘满了
    3月2
    mq集群
    3月3
  • 原文地址:https://www.cnblogs.com/ExMan/p/14919424.html
Copyright © 2020-2023  润新知