0. 前言
最近工作的需要,需要讲SRILM通过JNI编译到Java程序中,一直没有找到一个完成的教程,遇到不少坑,特此整理,帮助大家少走弯路,不用各处去找,一篇文章解决问题。这里提供Mac和Linux两种系统的说明,请大家按照各自的系统进行。
1. SRILM的安装
SRILM的下载链接为http://www.speech.sri.com/projects/srilm/download.html 这里用的版本为1.7.1,创建安装的目录,假设安装目录为:$SRILM_HOME。
在编译之前,需要修改一下的几个地方:
1. 修改根目录的Makefile文件,添加一行SRILM = $SRILM_HOME,把SRILM在本地的位置写上。
2. 同样在根目录的Makefile文件,找到MACHINE_TYPE := $(shell $(SRILM)/sbin/machine-type)这一行,如果是Mac电脑,那么将这个注释掉,加一行MACHINE_TYPE := macosx-m64(如果32位那就写32);如果是Linux,那么不需要注释,基本会自动识别的。
3. 这个需要修改在common目录下的文件,如果是Mac系统,那么需要修改Makefile.machine.macosx,如果是Linux系统,需要根据你的系统情况找到对应的文件,查看系统情况使用uname -a命令。修改的内容均一样,找到GCC_FLAGS = -Wall -Wno-unused-variable -Wno-uninitialized -Wno-overloaded-virtual这一行,修改为GCC_FLAGS = -Wall -Wno-unused-variable -Wno-uninitialized -Wno-overloaded-virtual -fPIC。这一步很关键,否则会有错。具体解释参见http://blog.csdn.net/u010312436/article/details/52486811。
修改之后,就可以在srilm的目录中输入make World进行编译,编译结束之后,输入make test检查,如果大部分结果都为IDENTITY,那么证明编译成功。
2. 利用JNI生成头文件
这一步就是正常JNI的步骤,首先编写一个类来描述需要写的接口,例子如下
public class LMCaculate {
// TAG for log.
private static final String TAG = "LMCaculate";
// Single Instance.
private static LMCaculate instance = null;
// Get instance.
public static LMCaculate getInstance() {
if(instance == null) {
instance = new LMCaculate();
System.load(Config.LM_jni);
instance.init(Config.LM_file, 3);
}
return instance;
}
public native boolean init(String modelPath, int ngramOrder);
public native void release();
public native float calcProbSequence(String wordsSequence, String context);
}
需要接口完成的函数需要添加native标签,这里主要利用三个方法,一个是将lm文件读入内存,另一个是将释放资源,最后是计算语言模型的概率。
随后利用javah命令生成头文件,利用srilm编写对应的cpp文件。
3. 编写makefile文件
得到这两部分内容之后,接下来就是编译的步骤,这里直接将Mac平台和Linux平台的makefile贴出来,大家可以根据自己的情况进行修正。首先是Linux的makefile文件
Srilm_INCLUDE = $SRILM_HOME/include \ -I /usr/lib/jvm/java-7-openjdk-amd64/include Srilm_LIB = $SRILM_HOME/lib/i686-m64/liboolm.a \ $SRILM_HOME/lib/i686-m64/libmisc.a \ $SRILM_HOME/lib/i686-m64/liblattice.a \ $SRILM_HOME/lib/i686-m64/libflm.a \ $SRILM_HOME/lib/i686-m64/libdstruct.a \ $SRILM_HOME/lib/i686-m64/libz.a libCalcSentProb.so: jni.o $(Srilm_LIB) g++ -shared -o libCalcSentProb.so jni.o $(Srilm_LIB) -lpthread -fopenmp jni.o: jni.cpp g++ -fPIC -I $(Srilm_INCLUDE) -c jni.cpp -o jni.o
这里的jni.cpp就是上一步根据jni生成的头文件写的cpp文件
在Mac上的Makefile文件为
Srilm_INCLUDE = $SRILM_HOME/include \
-I /Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/include \
-I /Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/include/darwin/
Srilm_LIB = $SRILM_HOME/lib/macosx-m64/liboolm.a \
$SRILM_HOME/lib/macosx-m64/libmisc.a \
$SRILM_HOME/lib/macosx-m64/liblattice.a \
$SRILM_HOME/lib/macosx-m64/libflm.a \
$SRILM_HOME/lib/macosx-m64/libdstruct.a \
$SRILM_HOME/lib/macosx-m64/libz.a
libCalcSentProb.dylib: jni.o $(Srilm_LIB) libiconv.dylib
g++ -shared -o libCalcSentProb.dylib jni.o $(Srilm_LIB) libiconv.dylib
jni.o: jni.cpp
g++ -fPIC -I $(Srilm_INCLUDE) -c jni.cpp -o jni.o
这里需要说明的是,在Mac中虽然有libiconv.dylib,但是在g++编译的过程中不会自动加入,需要手动从/usr/lib目录中拷贝出来(记得是拷贝,不是剪切!),放在本地目录即可。
通过这种方式就可以获得SRILM的java接口