理解和學習Solr的score機制
阿新 • • 發佈:2018-12-25
在solr的document文件定義中(schema.xml),需要對每個欄位進行定義indexed, stored,其中表示的含義為:
而只有indexed的欄位可以用於查詢,雖然solr中有sort欄位可以進行排序,這一般用於精確匹配的查詢,例如按照分類/品牌進行搜尋得到結果。如果使用者採用關鍵詞的方式進行模糊匹配,此時使用sort根據某個欄位進行排序會顯得不合時宜,無法幫助使用者搜尋到他想要的結果(更多是被sort所控制,這就好比百度的競價排名系統)。
根據查詢打分的相關文件可以看出,Solr中採用了最基本的向量空間模型:
其中存在的索引檔案.tvx, tvd, tvf儲存了term vector相關資訊,我們學習如何使用term vector來反映相似性程度。在向量空間模型中,夾角越小,說明相似程度越大,可以用cos餘弦函式定理來計算。
相似值計算公式:t=term, d=document, q=query, f=field
可以看出當前使用了edismax方式,預設查詢欄位df為text,boost function已經被設定,但存在較大問題:搜尋的排序嚴重與貨物的庫存數量相關,這肯定會導致使用者無法查詢出其想要的最精確的結果(即便是無庫存,使用者也可能希望能夠返回正確的商品,而不是有庫存的不相關商品)。Solr查詢中的常用引數列表:
Solr 支援多種查詢解析,給搜尋引擎開發人員提供靈活的查詢解析。Solr 中主要包含這幾個查詢解析器:標準查詢解析器、DisMax 查詢解析器,擴充套件 DisMax 查詢解析器(eDisMax)。
在solr查詢時,使用debugQuery可以打印出其打分的詳細資訊以便我們能夠正確的分析:
sumOfSquaredWeights = q.getBoost()2 • ∑ ( idf(t) • t.getBoost() )2 Solr Copy Field對打分的影響 如果使用了solr中的copyfield,會對打分造成什麼影響?copyfield,solr允許將不同的欄位copy到一個欄位中,搜尋只需要搜尋拷貝欄位即可,當然這樣會造成內容中包含非常多的搜尋詞。 根據在StackOverflow上的回答,如果要設定各自欄位的boost,就不能使用統一的copyfield,或者將copyfield進行分組: 當前我們設定的預設df(default field)為<str name="df">text</str>,整個欄位,當前我們可以通過更改qf的方式來做自定義boost,
欄位名稱 | 欄位含義 |
indexed | 如果該欄位是要做查詢的,需要將其設定為indexed,進行索引,以便能夠根據該欄位進行查詢。 但是與具體分詞手段無關,如果涉及到如果分詞,需要使用type屬性 |
stored | 在solr查詢結果中能夠正常返回,如果一個欄位stored=false,則查詢結果不會包括該欄位。 |
其中存在的索引檔案.tvx, tvd, tvf儲存了term vector相關資訊,我們學習如何使用term vector來反映相似性程度。在向量空間模型中,夾角越小,說明相似程度越大,可以用cos餘弦函式定理來計算。
相似值計算公式:t=term, d=document, q=query, f=field
- tf(t in d ) 表示該term 在 這個文件裡出現的頻率(即出現了幾次)。
- idf(t) 表示 出現該term的文件個數。
- t.getBoost() 查詢語句中每個詞的權重,可以在查詢中設定某個詞更加重要。
- norm(t,d) 標準化因子d.getBoost() • lengthNorm(f) • f.getBoost() ,它包括三個引數:
- Document boost:此值越大,說明此文件越重要。
- Field boost:此域越大,說明此域越重要。
- lengthNorm(field) = (1.0 / Math.sqrt(numTerms)):一個域中包含的Term總數越多,也即文件越長,此值越小,文件越短,此值越大。
- coord(q,d):一次搜尋可能包含多個搜尋詞,而一篇文件中也可能包含多個搜尋詞,此項表示,當一篇文件中包含的搜尋詞越多,則此文件則打分越高 ,numTermsInDocumentFromQuery / numTermsInQuery
- queryNorm(q):計算每個查詢條目的方差和,此值並不影響排序,而僅僅使得不同的query之間的分數可以比較。
<!-- SearchHandler --> <requestHandler name="/select" class="com.zp.solr.handler.component.ZpSearchHandler"> <lst name="defaults"> <str name="defType">edismax</str> <str name="echoParams">explicit</str> <str name="wt">json</str> <str name="indent">true</str> <str name="df">text</str> <str name="bf"> map(psfixstock,0,0,0,100) </str> </lst> <shardHandlerFactory class="HttpShardHandlerFactory"> <int name="maxConnectionsPerHost">1000</int> <int name="corePoolSize">50</int> </shardHandlerFactory> </requestHandler> <!-- A request handler that returns indented JSON by default --> <requestHandler name="/query" class="solr.SearchHandler"> <lst name="defaults"> <str name="echoParams">explicit</str> <str name="wt">json</str> <str name="indent">true</str> <str name="df">text</str> </lst> </requestHandler>
引數 | 含義 |
df | default fields,預設查詢欄位 |
wt | writer type,指定查詢輸出結構格式,預設xml |
defType | 設定查詢解析器名稱 |
bf | boost function,可接受多個函式查詢,用空格隔開 |
qf | query fields,指定索引中查詢欄位,如果沒有指定,預設使用df |
q | 查詢字串,必輸項 |
q.op | 預設查詢連線符,AND OR |
sort | 排序,sort=<field_name>+<desc|asc>,... |
start | 分頁定義結果起始記錄數,預設為0 |
rows | 分頁定義結果每頁返回記錄數,預設為10 |
fq | filter query,可充分利用filter query cache,提高檢索效能。在q查詢符合結果中同時是fq查詢符合的 |
fl | field list,指定返回結果欄位,以空格或逗號分隔 |
timeAllowed | 設定查詢超時時間 |
bq | boost query,指定一個單詞或短語提升查詢權重 |
mm | Minimum Should Match,指定查詢中必須匹配的最小規則數,如果沒有在查詢中或在solrconfig.xml檔案中指定mm引數值,q.op引數的有效性將會受到影響。如果q.op是AND,則mm=100%,如果q.op是OR,則mm=1(100%表示全部匹配,1表示只要有一個匹配即可)。如果使用者想修改這些行為,可以在solrconfig.xml檔案中定義mm引數 |
"1046888": "
26.279617 = sum of:
0.9810601 = sum of:
0.1401725 = weight(text:女士 in 431) [DefaultSimilarity], result of:
0.1401725 = score(doc=431,freq=2.0), product of:
0.3656968 = queryWeight, product of:
1.4455243 = idf(docFreq=37139, maxDocs=57987)
0.25298557 = queryNorm
0.3833025 = fieldWeight in 431, product of:
1.4142135 = tf(freq=2.0), with freq of:
2.0 = termFreq=2.0
1.4455243 = idf(docFreq=37139, maxDocs=57987)
0.1875 = fieldNorm(doc=431)
0.8408876 = weight(text:手提包 in 431) [DefaultSimilarity], result of:
0.8408876 = score(doc=431,freq=2.0), product of:
0.89569205 = queryWeight, product of:
3.5404868 = idf(docFreq=4570, maxDocs=57987)
0.25298557 = queryNorm
0.9388133 = fieldWeight in 431, product of:
1.4142135 = tf(freq=2.0), with freq of:
2.0 = termFreq=2.0
3.5404868 = idf(docFreq=4570, maxDocs=57987)
0.1875 = fieldNorm(doc=431)
25.298557 = FunctionQuery(map(int(psfixstock),0.0,0.0,const(0))), product of:
100.0 = map(int(psfixstock)=1,min=0.0,max=0.0,target=const(0))
1.0 = boost
0.25298557 = queryNorm
",
女士手提包,搜尋詞拆分成兩個詞元:“女士” “手提包”,q.op預設為OR(通過設定mm的值可以影響該屬性),
idf為出現的頻率,單獨搜尋“女士”總條目數37139,單獨搜尋“手提包”總條目數4570,出現頻次越多就越不重要,idf的計算公式:
idf(t) = 1 + log (numDocs / (docFreq +1))
termFreq=2.0,tf的計算公式,2的1/2次方,得出1.414:
tf(t in d) = numTermOccurrencesInDocument 1/2
fieldNorm取決於匹配的文件field總數,大概在29個左右(由於查詢中並沒有設定boost),計算公式:
lengthNorm(field) = (1.0 / Math.sqrt(numTerms))
queryNorm,用來計算每個查詢條目的方差和,使得不同的query之間的分數可以進行比較:
寫道
queryNorm(q) = 1 / (sumOfSquaredWeights )sumOfSquaredWeights = q.getBoost()2 • ∑ ( idf(t) • t.getBoost() )2 Solr Copy Field對打分的影響 如果使用了solr中的copyfield,會對打分造成什麼影響?copyfield,solr允許將不同的欄位copy到一個欄位中,搜尋只需要搜尋拷貝欄位即可,當然這樣會造成內容中包含非常多的搜尋詞。 根據在StackOverflow上的回答,如果要設定各自欄位的boost,就不能使用統一的copyfield,或者將copyfield進行分組: 當前我們設定的預設df(default field)為<str name="df">text</str>,整個欄位,當前我們可以通過更改qf的方式來做自定義boost,
SearchText, SearchText2^3, SearchText3^10, SearchText4^100
另一種說法,在solr中支援的多值域(multiValued)其實也就是copyfield,同一個field對應多個value。當一個文件中出現同名的多值域時,倒排索引和項向量都會在邏輯上將這些詞的詞彙單元附加進去。當對多值域進行儲存的時候,它們在文件中的儲存順序是分離的,當在搜尋期間對文件進行檢索時,會發現多個field例項,最後該field的boost如何計算?使用每一個值域的boost相乘。
使用Query Field
我們設定query field,將下面的三個條件作為篩選條件,並設定其boost權重:
<str name="qf">brand_name^0.9 category_name^0.8 product_name^2.0</str>
此時我們查詢 “胸飾”,從開啟debugQuery,可以看到其中的詳細評分:
"debug": { "rawquerystring": "胸飾", "querystring": "胸飾", "parsedquery": "(+DisjunctionMaxQuery((brand_name:胸飾^0.9 | category_name:胸飾^0.8 | product_name:胸飾^2.0)) FunctionQuery(map(int(psfixstock),0.0,0.0,const(0),const(100))))/no_coord", "parsedquery_toString": "+(brand_name:胸飾^0.9 | category_name:胸飾^0.8 | product_name:胸飾^2.0) map(int(psfixstock),0.0,0.0,const(0),const(100))",
我們可以定義多個 requestHandler,用來專門處理多種型別,例如模糊查詢,根據商品id定位商品,後端的內部查詢,將它們分開這樣就可以完成不同的查詢了。