用 Eland 在 Elasticsearch Serverless 部署 Learning-to-Rank 排序模型
1 LTR 流程总览
阶段 | 关键产物 | 工具 |
---|---|---|
离线 | Judgment List → 特征 → 训练集 → model.json | Pandas / scikit-learn / XGBoost |
导入 | MLModel.import_ltr_model() | Eland |
在线 | retriever 召回 + text_similarity_reranker 重排 | Search API |
2 准备数据
2.1 Judgment List
qid doc_id relevance
1001 a12 4
1001 b77 3
1001 c02 0
...
- 来源:点击/下单日志 + 人工标注
- 平衡性:不同查询类型、正负样本比例要均衡
2.2 在 Python 里声明特征抽取器
from eland.ml.ltr import QueryFeatureExtractorfeature_extractors = [# title BM25 分数QueryFeatureExtractor(feature_name="title_bm25",query={"match": {"title": "{{query}}"}}),# title 命中词个数QueryFeatureExtractor(feature_name="title_matched_terms",query={"script_score": {"query": {"match": {"title": "{{query}}"}},"script": {"source": "return _termStats.matchedTermsCount();"}}},),# 文档属性:popularityQueryFeatureExtractor(feature_name="popularity",query={"script_score": {"query": {"exists": {"field":"popularity"}},"script": {"source": "return doc['popularity'].value;"}}},),
]
打包成配置对象:
from eland.ml.ltr import LTRModelConfig
ltr_config = LTRModelConfig(feature_extractors)
2.3 批量抽特征
from eland import Elasticsearch
from eland.ml.ltr import FeatureLoggeres = Elasticsearch("https://<endpoint>", api_key="...")
logger = FeatureLogger(es, index="products", ltr_config=ltr_config)# 针对单个 query 抽取部分文档的特征
df = logger.extract_features(query_params={"query": "wireless headset"},doc_ids=["doc-1","doc-9","doc-42"])
最佳实践:在 影子集群 做 feature logging,防止大批量请求影响线上搜索。
3 训练 XGBRanker
import xgboost as xgb
# 读取 DF => numpy
X = df[["title_bm25", "title_matched_terms", "popularity"]].values
y = df["relevance"].values
group = df.groupby("qid").size().to_list() # 每个 query 的样本数dtrain = xgb.DMatrix(X, label=y)
dtrain.set_group(group)params = {"objective": "rank:ndcg","eval_metric": "ndcg@10","eta": 0.1,"max_depth": 6,"n_estimators": 200,
}
ranker = xgb.train(params, dtrain)
ranker.save_model("ltr_model.json")
4 Eland 导入模型
from eland.ml import MLModelMLModel.import_ltr_model(es_client=es,model=ranker,model_id="ltr-wireless-v1",ltr_model_config=ltr_config,es_if_exists="replace" # 已存在则覆盖
)
导入过程做了两件事:
- 将
ltr_config
(特征模板)与 XGBoost 模型打包。 - 调用 Create Trained Model API 把模型存入
.ml-inference-*
索引,并自动创建推理端点。
上线后可在 DevTools 验证:
GET _inference/_deployments/ltr-wireless-v1/_stats
5 在线检索 + LTR 重排
POST /products/_search
{"size": 10,"retriever": { # Stage-1 召回"rrf": {"retrievers": [{ "standard": { # BM25"query": { "match": { "title": "wireless headset" } },"k": 200 }},{ "standard": { # 语义稀疏向量"query": { "semantic": {"field": "semantic_text","query": "wireless headset" } },"k": 200 }}]}},"reranker": { # Stage-2 LTR"text_similarity_reranker": {"model_id": "ltr-wireless-v1","field": "{{{features}}}", # Eland 自动注入多特征模板"max_passages": 10}}
}
- k=200:先召回 200 条,提供给重排;
- 模板
{{{features}}}
:Eland 生成的隐藏字段,服务端展开为多段 DSL,提取特征并送给模型。
6 模型管理与灰度
操作 | API or 工具 |
---|---|
查看已部署版本 | GET _inference/_deployments/* |
升级 v2 并灰度 | MLModel.import_ltr_model(..., model_id="ltr-wireless-v2") 给部分流量切换到 v2 端点 |
回滚 | DELETE _inference/_deployments/ltr-wireless-v2 |
可以通过 Search Template 参数化
model_id
,由 AB proxy 控制分流,完成无损灰度。
7 常见坑与调优
问题 | 解决方案 |
---|---|
线上返回 "Failed to load model" | 端点未 start ;POST /_inference/_deployments/<id>/_start |
特征数量对不上 | 保证离线 ltr_config 与线上一致;import_ltr_model 会自动校验 |
QPS 降低 | 适当降低 k ;或在 Elastic Cloud 升级 ML Node 规格 |
模型大小 > 20 MB | 调大 num_parallel_tree / max_depth 会指数增加模型体积,线上推理内存需同步加大 |
结语
- Eland = 特征模板 + 数据提取 + 导入模型 的官方胶水层,让 LTR 在 Elastic 全家桶里“无缝衔接”。
- 借助 XGBoost LambdaMART,精排 100 条只需数毫秒,能轻松嵌入任何检索流程作为二阶段 Re-rank。
- 当业务迭代、特征新增时,重复 3 行 Python 即可快速上线新模型,保持搜索体验常新。
赶紧把你的点击日志喂给 Notebook,五分钟后就能在结果页看到“AI 调味”的 Top 10!