最近在做自然语言相关的项目,查了下c++下比较好用的工具包,发现哈工大的语言技术平台(LTP)被推荐的很多,几年前也开源了,不但可以云上用,还可以本地用。果断下载试一下,版本是 3.3.2,但是在配置过程中还是遇到了些问题。具体的配置过程详见LTP的在线文档。文档中对于如何编译(Windows、Linux和Mac都有提到,本博文只讨论Windows)、如何配置、如何进行测试说的比较清楚,这里是自己碰到的一些问题,做一下记录,也与大家分享。
根据自己的vs版本编译出一大堆的lib和dll,博主用的是vs2013。记得还要下载模板文件,如何下载详见LTP的在线文档。下面用在线文档中的一个分词示例来说下博主遇到的问题。。。
</pre><pre name="code" class="cpp">#include <iostream>
#include <string>
#include "segment_dll.h"
int main(int argc, char * argv[]) {
if (argc < 2) {
std::cerr << "cws [model path]" << std::endl;
return 1;
}
void * engine = segmentor_create_segmentor(argv[1]);//分词接口,初始化分词器
if (!engine) {
return -1;
}
std::vector<std::string> words;
int len = segmentor_segment(engine,
"爱上一匹野马,可我的家里没有草原。", words);//分词接口,对句子分词。
for (int i = 0; i < len; ++ i) {
std::cout << words[i] << "|";
}
std::cout << std::endl;
segmentor_release_segmentor(engine);//分词接口,释放分词器
return 0;
}
具体函数的接口和调用方法详见在线文档,说的很详细,这里不赘述。其中argv[1]上面的代码如果直接放到vs中是分词的模板文件路径,文件名为cws.model。将代码放入vs控制台工程中,配置好lib、dll、头文件后,cmd运行(注意lib和dll是debug还是release,编译时要用相应的模式编译,在线文档中说的是release版,姑且就用它吧),输入模板文件地址。等待结果,不如我们所料,并没有输出分词结果,原因是容器words长度为0,即里面没有存放数据,也就是segmentor_segment函数没有正常执行,或者说这个函数没有执行出我们期望的结果。这时,一定要从自身找问题,如果质疑LTP有问题,那么一周也出不来成果!!!好了,不卖关子,发生这种情况的原因就是字符的编码问题,LTP中使用的都是utf-8,其内部处理的字符串格式都是utf-8,包括我们的输入。在上面的代码中,我们的输入字符串"爱上一匹野马,可我的家里没有草原。"编码格式并不是utf-8,所以我们需要将普通string对象转化成utf-8的编码形式的string。上代码:
//普通sting类型 转UTF-8编码格式字符串
std::string string_To_UTF8(const std::string & str)
{
int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
wchar_t * pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴
ZeroMemory(pwBuf, nwLen * 2 + 2);
::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen);
int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
char * pBuf = new char[nLen + 1];
ZeroMemory(pBuf, nLen + 1);
::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
std::string retStr(pBuf);
delete[]pwBuf;
delete[]pBuf;
pwBuf = NULL;
pBuf = NULL;
return retStr;
}
//UTF-8编码格式字符串 ,转普通sting类型
std::string UTF8_To_string(const std::string & str)
{
int nwLen = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
wchar_t * pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴
memset(pwBuf, 0, nwLen * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), pwBuf, nwLen);
int nLen = WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
char * pBuf = new char[nLen + 1];
memset(pBuf, 0, nLen + 1);
WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
std::string retStr = pBuf;
delete[]pBuf;
delete[]pwBuf;
pBuf = NULL;
pwBuf = NULL;
return retStr;
}
注意添加相应的头文件:windows.h和io.h。有了上面两个可逆的函数,就可以进行我们的操作了。先创建一个utf-8编码的文本文件input.txt,其中只有一行数据:“中国国际广播电台创办于1941年12月3日。示例程序通过命令行参数指定模型文件路径。”简单粗暴,上代码:
int _tmain(int argc, _TCHAR* argv[])
{
/************************************************************************/
/* 分词操作 */
/************************************************************************/
std::cout << "*******************分词操作*******************" << std::endl;
void * engine = 0;
engine = segmentor_create_segmentor("ltp_data\\cws.model");//初始化一个分词器,并读取分词模板
if (!engine)
{
std::cout << "cws engine error" << std::endl;
return -1;
}
std::ifstream in("input.txt");
std::string line;
std::ofstream out;
out.open("output.txt", std::ofstream::out);
getline(in, line);
std::cout << line << std::endl;
std::vector<std::string> words;
words.clear();
int len = segmentor_segment(engine, string_To_UTF8(line), words);//进行分词操作,注意输入的字符串应该为UTF-8编码格式
for (int i = 0; i < len; ++i) //输出结果
{
std::cout << UTF8_To_string(words[i]);
if (i + 1 == len) std::cout << std::endl;
else std::cout << "|";
out << UTF8_To_string(words[i]) << " ";//保证输出的是ANSI编码
}
std::string str1 = "返回结果中词的个数。";
std::cout << str1 << std::endl;
words.clear();
len = segmentor_segment(engine, string_To_UTF8(str1), words);
for (int i = 0; i < len; ++i)
{
std::cout << UTF8_To_string(words[i]);
if (i + 1 == len) std::cout << std::endl;
else std::cout << "|";
out << UTF8_To_string(words[i]) << " ";
}
segmentor_release_segmentor(engine);
in.close();
out.close();
/************************************************************************/
/* 词性标注操作 */
/************************************************************************/
std::cout << "*******************词性标注操作*******************" << std::endl;
engine = postagger_create_postagger("ltp_data\\pos.model");
if (!engine)
{
std::cout << "pos engine error" << std::endl;
return -1;
}
words.clear();
in.open("output.txt");
std::string word;
std::cout << "待分词文本:" <<std::endl;
while (in >> word)
{
words.push_back(string_To_UTF8(word));
std::cout << word << " ";
}
std::cout << std::endl;
std::vector<std::string> postags;
postagger_postag(engine, words, postags);
for (int i = 0; i < postags.size(); i++)
{
std::cout << UTF8_To_string(words[i]) << "/" << UTF8_To_string(postags[i]);
if (i == postags.size() - 1) std::cout << std::endl;
else std::cout << " ";
}
postagger_release_postagger(engine);
in.close();
/************************************************************************/
/* 命名实体识别 */
/************************************************************************/
std::cout << "*******************命名实体识别*******************" << std::endl;
engine = ner_create_recognizer("ltp_data\\ner.model");
if (!engine)
{
std::cout << "ner engine error" << std::endl;
return -1;
}
std::vector<std::string> nertags;
ner_recognize(engine, words, postags, nertags);
for (int i = 0; i < nertags.size(); ++i)
{
std::cout << UTF8_To_string(words[i]) << "\t" << UTF8_To_string(postags[i])
<< "\t" << UTF8_To_string(nertags[i]) << std::endl;
}
ner_release_recognizer(engine);
/************************************************************************/
/* 依存句法分析 */
/************************************************************************/
std::cout << "*******************依存句法分析*******************" << std::endl;
engine = parser_create_parser("ltp_data\\parser.model");
std::vector<int> heads;
std::vector<std::string> deprels;
words.clear();
postags.clear();
words.push_back(string_To_UTF8("一把手")); postags.push_back("n");
words.push_back(string_To_UTF8("亲自")); postags.push_back("d");
words.push_back(string_To_UTF8("过问")); postags.push_back("v");
words.push_back(string_To_UTF8("。")); postags.push_back("wp");
parser_parse(engine, words, postags, heads, deprels);
for (int i = 0; i < heads.size(); ++i)
{
std::cout << UTF8_To_string(words[i]) << "\t" << postags[i] << "\t"
<< heads[i] << "\t" << UTF8_To_string(deprels[i]) << std::endl;
}
system("pause");
return 0;
}
输出结果: