以 Eland 玩转 Elasticsearch 8.12 Learning-to-Rank

1 为什么要在 Elasticsearch 上做 LTR?

适用版本: Elasticsearch ≥ 8.12.0
前置条件: 需拥有包含 "Serverless LTR" 的订阅等级(详见官方订阅矩阵)
技术栈: Elasticsearch + Python + Eland + XGBoost / LightGBM / scikit-learn

  • 相关性瓶颈:经典 BM25 只能"打基础",很难兼顾业务特征(点击率、销量、评分......)。
  • 端到端成本:把向量检索或深度模型接到 ES 之外,需要额外的服务网关、时延和 DevOps。
  • Elasticsearch 8.12.0 新特性 :官方 Serverless LTR 能直接在 search → rescore 阶段调用训练好的模型,既继承 ES 的伸缩性,又省去了微服务运维。

2 整体流程概览

3 环境准备

bash 复制代码
pip install "elasticsearch>=8.12" eland pandas scikit-learn xgboost lightgbm
export ELASTICSEARCH_URL="http://localhost:9200"
export ELASTIC_PASSWORD="elastic_password"

贴士:离线训练最好用一套隔离的 ES 集群,避免大规模特征抽取冲击线上查询。

4 定义特征抽取模板(QueryFeatureExtractor)

python 复制代码
from eland.ml.ltr import QueryFeatureExtractor, LTRModelConfig

feature_extractors = [
    # 1️⃣  BM25 分数
    QueryFeatureExtractor(
        feature_name="title_bm25",
        query={"match": {"title": "{{query}}"}}
    ),
    # 2️⃣  Title 命中词数
    QueryFeatureExtractor(
        feature_name="title_matched_term_count",
        query={
            "script_score": {
                "query": {"match": {"title": "{{query}}"}},
                "script": {"source": "return _termStats.matchedTermsCount();"}
            }
        }
    ),
    # 3️⃣  直接把文档字段当特征
    QueryFeatureExtractor(
        feature_name="popularity",
        query={
            "script_score": {
                "query": {"exists": {"field": "popularity"}},
                "script": {"source": "return doc['popularity'].value;"}
            }
        }
    ),
    # 4️⃣  查询词长度
    QueryFeatureExtractor(
        feature_name="query_term_count",
        query={
            "script_score": {
                "query": {"match": {"title": "{{query}}"}},
                "script": {"source": "return _termStats.uniqueTermsCount();"}
            }
        }
    ),
]

ltr_config = LTRModelConfig(feature_extractors)

为什么不用自己写脚本?

Eland 与 Elasticsearch 同步开发&测试,可确保训练阶段与线上推理的特征完全一致,避免"训练-服务漂移"。

5 采集训练数据(FeatureLogger)

python 复制代码
from eland.ml.ltr import FeatureLogger
from elasticsearch import Elasticsearch
import pandas as pd

es = Elasticsearch(ELASTICSEARCH_URL, basic_auth=("elastic", ELASTIC_PASSWORD))
MOVIE_INDEX = "movies_demo"

logger = FeatureLogger(es, MOVIE_INDEX, ltr_config)

# 假设我们已有一份人工评判文件 judgements.csv
judgements = pd.read_csv("judgements.csv")  # query, doc_id, relevance

records = []
for _, row in judgements.iterrows():
    feats = logger.extract_features(
        query_params={"query": row.query},
        doc_ids=[row.doc_id]
    )
    record = feats[0].to_dict()
    record["label"] = row.relevance
    records.append(record)

df_train = pd.DataFrame(records)
df_train.to_csv("ltr_train.csv", index=False)

性能提示 :FeatureLogger 会自动合并批量请求、最小化查询次数,但大量特征 × 大量样本仍可能对 ES 造成压力。调低 size 或切分批次能进一步减负。

6 训练 Ranker

以下以 XGBRanker 为例(LightGBM、RandomForest、DecisionTree 均可):

python 复制代码
from xgboost import XGBRanker
import numpy as np

X = df_train[feature_extractors_names].values
y = df_train["label"].values
group = df_train.groupby("query").size().to_numpy()  # 每个 query 的样本数

ranker = XGBRanker(objective="rank:pairwise",
                   n_estimators=300,
                   max_depth=6,
                   learning_rate=0.1)
ranker.fit(X, y, group=group)

7 模型部署一键化(MLModel.import_ltr_model)

python 复制代码
from eland.ml import MLModel

MODEL_ID = "movies_ltr_xgboost_v1"

MLModel.import_ltr_model(
    es_client=es,
    model=ranker,
    model_id=MODEL_ID,
    ltr_model_config=ltr_config,
    es_if_exists="replace"       # 本例重复执行时覆盖
)

执行后,Eland 会自动:

  1. 将模型序列化为 ES 接受的 [Eland ML Pack] 格式;
  2. 调用 Create Trained Model API 上传;
  3. 生成 inference_config,包含上文 4 个特征查询模板。

8 线上调用:Search + Rescore

json 复制代码
POST movies_demo/_search
{
  "query": {
    "match": {
      "title": "science fiction"
    }
  },
  "rescore": {
    "window_size": 100,
    "query": {
      "rescore_query": {
        "ltr_model": {
          "model_id": "movies_ltr_xgboost_v1",
          "params": { "query": "science fiction" }
        }
      },
      "query_weight": 0.2,
      "rescore_query_weight": 1.0
    }
  }
}
  • window_size 控制重新排序的命中窗口,100-500 较常见;
  • params.query 必须与特征模板中的 {``{query}} 对应;
  • query_weight 越小,模型重排权重越大。

9 模型管理运维

操作 API 说明
查看模型 GET _ml/trained_models/<id> 包含元数据、大小、创建时间
移除旧版 DELETE _ml/trained_models/<id> 勿忘同时清理索引模板里旧 rescore 配置
批量迁移 POST _ml/trained_models/_revert 结合 CI/CD 做蓝绿发布
性能监控 _nodes/stats inference 关注推理延迟 & CPU/内存

10 最佳实践与踩坑

场景 建议
特征库膨胀 控制在 20-40 个以内;复杂逻辑交给模型学习权重,而不是写 N 个脚本评分
交叉验证 使用 group k-fold,确保同一 query 不同时出现在 train&test
线上流量回滚 给旧 BM25 排序保留 20% 流量,异常时快速切换
版本化 movies_ltr_xgboost_v1v2,避免热更新导致 cache 冲突
安全与订阅 开启 HTTPS + token;无服务器版本仅在满足订阅级别时可用

11 总结

借助 8.12.0 新增的 Serverless LTR + Eland:

  • 开发侧 :用 Python 就能完成 特征抽取 → 训练 → 部署 闭环,无需自研特征解析与序列化。
  • 运维侧:模型与索引共生,统一在 ES 集群里监控、热更新,简化了线上链路。
  • 业务侧:短期内即可验证"学习排序 vs 传统 BM25"的收益,并可逐步升级至更复杂的多阶段检索 / 向量召回方案。

开源笔记本示例https://github.com/elastic/elasticsearch-labs/tree/main/notebooks/ltr

赶紧试试把你的点击日志、评分字段喂给 XGBoost,让搜索结果 "更懂业务" 吧!

相关推荐
wdfk_prog1 小时前
实战教程:从“对象文件为空“到仓库重生——修复 Git 仓库损坏全记录
大数据·网络·笔记·git·学习·elasticsearch·全文检索
杨荧1 小时前
基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js
大数据·前端·javascript·vue.js·spring boot·后端·python
ALex_zry3 小时前
Git Status 命令深度指南:洞悉仓库状态的核心艺术
大数据·git·elasticsearch
ζั͡山 ั͡有扶苏 ั͡✾3 小时前
Elasticsearch 单节点迁移实战指南:从旧服务器到新环境的完整流程
服务器·elasticsearch·jenkins
weixin_425878234 小时前
AWS 可靠性工程深度实践: 从 Well-Architected 到“零失误”VPC 落地
大数据·云计算·aws
九河云4 小时前
华为云代理商的作用与价值解析
大数据·人工智能·华为云
SelectDB5 小时前
天翼云与飞轮科技达成战略合作,共筑云数融合新生态
大数据·数据库·数据分析
Sunhen_Qiletian6 小时前
机器学习实战:逻辑回归深度解析与欺诈检测评估指标详解(二)
大数据
Flink_China7 小时前
Flink 2.1 SQL:解锁实时数据与AI集成,实现可扩展流处理
大数据·flink