(1)      使用JNI对CRF++进行封装

由于CRF++是由C++语言编写的条件随机场工具,为了在系统主体的Java程序中使用,需要使用JNI技术对Java进行扩展,并将CRF++编译为可被Java加载和调用的动态链接库。

JNI是Java Native Interface的缩写,中文为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互。在没有SWIG工具时,需要手工编写大量代码用于将C++代码封装为可供Java调用的接口函数,在SWIG工具的帮助下,能大量减少工作。

SWIG是个帮助使用C或者C++编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具。在本系统中,我们使用SWIG将C++编写的CRF++程序与Java编写的系统主要部分联接。

下载SWIG Windows版,解压到某一安装目录下,将含有swig.exe的目录添加的环境变量Path中,供后续使用。

SWIG对程序的封装需要编写接口文件,在接口文件中导入“crfpp.h”文件,使得SWIG导出crfpp.h中定义的接口,将其封装为JNI。编写接口文件CRFPP.i如下:

[code]
%module CRFPP
%include exception.i
%{
#include "crfpp.h"
%}
%newobject surface;
%exception {
try { $action }
catch (char *e) { SWIG_exception (SWIG_RuntimeError, e); }
catch (const char *e) { SWIG_exception (SWIG_RuntimeError, (char*)e); }
}
%feature("notabstract") CRFPP::Tagger;
%ignore CRFPP::createTagger;
%ignore CRFPP::getTaggerError;
%extend CRFPP::Tagger { Tagger(const char *argc); }
%{
void delete_CRFPP_Tagger (CRFPP::Tagger *t) {
delete t;
t = 0;
}
CRFPP::Tagger* new_CRFPP_Tagger (const char *arg) {
CRFPP::Tagger *tagger = CRFPP::createTagger(arg);
if (! tagger) throw CRFPP::getTaggerError();
return tagger;
}
%}
%include crfpp.h
%include version.h
[/code]

其中crfpp.h与version.h文件都可以在CRF++源代码中找到,但需将crfpp.h替换为前文中提到的64位的头文件。

进入Windows命令行界面,切换至含有CRFPP.i接口文件的路径,输入命令:

[code]swig –c++ -java -package org.chasen.crfpp CRFPP.i[/code]

命令执行完后会生成如表生成的CRF++封装文件所示的6个文件。

生成的CRF++封装文件

CRFPP.java
CRFPPConstants.java
CRFPPJNI.java
SWIGTYPE_p_unsigned___int64.java
Tagger.java
CRFPP_wrap.cxx

其中,CRFPP_wrap.cxx文件是C++端的封装文件用于将CRF++封装为JNI接口。将其拷贝至CRF++编译工程目录。

将另外5个Java文件拷贝至本系统主体Java工程的org.chasen.crfpp包中,其中Tagger类是我们将要使用到的主要类。

(2)      JNI中文乱码的解决

由于在java内部是使用的16bit的Unicode编码(UTF-16)来表示字符串的,无论英文还是中文都是2字节;JNI内部是使用UTF -8编码来表示字符串的,UTF -8是变长编码的Unicode,一般ASCII字符是1字节,中文是3字节;C/C++使用的是原始数据,ASCII就是一个字节,在Windows操作系统中,中文一般是GB2312编码,用2个字节表示一个汉字。[21]如果直接在Java程序中传入中文字符串将会出现不可逆的中文乱码,对命名实体识别的效果有很大影响。

在swig生成的CRFPP_wrap.cxx文件中,在约270行的位置找到“#include “crfpp.h””,在这条语句下面加入两个函数:

l  JStringToWindows

[cpp]
//to use chinese never foget to free.
char * JStringToWindows(JNIEnv * pJNIEnv, jstring jstr)
{
jsize len = pJNIEnv->GetStringLength(jstr);
const jchar * jcstr = pJNIEnv->GetStringChars(jstr, NULL);
int size = 0;
char * str = (char *)malloc(len * 2 + 1);
if ((size = WideCharToMultiByte(CP_ACP, 0, LPCWSTR(jcstr), len, str, len * 2 + 1, NULL, NULL)) == 0)
return NULL;
pJNIEnv->ReleaseStringChars(jstr, jcstr);
str[size] = 0;
return str;
}
[/cpp]
l  WindowsTojstring

[cpp]
jstring WindowsTojstring( JNIEnv* env, char* str )
{
jstring rtn = 0;
int slen = strlen(str);
unsigned short * buffer = 0;
if( slen == 0 )
rtn = (env)->NewStringUTF(str );
else
{
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
buffer = (unsigned short *)malloc( length*2 + 1 );
if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
rtn = (env)->NewString(  (jchar*)buffer, length );
}
if( buffer )
free( buffer );
return rtn;
}
[/cpp]

在源文件中查找“GetStringUTFChars”函数,将其替换为“JStringToWindows”,查找“NewStringUTF”,将其替换为“WindowsTojstring”。并在“JStringToWindows”函数返回的字符串使用后,使用stdio标准库中的“free”函数将其释放,以防造成内存泄露。

在CRF++工程中,以动态链接库形式编译CRFPP_wrap.cxx为CRFPP.dll文件。将该文件拷贝到本系统Java主体的工程根目录下。

(3)      JNI封装后CRF++的使用

在Tagger类中加入如下代码:

[java]
static {
try {
System.loadLibrary("CRFPP");
} catch (UnsatisfiedLinkError e) {
System.err.println("Cannot load the example native code.\nMake sure your LD_LIBRARY_PATH contains \’.\’\n"+ e);
System.exit(1);
}
}
[/java]

在Tagger类被调用的时候,会自动在环境变量的Path路径下查找CRFPP.dll动态链接库,作为CRF++的核心载入主程序中使用。

1)        初始化Tagger类时使用“-m”参数指定模型文件地址。

2)        使用boolean clear()方法清除已有的数据。

3)        使用boolean add(String str)方法添加特征,每一个add方法中添加的特征形如CRF++的训练文件,只是少了标注列。

4)        使用boolean parse()方法出发CRF++的标注。

5)        使用String y2(long i)方法获取对应序号CRF++标注。

一、现有命名实体识别使用方法及达到的效果

现有的命名实体识别方法,主要包括隐性马科夫链,最大熵,CRF等。

根据综述:在这4种学习方法中,最大熵模型结构紧凑,具有较好的通用性,主要缺点是训练时间复杂性非常高,有时甚至导致训练代价难以承受,另外由于需要明确的归一化计算,导致开销比较大。而条件随机场为命名实体识别提供了一个特征灵活、全局最优的标注框架,但同时存在收敛速度慢、训练时间长的问题。一般说来,最大熵和支持向量机在正确率上要比隐马尔可夫模型高一些,但是隐马尔可夫模型在训练和识别时的速度要快一些,主要是由于在利用Viterbi算法求解命名实体类别序列的效率较高。隐马尔可夫模型更适用于一些对实时性有要求以及像信息检索这样需要处理大量文本的应用,如短文本命名实体识别。

但在实际实现中,仍然有不少采用ME的实现实例。但是可能是由于所找论文的质量原因,由ME实现的命名实体识别效果并不理想。反而是几个基于隐性马科夫链的实现效果较好。目前找到的唯一的基于CRF实现的实例效果并不是很好。

现将现有的一些实现及其效果罗列如下:

基于ME的方法:

1.南京大学2007硕士学位论文:吴宝琪《中文命名实体的识别方法研究及其实现》,对于人名识别:准确率为68.O%,召回率为40.2%,F值为50.5%对于地名识别:准确率为48.7%,召回率为31.1%,F值为39.O%对于组织名识别:准确率为51.2%,召回率为27.6%,F值为35.9%。其效果不好的原因由几个,一个是特征选择有待考量,二个是为了研究而研究,没有使用分词技术。但是论文中有一些可取的筛选方法。

2.哈工大2009年:付瑞吉,车万翔,刘挺《一种基于分类方法的音乐命名实体识别技术》音乐命名实体识别总的精确率、召回率和F值分别达到了89.89%,81.01%,87.93%。

3. 余传明,黄建秋,郭 飞。2011《从客户评论中识别命名实体》

从客户评论中识别命名实体

从客户评论中识别命名实体

中采用的几个模板,效果都不好。同样存在训练集太小的因素。

基于CRF的方法:

1.苏州大学2010年学位论文:史海峰《基于CRF的中文命名实体识别研究》。

命名实体 精确率P 召回率R F值
人名 98.4% 65.7% 78.8%
地名 96.3% 67.2% 79.2%
机构名 98.3% 78.2% 87.1%

作者分析可能产生了过拟合。本论文使用的训练集过小。

基于HMM的方法:

1.2004年哈工大:廖先桃 于海滨 秦兵 刘挺《HMM与自动规则提取相结合的中文命名实体识别》。

HMM与自动规则提取相结合的中文命名实体识别

HMM与自动规则提取相结合的中文命名实体识别

 

2.2006年中科院:俞鸿魁,张华平,刘群,吕学强,施水才《基于层叠隐马尔可夫模型的中文命名实体识别》。

封闭测试:

基于层叠隐马尔可夫模型的中文命名实体识别 封闭测试

基于层叠隐马尔可夫模型的中文命名实体识别 封闭测试

开放测试(人民日报1998.6):

基于层叠隐马尔可夫模型的中文命名实体识别 开放测试

基于层叠隐马尔可夫模型的中文命名实体识别 开放测试

总结:

最大熵方法在理论上有最好的效果,从CoNLL.2003会议的报告来看,最大熵模型相对更适合于处理命名实体识别问题,在参赛的16对选手中,有5对选手使用了最大熵模型。而且从竞赛的结果来看,英文的命名实体识别竞赛的前三名和德语的命名实体识别竞赛的前两名都采用了最大熵的方法,但是训练成本比较高。在CRF方面的研究还比较少。HMM模型看来能够取得的效果也不差,可以再HMM上加以进一步改进以提高其效果。