【问题标题】:Weighted random sampling in ElasticsearchElasticsearch 中的加权随机抽样
【发布时间】:2016-03-11 18:32:15
【问题描述】:

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

目前,我有以下查询:

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"}}]
}

它从选定的类别中随机返回 5 个项目。 每个项目都有一个字段weight。所以,我可能不得不使用

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

here 所述。

我有以下问题:

  • 这样做的正确方法是什么?
  • 我需要启用Dynamic Scripting吗?
  • 如何计算查询的总和?

非常感谢您的帮助!

【问题讨论】:

    标签: elasticsearch random-sample weighted


    【解决方案1】:

    我知道这个问题很老,但为任何未来的搜索者回答。

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

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

    【讨论】:

      【解决方案2】:

      如果它对任何人有帮助,以下是我最近实施加权洗牌的方式。

      在此示例中,我们对公司进行了洗牌。每家公司都有一个介于 0 到 100 之间的“company_score”。通过这种简单的加权洗牌,得分 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",
              }
          }
      }
      

      【讨论】:

        【解决方案3】:

        除了其他答案:

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

        在这种情况下,您可以使用自定义 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[]

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-11-11
          • 2012-01-26
          • 1970-01-01
          • 2021-07-19
          • 1970-01-01
          • 2012-10-14
          • 2016-09-06
          • 1970-01-01
          相关资源
          最近更新 更多