开源中文分词工具探析(六):Stanford CoreNLP

CoreNLP是由斯坦福大学开源的一套Java NLP工具,提供诸如:词性标注(part-of-speech (POS) tagger)、命名实体识别(named entity recognizer (NER))、情感分析(sentiment analysis)等功能。


【开源中文分词工具探析】系列:

  1. 开源中文分词工具探析(一):ICTCLAS (NLPIR)
  2. 开源中文分词工具探析(二):Jieba
  3. 开源中文分词工具探析(三):Ansj
  4. 开源中文分词工具探析(四):THULAC
  5. 开源中文分词工具探析(五):FNLP
  6. 开源中文分词工具探析(六):Stanford CoreNLP
  7. 开源中文分词工具探析(七):LTP

1. 前言

CoreNLP的中文分词基于CRF模型:

\[ P_w(y|x) = \frac{exp \left( \sum_i w_i f_i(x,y) \right)}{Z_w(x)} \]

其中,\(Z_w(x)\)为归一化因子,\(w\)为模型的参数,\(f_i(x,y)\)为特征函数。

2. 分解

以下源码分析基于3.7.0版本,分词示例见SegDemo类。

模型

主要模型文件有两份,一份为词典文件dict-chris6.ser.gz

// dict-chris6.ser.gz 对应于长度为7的Set数组词典
// 共计词数:0+7323+125336+142252+82139+26907+39243
ChineseDictionary::loadDictionary(String serializePath) {
    Set<String>[] dict = new HashSet[MAX_LEXICON_LENGTH + 1];
    for (int i = 0; i <= MAX_LEXICON_LENGTH; i++) {
        dict[i] = Generics.newHashSet();
    }
    dict = IOUtils.readObjectFromURLOrClasspathOrFileSystem(serializePath);
    return dict;
}

词典的索引值为词的长度,比如第0个词典中没有词,第1个词典为长度为1的词,第6个词典为长度为6的词。其中,第6个词典为半成词,比如,有词“《双峰》(电”、“80年国家领”、“1824年英”。

另一份为CRF训练模型文件ctb.gz

CRFClassifier::loadClassifier(ObjectInputStream ois, Properties props) {
    Object o = ois.readObject();
    if (o instanceof List) {
        labelIndices = (List<Index<CRFLabel>>) o; // label索引
    }
    classIndex = (Index<String>) ois.readObject(); // 序列标注label
    featureIndex = (Index<String>) ois.readObject(); // 特征
    flags = (SeqClassifierFlags) ois.readObject(); // 模型配置

    Object featureFactory = ois.readObject(); // 特征模板,用于生成特征
    else if (featureFactory instanceof FeatureFactory) {
        featureFactories = Generics.newArrayList();
        featureFactories.add((FeatureFactory<IN>) featureFactory);
    }

    windowSize = ois.readInt(); // 窗口大小为2
    weights = (double[][]) ois.readObject(); // 特征+label 对应的权重

    Set<String> lcWords = (Set<String>) ois.readObject(); // Set为空
    else {
        knownLCWords = new MaxSizeConcurrentHashSet<>(lcWords);
    }

    reinit();
}

不同于其他分词器采用B、M、E、S四种label来做分词,CoreNLP的中文分词label只有两种,“1”表示当前字符与前一字符连接成词,“0”则表示当前字符为另一词的开始——换言之前一字符为上一个词的结尾。

class CRFClassifier {
    classIndex: class edu.stanford.nlp.util.HashIndex
      ["1","0"]
}

// 中文分词label对应的类
public static class AnswerAnnotation implements CoreAnnotation<String>{}

特征

CoreNLP的特征如下(示例):

class CRFClassifier {
    // 特征
    featureIndex: class edu.stanford.nlp.util.HashIndex
        size = 3408491
        0=的膀cc2|C
        1=身也pc|C
        44=LSSLp2spscsc2s|C
        45=科背p2p|C
        46=迪。cc2|C
        ...
        =球-行pc2|CnC
        =音非cc2|CpC
    
    // 权重
    weights: double[3408491][2]
        [[2.2114868426005005E-5, -2.2114868091546352E-5]...]
}

特征后缀只有3类:C, CpC, CnC,分别代表了三大类特征;均由特征模板生成:

// 特征模板List
featureFactories: ArrayList<FeatureFactory>
    0 = Gale2007ChineseSegmenterFeatureFactory

// 具体特征模板
Gale2007ChineseSegmenterFeatureFactory::getCliqueFeatures() {
    if (clique == cliqueC) {
        addAllInterningAndSuffixing(features, featuresC(cInfo, loc), "C");
    } else if (clique == cliqueCpC) {
        addAllInterningAndSuffixing(features, featuresCpC(cInfo, loc), "CpC");
        addAllInterningAndSuffixing(features, featuresCnC(cInfo, loc - 1), "CnC");
    }
}

特征模板只用到了两个特征簇cliqueCcliqueCpC,其中,cliqueC由函数featuresC()实现,cliqueCpC由函数featuresCpC()featuresCnC()


Gale2007ChineseSegmenterFeatureFactory::featuresC() {
    if (flags.useWord1) {
        // Unigram 特征
        features.add(charc +"::c"); // c[0]
        features.add(charc2+"::c2"); // c[1]
        features.add(charp +"::p"); // c[-1]
        features.add(charp2 +"::p2"); // c[-2]

        // Bigram 特征
        features.add(charc +charc2  +"::cn"); // c[0]c[1]
        features.add(charc +charc3  +"::cn2"); // c[0]c[2]
        features.add(charp +charc  +"::pc"); // c[-1]c[0]
        features.add(charp +charc2  +"::pn"); // c[-1]c[1]
        features.add(charp2 +charp  +"::p2p"); // c[-2]c[-1]
        features.add(charp2 +charc  +"::p2c"); // c[-2]c[0]
        features.add(charc2 +charc  +"::n2c"); // c[1]c[0]
    }

    // 三个字符c[-1]c[0]c[1]对应的LBeginAnnotation、LMiddleAnnotation、LEndAnnotation 三种label特征
    // 结果特征分别以6种形式结尾,"-lb", "-lm", "-le", "-plb", "-plm", "-ple", "-c2lb", "-c2lm", "-c2le"
    // null || ".../models/segmenter/chinese/dict-chris6.ser.gz"
    if (flags.dictionary != null || flags.serializedDictionary != null) {
        dictionaryFeaturesC(CoreAnnotations.LBeginAnnotation.class,
                CoreAnnotations.LMiddleAnnotation.class,
                CoreAnnotations.LEndAnnotation.class,
                "", features, p, c, c2);
    }

    // 特征 c[1]c[0], c[1]
    if (flags.useFeaturesC4gram || flags.useFeaturesC5gram || flags.useFeaturesC6gram) {
        features.add(charp2 + charp + "p2p");
        features.add(charp2 + "p2");
    }

    // Unicode特征
    if (flags.useUnicodeType || flags.useUnicodeType4gram || flags.useUnicodeType5gram) {
        features.add(uTypep + "-" + uTypec + "-" + uTypec2 + "-uType3");
    }

    // UnicodeType特征
    if (flags.useUnicodeType4gram || flags.useUnicodeType5gram) {
        features.add(uTypep2 + "-" + uTypep + "-" + uTypec + "-" + uTypec2 + "-uType4");
    }

    // UnicodeBlock特征
    if (flags.useUnicodeBlock) {
        features.add(p.getString(CoreAnnotations.UBlockAnnotation.class) + "-"
                + c.getString(CoreAnnotations.UBlockAnnotation.class) + "-"
                + c2.getString(CoreAnnotations.UBlockAnnotation.class)
                + "-uBlock");
    }

    // Shape特征
    if (flags.useShapeStrings) {
        if (flags.useShapeStrings1) {
            features.add(p.getString(CoreAnnotations.ShapeAnnotation.class) + "ps");
            features.add(c.getString(CoreAnnotations.ShapeAnnotation.class) + "cs");
            features.add(c2.getString(CoreAnnotations.ShapeAnnotation.class) + "c2s");
        }
        if (flags.useShapeStrings3) {
            features.add(p.getString(CoreAnnotations.ShapeAnnotation.class)
                    + c.getString(CoreAnnotations.ShapeAnnotation.class)
                    + c2.getString(CoreAnnotations.ShapeAnnotation.class)
                    + "pscsc2s");
        }
        if (flags.useShapeStrings4) {
            features.add(p2.getString(CoreAnnotations.ShapeAnnotation.class)
                    + p.getString(CoreAnnotations.ShapeAnnotation.class)
                    + c.getString(CoreAnnotations.ShapeAnnotation.class)
                    + c2.getString(CoreAnnotations.ShapeAnnotation.class)
                    + "p2spscsc2s");
        }
        if (flags.useShapeStrings5) {
            features.add(p2.getString(CoreAnnotations.ShapeAnnotation.class)
                    + p.getString(CoreAnnotations.ShapeAnnotation.class)
                    + c.getString(CoreAnnotations.ShapeAnnotation.class)
                    + c2.getString(CoreAnnotations.ShapeAnnotation.class)
                    + c3.getString(CoreAnnotations.ShapeAnnotation.class)
                    + "p2spscsc2sc3s");
        }
    }
}

Gale2007ChineseSegmenterFeatureFactory::featuresCpC() {}

Gale2007ChineseSegmenterFeatureFactory::featuresCnC() {}

三大类特征分别以“|C”为结尾(共计有32个)、以“|CpC”结尾(共计有37个)、以“|CnC”结尾(共计有9个);总计78个特征。个人感觉CoreNLP定义的特征过于复杂,大部分特征并没有什么用。CoreNLP后面处理流程跟其他分词器别无二样了,求每个label的权重加权之和,Viterbi解码求解最大概率路径,解析label序列得到分词结果。

CoreNLP分词速度巨慢,效果也一般,在PKU、MSR测试集上的表现如下:

测试集分词器准确率召回率F1
PKUthulac4j0.9480.9360.942
CoreNLP0.9010.8940.897
MSRthulac4j0.8660.8960.881
CoreNLP0.8220.8590.840

3.参考资料

[1] Huihsin, Tseng, et al. "A conditional random field word segmenter." Fourth SIGHAN Workshop. 2005.
[2] Chang, Pi-Chuan, Michel Galley, and Christopher D. Manning. "Optimizing Chinese word segmentation for machine translation performance." Proceedings of the third workshop on statistical machine translation. Association for Computational Linguistics, 2008.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值