ElasticSearch Painless:在 for 循环错误中使用向量函数
ElasticSearch Painless: using vector functions in for loops bug
我 运行 研究了 Painless 中的一个错误,如果使用向量函数,比如 l2norm(),结果与第一次迭代的结果相同。我在函数评分中使用无痛脚本,我希望下面的查询能提供一些启发。我正在使用“异常”来查看每次迭代中的值是什么,每次都是第一个向量的分数。我知道这一点是因为我循环了几次参数,而分数每次都“卡在”第一件事上。所以我认为正在发生的是函数 l2norm() (以及所有向量函数?!)是只能实例化一次的对象实例?如果是这样,有什么解决方法?
Link 到 ES 讨论:https://discuss.elastic.co/t/painless-bug-using-for-loops-and-vector-functions/267263
{
"query": {
"nested": {
"path": "media",
"query": {
"function_score": {
"boost_mode": "replace",
"query": {
"bool": {
"filter": [{
"exists": {
"field": "media.full_body_dense_vector"
}
}]
}
},
"functions": [{
"script_score": {
"script": {
"source": "if (params.filterVectors.size() > 0 && params.filterCutOffScore >= 0) {\n for (int i=0; i < params.filterVectors.size();i++) {\n def c = params.filterVectors[i]; double euDistance = l2norm(c, doc['media.full_body_dense_vector']);\n if (i==1) { throw new Exception(euDistance + ''); } \n }\n return 1.0f;",
"params": {
"filterVectors":[
[1.0,2.0,3.0],[0.1,0.4,0.5]
],
"filterCutOffScore": 1.04
},
"lang": "painless"
}
}
}]
}
}
}
},
"size": 500,
"from": 0,
"track_scores": true
}
虽然 l2norm
是一个 static method,但它的行为当然不应该像一个纯函数!
我调查了一下,似乎只有一个循环级别的错误。当您使用参数化向量或硬编码向量在循环外调用 l2norm
时,结果将始终不同——它们本应如此。但不在 for 循环内(我也测试了 while
循环——同样的结果)。这是可用于 report a bug on github:
的最小可重现示例
"script": {
"source": """
def field = doc['media.full_body_dense_vector'];
def hardcodedVectors = [ [1,2,3], [0.1,0.4,0.5] ];
def noLoopDistances = [
l2norm(hardcodedVectors[0], field),
l2norm(hardcodedVectors[1], field)
];
def hardcodedDistances = [];
for (vector in hardcodedVectors) {
double euDistance = l2norm(vector, field);
hardcodedDistances.add(euDistance);
}
def parametrizedDistances = [];
for (vector in params.filterVectors) {
double euDistance = l2norm(vector, field);
parametrizedDistances.add(euDistance);
}
def comparisonMap = [
"no-loop": noLoopDistances,
"hardcoded": hardcodedDistances,
"parametrized": parametrizedDistances
];
Debug.explain(comparisonMap);
""",
"params": {
"filterVectors": [ [1,2,3], [0.1,0.4,0.5] ]
},
"lang": "painless"
}
产生
{
"no-loop":[
8.558621384311845, // <-- the only time two different l2norm calls behave correctly
11.071133967619906
],
"parametrized":[
8.558621384311845,
8.558621384311845
],
"hardcoded":[
8.558621384311845,
8.558621384311845
]
}
这告诉我,这不是 runtime caching 的问题,而是 Elastic 团队应该进一步调查的其他问题。
解决方法,目前,将继续使用参数化向量而不是循环执行类似石器时代的检查 :
if (params.filterVectors.length == 0) {
// default to something
} else if (params.filterVectors.length == 1) {
// call l2norm once
} else if (params.filterVectors.length == 2) {
// call l2norm twice, separately
}
P.S。抛出一个 new Exception()
来调试 Painless 就可以了。由于 this sub-chapter on Debugging of my Elasticsearch Handbook.
中解释的原因,使用 Debug.explain
甚至更好
首先,感谢 Joe 确认我不是凭空想象,这确实是一个错误。其次,可爱的 ElasticSearch 团队一直在对问题进行分类并确认它是一个错误,所以这个 post 的答案是 a link to the Github Issue 所以在未来,人们可以跟踪这个行为是在哪个 ElasticSearch 版本中修补的。
我 运行 研究了 Painless 中的一个错误,如果使用向量函数,比如 l2norm(),结果与第一次迭代的结果相同。我在函数评分中使用无痛脚本,我希望下面的查询能提供一些启发。我正在使用“异常”来查看每次迭代中的值是什么,每次都是第一个向量的分数。我知道这一点是因为我循环了几次参数,而分数每次都“卡在”第一件事上。所以我认为正在发生的是函数 l2norm() (以及所有向量函数?!)是只能实例化一次的对象实例?如果是这样,有什么解决方法?
Link 到 ES 讨论:https://discuss.elastic.co/t/painless-bug-using-for-loops-and-vector-functions/267263
{
"query": {
"nested": {
"path": "media",
"query": {
"function_score": {
"boost_mode": "replace",
"query": {
"bool": {
"filter": [{
"exists": {
"field": "media.full_body_dense_vector"
}
}]
}
},
"functions": [{
"script_score": {
"script": {
"source": "if (params.filterVectors.size() > 0 && params.filterCutOffScore >= 0) {\n for (int i=0; i < params.filterVectors.size();i++) {\n def c = params.filterVectors[i]; double euDistance = l2norm(c, doc['media.full_body_dense_vector']);\n if (i==1) { throw new Exception(euDistance + ''); } \n }\n return 1.0f;",
"params": {
"filterVectors":[
[1.0,2.0,3.0],[0.1,0.4,0.5]
],
"filterCutOffScore": 1.04
},
"lang": "painless"
}
}
}]
}
}
}
},
"size": 500,
"from": 0,
"track_scores": true
}
虽然 l2norm
是一个 static method,但它的行为当然不应该像一个纯函数!
我调查了一下,似乎只有一个循环级别的错误。当您使用参数化向量或硬编码向量在循环外调用 l2norm
时,结果将始终不同——它们本应如此。但不在 for 循环内(我也测试了 while
循环——同样的结果)。这是可用于 report a bug on github:
"script": {
"source": """
def field = doc['media.full_body_dense_vector'];
def hardcodedVectors = [ [1,2,3], [0.1,0.4,0.5] ];
def noLoopDistances = [
l2norm(hardcodedVectors[0], field),
l2norm(hardcodedVectors[1], field)
];
def hardcodedDistances = [];
for (vector in hardcodedVectors) {
double euDistance = l2norm(vector, field);
hardcodedDistances.add(euDistance);
}
def parametrizedDistances = [];
for (vector in params.filterVectors) {
double euDistance = l2norm(vector, field);
parametrizedDistances.add(euDistance);
}
def comparisonMap = [
"no-loop": noLoopDistances,
"hardcoded": hardcodedDistances,
"parametrized": parametrizedDistances
];
Debug.explain(comparisonMap);
""",
"params": {
"filterVectors": [ [1,2,3], [0.1,0.4,0.5] ]
},
"lang": "painless"
}
产生
{
"no-loop":[
8.558621384311845, // <-- the only time two different l2norm calls behave correctly
11.071133967619906
],
"parametrized":[
8.558621384311845,
8.558621384311845
],
"hardcoded":[
8.558621384311845,
8.558621384311845
]
}
这告诉我,这不是 runtime caching 的问题,而是 Elastic 团队应该进一步调查的其他问题。
解决方法,目前,将继续使用参数化向量而不是循环执行类似石器时代的检查 :
if (params.filterVectors.length == 0) {
// default to something
} else if (params.filterVectors.length == 1) {
// call l2norm once
} else if (params.filterVectors.length == 2) {
// call l2norm twice, separately
}
P.S。抛出一个 new Exception()
来调试 Painless 就可以了。由于 this sub-chapter on Debugging of my Elasticsearch Handbook.
Debug.explain
甚至更好
首先,感谢 Joe 确认我不是凭空想象,这确实是一个错误。其次,可爱的 ElasticSearch 团队一直在对问题进行分类并确认它是一个错误,所以这个 post 的答案是 a link to the Github Issue 所以在未来,人们可以跟踪这个行为是在哪个 ElasticSearch 版本中修补的。