前段時間使用了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。