Xapian :Document、Term和Value


在信息檢索(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系統中,存在一個名爲D的document,此document被一個名爲t的term所描述,那麼t被認爲索引了D,可以用以下式子表示:t->D。在實際應用的一個IR系統中通常是多個documents,如D1, D2, D3 ...組成的集合,且有多個term,如t1, t2, t3 ...組成的集合,從而有以下關係:ti -> Dj。

如果某個特定的term索引了某個特定的document,那麼稱之爲posting,說白了posting就是帶position信息的term,在相關度檢索中可能有一定的用途的。

給定一個名爲D的document,存在着一個terms列表索引着它,我們稱之爲D的term list。

給定一個名爲t的term,它索引着一個documents列表,這稱之爲t的posting 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並沒有長度限制,但最好讓它們儘可能短,如果你僅僅是想存儲某個字段以便作爲結果顯示,那麼建議您最好將它們保存在document的data中。

Document data

       每一個Document只有一個data,可以是任意類型格式的數據,當然在存儲的時候請先轉換爲字符串。這聽上去可能有點古怪,實情是這樣的:如果要存儲的數據是文本格式,則可以直接存儲;如果要存儲的數據是各種的對象,請先序列化成二進制流再保存,而在讀取的時候反序列化讀取。

UTF-8與Unicode

       Xapian裏的所有東西是用UTF-8來保存的,UTF-8是Unicode的一種實現。現在很多人用VC爲了方便是將編碼設成“未設置”或“多字節”的,也就是說用的是系統內碼(GB2312/GBK/ GB18030),這樣的話則將數據保存到Xapian前要先轉碼爲UTF-8,而從Xapian裏讀出的數據則要轉碼爲GB2312/GBK/ GB18030才能正確顯示,這裏推薦用iconv,這是一個非常方便的庫。

分詞

      很多文章都說現在的中文分詞已經很成熟的,但據實際考察,google或百度等大公司的分詞引擎都是自己開發或有專門的公司開發的,的確已經算比較成熟。但市場上提供免費甚至開源的分詞引擎不多,中科院研發的ictclas30分詞精確度和分詞速度都非常不錯,而且還有詞性標註和自定義添加詞的功能,可惜不開源。另外比較受歡迎的還有libmmseg和SCWS,因此都是開源的,不過經測試libmmseg的分詞精度似乎不高,而SCWS由於使用了大量的遞歸,在生成詞庫的時候經常導致棧溢出(我是用vc2005編譯的),需要自己將遞歸修改爲循環,從演示的情況來看,SCWS的分詞精度來算可以。

實戰

由於Xapian並不像Lucene那樣有Field的概念,因此一般採用以大寫字母作爲Term和posting的前綴,但單個字母的前綴對程序員太不友好了,所以一般的做法是自定義一個用戶前綴到term前綴的映射,如Title=>T,而Xapian的QueryParser也支持這種映射,QueryParser是查詢解釋器,能將一段字符串解釋爲Xapian的Query,後面會陸續提到。

添加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<string, int>放置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會自動將生成的term或posting添加“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<nCount; 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);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章