从 Elasticsearch 8.13 开始,我们提供了一种本地集成到 Elasticsearch 中的学习排序 (LTR) 实现。LTR 使用经过训练的机器学习 (ML) 模型为您的搜索引擎构建排名函数。通常,该模型用作第二阶段重新排序器,以提高由更简单的第一阶段检索算法返回的搜索结果的相关性。
这篇博客文章将解释此新功能如何帮助改进文本搜索中的文档排名以及如何在 Elasticsearch 中实现它。
无论您是尝试优化电子商务搜索、为检索增强生成 (RAG) 应用程序构建最佳上下文,还是在数百万篇学术论文上构建基于问答的搜索,您可能都已意识到准确优化搜索引擎中的文档排名是多么具有挑战性。这就是学习排序发挥作用的地方。
理解相关性特征以及如何构建评分函数
相关性特征是确定文档与用户查询或兴趣匹配程度的信号,所有这些都会影响搜索相关性。这些特征会根据上下文而有很大差异,但它们通常属于几类。让我们来看一下不同领域中使用的一些常见相关性特征
- 文本相关性分数(例如,BM25、TF-IDF):从文本匹配算法派生的分数,用于衡量文档内容与搜索查询的相似性。这些分数可以从 Elasticsearch 获取。
- 文档属性(例如,产品的价格、发布时间):可以直接从存储的文档中提取的特征。
- 流行度指标(例如,点击率、查看次数):文档流行程度或访问频率的指标。可以使用搜索分析工具获取流行度指标,Elasticsearch 提供开箱即用的工具。
评分函数将这些特征组合起来,为每个文档生成最终的相关性分数。分数较高的文档在搜索结果中的排名较高。
使用 Elasticsearch 查询 DSL 时,您隐式地编写了一个评分函数,该函数对相关性特征进行加权,并最终定义您的搜索相关性
Elasticsearch 查询 DSL 中的评分
考虑以下示例查询
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "the quick brown fox",
"fields": ["title^10", "content"]
}
},
"field_value_factor": {
"field": "monthly_views",
"modifier": "log1p"
}
}
}
}
此查询转换为以下评分函数
score = 10 x title_bm25_score + content_bm25_score + log(1+ monthly_views)
虽然这种方法效果很好,但它也有一些局限性
- 权重是估计的:分配给每个特征的权重通常基于启发式方法或直觉。这些猜测可能无法准确反映每个特征在确定相关性中的真正重要性。
- 跨文档的统一权重:手动分配的权重统一应用于所有文档,忽略特征之间的潜在交互作用以及它们的重要性如何在不同的查询或文档类型之间变化。例如,近期性的相关性对于新闻文章可能更重要,但对于学术论文则不太重要。
随着特征和文档数量的增加,这些限制会变得更加明显,从而使确定准确权重越来越具有挑战性。最终,选择的权重成为一种折衷方案,可能导致许多情况下排名次优。
一个引人注目的替代方案是用基于 ML 的模型替换使用手动权重的评分函数,该模型使用相关性特征计算分数。
学习排序 (LTR) 简介!
LambdaMART 是一种流行且有效的 LTR 技术,它使用梯度提升决策树(GBDT)从判断列表中学习最佳评分函数。
判断列表是一个数据集,其中包含查询和文档对及其相应的相关性标签或等级。相关性标签通常是二元的(例如,相关/不相关)或分级的(例如,从 0 表示完全不相关到 4 表示高度相关)。判断列表可以由人工手动创建,也可以从用户参与数据(例如点击或转化)生成。
下面的示例使用分级相关性判断。
LambdaMART 将排名问题视为使用决策树的回归任务,其中树的内部节点是相关性特征上的条件,叶子是预测的分数。
LambdaMART 使用梯度提升树方法,在训练过程中,它构建多个决策树,其中每棵树都纠正其前驱的错误。此过程旨在优化排名指标(如 NDCG),基于判断列表中的示例。最终模型是单个树的加权和。
XGBoost 是一个众所周知的库,它提供了LambdaMART 的实现,使其成为基于梯度提升决策树实现排名的流行选择。
Elasticsearch 中 LTR 的入门
从 8.13 版本开始,学习排序作为技术预览功能直接集成到 Elasticsearch 和相关工具中。
训练并部署 LTR 模型到 Elasticsearch
Eland 是我们用于 Elasticsearch 中 DataFrame 和机器学习的 Python 客户端和工具包。Eland 与大多数标准 Python 数据科学工具(如 Pandas、scikit-learn 和 XGBoost)兼容。
我们强烈建议使用它来训练和部署您的 LTR XGBoost 模型,因为它提供了简化此过程的功能
- 训练过程的第一步是定义 LTR 模型的相关特征。使用下面的 Python 代码,您可以使用 Elasticsearch 查询 DSL 指定相关特征。
from eland.ml.ltr import LTRModelConfig, QueryFeatureExtractor
feature_extractors=[
# We want to use the score of the match query for the fields title and content as a feature:
QueryFeatureExtractor(
feature_name="title_bm25_score",
query={"match": {"title": "{{query_text}}"}}
),
QueryFeatureExtractor(
feature_name="content_bm25_score",
query={"match": {"content": "{{query_text}}"}}
),
# We can use a script_score query to get the value
# of the field popularity directly as a feature
QueryFeatureExtractor(
feature_name="popularity",
query={
"script_score": {
"query": {"exists": {"field": "popularity"}},
"script": {"source": "return doc['popularity'].value;"},
}
},
)
]
ltr_config = LTRModelConfig(feature_extractors)
- 此过程的第二步是构建您的训练数据集。在此步骤中,您将为判断列表的每一行计算并添加相关性特征
为了帮助您完成此任务,Eland 提供了 FeatureLogger 类
from eland.ml.ltr import FeatureLogger
feature_logger = FeatureLogger(es_client, MOVIE_INDEX, ltr_config)
feature_logger.extract_features(
query_params={"query": "foo"},
doc_ids=["doc-1", "doc-2"]
)
- 构建训练数据集后,模型可以非常轻松地进行训练(如笔记本中所示)。
from xgboost import XGBRanker
from sklearn.model_selection import GroupShuffleSplit
# Create the ranker model:
ranker = XGBRanker(
objective="rank:ndcg",
eval_metric=["ndcg@10"],
early_stopping_rounds=20,
)
# Shaping training and eval data in the expected format.
X = judgments_with_features[ltr_config.feature_names]
y = judgments_with_features["grade"]
groups = judgments_with_features["query_id"]
# Split the dataset in two parts respectively used for training and evaluation of the model.
group_preserving_splitter = GroupShuffleSplit(n_splits=1, train_size=0.7).split(
X, y, groups
)
train_idx, eval_idx = next(group_preserving_splitter)
train_features, eval_features = X.loc[train_idx], X.loc[eval_idx]
train_target, eval_target = y.loc[train_idx], y.loc[eval_idx]
train_query_groups, eval_query_groups = groups.loc[train_idx], groups.loc[eval_idx]
# Training the model
ranker.fit(
X=train_features,
y=train_target,
group=train_query_groups.value_counts().sort_index().values,
eval_set=[(eval_features, eval_target)],
eval_group=[eval_query_groups.value_counts().sort_index().values],
verbose=True,
)
- 训练过程完成后,将您的模型部署到 Elasticsearch
from eland.ml import MLModel
LEARNING_TO_RANK_MODEL_ID = "ltr-model-xgboost"
MLModel.import_ltr_model(
es_client=es_client,
model=trained_model,
model_id=LEARNING_TO_RANK_MODEL_ID,
ltr_model_config=ltr_config,
es_if_exists="replace",
)
要了解我们的工具如何帮助您训练和部署模型,请查看此端到端笔记本。
在 Elasticsearch 中将您的 LTR 模型用作重新排序器
一旦您在 Elasticsearch 中部署了模型,就可以通过重新排序器来增强您的搜索结果。重新排序器允许您使用 LTR 模型提供的更复杂的评分来细化搜索结果的第一遍排名
GET my-index/_search
{
"query": {
"multi_match": {
"fields": ["title", "content"],
"query": "the quick brown fox"
}
},
"rescore": {
"learning_to_rank": {
"model_id": "ltr-model-xgboost",
"params": {
"query_text": "the quick brown fox"
}
},
"window_size": 100
}
}
在此示例中
- 第一遍查询:
The multi_match
查询检索标题和内容字段中与查询the quick brown fox
匹配的文档。此查询旨在快速捕获大量可能相关的文档。 - 重新评分阶段:
learning_to_rank
重新排序器使用 LTR 模型细化第一遍查询的顶级结果。model_id
:指定已部署的 LTR 模型的 ID(在我们的示例中为ltr-model-xgboost
)。params
:提供 LTR 模型提取与查询相关的特征所需的任何参数。此处query_text
允许您指定一些特征提取器期望的用户发出的查询。window_size
:定义要使用 LTR 模型重新评分的第一遍查询发出的搜索结果中的顶级文档数量。在此示例中,将重新评分前 100 个文档。
通过将LTR集成作为两阶段检索过程,您可以通过组合以下方式优化检索过程的性能和准确性:
- 传统搜索的速度:第一遍查询可以非常快速地检索大量具有广泛匹配的文档,从而确保快速响应时间。
- 机器学习模型的精度:LTR模型仅应用于顶级结果,从而优化其排名以确保最佳相关性。这种有针对性的模型应用提高了精度,而不会影响整体性能。
自己尝试LTR!
无论您是难以配置电子商务平台的搜索相关性,还是旨在提高RAG应用程序的上下文相关性,或者只是对增强现有搜索引擎的性能感到好奇,都应该认真考虑LTR。
要开始使用LTR,请访问我们的Notebook,其中详细介绍了如何在Elasticsearch中训练、部署和使用LTR模型,并阅读我们的文档。如果您根据这篇博文构建了任何内容,或者您对我们的Discuss论坛和社区Slack频道有任何疑问,请告知我们。
Elasticsearch 拥有众多新功能,可帮助您为您的用例构建最佳搜索解决方案。深入了解我们的示例Notebook以了解更多信息,启动免费云试用版,或立即在您的本地机器上试用Elastic。
想获得Elastic认证?了解下一期Elasticsearch工程师培训何时举行!