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 */publicvoidtag(Table table){    intsize = table.size();    doublebestScore = 0;    intbestTag = 0;    inttagSize = id2tag.length;    LinkedList<double[]> scoreList = computeScoreList(table, 0);    // 0位置命中的特征函数    for(inti = 0; i < tagSize; ++i)   // -1位置的标签遍历    {        for(intj = 0; j < tagSize; ++j)   // 0位置的标签遍历        {            doublecurScore = matrix[i][j] + computeScore(scoreList, j);            if(curScore > bestScore)            {                bestScore = curScore;                bestTag = j;            }        }    }    table.setLast(0, id2tag[bestTag]);    intpreTag = bestTag;    // 0位置打分完毕,接下来打剩下的    for(inti = 1; i < size; ++i)    {        scoreList = computeScoreList(table, i);    // i位置命中的特征函数        bestScore = Double.MIN_VALUE;        for(intj = 0; j < tagSize; ++j)   // i位置的标签遍历        {            doublecurScore = 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 = newCRFSegment();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实现的更多相关文章
- Hanlp中使用纯JAVA实现CRF分词
		Hanlp中使用纯JAVA实现CRF分词 与基于隐马尔可夫模型的最短路径分词.N-最短路径分词相比,基于条件随机场(CRF)的分词对未登录词有更好的支持.本文(HanLP)使用纯Java实现CRF模型 ... 
- 纯java配置SpringMVC
		一般情况下,我们会在web.xml下配置好Spring和SpringMVC,并指定好它们的配置文件 是最常用的也是最方便的方法 例如: web.xml <!-- The definition o ... 
- spring纯java注解式开发(一)
		习惯了用XML文件来配置spring,现在开始尝试使用纯java代码来配置spring. 其实,spring的纯java配置,简单来说就是将bean标签的内容通过注解转换成bean对象的过程,没什么神 ... 
- 纯java从apk文件里获取包名、版本号、icon
		简洁:不超过5个java文件 依赖:仅依赖aapt.exe 支持:仅限windows 功能:用纯java获取apk文集里的包名,版本号,图标文件[可获取到流直接保存到文件系统] 原理:比较上一篇文章里 ... 
- 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置
		经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ... 
- 纯JAVA驱动:sqlserver版本不同,驱动与连接也有所区别
		纯JAVA驱动:// 2005 版本:驱动:Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");连接:”jd ... 
- Android 使用纯Java代码布局
		java布局 java代码布局和xml布局的区别 1.Java纯布局更加的灵活,比如自定义控件或一些特殊要求时,使用java代码布局 2.常用的xml布局是所见即所得的编写方式,以及xml本身拥有一些 ... 
- simpleImageTool又纯java图片水印、缩放工具
		simpleImageTool又一个简单.好用的图片格式转换.缩放水印叠加等功能的纯Java图片工具库. simpleImageTool的由来,近期需要用到图片处理,通过网上的图片流直接进行缩放水印叠 ... 
- DataX通过纯Java代码启动
		DataX是阿里巴巴团队开发的一个很好开源项目,但是他们对如何使用只提供了python命令启动方式,这种方式对于只是想简单的用下DataX的人来说很是友好,仅仅需要几行代码就可以运行,但是如果你需要在 ... 
随机推荐
- iOS键盘类型最全
			一.键盘风格 UIKit框架支持8种风格键盘. typedef enum { UIKeyboardTypeDefault, // 默认键盘:支持所有字符 UIKeyboa ... 
- spring-boot 速成(4) 自定义配置
			spring-boot 提供了很多默认的配置项,但是开发过程中,总会有一些业务自己的配置项,下面示例了,如何添加一个自定义的配置: 一.写一个自定义配置的类 package com.example.c ... 
- LPC-LINK 2 Board IO TABLE
- ARM Cortex Design Considerations for Debug
			JTAG was the traditional mechanism for debug connections for ARM7/9 parts, but with the Cortex-M fam ... 
- [golang 易犯错误] golang 局部变量初始化:=的陷阱
			我们知道,golang中局部变量初始化方法(使用“:=”创建并赋值),让我们在使用变量时很方便.但是,这也是易犯错误的地方之一.特别是这个初始化符还支持多个变量同时初始化,更特别的是它还支持原有变量赋 ... 
- Android adb logcat使用技巧
			前言 新买的笔记本E431装了最新版的Eclipse,搞定了Android开发环境,可是logcat里查看东西居然仅仅显示level,没有错误的具体信息.我本身也不是一个愿意折腾图形界面,更喜欢纯命令 ... 
- springcloud 分布式服务跟踪sleuth+zipkin
			原文:https://www.jianshu.com/p/6ef0b76b9c26 分布式服务跟踪需求 随着分布式服务越来越多,调用关系越来越复杂,组合接口越来越多,要进行分布式服务跟踪监控的需求也越 ... 
- insert 语句后面不要跟 where 等条件语句
			insert 语句后面不要跟 where 等条件语句: update 才用得到. 
- C#编程(四十七)----------集合接口和类型
			原文链接: http://blog.csdn.net/shanyongxu/article/details/47005979 集合接口和类型 前面介绍了数组和Array类实现的接口.数组的大小是固定的 ... 
- 迭代dict的value
			我们已经了解了dict对象本身就是可迭代对象,用 for 循环直接迭代 dict,可以每次拿到dict的一个key. 如果我们希望迭代 dict 对象的value,应该怎么做? dict 对象有一个 ... 
