Lucene TFIDFSimilarity评分公式详解
版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明。
一、预热
TFIDFSimilarity曾经是Lucene/Solr默认评分公式,但是从lucene-6.0
开始已经改成BM25Similary
了(详见Lucene-6789)。但我们今天看的依然是TFIDFSimilarity,因为它相对简单一些,对我们理解评分过程有好处。
首先假定你知道怎么把一篇文档转化成一个空间向量,并且知道空间向量模型。
接下来先来统一一下术语和记号
q : query,表示一个查询
d : document,表示一篇文档
V(q) : q⃗ \vec{q}" role="presentation">q⃗ q⃗ \vec{q}q 表示Query的向量
V(d) : d⃗ \vec{d}" role="presentation">d⃗ d⃗ \vec{d}d 表示Document的向量
|V(q)| : ∣q⃗∣ |\vec{q}|" role="presentation">∣q⃗ ∣∣q⃗∣ |\vec{q}|∣q∣ 表示Query向量的归一化
|V(d)| : ∣d⃗∣ |\vec{d}|" role="presentation">∣d⃗ ∣∣d⃗∣ |\vec{d}|∣d∣ 表示Document向量的归一化
在看TFIDFSimilarity之前,我们先看简单复习几个简单的公式。
- 余弦定理
cosine_similarity(q,d)=V(q)⋅V(q)∣V(q)∣×∣V(d)∣ cosine\_similarity(q,d) = \frac{V(q) · V(q)}{|V(q)| \times |V(d)|}" role="presentation">cosine_similarity(q,d)=V(q)⋅V(q)∣V(q)∣×∣V(d)∣cosine_similarity(q,d)=V(q)⋅V(q)∣V(q)∣×∣V(d)∣ cosine\_similarity(q,d) = \frac{V(q) · V(q)}{|V(q)| \times |V(d)|}cosine_similarity(q,d)=∣V(q)∣×∣V(d)∣V(q)⋅V(q),用余弦定理通过计算两向量的夹角来表示两文本的相似,这是一切的基础。
这里沿*Lucene Docs的写法,cosine_similarity(q,d) cosine\_similarity(q,d)" role="presentation">cosine_similarity(q,d)cosine_similarity(q,d) cosine\_similarity(q,d)cosine_similarity(q,d)而不是用score(q,d) score(q,d)" role="presentation">score(q,d)score(q,d) score(q,d)score(q,d)的原因是相似度不是每个最终得分,相似度只是评分过程比较重要的一个因素而已。*建议你还是先看看TFIDFSimilarty的官方文档,它讲得相当完整也很细**。
- tf-idf公式
tf-idf算法是一种非常常见算法,用来计算文本每个权重的。
tf-idf算法的原理如果词条在文档出频率越高,则词条权重越高;如词条在越多篇文档出现,而词条的权重越低。具体计算如下:
tfidf(t)=tf(t)∗idf(t) tfidf(t) = tf(t) * idf(t)" role="presentation">tfidf(t)=tf(t)∗idf(t)tfidf(t)=tf(t)∗idf(t) tfidf(t) = tf(t) * idf(t)tfidf(t)=tf(t)∗idf(t)
tf(t)=frequency tf(t) = \sqrt{frequency}" role="presentation">tf(t)=frequency−−−−−−−−√tf(t)=frequency tf(t) = \sqrt{frequency}tf(t)=frequency
idf(t)=1+logdoc_count+1doc_freq+1 idf(t) = 1 + \log{\frac{doc\_count+1}{doc\_freq+1}}" role="presentation">idf(t)=1+logdoc_count+1doc_freq+1idf(t)=1+logdoc_count+1doc_freq+1 idf(t) = 1 + \log{\frac{doc\_count+1}{doc\_freq+1}}idf(t)=1+logdoc_freq+1doc_count+1
tfidf(t)=frequency×(1+logdoc_count+1doc_freq+1) tfidf(t) = \sqrt{frequency} \times (1 + \log{\frac{doc\_count+1}{doc\_freq+1}})" role="presentation">tfidf(t)=frequency−−−−−−−−√×(1+logdoc_count+1doc_freq+1)tfidf(t)=frequency×(1+logdoc_count+1doc_freq+1) tfidf(t) = \sqrt{frequency} \times (1 + \log{\frac{doc\_count+1}{doc\_freq+1}})tfidf(t)=frequency×(1+logdoc_freq+1doc_count+1)
对于VSM而言,tf-idf算法并不是必须,甚至权重的引入也不是必须。也就是只需要把每个词转化为一个数值即可,可以用词条的HashCode、词包的下标等等。
当然tf-idf算法也不是计算权重的唯一算法,比tf-idf效果更的还有BM25(BM25Similarity)。
故名思义,TFIDFSimilarity即是用TFIDF作为VSM向量的权重来计算相似度的打分器。
二、开始
原想从两个字符串的相似计算开始来推导我们Lucene的评分公式的,但这样的话篇幅太长太啰嗦太复杂。因此选择从Lucene的公式出发来看全公式每个细节的含义,一步步变化和计算最终推导出实践公式。
2.1 理论公式
我们先看一下,Lucene TFIDFSimilarity给出的理论评分公式:
score(q,d)=coord_factor(q,d)×query_boost(q)×V(q)⋅V(d)∣V(q)∣×doc_len_norm(d)×doc_boost(d) score(q,d) = coord\_factor(q,d) \times query\_boost(q) \times \frac{V(q)·V(d)}{|V(q)|} \times doc\_len\_norm(d) \times doc\_boost(d) " role="presentation">score(q,d)=coord_factor(q,d)×query_boost(q)×V(q)⋅V(d)∣V(q)∣×doc_len_norm(d)×doc_boost(d)score(q,d)=coord_factor(q,d)×query_boost(q)×V(q)⋅V(d)∣V(q)∣×doc_len_norm(d)×doc_boost(d) score(q,d) = coord\_factor(q,d) \times query\_boost(q) \times \frac{V(q)·V(d)}{|V(q)|} \times doc\_len\_norm(d) \times doc\_boost(d) score(q,d)=coord_factor(q,d)×query_boost(q)×∣V(q)∣V(q)⋅V(d)×doc_len_norm(d)×doc_boost(d)
为什么说它是理论公式呢,因为它由VSM直接推导出来的公式。然则它在实际运用上并不是很方便和高效,因此我们对它进行一系列的变转使得它的计算得到一个简化的式子。
2.1.1 细说相似度部分
前面提到过Lucene相似度是通过VSM来计算(当然*TFIDFSimilarty的官方文档*也是提及的),相似度similarity通过如下公式计算的:cosine_similarity(q,d)=V(q)⋅V(q)∣V(q)∣×∣V(d)∣ cosine\_similarity(q,d) = \frac{V(q) · V(q)}{|V(q)| \times |V(d)|}" role="presentation">cosine_similarity(q,d)=V(q)⋅V(q)∣V(q)∣×∣V(d)∣cosine_similarity(q,d)=V(q)⋅V(q)∣V(q)∣×∣V(d)∣ cosine\_similarity(q,d) = \frac{V(q) · V(q)}{|V(q)| \times |V(d)|}cosine_similarity(q,d)=∣V(q)∣×∣V(d)∣V(q)⋅V(q)
a. 如何计算俩向量的内积
由于我们研究的是TFIDFSimilarity的评分公式,我们知道TFIDFSimilarity评分过程是采用了tf-idf算法作为向量的权重(weight)。
因此有
q⃗=(w1,w2,...,wn) \vec{q}=(w_1, w_2, ..., w_n)" role="presentation">q⃗ =(w1,w2,...,wn)q⃗=(w1,w2,...,wn) \vec{q}=(w_1, w_2, ..., w_n)q=(w1,w2,...,wn),且wi=tf(ti)×idf(ti,q) w_i=tf(t_i)\times idf(t_i, q)" role="presentation">wi=tf(ti)×idf(ti,q)wi=tf(ti)×idf(ti,q) w_i=tf(t_i)\times idf(t_i, q)wi=tf(ti)×idf(ti,q);
d⃗=(d1,d2,...,dn) \vec{d}=(d_1, d_2, ..., d_n)" role="presentation">d⃗ =(d1,d2,...,dn)d⃗=(d1,d2,...,dn) \vec{d}=(d_1, d_2, ..., d_n)d=(d1,d2,...,dn),且di=tf(ti)×idf(ti,d) d_i=tf(t_i)\times idf(t_i, d)" role="presentation">di=tf(ti)×idf(ti,d)di=tf(ti)×idf(ti,d) d_i=tf(t_i)\times idf(t_i, d)di=tf(ti)×idf(ti,d)
通常来说每个Query的每个词条的出现次数都是1
,因此 tf(t1)=tf(t2)=...=tf(ti)=a,a∈(0,1] tf(t_1) = tf(t_2) = ... = tf(t_i) = a, a\in(0, 1]" role="presentation">tf(t1)=tf(t2)=...=tf(ti)=a,a∈(0,1]tf(t1)=tf(t2)=...=tf(ti)=a,a∈(0,1] tf(t_1) = tf(t_2) = ... = tf(t_i) = a, a\in(0, 1]tf(t1)=tf(t2)=...=tf(ti)=a,a∈(0,1]。
V(q)⋅V(d)=q⃗⋅d⃗=∑t in q(wt×dt) V(q)·V(d) = \vec{q} · \vec{d} = \sum_{t\ in\ q}{(w_t \times d_t)}" role="presentation">V(q)⋅V(d)=q⃗ ⋅d⃗ =∑t in q(wt×dt)V(q)⋅V(d)=q⃗⋅d⃗=∑t in q(wt×dt) V(q)·V(d) = \vec{q} · \vec{d} = \sum_{t\ in\ q}{(w_t \times d_t)}V(q)⋅V(d)=q⋅d=t in q∑(wt×dt)
=∑t in q(tf(t,q)×idf(t,q)×tf(t,d)×idf(t,d)) = \sum_{t\ in\ q}{(tf(t,q)\times idf(t,q)\times tf(t,d)\times idf(t,d))}" role="presentation">=∑t in q(tf(t,q)×idf(t,q)×tf(t,d)×idf(t,d))=∑t in q(tf(t,q)×idf(t,q)×tf(t,d)×idf(t,d)) = \sum_{t\ in\ q}{(tf(t,q)\times idf(t,q)\times tf(t,d)\times idf(t,d))}=t in q∑(tf(t,q)×idf(t,q)×tf(t,d)×idf(t,d))
=tf(t,q)×∑t in qidf(t,q)×tf(t,d)×idf(t,d)) = tf(t,q) \times \sum_{t\ in\ q}{idf(t,q)\times tf(t,d)\times idf(t,d))}" role="presentation">=tf(t,q)×∑t in qidf(t,q)×tf(t,d)×idf(t,d))=tf(t,q)×∑t in qidf(t,q)×tf(t,d)×idf(t,d)) = tf(t,q) \times \sum_{t\ in\ q}{idf(t,q)\times tf(t,d)\times idf(t,d))}=tf(t,q)×t in q∑idf(t,q)×tf(t,d)×idf(t,d))
=a×∑t in qidf(t,q)×tf(t,d)×idf(t,d)) = a \times \sum_{t\ in\ q}{idf(t,q)\times tf(t,d)\times idf(t,d))}" role="presentation">=a×∑t in qidf(t,q)×tf(t,d)×idf(t,d))=a×∑t in qidf(t,q)×tf(t,d)×idf(t,d)) = a \times \sum_{t\ in\ q}{idf(t,q)\times tf(t,d)\times idf(t,d))}=a×t in q∑idf(t,q)×tf(t,d)×idf(t,d))
由上式易知a对同一个Query而言是一个常量,因此上式可以进一步简化为
V(q)⋅V(d)=∑t in qtf(t,d)×idf(t,d)×idf(t,q) V(q)·V(d) = \sum_{t\ in\ q}{tf(t,d)\times idf(t,d) \times idf(t,q)}" role="presentation">V(q)⋅V(d)=∑t in qtf(t,d)×idf(t,d)×idf(t,q)V(q)⋅V(d)=∑t in qtf(t,d)×idf(t,d)×idf(t,q) V(q)·V(d) = \sum_{t\ in\ q}{tf(t,d)\times idf(t,d) \times idf(t,q)}V(q)⋅V(d)=t in q∑tf(t,d)×idf(t,d)×idf(t,q)
那么又是怎么计算Query
的idf(t,q) idf(t,q)" role="presentation">idf(t,q)idf(t,q) idf(t,q)idf(t,q)呢?
已知道idf(t,d)=1+logdoc_count+1doc_freq+1 idf(t,d) = 1 + \log{\frac{doc\_count+1}{doc\_freq+1}}" role="presentation">idf(t,d)=1+logdoc_count+1doc_freq+1idf(t,d)=1+logdoc_count+1doc_freq+1 idf(t,d) = 1 + \log{\frac{doc\_count+1}{doc\_freq+1}}idf(t,d)=1+logdoc_freq+1doc_count+1,同时我们可以把Query也当作一篇文档。因此对于idf(t,q) idf(t,q)" role="presentation">idf(t,q)idf(t,q) idf(t,q)idf(t,q)有doc_countq=doc_countd+1;doc_freqq=doc_freqd+1 doc\_count_q=doc\_count_d + 1; doc\_freq_q = doc\_freq_d+1" role="presentation">doc_countq=doc_countd+1;doc_freqq=doc_freqd+1doc_countq=doc_countd+1;doc_freqq=doc_freqd+1 doc\_count_q=doc\_count_d + 1; doc\_freq_q = doc\_freq_d+1doc_countq=doc_countd+1;doc_freqq=doc_freqd+1(即直接把原文档总数以及对应的文档频率都加上1
)。整理可得
idf(t,q)=1+log1+(doc_count+1)1+(doc_freq+1) idf(t,q) = 1 + \log{\frac{1 + (doc\_count+1)}{1 + (doc\_freq+1)}}" role="presentation">idf(t,q)=1+log1+(doc_count+1)1+(doc_freq+1)idf(t,q)=1+log1+(doc_count+1)1+(doc_freq+1) idf(t,q) = 1 + \log{\frac{1 + (doc\_count+1)}{1 + (doc\_freq+1)}}idf(t,q)=1+log1+(doc_freq+1)1+(doc_count+1)
当文档数量比较大的时候,我们可以认为idf(t,q)=idf(t,d) idf(t,q) = idf(t,d)" role="presentation">idf(t,q)=idf(t,d)idf(t,q)=idf(t,d) idf(t,q) = idf(t,d)idf(t,q)=idf(t,d),因此上式可以进一步简化为
V(q)⋅V(d)=∑t in qtf(t,d)×idf2(t,q)) V(q)·V(d) = \sum_{t\ in\ q}{tf(t,d)\times idf^2(t,q))}" role="presentation">V(q)⋅V(d)=∑t in qtf(t,d)×idf2(t,q))V(q)⋅V(d)=∑t in qtf(t,d)×idf2(t,q)) V(q)·V(d) = \sum_{t\ in\ q}{tf(t,d)\times idf^2(t,q))}V(q)⋅V(d)=t in q∑tf(t,d)×idf2(t,q))
中间的计算过程不能理解也没关系,你只需要知道并记住最终结果就可以了。
b. 为什么|V(d)|不见了
先看一下Lucene Docs对∣V(d)∣ |V(d)|" role="presentation">∣V(d)∣∣V(d)∣ |V(d)|∣V(d)∣的解释吧。
Normalizing V(d) to the unit vector is known to be problematic in that it removes all document length information. For some documents removing this info is probably ok, e.g. a document made by duplicating a certain paragraph 10 times, especially if that paragraph is made of distinct terms. But for a document which contains no duplicated paragraphs, this might be wrong. To avoid this problem, a different document length normalization factor is used, which normalizes to a vector equal to or larger than the unit vector: doc-len-norm(d).
大意是说|V(d)|是一个单位向量,因此它并不带文档的长度信息。如果文档是由一段文本重复十次组成的,尤其这段文本由完全不一样词条组成时,文档长度信息对于这类文档可能并不会有影响。但是文档由多段不一样文本构成的,那么文档长度信息可能会有影响。为此,使用不同文档长度归一化因子,可实现一个向量大于或等于它的单位向量doc-len-norm(d)。
我猜你没看懂。简单说,如你所知文档长度信息是会影响相似度,即文档长度越大可能更有优势。为了解决这个问题我们引用**doc-len-norm(d)**来代替∣V(d)∣ |V(d)|" role="presentation">∣V(d)∣∣V(d)∣ |V(d)|∣V(d)∣。
到此为止,并没有解释∣V(d)∣ |V(d)|" role="presentation">∣V(d)∣∣V(d)∣ |V(d)|∣V(d)∣为什么可以不要,只是说明了doc-len-norm(d)的作用。
至于为什么不要∣V(d)∣ |V(d)|" role="presentation">∣V(d)∣∣V(d)∣ |V(d)|∣V(d)∣呢?虽然∣V(d)∣ |V(d)|" role="presentation">∣V(d)∣∣V(d)∣ |V(d)|∣V(d)∣是文档归一化因子,理论上它是可以评价文档的重要性的。但是它计算比较复杂(又有开文又有平方),关键还没什么用。下面是∣V(d)∣ |V(d)|" role="presentation">∣V(d)∣∣V(d)∣ |V(d)|∣V(d)∣的计算公式:
∣V(d)∣=∑t in q(tf(t,d)×idf(t,d))2 |V(d)| = \sqrt{\sum_{t\ in\ q}{(tf(t,d)\times idf(t,d))^2}}" role="presentation">∣V(d)∣=∑t in q(tf(t,d)×idf(t,d))2−−−−−−−−−−−−−−−−−−−−−√∣V(d)∣=∑t in q(tf(t,d)×idf(t,d))2 |V(d)| = \sqrt{\sum_{t\ in\ q}{(tf(t,d)\times idf(t,d))^2}}∣V(d)∣=t in q∑(tf(t,d)×idf(t,d))2
上式易知它的计算复杂同时并没有涉及文档的长度信息,因此Lucene选择另一种方式来表示,它能把文档的长度信息考虑进来,这就是我们前面提到doc-len-norm(d)。
c. 什么是 doc-len-norm(d)
故名思义,它是文档长度归一化因子,即是弱化文档长度对最终评分的影响。那我们怎么在搜索过程引用文档的长度对评分的影响呢? 首先lucene文档是可以有多个字段,而且搜索时被指定字段. 由此可以用搜索时指定搜索字段f f" role="presentation">ff ff的归一化因子来表示文档的归一化因子,所以就把问题转化算字段f f" role="presentation">ff ff的归一化因子的问题了。设norm(t,d) norm(t,d)" role="presentation">norm(t,d)norm(t,d) norm(t,d)norm(t,d)表示文档d的搜索字段f f" role="presentation">ff ff的归一化因子。
doc_len_norm(d)=∑t in qnorm(t,d) doc\_len\_norm(d) = \sum_{t\ in\ q}{norm(t,d)}" role="presentation">doc_len_norm(d)=∑t in qnorm(t,d)doc_len_norm(d)=∑t in qnorm(t,d) doc\_len\_norm(d) = \sum_{t\ in\ q}{norm(t,d)}doc_len_norm(d)=t in q∑norm(t,d)
用什么来表示norm(t,d) norm(t,d)" role="presentation">norm(t,d)norm(t,d) norm(t,d)norm(t,d)呢?Lucene用了下面的式子来表示:
norm(t,d)=lengthNorm∏f in df.getBoost() norm(t,d) = lengthNorm \prod_{f\ in\ d}{f.getBoost()}" role="presentation">norm(t,d)=lengthNorm∏f in df.getBoost()norm(t,d)=lengthNorm∏f in df.getBoost() norm(t,d) = lengthNorm \prod_{f\ in\ d}{f.getBoost()}norm(t,d)=lengthNormf in d∏f.getBoost()
lengthNorm=1length(f)−numOverlap(f) lengthNorm = \frac{1}{\sqrt{length(f) - numOverlap(f)}} " role="presentation">lengthNorm=1length(f)−numOverlap(f)√lengthNorm=1length(f)−numOverlap(f) lengthNorm = \frac{1}{\sqrt{length(f) - numOverlap(f)}} lengthNorm=length(f)−numOverlap(f)1
length : 字段的所有词条的数量
numOverlap : 字段的所有词条中出现过2次及以上词条的数量
lengthNorm是索引时计算好跟其它字段信息一样存储的,因此在搜索时它的“计算”是比较高效的。同时它并不是所有字段类型都会存储的,例如数值类型就不会计算和存储。当然对文本类型也可以关闭norm的计算,同时如果开启norm的计算的话,还能选择是否压缩。为了减小索引的体积,默认情况下是带用压缩的。
这里lengthNorm只是一般情况下的计算公式,并非所有情况都是这个公式
说到这里,我们顺便把norm(d)编码和解码两过程出现误差的情况解释一下吧。
其实前面已经解释过了,即是lucene为了减小索引的体积,对norm进行压缩。即是把原本应该用float表示norm数值,压缩成一个byte造成精度缺失,这就是decodeNormValue(encodeNormValue(norm))不一定等于norm的原因。
Lucene docs对LengthNorm的说明是这样的:
computed when the document is added to the index in accordance with the number of tokens of this field in the document, so that shorter fields contribute more to the score. LengthNorm is computed by the Similarity class in effect at indexing.
d. |V(q)|是什么呢
细心的你可能已经知道了|V(q)|是怎么回事,对的没错它就是queryNorm的别名。看一下QueryNorm的公式
queryNorm(q)=1q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())2 queryNorm(q)=\frac{1}{\sqrt{q.getBoost()^2 \times \sum_{t\ in\ q}{(idf(t,q)\times t.getBoost())}^2}}" role="presentation">queryNorm⎛⎝⎜q⎞⎠⎟=1q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())2√queryNorm(q)=1q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())2 queryNorm(q)=\frac{1}{\sqrt{q.getBoost()^2 \times \sum_{t\ in\ q}{(idf(t,q)\times t.getBoost())}^2}}queryNorm(q)=q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())21
我们先假定完全不设boost,即先假定boost=1
。因此原式可以变成queryNorm(q)=1∑t in qidf2(t,q) queryNorm(q)=\frac{1}{\sqrt{\sum_{t\ in\ q}{idf^2(t,q)}}}" role="presentation">queryNorm(q)=1∑t in qidf2(t,q)√queryNorm(q)=1∑t in qidf2(t,q) queryNorm(q)=\frac{1}{\sqrt{\sum_{t\ in\ q}{idf^2(t,q)}}}queryNorm(q)=∑t in qidf2(t,q)1。
再回顾一下∣V(q)∣ |V(q)|" role="presentation">∣V(q)∣∣V(q)∣ |V(q)|∣V(q)∣的公式,∣V(q)∣=1∑t in qidf2(t,q)×tf2(t,q) |V(q)|=\frac{1}{\sqrt{\sum_{t\ in\ q}{idf^2(t,q) \times tf^2(t,q)}}}" role="presentation">∣V(q)∣=1∑t in qidf2(t,q)×tf2(t,q)√∣V(q)∣=1∑t in qidf2(t,q)×tf2(t,q) |V(q)|=\frac{1}{\sqrt{\sum_{t\ in\ q}{idf^2(t,q) \times tf^2(t,q)}}}∣V(q)∣=∑t in qidf2(t,q)×tf2(t,q)1,因为一般情况下Query中每个词出现的频率都是1。因此原式可简写成∣V(q)∣=1∑t in qidf2(t,q) |V(q)|=\frac{1}{\sqrt{\sum_{t\ in\ q}{idf^2(t,q)}}}" role="presentation">∣V(q)∣=1∑t in qidf2(t,q)√∣V(q)∣=1∑t in qidf2(t,q) |V(q)|=\frac{1}{\sqrt{\sum_{t\ in\ q}{idf^2(t,q)}}}∣V(q)∣=∑t in qidf2(t,q)1,对于这点我们前面解释过了。然后再把各种boost考虑进去,你会发现∣V(q)∣ |V(q)|" role="presentation">∣V(q)∣∣V(q)∣ |V(q)|∣V(q)∣的式子跟queryNorm(q) queryNorm(q)" role="presentation">queryNorm(q)queryNorm(q) queryNorm(q)queryNorm(q)一模一样。
我们知道打分是对文档打分,因此是每个被检索到的文档都要套这个公式来计算它的分值。简而言之,评分公式是文档级别的。
- 1
它使得两个不同Queries的搜索结果的每个文档的得分可以比较,可以比较意思是它们的比较是有意义的。
QueryNorm不影响排名,也就是不影响评分,就是对于同一个Query来说queryNorm是唯一的。好吧,这说法并不准确,应该是对于同一个Index的同一个Query它的queryNorm是唯一的。如果不能理解就看公式吧,作为查询归一化,那么它作用就是缩小同一个Query在不同Index上产生的影响。这使得分布式查询(同一个Query用在不同的Index上并计算文档的分数)的评分变得有意义和有可比性。
不影响排名,也就是不影响评分,有两层意思:
- 在单个Index而queryNorm是没意义,不会影响评分和排序。
- 对于多个Index,queryNorm实际上是会影响的评分和排序。只是这影响是降低不同的index之间在搜索时的影响,因此可以认为没有影响。
2.2 coord因子
coord相关性,overlap与maxoverlap比值 coord(q,d)=overlapmaxOverlap coord(q,d) = \frac{overlap}{maxOverlap}" role="presentation">coord(q,d)=overlapmaxOverlapcoord(q,d)=overlapmaxOverlap coord(q,d) = \frac{overlap}{maxOverlap}coord(q,d)=maxOverlapoverlap
maxOverlap:是Query中有效的词条数
overlap:表示文档中出现词条数
2.3 boost的作用
boost的作用就是在索引时或搜索时,改变字段或者词条的重要,从而影响文档的最终得分。
- 两个时间点:搜索时和索引时,都能设置
- 四个级别:Query条件、文档、字段和词条
三、实践公式即将出现
我们先上面推导出来最终式子罗列一下:
V(q)⋅V(d)=∑t in qtf(t,d)×idf2(t,q)) V(q)·V(d) = \sum_{t\ in\ q}{tf(t,d)\times idf^2(t,q))}" role="presentation">V(q)⋅V(d)=∑t in qtf(t,d)×idf2(t,q))V(q)⋅V(d)=∑t in qtf(t,d)×idf2(t,q)) V(q)·V(d) = \sum_{t\ in\ q}{tf(t,d)\times idf^2(t,q))}V(q)⋅V(d)=∑t in qtf(t,d)×idf2(t,q))
doc_len_norm(d)=∑t in qnorm(t,d) doc\_len\_norm(d) = \sum_{t\ in\ q}{norm(t,d)}" role="presentation">doc_len_norm(d)=∑t in qnorm(t,d)doc_len_norm(d)=∑t in qnorm(t,d) doc\_len\_norm(d) = \sum_{t\ in\ q}{norm(t,d)}doc_len_norm(d)=∑t in qnorm(t,d)
∣V(q)∣=queryNorm(q)=1q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())2 |V(q)| = queryNorm(q) = \frac{1}{\sqrt{q.getBoost()^2 \times \sum_{t\ in\ q}{(idf(t,q)\times t.getBoost())}^2}}" role="presentation">∣V⎛⎝⎜q⎞⎠⎟∣=queryNorm⎛⎝⎜q⎞⎠⎟=1q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())2√∣V(q)∣=queryNorm(q)=1q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())2 |V(q)| = queryNorm(q) = \frac{1}{\sqrt{q.getBoost()^2 \times \sum_{t\ in\ q}{(idf(t,q)\times t.getBoost())}^2}}∣V(q)∣=queryNorm(q)=q.getBoost()2×∑t in q(idf(t,q)×t.getBoost())21
把上面三个式子代入下式
score(q,d)=coord(q,d)×queryNorm(q)×∑t in q(idf2(t,q)×tf(t,d))×doc_len_norm(d) score(q,d) = coord(q,d)\times queryNorm(q) \times \sum_{t\ in\ q}{(idf^2(t,q)\times tf(t,d))} \times doc\_len\_norm(d) " role="presentation">score(q,d)=coord(q,d)×queryNorm(q)×∑t in q(idf2(t,q)×tf(t,d))×doc_len_norm(d)score(q,d)=coord(q,d)×queryNorm(q)×∑t in q(idf2(t,q)×tf(t,d))×doc_len_norm(d) score(q,d) = coord(q,d)\times queryNorm(q) \times \sum_{t\ in\ q}{(idf^2(t,q)\times tf(t,d))} \times doc\_len\_norm(d) score(q,d)=coord(q,d)×queryNorm(q)×t in q∑(idf2(t,q)×tf(t,d))×doc_len_norm(d)
整理可得
=coord(q,d)×queryNorm(q)×∑t in q(idf2(t,q)×tf(t,d)×norm(t,d)) = coord(q,d) \times queryNorm(q) \times \sum_{t\ in\ q}{(idf^2(t,q)\times tf(t,d) \times norm(t,d))}" role="presentation">=coord(q,d)×queryNorm(q)×∑t in q(idf2(t,q)×tf(t,d)×norm(t,d))=coord(q,d)×queryNorm(q)×∑t in q(idf2(t,q)×tf(t,d)×norm(t,d)) = coord(q,d) \times queryNorm(q) \times \sum_{t\ in\ q}{(idf^2(t,q)\times tf(t,d) \times norm(t,d))}=coord(q,d)×queryNorm(q)×t in q∑(idf2(t,q)×tf(t,d)×norm(t,d))
四、结语
之前在学习lucene的时候就想写这么一篇博客,但是一直没能实现。如今也是能花好几个小时才整理出来,当第一次把所有内容都罗列出来的时候,篇幅是那么那么的长。经过几番裁剪才如今这个样子,肯定是有一些地方没说明白的,若问题可以一起论,这么复杂的博文难免会出错误,若有发现错误望不吝指教。
</div>
Lucene TFIDFSimilarity评分公式详解的更多相关文章
- ElasticSearch7.3学习(二十四)----相关度评分机制详解
1.算法介绍 relevance score(相关性分数) 算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度.Elasticsearch使用的是 term freque ...
- Lucene的Vint类型详解
Lucene Vint压缩策略是,用每个字节的最高位做标志位,后7位为有效算术位,如果标志位为1,则说明后一个字节和当前字节是同一个数字,为0说明后一个字节是一个新的数字 Lucene源代码中进行存储 ...
- 贝塞尔曲线 WPF MVVM N阶实现 公式详解+源代码下载
源代码下载 效果图: 本程序主要实现: N阶贝塞尔曲线(通用公式) 本程序主要使用技术 MVVM InterAction 事件绑定 动态添加Canvas的Item 第一部分公式: n=有效坐标点数量 ...
- lucene 的评分机制
lucene 的评分机制 elasticsearch是基于lucene的,所以他的评分机制也是基于lucene的.评分就是我们搜索的短语和索引中每篇文档的相关度打分. 如果没有干预评分算法的时候,每次 ...
- Lucene Scoring 评分机制
原文出处:http://blog.chenlb.com/2009/08/lucene-scoring-architecture.html Lucene 评分体系/机制(lucene scoring)是 ...
- Lucene打分规则与Similarity模块详解
搜索排序结果的控制 Lucnen作为搜索引擎中,应用最为广泛和成功的开源框架,它对搜索结果的排序,有一套十分完整的机制来控制:但我们控制搜索结果排序的目的永远只有一个,那就是信息过滤,让用户快速,准确 ...
- Lucene系列六:Lucene搜索详解(Lucene搜索流程详解、搜索核心API详解、基本查询详解、QueryParser详解)
一.搜索流程详解 1. 先看一下Lucene的架构图 由图可知搜索的过程如下: 用户输入搜索的关键字.对关键字进行分词.根据分词结果去索引库里面找到对应的文章id.根据文章id找到对应的文章 2. L ...
- lucene、lucene.NET详细使用与优化详解
lucene.lucene.NET详细使用与优化详解 2010-02-01 13:51:11 分类: Linux 1 lucene简介1.1 什么是luceneLucene是一个全文搜索框架,而不是应 ...
- Lucene系列五:Lucene索引详解(IndexWriter详解、Document详解、索引更新)
一.IndexWriter详解 问题1:索引创建过程完成什么事? 分词.存储到反向索引中 1. 回顾Lucene架构图: 介绍我们编写的应用程序要完成数据的收集,再将数据以document的形式用lu ...
随机推荐
- mac 堡垒机传文件
安装zssh brew install zssh 上传文件 zssh登陆上跳板机 在跳板机上ssh到相应服务器 在服务器上cd至相应要放上传文件的目录 rz -bye //在远程服务器的相应目录上运行 ...
- Java-Class-C:com.github.pagehelper.PageHelper
ylbtech-Java-Class-C:com.github.pagehelper.PageHelper 1.返回顶部 2.返回顶部 1.1. import com.github.pagehel ...
- *&m与m的区别
int *a;int * &p=a; 把 int * 看成一个类型,a就是一个整型指针,p 是a的别名 #include<iostream> using namespace std ...
- mac 安装brew mac安装expect mac一键登录服务器脚本
mac 安装brew /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/ma ...
- 由Resin引发的java.lang.IllegalArgumentException: object is not an instance of declaring class(反射中使用)思考
文章目录 背景 原因 解决办法 背景 在java agent中抓取Resin的 某些方法,在invoke的时候出现错误 java.lang.IllegalArgumentException: obje ...
- 静态成员 static 能被继承吗
在类定义中,它的成员(包括数据成员和 成员函数)可以用关键字static声明为静 态的,这些成员称为静态成员 静态成员的特性: • 不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于 ...
- 剑指offer——43数据流中的中位数
题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们 ...
- 线程创建后为什么要调用CloseHandle
很多程序在创建线程都这样写的: ............ ThreadHandle = CreateThread(NULL,0,.....); CloseHandel(ThreadHandle ); ...
- web前端Vue+Django rest framework 框架 生鲜电商项目实战✍✍✍
web前端Vue+Django rest framework 框架 生鲜电商项目实战 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频 ...
- Windows平台将远程服务器的目录挂载为本地磁盘
我们在设置数据库自动备份时,为了数据的安全往往需要直接将数据备份到远程服务器上.在Linux可以通过NFS挂载来实现,在Windows平台可以直接通过net use+subst来实现将远程服务器的目录 ...