最近业务上遇到重新排序制定打分策略需求,参考这篇文档,对es打分策略有所了解
在进行搜索时,对于召回的排序方式一般是两种方式:不指定sort按照相关度以及其他因素综合得到的分值排序;另外一种是完全按照指定的sort(可以使多个field,和顺序有关),此时分数是0,即没有相关性的概念。指定字段排序比较简单,按照分值排序就涉及到一些打分策略和二次评分的方式。ES采用的是lucene的打分算法(es 5.0之前默认是TF/IDF, 5.0之后 采用的是lucene6 默认是bm2.5)。
布尔模型(Boolean model)
- "match": {
- "title": "hello world"
- }
TF/IDF/lnorm
经过布尔模型的过滤、重组就会得到一堆的term,同时也会得到term和doc之间的关系。
TF:一个term在一个doc中出现的次数(出现的次数越多,那么最后给的相关度评分就会越高)
IDF:一个term在所有的doc中出现的次数(出现的次数越多,那么最后给的相关度评分就会越低)
length norm:关键词搜索的那个field的长度(field长度越长,给的相关度评分越低; field长度越短,给的相关度评分越高)
其实lucene的打分过程就是将搜索的关键词的某个term,综合TF,IDF,length norm以及设置的权重对某个文档综合计算出来一个分数。但是往往一个关键词并不是一个term,而是多个term比如hello word会是hell/word/hello word。所以就需要综合计算多个term对一个文档的总分数来作为这个文档对这个关键词的最终得分。
空间向量模型
索引向量(query vector):这个query同样被当成一个文档来看待,同样可以在空间中找到某个term维上的坐标,根据坐标可以构成一个空间向量。
query对doc的得分计算:通过计算doc vector和query vector的夹角的cose值作为最终的分数。弧度越大,分数月底; 弧度越小,分数越高。
![](/qrcode.jpg)
加上一些文档的权重、field的权重组成一个公式:
总结打分过程
1、先根据query的语法判断是否对keyword进行分词。
2、得出keyword中包含的term。
3、根据term去倒排表中找到多个doc
4、根据query的语法,通过boolean模型得到最终的文档集。
5、根据TF/RDF算法模型计算每个term对文档、query的分值,得出文档、query在某 一维的坐标。
6、根据在维度上的坐标画出文档、query的空间向量。
7、计算文档以及query空间向量的夹角余弦值,在计算的过程中加入boost,得到最终的得分score。
打分公式
- public float coord(int overlap, int maxOverlap) {
- return overlap / (float)maxOverlap;
- }
overlap:匹配上的term数量。
maxOverlap:此文档总term数量。
加入不加cooord的时候搜索 hello word java的每个文档的得分是:
doc1->1.5
doc2->3
doc3->4.5
加上之后会是
doc1->1.5 * 1/3=0.5
doc2->3*2/3=2.0
doc3->4.5*3/4=4.5
所以对命中比较好的doc有一些分数上的成倍奖励。
2、queryNorm(q)
- @Override
- public float queryNorm(float sumOfSquaredWeights) {
- return (float)(1.0 / Math.sqrt(sumOfSquaredWeights));
- }
- sumOfSquaredWeights = q.getBoost()^2·∑( idf(t)·t.getBoost() )^2
3、tf(t ind d)
- @Override
- public float tf(float freq) {
- return (float)Math.sqrt(freq);
- }
4、idf(t)存在的意义是告诉打分公式一个词的稀有程度
- @Override
- public float idf(long docFreq, long numDocs) {
- return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0);
- }
docFreq:有几个文档除了这个词
docFreq+1是因为docFreq可能会是0。1.0同理。
5、t.getBoost
在执行query的时候为每个term设定的权重。term的权重在这起作用。
6、norm(t,d)
标准化因子,匹配到的field的长度,长度越长分数越低。索引时就确定了,更改的话需要重建索引。
norm(t,d) = doc.getBoost()· lengthNorm· ∏ f.getBoost()
doc.getBoost():文档的权重5.0之后取消
lengthNorm:
- @Override
- public float lengthNorm(FieldInvertState state) {//FieldInvertState 存储和统计当前field的所有term出现的位置position和offset信息
- final int numTerms;
- if (discountOverlaps)//同义词相关,如果是同义词会把同义词去掉留一个。
- numTerms = state.getLength() - state.getNumOverlap();
- else
- numTerms = state.getLength();
- return state.getBoost() * ((float) (1.0 / Math.sqrt(numTerms)));//看代码state.getBoost()是1.0f
- }
综上可以看出每个环节分值确定的时机是:
查询时可以确定的是在:1/2/3/4/5
索引时确定的是6。