Elasticsearch:使用 LTR 进行个性化搜索

news2024/9/20 16:35:57

作者:来自 Elastic Max Jakob

如今,用户已经开始期待根据个人兴趣定制搜索结果。如果我们听的所有歌曲都是摇滚歌曲,那么在搜索 “Crazy” 时,我们会期望 Aerosmith 的歌曲排在搜索结果的首位,而不是 Gnarls Barkley 的歌曲。在本文中,我们将介绍个性化搜索的方法,然后以音乐偏好为例,深入探讨如何使用学习排名 (learning-to-rank: LTR) 来实现个性化搜索的具体方法。

排名因素

首先,让我们回顾一下一般搜索排名中哪些因素很重要。给定一个用户查询,相关性函数可以考虑以下一个或多个因素:

  • 文本相似度可以用多种方法测量,包括 BM25、密集向量相似度、稀疏向量相似度或通过交叉编码器模型。我们可以计算查询字符串与文档中多个字段(标题、描述、标签等)的相似度分数,以确定输入查询与文档的匹配程度。
  • 查询属性可以从查询本身推断出来,例如语言、命名实体或用户意图。领域将影响这些属性中的哪一个最有助于提高相关性。
  • 文档属性与文档本身有关,例如其受欢迎程度或文档所代表产品的价格。如果应用正确的权重,这些属性通常会对相关性产生很大影响。
  • 用户和上下文属性是指与查询或文档无关但与搜索请求上下文相关的数据,例如用户的位置、过去的搜索行为或用户偏好。这些信号将帮助我们个性化搜索。

个性化结果

当查看最后一类因素,即用户和上下文属性时,我们可以区分三种类型的系统:

  1. General -常规” 搜索不考虑任何用户属性。只有查询输入和文档属性决定搜索结果的相关性。输入相同查询的两个用户会看到相同的结果。当你启动 Elasticsearch 时,你就会拥有这样一个开箱即用的系统。
  2. 个性化搜索将用户属性添加到组合中。输入查询仍然很重要,但现在由用户和/或上下文属性补充。在此设置中,用户可以针对同一查询获得不同的结果,并且希望结果与个人更相关。
  3. 推荐更进一步,专注于文档、用户和上下文属性。这些系统没有主动提供的用户查询。许多平台在主页上推荐针对用户帐户量身定制的内容,例如基于购物历史或之前观看的电影。

如果我们将个性化视为一个范围,个性化搜索位于中间。用户输入和用户偏好都是相关性方程的一部分。这也意味着搜索中的个性化应该谨慎应用。如果我们过于看重过去的用户行为而忽视当前的搜索意图,那么当用户专门搜索其他内容时,他们可能会对自己最喜欢的文档感到失望。也许你也有过这样的经历:观看朋友发布的一段民间舞蹈视频,然后在搜索舞蹈音乐时找到了更多这样的视频。这里的教训是,确保用户拥有足够多的历史数据,以便自信地将搜索结果偏向某个方向,这一点很重要。还请记住,个性化主要会对模糊的用户输入和探索性查询产生影响。你的一般搜索机制应该已经涵盖了明确的导航查询。

个性化有很多方法。有基于规则的启发式方法,开发人员手动将用户属性匹配到特定文档集上,例如手动提升新用户的入职文档。也有低技术含量的方法从一般和个人结果列表中抽样结果。许多更有原则的方法使用基于项目相似性或协同过滤技术(例如 “客户也购买了”)训练的向量表示。你可以在网上找到很多关于这些方法的文章。在这篇文章中,我们将重点介绍如何使用学习排名。

使用 LTR 进行个性化

学习排序 (Learning-to-rank: LTR) 是创建相关性排序统计模型的过程。你可以将其视为自动调整不同相关性因素的权重。我们不是手动提出结构化查询和所有文本相似性、查询属性和文档属性的权重,而是训练一个模型,该模型在给定一些数据的情况下找到最佳权衡。数据以判断列表的形式出现。在这里,我们将研究使用 LTR 的基于行为的个性化,这意味着我们将利用过去的用户行为来提取将用于我们的 LTR 训练过程的用户属性。

重要的是要注意,为了取得成功,你应该在开始个性化之前已经在 LTR 之旅中取得了良好的进展:

  1. 你应该已经拥有 LTR。如果你想将 LTR 引入你的搜索,最好先优化你的通用(非个性化)搜索。那里可能会有一些唾手可得的成果,这将使你有机会在增加复杂性之前建立坚实的技术基础。处理用户相关数据意味着你在训练和评估期间需要更多数据,这变得更加棘手。我们建议你等到你的整体 LTR 设置处于稳定状态后再进行个性化。
  2. 你应该已经在收集使用数据。没有它,你将没有足够的数据来对你的相关性进行有意义的改进:冷启动问题。同样重要的是,你要对使用跟踪数据的正确性有高度的信心。错误发送的跟踪事件和错误的数据管道通常不会被检测到,因为它们不会引发任何错误,但最终的数据会歪曲实际的用户行为。随后基于这些数据的个性化项目可能不会成功。
  3. 你应该已经根据使用数据创建了判断列表。这个过程也称为点击建模,它既是一门科学,也是一门艺术。在这里,你无需手动标记搜索结果中的相关和不相关文档,而是使用点击信号(点击搜索结果、添加到购物车、购买、收听整首歌曲等)来估计用户在过去的搜索结果中看到的文档的相关性。你可能需要进行多次实验才能做到这一点。此外,这里还引入了一些偏见(最明显的是位置偏见)。你应该有信心,你的判断列表很好地代表了你的搜索的相关性。

如果所有这些都已确定,那么让我们继续添加个性化。首先,我们将深入研究特征工程。

特征工程 - Feature engineering

在特征工程中,我们会问自己,在特定搜索中可以使用哪些具体的用户属性来使结果更相关?我们如何将这些属性编码为排名特征?你应该能够准确地想象添加用户的位置如何提高结果质量。例如,代码搜索通常是与用户位置无关的用例。另一方面,音乐品味受到当地趋势的影响。如果我们知道搜索者在哪里,并且知道我们可以将文档归因于哪个地理位置,那么这就可以奏效。仔细考虑哪些用户特征和哪些文档特征可以协同工作是值得的。如果你无法想象这在理论上如何工作,那么可能不值得在你的模型中添加新特征。无论如何,你应该始终在训练后的离线和稍后的在线 A/B 测试中测试新特征的有效性。

一些属性可以直接从跟踪数据中收集,例如用户的位置或文档的上传位置。当涉及到表示用户偏好时,我们必须进行更多计算(如下所示)。此外,我们必须考虑如何将我们的属性编码为特征,因为所有特征都必须是数字。例如,我们必须决定是否将分类特征表示为由整数表示的标签,还是表示为多个二进制标签的独热编码(one-hot encoding)。

为了说明用户特征如何影响相关性排名,请考虑下面的虚构示例提升树,它可以成为音乐搜索引擎的 XGBoost 模型的一部分。训练过程了解了位置特征 “from France - 来自法国”(左侧)的重要性,并将其与其他特征(如文本相似性和文档特征)进行权衡。请注意,这些树通常更深,数量更多。我们在搜索和文档中都为位置特征选择了独热编码(one-hot encoding)。

请注意,添加的特征越多,这些树中需要的节点就越多。因此,在训练过程中需要更多的时间和资源才能达到收敛。从小处着手,衡量改进并逐步扩大。

示例:音乐偏好

我们如何在 Elasticsearch 中实现这一点?我们再次假设我们有一个音乐网站的搜索引擎,用户可以在其中查找和收听歌曲。每首歌曲都归类为高层次流派。示例文档可能如下所示:

{
  "title": "Personal Jesus",
  "artist": "Depeche Mode",
  "genre": "pop"
}

进一步假设我们已经建立了从使用数据中提取判断列表的方法。这里我们使用相关性等级从 0 到 3 作为示例,这可以通过无交互(no interaction)、点击结果(clicking on a result)、收听歌曲(listening to the song)和为歌曲点赞(thumbs-up rating for the song)来计算。这样做会在我们的数据中引入一些偏差,包括位置偏差(以后的文章中会对此进行详细介绍)。判断列表(judgement list)可能如下所示:

query_id  query   user_id  document_id  grade
q:1       jump    u:1      d:1          1
q:1       jump    u:1      d:2          3
q:1       jump    u:1      d:3          0
q:2       crazy   u:2      d:4          2
q:2       crazy   u:2      d:5          0

我们跟踪用户在我们网站上听过的歌曲,因此我们可以为每个用户构建一个音乐类型偏好(genre preferences)数据集。例如,我们可以回顾过去一段时间并汇总用户听过的所有类型。在这里,我们可以尝试不同类型的类型偏好表示,包括潜在特征,但为了简单起见,我们将坚持相对的收听频率。在这个例子中,我们希望针对单个用户进行个性化设置,但请注意,我们也可以根据用户细分进行计算(并使用细分 ID)。

user_id  user_hiphop  user_pop  user_rock
u:1      0.2          0.7       0.1
u:2      0.4          0.2       0.4
u:3      0.8          0.0       0.2

在计算时,最好将用户的活动量考虑在内。这可以追溯到上面的民间舞蹈示例。如果用户只与一首歌曲互动,那么流派偏好(genr preference)将完全偏向其流派。为了防止后续的个性化过分强调这一点,我们可以将互动次数添加为特征,以便模型可以学习何时将权重放在流派播放上。我们还可以在归一化之前平滑互动并为所有频率添加一个常数,这样它们就不会偏离低计数的均匀分布。这里我们假设后者。

上述数据需要存储在特征存储中,以便我们可以在训练和搜索时通过用户 ID 查找用户偏好值。你可以在此处使用专用的 Elasticsearch 索引,例如:

PUT genre-preferences/_doc/u:1
{
  "user_hiphop": 0,2,
  "user_pop": 0.7,
  "user_rock": 0.1
}

使用 user ID 作为 Elasticsearch 文档 ID,我们可以使用 Get API(见下文)来检索偏好值。从 Elasticsearch 版本 8.15 开始,必须在应用程序代码中完成此操作。另请注意,这些单独存储的特征值需要通过定期运行的作业进行刷新,以便在偏好随时间变化时保持值最新。

现在我们已准备好定义特征提取。在这里,我们对类型进行独热编码(one-hot-encode)。我们还计划在未来版本中启用将类别表示为整数的功能。

from eland.ml.ltr import LTRModelConfig, QueryFeatureExtractor

feature_extractors = [
    # Example text similarity feature
    QueryFeatureExtractor(
        feature_name="title_match",
        query={"match": {"title": "{{query}}"}},
    ),

    # One-hot encode genre categories. Make sure `genre` is of type `keyword`.
    QueryFeatureExtractor(
        feature_name="is_hiphop",
        query={
            "constant_score": {
                "filter": { "term": { "genre": "hiphop" } },
                "boost": 1,
            }
        },
    ),
    QueryFeatureExtractor(
        feature_name="is_pop",
        query={
            "constant_score": {
                "filter": { "term": { "genre": "pop" } },
                "boost": 1,
            }
        },
    ),
    QueryFeatureExtractor(
        feature_name="is_rock",
        query={
            "constant_score": {
                "filter": { "term": { "genre": "rock" } },
                "boost": 1,
            }
        },
    ),

    # Forward user preference values from the params as features
    QueryFeatureExtractor(
        feature_name="user_hiphop",
        query={
            "query": {"match_all": {}},
            "script_score": {"script": {"source": "{{user_hiphop}}"} },
        },
    ),
    QueryFeatureExtractor(
        feature_name="user_pop",
        query={
            "query": {"match_all": {}},
            "script_score": {"script": {"source": "{{user_pop}}"} },
        },
    ),
    QueryFeatureExtractor(
        feature_name="user_rock",
        query={
            "query": {"match_all": {}},
            "script_score": {"script": {"source": "{{user_rock}}"} },
        },
    ),
]

ltr_config = LTRModelConfig(feature_extractors)

现在,在应用特征提取时,我们必须首先查找流派偏好值并将其转发给特征记录器(feature logger)。根据性能,批量查找这些值可能会更好。

import numpy as np

PREFERENCES_INDEX = "genre-preferences"

def get_genre_preferences(es_client, index_name, user_id):
    return es_client.get(index=index_name, id=user_id)["_source"]

def extract_query_features(query_group):
    # get query string, user ID and document IDs from the judgment list
    query_string = query_group["query"].iloc[0]
    user_id = query_group["query"].iloc[0]
    doc_ids = query_group["doc_id"].astype("str").to_list()

    # get genre preference values from Elasticsearch index
    # (consider using mget outside this function in case of slowness)
    genre_preferences = get_genre_preferences(es_client, PREFERENCES_INDEX, user_id)

    # run the extraction
    search_params = {
        "query": query_string,
        "user_hiphop": genre_preferences["user_hiphop"],
        "user_pop": genre_preferences["user_pop"],
        "user_rock": genre_preferences["user_rock"],
    }
    features = feature_logger.extract_features(search_params, doc_ids)

    # add features as new columns
    for feature_index, feature_name in enumerate(ltr_config.feature_names):
        query_group[feature_name] = np.array(
            [doc_features[doc_id][feature_index] for doc_id in doc_ids]
        )

    return query_group


# extract features for all data with the same query ID
judgments_df.groupby("query_id", group_keys=False).apply(_extract_query_features)

特征提取后,我们就可以准备好训练数据了。请参阅之前的 LTR 帖子和随附的 notebook,了解如何训练和部署模型(并确保不要将 ID 作为特征发送)。

query_id  query   user_id  document_id  grade  title_match  is_hiphop  is_pop  is_rock  user_hiphop  user_pop  user_rock
q:1       jump    u:1      d:1          1      1.4          1          0       0        0.2          0.7       0.1
q:1       jump    u:1      d:2          3      1.4          0          0       1        0.2          0.7       0.1
q:1       jump    u:1      d:3          0      1.2          0          1       0        0.2          0.7       0.1
q:2       crazy   u:2      d:4          2      2.2          0          0       1        0.4          0.2       0.4
q:2       crazy   u:2      d:5          0      2.2          0          0       0        0.4          0.2       0.4

模型训练并部署后,你可以在这样的重新评分器中使用它。请注意,在搜索时,你还需要事先查找用户偏好值,并将这些值添加到查询中。

# inputs
user_query = "crazy"
user_id = "u:42"

# preference lookup
genre_preferences = get_genre_preferences(es_client, PREFERENCES_INDEX, user_id)

# search
query = {
  "match": {
    "title": user_query
  }
}

rescore = {
  "learning_to_rank": {
    "model_id": "my-genre-personalization-model",
    "params": {
      "query": user_query,
      "user_hiphop": genre_preferences["user_hiphop"],
      "user_pop": genre_preferences["user_pop"],
      "user_rock": genre_preferences["user_rock"]
    }
  },
  "window_size": 100
}

es_client.search(index="my-music-index", query=query, rescore=rescore)

现在,我们音乐网站中不同类型偏好的用户都可以从你的个性化搜索中受益。摇滚和流行音乐爱好者都会在搜索结果顶部找到他们最喜欢的歌曲版本 Crazy。

结论

添加个性化可能会提高相关性。个性化搜索的一种方法是通过 Elasticsearch 中的 LTR。我们已经研究了一些应该提供的先决条件,并进行了实际操作示例。

但是,为了突出重点,我们遗漏了几个重要细节。我们将如何评估模型?在模型开发期间可以应用离线指标,但最终必须通过真实用户的在线 A/B 测试来决定模型是否提高了相关性。我们如何知道我们是否使用了足够的数据?在此阶段投入更多资源可以提高质量,但我们需要知道在什么条件下这样做是值得的。我们如何建立一个好的判断列表并处理使用行为跟踪数据引入的不同偏差?部署后我们是否可以忘记我们的个性化模型,还是需要重复维护来解决偏差问题?其中一些问题将在 LTR 的未来帖子中得到解答,敬请期待。

准备好自己尝试一下了吗?开始免费试用。
想要获得 Elastic 认证吗?了解下一期 Elasticsearch 工程师培训何时举行!

原文:Personalized search with Elasticsearch Learning to Rank — Search Labs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2091622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用安信可Ai-WB2-12F开启wifi与手机通信TCP-IP(AT指令)

当时在做两个单片机之间无线通信,或者单片机与手机无线通信,就像找一个蓝牙和wifi双模的无线模块,一开始看ESP8684(ESP32-C2)这个芯片模组是有wifi和蓝牙的,买回来后才发现他不可以在程序运行中更换蓝牙或者…

《黑神话·悟空》这款游戏到底是用什么编程语言开发的?

你也有被这段游戏试玩视频刷屏吗? 13分钟、国产团队出品、B站上线不到24小时,播放量已经破千万,迅速火爆全网。 这就是来自国内游戏团队游戏科学(Game Science)开发的3A大作《黑神话:悟空》。 《黑神话悟…

vscode开发小程序

1 安装 "微信小程序开发工具" 2 安装 "WXML - Language Service" 3 安装 "wxmp-api-plugin" 或 "wechat-snippet" 4 安装"WXSS"

顶级的python入门教程!小白到大师,从这篇教程开始!

1. 为什么要学习Python? 学习Python的原因有很多,以下是几个主要的原因: 广泛应用:Python被广泛应用于Web开发、数据科学、人工智能、机器学习、自动化运维、网络爬虫、科学计算、游戏开发等多个领域。掌握Python意味着你可以在这…

嵌入式全栈开发学习笔记---Linux系统编程(进程间通信)

目录 进程间通信概述 进程通信目的 进程间通信的发展 进程间通信分类 管道通信 无名管道 有名管道mkfifo() 信号 发送信号kill & raise 忽略信号signal() 发送信号alarm() 消息队列 消息队列使用的步骤 创建消息队列msgget() 读写消息队列msgrcv()/msgsnd()…

ip地址一天变化好几次

‌IP地址每天变化的原因主要取决于其分配方式:静态或动态。静态IP地址是长期固定分配给一台设备的,除非进行手动更改或网络配置发生变化,否则该设备的IP地址将保持不变。而动态IP地址则是根据网络环境和需求动态分配给设备的,可能…

一些评估模型的总结(1)

最近学习了评估模型(如下所示),对这四种方法进行小总结。 目录 1. 层次分析法。(主观赋权方法,主观确定成对比较矩阵) 2. 熵权法(基于数据的客观赋权的方法) 3. topsis方法&…

【图论入门】图的存储

1.邻接矩阵 邻接矩阵是图论中用于表示图(Graph)结构的一种重要数据结构,特别适用于表示顶点之间连接关系的图形。在计算机科学和数学领域,它被广泛应用来编码无向图和有向图的信息。 特点: 1、无向图的邻接矩阵是对称…

Java:时区的用法

文章目录 ZoneId常见用法 ZonedDateTime常见方法 代码 黑马学习笔记 ZoneId 常见用法 ZonedDateTime 常见方法 代码 package NewTime;import java.time.Clock; import java.time.ZoneId; import java.time.ZonedDateTime;/*** Author: ggdpzhk* CreateTime: 2024-08-31*/ pu…

09:Logic软件原理图信号连通

原理图信号连通 快捷键:F2 2.添加网络名称

【React】为什么Hooks不能出现在判断中

前言 在 React 中,Hooks 不能写在条件语句中,如下面这段代码点击button后则会报错。 import { useEffect, useState } from "react"export default () > {const [count, setCount] useState(0)if (count > 0) {useEffect(() > {co…

4-4 初始化引导程序

基本原理的讲解 在loader所需要做的事情, 1 他这个检测内存的容量,我想知道是怎么做的。 2 然后就是模式的切换。 3 然后就是加载操作系统,并跳转到操作系统执行。 这是 他的总体的逻辑。 首先是加载 512 字节。 所以这512 字节的主要任务…

【Kubernetes部署篇】二进制搭建K8s高可用集群1.26.15版本

文章目录 一、服务器环境信息及部署规划1、K8S服务器信息及网段规划2、服务器部署架构规划3、组件版本信息 二、初始化环境操作1、关闭防火墙2、配置本地域名解析3、配置服务器时间保持一致4、禁用swap交换分区(K8S强制要求禁用)5、配置主机之间无密码登录6、修改Linux内核参数…

springboot 医院挂号系统 ---附源码91789

目录 1 绪论 1.1 研究背景 1.2研究意义 1.3论文结构与章节安排 2 医院挂号系统系统分析 2.1 可行性分析 2.2 系统功能分析 2.3 系统用例分析 2.4 系统流程分析 图2-5业务流程图 2.5本章小结 3 医院挂号系统总体设计 3.1 系统功能模块设计 3.2 数据库设计 3.4本章…

Python读取CSV文件的几种方法!

1、使用 csv 模块 首先,你需要导入csv模块: import csv接下来,你可以使用csv.reader()函数来读取CSV文件。假设你的CSV文件名为data.csv,它的内容如下: Name, Age, Salary John, 25, 5000 Alice, 30, 6000 Bob, 35,…

书生浦语实训营-InternVL 多模态模型部署微调实践

1.什么是InternVL InternVL 是一种用于多模态任务的深度学习模型,旨在处理和理解多种类型的数据输入,如图像和文本。它结合了视觉和语言模型,能够执行复杂的跨模态任务,比如图文匹配、图像描述生成等。 2.InternVL模型介绍 对于…

【自由能系列(初级)】生命负熵——熵增原理与生命秩序的对抗

【通俗理解】生命负熵——熵增原理与生命秩序的对抗 关键词提炼 #生命负熵 #熵增原理 #生命秩序 #薛定谔方程 #熵减过程 #热力学第二定律 #信息熵 #生命系统建模 #负熵流 #熵平衡 第一节:生命负熵的类比与核心概念 1.1 生命负熵的类比 生命负熵可以被视为生命系…

如何打造免费体育馆场地预约系统?php vue技术实现,简易操作指南

✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 |…

Unity(2022.3.41LTS) - 脚本

目录 零.简介 一、脚本的基本概念 二、脚本的创建和使用 三、脚本的编程基础 四、与 Unity 引擎的交互 五、重要的类介绍 六、事件函数介绍 七、事件函数的执行顺序 八、脚本的优化和调试 零.简介 在 Unity 中,脚本是实现游戏逻辑和交互的重要组成部分。 …

后台框架-统一数据格式2

在上一篇中,当在Controller类中需要返回统一格式的数据时,需要实例化一个R,有时候觉得还是不够简洁,那有没有一种方法Controller中直接返回对象,但是返回的对象统一保存到如下格式的data中? ResponseBody…