Sphinx關聯排序是怎樣工作的

Sphinx關聯排序是怎樣工作的 

一直以來,我們給Sphinx添加了相當多的匹配和排序模式,並且將添加更多。一些不同的問題經常被提出,從我怎樣讓指定的文檔排在第一位我怎麼根據匹配度來評定星級,實際處理要歸結於內在的匹配和排序。因些,讓我們看看內部的匹配和排序模式到底是怎樣工作的,影響最終權重值的權重因素 有哪些,是怎樣影響的, how does one tweak stuff,等等,當然還有我們的目標,評定星級。

什麼是匹配模式?

首先,我們歸類那些令人困惑的模式。SphinxAPI 提供兩個不同的方法,分別是 SetMatchMode() SetRankingMode() SphinxQL沒有提供方法,它只提供了排序選項,對應排序模式,但是沒有匹配模式。它們都是幹嘛的呢?

匹配模式都是爲了遺留和兼容。而排序模式是關於Sphinx計算相關度的。

在版本0.9.8之前,Sphinx只有匹配模式,並且每個匹配模式是用不同的代碼路徑實現。每個代碼路徑實現一組不同類型的匹配和排序。例如,SPH_MATCH_ALL要求所有的關鍵字都出現,並且只用phrase proximity計算文檔的權重。SPH_MATCH_ANY要求任何關鍵字中的一個,並且用不同的方式計算文檔權重。等等。

在版本0.9.8中,我們開始啓用一個全新的,統一的匹配引擎。爲了避免在使用它工作時破壞兼容性,在版本0.9.8中,只提供一個獨立的匹配模式,叫做SPH_MATCH_EXTENDED2。到版本0.9.9時,顯然新的引擎已經變的穩定並且表現的足夠好,因而我們贊成從新引擎中移除所有遺留的代碼路徑。因此從版本0.9.9開始,所有的查詢都用統一的引擎處理,這跟之前的情況不同,而且維護困難(指之前維護困難)。因此實際上現在所有的匹配模式都只是歷史遺留。

當然Sphinx仍然繼續兼容那些遺留的模式,並且當你使用其中的一種時,它會自動轉換成一個簡單的查詢短語代碼(完全忽略查詢語法)然後自動選擇一種適當的排序模式。但其實本來就是這樣。 一切都是由我們的統一引擎處理,因此,文檔權重(即@weight)只跟選擇的排序模式(即ranker)有關。例如,下面兩個查詢將得到完全一樣的權重(同樣一樣的處理時間):

// 1st route
$cl->SetMatchMode ( SPH_MATCH_ALL );
$cl->Query ( "hello world" );
 
// 2nd route
$cl->SetMatchMode ( SPH_MATCH_EXTENDED2 );
$cl->SetRankingMode ( SPH_RANK_PROXIMITY );
$cl->Query ( "hello world" );

注意第二種方法允許使用(@title hello world)語法,因爲這種匹配模式允許這樣做。第一種是不允許的,因爲在那種匹配模式中所有的特殊操作符會被忽略,@title會被當成一個關鍵字。

因此SetMatchMode()除了過濾關鍵字和選擇一個合適的排序外沒有做任何事情。它相當於一個歷史調用,因爲將不會再有新的匹配模式(起初在我們有一個完全成熟的查詢語法之前,有一種臨時的解決辦法,但是這個臨時解決辦法臭名昭著for their tendency to last)而且支持查詢語法的匹配模式允許你使用任何以前舊模式提供的東西,這更不用說了。SetRankingMode()做的更少,它只是讓你明確的指定一個排序。它帶領我們到達問題……

什麼是排序?

排序模式,或者簡稱排序,可以正式的定義成函數:對一個給定的查詢和文檔參數計算相關值(權重)。

相關度基本上是主觀的,因此沒有一個適合所有的排序模式,將來也不會有。因此有多個不同的因子用於計算最終的權重,有無數的方法合併這些因子爲一個權重,討論這些是另外單獨帖子的主題。

Sphinx 1.10版本中使用的兩個最重要的權重因子是:1)經典統計學BM25因子,從80年代開始被大部分的搜索引擎使用,2Sphinx特有的短語相似因子。

BM25 因子

BM25是一個只依賴於匹配關鍵字出現頻率的浮點數值。Frequencies in question are in-document and in-collection frequencies.基本上,關鍵字和/或在文檔字段中出現多次,那個文檔的權重越大,這是很罕見的。

標準的BM25實現在Wikipedia article on BM25解釋的非常明白,但是Sphinx使用的是稍微修改過的變體。首先,考慮到性能原因,我們計算所有的關鍵字在文檔中出現的次數,而不只是計算匹配的關鍵字。例如(@title “hello world”)查詢只在標題中匹配“hello world”的單一實例,它的BM25的計算結果和(hello world)查詢一樣,(hello world)查詢文檔中匹配所有同時出現關鍵字的實例。第二,我們不強制任何文檔屬性,因此不需要文檔的長度,這樣我們也忽略了文檔長度(等於在原始的BM25中設置 b=0)。全部的變化都是內部的,在我們的測試中,使用原始BM25得到的計算結果不足夠說明排序關聯性能作用的改善。在Sphinx中使用的BM25計算算法的僞代碼如下:

BM25 = 0
foreach ( keyword in matching_keywords )
{
    n = total_matching_documents ( keyword )
    N = total_documents_in_collection
    k1 = 1.2
 
    TF = current_document_occurrence_count ( keyword )
    IDF = log((N-n+1)/n) / log(1+N)
    BM25 = BM25 + TF*IDF/(TF+k1)
}
 
// normalize to 0..1 range
BM25 = 0.5 + BM25 / ( 2*num_keywords ( query ) )

TF是指在一個文檔中被排序的檢索詞頻。它是基於在一個文檔內關鍵字出現的次數,但是因爲用對數函數平滑處理,因此出現1000次並不會得到1000倍的影響,而是1

TF一般在01之間變化,但是在條件k=1.2的情況下,它實現的變化範圍是0.4545…1之間。

IDF是指在整個文檔集中的反向文檔頻率。常見詞(如“the” or “to”等)的IDF值小,罕見詞的IDF值大,當一個關鍵詞只在一個文檔中出現時,達到峯值IDF1,而當關鍵詞在每個索引文檔都出現時,IDF=-1

因此,就像你上面看到的代碼,BM25值當關鍵字出現頻率小時會增大,相反在文檔中頻繁出現的話,BM25值會減小。要注意的是當關鍵詞過度頻繁匹配索引文檔超過一半以上時會降低BM25的值!事實上,當一個關鍵詞出現在90%的文檔中而很少的文檔沒有包含關鍵詞時,或許大概會更有趣,應該得到更大的權重。

短語相似因子

短語相似因子,與上面BM25截然相反,根本不關心關鍵詞出現的頻率和查詢關鍵詞在文檔中的位置。代替BM25使用的關鍵詞頻率,Sphinx分析關鍵詞在每個字段的位置,並且用最長公共子串算法(LCS)計算關鍵詞和文檔的短語相似度。基本上,每個字段的短語相似度就是一些關鍵詞在文檔中出現並且順序和查詢一致。這裏是一些例子:

1) query = one two three, field = one and two three
field_phrase_weight = 2 (because 2-keyword long "two three" subphrase matched)
 
2) query = one two three, field = one and two and three
field_phrase_weight = 1 (because single keywords matched but no subphrase did)
 
3) query = one two three, field = nothing matches at all
field_phrase_weight = 0

每個字段的短語權重將乘以每個字段的權重值,字段權重值通過調用SetFieldWeights() API或者在SphinxQL中的field_weights選項設置的,然後再全部相加起來生成每個文檔的短語權重。字段的默認權重值爲1,不能設成小於1的值。整個短語相似算法的僞代碼如下所示:

doc_phrase_weight = 0
foreach ( field in matching_fields )
{
   field_phrase_weight = max_common_subsequence_length ( query, field )
   doc_phrase_weight += user_weight ( field ) * field_phrase_weight
}

Example:

doc_title = hello world
doc_body = the world is a wonderful place
 
query = hello world
query_title_weight = 5
query_body_weight = 3
 
title_phrase_weight = 2
body_phrase_weight = 1
doc_phrase_weight = 2*5+3*1 = 13

正是由於短語相似因子保證了越相似的短語將排在前面,而精確匹配的短語將排在非常前面。可以使用字段權重值來調整排序,例如,上面例子中,匹配單個關鍵字的標題的權重值和匹配兩個關鍵字短語的內容一樣。

短語相似設計成比BM25需要更多的計算,因爲它需要計算所有在文檔中匹配的關鍵詞,而不僅僅只計算文檔本身。Sphinx默認使用短語相似算法,因爲我們相信這個產生更好的搜索質量。當然你也可以選擇使用一個更輕量級的排序器來省掉這些昂貴的相似計算。

 

Orbital view of the rankers

短語相似和BM25是兩個最重要的因子,就是說,決定最終的文檔權重。雖然,最終的權重值是由排序模式決定的,也就是,一個或者多個因子經過特殊函數的處理得到一個值(同樣,除了短語權重和BM25外,Sphinx還可以使用其他的排序因子。)

1.10beta版本,Sphinx8種不同的排序模式,並且在將來還會添加更多的。每個排序模式計算得到不同的權重值,因此可能或者可能不會適合一個特殊的方案。

有三種簡單的排序模式(NONE, WORDCOUNT, FIELDMASK)不做任何事,只統計關鍵字出現的次數,然後分別的返回匹配字段的位標識。它們在根本不需要排序或者由於應用端以某種方式計算時很有用。

有兩種遺留的排序模式(PROXIMITY, MATCHANY)是隻依靠短語相似算法,並分別用於模擬MATCH_ALL MATCH_ANY兩種遺留模式。

有三種排序模式(BM25, PROXIMITY_BM25, SPH04)是可以合併短語相似、BM25還有其他。允許查詢語法模式並且SphinxQL現在默認是用PROXIMITY_BM25,同時強烈建議PROXIMITY_BM25內部替換PROXIMITYBM25被推薦做爲一個合適的快速排序模式,不亞於其他系統。SPH04是建立在PROXIMITY_BM25之上,但另外排序精確字段匹配,字段開頭匹配比僅僅只是匹配等級高。

PROXIMITY_BM25 SPH04被期望產生最佳的質量,但是你特殊的結果可能不同。

選擇的排序模式會嚴重影響搜索的性能。NONE模式顯明是最快的排序模式,但是其他幾個呢?處理關鍵字位置(出現次數)是典型的最耗時的部分,因此不需要處理這部分的排序模式(FIELDMASK, BM25)總是比其他的快。同樣也需要較少的磁盤IO(不需要讀取位置)。處理關鍵字位置的排序模式(WORDCOUNT, PROXIMITY, MATCHANY, PROXIMITY_BM25, SPH04)只在CPU影響上有所區別。

排序本質的詳細信息

這章節描述Sphinx排序使用的準確算法並且提供僞代碼。你可以直接跳過去,除非你想微調排序,調整字段權重等。

雖然因子可能是整型,布爾型,浮點數或者其他任何可能的值,但權重值一定是個單標量值 。在Sphinx裏,權重值不只是標量而是一個整數。這不是一個強制限制,浮點數權重值可以通過各種各樣方法映射到一個整數值。

讓我們從三種最簡單的排序模式開始吧。

1) SPH_RANK_NONE 排序模式只是簡單的給每個文檔賦權重爲1.

weight = 1

爲什麼這樣並且實際跳過所有的排序呢?答案就是性能。

如果你需要搜索結果按價格排序,那爲什麼要浪費CPU週期來處理耗時而你並不需要的排序呢?

2) SPH_RANK_WORDCOUNT 排序模式計算所有的關鍵字出現的次數並乘以用戶設置的字段權重。

weight = 0
foreach ( field in matching_fields )
    weight += num_keyword_occurrences ( field )

注意它計算所有關鍵字出現的次數,而不只是唯一的關鍵字。因此1個匹配的關鍵字出現3次和3個不同關鍵字出現1次是一樣的。

3) SPH_RANK_FIELDMASK 排序模式返回一個匹配字段的位標識。

weight = 0
foreach ( field in matching_fields )
    set_bit ( weight, index_of ( field ) )
    // or in other words, weight |= ( 1 << index_of ( field ) )

其他五種排序模式稍微有點複雜並且大部分都依賴於短語相似。

4) SPH_RANK_PROXIMITY, 是遺留模式SPH_MATCH_ALL的默認排序模式,通過簡單的短語相似算法得到一個權重值:

weight = doc_phrase_weight

由短語權重的定義可知,當文檔匹配了查詢但是沒有保持匹配關鍵字的順序,所有這樣的文檔的權重都爲1.很顯然,它跟建議使用的PROXIMITY_BM25排序模式得到的結果並沒有區別。相關的搜索性能影響可以忽略不計。

5) SPH_RANK_MATCHANY 排序模式,用來模擬遺留的MATCH_ANY模式,結合了短語相似算法和匹配關鍵字次數,因此每個字段默認權重,a)較長子短語匹配(即更大短語相似)在任何字段將獲得更高的排序,b)與短語相似一致,文檔匹配不同關鍵字越多則排名越高。換句話說,我們先看最大匹配子短語的長度,再看匹配不同關鍵字的數量。僞代碼如下,

k = 0
foreach ( field in all_fields )
    k += user_weight ( field ) * num_keywords ( query )
 
weight = 0
foreach ( field in matching_fields )
{
   field_phrase_weight = max_common_subsequence_length ( query, field )
   field_rank = ( field_phrase_weight * k + num_matching_keywords ( field ) )
   weight += user_weight ( field ) * field_rank
}

它不使用BM25,因爲遺留的模式沒有使用,我們要保持兼容。

6) SPH_RANK_PROXIMITY_BM25, SphinxQL的默認排序模式,同樣也是SphinxAPI中“extended”匹配模式使用的默認排序,計算權重如下,

weight = doc_phrase_weight*1000 + integer(doc_bm25*999)

因此文檔短語相似是主要因子,BM25是輔助部分,當相同的短語相似時進行附加的文檔排序。BM2501之間,因此最終權重包含的最後3個數字是由BM25決定的,所有其他的數字用於短語權重。

7) SPH_RANK_BM25 排序模式計算匹配字段用戶設置的權重和BM25的總和.

field_weights = 0
foreach ( field in matching_fields )
    field_weights += user_weight ( field )
weight = field_weights*1000 + integer(doc_bm25*999)

PROXIMITY_BM25模式基本相似,除了用戶權重沒有乘以每個字段的短語相似值。不使用短語相似允許引擎只使用文檔列表來評估搜索,跳過處理關鍵字出現。除非你的文檔非常短(think tweets, titles, etc),關鍵字出現列表比文檔列表大,並且需要更多的時間去處理。因此BM25比其他任何相似算法快。

同樣,很多其他搜索系統默認使用BM25排序模式,或者有的只提供它做爲唯一選擇。因此當做性能測試展示的時候使用BM25排序可能有意義。

8) SPH_RANK_SPH04 排序模式更進一步改善PROXIMITY_BM25模式(引入數字代替有意義的名字,因爲名字太複雜)。短語相似仍然是主導因素,但是當給定一個短語相似的時候,在字段最開始匹配將排序更高,如果是整個字段完全匹配的話將排到最高處。僞代碼如下,

field_weights = 0
foreach ( field in matching_fields )
{
    f = 4*max_common_subsequence_length ( query, field )
    if ( exact_field_match ( query, field ) )
        f += 3
    else if ( first_keyword_matches ( query, field ) )
        f += 2
    field_weights += f * user_weight ( field )
}
weight = field_weights*1000 + integer(doc_bm25*999)

因此,當查詢“Market Street”,SPH04模式基本上將某個字段完全匹配“Market Street”的文檔排序在最前面,接着排像“Market Street Grocery”這樣在字段最開始匹配的文檔,然後排像“West Market Street”這樣在字段某處有與短語相匹配的文檔,最後排那些有包含短語所有關鍵字但不是一個短語的文檔(例如,“Flea Market on 26th Street”)。

那我怎麼畫出那些星星?

或者,更正式點,我怎樣計算最大可能的權重,然後根據返回的權重評定A-F等級,或者百分比,或者其他任何東西?

從前面的章節可以看到沒有簡單的辦法可以實現。最大權重依靠於選擇的排序模式和特定的查詢。例如,PROXIMITY_BM25模式權重的上界應該是

max_weight = num_keywords * sum ( user_field_weights ) * 1000 + 999

但這個上界可以達到嗎?實際上幾乎不可能,因爲那需要a)精確短語匹配b)在所有的字段c)附加的BM25峯值達到999which roughly translates to only using one-in-a-million keywords.此外,如果查詢使用字段權限符將會怎樣?例如:@title hello world? 在那種情況我們的上界將永遠不會被達到,因爲我們除了標題字段外的其他字段都不會匹配。For this particular query the practical upper bound, which could possibly be reached by an “ideal” document, is different.

Therefore, computing the “ideal” maximum weight (one that can actually be reached) is really, really complicated. We could possibly do that on Sphinx side but that’s a lengthy R&D project with questionable outcome. So if you can’t live without percentiles (or stars!), you can either use the “absolute” upper bound estimate like the one given above (that might never be practically reached and result in “100% match”), or just use the maximum weight from your particular query, and rescale everything to that weight. Using multi-queries, the latter can be made pretty cheap.

那麼我怎樣把精確匹配排在前面?

你使用一個排序模式實現。

不論SphinxAPI-默認使用PROXIMITY還是SphinxQL-默認使用PROXIMITY_BM25都不行。它們只把更長子短語匹配排在前面,但是不關心在哪字段的哪裏出現,還有是否匹配整個字段。

版本1.10-beta中添加的SPH04模式可以實現。

那麼我怎樣強制把文檔D排在第一位?

根據文檔D需要被排在前面的原因,你即可以使用一個適合你需求的排序模式,或者使用Sphinx運行時表達式來計算你所需要的並把結果集排序成不同。

例子如下,把精確匹配排在前面可以用表達式模擬排序:

SELECT *, @weight+IF(fieldcrc==$querycrc,1000,0) AS myweight ...
ORDER BY myweight DESC

fieldcrcCRC(field)屬性在索引時計算並存在索引文件裏,querycrc是在搜索時計算CRC(query)

例子如下,代替嚴格檢查CRC值匹配,你可以索引並保存字段長度,然後通過表達式把越短的字段排越前面

SELECT *, @weight+ln(len+1)*1000 AS myweight ...

例子,當搜索一個關鍵字時爲了強制一個文檔排的更靠前,你可以創建一個單獨的字段,放超級重要的關鍵字,然後給這個字段賦一個很高的權重。(不要把權重設置超過1000000

那麼和系統XYZ相比Sphinx是怎樣排序的?

主要的WEB搜索引擎(像Google)都有完全不同的主題。WEB範圍排序(還有垃圾信息過濾)迫使他們在排序中考慮成百上千種因素。雖然它們其中很多因素(PageRank,頁面和域名年齡,反向鏈接數量,代碼文本比等等)都是與文本無關,但也可以用於Sphinx,在某個特殊應用中使用表達式實現。Sphinx本身很普通而且它使用的排序模式只和文本相關,在上文都說的很清楚了。

雖然大部分其他全文搜索系統仍然使用BM25做爲默認的文本相關因子,或者甚至限制只能使用它。不要誤會我意思,BM25是重要的,它是一個重要的權重因子。但是使用它做爲唯一的排序因子確實是上世紀的事情。Sphinx基於相似的排序模式向前改進了一大步,並且我們計劃繼續改進。敬請期待,有趣的事將要發生。

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