当前位置: 首页 > news >正文

开源中文分词工具探析(六):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.

转载于:https://www.cnblogs.com/en-heng/p/8428504.html

相关文章:

  • 用脚本模式配置数据同步
  • 如何在ubuntu16上安装docker
  • saltstack自动化运维系列④之saltstack的命令返回结果mysql数据库写入
  • 边车容器下的服务网格istio
  • Git基本
  • 【个人向】《HTTP图解》阅后小结
  • complexType
  • JAVA利用HttpClient进行POST请求(HTTPS)
  • 关于terracotta在tomcat集群中做session共享的问题
  • JAVA生成微信JSSDK接口签名
  • 工作总结-发送修改数据的请求时,修改数据成功,但是报非安全https请求,响应失败...
  • 陈松松:一个视频如何获得不同视频网站的排名秘诀
  • [译]自主权身份简介
  • [Linux] PHP程序员玩转Linux系列-telnet轻松使用邮箱
  • Linux程序接口实验:取进程标志及用户信息
  • Brief introduction of how to 'Call, Apply and Bind'
  • canvas 五子棋游戏
  • Redis字符串类型内部编码剖析
  • Shadow DOM 内部构造及如何构建独立组件
  • underscore源码剖析之整体架构
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 前端学习笔记之观察者模式
  • 山寨一个 Promise
  • 深入浅出webpack学习(1)--核心概念
  • 使用docker-compose进行多节点部署
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 在Unity中实现一个简单的消息管理器
  • Java数据解析之JSON
  • (4.10~4.16)
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (五)c52学习之旅-静态数码管
  • (一)Thymeleaf用法——Thymeleaf简介
  • (一)VirtualBox安装增强功能
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .NET6 命令行启动及发布单个Exe文件
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • /dev下添加设备节点的方法步骤(通过device_create)
  • @RequestBody详解:用于获取请求体中的Json格式参数
  • @SuppressWarnings注解
  • @Transactional 详解
  • [ element-ui:table ] 设置table中某些行数据禁止被选中,通过selectable 定义方法解决
  • [ SNOI 2013 ] Quare
  • [ 常用工具篇 ] POC-bomber 漏洞检测工具安装及使用详解
  • [04]Web前端进阶—JS伪数组
  • [2016.7.Test1] T1 三进制异或
  • [20161214]如何确定dbid.txt
  • [AutoSar]工程中的cpuload陷阱(三)测试
  • [BUUCTF]-Reverse:reverse3解析
  • [C++]高精度 bign (重载运算符版本)
  • [J2ME]url请求返回参数非法(java.lang.illegalArgument)
  • [Json.net]快速入门
  • [LeetCode] Longest Common Prefix 字符串公有前序