1. 程式人生 > >如何在Solr中更好的處理同義詞

如何在Solr中更好的處理同義詞

當使用Solr來構建搜尋引擎的時候,你可能經常會遇到這樣的場景:你有一個同義詞列表,並且你想使用者查詢也能夠命中到同義詞。聽起來很簡單不是嗎?為什麼搜尋“dog”的時候,不能命中包含“hound(獵犬)”或者“pooch(狗)”的文件呢?甚至包含“Rover(流浪者)”和“canis familiaris(犬)”?

小狗

叫Rover或者其他名字,可能只是為了讓小狗聽起來很可愛。

事實證明,Solr的同義詞擴充套件沒有你想象的那麼簡單。但是我們有很多好的方法來搬石頭砸自己的腳。

The SynonymFilterFactory

Solr提供了一個聽起來很酷的SynonymFilterFactory,它可以接收一個逗號分割的同義詞文字。你甚至可以選擇同義詞是相互擴充套件還是特定方向的替換。

舉例來說,你可以讓“dog”,“hound”和“pooch”都擴充套件為“dog hound pooch”,或者你可以指定“dog”對映到“hound”,反過來卻不可以,或者你可以把所有的詞都轉化為”dog“,Solr處理這部分是非常靈活的並且做的很棒。

當你考慮是把SynonymFilterFactory放在查詢分析器還是索引分析器時,這個問題就變得很複雜啦。

Index-time vs. query-time

下圖總結了查詢時(query-time)和索引時(index-time)同義詞擴充套件的基本差異。當然我們是為了解決solr中使用的問題,但是這2種方法適用於任何資訊檢索系統。

Index-time vs. query-time expansion.

你的直觀選擇可能是將SynonymFilterFactory放在查詢分析器內。理論上,這樣做有以下優點:

  • 索引大小不會變化
  • 同義詞可以隨時更換,不用更新索引
  • 同義詞實時生效,不需要重新索引

然而,按Solr Docs所說,這是一個Very Bad Thing to Do(™),顯然的你應該把SynonymFilterFactory放在索引分析器裡,而不是簡單的依靠你的直覺來判斷。文件裡說,查詢時的同義詞擴充套件有以下的缺點:

  • 多字同義詞並不能識別為短語查詢
  • 罕見同義詞的IDF會被加權,導致不可想象的搜尋結果
  • 多字同義詞不會匹配查詢

這有點複雜,因此也值得我們一一解決這些問題。

多字同義詞並不能識別為短語查詢

在Health On the Net,我們的搜尋引擎使用MeSH來做查詢擴充套件,MeSH是一個為健康領域提供優質同義詞的醫療本體。例如”breast cancer“的同義詞:

breast neoplasm
breast neoplasms
breast tumor
breast tumors
cancer of breast
cancer of the breast

因此在正常情況下,如果SynonymFilterFactory配置了expand="true",查詢”breast cancer“就變成了:

+((breast breast breast breast breast cancer cancer) (cancer neoplasm neoplasms tumor tumors) breast breast)

這將命中包含”breast neoplasms“,”cancer of the breast”等等的文件。

然而,這也意味著,如果你正在做一個短語查詢(比如”breast cancer“),如果想生效,你的文件必須字面上匹配類似”breast cancer breast breast“這樣的字元。

啊?這裡到底發生了什麼?事實證明SynonymFilterFactory並沒有按你所想來擴充套件多字同義詞。直覺上,可能認為它表現為一個有限自動機,Solr構建出的結果可能類似這樣(忽略複數):

但是,它真正構建的是下面這樣的:

簡直是一碗義大利麵。

你可憐的文件必須依序包含所有的4個部分。讓人驚訝。

同樣,DisMax和EDisMax查詢分析器的mm(最小匹配)引數,並不能像你所想的那樣工作。在上面的例子中,設定mm=100%將需要所有4個部分都匹配。

+((breast breast breast breast breast cancer cancer) (cancer neoplasm neoplasms tumor tumors) breast breast)~4

罕見同義詞的IDF會被加權

即使你沒有多字同義詞,Solr Docs也提到了第二個避免查詢時擴充套件的原因:不正常的IDF加權。考慮我們的”dog”,”hound”,”pooch”例子,查詢3個裡面的任意一個都會被擴充套件為:

+(dog hound pooch)

由於“hound”和”pooch“是比較少見的字,因此無論查詢什麼,包含這些字的文件會在查詢結果中排名特別高。這對可憐的使用者來說,簡直是一個浩劫,為什麼搜尋”dog“的時候,會有那麼多包含”hound“和”pooch“的怪異文件排名那麼高。

索引時擴充套件通過給”dog”,”hound”,”pooch”賦予相同的IDF值,而不管原始文件是什麼。

多字同義詞不會匹配查詢

最後,也是最嚴重的是,如果你對使用者查詢做任意型別的分詞,SynonymFilterFactory並不會匹配多字同義詞。這是因為分詞器會將使用者輸入分開,然後才交給SynonymFilterFactory來轉換。

比如,查詢“cancer of the breast”會被StandardTokenizationFactory分詞為[“cancer”,”of”,”the”,”breast],並且只有獨立的詞才會傳給SynonymFilterFactory。因此,在這種情況下,如果分詞後的單個詞,比如‘cancer“和”breast“都沒有同義詞的情況下,同義詞擴充套件就壓根不會發生。

其他問題

最初,我按照Solr的建議,使用索引時擴充套件,但是我發現索引時同義詞擴充套件有它自己的問題。顯然,除了有索引爆炸的問題,我還發現一個關於高亮的有趣的bug。

當我搜索”breast cancer“的時候,我發現高亮器會很神奇的把”breast cancer X Y“給高亮了,其中”X“和”Y“是文件中任何跟在”breast cancer“後面的2個字元。例如,它可能會高亮”breast cancer frauds are“或者”breast cancer is to“。

看完這個solr bug,這和前面提到的Solr多字同義詞擴充套件是一個原因。

使用查詢時擴充套件,你的查詢被轉換為像義大利麵般的圖已經足夠的怪異了。但是在索引時擴充套件,假如你的文件包含”breast cancer treatment options“,會變成什麼樣子呢。

這就是Lucene認為的你文件的樣子。同義詞擴充套件給你帶來了比你要求更多的東西,類似”Dada-esque“的結果!”Breast tumor the options“確實是這樣的。

從根本上來說,Lucene認為一個查詢”cancer of the breast“(4個Token)和你原始文件裡的”breast cancer treatment options“(4個Token)是一樣的。這是因為Tokens只是一個疊加另一個上面而已,丟失任何資訊的部分都可以由它後面的部門來替代。

查詢時擴充套件不會引起這個問題,因為Solr只擴充套件了查詢,而不是文件。因此Lucene仍然認為查詢的”cancer of the breast“只會匹配文件裡的”breast cancer“。

總結

所有這些古怪的問題,讓我得出這樣的結論:Solr內建的同義詞擴充套件機制是及其糟糕的。我必須找出一個更好的方法來讓Solr按我想的來執行。

總之,無論是索引時擴充套件還是查詢時擴充套件使用標準的SynonymFilterFactory都是不可行的,因為它們都有各自不同的問題。

Index-time

  • 索引爆炸
  • 同義詞不能立即生效,所有文件需重新索引
  • 同義詞不能立即刪除
  • 多字同義詞導致多餘的文字被高亮

Query-time

  • 短語查詢不支援
  • 罕見同義詞被認為加權了
  • 多字同義詞不匹配查詢

我開始假設理想的同義詞擴充套件系統應該是基於查詢時的,由於基於索引的擴充套件有那麼多固有的缺點。同時,我也意識到在Solr實現同義詞擴充套件之前,有一個更加根本的問題需要解決。

回到”dog“/”hound”/”pooch”的例子,對待3個詞對等的是不明智的。在特定的查詢中,”dog“可能並不與”hound“和”pooch“是一樣的,比如 (e.g. “The Hound of the Baskervilles,” “The Itchy & Scratchy & Poochy Show”). 一視同仁感覺是錯誤的。

同樣的,即使使用官方推薦的索引時擴充套件,IDF權重也被拋棄了。每個包含”dog“的文章現在也都包含”pooch“,這意味著我們將永久的丟失關於”pooch“的真實IDF值。

在一個理想的系統裡,搜尋”dog“,返回的結果應該包含所有存在”hound“和”pooch“的文件,但是應該將所有包含真實查詢的文件排的更靠前面,包含”dog“的應該得到更高的分。同樣的,搜尋“hound”應該把包含“hound”的排的更靠前面,搜尋“pooch”就應該將包含“pooch”的更靠前。所有的3個搜尋都返回相同的文件集,但是結果排序不一樣。 ###Solution

我的解決方法是,把同義詞擴充套件從分析器的Tokenizer鏈移動到QueryParser。不是把查詢變成如上面的縱橫交錯的圖,而是把它分為2個部分:主查詢和同義詞查詢。然後我為每個部分獨立配置權重,指定每個部分內部為“should occur”。最後將二者使用“must occur”的布林查詢包裝起來。

因此,搜尋“dog”為被解析為類似這樣:

+((dog)^1.2 (hound pooch)^1.1)

1.2和1.1是獨立的權重,可以配置。文件必須包含“dog”,”hound”或者“pooch”,但是“dog”更優先顯示。

這樣來處理同義詞,帶來了另一個有趣的副作用:它消除了短語查詢不支援的問題。如果是“breast cancer”(帶引號),將會被解析為這樣:

+(("breast cancer")^1.2 (("breast neoplasm") ("breast tumor") ("cancer ? breast") ("cancer ? ? breast"))^1.1)

(問號?的出現是由於停用詞“of”和“the”)

這意味著查詢帶引號的“breast cancer”會匹配所有包含“breast neoplasm,” “breast tumor,” “cancer of the breast,” and “cancer of breast.“字元的文件。

我比原始的SynonymFilterFactory更進一步,針對一個特定的查詢構建了所有可能的同義詞組合查詢。比如查詢”dog bite“,同義詞檔案是:

dog,hound,pooch
bite,nibble

… then the query will be expanded into:

查詢將會被擴充套件為:

dog bite
hound bite
pooch bite
dog nibble
hound nibble
pooch nibble