【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

在 Elasticsearch 中应用机器学习排序 LTR

  • 2017-04-12
  • 本文字数:5376 字

    阅读完需:约 18 分钟

众所周知,机器学习正在重构很多行业。搜索领域同样如此,很多公司竭尽全力通过手动调优搜索相关度来实现非常微小的改进;更成熟的搜索团队则希望进一步超越已经“足够好”的人工优化,从而构建更加智能的、自学习的搜索系统。

因此,我们很激动地发布了 Elasticsearch 的 LTR(Learning to Rank,机器学习排序)插件。什么是 LTR?一个团队可以通过 LTR 训练一个机器学习模型,来学习用户认为什么是最相关的。

实现 LTR 时需要做到:

  1. 通过分析来衡量用户所反馈的相关度,并构建一个评价列表,将文档分级为精确相关的、模糊相关的和无关的,并用于查询。
  2. 猜想哪些特征可能有助于相关度预测,比如特定属性匹配程度的 TF-IDF 相关度、新颖性,以及搜索用户的个性化特征等。
  3. 训练一个模型,将这些特征准确无误地映射到一个相关性得分上。
  4. 部署该模型到你的搜索基础设施中,在线上环境中使用它来对搜索结果排序。

不要自欺欺人:这些步骤里面的每一个都是非常复杂和困难的技术或非技术问题,并没有什么所谓的银弹。就像我们在《相关性搜索》一书中提到的,搜索结果的人工调优与一个好的机器学习排序算法面对着许多相同的挑战。我们会在今后的博客文章中探讨更多关于成熟的机器学习排序解决方案会面临的许多基础设施、技术性及非技术性挑战的内容。

本文中介绍的是我们如何把 LTR 集成到了 Elasticsearch。几乎在每一个关于相关性的咨询案例中客户都会问我们,这项技术是否可以帮助到他们。在 Solr 中要感谢 Bloomberg 给出了一个明确的路径来达到目的,而 Elasticsearch 中并没有。在为搜索技术栈做技术选型时,很多客户看重 Elasticsearch 与时俱进的高效特性,但同时也发现缺少了这个关键性的特性。

实际上 Elasticsearch 查询语句能够通过其强大的能力和复杂的结构来对结果进行排序。一个技能娴熟的相关度工程师能使用查询语句来计算多种可能涉及信号相关度的查询时特征,并定量地回答以下问题:

  1. 搜索的词项在标题中被提及多少次?
  2. 文章 / 电影 / 其他已经发布多久了?
  3. 文档是如何与用户的浏览行为关联的?
  4. 产品比买家的预期昂贵多少?
  5. 用户的检索词项与文章的标题在概念上有多相关?

在搜索引擎中这些特征很多都不是文档的静态属性,相反地它们依赖于查询——它们度量用户或用户的查询与一个文档之间的关联关系。对于《相关性搜索》的读者,这便是我们在该书中所说的信号。

所以现在问题变成了,如何才能把机器学习的能力与已有的 Elasticsearch Query DSL 查询语言的能力结合起来?这恰恰就是我们的插件所做的事情:把 Elasticsearch 查询语言所构造的查询作为特征输入到一个机器学习模型中。

工作原理

长话短说

该插件集成了 RankLib 和 Elasticsearch。RankLib 有一个输入文件作为评价依据,并输出一个模型,该模型是内置的可阅读格式。接下来 RankLib 可通过编程或命令行来训练模型。一旦有了模型,Elasticsearch 插件就会包含以下内容:

  1. 一个自定义的 Elasticsearch 脚本语言,叫做 ranklib,它把 RankLib 生成的模型作为一个 Elasticsearch 脚本
  2. 一个自定义的 LTR 查询,它输入一个包含 Query DSL 查询(那些特征)、一个模型名称(就是第一步中上传的模型)和打分结果的列表

由于 LTR 模型的实现成本很高,人们几乎不会直接使用 LTR 查询,而是对结果的 Top N 重新打分,比如:

复制代码
{
"query": {/*a simple base query goes here*/},
"rescore": {
"window_size": 100,
"query": {
"rescore_query": {
"ltr": {
"model": {
"stored": "dummy"
},
"features": [{
"match": {
"title": <users keyword search>
}
}
...

更多重要细节:来看看这个完整的 LTR 函数示例的强大之处吧!

你可以在项目脚本目录里仔细研究下这个功能完整的示例。这是一个严谨的示例,使用了 TMDB 数据库中的电影人工评分数据。我创建了一个 Elasticsearch 索引叫 TMDB,用来执行查询,并通过命令行训练了一个 RankLib 模型。接下来把模型保存在 Elasticsearch 中,并提供一个脚本通过改模型来实现检索。

千万别被这个简单的例子给误导了。实践中一个真正的 LTR 解决方案有着非常大量的工作要做,包括用户研究、分析处理、数据工程以及特征工程等。这样说并不是为了吓唬你,因为付出是值得的,想想你的投入回报吧。小规模团队使用手工调优的 ROI 可能会获得更好的结果。

训练并加载 LTR 模型

现在使用我手工创建的迷你评价列表,来演示下如何训练一个模型。

RankLib 评分列表有着严格的标准格式。第一列包括一个文档的评分(0 到 4);接下来的一个列是查询 ID,比如“qid:1”;随后的列包含了与文档关联的特征值对,其中左边是 1 开始的特征索引,右边的数字是该特征的值。RankLib 的 README 文件中有示例如下:

复制代码
3 qid:1 1:1 2:1 3:0 4:0.2 5:0 # 1A
2 qid:1 1:0 2:0 3:1 4:0.1 5:1 # 1B
1 qid:1 1:0 2:1 3:0 4:0.4 5:0 # 1C
1 qid:1 1:0 2:0 3:1 4:0.3 5:0 # 1D
1 qid:2 1:0 2:0 3:1 4:0.2 5:0 # 2A

注意其中的注释(# 1A 等),这些注释是评价该文档的标识。RankLib 并不需要该文档标识,但是很便于阅读。当通过 Elasticsearch 查询来收集特征时,就会看到这些文档标识也很有用。

我们的示例使用上述文件的一个迷你版本(参考这里),仅需从一个精简版本的评价文件开始,只有一个等级、查询 ID 和文档 ID 元组。就像这样:

复制代码
4 qid:1 # 7555
3 qid:1 # 1370
3 qid:1 # 1369
3 qid:1 # 1368
0 qid:1 # 136278
...

如上,我们为分级文档提供 Elasticsearch 中的 _id 属性作为每行的注释。

我们需要进一步改进这个方面,必须把每个查询 ID(qid:1)映射到一个实际的关键字查询(“Rambo”)上,从而可以使用关键字来生成特征值。我们在头信息中提供了这个映射,示例代码会展示:

复制代码
# Add your keyword strings below, the feature script will
# Use them to populate your query templates
#
# qid:1: rambo
# qid:2: rocky
# qid:3: bullwinkle
#
# https://sourceforge.net/p/lemur/wiki/RankLib%20File%20Format/
#
#
4 qid:1 # 7555
3 qid:1 # 1370
3 qid:1 # 1369
3 qid:1 # 1368
0 qid:1 # 136278
...

为了理清思路,马上开始探讨作为“关键字”的 ranklib“查询”,区别于 Elasticsearch Query DSL“查询”,后者是符合 Elasticsearch 规范的,用于生成特征值。

上面并不是一个文章的 RangLib 评价列表,仅仅是一个给定文档对于一个给定关键字搜索的迷你示例。要成为一个完备的训练集,需要包含上述特征值,并在每行后面的第一个评分列表后显示 1:0 2:1 。。。等。

为了生成这些特征值,还需要提出可能跟电影相关性对应的特征。正如前文所述,这些就是 Elasticsearch 查询。这些 Elasticsearch 查询的得分将会填充上述评分列表。在上述例子中,我们通过一个对应到每个特征数字的 jinja 模板来实现。比如文件 1.json.jinja 就是下列查询:

复制代码
{
"query": {
"match": {
"title": ""
}
}
}

换句换说,我们已经决定特征 1 对于我们的电影搜索系统来说,应该就是用户关键字与所匹配的标题属性的 TF*IDF 相关度。2.jinja.json 展示了一个多文本字段的复杂检索:

复制代码
{
"query": {
"multi_match": {
"query": "",
"type": "cross_fields",
"fields": ["overview", "genres.name", "title", "tagline", "belongs_to_collection.name", "cast.name", "directors.name"],
"tie_breaker": 1.0
}
}
}

LTR 的一个有趣之处是猜测哪些特征与相关度相关联。比如,你可以改变特征 1 和 2 为任意 Elasticsearch 查询,也可以多次试验增加额外的特征 3。多个特征的问题是,你想获得足够典型的训练样本来覆盖所有候选特征值。后面的文章我们会探讨更多关于训练和测试 LTR 模型的话题。

基于这两点,最小评分列表和建议的 Query DSL 查询 / 特征集合,我们需要为 RankLib 生成一个全量的评价列表,并加载 RankLib 生成的模型到 Elasticsearch 中待用。这表示:

  1. 获取特征的每一个关键字 / 文档对的相关度得分。即发布查询到 Elasticsearch 来记录相关度得分
  2. 输出一个完整的评分文件,同时包含等级和关键字查询 id,以及第一步中的特征值
  3. 运行 Ranklib 来训练模型
  4. 加载模型到 Elasticsearch 待搜索时调用

完成这一切的代码都在 train.py 中了,建议分步执行:

  • 下载 RankLib.jar 到 scripts 目录
  • 安装安装 Python 包 elasticsearch 和 jinja2(如果你熟悉的话会有一个 Python 的 requirements.txt 文件)

然后只要运行:

复制代码
python train.py

这个单一脚本执行了上面提到的所有步骤。现在来过一遍代码:

首先加载最小评判列表,仅包含文档、关键词查询 ID、等级元组,以及文件头部的特定搜索关键词:

复制代码
judgements = judgmentsByQid(judgmentsFromFile(filename='sample_judgements.txt'))

然后我们发起批量的 Elasticsearch 查询并记录每个评价的特征(在评价中扩大通过率)。

复制代码
kwDocFeatures(es, index='tmdb', searchType='movie', judgements=judgements)

函数 kwDocFeatures 遍历 1.json.jinja 到 N.json.jinja(特征 / 查询对),策略上使用 Elasticsearch 的批量搜索 API( _search )来批量执行 Elasticsearch 查询,以便为每一个关键词 / 文档元组获取一个相关度得分。代码有点长,可以在这里看到。

一旦我们有了全部的特征,接下来就可以输出整个训练集(评判附加特征)到一个新的文件(sample_judgements_wfeatures.txt):

复制代码
buildFeaturesJudgmentsFile(judgements, filename='sample_judgements_wfeatures.txt')

相应地将输出一个完整的具体 RankLib 评分列表:

复制代码
3 qid:1 1:9.476478 2:25.821222 # 1370
3 qid:1 1:6.822593 2:23.463709 # 1369

这里特征 1 是在属性 title(1.json.jinja) 上检索“Rambo”时的 TF*IDF 分值;特征 2 是更复杂的检索(2.json.jinja)的 TF*IDF 分值。

接下来进行训练!这一行是通过命令行使用已保存文件作为评判数据来执行 Ranklib.jar

复制代码
trainModel(judgmentsWithFeaturesFile='
sample_judgements_wfeatures.txt', modelOutput='model.txt')

正如下文所示,这只是很基础地执行 java -jar Ranklib.jar 来训练一个 LambdaMART 模型:

复制代码
def trainModel(judgmentsWithFeaturesFile, modelOutput):
# java -jar RankLib-2.6.jar -ranker 6 -train sample_judgements_wfeatures.txt -save model.txt
cmd = "java -jar RankLib-2.6.jar -ranker 6 -train %s -save %s" % (judgmentsWithFeaturesFile, modelOutput)
print("Running %s" % cmd)
os.system(cmd)

然后使用简单的 Elasticsearch 命令把模型保存到 Elasticsearch 中:

复制代码
saveModel(es, scriptName='test', modelFname='model.txt')

这里的 savaModel 跟看起来一样,只是读取文件内容并 POST 到 Elasticsearch 中,作为一个 ranklib 脚本存储。

使用 LTR 模型进行搜索

一旦完成训练,就准备好了发起检索!在 search.py 中有一个非常简明直观的例子,里面只有一个简单查询。执行命令 python search.py rambo,将会使用训练好的模型检索“rambo”,并执行以下重打分查询:

复制代码
{
"query": {
"match": {
"_all": "rambo"
}
},
"rescore": {
"window_size": 20,
"query": {
"rescore_query": {
"ltr": {
"model": {
"stored": "test"
},
"features": [{
"match": {
"title": "rambo"
}
}, {
"multi_match": {
"query": "rambo",
"type": "cross_fields",
"tie_breaker": 1.0,
"fields": ["overview", "genres.name", "title", "tagline", "belongs_to_collection.name", "cast.name", "directors.name"]
}
}]
}
}
}
}
}

注意这里我们只对前 20 个结果做重排序。也可以直接使用 LTR 查询,实际上直接运行模型更好一些,即使在整个集合上运行要耗费几百毫秒。对于一个较大的集合可能并不可行。一般来讲,最好只对前面 N 个结果做重排序,因为机器学习排序模型的性能成本的原因。

这就是一个刚好能工作的完整例子了。当然只是一个入门级的小规模示例,刚刚能达到目的;对于特定问题可能会有更多不同的状况,所选的特征、如何记录特征、训练模型,以及实现一个排序基线函数,基本上依赖于你的领域。但我们在《相关性检索》中提到的很多内容仍然适用。

后续内容

后续的博客文章中我们会更多地介绍 LTR,包括:

  • 基础:更多地介绍 LTR 到底是什么
  • 应用:将 LTR 应用于搜索、推荐系统、个性化及更多场景
  • 模型:流行的模型是什么?模型选择时如何考量?
  • 思考:使用 LTR 时有哪些技术和非技术因素需要考量?

查看英文原文: http://opensourceconnections.com/blog/2017/02/14/elasticsearch-learning-to-rank/


感谢冬雨对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2017-04-12 17:1720856

评论

发布
暂无评论
发现更多内容

mac office 365 商业专业版破解 含Office 365激活工具 兼容M2/M3

Rose

微软 Office

API 性能测试教程:让你的应用运行更加顺畅

Apifox

测试 性能测试 接口测试 测试工具 API 性能测试

学编程前需要知道哪些编程语言呢?

小齐写代码

Solidity案例详解(五)服务评价合约

BSN研习社

区块链 Solidity

数字人引领品牌增长新纪元!

青否数字人

数字人

机器人装行业MES/低代码平台免费使用/低代码MES

万界星空科技

低代码 低代码平台 mes 万界星空科技 机器人组装

Python 初学者容易踩的 5 个坑

不在线第一只蜗牛

Java Python 开发语言

专业且功能齐全的pdf编辑软件 acrobat pro dc 2023中文版 mac/win

Rose

WebRTC构建点对点的即时通讯工具

鲸品堂

工具 企业号 3 月 PK 榜

程序员有哪些常用的技术网站呢?

这我可不懂

效率 程序员 前端 低代码 JNPF

2024 年 2 月 NFT 行业动态:加密货币飙升,NFT 市场调整

Footprint Analytics

blockchain NFT NFT链游

新一代实时数据集成框架 Flink CDC 3.0 —— 核心技术架构解析

Apache Flink

抓住2024新风口,数字人直播创业怎么做?

青否数字人

深入理解位运算符及其在JavaScript中的应用

控心つcrazy

通过统一规划和团队整合,提升企业财务洞察

智达方通

全面预算管理 财务洞察

为多渠道销售集成商品API接口

Noah

网络安全AI智能体公司「云起无垠」获数千万元天使+轮融资,致力于大模型与网络安全深度融合的技术研究

云起无垠

ICP Rust CDK|课程介绍

TinTinLand

学习 黑客 编程语言 #Web3 ICP

2024年值得尝试的22款在线管理软件盘点!

PingCode

项目管理软件 项目管理x

一文让你知道,云计算环境下云管平台的重要性

行云管家

云计算 云服务 云管平台 云资源

Sol链发币教程系列02:放弃代币冻结权限

加密先生

发布DDD脚手架到Maven仓库,IntelliJ IDEA 配置一下即可使用

EquatorCoco

Java DDD IDEA

实例带你了解GaussDB数据库的LOCK TABLE

华为云开发者联盟

数据库 后端 华为云 华为云GaussDB 华为云开发者联盟

火山引擎“数据飞轮”助力哪吒汽车智能创新

Geek_2d6073

图数据库基准测试 LDBC SNB 系列讲解:Schema 和数据生成的机制

NebulaGraph

图数据库 LDBC

ICP Rust CDK|IC和Rust简介

TinTinLand

学习 黑客 编程语言 web3 ICP

印刷行业实施MES管理系统有哪些重要的意义

万界星空科技

工业互联网 制造业 mes 印刷行业 万界星空科技

ToDesk专属功能全解析:多屏操作与高效协作并重

小喵子

远程办公 远程协作 远程控制 远程桌面 多屏

充电桩组装行业生产管理MES系统解决方案

万界星空科技

制造业 生产管理系统 mes 充电桩 汽车充电桩

微信小程序管理软件助力企业数字化转型

Geek_2305a8

RDS for MariaDB“智能DBA助手”,让运维效率嗖嗖地!

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟

在Elasticsearch中应用机器学习排序LTR_语言 & 开发_OpenSource Connections_InfoQ精选文章