首页 > 编程语言 >Python-与-Jax-现代推荐系统构建指南-全-

Python-与-Jax-现代推荐系统构建指南-全-

时间:2024-06-17 19:04:43浏览次数:10  
标签:指南 Jax 模型 Python 推荐 项目 用户 一个 我们

Python 与 Jax 现代推荐系统构建指南(全)

原文:annas-archive.org/md5/da17d05291861831978609329c481581

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

你是如何找到这本书的?是在网站上看到广告吗?也许是朋友或导师建议的;或者你在社交媒体上看到了提到它的帖子。也许你是在书店的书架上发现它的——一家你信任的地图应用带你去的书店?不管你是怎么找到它的,你几乎肯定是通过推荐系统来到这本书的。

实现和设计能够为用户提供建议的系统是任何业务中应用机器学习(ML)最流行和最重要的应用之一。无论您想帮助用户找到与他们口味相配的最佳服装、从在线商店购买的最吸引人的物品、丰富和娱乐他们的视频,最大程度地吸引来自他们网络的内容,还是那一天他们需要了解的新闻要点,推荐系统都提供了解决方案。

现代推荐系统的设计与其服务的领域一样多样化。这些系统包括用于实现和执行产品目标的计算机软件架构,以及排名的算法组件。用于排名推荐的方法可以来自传统的统计学习算法、线性代数的灵感、几何考虑,当然还有基于梯度的方法。正如算法方法多样化一样,推荐的建模和评估考虑也是如此:个性化排名、搜索推荐、序列建模以及所有这些的评分,现在对于与推荐系统工作的 ML 工程师来说都是必须知道的。

注意

从业者经常使用缩写 RecSys 来描述推荐系统领域。因此,在本书中,我们在提到该领域时使用 RecSys,而在提到我们构建的推荐系统时使用 recommendation system。

如果你是一个 ML 从业者,你可能已经意识到推荐系统,并且可能了解一两种最简单的建模方法,并能够对相关的数据结构和模型架构进行明智的讨论;然而,推荐系统经常超出数据科学和 ML 核心课程的范围。许多在行业中有多年经验的高级数据科学家对实际构建推荐系统知之甚少,并且在谈论这个话题时可能感到害怕。尽管借鉴了与其他 ML 问题类似的基础和技能,但 RecSys 拥有一个快速发展的充满活力的社区,这使得把建立推荐系统交给已经投入时间或愿意紧跟最新信息的其他数据科学家变得很容易。

本书存在的原因是打破这些感知障碍。在实际水平上理解推荐系统不仅对需要向用户提供内容的业务案例有用,而且 RecSys 的基本思想通常弥合了非常多其他类型的机器学习之间的鸿沟。例如,文章推荐系统可能利用自然语言处理(NLP)来找到文章的表达,利用序列建模来促进更长时间的参与,以及利用上下文组件允许用户查询来引导结果。如果您从纯学术兴趣的角度接近这一领域,无论您对数学的哪些方面感兴趣, sooner or later,都会出现与 RecSys 有关的链接或应用!

最后,如果与其他领域的联系、几乎所有数学的应用或明显的商业效用都不足以引起您对推荐系统的兴趣,那么令人惊叹的尖端技术可能会:RecSys 始终处于并超越机器学习的前沿。显而易见的收入影响的一个好处是,公司和从业者需要始终推动可能性的边界以及如何实现它。最先进的深度学习架构和最佳的代码基础设施被应用于这一领域。当你考虑到在 FAANG 的五个字母中,四个字母的核心——这个缩写代表着 Meta(以前称为 Facebook)、Apple、Amazon、Netflix 和 Google——至少有一个或多个推荐系统时,这并不奇怪。¹

作为一名从业者,您需要理解如何执行以下操作:

  • 将您的数据和业务问题视为 RecSys 问题

  • 识别关键数据以开始构建 RecSys

  • 确定适合您的 RecSys 问题的模型,以及您应该如何评估它们。

  • 实现、训练、测试和部署上述模型

  • 跟踪指标以确保您的系统按计划运行

  • 在学习有关用户、产品和业务案例的更多信息后逐步改进您的系统

本书阐明了完成这些步骤所需的核心概念和示例,无论是哪个行业或规模。我们将指导您完成建立推荐系统的数学、思想和实现细节——无论是您的第一个还是第五十个。我们将向您展示如何使用 Python 和 JAX 构建这些系统。

如果您还不熟悉,JAX 是来自 Google 的 Python 框架,旨在将自动微分和函数式编程范式作为一流对象。此外,它使用了一种特别适合来自各种背景的机器学习从业者的 NumPy API 风格。

我们将展示捕捉必要概念的代码示例和体系结构模型,并提供扩展这些系统到生产应用程序的方式。

本书使用的惯例

本书使用以下印刷惯例:

斜体

表示新术语、网址、电子邮件地址、文件名和文件扩展名。

固定宽度

用于程序列表,以及段落内引用程序元素如变量或函数名、数据库、数据类型、环境变量、语句和关键字。

固定宽度粗体

显示用户应按字面输入的命令或其他文本。

固定宽度斜体

显示应由用户提供值或由上下文确定值的文本。

提示

此元素表示提示或建议。

注意

此元素表示一般说明。

警告

此元素指示警告或注意事项。

使用代码示例

包含的代码片段引用的笔记本将在中等大小和大多数情况下免费资源上运行。为了便于进行实验和探索,我们通过 Google Colab 笔记本提供代码。

可下载补充材料(代码示例、练习等)位于ESRecsys on GitHub

如果您有技术问题或使用代码示例时遇到问题,请发送电子邮件至[email protected]

本书旨在帮助您完成工作。一般而言,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了大量代码,否则无需联系我们以获得许可。例如,编写使用本书多个代码块的程序不需要许可。销售或分发 O’Reilly 图书中的示例代码需要许可。引用本书并引用示例代码回答问题不需要许可。将本书大量示例代码整合到产品文档中需要许可。

我们感谢,但通常不要求归属。归属通常包括标题、作者、出版商和 ISBN。例如:“Python 和 JAX 中的建议系统 作者 Bryan Bischof 和 Hector Yee,2024 年版权 Bryan Bischof 和 Resonant Intelligence LLC,978-1-492-09799-0。”

如果您觉得您使用的代码示例超出了公平使用范围或上述许可,请随时通过[email protected]与我们联系。

O’Reilly 在线学习

注意

40 多年来,O’Reilly Media一直致力于提供技术和业务培训、知识和见解,以帮助公司取得成功。

我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专长。O’Reilly 的在线学习平台为您提供按需访问的实时培训课程、深度学习路径、交互式编码环境,以及来自 O’Reilly 和其他 200 多家出版商的广泛的文本和视频集合。更多信息,请访问http://oreilly.com

如何联系我们

请将关于本书的评论和问题寄给出版商:

我们为这本书创建了一个网页,在那里列出勘误、示例和任何额外信息。您可以访问此页面:https://oreil.ly/build_rec_sys_python_jax

获取有关我们的书籍和课程的新闻和信息,请访问https://oreilly.com

在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media

在 Twitter 上关注我们:https://twitter.com/oreillymedia

在 YouTube 上观看我们:https://youtube.com/oreillymedia

致谢

Hector 想要感谢他的丈夫,Donald,在写作过程中给予他的爱与支持,以及他的姐姐 Serena 经常寄来的零食。他还想将这本书献给已故的亲人。特别感谢 Google 的审阅者 Ed Chi、Courtney Hohne、Sally Goldman、Richa Nigam、Mingliang Jiang 和 Anselm Levskaya。感谢 Bryan Hughes 审阅维基百科代码。

Bryan 要感谢他在 Stitch Fix 的同事们,在那里他学习到了本书中许多关键的思想——特别是 Ian Horn 在迁移学习方面的耐心指导,Molly Davies 博士在实验和效果估计方面的指导,Mark Weiss 在理解可用性与推荐之间关系方面的深入合作,Reza Sohrabi 博士对变换器的介绍,Xi Chen 博士在推荐系统中使用图神经网络的鼓励,以及 Leland McInnes 博士在维度减少和近似最近邻方面的细致建议。Bryan 还从与 Natalia Gardiol 博士、Daniel Fleischman 博士、Andrew Ho 博士、Jason Liu、Dan Marthaler 博士、Chris Moody 博士、Oz Raza、Anna Schneider 博士、Ujjwal Sarin、Agnieszka Szefer 博士、Daniel Tasse 博士、Diyang Tang、Zach Winston 等人的交流中受益匪浅。除了他不可思议的 Stitch Fix 同事们,他特别感谢 Eric Bunch 博士、Lee Goerl 博士、Will Chernoff 博士、Leo Rosenberg 和 Janu Verma 多年来的合作。Brian Amadio 博士作为一位出色的同事最初建议他写这本书。Even Oldridge 博士鼓励他实际去尝试。Eugene Yan 和 Karl Higley——虽然他们都未曾见面,但对他有极大的启发。他要感谢对他职业生涯产生重要影响的 Zhongzhu Lin 博士和 Alexander Rosenberg 博士。Cianna Salvatora 协助进行早期文献综述,Valentina Besprozvannykh 在阅读早期草稿笔记和提供指导方面提供了极大帮助。

两位作者感谢 Tobias Zwingmann、Ted Dunning、Vicki Boykis、Eric Schles、Shaked Zychlinski 和 Will Kurt,在书稿上给予了大量细致的技术反馈——没有这些反馈,这本书将会难以理解。Rebecca Novack 力劝我们加入这个项目。Jill Leonard 从书稿中删除了近 100 处错误的利用一词,并在书稿文本上提供了大量耐心的合作。

¹ 有人可能会争论说苹果公司的核心推荐系统也是其公司的核心。虽然苹果应用商店确实是公司的重要战略产品,但我们仍然保守地给出四分之五的评估,并表示推荐系统不是苹果的主要盈利能力。

第一部分:热身

我们如何将所有数据放在正确的位置以训练推荐系统,并进行实时推断?

所以,你决定投身于推荐系统的世界!你是希望根据用户在广阔选择范围内的古怪偏好建议恰到好处的东西吗?如果是这样,那么你给自己设定了相当大的挑战!表面上,这些系统可能看起来很简单:如果用户 A 和用户 B 有相似的品味,那么 A 喜欢的东西,B 也可能喜欢。但是,就像所有看似简单的事物一样,等待着被探索的深度。

我们如何捕捉用户历史的本质并将其输入模型?我们将这个模型放在哪里,以便随时为推荐服务?我们如何确保它不会建议违反界限或违反业务规则的东西?协同过滤是我们的起点,一个指引明灯。但是,在它之外有一个整个宇宙,使这些系统运转,我们将一起探索它。

第一章:介绍

推荐系统是我们今天互联网发展的核心,并且是新兴科技公司的重要功能。除了打开网络广度给每个人的搜索排名外,每年还有更多应用推荐系统的新颖和令人兴奋的电影、所有朋友都在看的新视频,或者是公司支付高价展示给你的最相关广告。TikTok 的令人上瘾的 For You 页面,Spotify 的 Discover Weekly 播放列表,Pinterest 的板块建议以及 Apple 的 App Store 都是推荐系统技术的热门应用。如今,序列变压器模型、多模态表示和图神经网络是机器学习研发中最光明的领域之一,都被应用在推荐系统中。

任何技术的普遍性往往引发如何运作、为什么变得如此普遍以及我们是否能参与其中等问题。对于推荐系统来说,如何是相当复杂的。我们需要理解口味的几何形状,以及用户的少量互动如何在那个抽象空间中为我们提供一个GPS 信号。你将看到如何快速收集一组优秀的候选者,并将它们精细化为一组协调的推荐。最后,您将学习如何评估您的推荐器,构建服务推理的端点,并记录其行为。

我们将提出核心问题的各种变体,供推荐系统解决,但最终,激励问题的框架如下:

给定可能推荐的事物集合,根据特定目标选择适合当前上下文和用户的有序少数。

推荐系统的关键组成部分

随着复杂性和精密度的增加,让我们牢记系统的组成部分。我们将使用字符串图表来跟踪我们的组件,但在文献中,这些图表以多种方式呈现。

我们将确定并建立推荐系统的三个核心组件:收集者、排名器和服务器。

收集者

收集者的角色是了解可能推荐的事物集合及其必要的特征或属性。请注意,这个集合通常是基于上下文或状态的子集。

排名器

排名器的角色是接受收集者提供的集合,并根据上下文和用户的模型对其元素进行排序。

服务器

服务员的角色是接收排名器提供的有序子集,确保满足必要的数据模式,包括基本的业务逻辑,并返回请求的推荐数量。

例如,以餐馆服务员为例的款待场景:

当您坐下来看菜单时,不确定应该点什么。您问服务员:“你认为我应该点什么作为甜点?”

侍者检查他们的笔记,并说:“柠檬派已经卖完了,但人们真的很喜欢我们的香蕉奶油派。如果你喜欢石榴,我们会从头开始制作石榴冰淇淋;而且甜甜圈冰淇淋是不会错的——这是我们最受欢迎的甜点。”

在这个简短的交流中,侍者首先充当收集者:识别菜单上的甜点,适应当前的库存情况,并通过检查它们的笔记准备讨论甜点的特性。

接下来,侍者充当排名者;他们提到在受欢迎程度方面得分较高的项目(香蕉奶油派和甜甜圈冰淇淋),以及基于顾客特征的情境高匹配项目(如果他们喜欢石榴)。

最后,侍者口头提供建议,包括他们算法的解释特性和多个选择。

虽然这似乎有点卡通 ish,但请记住,将推荐系统的讨论落实到现实世界的应用中。在 RecSys 中工作的一个优点是灵感总是在附近。

最简单的可能的推荐者

我们已经建立了推荐者的组件,但要真正使其实用,我们需要看到它在实践中的运行情况。虽然这本书的大部分内容都专注于实际的推荐系统,但首先我们将从一个玩具开始,并从那里构建。

平凡推荐者

最简单的推荐者实际上并不是很有趣,但仍然可以在框架中演示。它被称为 平凡推荐者TR),因为它几乎没有逻辑:

def get_trivial_recs() -> Optional[List[str]]:
   item_id = random.randint(0, MAX_ITEM_INDEX)

   if get_availability(item_id):
       return [item_id]
   return None

请注意,这个推荐者可能返回一个特定的 item_idNone。还请注意,这个推荐者不接受任何参数,并且 MAX_ITEM_INDEX 是引用了一个超出范围的变量。忽略软件原则,让我们思考这三个组件:

收集者

生成了一个随机的 item_id。TR 通过检查 item_id 的可用性进行收集。我们可以争论说,获得 item_id 也是收集者的责任的一部分。有条件地,可推荐的事物的收集要么是 [item_id],要么是 None请回想 None 是集合论意义上的一个集合)。

排名者

TR(Trivial Recommender)在与无操作相比较;即,在集合中对 1 或 0 个对象进行排名时,对该集合的恒等函数是排名,所以我们只是不做任何事情,继续进行下一步。

服务器

TR 通过其 return 语句提供建议。在这个例子中指定的唯一模式是 ⁠Optional​[List[str]] 类型的返回类型。

这个推荐者,虽然不太有趣或有用,但提供了一个我们将在进一步开发中添加的框架。

最受欢迎的项目推荐者

最受欢迎的项目推荐者(MPIR)是包含任何效用的最简单的推荐者。你可能不想围绕它构建应用程序,但它在与其他组件一起使用时很有用,除了提供进一步开发的基础之外。

MPIR 正如它所说的那样工作;它返回最受欢迎的项目:

def get_item_popularities() -> Optional[Dict[str, int]]:
    ...
        # Dict of pairs: (item-identifier, count times item chosen)
        return item_choice_counts
    return None

def get_most_popular_recs(max_num_recs: int) -> Optional[List[str]]:
    items_popularity_dict = get_item_popularities()
    if items_popularity_dict:
        sorted_items = sorted(
            items_popularity_dict.items(),
            key=lambda item: item[1]),
            reverse=True,
        )
        return [i[0] for i in sorted_items][:max_num_recs]
    return None

在这里,我们假设get_item_popularities知道所有可用项目及其被选择的次数。

这个推荐系统试图返回可用的k个最受欢迎的项目。虽然简单,但这是一个有用的推荐系统,是构建推荐系统时的一个很好的起点。此外,我们将看到这个例子一次又一次地返回,因为其他推荐器使用这个核心并逐步改进内部组件。

让我们再次看看我们系统的三个组成部分:

收集器

MPIR 首先调用get_item_popularities——通过数据库或内存访问——知道哪些项目可用以及它们被选择的次数。为方便起见,我们假设项目以字典形式返回,键由标识项目的字符串给出,值表示该项目被选择的次数。我们在这里暗示假设不出现在此列表中的项目不可用。

排名器

在这里,我们看到我们的第一个简单的排名器:通过对值进行排序来排名。因为收集器组织了我们的数据,使得字典的值是计数,所以我们使用 Python 内置的排序函数sorted。请注意,我们使用key指示我们希望按元组的第二个元素排序——在这种情况下,相当于按值排序——并发送reverse标志来使我们的排序降序。

服务器

最后,我们需要满足我们的 API 模式,这再次通过返回类型提示提供:Optional[List[str]]。这表示返回类型应为可空列表,其中包含我们推荐的项目标识字符串,因此我们使用列表推导来获取元组的第一个元素。但等等!我们的函数有一个max_num_recs字段——它可能在做什么?当然,这暗示我们的 API 模式希望响应中不超过max_num_recs个结果。我们通过切片操作来处理这个问题,但请注意,我们的返回结果在 0 和max_num_recs之间。

考虑到你手头的 MPIR 所提供的可能性;在每个一级类别中推荐客户最喜欢的项目可能会成为电子商务推荐的一个简单但有用的第一步。当天最受欢迎的视频可能会成为你视频网站主页的良好体验。

对 JAX 的简要介绍

由于这本书标题中含有JAX,我们将在这里提供对 JAX 的简要介绍。其官方文档可以在JAX 网站上找到。

JAX 是一个用 Python 编写数学代码的框架,它是即时编译的。即时编译允许相同的代码在 CPU、GPU 和 TPU 上运行。这使得编写利用向量处理器并行处理能力的高性能代码变得容易。

此外,JAX 的设计哲学之一是支持张量和梯度作为核心概念,使其成为利用梯度为基础的学习在张量形状数据上的理想工具。玩转 JAX 的最简单方式可能是通过Google Colab,这是一个托管在网络上的 Python 笔记本。

基本类型、初始化和不可变性

让我们从学习 JAX 类型开始。我们将在 JAX 中构建一个小的三维向量,并指出 JAX 和 NumPy 之间的一些区别:

import jax.numpy as jnp
import numpy as np

x = jnp.array([1.0, 2.0, 3.0], dtype=jnp.float32)

print(x)
[1. 2. 3.]

print(x.shape)
(3,)

print(x[0])
1.0

x[0] = 4.0
TypeError: '<class 'jaxlib.xla_extension.ArrayImpl'>'
object does not support item assignment. JAX arrays are immutable.

JAX 的接口与 NumPy 的接口大部分相似。我们按惯例导入 JAX 的 NumPy 版本作为jnp,以区分它和 NumPy(np),这样我们就知道要使用哪个数学函数的版本。这是因为有时我们可能希望在像 GPU 或 TPU 这样的向量处理器上运行代码,这时我们可以使用 JAX,或者我们可能更喜欢在 CPU 上使用 NumPy 运行一些代码。

首先要注意的是 JAX 数组具有类型。典型的浮点类型是float32,它使用 32 位来表示浮点数。还有其他类型,如float64,具有更高的精度,以及float16,这是一种半精度类型,通常仅在某些 GPU 上运行。

另一个要注意的地方是 JAX 张量具有形状。通常这是一个元组,因此(3,)表示沿第一个轴的三维向量。矩阵有两个轴,而张量有三个或更多个轴。

现在我们来看看 JAX 与 NumPy 不同的地方。非常重要的是要注意“JAX—The Sharp Bits”来理解这些差异。JAX 的哲学是关于速度和纯度。通过使函数纯粹(没有副作用)并使数据不可变,JAX 能够向其所使用的加速线性代数(XLA)库提供一些保证。JAX 保证这些应用于数据的函数可以并行运行,并且具有确定性结果而没有副作用,因此 XLA 能够编译这些函数并使它们比仅在 NumPy 上运行时更快地运行。

您可以看到修改x中的一个元素会导致错误。JAX 更喜欢替换数组x而不是修改它。修改数组元素的一种方法是在 NumPy 中进行,而不是在 JAX 中进行,并在随后的代码需要在不可变数据上快速运行时将 NumPy 数组转换为 JAX——例如,使用jnp.array(np_array)

索引和切片

另一个重要的学习技能是索引和切片数组:

x = jnp.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=jnp.int32)

# Print the whole matrix.
print(x)
[[1 2 3]
 [4 5 6]
 [7 8 9]]

# Print the first row.
print(x[0])
[1 2 3]

# Print the last row.
print(x[-1])
[7 8 9]

# Print the second column.
print(x[:, 1])
[2 5 8]

# Print every other element
print(x[::2, ::2])
[[1 3]
 [7 9]]

NumPy 引入了索引和切片操作,允许我们访问数组的不同部分。一般来说,符号遵循start:end:stride约定。第一个元素指示从哪里开始,第二个指示结束的位置(但不包括该位置),而步长表示跳过的元素数量。该语法类似于 Python range 函数的语法。

切片允许我们优雅地访问张量的视图。切片和索引是重要的技能,特别是当我们开始批处理操作张量时,这通常是为了充分利用加速硬件。

广播

广播是 NumPy 和 JAX 的另一个要注意的特性。当应用于两个不同大小的张量的二元操作(如加法或乘法)时,具有大小为 1 的轴的张量会被提升到与较大张量相匹配的秩。例如,如果形状为 (3,3) 的张量乘以形状为 (3,1) 的张量,则在操作之前会复制第二个张量的行,使其看起来像形状为 (3,3) 的张量:

x = jnp.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=jnp.int32)

# Scalar broadcasting.
y = 2 * x
print(y)
[[ 2  4  6]
 [ 8 10 12]
 [14 16 18]]

# Vector broadcasting. Axes with shape 1 are duplicated.
vec = jnp.reshape(jnp.array([0.5, 1.0, 2.0]), [3, 1])
y = vec * x
print(y)
[[ 0.5  1.   1.5]
 [ 4.   5.   6. ]
 [14.  16.  18. ]]

vec = jnp.reshape(vec, [1, 3])
y = vec * x
print(y)
[[ 0.5  2.   6. ]
 [ 2.   5.  12. ]
 [ 3.5  8.  18. ]]

第一种情况是最简单的,即标量乘法。标量在整个矩阵中进行乘法。在第二种情况中,我们有一个形状为 (3,1) 的向量乘以矩阵。第一行乘以 0.5,第二行乘以 1.0,第三行乘以 2.0。然而,如果向量已经重塑为 (1,3),则列将分别乘以向量的连续条目。

随机数

伴随 JAX 的纯函数哲学而来的是其特殊的随机数处理方式。因为纯函数不会造成副作用,一个随机数生成器不能修改随机数种子,不像其他随机数生成器。相反,JAX 处理的是随机数密钥,其状态被显式地更新:

import jax.random as random

key = random.PRNGKey(0)
x = random.uniform(key, shape=[3, 3])
print(x)
[[0.35490513 0.60419905 0.4275843 ]
 [0.23061597 0.6735498  0.43953657]
 [0.25099766 0.27730572 0.7678207 ]]

key, subkey = random.split(key)
x = random.uniform(key, shape=[3, 3])
print(x)
[[0.0045197  0.5135027  0.8613342 ]
 [0.06939673 0.93825936 0.85599923]
 [0.706004   0.50679076 0.6072922 ]]

y = random.uniform(subkey, shape=[3, 3])
print(y)
[[0.34896135 0.48210478 0.02053976]
 [0.53161216 0.48158717 0.78698325]
 [0.07476437 0.04522789 0.3543167 ]]

首先,JAX 要求你从种子创建一个随机数 key。然后将这个密钥传递给类似 uniform 的随机数生成函数,以创建范围在 0 到 1 之间的随机数。

要创建更多的随机数,然而,JAX 要求你将密钥分为两部分:一个新密钥用于生成其他密钥,一个子密钥用于生成新的随机数。这使得 JAX 即使在许多并行操作调用随机数生成器时,也能确定性地和可靠地复现随机数。我们只需将一个密钥分成需要的许多并行操作,所得的随机数现在既是随机分布的,又是可重现的。这在你希望可靠地复现实验时是一种良好的特性。

即时编译

当我们开始使用 JIT 编译时,JAX 在执行速度上开始与 NumPy 有所不同。JIT 编译——即时将代码转换为即时编译——允许相同的代码在 CPU、GPU 或 TPU 上运行:

import jax

x = random.uniform(key, shape=[2048, 2048]) - 0.5

def my_function(x):
  x = x @ x
  return jnp.maximum(0.0, x)

%timeit my_function(x).block_until_ready()
302 ms ± 9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

my_function_jitted = jax.jit(my_function)

%timeit my_function_jitted(x).block_until_ready()
294 ms ± 5.45 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

JIT 编译的代码在 CPU 上速度提升不多,但在 GPU 或 TPU 后端上速度会显著提升。当函数第一次调用时,编译也会带来一些开销,这可能会使第一次调用的时间偏离。能够 JIT 编译的函数有一些限制,比如主要在内部调用 JAX 操作,并对循环操作有限制。长度可变的循环会触发频繁的重新编译。“Just-in-Time Compilation with JAX” 文档详细介绍了许多 JIT 编译函数的细微差别。

摘要

虽然我们还没有进行太多的数学工作,但我们已经到了可以开始提供推荐和实现这些组件更深层逻辑的阶段。我们很快将开始做一些看起来像是机器学习的事情。

到目前为止,我们已经定义了推荐问题的概念,并设置了我们推荐系统的核心架构——收集器、排名器和服务器,并展示了几个简单的推荐器来说明这些部件如何组合在一起。

接下来,我们将解释推荐系统试图利用的核心关系:用户-物品矩阵。这个矩阵使我们能够构建个性化模型,从而进行排名。

第二章:用户-物品评分及问题的构建

如果你被要求为当地咖啡馆的奶酪拼盘进行策划,你可能会从你的最爱开始。你也可能会花一点时间询问你朋友的最爱。在为咖啡馆订购大量这些奶酪之前,你可能会想进行一次小实验 —— 也许请一群朋友品尝你的选择,并告诉你他们的偏好。

除了收到朋友们的反馈意见外,你还将了解到有关朋友和奶酪的信息。你将会了解到朋友们喜欢哪种类型的奶酪,哪些朋友口味相似。你还可以了解到哪些奶酪最受欢迎,以及哪些奶酪被同一些人喜欢。

这些数据将开始为你的第一个奶酪推荐系统提供线索。在本章中,我们将讨论如何将这个想法转化为推荐系统的正确组成部分。通过这个例子,我们将讨论推荐系统的一个基本概念:如何预测用户对他们从未见过的东西的喜好。

用户-物品矩阵

非常常见的是听到从事推荐系统工作的人谈论矩阵,特别是用户-物品矩阵。虽然线性代数在数学上很深奥,但它在推荐系统中的应用很简单直观。

在我们讨论矩阵形式之前,让我们先列出一些用户集合和物品集合之间的二元关系。为了举例,想象一组五个朋友(神秘地被命名为A, B, C, D, E)和一个盲目品尝奶酪的场景,包括四种奶酪(gouda, chèvre, emmentaler, brie)。朋友们被要求对这些奶酪评分,从 1 到 4:

  1. A开始说:“好吧,我真的很喜欢gouda,所以给它 5 分;chèvreemmentaler也很好吃,给 4 分;而brie太难吃了,只给 1 分。”

  2. B回答说:“什么?!brie是我的最爱!4.5!chèvreemmentaler很好,3;而gouda只是还行,2。”

  3. C分别给出 3、2、3 和 4 的评分。

  4. D给出 4、4、5 的评分,但我们在D试之前已经没brie了。

  5. E开始感觉不舒服,并只尝试了gouda,给了它 3 分。

你可能首先注意到这种解说性写作有点乏味并且难以解析。让我们在一个方便的表格中总结这些结果(表 2-1):

表 2-1. 奶酪及评分

奶酪品鉴师 Gouda Chèvre Emmentaler Brie
A 5 4 4 1
B 2 3 3 4.5
C 3 2 3 4
D 4 4 5 -
E 3 - - -

你的第一个直觉可能是将这些内容写成更适合计算机的形式。你可以创建一系列的列表:

A : [ 5 , 4 , 4 , 1 ] B : [ 2 , 3 , 3 , 4 . 5 ] C : [ 3 , 2 , 3 , 4 ] D : [ 4 , 4 , 5 , - ] E : [ 3 , - , - , - ]

在某些情况下这可能有效,但你可能希望更清楚地表明每个列表中的位置意义。你可以简单地使用热图来可视化这些数据(图 2-1):

import seaborn as sns

_ = np.nan
scores = np.array([[5,4,4,1],
    [2,3,3,4.5],
    [3,2,3,4],
    [4,4,5,_],
    [3,_,_,_]])
sns.heatmap(
    scores,
    annot=True,
    fmt=".1f",
    xticklabels=['Gouda', 'Chevre', 'Emmentaler', 'Brie',],
    yticklabels=['A','B','C','D','E',]
)

评分热图

图 2-1. 奶酪评分矩阵

当我们观察到拥有大量用户或物品,并且稀疏度越来越高的数据集时,我们需要使用更适合表示必要数据的数据结构。存在各种所谓的稠密表示,但现在我们将使用最简单的形式:user_iditem_idrating的元组。在实践中,这种结构通常是一个由 ID 提供索引的字典。

稠密和稀疏表示

这些数据的两种类型结构是稠密和稀疏表示。宽泛地说,稀疏表示是这样一种表示,即每个非平凡观察都存在一个数据。稠密表示总是包含每个可能性的数据,即使是在平凡(null或零)情况下也是如此。

让我们看看这些数据作为字典的样子:

'indices': [
  (0,0),(0,1),(0,2),(0,3),
  (1,0),(1,1),(1,2),(1,3),
  (2,0),(2,1),(2,2),(2,3),
  (3,0),(3,1),(3,2),
  (4,0)
]
'values': [
  5,4,4,1,
  2,3,3,4.5,
  3,2,3,4,
  4,4,5,
  3
]

出现了几个自然的问题:

  1. 从迄今的观察来看,最受欢迎的奶酪是什么?看起来emmentaler可能是最喜欢的,但E没有尝试过emmentaler

  2. D会喜欢brie吗?看起来这是一种有争议的奶酪。

  3. 如果你只被要求买两种奶酪,你应该买哪两种来最好地满足所有人的需求?

这个例子及其相关问题故意简单,但其要点是很明确的,即这种矩阵表示至少对捕捉这些评分是方便的。

也许并不明显的是,除了这种数据可视化的便利性之外,这种表示法在数学上的实用性也是存在的。问题 2 暗示了一个固有的推荐系统问题:“预测用户未看到的物品会喜欢多少。”这个问题也可以被认出是线性代数课程中的问题:“如何从我们知道的元素中填充矩阵的未知元素?”这被称为矩阵完成

在创造捕捉用户需求的用户体验和数学建模数据及需求之间的反复过程中,推荐系统的核心问题浮出水面。

用户-用户与物品-物品协同过滤

在我们深入线性代数之前,让我们考虑一下纯数据科学的视角,称为协同过滤CF),这个术语最初是由 David Goldberg 等人在他们 1992 年的论文“Using Collaborative Filtering to Weave an Information Tapestry”中使用的。

CF 的基本思想是,具有相似品味的人可以帮助其他人了解他们喜欢的东西,而无需自己去尝试。协同术语最初是指类似品味的用户之间的协同,过滤最初是指过滤掉人们不喜欢的选择。

你可以用两种方式来思考这种协同过滤策略:

  • 两个具有相似品味的用户将继续拥有相似的品味。

  • 两个具有相似用户粉丝的物品将继续受到其他类似粉丝的用户的欢迎。

这些听起来可能相同,但在数学解释上它们看起来不同。在高层次上,区别在于你的推荐系统应该优先考虑哪种相似性:用户相似性还是物品相似性。

如果你优先考虑 用户相似性,那么为了为用户 A 提供推荐,你会找到一个类似的用户 B,然后从 B 喜欢的内容列表中选择一个 A 还没看过的推荐。

如果你优先考虑 物品相似性,那么为了为用户 A 提供推荐,你会找到一个 A 喜欢的物品 奶酪,然后找到一个与 奶酪 相似的 A 还没看过的物品 埃门塔勒,并向 A 推荐它。

稍后我们将深入探讨相似性,但让我们快速将这些想法与前面的讨论联系起来。相似的用户 是用户-物品矩阵中作为向量相似的行;相似的物品 是作为向量相似的用户-物品矩阵中的列。

向量相似性

点积相似性 在 第十章 中有更精确的定义。暂时来说,相似性是通过将向量归一化然后计算它们的余弦相似度来计算的。给定任何类型的实体,你已经将它们关联到向量(数字列表)中,向量相似性 比较的是哪些实体在特征上最相似,这些特征由这些数字列表(称为 潜在空间)捕获。

Netflix 挑战

2006 年,Netflix 启动了一个名为 Netflix 奖 的在线竞赛。这个竞赛挑战团队在由公司发布的开放数据集上改进 Netflix 的 CF 算法的性能。虽然像 Kaggle 或者会议网站上的这类竞赛如今已经很普遍,但在当时对于对推荐系统感兴趣的人来说,这是非常令人兴奋和新颖的。

这个竞赛包括多个中间回合,颁发进步奖以及 2009 年颁发的最终 Netflix 奖。提供的数据集包括 2,817,131 个三元组,包括 (用户,电影,评分日期)。其中一半还包括评分本身。请注意,就像我们前面的例子一样,用户-物品信息几乎足以指定问题。在这个特定的数据集中,日期是已提供的信息。稍后,我们将探讨时间可能是如何成为因素的,特别是对于顺序推荐系统。

这个竞赛的赌注非常高。击败内部性能的要求是将均方根误差(RMSE)提高 10%;稍后我们将讨论这个损失函数。奖金总额超过了 110 万美元。最终的获胜者是 BellKor's Pragmatic Chaos(其实也赢得了之前两个进步奖)的测试 RMSE 为 0.8567。最终,仅仅是 20 分钟的提前提交时间使 BellKor 领先于竞争对手 The Ensemble。

要详细了解获胜作品,请查看“Netflix 大奖的 BigChaos 解决方案”由 Andreas Töscher 和 Michael Jahrer 以及相同作者的“Netflix Prize 2008 的 BigChaos 解决方案”。与此同时,让我们回顾一下这场比赛中的几个重要教训:

首先,我们看到我们讨论过的用户-项目矩阵在这些解决方案中出现为关键的数学数据结构。模型选择和训练很重要,但参数调整在几种算法中提供了巨大的改进。我们将在后续章节中回到参数调整。作者指出,几种模型创新来自于反思业务用例和人类行为,并尝试在模型架构中捕捉这些模式。接下来,线性代数方法导致了第一个相对性能良好的解决方案,并在此基础上构建了获胜模型。最后,为了获得 Netflix 最初要求赢得比赛的性能,花费了很长时间,而业务环境却发生了变化,解决方案不再有用

最后一点可能是机器学习开发者需要了解的重要的关于推荐系统的事情;请看以下提示。

从简单开始

快速构建一个可用的工作模型,并在模型仍然符合业务需求时进行迭代。

软评分

在我们品尝奶酪的例子中,每种奶酪都会收到一个数字评分或者客人未尝试过。这些是硬评分:无论奶酪是布里还是露琪,评分都是明确的,其缺失表明用户与项目之间缺乏互动。在某些情况下,我们需要处理表明用户确实与项目进行了互动但未提供评分的数据。

一个常见的例子是电影应用程序;用户可能使用应用程序观看了电影,但未提供星级评分。这表明项目(在这种情况下是电影)已被观察到,但我们的算法没有学习的评分。然而,我们仍然可以使用这些隐含数据来执行以下操作:

  • 从未来的推荐中排除这个项目

  • 将这些数据作为我们学习者中的一个单独项使用

  • 分配一个默认的评分值,以指示“有趣但不足以评级”

结果表明,隐含评分对训练有效的推荐系统至关重要,不仅因为用户经常不提供硬评分,而且因为隐含评分提供了不同水平的信号。稍后,当我们希望训练多级模型以预测点击和购买可能性时,这两个级别将非常重要。

总结一下:

  • 当用户直接对项目的反馈提出请求时,会产生硬评分。

  • 当用户的行为在未直接回应提示的情况下隐含地传达对项目的反馈时,会产生软评分。

数据收集和用户记录

我们已经确定,我们既从显式评级学习,也从隐式评级学习,那么我们如何以及从哪里获取这些数据?为了深入探讨这一点,我们需要开始关注应用程序代码。在许多企业中,数据科学家和机器学习工程师与软件工程师是分开的,但是处理推荐系统需要两个功能之间的协调。

记录什么内容

最简单和最明显的数据收集是用户评级。如果用户可以选择提供评级,甚至是赞成或反对的选项,那么就需要构建该组件并存储这些数据。这些评级不仅需要存储,以便建立推荐,还要防止用户评级某物品后不久再次访问页面时评级消失,从而导致不好的用户体验。

同样地,了解几个其他关键互动也可以改进和扩展您的推荐系统:页面加载、页面浏览、点击和加入购物袋。

对于这些类型的数据,让我们使用一个稍微复杂的例子:电子商务网站 Bookshop.org。这个站点有多个推荐系统的应用,我们将逐步回到几乎所有这些应用。现在,让我们专注于一些互动(图 2-2)。

图书店首页

图 2-2. Bookshop.org 首页

页面加载

当您首次加载 Bookshop.org 时,页面上会显示项目。本周畅销书都是可点击的书籍列表的图片。尽管用户在加载此初始页面时没有选择,但记录这个初始页面加载的内容实际上非常重要。

这些选项代表用户看过的所有书籍的总体。如果用户看过某个选项,他们就有点击它的机会,这将最终成为一个重要的隐式信号。

倾向评分

考虑用户看过的所有项目的总体与倾向评分匹配密切相关。在数学上,倾向评分是观察单元被分配到处理组与对照组的概率。

将这种设置与简单的 50-50 A/B 测试进行比较:每个单元有 50% 的机会暴露于您的处理。在特征分层的 A/B 测试中,您有意根据某些特征或特征集(在这种情况下通常称为协变量)更改暴露的概率。这些暴露概率就是倾向评分。

为什么在这里提到 A/B 测试?稍后,我们将对用户偏好的软评级进行挖掘,但我们必须考虑到缺乏软评级可能并不意味着隐含的不良评级。回想起奶酪:品鉴员 D 没有机会评价 brie,因此没有理由认为 Dbrie 有偏好或厌恶。这是因为 D 没有接触到 brie

现在回想起 Bookshop.org:首页没有显示《银河系漫游指南》,所以用户无法点击它并隐式表达对该书的兴趣。用户可以使用搜索选项,但这是一种不同类型的信号——我们稍后会讨论,实际上是一种更强的信号。

在理解像“用户是否看过某物”这样的隐式评级时,我们需要正确考虑他们暴露给的所有选择,并使用该人群规模的倒数来衡量点击的重要性。因此,理解所有页面加载都很重要。

页面浏览和悬停

网站已经变得更加复杂,现在用户必须应对各种交互。图 2-3 展示了如果用户在本周畅销书籍的轮播中点击右箭头,然后将鼠标移动到《家庭烹饪》选项上会发生什么。

Bookshop 畅销书籍

图 2-3. Bookshop.org 畅销书籍

用户揭示了一个新选项,并通过将鼠标悬停在其上,使其变大并产生视觉效果。这些都是向用户传达更多信息的方法,并提醒用户这些选项是可点击的。对于推荐系统来说,这些点击可以被用作更多的隐式反馈。

首先,用户点击了轮播滚动条——因此他们在轮播中看到的部分内容足够有趣,以便进一步挖掘。其次,他们将鼠标悬停在《家庭烹饪》上,这可能会导致点击,也可能只是想看看在悬停时是否提供了额外信息。许多网站使用悬停交互来提供弹出式详细信息。尽管 Bookshop.org 没有实现类似的功能,但互联网用户已经习惯了所有实现此行为的网站,因此这个信号仍然具有意义。第三,用户现在在轮播滚动中发现了一个新的潜在项目——我们应该将其添加到我们的页面加载中,但评分较高,因为它需要交互才能揭示。

所有这些信息和更多信息都可以编码到网站的日志中。丰富和详细的日志记录是改善推荐系统的最重要方法之一。比你实际需要的更多的日志数据几乎总是比相反情况更好。

点击

如果您认为悬停意味着兴趣,那么等您考虑到点击时会怎样!在大多数情况下,点击是产品兴趣的强有力指标。对于电子商务来说,点击经常作为推荐团队核心关键绩效指标(KPI)的一部分计算。

这是出于两个原因:

  • 点击几乎总是购买所必需的,因此对于大多数业务交易来说,它是一个上游过滤器。

  • 点击需要明确的用户操作,因此它是意图的良好衡量指标。

当然,噪音总是存在的,但点击是客户兴趣的主要指标。许多生产推荐系统是基于点击数据训练的,而不是评级数据,因为点击行为和购买行为之间存在强烈的相关性,且数据量更大。

点击流数据

有时在推荐系统中,您会听到人们谈论点击流数据。这一重要的点击数据视图还考虑了用户在单个会话中点击的顺序。现代推荐系统致力于利用用户点击顺序,称之为顺序推荐,并通过这一额外的维度显示了显著的改进。我们将在第七章中讨论基于序列的推荐。

添加到购物袋

我们终于到了;用户已将物品添加到他们的购物袋、购物车或队列中。这是极其强烈的兴趣指示器,通常与购买行为高度相关。甚至有理由认为添加到购物袋比购买/订单/观看更好作为信号。添加到购物袋基本上是软评级的终点,通常在此之后您希望开始收集评级和评价。

印象

我们可能还希望记录那些未被点击的项目的印象。这为推荐系统提供了用户对不感兴趣项目的负反馈。例如,如果用户被提供了goudachèvreemmentaler这些奶酪,但用户只尝试了chèvre,也许用户不喜欢gouda。另一方面,他们可能还没来得及尝试emmentaler,所以这些印象可能只带来噪声信号。

收集和仪器化

Web 应用程序通常通过事件来仪表化我们讨论过的所有交互。如果您还不知道什么是事件,可以向您工程组织的同事询问,但我们也会为您提供简略信息。与日志记录类似,事件是应用程序在执行某段代码时发送的特殊格式消息。

正如点击的例子中,应用程序需要调用以获取下一个要向用户显示的内容,此时通常也会“触发事件”,指示有关用户的信息,他们点击了什么,会话 ID 以供以后参考,时间以及其他各种有用的细节。此事件可以在下游以任何方式处理,但有一种越来越普遍的路径分歧模式如下:

  • 一个日志数据库,如与服务绑定的 mySQL 应用程序数据库

  • 一个实时处理事件流

后者将很有趣:事件流通常通过诸如 Apache Kafka 之类的技术与侦听器连接。这种基础设施可能会迅速变得复杂(请咨询您的本地数据工程师或 MLOps 人员),但发生的简单模型是所有特定类型的日志都发送到您认为可以利用这些事件的几个目的地。

在推荐系统的情况下,事件流可以连接到一系列转换以处理下游的学习任务数据。如果您希望构建一个使用这些日志的推荐系统,这将非常有用。其他重要用途包括实时度量日志,用于了解网站上的实时情况。

漏斗

我们刚刚通过了我们的第一个漏斗示例,任何一个好的数据科学家都无法避免考虑它们。无论喜欢与否,漏斗分析对于评估你的网站以及扩展你的推荐系统至关重要。

点击流

漏斗是用户从一个状态到另一个状态必须执行的步骤集合;之所以称为漏斗,是因为在每个离散步骤,用户可能会停止继续进行,或者掉队,从而在每个步骤中减少人口数量。

在我们对事件和用户日志的讨论中,每个步骤都与前面的某个子集相关。这意味着该过程是一个漏斗,如图 2-4 所示。了解每个步骤的流失率揭示了你的网站和推荐的重要特征。

一个入门漏斗

图 2-4. 一个入门漏斗

可以在图 2-4 中考虑三种重要的漏斗分析:

  1. 页面浏览到加入购物袋用户流

  2. 页面浏览到每个推荐的加入购物袋

  3. 从加入购物袋到完成购买

第一个漏斗只是在高层次上识别用户在流程中每个步骤中的占比。这是你的网站优化的高级衡量标准,产品提供的一般吸引力,以及用户引导的质量。

第二个更为精细的漏斗考虑了推荐系统本身。正如在倾向性评分方面之前提到的那样,用户只有在看到某个物品时才能进入特定物品的推荐漏斗。这个概念与漏斗的使用相交汇,因为你希望从高层次理解某些推荐与漏斗流失的关系,但是同时,在使用推荐系统时,你对推荐的信心应该与漏斗指标很好地相关。我们将在第三部分详细讨论这个问题,但现在你应该记住考虑不同类别的推荐用户对和他们的漏斗可能与平均水平有何不同。

最后,我们可以考虑从加入购物车到完成购买。这实际上不是推荐系统问题的一部分,但作为试图改进产品的数据科学家或机器学习工程师,你应该时刻关注这一点。无论你的推荐有多好,这一漏斗可能会摧毁你的所有努力。在解决推荐问题之前,你几乎总是应该调查漏斗在从加入购物车到完成购买过程中的表现。如果流程中有任何复杂或困难之处,修复这些问题几乎肯定比改进推荐系统更有价值。调查流失情况,进行用户研究以理解可能的混淆因素,并与产品和工程团队合作,确保每个人在开始构建电子商务推荐系统之前都对这一流程有共识。

业务见解和用户喜好

在之前来自 Bookshop.org 的例子中,本周热销排行榜是页面上的主要轮播内容。回顾我们之前的get_most_popular_recs工作;轮播的动力仅仅是推荐系统应用于特定收藏者的结果——一个只看最近一周的收藏者。

这个轮播示例展示了推荐系统在提供业务见解的同时,也推动了推荐功能。增长团队的常见任务是理解每周的趋势和关键绩效指标,通常是每周活跃用户和新注册用户这样的指标。对于许多数字化公司来说,增长团队还对理解用户参与的主要驱动因素感兴趣。

让我们举个例子:截至目前,Netflix 的节目《鱿鱼游戏》成为了该公司有史以来最受欢迎的系列,刷新了大量记录。《鱿鱼游戏》在第一个月就吸引了 1.11 亿观众。显而易见,《鱿鱼游戏》需要在本周热门节目或最热门标题的轮播中亮相,但是像这样的爆款还应该在哪些地方受到重视呢?

公司几乎总是会首先寻求的第一个重要洞见是归因:如果数字在一周内上升,是什么导致了这一点?推动额外增长的重要或特别因素是什么?我们如何从这些信号中学习以在未来做得更好?在Squid Game这样的外语节目引起英语观众的巨大兴趣的情况下,高管可能会倾向于增加对来自韩国的节目或具有高戏剧性的字幕节目的投资。另一面同样重要:当增长指标滞后时,高管几乎总是会问为什么。能够指出什么是最受欢迎的,以及它可能如何偏离预期,会有很大帮助。

另一个重要的洞见可以反馈到推荐中;在像Squid Game这样令人兴奋的首播期间,当你看到所有指标都在上升时很容易被兴奋所影响,但这是否会对指标产生负面影响呢?如果你的节目和Squid Game在同一周或两周内首播,你可能对这一成功不那么热衷。总体而言,这种成功通常会带动增量增长,这对业务非常有利,整体而言,指标可能会全部看起来向好的方向发展。然而,其他项目可能由于核心用户群体之间的零和游戏而推出不成功。这可能会对长期指标产生负面影响,甚至可能使后续的推荐效果不佳。

后来,你将会了解到推荐的多样性;关于为何关心推荐多样化有很多理由,但我们在这里观察其中一个:多样化可以增加匹配用户与物品的整体能力。当你保持广泛的用户基础高度参与时,你增加了未来增长的机会。

最后,在突显热门命中之外,了解平台或服务上真正热门的另一个好处是广告。当一个现象开始时,通过引导和推广成功可以带来巨大优势。有时这会导致网络效应,在这些天病毒式内容和简易传播,这可以对平台的增长产生多重影响。

总结

这构成了制定推荐问题和为解决问题做好准备的最基本方面。

用户-物品矩阵为我们提供了一个工具,以数字评分的最简单情况总结用户和物品之间的关系,并且将在后续更复杂的模型中进行泛化。我们看到了第一个向量相似性的概念,它将扩展到深层次的相关性概念。接下来,我们了解了用户通过显式和隐式行动提供的信号类型。最后,我们学会了如何捕捉这些行动来训练模型。

现在我们完成了问题框架,接下来为您进行一些数学复习。别担心,您可以将尺子和圆规收起来,无需证明任何事情或计算任何积分。然而,您会看到一些重要的数学概念,这些概念将帮助您清晰地思考推荐系统的期望,并确保您提出正确的问题。

第三章:数学考虑

本书大部分内容侧重于实施和让推荐系统正常运行所需的实际考虑。在本章中,你将找到本书最抽象和理论性的概念。本章的目的是涵盖支撑该领域的一些基本思想。理解这些思想很重要,因为它们导致推荐系统中的病态行为,并激励许多架构决策。

我们将从讨论你经常在推荐系统中看到的数据形状开始,以及为什么这种形状可能需要仔细思考。接下来,我们将谈论推动大多数现代推荐系统的基本数学思想——相似性。我们将简要涵盖一种不同的思考推荐者作用的方式,适合那些更倾向于统计的人。最后,我们将使用与自然语言处理类比的方法来制定流行的方法。

推荐系统中的 Zipf 定律和马太效应

在许多机器学习应用中,早期就提到了一个警告:大语料库中唯一项的观察分布由Zipf 定律建模,即出现频率呈指数级下降。在推荐系统中,马太效应体现在热门项目的点击率或热门用户的反馈率上。例如,热门项目的点击量远远大于平均水平,而更积极参与的用户比平均水平给出更多评分。

马太效应

马太效应或流行偏见表明,最受欢迎的项目将继续吸引最多的注意力,并扩大与其他项目之间的差距。

MovieLens 数据集为例,这是一个用于基准推荐系统的极为流行的数据集。Jenny Sheng观察到图 3-1 中展示的电影评分行为:

Movierank Zipfian

图 3-1. 电影排名评分的 Zipf 分布

乍一看,评分的急剧下降显而易见,但这是否是问题呢?让我们假设我们的推荐系统将建立为基于用户的协同过滤(CF)模型——正如在第二章中所暗示的。那么这些分布会如何影响推荐系统呢?

我们将考虑这一现象的分布影响。让概率质量函数由简单的 Zipf 定律描述:

f ( k , M ) = 1/k n=1 M (1/n)

对于语料库中的M个标记(在我们的例子中是电影的数量),k是按出现次数排序的标记的排名。

让我们考虑用户AB,分别具有N A = | A |N B = | B |的评级。注意,V i,即第i个最流行的视频,在用户X X中出现的概率由以下公式给出:

P ( i ) = f(i,M) j=1 M f(j,M) = 1/i j=1 M 1/j

因此,一个项目出现在两个用户评级中的联合概率如下所示:

P ( i 2 ) = 1/i j=1 M 1/j 2

换句话说,两个用户共享其评级集中项目的概率随其受欢迎排名的平方而减小。

当我们考虑到我们尚未明确的基于用户的 CF 定义基于用户评级集中的相似性时,这变得很重要。这种相似性是两个用户共同评级项目的数量除以任一用户评级的总项目数

以此定义,例如,我们可以计算在用户AB之间共享项目的相似性分数:

i=1 M P(i 2 ) A B

然后,两个用户的平均相似性分数通过前述方程的重复应用被概括如下:

t=1 min(N A ,N B ) i k =i k-1 +1 t-1 i=1 M P(i k 2 ) A B t

通过前述观察的重复应用。

这些组合公式不仅指示了我们算法中 Zipfian 的相关性,而且我们还看到这对得分输出的几乎直接影响。考虑 Hao Wang 等人在《量化分析马太效应和推荐系统稀疏问题》中的实验,针对Last.fm 数据集中用户的平均相似性分数,作者发现这种马太效应在相似性矩阵中持续存在(参见图 3-2)。

Last.fm Matthew Effect

图 3-2。Last.fm 数据集上的马太效应

观察“热门”单元格与其他单元格之间的根本差异。明亮的单元格在大多数暗色单元格中为数不多,表明某些极其热门的项目在更普通频率接近零的项目中难以组合。虽然这些结果可能看起来令人担忧,但后来我们将考虑能够减轻马修效应的多样性感知损失函数。一个更简单的方法是使用下游采样方法,我们将作为探索利用算法的一部分进行讨论。最后,马修效应只是这种 Zipf 分布的两大主要影响之一,让我们把注意力转向第二个方面。

稀疏性

现在我们必须认识到稀疏性的存在。随着评分越来越偏向最受欢迎的项目,较不受欢迎的项目将因数据和推荐的匮乏而受到影响,这被称为数据稀疏性。这与线性代数中的定义相联系:向量中大多数为零或未被填充的元素。当您再次考虑我们的用户-物品矩阵时,不受欢迎的项目构成了具有少量条目的列;这些是稀疏向量。同样,在规模上,我们看到马修效应将更多的总评分推向某些列,并且矩阵在传统数学意义上变得稀疏。因此,稀疏性是推荐系统面临的一个极其知名的挑战。

正如之前所述,让我们从这些稀疏评分对我们的协同过滤算法的影响考虑一下。再次观察到,对于用户Xi th 最受欢迎的项目出现在 X的概率由以下给出:

P ( i ) = f(i,M) j=1 M f(j,M) = 1/i j=1 M 1/j

然后

( M - 1 ) * P ( i )

是预期的点击i th 最受欢迎项目的其他用户数量,因此总结所有i,得到与X共享评分的其他用户总数:

i=1 M ( M - 1 ) * P ( i )

再次,当我们回到整体趋势时,我们观察到这种稀疏性潜入到我们的协同过滤算法的实际计算中,考虑不同排名的用户的趋势,并看到他们的排名在其他用户的排名中协作(图 3-3)。

lastfm 用户相似性

图 3-3. Last.fm 数据集的用户相似性计数

我们看到这是一个始终要注意的重要结果:稀疏性将重点放在最受欢迎的用户身上,并有可能使您的推荐系统变得近视。

基于物品的协同过滤

虽然方程式不同,但在本节中,它们同样适用于基于物品的协同过滤。物品之间的相似性表现出与它们的分数中的 Zipf 分布的相同继承,并且在 CF 过程中咨询的物品按排名下降。

协同过滤的用户相似性

在数学中,经常听到讨论距离。甚至回溯到毕达哥拉斯定理,我们被教导将点之间的关系看作是距离或不相似性。事实上,这一基本思想被数学确立为度量的一部分:

d ( a , c ) d ( a , b ) + d ( b , c )

在机器学习中,我们通常更关注相似性的概念——这是一个极为相关的主题。在许多情况下,我们可以计算相似性或不相似性,因为它们是互补的;当 d : X × X [ 0 , 1 ] 是一个dissimilarity function,那么我们通常定义如下:

S i m ( a , b ) : = 1 - d ( a , b )

这可能看起来像是一个过分精确的陈述,但实际上你会看到,有多种选择可以用来构建相似性的框架。此外,有时我们甚至制定相似性度量,其中关联的距离度量并不在对象集合上建立度量。这些所谓的伪空间仍然可能非常重要,我们将展示它们在第十章中的应用场景。

在文献中,你会发现论文通常以介绍新的相似性度量开始,然后在该新度量上训练一个你之前见过的模型。正如你将看到的,你选择如何关联对象(用户、物品、特征等)可以对你的算法学到什么有很大影响。

现在,让我们专注于一些具体的相似性度量方法。考虑一个经典的机器学习问题,即聚类:我们有一个空间(通常是 n ),我们的数据在其中表示,并且被要求将数据分成子集,并为这些集合分配名称。这些集合经常旨在捕捉某种意义,或者至少对于总结集合元素的特征是有用的。

当你进行聚类时,你经常考虑在该空间中彼此接近的点。此外,如果给定一个新的观测值,并要求将其分配给一个集合作为推理任务,你通常计算新观测值的最近邻。这可以是k最近邻或者仅仅是最接近的集群中心的最近邻;无论哪种方式,你的任务是利用相似性的概念来关联——从而分类。在协同过滤中,这个相同的概念被用来将你希望推荐的用户与你已有数据的用户联系起来。

最近邻

最近邻 是一个总称,它源于一个简单的几何概念,即在给定某个空间(由特征向量定义的点)和该空间中的一个点的情况下,可以找到距离它最近的其他点。这在所有的机器学习中都有应用,包括分类、排名/推荐和聚类。“近似最近邻” 提供了更多细节。

如何为我们的用户在 CF 中定义相似性呢?他们显然不处于同一空间,所以我们通常的工具似乎不够用。

皮尔逊相关系数

我们最初的 CF 公式表明,口味相似的用户合作推荐物品给彼此。让两个用户 AB 有一组共同评分的项目 —— 简单地说就是每个人评分的项目集合,写成 A,B ,以及用户 A 对项目 x 的评分写成 r A,x 。那么以下是从 A 对其所有与 B 共同评分的项目的平均评分偏差之和:

x A,B ( r A,x - r ¯ A )

如果我们将这些评分视为随机变量,并考虑 B 的类似物,即联合分布变量之间的相关性(总体协方差)就是我们的 皮尔逊相关系数

USim A,B = x A,B (r A,x -r ¯ A )(r B,x -r ¯ B ) x A,B (r A,x -r ¯ A ) 2 x A,B (r B,x -r ¯ B ) 2

在这里记住一些细节非常重要:

  • 这是描述用户评分的联合分布变量的相似性。

  • 我们通过所有共同评分的项目来计算这一点,因此用户相似性是通过项目评分来定义的。

  • 这是一个取值范围在 [-1,1] 的成对相似度测量

相关性和相似性

在 第三部分 中,您将了解更多关于处理排名数据的 相关性相似性 的其他定义,特别是适合处理隐式排名的定义。

通过相似性进行评分

现在我们介绍了用户相似性,让我们来使用它吧!对于用户 A 和项目 x ,我们可以通过相似用户的评分来估计评分:

A f f A,i = r ¯ A + U

标签:指南,Jax,模型,Python,推荐,项目,用户,一个,我们
From: https://www.cnblogs.com/apachecn/p/18253009

相关文章

  • 无监督学习实用指南-全-
    无监督学习实用指南(全)原文:annas-archive.org/md5/5d48074db68aa41a4c5eb547fcbf1a69译者:飞龙协议:CCBY-NC-SA4.0序言机器学习的简要历史机器学习是人工智能的一个子领域,计算机通过数据学习,通常是为了在某些狭义定义的任务上提高性能,而无需显式编程。机器学习这个术语早......
  • Python-机器学习秘籍第二版-全-
    Python机器学习秘籍第二版(全)原文:annas-archive.org/md5/343c5e6c97737f77853e89eacb95df75译者:飞龙协议:CCBY-NC-SA4.0前言当本书的第一版于2018年出版时,填补了机器学习(ML)内容日益丰富的关键空白。通过提供经过充分测试的、实用的Python示例,使从业者能够轻松地复制和......
  • 【cv-python基础】不同数据集的解析
    前言数据集使用之前需要对标注文件进行解析,故此记录。代码实现1.APA数据集解析#20240612:parsejsonfiletolabeledimage.importargparseimportjsonimportosimportos.pathasospimportcv2ascvimportnumpyasnpjsonfilename="freespace_3Dbox_APA.j......
  • 2024华为OD机试真题-出租车计费 、靠谱的车-(C++/Python)-C卷D卷-100分
    2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++) 题目描述:程序员小明打了一辆出租车去上班。出于职业敏感,他注意到这辆出租车的计费表有点问题,总是偏大。出租车司机解释说他不喜欢数字4,所以改装了计费表,任何数字位置遇到数字4就直接跳过,其余功能都正常。比如:23再多......
  • 2024华为OD机试真题-API集群负载统计-(C++/Python)-C卷D卷-100分
     2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++)题目描述某个产品的RESTfulAPI集合部署在服务器集群的多个节点上,近期对客户端访问日志进行了采集,需要统计各个API的访问频次,根据热点信息在服务器节点之间做负载均衡,现在需要实现热点信息统计查询功能。RESTfulAPI是......
  • 用Python实现学生信息管理系统
    哈喽......
  • 基于springboot的南门桥社区疫情防疫系统-48138(免费领源码+数据库)可做计算机毕业设计J
    Springboot南门桥社区疫情防疫系统的设计与实现摘 要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对南门桥社区疫情防疫系统等问题,对南门桥社区......
  • python中的魔法方法
    魔法方法,重载方法,重载了一些内置的操作,一些等价于重载运算符__new__为构造函数__init__为初始化函数点击查看代码classMyClass:"""静态成员变量静态成员变量是被类的所有实例共享的访问方式:通过类名."""my_static_variable=0"""......
  • Python工具箱系列(五十三)
    ​​水印水印是一种常见的图片处理需求。当既需要展示,又需要保护知识产权时,就需要使用文字或者图片来打水印。下面的代码展示了文字水印与图片水印的过程。​--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdownfrompathlibimportPathfromPILimp......
  • 使用Jupyter(python+opencv)实现特别难的脚本-Day2
    Day2那昨天实现了这个自动挖土,我发现这个yb也是很扯0的东西,所以今天简单优化优化,完了再简单优化一下双手,写个yb吧。首先依旧是库一小堆儿fromPILimportImageimportpyautoguiimportrandomimportpygetwindowasgwimporttime然后那既然是优化那肯定是面向对象......