0

我們試圖複製此ES插件。原因是AWS ES不允許安裝任何自定義插件。該插件只是做點積和餘弦相似性,所以我猜測它應該非常簡單,在painless腳本中複製它。它看起來像groovy腳本在5.0中已棄用。如何在無痛腳本中執行此操作Elasticsearch 5.3

這裏是插件的源代碼。

/** 
    * @param params index that a scored are placed in this parameter. Initialize them here. 
    */ 
    @SuppressWarnings("unchecked") 
    private PayloadVectorScoreScript(Map<String, Object> params) { 
     params.entrySet(); 
     // get field to score 
     field = (String) params.get("field"); 
     // get query vector 
     vector = (List<Double>) params.get("vector"); 
     // cosine flag 
     Object cosineParam = params.get("cosine"); 
     if (cosineParam != null) { 
      cosine = (boolean) cosineParam; 
     } 
     if (field == null || vector == null) { 
      throw new IllegalArgumentException("cannot initialize " + SCRIPT_NAME + ": field or vector parameter missing!"); 
     } 
     // init index 
     index = new ArrayList<>(vector.size()); 
     for (int i = 0; i < vector.size(); i++) { 
      index.add(String.valueOf(i)); 
     } 
     if (vector.size() != index.size()) { 
      throw new IllegalArgumentException("cannot initialize " + SCRIPT_NAME + ": index and vector array must have same length!"); 
     } 
     if (cosine) { 
      // compute query vector norm once 
      for (double v: vector) { 
       queryVectorNorm += Math.pow(v, 2.0); 
      } 
     } 
    } 

    @Override 
    public Object run() { 
     float score = 0; 
     // first, get the ShardTerms object for the field. 
     IndexField indexField = this.indexLookup().get(field); 
     double docVectorNorm = 0.0f; 
     for (int i = 0; i < index.size(); i++) { 
      // get the vector value stored in the term payload 
      IndexFieldTerm indexTermField = indexField.get(index.get(i), IndexLookup.FLAG_PAYLOADS); 
      float payload = 0f; 
      if (indexTermField != null) { 
       Iterator<TermPosition> iter = indexTermField.iterator(); 
       if (iter.hasNext()) { 
        payload = iter.next().payloadAsFloat(0f); 
        if (cosine) { 
         // doc vector norm 
         docVectorNorm += Math.pow(payload, 2.0); 
        } 
       } 
      } 
      // dot product 
      score += payload * vector.get(i); 
     } 
     if (cosine) { 
      // cosine similarity score 
      if (docVectorNorm == 0 || queryVectorNorm == 0) return 0f; 
      return score/(Math.sqrt(docVectorNorm) * Math.sqrt(queryVectorNorm)); 
     } else { 
      // dot product score 
      return score; 
     } 
    } 

我想從索引中獲得一個字段開始。但我得到錯誤。

這裏是我的索引的形狀。

我已經啓用delimited_payload_filter

"settings" : { 
    "analysis": { 
      "analyzer": { 
       "payload_analyzer": { 
        "type": "custom", 
        "tokenizer":"whitespace", 
        "filter":"delimited_payload_filter" 
       } 
     } 
    } 
} 

而且我有一個字段名爲@model_factor存儲載體。

{ 
    "movies" : { 
     "properties" : { 
      "@model_factor": { 
          "type": "text", 
          "term_vector": "with_positions_offsets_payloads", 
          "analyzer" : "payload_analyzer" 
        } 
     } 
    } 
} 

這是文檔

{ 
    "@model_factor":"0|1.2 1|0.1 2|0.4 3|-0.2 4|0.3", 
    "name": "Test 1" 
} 

的形狀,下面是我如何使用腳本

{ 
    "query": { 
     "function_score": { 
      "query" : { 
       "query_string": { 
        "query": "*" 
       } 
      }, 
      "script_score": { 
       "script": { 
        "inline": "def termInfo = doc['_index']['@model_factor'].get('1', 4);", 
        "lang": "painless", 
        "params": { 
         "field": "@model_factor", 
         "vector": [0.1,2.3,-1.6,0.7,-1.3], 
         "cosine" : true 
        } 
       } 
      }, 
      "boost_mode": "replace" 
     } 
    } 
} 

這是我的錯誤。

"failures": [ 
     { 
     "shard": 2, 
     "index": "test", 
     "node": "ShL2G7B_Q_CMII5OvuFJNQ", 
     "reason": { 
      "type": "script_exception", 
      "reason": "runtime error", 
      "caused_by": { 
      "type": "wrong_method_type_exception", 
      "reason": "wrong_method_type_exception: cannot convert MethodHandle(List,int)int to (Object,String)String" 
      }, 
      "script_stack": [ 
      "termInfo = doc['_index']['@model_factor'].get('1',4);", 
      "    ^---- HERE" 
      ], 
      "script": "def termInfo = doc['_index']['@model_factor'].get('1',4);", 
      "lang": "painless" 
     } 
     } 
    ] 

問題是如何訪問索引字段以獲得@model_factor無痛腳本?

回答

3

選項1

由於這樣的事實,@model_factor是text字段,在無痛的腳本,這將是可以訪問它,設定fielddata在映射=真。因此,映射應該是:

{ 
    "movies" : { 
     "properties" : { 
      "@model_factor": { 
          "type": "text", 
          "term_vector": "with_positions_offsets_payloads", 
          "analyzer" : "payload_analyzer", 
          "fielddata" : true 
        } 
     } 
    } 
} 

然後它可以砍下accessing doc-values:與期權

{ 
    "query": { 
     "function_score": { 
      "query" : { 
       "query_string": { 
        "query": "*" 
       } 
      }, 
      "script_score": { 
       "script": { 
        "inline": "return Double.parseDouble(doc['@model_factor'].get(1)) * params.vector[1];", 
        "lang": "painless", 
        "params": { 
         "vector": [0.1,2.3,-1.6,0.7,-1.3] 
        } 
       } 
      }, 
      "boost_mode": "replace" 
     } 
    } 
} 

問題1

那麼就可以訪問該字段的數據值設置fielddata=true ,但是在這種情況下,該值是作爲項的向量索引,而不是存儲在有效載荷中的向量的值。不幸的是,它似乎沒有辦法使用無痛腳本和文檔值來訪問令牌有效載荷(真正的向量索引值存儲在哪裏)。請參閱source code for elasticsearch和另一個類似的question re: accessing term info

所以答案是使用無痛腳本不可能訪問有效載荷。

我試着用一個簡單的模式標記器來存儲矢量值,但是當訪問矢量數值時,順序不會被保留下來,這可能是插件作者決定使用該術語作爲然後檢索the position 0 of the vector as the term "0",然後在有效負載中查找真實向量值。

選項2

一個非常簡單的替代方法是在文件中使用的n個域,其中每一個代表在載體中的位置,所以在你的例子,我們有5暗淡矢量與存儲在值V0 ... V4直接作爲雙:

{ 
    "@model_factor":"0|1.2 1|0.1 2|0.4 3|-0.2 4|0.3", 
    "name": "Test 1", 
    "v0" : 1.2, 
    "v1" : 0.1, 
    "v2" : 0.4, 
    "v3" : -0.2, 
    "v4" : 0.3 
} 

,然後無痛腳本應該是:

{ 
    "query": { 
     "function_score": { 
      "query" : { 
       "query_string": { 
        "query": "*" 
       } 
      }, 
      "script_score": { 
       "script": { 
        "inline": "return doc['v0'].getValue() * params.vector[0];", 
        "lang": "painless", 
        "params": { 
         "vector": [0.1,2.3,-1.6,0.7,-1.3] 
        } 
       } 
      }, 
      "boost_mode": "replace" 
     } 
    } 
} 

它應該很容易迭代輸入矢量長度並動態獲取字段來計算爲了簡單起見我編寫的點積修改doc['v0'].getValue() * params.vector[0]。只要矢量尺寸仍然不大

與1選項

選項2的問題是可行的。我認爲每個文檔領域的該默認Elasticsearch最大數量爲1000,但it can be changed也AWS環境:

curl -X PUT \ 
    'https://.../indexName/_settings' \ 
    -H 'cache-control: no-cache' \ 
    -H 'content-type: application/json' 
    -d '{ 
"index.mapping.total_fields.limit": 2000 
}' 

此外,還應該測試腳本的速度上有大量的文檔。 也許在重新評分/重新排名的情況下,這是一個可行的解決方案。

選項3

第三個選項是一個真正的實驗,在我看來是最迷人的。 它嘗試利用向量空間模型的內部Elasticsearch表示,並且不使用任何腳本進行評分,但重新使用基於tf/idf的默認相似性分數。

Lucene的,即在Elasticsearch芯座椅,已在使用內部餘弦相似性的修改calculate the similarity score而言,如下式,從TFIDFSImilarity javadoc採取的他的向量空間模型表示的文檔之間,示出了:

enter image description here

特別地,表示該字段的向量的權重是該字段的項的tf/idf值。

所以我們可以使用termvectors來索引一個文檔,使用term作爲矢量的索引。如果我們重複N次,我們代表矢量的值,利用得分公式的tf部分。 這意味着矢量的域應該在{1 ..無限}正整數域中進行轉換和重新縮放。我們從1開始,以便我們確信所有文檔都包含所有條款,這將使公式更易於使用。

例如,向量:[21,54,45]可以被索引爲使用簡單的空白分析儀在文檔中的字段和以下值:

{ 
    "@model_factor" : "0<repeated 21 times> 1<repeated 54 times> 2<repeated 45 times>", 
    "name": "Test 1" 
} 

然後進行查詢,即計算點積,我們提高表示矢量索引位置的單個項。

因此,使用輸入矢量上述相同的例子:[45,1,1]將在查詢轉化:

"should": [ 
     { 
      "term": { 
      "@model_factor": { 
       "value": "0", 
       "boost": 45 
      } 
      } 
     }, 
     { 
      "term": { 
      "@model_factor": "1" // boost:1 by default 

      } 
     }, 
     { 
      "term": { 
      "@model_factor": "2" // boost:1 by default 
      } 
     } 
     ] 

範數(T,d)disabled in the mapping以便它不在上面的公式中使用。 idf部分對於所有文檔都是不變的,因爲它們都包含所有術語(所有的矢量具有相同的維度)。

queryNorm(q)中爲上式中的所有文件一樣的,所以它不是一個問題。

coord(q,d)是一個常量,因爲所有文檔都包含所有的術語。

與選項3

問題需要進行測試。

它只適用於正數矢量,請參閱math stackoverflow中的這個問題,以使它對負數也有效。

它不是完全相同的點積,但非常接近找到基於原始矢量的類似文件。

大型矢量維度上的可伸縮性可能是查詢時間的問題,因爲這意味着我們需要使用不同的提升來執行N個暗淡術語查詢。

我會在測試索引中嘗試它,並用結果編輯此問題。