前段时间使用了HanLP一个纯JAVA分词工具包,后来老大说分词效果不是很好,需要换一个分词工具。于是推荐了一个分词工具——NLPIR,它是中科院XXX研发的一个分词工具。这个分词工具只用C/C++写的,但是它提供了JAVA,C#等调用接口。于是我希望是的通过java来调用。使用java调用C/C++的代码需要用到JNA,所以工程需要添加JNA的依赖包。

这里面官网上介绍的不是特别清楚,里面有些坑,第一次使用的人还真需要一段时间解决,下面将我踩的坑记录一下:

 1、首先进官网:http://ictclas.nlpir.org/  下载相应的安装包,我下载的是:

  20160907102816_ICTCLAS2016分词系统下载包.zip

解压之后会出现如下文件目录:

其中,主要的几个目录是:

(1)Data,这个目录是放了license(NLPIR.user)在里面,这个如果你配置成功,初始化失败,一般都是license过期了。这个license目前是一个月更新一次。

(2)lib ,这个目录存放了linux和window下,运行需要的dll文件和.so文件。其中分别有对应的位数:

(3)sample,这个目录下面有多个语言实现的example。由于我这里使用的是java,所以就选择java,选择JNA,在选择JnaTest_NLPIR。在这个java工程就是提供的一个例子。

可以将例子导入工程中,代码如下:

package code;

import java.io.UnsupportedEncodingException;

import utils.SystemParas;

import com.sun.jna.Library;
import com.sun.jna.Native;

public class NlpirTest {

	// 定义接口CLibrary,继承自com.sun.jna.Library
	public interface CLibrary extends Library {
		// 定义并初始化接口的静态变量
		CLibrary Instance = (CLibrary) Native.loadLibrary(
				"NLPIR", CLibrary.class);   //这里需要注意,这个位置就设置为NLPIR是不能够改变,这里折腾我好久了,原来是这个NLPIR文件是不能改变。

		// printf函数声明
		public int NLPIR_Init(byte[] sDataPath, int encoding,
				byte[] sLicenceCode);

		public String NLPIR_ParagraphProcess(String sSrc, int bPOSTagged);

		public String NLPIR_GetKeyWords(String sLine, int nMaxKeyLimit,
				boolean bWeightOut);

		public void NLPIR_Exit();
	}

	public static String transString(String aidString, String ori_encoding,
			String new_encoding) {
		try {
			return new String(aidString.getBytes(ori_encoding), new_encoding);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static void main(String[] args) throws Exception {
		String argu = "";
		// String system_charset = "GBK";//GBK----0
		String system_charset = "GBK";
		int charset_type = 1;
		// int charset_type = 0;
		// 调用printf打印信息
		int init_flag = CLibrary.Instance.NLPIR_Init(argu
				.getBytes(system_charset), charset_type, "0"
				.getBytes(system_charset));

		if (0 == init_flag) {
			System.err.println("初始化失败!");
			return;
		}

		String sInput = "据悉,质检总局已将最新有关情况再次通报美方,要求美方加强对输华玉米的产地来源、运输及仓储等环节的管控措施,有效避免输华玉米被未经我国农业部安全评估并批准的转基因品系污染。";

		String nativeBytes = null;
		try {
			nativeBytes = CLibrary.Instance.NLPIR_ParagraphProcess(sInput, 3);

			System.out.println("分词结果为: " + nativeBytes);

			int nCountKey = 0;
			String nativeByte = CLibrary.Instance.NLPIR_GetKeyWords(sInput, 10,false);

			System.out.print("关键词提取结果是:" + nativeByte);

			CLibrary.Instance.NLPIR_Exit();

		} catch (Exception ex) {
			// TODO Auto-generated catch block
			ex.printStackTrace();
		}

	}
}

 此时这样设置之后,接下来需要区分window和linux:

(1)若是在window下,只需要将NLPIR.dll文件拷贝到工程目录下即可,就可以在IDE里面运行。若是在控制台的话,同样需要将NLPIR.dll文件拷贝到jar包同目录下即可。

(2)若是在linux下,只需要将libNLPIR.so文件拷贝到当前jar包的目录下即可。

下面说说我踩得坑:

坑1、如上述描述,非常简单的使用,硬是给我搞了一天才搞定,这说明什么?刚开始我所遇到的是CLibrary Instance = (CLibrary) Native.loadLibrary( "NLPIR", CLibrary.class);这句到底是加载什么路径下的库文件。费了很大的劲,最后采用的是使用相对路径。只需要填写“NLPIR”即可。并且这个名字是不可以改变的。

坑2、当你将NLPIR.dll文件添加到工程后,以为应该是可以了,但是还是报错,报如下错误:

这个错误主要原因是dll文件位数与操作系统的位数不一致所造成的。由于我是64位的操作系统,所以讲NLPIR.dll文件换成64位的即可。

坑3、当你上面都搞定了,运行之后,会报"初始化失败"的错误,这个错误刚开始搞了好久,没弄明白,因为觉得自己都配置好了,为什么还是初始化失败,这也是我花费时间最长的地方。后来网上一查发现,原来是有一个license。它是有时间的限制的。于是需要更换最新的license。去哪里更换呢?我在网上找到一个人的电话,直接打电话过去问的,听过那人介绍,原来在下载的时候,有标明license下载的地方。如下所示:https://github.com/NLPIR-team/NLPIR/tree/master/License

license原来是在这里下载。搞了半天,license放到了这么隐蔽的位置。

坑4、如上所示,也更换了最新的license,可以运行了。但是接下来,我的想法是将这个东西打jar包,在linux上运行,搞了半天不知道怎么回事,老是报错,最后的原因是在设置坑1的时候路径问题。因为一开始我设置的是绝对路径,总是报错,后来直接用相对路径。就不报错了。

坑5、本地是可以运行ICTCLAS了,但是由于NLP处理的预料都是比较大的数据,想法是提交到集群上去运行。那么如何实现呢?这里需要解决两个问题:

(1)libNLPIR.so文件的问题,由于ICTCLAS是C/C++写的代码,利用java/scala调用相应的API的时候,都是通过JNA调用.so里面的库函数来实现的。所以要想提交到集群上去运行,首先要解决的是各个节点如何能读取到libNLPIR.so文件?

解决的办法:将libNLPIR.so文件放在所有的节点的同一个目录下:/lib64/libNLPIR.so ,并且修改该.so文件的权限为777.

(2)Data中的配置文件和license文件在集群中如何加载?

解决的办法:再次查看ICTCLAS的参考手册,发现函数NLPIR_Init是有一个参数可以指定Data的路径的。于是借鉴上面的思路,即将Data目录放到所有的节点相同的目录下/dic/Data。这里需要注意点,函数NLPIR_Init的表示形式是:

bool NLPIR_Init(const char * sInitDirPath=0,int encoding=GBK_CODE,const char*sLicenceCode=0);

这里的参数sInitDirPath只需要传递Data的父目录即可。即:/dic 即可。(一开始我传入的参数是/dic/Data,最后结果还是报“初始化失败”的错误。)

至此,就可以在集群上运行ICTCLAS分词了。

坑6、利用ICTCLAS在集群上分词,为了提高运行速度,我们通常是考虑增加运行集群资源,如CPU,内存。但这样造成如下错误:

在datanote节点上出现如下两个错误:

 

造成以上两个错误的原因是:当我们在增加运行资源的同时,集群会在同个节点上分配两个或者两个以上的executor,而在程序运行的过程中,当一个executor释放资源时,会导致另外一个executor的资源也释放了。从而引发上面的错误。

解决的办法是:减少运行的资源,从而保证每个节点只运行一个executor。