源码透视 elasticsearch 衰减函数EXP 计算过程

版本声明

以下内容都是基于elasticsearch-6.3.2.jar

一、kibana开启调试

“explain”: true 会打印分数计算过程

{
    
    
                         "value": 2.0702924e-8,
                         "description": "Function for field createdAt:",
                         "details": [
                           {
    
    
                             "value": 2.0702924e-8,
                             "description": "exp(- MAX[Math.max(Math.abs(1.582024989E12(=doc value) - 1.5842304E12(=origin))) - 0.0(=offset), 0)] * 8.022536812036404E-9)",
                             "details": []
                           }
                         ]
                       }

以上结果片段是衰减函数exp的计算日志,由details-》description 大概是可以看出exp函数的公式, 但是有一个陌生的数字 8.022536812036404E-9 这是什么?怎么来了?日志中无法看到它的来路,只能在源码中一探究竟。

二、源码透视

经过一系列的代码定位 搜索 终于找到了打印此处日志的代码块
org.elasticsearch.index.query.functionscore.ExponentialDecayFunctionBuilder.explainFunction:72

public Explanation explainFunction(String valueExpl, double value, double scale) {
    
    
           return Explanation.match(
                   (float) evaluate(value, scale),
                   "exp(- " + valueExpl + " * " + -1 * scale + ")");
       }

继续深入
org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:558

 public Explanation explainScore(int docId, Explanation subQueryScore) throws IOException {
    
    
                   if (distance.advanceExact(docId) == false) {
    
    
                       return Explanation.noMatch("No value for the distance");
                   }
                   double value = distance.doubleValue();
                   return Explanation.match(
                           (float) score(docId, subQueryScore.getValue()),
                           "Function for field " + getFieldName() + ":",
                           func.explainFunction(getDistanceString(ctx, docId), value, scale));
               }

func.explainFunction 看下func的面目
org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:506

private final DecayFunction func;
public interface DecayFunction {
    
    

   double evaluate(double value, double scale);

   Explanation explainFunction(String valueString, double value, double scale);

/**
    * The final scale parameter is computed from the scale parameter given by
    * the user and a value. This value is the value that the decay function
    * should compute if document distance and user defined scale equal. The
    * scale parameter for the function must be adjusted accordingly in this
    * function
    * 
    * @param scale
    *            the raw scale value given by the user
    * @param decay
    *            the value which decay function should take once the distance
    *            reaches this scale
    * */
   double processScale(double scale, double decay);
}

实现类有三个ExponentialDecayScoreFunction、GaussScoreFunction、LinearDecayScoreFunction 刚好对应了es的三种衰减函数。

注意一下processScale这个方法,看注释说 最终比例参数 是此方法计算出来的,看一下exp是怎么实现的。
org.elasticsearch.index.query.functionscore.ExponentialDecayFunctionBuilder:79

 public double processScale(double scale, double decay) {
    
    
           return Math.log(decay) / scale;
       }

看到此处就可以明白 8.022536812036404E-9是怎么来的了。

回到org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:558

public Explanation explainScore(int docId, Explanation subQueryScore) throws IOException {
    
    
                   if (distance.advanceExact(docId) == false) {
    
    
                       return Explanation.noMatch("No value for the distance");
                   }
                   double value = distance.doubleValue();
                   return Explanation.match(
                           (float) score(docId, subQueryScore.getValue()),
                           "Function for field " + getFieldName() + ":",
                           func.explainFunction(getDistanceString(ctx, docId), value, scale));
               }

此处调用了explainFunction方法 前面已经说过此方法用于打印调试日志。进入getDistanceString 方法看一下,此方法是一个抽象方法,实现类有有两个,用于数值衰减 和 地理位置衰减。

 protected String getDistanceString(LeafReaderContext ctx, int docId) throws IOException {

           StringBuilder values = new StringBuilder(mode.name());
           values.append("[");
           final SortedNumericDoubleValues doubleValues = fieldData.load(ctx).getDoubleValues();
           if (doubleValues.advanceExact(docId)) {
               final int num = doubleValues.docValueCount();
               for (int i = 0; i < num; i++) {
                   double value = doubleValues.nextValue();
                   values.append("Math.max(Math.abs(");
                   values.append(value).append("(=doc value) - ");
                   values.append(origin).append("(=origin))) - ");
                   values.append(offset).append("(=offset), 0)");
                   if (i != num - 1) {
                       values.append(", ");
                   }
               }
           } else {
               values.append("0.0");
           }
           values.append("]");
           return values.toString();

       }

正是调试日志。
再看func.explainFunction(getDistanceString(ctx, docId), value, scale));
value参数是double value = distance.doubleValue()得到的,继续深入,可以看到
final NumericDoubleValues distance = distance(ctx); 537行
进入distance方法

 protected NumericDoubleValues distance(LeafReaderContext context) {
    
    
           final SortedNumericDoubleValues doubleValues = fieldData.load(context).getDoubleValues();
           return mode.select(new SortingNumericDoubleValues() {
    
    
               @Override
               public boolean advanceExact(int docId) throws IOException {
    
    
                   if (doubleValues.advanceExact(docId)) {
    
    
                       int n = doubleValues.docValueCount();
                       resize(n);
                       for (int i = 0; i < n; i++) {
    
    
                           values[i] = Math.max(0.0d, Math.abs(doubleValues.nextValue() - origin) - offset);
                       }
                       sort();
                       return true;
                   } else {
    
    
                       return false;
                   }
               }
           }, 0.0);
       }

446行
Math.max(0.0d, Math.abs(doubleValues.nextValue() - origin) - offset);
再看func.explainFunction(getDistanceString(ctx, docId), value, scale));
看下scale参数的值
org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:519行
this.scale = func.processScale(userSuppiedScale, decay);
processScale这个方法和我们前面猜想的一样 就是根据设置的scale 和 decay计算出一个比例
返回主线 继续看 explainScore 方法 556行
(float) score(docId, subQueryScore.getValue())
这里就是计算最终分数的了,点进去看下

public double score(int docId, float subQueryScore) throws IOException {
    
    
                   if (distance.advanceExact(docId)) {
    
    
                       return func.evaluate(distance.doubleValue(), scale);
                   } else {
    
    
                       return 0;
                   }
               }

evaluate 此方法正是前面说的接口中三个方法的其中之一,看下exp如何实现的

public double evaluate(double value, double scale) {
    
    
           return Math.exp(scale * value);
       }

此刻真相大白

EXP函数公式

value=Math.max(0.0d, Math.abs(doubleValues.nextValue() - origin) - offset);
processScale= Math.log(decay) / scale;
最终:
sorce=Math.exp(processScale* value)

  • doubleValues.nextValue() 此值为目标字段的值

附exp配置示例

衰减函数配置

"exp": {
                 "createdAt": {
                   "origin": "2020-03-15 00:00:00",
                   "scale": "1d",
                   "offset": "0d", 
                   "decay": 0.5
                 },
                 "multi_value_mode": "MAX"
               }

猜你喜欢

转载自blog.csdn.net/sinat_25926481/article/details/104379196
今日推荐