一、分词方法

关于中文分词 参考之前写的jieba分词源码分析 。
中文分词算法大概分为两大类:

  • 一是基于字符串匹配,即扫描字符串,使用如正向/逆向最大匹配,最小切分等策略(俗称基于词典的)
    基于词典的分词算法比较常见,比如正向/逆向最大匹配,最小切分(使一句话中的词语数量最少)等。具体使用的时候,通常是多种算法合用,或者一种为主、多种为辅,同时还会加入词性、词频等属性来辅助处理(运用某些简单的数学模型)。
    这类算法优点是速度块,时间复杂度较低,实现简单,文中介绍的mmseg属于这类算法。
  • 二是基于统计以及机器学习的分词方式(非词典方法)
    一般主要是运用概率统计、机器学习等方面的方法,目前常见的是CRF,HMM等。
    这类分词事先对中文进行建模,根据观测到的数据(标注好的语料)对模型参数进行估计(训练)。 在分词阶段再通过模型计算各种分词出现的概率,将概率最大的分词结果作为最终结果。
    有关HMM可以参见之前写的 jieba HMM。CRF分词可以参考 这里
    这类算法对未登录词识别效果较好,能够根据使用领域达到较高的分词精度,但是实现比较复杂,通常需要大量的前期工作。

二、mmseg算法

下面介绍下mmseg算法:

mmseg算法

使用过mmseg算法的童鞋都反映其简单、高效、实用、效果还不错,参考论文 mmseg
其主要思想每次从一个完整的句子里,按照从左向右的顺序,识别出多种不同的3个词的组合,然后根据下面的4条消歧规则,确定最佳的备选词组合。
选择备选词组合中的第1个词,作为1次迭代的分词结果;剩余的词(即这个句子中除了第一个已经分出的词的剩余部分)继续进行下一轮的分词运算。采用这种办法的好处是,为传统的前向最大匹配算法加入了上下文信息,解决了其每次选词只考虑词本身,而忽视上下文相关词的问题。 mmseg论文中说明了(The Maximum Matching Algorithm and Its Variants)Maximum Matching Algorithm的两种方法:
1. Simple方法,即简单的正向匹配,根据开头的字,列出所有可能的结果。比如“国际化大都市”,可以得到:

国际
国际化

这三个匹配结果(假设这四个词都包含在词典里)。
2. Complex方法,匹配出所有的“三个词的词组”(即原文中的chunk,“词组”),即从某一既定的字为起始位置,得到所有可能的“以三个词为一组”的所有组合。比如“研究生命起源”,可以得到
研_究_生
研_究_生命
研究生_命_起源
研究_生命_起源
这些“词组”(根据词典,可能远不止这些,仅此举例)。

四个规则

消除歧义的规则”有四个,使用中依次用这四个规则进行过滤,直到只有一种结果或者第四个规则使用完毕,4条消歧规则包括:
1. 备选词组合的长度之和最大(Maximum matching (Chen & Liu 1992));
2. 备选词组合的平均词长最大(Largest average word length (Chen & Liu, 1992));
3. 备选词组合的词长变化最小(Smallest variance of word lengths (Chen & Liu, 1992));
4. 备选词组合中,单字词的出现频率统计值最高(取单字词词频的自然对数,然后将得到的值相加,取总和最大的词)(Largest sum of degree of morphemic freedom of one-character words)。
下面分别举例说明一下4个规则:

  • 备选词组合的长度之和最大(Maximum matching (Chen & Liu 1992))
    有两种情况,分别对应于使用“simple”和“complex”的匹配方法。对“simple”匹配方法,选择长度最大的词,用在上文的例子中即选择“国际化”;
    对“complex”匹配方法,选择“词组长度最大的”那个词组,然后选择这个词组的第一个词,作为切分出的第一个词,如上文 的例子中即“研究生_命_起源”中的“研究生”,或者“研究_生命_起源”中的“研究”。
    对于有超过一个词组满足这个条件的则应用下一个规则。
  • 备选词组合的平均词长最大(Largest average word length (Chen & Liu, 1992))
    经过规则1过滤后,如果剩余的词组超过1个,那就选择平均词语长度最大的那个(平均词长=词组总字数/词语数量)。比如“生活水平”,可能得到如下词组:
    生_活水_平 (4/3=1.33)
    生活_水_平 (4/3=1.33)
    生活_水平 (4/2=2)
    根据此规则,就可以确定选择“生活_水平”这个词组。
    对于上面的 “研究生命起源” 结果:”研究生_命_起源“ “研究_生命_起源” 利用rule2有
    研究生_命_起源 (6/3=2)
    研究_生命_起源 (6/3=2)
    可见这条规则没有起作用,因为两者平均长度相同。论文中也有说明:

This rule is useful only for condition in which one or more word position in the chunks are empty. When the chunks are real three-word chunks, this rule is not useful. Because three-word chunks with the same total length will certainly have the same average length. Therefore we need another solution.


  • 备选词组合的词长变化最小(Smallest variance of word lengths (Chen & Liu, 1992))
    由于词语长度的变化率可以由标准差反映,所以此处直接套用标准差公式即可。比如 对于上面的case “研究生命起源”有:
    研究_生命_起源 (标准差=sqrt(((2-2)^2+(2-2)^2+(2-2^2))/3)=0)
    研究生_命_起源 (标准差=sqrt(((2-3)^2+(2-1)^2+(2-2)^2)/3)=0.8165)
    于是通过这一规则选择“研究_生命_起源”这个词组。
  • 备选词组合中,单字词的出现频率统计值最高(Largest sum of degree of morphemic freedom of one-character words)
    这里的degree of morphemic freedom可以用一个数学公式表达:log(frequency),即词频的自然对数(这里log表示数学中的ln)。这个规则的意思是“计算词组中的所有单字词词频的自然对数,然后将得到的值相加,取总和最大的词组”。比如:
    “设施和服务”,这个会有如下几种组合
    设施_和服_务_
    设施_和_服务_
    设_施_和服_

    经过规则1过滤得到:
    设施_和服_务_
    设施_和_服务_

    规则2和规则3都无法得到唯一结果,只能利用最后一个规则 第一条中的“务”和第二条中的“和”,从直观看,显然是“和”的词频在日常场景下要高,这依赖一个词频字典和的词频决定了的分词。
    假设“务”作为单字词时候的频率是30,“和”作为单字词时候的频率是100,对30和100取自然对数,然后取最大值者,所以取“和”字所在的词组,即“设施_和_服务”。

    也许会问为什么要对“词频”取自然对数呢?可以这样理解,词组中单字词词频总和可能一样,但是实际的效果并不同,比如
    A_BBB_C (单字词词频,A:3, C:7)
    DD_E_F (单字词词频,E:5,F:5)
    表示两个词组,A、C、E、F表示不同的单字词,如果不取自然对数,单纯就词频来计算,那么这两个词组是一样的(3+7=5+5),但实际上不同的词频范围所表示的效果也不同,所以这里取自然对数,以表区分(ln(3)+ln(7) < ln(5)+ln(5), 3.0445<3.2189)。


这个四个过滤规则中,如果使用simple的匹配方法,只能使用第一个规则过滤,如果使用complex的匹配方法,则四个规则都可以使用。实际使用中,一般都是使用complex的匹配方法+四个规则过滤。(实际中complex用的最多)。


通过上面的叙述可以发现MMSEG是一个“直观”的分词方法。它把一个句子“尽可能长” && “尽可能均匀”(“尽可能长”是指所切分的词尽可能的长)的区切分,这与中文的语法习惯比较相符,其实MMSEG的分词效果与词典关系较大,尤其是词典中单字词的频率。可以根据使用领域,专门定制词典(比如计算机类词库,生活信息类词库,旅游类词库等,欢迎体验我狗的 细胞词库 ),尽可能的细分词典,这样得到的分词效果会好很多。


总的来说 MMSEG是一个简单、可行、快速的分词方法。

三、mmseg分词算法实现

有关mmseg的分词策略通过上面的介绍,大家有一定的认识了,其**主要**思想就是每次从一个完整的句子里,按照从左向右的顺序,识别出多种不同的3个词的组合(chunk),然后根据mmseg的4条消歧规则,确定最佳的备选词组合,选择备选词组合中的最佳词(即第1个词),作为1次迭代的分词结果;剩余的词(即这个句子中除了第一个已经分出的词的剩余部分)继续进行下一轮的分词运算。可以仔细阅读mmseg的论文,写的挺详细的。指导算法思路,下面来看下实现过程: mmseg分词所需要的工作分为下面几点:

  • 转码,string转换成utf16( unicode),以及逆转换。
    关键部分,是分词的前提。因为每次进行分词前,都需要将string decode成utf16(unicode),分词完要输出的时候又需要将unicode encode成string。 因为在分词算法中,对于句子是按一个字一个字来计算和分词的。所以转换成unicode是完成分词算法的必要前提。转码部分利用了StringUtil.hpp的转码模块。
  • trie字典树
    将词库字典转换成trie树,从而可以进行高效查找。实现起来利用c++的数据结构unordered_map即可实现,详细参见代码。
    mmseg实现起来采用了{.h, .cpp} and {.hpp} 两种组织方式,为什么采用hpp?

mmseg的主要目录结构如下:

mmseg
├── build : 生成*.so 动态链接库
├── data :词典数据
└── src : 实现源码
└── util : 字符串处理通用库

代码采用c++11实现(g++ version >= 4.8 is recommended),已有详细注释,不在敷述,请见源码。
https://github.com/ustcdane/mmseg

  • 使用方法
    在主程序中 如main.cpp
  • 如果使用hpp格式的文件 即#include “src/Mmseg.hpp”,编译方式为:
    g++ -Ofast -march=native -funroll-loops -o mmseg -std=c++11 main.cpp
  • 如果使用h格式的文件 即#include “src/Mmseg.h”,编译方式为:
    g++ -Ofast -march=native -funroll-loops -o mmseg -std=c++11 main.cpp src/Mmseg.cpp
    另外为了能够方便的在其他地方使用mmseg分词,也可以把这部分代码编译成*.so的动态链接库,进入目录build , 里面有已经写好的Makefile文件 直接make 即可生成 libmmseg.so的 动态链接库文件,从而方便在其它程序中调用。

注意 为了更清楚的看mmseg的过程,我分别在 Mmseg.hpp 和 Mmseg.cpp中定义了宏
#define DEBUG_LEVEL , 如果不想看计算过程可以直接注释掉这一行代码。

四、测试结果

进入mmesg ,运行命令,编译main.cpp,生成mmseg可执行文件。

g++ -Ofast -march=native -funroll-loops -o mmseg -std=c++11 main.cpp src/Mmseg.cpp

运行mmseg即可测试,在我的机器上测试结果如下。

java 中文分词开源库技术选型 开源分词算法_分词

源码见:https://github.com/ustcdane/mmseg ,实现过程参考了 jdeng 关于mmseg的代码。艾玛,搞了一天终于写完。