1. 程式人生 > >crf++模型訓練到c++、java呼叫(介面)

crf++模型訓練到c++、java呼叫(介面)

crf模型是個特別好用的模型,做分詞、做ner等nlp工作都力離不開,訓練crf模型用很多工具,比較出名的就是今天要講的crf++,其文件清晰,支援各種語言的介面,本篇blog要講的是c++和java的介面,   這裡java的介面是通過jni呼叫c++實際本質還是c++,不過也有人通過java實現了c++載入crf模型的那套邏輯,比較出名的就是在ansj中分詞,就是通過java實現載入模型的邏輯自己實現了要,廢話不多說,直接看步驟吧:

下面訓練的是一箇中文分詞模型,ner等也是一樣,在example中有

crf++下載地址,google的雲盤,最新版本0.58:

https://drive.google.com/drive/folders/0B4y35FiV1wh7fngteFhHQUN2Y1B5eUJBNHZUemJYQV9VWlBUb3JlX0xBdWVZTWtSbVBneU0

crf++官方文件:

https://github.com/search?l=C%2B%2B&q=fp-growth&type=Repositories

crf++安裝步驟:

下載之後解壓,進入crf++目錄,執行下面命令
 ./configure 
 make 
 sudo make install #把標頭檔案、so文字拷貝到 /usr/local/include  /usr/local/lib 中去,後面c++介面可以用到生成的動態檔案

在這裡會生成訓練模型的命令

訓練模型的語料,用的人民日報的語料

https://pan.baidu.com/s/1jIy3YSY  

轉化crf++模型訓練的輸入格式,在這裡我寫了個java程式碼:

package com.xxx.crf;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import org.apache.commons.lang3.StringUtils;

public class DataUtils {

	public static void main(String[] args) {
		String inputpath="/Users/zhoumeixu/Downloads/crffile/swresult_withoutnature.txt";
		String outputpath="/Users/zhoumeixu/Downloads/crffile/crfmodelinput.txt";
		transFile(inputpath,outputpath);
		
		
	}
	
	public static  void transFile(String inputpath,String outputpath) {
		BufferedReader  br=null;
		BufferedWriter  bw=null;
		
		try {
			br=new BufferedReader(new InputStreamReader(new FileInputStream(inputpath)));
			bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputpath)));
			
			String line=br.readLine();
			while(line!=null) {
				if(StringUtils.isNotBlank(line)) {
					String[] lines=line.trim().split("\t");
					for(String  element:lines) {
						int len=element.length();
						if(len==1) {
							bw.write(element+"\t"+"S"+"\n");
						}else {
							
							bw.write(String.valueOf(element.charAt(0))+"\t"+"B"+"\n");
							for(int i=1;i<len-1;i++) {
								bw.write(String.valueOf(element.charAt(i))+"\t"+"M"+"\n");
							
							}
							
							bw.write(String.valueOf(element.charAt(len-1))+"\t"+"E"+"\n");
							
						}
					}
					bw.write("\n");
				}
				line=br.readLine();
			}
			
			
		}catch(Exception e) {
			
			
			
		}
		
		
		
	}

}

模型輸入的檔案格式見如下,在這裡我沒有用其他的特徵,比如說分詞可以加這個特徵字是數字、英文、漢字等形式,那就應該有三列:

邁	B
向	E
充	B
滿	E
希	B
望	E
的	S
新	S
世	B
紀	E

模型訓練:

#這裡加-t是生成model.txt檔案,非二進位制檔案,同時也會生成一個二進位制模型檔案
 crf_learn template    crfmodelinput.txt   model -t

會看到生成兩個模型,一個model  另外一個model.txt, 這裡去載入model檔案就可以,用ansj的那套邏輯是直接載入model.txt

java呼叫:

java呼叫必須先生成所需要的動態檔案,通過jni載入,首先在 **/CRF++-0.58/java目錄下執行make命令,不出意外的花的會報錯,這是因為對jni.h  和 jni_md.h檔案沒有,

其中jni.h 和 jni_md.h 檔案分別在java的include和include/darwin目錄下面,執行下面命令拷貝到這目錄下面:

把CRFPP_wrap.cxx下面的#include <jni.h>  改為#include "jni.h"

輸入命名:

sudo cp jni.h ~/Downloads/CRF++-0.58/java/

sudo cp jni_md.h ~/Downloads/CRF++-0.58/java/

修改 CRFPP_warp.xx 中的#include jni.h> ——#include “jni.h” 

make 
生成CRFPP.jar 和 libCRFPP.so

把上面的jar包丟在maven中進行依賴,下面寫了個測試程式碼:

 package com.xxx.crf;

import org.chasen.crfpp.Tagger;

public class Test {

	static {
		System.load("/Users/zhoumeixu/Downloads/CRF++-0.58/java/libCRFPP.so");

		System.out.println("so檔案載入成功");
	}

	public static void main(String[] args) {

		String text = "講述了三個代表重要思想,我們要解放思想,實事求是,抓住機遇";
		Tagger tagger = new Tagger("-m /Users/zhoumeixu/Downloads/crffile/model -v 3 -n2");
		tagger.clear();
		char[] ch = text.toCharArray();
		tagger.clear();
		StringBuffer sb = new StringBuffer();

		for (int i = 0; i < ch.length; i++) {

			tagger.add(String.valueOf(ch[i]));
		}
		tagger.parse();
		long size = tagger.size();
		long xsize = tagger.xsize();
		for (long i = 0; i < size; i++) {
			for (long j = 0; j < xsize; j++) {
				String str = tagger.x(i, j);
				String tag = tagger.y2(i);
				if ("B".equalsIgnoreCase(tag)) {
					sb.append(" ").append(str);
				} else if ("M".equalsIgnoreCase(tag)) {
					sb.append(str);

				} else if ("E".equalsIgnoreCase(tag)) {
					sb.append(str).append(" ");

				} else if ("S".equalsIgnoreCase(tag)) {
					sb.append(" ").append(str).append(" ");

				}
			}
		}
		
		System.out.println(sb.toString().trim());

	}

}

結果:

so檔案載入成功
講述  了  三  個  代表  重要  思想  ,  我們  要  解放  思想  ,  實事  求是  ,  抓住  機遇

c++介面:

c++相關的原始碼,在這篇部落格裡面寫的是比較清楚的http://www.hankcs.com/ml/crf-code-analysis.html,可以去看看,在這裡我主要寫個程式碼載入上面crf++訓練的模型。

先看下CMakeList:

cmake_minimum_required(VERSION 3.10)
project(cppexcise)

set(CMAKE_CXX_STANDARD 11)

link_directories(/usr/local/include)
link_libraries(/usr/local/lib)

add_executable(cppexcise  main.cpp )
target_link_libraries(cppexcise crfpp.0)

c++程式碼:

#include <iostream>
#include <vector>
#include <sstream>
#include "crfpp.h"

using namespace std;

int is_zh_ch(char p) {
    if (~(p >> 8) == 0) {
        return 1;
    }
    return -1;
}
//c++中string對中文支援不友好,不像java那麼隨意,用下面的方法實現
void string2vec(vector<string> &dump,string &text) {
    int len=text.length();
    int i = 0;
    while (i < len) {
        if (is_zh_ch(text.at(i)) == 1) {
            dump.push_back(text.substr(i, 3));
            i = i + 3;
        } else {
            dump.push_back(text.substr(i, 1));
            i = i + 1;
        }
    }

}


int main(int argc, const char *argv[]) {
    string text = "講述了三三代表重要思想,我們要解放思想,實事求是,抓住機遇";

    cout << text.length();

    vector<string> vec;

    string2vec(vec,text);

    cout << " Begin to load crf++ model" << endl;
    CRFPP::Model *crf_model = CRFPP::createModel("-m /Users/zhoumeixu/Downloads/crffile/model -v 3 -n2");

    CRFPP::Tagger *tagger = crf_model->createTagger();

    tagger->clear();

    vector<string>::iterator iter;

    for (iter = vec.begin(); iter != vec.end(); ++iter) {
        tagger->add((*iter).c_str());
    }
    if (!tagger->parse()) {
        cout << ">>>>>> Fail to parse !" << endl;
        return -1;
    }
    std::stringstream ss;


    for (size_t i = 0; i < tagger->size(); i++) {
        for (size_t j = 0; j < tagger->xsize(); j++) {
            string str = tagger->x(i, j);
            string tag = tagger->y2(i);
            if (tag == "B") {
                ss << " " << str;
            } else if (tag == "M") {
                ss << str;
            } else if (tag == "E") {
                ss << str << " ";
            } else if (tag == "S") {
                ss << " " << str << " ";
            }
        }
    }

    cout << ss.str() << endl;

    ss.str("");
    cout << "清空結果:" << ss.str() << endl;


    return 0;
}

結果:

87 Begin to load crf++ model
講述  了  三  三  代表  重要  思想  ,  我們  要  解放  思想  ,  實事  求是  ,  抓住  機遇 

和java的結果是一模一樣的,總體是比較簡單的,我在這裡沒有加入其它特徵還是比較遺憾的,不過在crf++裡面有句法分析、ner識別,流程也比較簡單,大家可以去看看。