在進(jìn)行Lucene的搜索過程解析之前,有必要單獨的一張把Lucene score公式的推導(dǎo),各部分的意義闡述一下。因為Lucene的搜索過程,很重要的一個步驟就是逐步的計算各部分的分?jǐn)?shù)。
Lucene的打分公式非常復(fù)雜,如下:
在推導(dǎo)之前,先逐個介紹每部分的意義:
- t:Term,這里的Term是指包含域信息的Term,也即title:hello和content:hello是不同的Term
- coord(q,d):一次搜索可能包含多個搜索詞,而一篇文檔中也可能包含多個搜索詞,此項表示,當(dāng)一篇文檔中包含的搜索詞越多,則此文檔則打分越高。
- queryNorm(q):計算每個查詢條目的方差和,此值并不影響排序,而僅僅使得不同的query之間的分?jǐn)?shù)可以比較。其公式如下:

- tf(t in d):Term t在文檔d中出現(xiàn)的詞頻
- idf(t):Term t在幾篇文檔中出現(xiàn)過
- norm(t, d):標(biāo)準(zhǔn)化因子,它包括三個參數(shù):
- Document boost:此值越大,說明此文檔越重要。
- Field boost:此域越大,說明此域越重要。
- lengthNorm(field) = (1.0 / Math.sqrt(numTerms)):一個域中包含的Term總數(shù)越多,也即文檔越長,此值越小,文檔越短,此值越大。
- 各類Boost值
- t.getBoost():查詢語句中每個詞的權(quán)重,可以在查詢中設(shè)定某個詞更加重要,common^4 hello
- d.getBoost():文檔權(quán)重,在索引階段寫入nrm文件,表明某些文檔比其他文檔更重要。
- f.getBoost():域的權(quán)重,在索引階段寫入nrm文件,表明某些域比其他的域更重要。
以上在Lucene的文檔中已經(jīng)詳細(xì)提到,并在很多文章中也被闡述過,如何調(diào)整上面的各部分,以影響文檔的打分,請參考有關(guān)Lucene的問題(4):影響Lucene對文檔打分的四種方式一文。
然而上面各部分為什么要這樣計算在一起呢?這么復(fù)雜的公式是怎么得出來的呢?下面我們來推導(dǎo)。
首先,將以上各部分代入score(q, d)公式,將得到一個非常復(fù)雜的公式,讓我們忽略所有的boost,因為這些屬于人為的調(diào)整,也省略coord,這和公式所要表達(dá)的原理無關(guān)。得到下面的公式:
然后,有Lucene學(xué)習(xí)總結(jié)之一:全文檢索的基本原理中的描述我們知道,Lucene的打分機(jī)制是采用向量空間模型的:
我們把文檔看作一系列詞(Term),每一個詞(Term)都有一個權(quán)重(Term weight),不同的詞(Term)根據(jù)自己在文檔中的權(quán)重來影響文檔相關(guān)性的打分計算。
于是我們把所有此文檔中詞(term)的權(quán)重(term weight) 看作一個向量。
Document = {term1, term2, …… ,term N}
Document Vector = {weight1, weight2, …… ,weight N}
同樣我們把查詢語句看作一個簡單的文檔,也用向量來表示。
Query = {term1, term 2, …… , term N}
Query Vector = {weight1, weight2, …… , weight N}
我們把所有搜索出的文檔向量及查詢向量放到一個N維空間中,每個詞(term)是一維。
我們認(rèn)為兩個向量之間的夾角越小,相關(guān)性越大。
所以我們計算夾角的余弦值作為相關(guān)性的打分,夾角越小,余弦值越大,打分越高,相關(guān)性越大。
余弦公式如下:
下面我們假設(shè):
查詢向量為Vq = <w(t1, q), w(t2, q), ……, w(tn, q)>
文檔向量為Vd = <w(t1, d), w(t2, d), ……, w(tn, d)>
向量空間維數(shù)為n,是查詢語句和文檔的并集的長度,當(dāng)某個Term不在查詢語句中出現(xiàn)的時候,w(t, q)為零,當(dāng)某個Term不在文檔中出現(xiàn)的時候,w(t, d)為零。
w代表weight,計算公式一般為tf*idf。
我們首先計算余弦公式的分子部分,也即兩個向量的點積:
Vq*Vd = w(t1, q)*w(t1, d) + w(t2, q)*w(t2, d) + …… + w(tn ,q)*w(tn, d)
把w的公式代入,則為
Vq*Vd = tf(t1, q)*idf(t1, q)*tf(t1, d)*idf(t1, d) + tf(t2, q)*idf(t2, q)*tf(t2, d)*idf(t2, d) + …… + tf(tn ,q)*idf(tn, q)*tf(tn, d)*idf(tn, d)
在這里有三點需要指出:
- 由于是點積,則此處的t1, t2, ……, tn只有查詢語句和文檔的并集有非零值,只在查詢語句出現(xiàn)的或只在文檔中出現(xiàn)的Term的項的值為零。
- 在查詢的時候,很少有人會在查詢語句中輸入同樣的詞,因而可以假設(shè)tf(t, q)都為1
- idf是指Term在多少篇文檔中出現(xiàn)過,其中也包括查詢語句這篇小文檔,因而idf(t, q)和idf(t, d)其實是一樣的,是索引中的文檔總數(shù)加一,當(dāng)索引中的文檔總數(shù)足夠大的時候,查詢語句這篇小文檔可以忽略,因而可以假設(shè)idf(t, q) = idf(t, d) = idf(t)
基于上述三點,點積公式為:
Vq*Vd = tf(t1, d) * idf(t1) * idf(t1) + tf(t2, d) * idf(t2) * idf(t2) + …… + tf(tn, d) * idf(tn) * idf(tn)
所以余弦公式變?yōu)椋?/p>
下面要推導(dǎo)的就是查詢語句的長度了。
由上面的討論,查詢語句中tf都為1,idf都忽略查詢語句這篇小文檔,得到如下公式
所以余弦公式變?yōu)椋?/p>
下面推導(dǎo)的就是文檔的長度了,本來文檔長度的公式應(yīng)該如下:
這里需要討論的是,為什么在打分過程中,需要除以文檔的長度呢?
因為在索引中,不同的文檔長度不一樣,很顯然,對于任意一個term,在長的文檔中的tf要大的多,因而分?jǐn)?shù)也越高,這樣對小的文檔不公平,舉一個 極端的例子,在一篇1000萬個詞的鴻篇巨著中,"lucene"這個詞出現(xiàn)了11次,而在一篇12個詞的短小文檔中,"lucene"這個詞出現(xiàn)了10 次,如果不考慮長度在內(nèi),當(dāng)然鴻篇巨著應(yīng)該分?jǐn)?shù)更高,然而顯然這篇小文檔才是真正關(guān)注"lucene"的。
然而如果按照標(biāo)準(zhǔn)的余弦計算公式,完全消除文檔長度的影響,則又對長文檔不公平(畢竟它是包含了更多的信息),偏向于首先返回短小的文檔的,這樣在實際應(yīng)用中使得搜索結(jié)果很難看。
所以在Lucene中,Similarity的lengthNorm接口是開放出來,用戶可以根據(jù)自己應(yīng)用的需要,改寫lengthNorm的計算 公式。比如我想做一個經(jīng)濟(jì)學(xué)論文的搜索系統(tǒng),經(jīng)過一定時間的調(diào)研,發(fā)現(xiàn)大多數(shù)的經(jīng)濟(jì)學(xué)論文的長度在8000到10000詞,因而lengthNorm的公 式應(yīng)該是一個倒拋物線型的,8000到 10000詞的論文分?jǐn)?shù)最高,更短或更長的分?jǐn)?shù)都應(yīng)該偏低,方能夠返回給用戶最好的數(shù)據(jù)。
在默認(rèn)狀況下,Lucene采用DefaultSimilarity,認(rèn)為在計算文檔的向量長度的時候,每個Term的權(quán)重就不再考慮在內(nèi)了,而是全部為一。
而從Term的定義我們可以知道,Term是包含域信息的,也即title:hello和content:hello是不同的Term,也即一個Term只可能在文檔中的一個域中出現(xiàn)。
所以文檔長度的公式為:
代入余弦公式:
再加上各種boost和coord,則可得出Lucene的打分計算公式。