引入Elasticsearch中的Learning To Rank功能
从Elasticsearch 8.13版本开始,我们原生集成了Learning To Rank (LTR)功能。LTR利用训练过的机器学习(ML)模型为你的搜索引擎构建一个排名函数。通常,该模型作为第二阶段的重新排序器,以改进由第一阶段简单检索算法返回的搜索结果的相关性。
本文将解释这一新功能如何帮助改进文本搜索中的文档排名,并介绍如何在Elasticsearch中实现它。
无论你是尝试优化电子商务搜索,构建最优的检索增强生成(RAG)应用,还是在数百万学术论文中进行基于问答的搜索,你可能都意识到在搜索引擎中准确优化文档排名是多么具有挑战性。这正是Learning to Rank的用武之地。
理解相关性特征及如何构建评分函数
相关性特征是用于确定文档与用户查询或兴趣匹配程度的信号,这些信号都会影响搜索相关性。这些特征可能因情境而异,但通常可以归为几类。让我们来看看在不同领域中常用的一些相关性特征:
- 文本相关性评分(例如,BM25,TF-IDF):从文本匹配算法中得出的分数,用于衡量文档内容与搜索查询的相似性。这些分数可以从Elasticsearch中获得。
- 文档属性(例如,产品价格,发布日期):直接从存储的文档中提取的特征。
- 受欢迎度指标(例如,点击率,浏览量):文档的受欢迎程度或访问频率的指标。受欢迎度指标可以通过搜索分析工具获得,Elasticsearch提供现成的工具。
评分函数将这些特征结合起来,为每个文档生成最终的相关性分数。分数越高,文档在搜索结果中的排名越高。
使用Elasticsearch查询DSL时,你实际上是在编写一个评分函数,该函数为相关性特征赋权,最终定义了你的搜索相关性。
在Elasticsearch查询DSL中进行评分
考虑以下示例查询:
代码语言:json复制{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "the quick brown fox",
"fields": ["title^10", "content"]
}
},
"field_value_factor": {
"field": "monthly_views",
"modifier": "log1p"
}
}
}
}
此查询转换为以下评分函数:
代码语言:python代码运行次数:1复制score = 10 x title_bm25_score content_bm25_score log(1 monthly_views)
虽然这种方法有效,但它有一些限制:
- 权重是估计的:为每个特征分配的权重通常基于启发式或直觉。这些猜测可能无法准确反映每个特征在确定相关性方面的真实重要性。
- 文档间权重统一:手动分配的权重对所有文档均适用,忽略了特征之间的潜在交互以及它们的重要性在不同查询或文档类型中可能存在的变化。例如,新闻文章的时效性可能更重要,而对于学术论文则不那么重要。
随着特征和文档数量的增加,这些限制变得更加明显,准确确定权重变得越来越困难。最终,所选择的权重可能是一个折衷方案,导致在许多场景中排名次优。
一个有力的替代方案是用基于ML的模型替代手动权重的评分函数,该模型使用相关性特征计算分数。
认识Learning To Rank (LTR)!
LambdaMART是一种流行且有效的LTR技术,它使用梯度提升决策树(GBDT)从评估列表中学习最佳评分函数。
评估列表是包含查询和文档对及其对应的相关性标签或评分的数据集。相关性标签通常是二元的(例如,相关/不相关)或分级的(例如,从0表示完全不相关到4表示高度相关)。评估列表可以由人工手动创建,也可以从用户参与数据(如点击或转化)中生成。
以下示例使用的是分级相关性评估。
LambdaMART将排序问题视为使用决策树的回归任务,其中树的内部节点是关于相关性特征的条件,叶节点是预测分数。
LambdaMART使用梯度提升树方法,在训练过程中构建多个决策树,每棵树纠正其前辈的错误。此过程旨在基于评估列表中的示例优化排名指标如NDCG。最终模型是各个树的加权和。
XGBoost是一个著名的库,提供了LambdaMART的实现,因此成为实现基于梯度提升决策树的排名的流行选择。
在Elasticsearch中开始使用LTR
从8.13版本开始,Learning To Rank直接集成到Elasticsearch和相关工具中,作为技术预览功能提供。
训练并部署LTR模型到Elasticsearch
Eland是我们的Python客户端和用于在Elasticsearch中处理DataFrame和机器学习的工具包。Eland与大多数标准的Python数据科学工具兼容,如Pandas、scikit-learn和XGBoost。
我们强烈推荐使用Eland来训练和部署你的LTR XGBoost模型,因为它提供了简化这一过程的功能:
- 训练过程的第一步是定义LTR模型的相关特征。使用下面的Python代码,你可以使用Elasticsearch查询DSL指定相关特征。
from eland.ml.ltr import LTRModelConfig, QueryFeatureExtractor
feature_extractors = [
# 我们希望使用字段标题和内容的匹配查询得分作为特征:
QueryFeatureExtractor(
feature_name="title_bm25_score",
query={"match": {"title": "{{query_text}}"}}
),
QueryFeatureExtractor(
feature_name="content_bm25_score",
query={"match": {"content": "{{query_text}}"}}
),
# 我们可以使用script_score查询直接获取字段受欢迎度的值作为特征
QueryFeatureExtractor(
feature_name="popularity",
query={
"script_score": {
"query": {"exists": {"field": "popularity"}},
"script": {"source": "return doc['popularity'].value;"}
}
}
)
]
ltr_config = LTRModelConfig(feature_extractors)
- 第二步是构建你的训练数据集。在此步骤中,你将为评估列表的每一行计算并添加相关性特征:
为帮助完成此任务,Eland提供了FeatureLogger类:
代码语言:python代码运行次数:0复制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"]
)
- 当训练数据集构建完成后,模型的训练变得非常简单(也可以在notebook中看到):
from xgboost import XGBRanker
from sklearn.model_selection import GroupShuffleSplit
# 创建排序模型:
ranker = XGBRanker(
objective="rank:ndcg",
eval_metric=["ndcg@10"],
early_stopping_rounds=20,
)
# 以预期格式整形训练和评估数据。
X = judgments_with_features[ltr_config.feature_names]
y = judgments_with_features["grade"]
groups = judgments_with_features["query_id"]
# 将数据集分为两部分,分别用于模型的训练和评估。
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]
# 训练模型
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",
)
要了解更多关于我们的工具如何帮助你训练和部署模型的信息,请查看这个端到端的notebook。
在Elasticsearch中使用你的LTR模型作为重新排序器
一旦你将模型部署到Elasticsearch,你可以通过重新排序器增强搜索结果。重新排序器允许你使用LTR模型提供的更复杂的评分来优化第一次排序的搜索结果:
代码语言:json复制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
}
}
在这个例子中:
- 第一次查询:
multi_match
查询在标题和内容字段中检索匹配查询the quick brown fox
的文档。该查询设计得很快,并捕获大量潜在相关文档。 - 重新排序阶段:
learning_to_rank
重新排序器使用LTR模型优化第一次查询的前100个结果。model_id
:指定已部署LTR模型的ID(在我们的例子中为ltr-model-xgboost
)。params
:提供LTR模型提取与查询相关的特征所需的任何参数。这里的query_text
允许你指定用户发出的查询,这是一些特征提取器所期望的。window_size
:定义第一次查询返回的搜索结果中要重新排序的前几个文档的数量。在这个例子中,前100个文档将被重新排序。
通过将LTR集成为两阶段检索过程,你可以通过结合以下两点来优化检索过程的性能和准确性:
- 传统搜索的速度:第一次查询快速检索大量广泛匹配的文档,确保响应时间快。
- 机器学习模型的精确度:LTR模型仅应用于前几名结果,优化它们的排名以确保最佳相关性。模型的这种有针对性的应用提高了精度而不影响整体性能。
尝试一下吧!
无论你是在为电子商务平台配置搜索相关性而苦恼,还是希望改进RAG应用的上下文相关性,或者只是对提升现有搜索引擎性能感到好奇,都应该认真考虑LTR。
要开始实现LTR的旅程,请务必访问我们的notebook,了解如何在Elasticsearch中训练、部署和使用LTR模型,并阅读我们的文档。如果你基于这篇博客文章构建了任何东西或有任何问题,请在我们的讨论论坛和社区Slack频道上告诉我们。