与基于隐马尔可夫模型的最短路径分词、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表示新词。

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

  1. Hanlp中使用纯JAVA实现CRF分词

    Hanlp中使用纯JAVA实现CRF分词 与基于隐马尔可夫模型的最短路径分词.N-最短路径分词相比,基于条件随机场(CRF)的分词对未登录词有更好的支持.本文(HanLP)使用纯Java实现CRF模型 ...

  2. 纯java配置SpringMVC

    一般情况下,我们会在web.xml下配置好Spring和SpringMVC,并指定好它们的配置文件 是最常用的也是最方便的方法 例如: web.xml <!-- The definition o ...

  3. spring纯java注解式开发(一)

    习惯了用XML文件来配置spring,现在开始尝试使用纯java代码来配置spring. 其实,spring的纯java配置,简单来说就是将bean标签的内容通过注解转换成bean对象的过程,没什么神 ...

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

    简洁:不超过5个java文件 依赖:仅依赖aapt.exe 支持:仅限windows 功能:用纯java获取apk文集里的包名,版本号,图标文件[可获取到流直接保存到文件系统] 原理:比较上一篇文章里 ...

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

    经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ...

  6. 纯JAVA驱动:sqlserver版本不同,驱动与连接也有所区别

    纯JAVA驱动:// 2005 版本:驱动:Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");连接:”jd ...

  7. Android 使用纯Java代码布局

    java布局 java代码布局和xml布局的区别 1.Java纯布局更加的灵活,比如自定义控件或一些特殊要求时,使用java代码布局 2.常用的xml布局是所见即所得的编写方式,以及xml本身拥有一些 ...

  8. simpleImageTool又纯java图片水印、缩放工具

    simpleImageTool又一个简单.好用的图片格式转换.缩放水印叠加等功能的纯Java图片工具库. simpleImageTool的由来,近期需要用到图片处理,通过网上的图片流直接进行缩放水印叠 ...

  9. DataX通过纯Java代码启动

    DataX是阿里巴巴团队开发的一个很好开源项目,但是他们对如何使用只提供了python命令启动方式,这种方式对于只是想简单的用下DataX的人来说很是友好,仅仅需要几行代码就可以运行,但是如果你需要在 ...

随机推荐

  1. Codeforces Round #369 (Div. 2) D. Directed Roads 数学

    D. Directed Roads 题目连接: http://www.codeforces.com/contest/711/problem/D Description ZS the Coder and ...

  2. Linux学习笔记02—磁盘分区

    下面介绍四种最常见的分区方式: (1)    最简单的分区方案. SWAP分区:即交换分区,建议大小是物理内存的1-2倍. /分区:建议大小在6GB以上. 使用以上的分区方案,所有的数据都在/分区上, ...

  3. 2018 dnc .NET Core、.NET开发的大型网站列表、各大公司.NET职位精选,C#王者归来

    简洁.优雅.高效的C#语言,神一样的C#创始人Anders Hejlsberg,async/await编译器级异步语法,N年前就有的lambda表达式,.NET Native媲美C++的原生编译性能, ...

  4. vuessr nuxt入门指南

    nuxt.js 官网地址:https://zh.nuxtjs.org/guide/installation 1.安装 vue init nuxt-community/starter-template ...

  5. Oracle 取整函数

    ceil(n):ceil英文含义是天花板,天花板在上面,所以意为向上取整,即大于或等于n的整数. floor(n):floor英文含义是地板,地板在下面,所以意为向下取整,即小于或等于n的整数.

  6. CentOS 7解压安装PHP7.1.21

    下载php yum install -y wget wget http://cn2.php.net/distributions/php-7.1.21.tar.gz 解压 tar -zxvf php-7 ...

  7. CentOS 7.x,不重新编译 PHP,动态安装 imap 扩展

    先前的教程:PHP5不重新编译,如何安装自带的未安装过的扩展,如soap扩展? # 安装依赖包 yum install -y libc-client-devel /usr/local/src/cent ...

  8. Code Fragment-UI加载策略之-可视者优先加载

    通常情况 通常程序的UI不太复杂,我们会直接加载这些UI信息 复杂的UI 加载的元素就相对多一些. 加载的数据相对多. 因为UI元素和数据元素都比较多,加载的时间相对多. 可视者优先加载 不是默认的加 ...

  9. 内存映射函数remap_pfn_range学习——示例分析(2)

    li {list-style-type:decimal;}ol.wiz-list-level2 > li {list-style-type:lower-latin;}ol.wiz-list-le ...

  10. RenderPartial和RenderAction区别

    本篇参考了Shailendra Chauhan和 Jag Reehal的博文. RenderParital和RenderAction的共同点: ※ 都能返回部分视图 ※ 返回的部分视图和主视图共用一个 ...