利用Xapian构建自己的搜索引擎:Document、Term和Value

zt371 发布于 2009/05/21 09:30
阅读 1K+
收藏 2

在上一篇《利用Xapian构建自己的搜索引擎:Database》里指出databaseXapian的基础,而这一篇里讲到的documentstermsvalues则是索引和查询的必要组成部分。

Documents terms and posting

在信息检索(IR)中,我们企图要获取的项称之为“document”,每一个document是被一个terms集合所描述的。 document”和“term”这两个词汇是IR中的术语,它们是来自“图书馆管理学”的。通常一个document认为是一块文本,. Usually a document is thought of as a piece of text, most likely in a machine readable form, 而一个term则是一个词语或短语以用作描述document的,在document中大多数会存在着多个term,例如某个document是跟口腔卫生相关的,那么可能会存在着以下的terms:“tooth”、“teeth”、“toothbrush”、“decay”、 cavity”、“plaque”或“diet”等等。

如果在一个IR系统中,存在一个名为Ddocument,此document被一个名为tterm所描述,那么t被认为索引了D,可以用以下式子表示:t->D。在实际应用的一个IR系统中通常是多个documents,如D1, D2, D3 ...组成的集合,且有多个term,如t1, t2, t3 ...组成的集合,从而有以下关系:ti -> Dj

如果某个特定的term索引了某个特定的document,那么称之为posting,说白了posting就是带position信息的term,在相关度检索中可能有一定的用途的。

给定一个名为Ddocument,存在着一个terms列表索引着它,我们称之为Dterm list

给定一个名为tterm,它索引着一个documents列表,这称之为tposting list(使用“Document list”可能会在叫法上更一致,但听起来过于空泛)。

在一个存在于计算机的IR系统中,terms是存储于索引文件中的。term可以用作有效地查找它的posting list,在posting list里,每一个document带有一个很短的标识符,就是document id。简单来说,一个posting list可以被认为是一个由document ids组成的集合,而term list则是一个字符串组成的集合。在某些IR系统的内部是使用数字来表示term的,因此在这些系统中,term list则是数字组成的集合,而Xapian则不是这样,它使用原汁原味的term,而使用前缀来压缩存储空间。

Terms不一定是要是document中出现的词语,通常它们会被转换为小写,而且往往它们被词干提取算法处理过,因此通过一个值为“connect”的term可能会检索出一系列的词语,例如“connect”、“connects”、“connection”或“connected”等,而一个词语也可能产生多个的terms,例如你会将提取出的词干和未提取的词语都索引起来。当然,这可能只适用于英语、法语或拉丁语等欧美系列的语言,而中文的分词则有很大的区别,总的来说,欧美语系的语言分词与中文分词有以下的区别:

l         拿英语来说,通常情况下英语的每一个词语之间是用空格来隔开的,而中文则不然,甚至可以极端到整篇文章都不出现空格或标点符号。

l         像上面提到的,“connect”、“connects”、“connection”或“connected”分别的意思“动词性质的连接”、“动词性质的第三人称的连接”、“名称性质的连接”或“连接的过去式”,但在中文里,用“连接”就可以表示全部了,几乎不需要词干提取。这意味着英语的各种词性大部分是有章可循的,而中文的词性则是天马行空的。

l         第 二点只是中文分词非常困难的一个缩影,要完全正确地标识出某个句子的语意是很困难的,例如“中华人民共和国成立了”这个句子,可以分出“中华”、“华人 ”、“人民”、“共和国”、“成立”等词语,不过其中“华人”跟这个句子其实关系不大。咋一眼看上去很简单,但机器那有这么容易懂这其中的奥妙呢?

 

Values

Values是附加在document上一种元数据,每一个document可以有多个values,这些values通过不同的数字来标识。Values被设计成在匹配过程中快速地访问,它们可以用作排序、排队多余重复的document和范围检索等用途。虽然values并没有长度限制,但最好让它们尽可能短,如果你仅仅是想存储某个字段以便作为结果显示,那么建议您最好将它们保存在documentdata中。

Document data

       每一个Document只有一个data,可以是任意类型格式的数据,当然在存储的时候请先转换为字符串。这听上去可能有点古怪,实情是这样的:如果要存储的数据是文本格式,则可以直接存储;如果要存储的数据是各种的对象,请先序列化成二进制流再保存,而在读取的时候反序列化读取。

UTF-8Unicode

       Xapian里的所有东西是用UTF-8来保存的,UTF-8Unicode的一种实现。现在很多人用VC为了方便是将编码设成“未设置”或“多字节”的,也就是说用的是系统内码(GB2312/GBK/ GB18030),这样的话则将数据保存到Xapian前要先转码为UTF-8,而从Xapian里读出的数据则要转码为GB2312/GBK/ GB18030才能正确显示,这里推荐用iconv,这是一个非常方便的库。

分词

      很多文章都说现在的中文分词已经很成熟的,但据实际考察,google或百度等大公司的分词引擎都是自己开发或有专门的公司开发的,的确已经算比较成熟。但市场上提供免费甚至开源的分词引擎不多,中科院研发的ictclas30分词精确度和分词速度都非常不错,而且还有词性标注和自定义添加词的功能,可惜不开源。另外比较受欢迎的还有libmmsegSCWS,因此都是开源的,不过经测试libmmseg的分词精度似乎不高,而SCWS由于使用了大量的递归,在生成词库的时候经常导致栈溢出(我是用vc2005编译的),需要自己将递归修改为循环,从演示的情况来看,SCWS的分词精度来算可以。

实战

由于Xapian并不像Lucene那样有Field的概念,因此一般采用以大写字母作为Termposting的前缀,但单个字母的前缀对程序员太不友好了,所以一般的做法是自定义一个用户前缀到term前缀的映射,如Title=>T,而XapianQueryParser也支持这种映射,QueryParser是查询解释器,能将一段字符串解释为XapianQuery,后面会陆续提到。

添加document的例子:

Xapian::Document doc;

    doc.add_term("K你好");

    doc.add_term("K那里");

    //posting是带position的term

    doc.add_posting("K吃饭", 14);

    doc.add_posting("K玩耍", 8);

    /*

    这里最好先用一个map放置value的名称和索引的配对

    这里使用起来像Lucene的SortField一样了。

    */

    doc.add_value(1, "1");

    doc.set_data("你好啊,在那里玩耍呢?还没吃饭吗?");

    //创建一个可写的db

    Xapian::WritableDatabase db("c:\\db");

    //将document加入到db中,返回document的id,此id在db中是唯一的

    Xapian::docid id = db.add_document(doc);

    //刷新到硬盘中

    db.flush();

获取document信息的例子:

//获取

    Xapian::Document doc = db.get_document(id);

    string v = doc.get_value(1);

    printf(v);//输出

    string data = doc.get_data();

    printf(data);//输出"你好啊,在那里玩耍呢?还没吃饭吗?"

    for (Xapian::TermIterator iter = doc.termlist_begin(); iter != doc.termlist_end(); ++iter)

    {

        printf(*iter);//依次输出term和posting

    }

上面的两个例子比较简单,如果要想更深入请查阅Omega的代码,里面有更复杂的应用。值得一提的Xapian里有一个TermGenerator,可以更方便地索引数据,不过这个类有两个不知道算不算缺点的特点:首先是依赖Stem,对于中文来说除非自己实现了一个Stem,否则TermGenerator用处不大;另外TermGenerator会自动将生成的termposting添加“Z”前缀。

在这里要提一下一个名为“Xapwrap”的东东,这是某个外国人用python写的一个封装Xapian的类库,里面某些思想还是不错的,只可惜只兼容Xapian 1.x之前的版本。我自己封装的类有一部分就是参考Xapwrap的。

下面是一段我正在用的代码:

//CXapianDocument是封装过的Xapian::Document

void  doSegment(CXapianDocument& document, const char* lpszInput, string strUserPrefix)
{
    //先分词,这里使用的是中科院的分词引擎
    int nCount = ICTCLAS_GetParagraphProcessAWordCount(lpszInput);
    result_t *result =(result_t*)malloc(sizeof(result_t)*nCount);
    //获取分词结果
    ICTCLAS_ParagraphProcessAW(nCount,result);
    string termPrefix;

    //通过用户前缀取得term前缀,这是我自定义的一个宏
    GetTermPrefixFromMap(this->m_userPrefixToTermPrefixMap, strUserPrefix, termPrefix)
    for (int i=0; i
    {
        //忽略标点符号,标点符号的词性标注为w开头的
        if(result[i].sPOS[0] == 'w')
        {
            continue;
        }

        char buf[100];
        memset(buf, 0, 100);
        int index = result[i].start;
        memcpy(buf,(void *)(lpszInput+index), result[i].length);
        //添加posting
        document.AppendPosting(termPrefix, buf, result[i].start);
    }
    free(result);

}

 

加载中
返回顶部
顶部