CRF分词的纯Java实现

与基于隐马尔可夫模型的最短路径分词、N-最短路径分词相比,基于随机条件场(CRF)的分词对未登录词有更好的支持。本文(HanLP)使用纯Java实现CRF模型的读取与维特比后向解码,内部特征函数采用 双数组Trie树(DoubleArrayTrie)储存,得到了一个高性能的中文分词器。

CRF简介

CRF是序列标注场景中常用的模型,比HMM能利用更多的特征,比MEMM更能抵抗标记偏置的问题。

CRF训练

这类耗时的任务,还是交给了用C++实现的CRF++。关于CRF++输出的CRF模型,请参考《CRF++模型格式说明》。

CRF解码

解码采用维特比算法实现。并且稍有改进,用中文伪码与白话描述如下:

首先任何字的标签不仅取决于它自己的参数,还取决于前一个字的标签。但是第一个字前面并没有字,何来标签?所以第一个字的处理稍有不同,假设第0个字的标签为X,遍历X计算第一个字的标签,取分数最大的那一个。

如何计算一个字的某个标签的分数呢?某个字根据CRF模型提供的模板生成了一系列特征函数,这些函数的输出值乘以该函数的权值最后求和得出了一个分数。该分数只是“点函数”的得分,还需加上“边函数”的得分。边函数在本分词模型中简化为f(s‘,s),其中s‘为前一个字的标签,s为当前字的标签。于是该边函数就可以用一个4*4的矩阵描述,相当于HMM中的转移概率。

实现了评分函数后,从第二字开始即可运用维特比后向解码,为所有字打上BEMS标签。

实例

还是取经典的“商品和服务”为例,首先HanLP的CRFSegment分词器将其拆分为一张表:


1

2

3

4

5

商   null   

品   null   

和   null   

服   null   

务   null   

null表示分词器还没有对该字标注。

代码

上面说了这么多,其实我的实现非常简练:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

/**

 * 维特比后向算法标注

 * @param table

 */

public void tag(Table table)

{

    int size = table.size();

    double bestScore = 0;

    int bestTag = 0;

    int tagSize = id2tag.length;

    LinkedList<double[]> scoreList = computeScoreList(table, 0);    // 0位置命中的特征函数

    for (int i = 0; i < tagSize; ++i)   // -1位置的标签遍历

    {

        for (int j = 0; j < tagSize; ++j)   // 0位置的标签遍历

        {

            double curScore = matrix[i][j] + computeScore(scoreList, j);

            if (curScore > bestScore)

            {

                bestScore = curScore;

                bestTag = j;

            }

        }

    }

    table.setLast(0, id2tag[bestTag]);

    int preTag = bestTag;

    // 0位置打分完毕,接下来打剩下的

    for (int i = 1; i < size; ++i)

    {

        scoreList = computeScoreList(table, i);    // i位置命中的特征函数

        bestScore = Double.MIN_VALUE;

        for (int j = 0; j < tagSize; ++j)   // i位置的标签遍历

        {

            double curScore = matrix[preTag][j] + computeScore(scoreList, j);

            if (curScore > bestScore)

            {

                bestScore = curScore;

                bestTag = j;

            }

        }

        table.setLast(i, id2tag[bestTag]);

        preTag = bestTag;

    }

}

标注结果

标注后将table打印出来:


1

2

3

4

5

6

CRF标注结果

商   B  

品   E  

和   S  

服   B  

务   E

最终处理

将BEMS该合并的合并,得到:


1

[商品/null, 和/null, 服务/null]

然后将词语送到词典中查询一下,没查到的暂时当作nx,并记下位置(因为这是个新词,为了表示它的特殊性,最后词性设为null),再次使用维特比标注词性:


1

[商品/n, 和/cc, 服务/vn]

新词识别

CRF对分词有很好的识别能力,比如:


1

2

3

CRFSegment segment = new CRFSegment();

segment.enableSpeechTag(true);

System.out.println(segment.seg("你看过穆赫兰道吗"));

输出:


1

2

3

4

5

6

7

8

9

10

CRF标注结果

你   S  

看   S  

过   S  

穆   B  

赫   M  

兰   M  

道   E  

吗   S  

[你/rr, 看/v, 过/uguo, 穆赫兰道/null, 吗/y]

null表示新词。

时间: 2024-12-21 23:26:15

CRF分词的纯Java实现的相关文章

NLP之CRF分词训练(六)

分三步1.先分词2.做BEMS标注,同时做词性标注3.训练模型 1.对语料进行分词 拿到测试部的语料或者其他渠道的语料,先对语料进行分词,我刚刚开始是用NS分词的,等CRF模型训练好后,可以直接用CRF进行分词,分完词后要人工核对分词结果,将分词分得不正确的地方修改好 2.标注词性,标注BEMS BEMS所说是中科院的提出一种标注,也有说BEIS的,hanlp用的是BEMSB:开始E:结束M/I:中间 S:单独BEMS标注已经写了一个方法generateCRF在SegAndSave.class中

如何使用纯Java方式连接数据库?

假定需要连接的数据库名称为`myschool`,使用"root"登录,密码为"0000",使用该数据库用户登录并访问`myschool`数据库. 在写代码之前需要导入jar包"mysql-connector-java-5.1.0-bin" 1.使用纯Java方式连接数据库查找数据(为方便查阅,暂使用throws抛出异常,省略记录日志). package cn.jbdc;import java.sql.Connection;import java.

纯JAVA环境获取APK信息(包名,版本,版本号,大小,权限...),纯JAVA语言编写PC端获取APK信息

纯JAVA环境获取APK信息:包名,版本,版本号,大小,权限... 纯Java环境获取APK信息需要两个包:AXMLPrinter2.jar 跟jdom.jar,用于反编译XML和解析XML的 项目目录 这个类是获取APK信息的 public class ApkUtil { private static final Namespace NS = Namespace.getNamespace("http://schemas.android.com/apk/res/android"); @

纯 Java 开发 WebService 调用测试工具(wsCaller.jar)

注:本文来自hacpai.com:Tanken的<纯 Java 开发 WebService 调用测试工具(wsCaller.jar)>的文章 基于 Java 开发的 WebService 测试工具,不像上文的 iWallpaper.jar 只能实现在 Windows 系统下的功能,此工具发挥了 Java 跨平台的优势,亲测可在 Windows.Mac OS 及 Linux 下运行及使用.简单易用的专门用于测试 WebService 的小工具,在 2003 版 wsCaller.jar 的基础上

基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置

经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - 静态资源.视图和消息器>两篇博文的介绍,我们已经配置好了Spring所需的基本配置.在这边博文中,我们将介绍怎么使用这些配置到实际项目中,并将web.xml文件替换为一个Java类. 我们使用Java代码来配置Spring,目的就是使我们的这些配置能够复用,对于这些配置的复用,我们采用继承和引入来实现

spring纯java注解式开发(一)

习惯了用XML文件来配置spring,现在开始尝试使用纯java代码来配置spring. 其实,spring的纯java配置,简单来说就是将bean标签的内容通过注解转换成bean对象的过程,没什么神秘的地方. 首先来配置AppConfig文件: 配置的英文叫做configuration,所以,java配置文件的类前,为了说明此类属于配置文件的范畴,就加上这样一个标签:@Configuration 用来标识此类是一个配置类:然后就是@ComponentScan 标签,是不是很熟悉?对的,这个就是

纯 java 实现 Http 资源读取工具,支持发送和接收数据,不依赖任何第三方 jar 包

原文:纯 java 实现 Http 资源读取工具,支持发送和接收数据,不依赖任何第三方 jar 包 源代码下载地址:http://www.zuidaima.com/share/1550463379950592.htm 纯 java 实现 Http 资源读取工具,支持发送和接收数据,不依赖任何第三方 jar 包 1. 抓取指定 URL 的资源,可以作为流,也可以作为 String 2. 向指定 URL POST 数据,模拟表单提交. 例如:你想模拟 XXX 自动登陆,然后再发表心情.签名之类的 3

纯java从apk文件里获取包名、版本号、icon

简洁:不超过5个java文件 依赖:仅依赖aapt.exe 支持:仅限windows 功能:用纯java获取apk文集里的包名,版本号,图标文件[可获取到流直接保存到文件系统] 原理:比较上一篇文章里通过反编译然后解析AndroidManifest.xml的方式,此种方式更加简单,通过模拟执行aapt截取cmd输出并整理获取信息. 附件为源码,test/demo 为演示,output下为编译后的jar文件. 参考开源项目: http://code.google.com/p/cfuture09-a

Stripes视图框架实现纯Java代码控制表现层参考文档

Stripes是一个开放源码的Web应用程序框架的基础上的模型 - 视图 - 控制器(MVC)模式.它的目的是通过使用Java技术,如在Java 1.5.x或以上版本中引入,实现"约定优于配置"的注解和泛型,它比Struts2框架更轻量.Stripes强调一组简单的约定整个框架的想法,减少配置的开销.在实践中,这意味着几乎没有Stripes的应用程序需要的任何配置文件,从而降低开发和维护工作. 1.Stripes特性 行动基于MVC框架 没有配置文件 POJO的 注解取代XML配置文件