Elasticsearch 中的加权随机抽样

Weighted random sampling in Elasticsearch

我需要从 ElasticSearch 索引中获取随机样本,即发出查询,以加权概率 Wj/ΣWi 从给定索引中检索某些文档(其中 Wj 是行的权重jWj/ΣWi 是该查询中所有文档的权重之和。

目前,我有以下查询:

GET products/_search?pretty=true

{"size":5,
  "query": {
    "function_score": {
      "query": {
        "bool":{
          "must": {
            "term":
              {"category_id": "5df3ab90-6e93-0133-7197-04383561729e"}
          }
        }
      },
      "functions":
        [{"random_score":{}}]
    }
  },
  "sort": [{"_score":{"order":"desc"}}]
}

returns 所选类别中的 5 个项目,随机。 每个项目都有一个字段 weight。所以,我可能必须使用

"script_score": {
  "script": "weight = data['weight'].value / SUM; if (_score.doubleValue() > weight) {return 1;} else {return 0;}"
}

如所述here

我有以下问题:

非常感谢您的帮助!

我知道这个问题很老,但可以为以后的搜索者回答。

comment before yours in the GitHub thread 似乎有了答案。如果您的每个文档都有一个相对权重,那么您可以为每个文档选择一个随机分数并将其乘以权重以创建新的加权随机分数。这具有不需要权重总和的额外好处。

例如如果两个文档的权重为 12,那么您会期望第二个文档的选择可能性是第一个文档的两倍。给每个文档一个介于 01 之间的随机分数(您已经用 "random_score" 做了)。将随机分数乘以权重,您将得到第一个文档的分数在 01 之间,第二个文档的分数在 02 之间,所以被选中的可能性翻倍!

如果对任何人有帮助,下面是我最近实现加权洗牌的方法。

在这个例子中,我们洗牌公司。每家公司的 "company_score" 介于 0 和 100 之间。通过这种简单的加权洗牌,得分为 100 的公司出现在首页的可能性是得分为 20 的公司的 5 倍。

json_body = {
    "sort": ["_score"],
    "query": {
        "function_score": {
            "query": main_query,  # put your main query here
            "functions": [
                {
                    "random_score": {},
                },
                {
                    "field_value_factor": {
                        "field": "company_score",
                        "modifier": "none",
                        "missing": 0,
                    }
                }
            ],
            # How to combine the result of the two functions 'random_score' and 'field_value_factor'.
            # This way, on average the combined _score of a company having score 100 will be 5 times as much
            # as the combined _score of a company having score 20, and thus will be 5 times more likely
            # to appear on first page.
            "score_mode": "multiply",
            # How to combine the result of function_score with the original _score from the query.
            # We overwrite it as our combined _score (random x company_score) is all we need.
            "boost_mode": "replace",
        }
    }
}

另外回答:

您还可以考虑 源文档的非均匀分布 的情况,以进行平衡。例如,您想从索引中检索 100 条随机混合新闻:50% 的体育新闻和 50% 的政治新闻,其中有 10,000 条体育新闻和 1,000,000 条政治新闻。

在这种情况下,您可以使用自定义 script_score 函数与 random_score 混合,将源分布转换为结果中想要的 50/50 分布:

GET objects/_search
{
  "size": 100,
  "sort": [
    "_score"
  ],
  "query": {
    "function_score": {
      "query": { "match_all": {} },
      "functions": [
        {
          "random_score": {}
        },
        {
          "script_score": {
            "script": {
              "source": """
                double boost = 0.0;
                if (params._source['labels'] != null && params._source['labels']['genres'] != null && params._source['labels']['genres'].contains('politics') && Math.random()*1000000 <= 50) {
                  boost += 1.0;
                }
                if (params._source['labels'] != null && params._source['labels']['genres'] != null && params._source['labels']['genres'].contains('sports') && Math.random()*10000 <= 50) {
                  boost += 1.0;
                }
                return boost;
              """
            }
          }
        }
      ],
      "score_mode": "multiply",
      "boost_mode": "replace"
    }
  }
}

请注意,上面示例中的源文档是 嵌套,如下所示:

{
  "title": "...",
  "body": "...",
  "labels": {
    "genres": ["news"],
    "topics": ["sports", "celebrities"]
  }
}

但您可能有一个更简单的数据模型,其中包含普通字段;在这种情况下,只需使用 doc['topic'].contains('sports') 而不是 params._source[].