中文分词——知更鸟分词(RS)设计与实现

本文介绍了中文分词技术,特别是知更鸟分词器(RS/Robin-Segmenter)的设计和实现,重点讨论了逆向回退策略的正向最大匹配算法。此外,还与其他常见分词工具如JE、IK、PD和MS进行了性能和准确性的对比验证。

  本文设计了一种带逆向回退策略的正向最大匹配。

1 分词概述

  英文文本的处理相对简单,每一个单词之间有空格或标点符号隔开。如果不考虑短语,仅以单词作为唯一的语义单元的话,处理英文单词切分相对简单,只需要分类多有单词,去除标点符号。中文自然语言处理首先要解决的难题就是中文分词技术。
  中文分词(Chinese Word Segmentation) 指的是将一个汉字序列切分成一个个单独的词。分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。

2 算法分类

  现有的分词算法可分为三大类:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。

2.1 字符匹配

  这种方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个“充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配。
常用的几种机械分词方法如下:
  1)正向最大匹配法(由左到右的方向);
  2)逆向最大匹配法(由右到左的方向);
  3)最少切分(使每一句中切出的词数最小);
  4)双向最大匹配法(进行由左到右、由右到左两次扫描)

2.2 理解法

  这种分词方法是通过让计算机模拟人对句子的理解,达到识别词的效果。其基本思想就是在分词的同时进行句法、语义分析,利用句法信息和语义信息来处理歧义现象。它通常包括三个部分:分词子系统、句法语义子系统、总控部分。在总控部分的协调下,分词子系统可以获得有关词、句子等的句法和语义信息来对分词歧义进行判断,即它模拟了人对句子的理解过程。这种分词方法需要使用大量的语言知识和信息。由于汉语语言知识的笼统、复杂性,难以将各种语言信息组织成机器可直接读取的形式,因此目前基于理解的分词系统还处在试验阶段。

2.3 统计法

  从形式上看,词是稳定的字的组合,因此在上下文中,相邻的字同时出现的次数越多,就越有可能构成一个词。因此字与字相邻共现的频率或概率能够较好的反映成词的可信度。可以对语料中相邻共现的各个字的组合的频度进行统计,计算它们的互现信息。定义两个字的互现信息,计算两个汉字X、Y的相邻共现概率。互现信息体现了汉字之间结合关系的紧密程度。当紧密程度高于某一个阈值时,便可认为此字组可能构成了一个词。这种方法只需对语料中的字组频度进行统计,不需要切分词典,因而又叫做无词典分词法或统计取词方法。但这种方法也有一定的局限性,会经常抽出一些共现频度高、但并不是词的常用字组,例如“这一”、“之一”、“有的”、“我的”、“许多的”等,并且对常用词的识别精度差,时空开销大。实际应用的统计分词系统都要使用一部基本的分词词典(常用词词典)进行串匹配分词,同时使用统计方法识别一些新的词,即将串频统计和串匹配结合起来,既发挥匹配分词切分速度快、效率高的特点,又利用了无词典分词结合上下文识别生词、自动消除歧义的优点。

3 常见项目

极易分词(JE)
  MMAnalyzer极易中文分词组件,由中科院提供的中文极易分词器。比较完善的中文分词器。
  支持英文、数字、中文(简体)混合分词。
  常用的数量和人名的匹配。
  超过22万词的词库整理。
  实现正向最大匹配算法。

IKAnalyzer(IK)
  IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出了3个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。
Paoding(PD)
  Paoding(庖丁解牛分词)基于Java的开源中文分词组件,提供lucene和solr 接口,具有极 高效率和 高扩展性。引入隐喻,采用完全的面向对象设计,构思先进。
  高效率:在PIII 1G内存个人机器上,1秒可准确分词 100万汉字。
  采用基于不限制个数的词典文件对文章进行有效切分,使能够将对词汇分类定义。
  能够对未知的词汇进行合理解析。
  仅支持Java语言。
MMSEG4J(MS)
  MMSEG4J基于Java的开源中文分词组件,提供lucene和solr 接口:
  1.mmseg4j 用 Chih-Hao Tsai 的 MMSeg 算法实现的中文分词器,并实现 lucene 的 analyzer 和 solr 的TokenizerFactory 以方便在Lucene和Solr中使用。
  2.MMSeg 算法有两种分词方法:Simple和Complex,都是基于正向最大匹配。Complex 加了四个规则过虑。官方说:词语的正确识别率达到了 98.41%。mmseg4j 已经实现了这两种分词算法。
smallseg(SS)
  开源的,基于DFA的轻量级的中文分词工具包。
  特点:可自定义词典、切割后返回登录词列表和未登录词列表、有一定的新词识别能力。

4 知更鸟分词实现

  中文分词既然作为自然语言处理的基本工具,那么我们就来实现一个基于字符匹配的中文分词,用我的英文名(Robin)给她起个名字,就叫做——知更鸟分词器(RS/Robin-Segmenter)

4.1 算法描述

  基本实现算法:
  (1)正向最大匹配;
  (2)逆向最大匹配;
  (3)逆向回退策略的正向最大匹配(重点)。
  支持以下功能:

  • 新词发现、新词标注;
  • 字符转换、格式统一;
  • 自定义分割符;
  • 特殊符号保留;
  • 检索分词模式;
  • 合并拆分模式;

4.2 数据结构

  基于字典字符匹配匹配的分词算法,重点是字典数据结构的设计。好的字典数据结构能够极大提升匹配检索的效率。
  采用基本容器HashMap实现的一种变种字典树(Trie)结构。其基本的思想如图。

Trie树型结构之正向最大匹配过程

  假设词典:中国 | 中南海 | 人民 | 人民币 | 人情 | 银行 | 中国人民 | 中国人民银行 | 中国人寿保险 | ……
  例如:“中国人民银行行长易纲。”这样一条文本的匹配过程如图示红线路径。分词结果应该示“中国人民银行 行长 易纲”。
  由于中文词首字分布比较均匀,并且查询首字的概率远大于其他非首字,根节点采用map数组方式,数组下标为字符的Unicode码减去最小中文汉字 Unicode码。数组维度为汉字的Unicode码范围。

4.3 部分代码

Unicode工具类:

package com.robin.segment.robinseg;

import com.robin.config.ConfigUtil;
import com.robin.file.FileUtil;
import java.util.HashSet;
import java.util.Set;

/**
 * <DT><B>描述:</B></DT>
 * <DD>Unicode 编码工具类</DD>
 *
 * @version Version1.0
 * @author Robin
 * @version <I> V1.0 Date:2018-01-28</I>
 * @author  <I> E-mail:xsd-jj@163.com</I>
 */
public class Unicode {
   
   

    /** 量词配置路径 */
    private static final String QUANTIFIER_PATH;
    /** 量词集合 */
    private static Set<Character> quantifierSet;
    /** 中文数词路径 */
    private static final String NUMERAL_PATH;
    /** 中文数词集合 */
    private static Set<Character> numeralSet;
    /** 词典文件编码 */
    private static final String DIC_ENCODING;
    /** 空字符 Unicode */
    public static final int NULL = 0x0;
    /** 半角空格 Unicode */
    public static final int DBC_BLANK = 0x20;
    /** 半角句点 Unicode */
    public static final int DBC_POINT = 0x2E;
    /** 半角0 Unicode */
    public static final int DBC_0 = 0x30;
    /** 半角9 Unicode */
    public static final int DBC_9 = 0x39;
    /** 半角A Unicode */
    public static final int DBC_A = 0x41;
    /** 半角Z Unicode */
    public static final int DBC_Z = 0x5A;
    /** 半角a Unicode */
    public static final int DBC_LA = 0x61;
    /** 半角z Unicode */
    public static final int DBC_LZ = 0x7A;
    /** 普通最小中文汉字 Unicode */
    public static final int MIN_CHINESE = 0x4E00;
    /** 普通最大中文汉字 Unicode */
    public static final int MAX_CHINESE = 0x9FA5;
    /** 全角空格 Unicode */
    public static final int SBC_BLANK = 0x3000;//0xFF00也是全角空格
    /** 关注全角下限 Unicode */
    public static final int SBC_LOW_LIMIT = 0xFF00;
    /** 关注全角上限 Unicode */
    public static final int SBC_UP_LIMIT = 0xFF5E;
    /** 全角句点 Unicode */
    public static final int SBC_POINT = 0xFF0E;
    /** 全角0 Unicode */
    public static final int SBC_0 = 0xFF10;
    /** 全角9 Unicode */
    public static final int SBC_9 = 0xFF19;
    /** 全角A Unicode */
    public static final int SBC_A = 0xFF21;
    /** 全角Z Unicode */
    public static final int SBC_Z = 0xFF3A;
    /** 全角a Unicode */
    public static final int SBC_LA = 0xFF41;
    /** 全角z Unicode */
    public static final int SBC_LZ = 0xFF5A;
    /** 半角全角数字或字母 Unicode 编码偏移 */
    public static final int OFFSET_DBC_SBC = 0xFEE0;
    /** 英文大小写字母 Unicode 编码偏移 */
    public static final int OFFSET_UP_LOW = 0x20;

    static {
   
   
        QUANTIFIER_PATH = ConfigUtil.getConfig("dic.quantifier");
        NUMERAL_PATH = ConfigUtil.getConfig("dic.numeral");
        DIC_ENCODING = ConfigUtil.getConfig("dic.encoding");
        initQantifierSet();
        initNumeralSet();
    }

    /**
     * Unicode 字符类型
     */
    public enum CharType {
   
   
        /** 控制符 */
        CONTROL,
        /** 半角数字 */
        DBC_DIGIT,
        /** 半角大写字母 */
        DBC_UPPER_CASE,
        /** 半角小写字母 */
        DBC_LOWER_CASE,
        /** 中文 */
        COMMON_CHINESE,
        /** 全角字符 */
        SBC_CHAR,
        /** 全角字母 */
        SBC_CASE,
        /** 全角数字 */
        SBC_DIGIT,
        /** 全角大写字母 */
        SBC_UPPER_CASE,
        /** 全角小写字母 */
        SBC_LOWER_CASE,
        /** 小数点 */
        DECIMAL_POINT,
        /** 数字后缀 */
        DECIMAL_SUFFIX,
        /** 百分比符号 */
        PERCENT_CHAR,
        /** 空白符 */
        BLANK_CHAR,
        /** 其他 */
        OTHER_CHAR,
    }

    /**
     * 通过字符判断其类型
     *
     * @param ch Unicode字符
     * @return 字符类型
     */
    public static CharType getCharType(char ch) {
   
   
        int value = ch;
        //Unicode 普通中文汉字区
        if ((value >= MIN_CHINESE) && (value <= MAX_CHINESE)) {
   
   
            return CharType.COMMON_CHINESE;
        }
        //空白符、控制字符、半角空格0x20、全角空格0x3000
        if ((value <= DBC_BLANK) || (value == SBC_BLANK)) {
   
   
            return CharType.BLANK_CHAR;
        }
        //半角数字0-9:0x30-0x39
        if ((value >= DBC_0) && (value <= DBC_9)) {
   
   
            return CharType.DBC_DIGIT;
        }
        //半角字母a-z:0x61-0x7A
        if ((value >= DBC_LA) && (value <= DBC_LZ)) {
   
   
            return CharType.DBC_LOWER_CASE;
        }
        //半角字母A-Z:0x41-0x5A
        if ((value >= DBC_A) && (value <= DBC_Z)) {
   
   
            return CharType.DBC_UPPER_CASE;
        }
        //小数点:半角0x2E
        if (value == DBC_POINT) {
   
   
            return CharType.DECIMAL_POINT;
        }
        //数字后缀
        if ((value == (int) ('%'))
                || (value == (int) ('$'))
                || (value == (int) ('¥'</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值