上次写的博客中提到了给lemur加入中文分词的一种办法,但是现在经过测试发现那种办法有个缺点,就是说对于想使用field 的用户来说会出现问题,如果采用我上次说的办法,在用field建立好的索引就会出现丢失一些field中的词的情况,比如隊于这样的文档:
<DOC>
<DOCNO>1<DOCNO>
<TEXT>
<AUTHOR>小黑</AUTHOR>
<CONTENT>我热爱我的祖国,我热爱我的家!</CONTENT>
</TEXT>
</DOC>
如果采用如下的parameters建立索引
<parameters>
<corpus>
<path>/home/ubuntu/Desktop/lemur/indexcode/text/</path>
<class>trectext</class>
</corpus>
<field>
<name>DOCNO</name>
</field>
<field>
<name>TEXT</name>
</field>
<field>
<name>AUTHOR</name>
</field>
<field>
<name>CONTENT</name>
</field>
<memory>256m</memory>
<index>/home/ubuntu/Desktop/lemur/indexcode/index</index>
</parameters>
在检索的时候你可以通过如下的命令
IndriRunQuery -index=./index/ -query="#combine[content](热爱.content)" -printPassages=true
发现在content这个field中只有"我"和“热爱”两个词,如果你在content这个field中检索其他的词是不会有任何结果的,经过观察和猜测发现原因出现在indri::api::ParsedDocument中的tags这个向量上,在它的里面记录了一个field中的词是从第几个词开始从第几个词结束,而由于我的办法是在tokenize之后进行分词,然后用分好词的结果去替代terms,用新统计好的位置信息去替代positions,但是却忽略了tags这个向量,如果打印看的话,隊于上面的文档,可以看到tags标签隊于名字是<content>的field的begin是1,end是3。所以我们在分词的时候还要同时统计一下这个相关信息,然后利用新的开始和结束信息来替换indri::api::ParsedDocument中的tags的开始和结束信息,这样就万事大吉了!
给出我的整个代码如下:
void IndexEnvironment::addFile( const std::string& fileName, const std::string& fileClass ) {
if (!LoadSegRes()) //加载分词的资源(分词程序是实验室的程序)
{
cerr << "Can not loadSegRes!" << endl;
exit(-1);
}
void* pSegger = CreateSegger(); //创建分词器
SetOption(pSegger, 1, 1, 0); //设置相关参数
//编码转换的类,从utf-8到gb2312,实验室的分词只能切分gb2312的文字,所以作这个转换
CodeConvert ccu2g = CodeConvert("utf-8","gb2312");
CodeConvert ccg2u = CodeConvert("gb2312","utf-8");
indri::parse::Parser* parser = 0;
indri::parse::Tokenizer* tokenizer = 0;
indri::parse::DocumentIterator* iterator = 0;
indri::parse::Conflater* conflater = 0;
_getParsingContext( &parser, &tokenizer, &iterator, &conflater, fileClass );
if( !parser || !iterator ) {
_documentsSeen++;
if( _callback ) (*_callback) ( IndexStatus::FileSkip, fileName, _error, _documentsIndexed, _documentsSeen );
} else {
try {
indri::parse::UnparsedDocument* document;
indri::parse::TokenizedDocument* tokenized;
indri::api::ParsedDocument* parsed;
iterator->open( fileName );
std::vector<indri::parse::Transformation*> annotators = _createAnnotators( fileName, fileClass, &conflater );
// notify caller that the file was successfully parsed
if( _callback ) (*_callback)( IndexStatus::FileOpen, fileName, _error, _documentsIndexed, _documentsSeen );
while( document = iterator->nextDocument() ) {
_documentsSeen++;
tokenized = tokenizer->tokenize( document );
indri::utility::greedy_vector<char*> terms; //这个存放分词结果
indri::utility::greedy_vector<indri::parse::TermExtent> positions; //存放新的位置信息
int len = tokenized->terms.size();
int total_size = 0;
//计算分词之后结果所需要的空间,因为terms存放的是char*,需要用Buffer类开辟空间
for(int i = 0; i < len; i++)
{
char* s = tokenized->terms[i];
int size = strlen(s)*2;
total_size += (++size);
}
indri::utility::Buffer _termBuffer(total_size); //开辟terms中char* 的空间
int tag_size = tokenized->tags.size(); //统计tag信息
int p_tag_end[tag_size/2]; //tokenized中的tag存放的是从类似position位置信息,
int c_tag_end[tag_size/2]; //parsed中tag存放的是第几个词的位置信息
for(int i = 1; i < tag_size; i+=2)
{
p_tag_end[i/2] = tokenized->tags[i].end;
c_tag_end[i/2] = 0;
}
for(int i = 0; i < len ; i++)
{
int begin = tokenized->positions[i].begin;
int k = 0;
for(k = 0; k < tag_size; k++) //找到待切分的句子属于哪个field
{
if(begin < p_tag_end[k]){break;}
}
//得到待切分句子,tokenized已经按照空格和标点切好了,这里可以直接得到
char* s = tokenized->terms[i];
int size = strlen(s);
if(size == 0) {continue;}
char out[size*2];
ccu2g.convert(s,size,out,size*2); //编码转换
vector<string> vecWords;
string temp(out);
WordSegment(pSegger, temp, vecWords); //分词,结果存放在vecWords中
for(int j = 0; j < vecWords.size(); j++)
{
c_tag_end[k]++; //统计新分词的tag信息
char* word= (char*)vecWords[j].c_str();
int wordsize = vecWords[j].size();//strlen(word);
char outword[wordsize*2];
ccg2u.convert(word, wordsize, outword, wordsize*2); //转换回utf编码
string w(outword);
char* write_loc = _termBuffer.write( w.size()+1 ); //写term到termbuffer中
strncpy( write_loc, outword, w.size() );
write_loc[w.size()] = '/0';
terms.push_back(write_loc); //把词加入terms向量中去
//统计新的位置信息
int usize = strlen(outword);
indri::parse::TermExtent term_extent;
term_extent.begin = begin;
term_extent.end = usize + begin;
begin += usize;
positions.push_back(term_extent);
}
//改变后续的tag的end信息,使得信息准确
for(int j = k+1; j < tag_size/2; j++ ){c_tag_end[j] = c_tag_end[k];}
}
parsed = parser->parse( tokenized );
parsed = _applyAnnotators( annotators, parsed );
//替换相关信息,terms和positions
parsed->terms = terms;
parsed->positions = positions;
//得到新的tags信息,前一个tag的end是后一个tag的begin
if(tag_size > 0) parsed->tags[0]->end = c_tag_end[0];
for(int i = 1; i < tag_size/2; i++)
{
parsed->tags[i]->begin = c_tag_end[i-1];
parsed->tags[i]->end = c_tag_end[i];
}
_repository.addDocument( parsed );
_documentsIndexed++;
if( _callback ) (*_callback)( IndexStatus::DocumentCount, fileName, _error, _documentsIndexed, _documentsSeen );
}
// notify caller that the file was successfully closed
if( _callback ) (*_callback)( IndexStatus::FileClose, fileName, _error, _documentsIndexed, _documentsSeen );
iterator->close();
} catch( lemur::api::Exception& e ) {
if( iterator )
iterator->close();
// notify caller of errors
if( _callback ) (*_callback)( IndexStatus::FileError, fileName, e.what(), _documentsIndexed, _documentsSeen );
}
}
ReleaseSegger(pSegger); //释放分词资源
ReleaseSegRes();
}
上述就是我的代码,由于是实验品,加之我的c++水平问题,所以写的有效率不足之处一定很多,但是我实验过功能没有任何问题,目前看来稳定性也不错,下一步将把分词移和编码转换移到全局空间去,以提高效率。
<DOC>
<DOCNO>1<DOCNO>
<TEXT>
<AUTHOR>小黑</AUTHOR>
<CONTENT>我热爱我的祖国,我热爱我的家!</CONTENT>
</TEXT>
</DOC>
如果采用如下的parameters建立索引
<parameters>
<corpus>
<path>/home/ubuntu/Desktop/lemur/indexcode/text/</path>
<class>trectext</class>
</corpus>
<field>
<name>DOCNO</name>
</field>
<field>
<name>TEXT</name>
</field>
<field>
<name>AUTHOR</name>
</field>
<field>
<name>CONTENT</name>
</field>
<memory>256m</memory>
<index>/home/ubuntu/Desktop/lemur/indexcode/index</index>
</parameters>
在检索的时候你可以通过如下的命令
IndriRunQuery -index=./index/ -query="#combine[content](热爱.content)" -printPassages=true
发现在content这个field中只有"我"和“热爱”两个词,如果你在content这个field中检索其他的词是不会有任何结果的,经过观察和猜测发现原因出现在indri::api::ParsedDocument中的tags这个向量上,在它的里面记录了一个field中的词是从第几个词开始从第几个词结束,而由于我的办法是在tokenize之后进行分词,然后用分好词的结果去替代terms,用新统计好的位置信息去替代positions,但是却忽略了tags这个向量,如果打印看的话,隊于上面的文档,可以看到tags标签隊于名字是<content>的field的begin是1,end是3。所以我们在分词的时候还要同时统计一下这个相关信息,然后利用新的开始和结束信息来替换indri::api::ParsedDocument中的tags的开始和结束信息,这样就万事大吉了!
给出我的整个代码如下:
void IndexEnvironment::addFile( const std::string& fileName, const std::string& fileClass ) {
if (!LoadSegRes()) //加载分词的资源(分词程序是实验室的程序)
{
cerr << "Can not loadSegRes!" << endl;
exit(-1);
}
void* pSegger = CreateSegger(); //创建分词器
SetOption(pSegger, 1, 1, 0); //设置相关参数
//编码转换的类,从utf-8到gb2312,实验室的分词只能切分gb2312的文字,所以作这个转换
CodeConvert ccu2g = CodeConvert("utf-8","gb2312");
CodeConvert ccg2u = CodeConvert("gb2312","utf-8");
indri::parse::Parser* parser = 0;
indri::parse::Tokenizer* tokenizer = 0;
indri::parse::DocumentIterator* iterator = 0;
indri::parse::Conflater* conflater = 0;
_getParsingContext( &parser, &tokenizer, &iterator, &conflater, fileClass );
if( !parser || !iterator ) {
_documentsSeen++;
if( _callback ) (*_callback) ( IndexStatus::FileSkip, fileName, _error, _documentsIndexed, _documentsSeen );
} else {
try {
indri::parse::UnparsedDocument* document;
indri::parse::TokenizedDocument* tokenized;
indri::api::ParsedDocument* parsed;
iterator->open( fileName );
std::vector<indri::parse::Transformation*> annotators = _createAnnotators( fileName, fileClass, &conflater );
// notify caller that the file was successfully parsed
if( _callback ) (*_callback)( IndexStatus::FileOpen, fileName, _error, _documentsIndexed, _documentsSeen );
while( document = iterator->nextDocument() ) {
_documentsSeen++;
tokenized = tokenizer->tokenize( document );
indri::utility::greedy_vector<char*> terms; //这个存放分词结果
indri::utility::greedy_vector<indri::parse::TermExtent> positions; //存放新的位置信息
int len = tokenized->terms.size();
int total_size = 0;
//计算分词之后结果所需要的空间,因为terms存放的是char*,需要用Buffer类开辟空间
for(int i = 0; i < len; i++)
{
char* s = tokenized->terms[i];
int size = strlen(s)*2;
total_size += (++size);
}
indri::utility::Buffer _termBuffer(total_size); //开辟terms中char* 的空间
int tag_size = tokenized->tags.size(); //统计tag信息
int p_tag_end[tag_size/2]; //tokenized中的tag存放的是从类似position位置信息,
int c_tag_end[tag_size/2]; //parsed中tag存放的是第几个词的位置信息
for(int i = 1; i < tag_size; i+=2)
{
p_tag_end[i/2] = tokenized->tags[i].end;
c_tag_end[i/2] = 0;
}
for(int i = 0; i < len ; i++)
{
int begin = tokenized->positions[i].begin;
int k = 0;
for(k = 0; k < tag_size; k++) //找到待切分的句子属于哪个field
{
if(begin < p_tag_end[k]){break;}
}
//得到待切分句子,tokenized已经按照空格和标点切好了,这里可以直接得到
char* s = tokenized->terms[i];
int size = strlen(s);
if(size == 0) {continue;}
char out[size*2];
ccu2g.convert(s,size,out,size*2); //编码转换
vector<string> vecWords;
string temp(out);
WordSegment(pSegger, temp, vecWords); //分词,结果存放在vecWords中
for(int j = 0; j < vecWords.size(); j++)
{
c_tag_end[k]++; //统计新分词的tag信息
char* word= (char*)vecWords[j].c_str();
int wordsize = vecWords[j].size();//strlen(word);
char outword[wordsize*2];
ccg2u.convert(word, wordsize, outword, wordsize*2); //转换回utf编码
string w(outword);
char* write_loc = _termBuffer.write( w.size()+1 ); //写term到termbuffer中
strncpy( write_loc, outword, w.size() );
write_loc[w.size()] = '/0';
terms.push_back(write_loc); //把词加入terms向量中去
//统计新的位置信息
int usize = strlen(outword);
indri::parse::TermExtent term_extent;
term_extent.begin = begin;
term_extent.end = usize + begin;
begin += usize;
positions.push_back(term_extent);
}
//改变后续的tag的end信息,使得信息准确
for(int j = k+1; j < tag_size/2; j++ ){c_tag_end[j] = c_tag_end[k];}
}
parsed = parser->parse( tokenized );
parsed = _applyAnnotators( annotators, parsed );
//替换相关信息,terms和positions
parsed->terms = terms;
parsed->positions = positions;
//得到新的tags信息,前一个tag的end是后一个tag的begin
if(tag_size > 0) parsed->tags[0]->end = c_tag_end[0];
for(int i = 1; i < tag_size/2; i++)
{
parsed->tags[i]->begin = c_tag_end[i-1];
parsed->tags[i]->end = c_tag_end[i];
}
_repository.addDocument( parsed );
_documentsIndexed++;
if( _callback ) (*_callback)( IndexStatus::DocumentCount, fileName, _error, _documentsIndexed, _documentsSeen );
}
// notify caller that the file was successfully closed
if( _callback ) (*_callback)( IndexStatus::FileClose, fileName, _error, _documentsIndexed, _documentsSeen );
iterator->close();
} catch( lemur::api::Exception& e ) {
if( iterator )
iterator->close();
// notify caller of errors
if( _callback ) (*_callback)( IndexStatus::FileError, fileName, e.what(), _documentsIndexed, _documentsSeen );
}
}
ReleaseSegger(pSegger); //释放分词资源
ReleaseSegRes();
}
上述就是我的代码,由于是实验品,加之我的c++水平问题,所以写的有效率不足之处一定很多,但是我实验过功能没有任何问题,目前看来稳定性也不错,下一步将把分词移和编码转换移到全局空间去,以提高效率。