TikTok 的个性化推荐系统:世界上最有价值的 AI 系统

本章以案例研究的形式把我们迄今学到的知识汇集在一起。你将设计、构建并部署一个大规模运行、实时的个性化视频推荐系统。它的灵感来自 TikTok 的推荐系统——正是这个 AI 系统让 TikTok 通过实时 AI 创新把 YouTube 拉下了王座。我们将使用面向实时个性化 AI 系统的检索-排序架构(retrieval-and-ranking architecture)来构建我们的推荐系统。我们还将扩展视频推荐系统,加入用自然语言进行视频智能体搜索(agentic search)的功能。最后,我们将以 MLOps 的"十二大谬误"(dirty dozen of fallacies)来结束本书——希望你在读完本书后不再犯这些错误——并附上一些关于你作为 AI 系统构建者所肩负的伦理责任的建议。感谢你坚持读到这里,让我们开始动手做与 AI 打交道最有成就感的部分——构建能够让世界变得更好的真实 AI 系统。

推荐系统简介

推荐系统(recommender system)帮助用户在面向用户的系统中发现相关内容。内容可以是任何东西,从视频到音乐,从电子商务到社交媒体帖子。最早的推荐系统方法并不是个性化的。基于内容的推荐系统(content-based recommendation system)可以利用类型、导演、演员或情节关键词来推荐与用户此前观看并喜欢的视频相似的视频。训练基于内容的推荐模型只需要内容使用特征(content usage feature),因此它们很容易扩展。Netflix 和 YouTube 至今仍把基于内容的推荐作为它们提供的多种推荐类型之一。

下一类推荐系统建立在交互数据集(interaction dataset)之上,其中包含用户对内容的操作事件,例如观看、点赞和分享。物品到物品(item-to-item,i2i)推荐关注物品本身之间的关系,从而支持"购买了该商品的顾客还购买了……“或"如果你喜欢这个视频,你可能会喜欢……“之类的功能。交互数据集提供了共同消费的模式或相似性,i2i 方法让用户可以轻松探索相关选项。

用户到物品(user-to-item,u2i)推荐则采取不同的方法,把推荐的中心放在单个用户身上。这里的目标是根据用户的历史偏好和行为,或借鉴相似用户的经验,向用户推荐物品。i2i 和 u2i 推荐系统第一种被广泛使用的方法是协同过滤(collaborative filtering),但它在处理海量数据和稀疏数据(即大多数用户只与极小一部分物品互动)时面临挑战。因子分解机(factorization machine)被引入以更好地处理数据稀疏性,但在大数据量和实时更新方面也存在可扩展性问题。

在下一节中,我们将研究解决这些挑战的最先进的检索-排序架构,但我们将从查看构建推荐系统所需收集的数据开始。表 15-1 展示了用于训练视频推荐模型的常用特征。

分组特征变换数据量/更新速度
用户画像性别、年龄、语言、设备、兴趣、位置、最近观看与模型无关、与模型相关GB/TB 级,批处理和流式
视频标题、类型、时长、年龄、点击量、CTR、点赞、描述、内容与模型无关、与模型相关GB/TB 级,批处理和流式
交互观看、跳过、点赞、分享、观看时长与模型无关、与模型相关TB/PB 级,批处理和流式
实时上下文热门(附近的人、你的同类型人群、好友)与模型无关、按需GB/TB 级,流式
会话内浏览设备、使用模式(连续刷视频等)、最后一次点击、会话时长按需GB/TB 级,实时处理
图/社交社交行为(如好友点赞)、社交亲密度与模型相关、按需GB/TB 级,批处理和流式

总体而言,对构建推荐模型有用的特征围绕用户、物品(在我们的案例中是视频)以及用户与物品之间的交互展开。其中一些特征包含变化缓慢的数据,存储在数据仓库中,由批处理特征管道更新;例如,关于用户观看行为的信息,如视频的平均观看百分比,以及按视频类型压缩的观看统计。

另一些特征则包含关于全球或局部观看趋势的实时上下文信息。例如,为了让推荐系统快速传播突发新闻,点击量和点击率(click-through rate,CTR)都是视频的重要实时上下文特征,它们由流式特征管道更新。对于传播突发新闻来说,批处理特征管道太慢了。

会话内浏览特征同样包含关于用户近期活动的宝贵实时信号,但它们是根据请求时参数按需计算的。例如,如果用户开始观看关于烹饪的视频,但随后转向了体育,推荐系统可以推荐用户历史上互动过的体育内容,以及与该用户历史上观看过的其他视频时长相同的视频。

基于检索-排序架构的 TikTok 推荐系统

TikTok 是 2025 年全球最受欢迎的视频流平台。它有几种不同的视频推荐方式,包括好友信息流和关注信息流。但正是它的"为你推荐”(For You)信息流让 TikTok 与其他视频流平台区分开来。它确实是为你个性化的,并且会根据你的活动实时更新推荐。要让用户感知到信息流对他们的行为做出了反应,更新就不能超过几秒钟;否则它就会显得"卡顿”(laggy),而不是智能。

我们将基于图 15-1 所示的检索-排序架构,构建我们自己的个性化"为你推荐"信息流版本。我们将推荐视频的问题分解为两个阶段:(1)检索阶段,使用可扩展的向量索引返回几百个候选视频;(2)排序阶段,根据我们要优化的指标(如提高用户互动)对几百个候选视频进行排序。

图示:说明 TikTok 的个性化推荐系统,它使用检索-排序架构高效处理并对数十亿个视频进行排序,为用户生成个性化推荐。

原书插图

构建大规模个性化推荐系统所面临的关键系统挑战(其中一些在 TikTok 的 Monolith 研究论文中有介绍)如下:

  • 非平稳性挑战
    • 用户偏好和热门视频不断变化,导致特征在几秒钟内就会过期,需要持续重训模型。当环境是动态的时,你的系统需要不断适应。在短时间尺度上,这意味着要有新鲜的预计算特征(流处理)和从请求参数实时计算的特征。在较长时间尺度上,这意味着要频繁重训模型以防止概念漂移(concept drift)。TikTok 使用 Flink 实现从用户动作(点击、点赞等)亚秒级计算流式特征,并使用 Cassandra(键值存储)和 Redis(缓存)进行实时特征服务。TikTok 的 Monolith 还包括模型的持续重训(每分钟一次),但我们可以简化为调度每小时运行的批训练作业。
  • 稀疏特征挑战
    • 大多数用户和视频特征都是高基数(high-cardinality)类别变量,其原始形式极其稀疏。例如,推荐系统通常把观看历史存储为独热向量(one-hot vector),其中 1 表示用户看过某个视频,0 表示用户没看过。这会导致极高维度且大部分为零值(稀疏)的矩阵,而协同过滤因子分解机等技术无法扩展到所需的更高内存和计算复杂度。稀疏特征还存在冷启动(cold-start)问题,因为它们意味着这些实体几乎没有数据,难以生成好的推荐。模型倾向于只推荐热门物品,忽略了互动较少的物品的"长尾"(long tail),从而降低了推荐的多样性和惊喜感。稀疏特征还可能导致过拟合,因为模型可能会"记住"罕见的用户-物品交互,而不是泛化。神经网络通常需要稠密表示,而原始交互数据是稀疏的。我们将使用嵌入(embedding)来解决稀疏特征数据问题。嵌入将高维稀疏特征转换为低维稠密向量。然而,我们还面临连接两个不同数据源的挑战:用户行为数据和视频数据。我们将通过在单个双塔架构(two-tower architecture)中训练两个模型(一个用户嵌入模型和一个视频嵌入模型)(见下一节)并使用交互数据(用户事件,如观看/点赞视频等)来解决这个问题。
  • 检索挑战
    • 我们将使用向量索引进行相似性搜索,在几毫秒内从包含数十亿个视频的目录中检索出几百个候选视频。我们将使用在双塔架构中训练的视频嵌入模型,构建一个为系统中所有视频建立索引的向量索引。我们将获取一个用户动作以及用户历史数据,用用户嵌入模型创建一个向量嵌入。我们将用该用户嵌入查询向量索引,找到"最近的"视频。最近是基于交互数据得出的——给定这个用户查询和历史,这些是该用户最可能点击或观看时间最长的视频(在构建双塔嵌入架构时,你可以自行决定优化什么指标)。
  • 个性化排序挑战
    • 检索阶段返回几百个候选视频,以确保相关的物品被包含在内。也就是说,它应该有高召回率(recall)。然后我们需要通过排序来提高推荐的精确率和效用,让吸引人/相关的视频出现在顶部。目标应该是学习一个排序函数,根据期望的指标为每个用户对物品排序。例如,如果你想优化用户与视频的互动,那么概率最高的视频应该出现在每个用户推荐列表的最顶部。注意,2012 年 YouTube 把优化目标从用户点击视频(观看次数)改为用户观看推荐视频的时长(观看时长),获益巨大。排序通常使用低延迟模型(如 XGBoost)和捕捉近期趋势的实时特征。
  • 可扩展性挑战
    • 系统需要能够处理数百万并发请求、存储 PB 级数据,并且需要计算和内存高效的设计,以及高可用架构以防止停机。对于检索阶段,我们将使用 Hopsworks 的向量索引(OpenSearch),它在节点上分区并复制以实现高可用。它可以扩展到存储海量数据(最高 PB 级)并处理数千个并发请求。延迟取决于向量索引的大小(条目数)、向量嵌入的大小、它们存储在内存还是磁盘上,以及 Facebook AI 相似性搜索(FAISS)引擎中的存储配置。可以实现 10 毫秒以下的延迟,你需要应用一些技巧才能在数据量巨大时保持这么低的延迟。排序阶段需要检索候选视频的预计算特征。这意味着要在单次批量操作中进行几百次键值查找。我们将使用 Hopsworks 构建在 RonDB 之上的特征存储,在 10-20 毫秒(p99)内检索一个批次,并且可以扩展以处理数万个并发批请求。
  • 数据源挑战
    • 我们需要用户画像数据、视频数据和交互数据来构建我们的个性化视频播放器。鉴于缺乏高质量的开源数据集,我们将创建模拟用户与视频交互的合成数据。学习用户观看行为最重要的数据源是用户与视频之间的交互。

图 15-2 展示了正交互(如观看和点赞)和负交互(如忽略推荐视频)。我们将为检索阶段训练嵌入模型,帮助预测用户在给定其长期观看行为、近期短期观看行为以及其他用户当前观看行为的情况下,可能观看/点赞哪个视频。

图示:说明用户-视频交互(如观看和点赞)如何作为双塔模型和排序模型的训练数据被记录下来,并带有分配的交互分数。

原书插图

我们将为推荐给用户的视频的用户交互分配一个 interaction_score

  • 0:用户没有观看推荐的视频(或在极短时间内滑走了视频)。
  • 1:用户观看了推荐的视频。
  • 2:用户点赞了推荐的视频。
  • 3:用户分享了推荐的视频。

如果用户观看了一个视频,我们还会通过计算观看两个视频之间的时间差来度量 watch_time(用户观看该视频的时长)(你也可以添加一个停止观看事件,但大多数观众只会滑动切换视频)。

在下一节中,你将基于这种检索-排序架构设计你自己的个性化、实时的 AI 驱动推荐系统,包括数据模型和 FTI 管道。

Note

Google 在 RecSys 2016 发表的《用于 YouTube 推荐的深度神经网络》中普及了用于个性化推荐的检索-排序架构。2025 年,Netflix 推出了一个用于预测用户下一次交互的基础 Transformer 模型。看看 Transformer 能否像颠覆 NLP 一样颠覆推荐模型,将会很有趣。

实时个性化推荐系统

你的个性化视频推荐系统的起点是构建一个 MVPS(见第2章)。图 15-3 中的看板展示了 FTI 管道所用的不同技术、数据源(一个 Kafka 主题和外部湖仓表),以及预测消费者——视频播放器的个性化推荐。对于你的特征管道,你将需要流处理(Feldera)、批处理(Polars)和向量嵌入(PySpark)管道。

图示:看板图,展示最小可行视频推荐系统的组件,包括数据源、机器学习管道,以及面向个性化视频推荐的应用集成。

原书插图

我们选择这些数据变换框架是因为 Feldera 和 Polars 的学习曲线最平缓,并且可以扩展以处理我们预期的负载(数百万用户);我们将使用 PySpark 计算向量嵌入,因为从视频数据回填向量嵌入的计算量很大,而 PySpark 可以横向扩展以运行在许多节点上。我们将使用双塔模型,配合 TensorFlow Recommenders ,训练用于检索系统的用户嵌入模型和视频嵌入模型。TensorFlow Recommenders 内置了对训练双塔嵌入模型的支持。我们将使用 XGBoost 作为排序模型,因为它的性能好且预测延迟低。我们将把在线推理管道作为 Python 服务器(FastAPI)托管在 KServe 中,视频播放器应用通过 REST API 调用它。我们将在 Hopsworks 上运行管道并部署模型。

Note

Netflix 等大公司把这种检索-排序架构同时用于个性化推荐和搜索——“一个可以服务所有搜索和推荐任务的统一上下文推荐系统”。Netflix 有基于同一检索-排序基础设施构建的推荐系统 PreQueryMoreLikeThis,以及一个搜索系统,它们使用许多相同的数据源和特征。统一平台降低了维护成本,并让搜索或推荐中的创新也能惠及另一个领域。

在接下来的几节中,我们将逐一介绍 ML 管道,但首先我们将设计系统架构:从数据源到特征管道的类型(批处理还是流式)、特征组,以及模型所需的特征视图。图 15-4 展示了我们的 MVPS 需要四个特征组和两个特征视图,并将创建三个模型。

图示:说明视频推荐系统的特征组和特征视图,展示交互数据如何通过流式和批处理管道处理,为检索-排序模型生成输入。

原书插图

图中展示了交互数据到达 Kafka,流式特征管道计算聚合观看统计,批处理管道计算用户画像、视频属性和排序特征数据。这些特征组包含向量嵌入和一些实时特征。我们的检索系统基于向量索引,需要两个嵌入模型——一个用于用户数据,一个用于视频数据——我们为这些模型创建一个检索特征视图。对于排序模型,我们还创建一个排序特征视图。

我们的管道代码以及如何运行这些 ML 管道的说明都在本书的源代码仓库中。现在,我们将研究如何为推荐系统实现 FTI 管道。

特征管道

我们从交互数据开始,它作为事件到达由所有视频播放器应用生成的 Kafka 主题。我们假设有一个外部事件溯源管道,把历史交互事件存储在湖仓表中。在源代码仓库中,我们创建合成交互数据并把它写入 Kafka 主题。同样的代码还可以用历史交互数据回填一个 interaction_fg 特征组。用户画像数据将由用户在视频播放器应用中的操作更新。视频属性将由定期运行的批处理管道更新,以处理用户上传的新视频。图 15-4 还展示了各特征组的特征管道类别(批处理、流式、向量嵌入)。同样,我们有合成数据生成程序来创建这些数据。创建合成数据生成程序的提示词在本书的源代码仓库中。所有特征组都将同时具备离线和在线能力。离线数据用于训练,在线数据用于检索和排序阶段。

我们将需要一个流式特征管道来计算视频的窗口聚合( video_stats_fg ):

  • cnt_views_last_{h/d/w/m}
    • 视频在之前一小时、一天、一周和一个月的观看次数
  • ctr
    • 之前一小时、一天、一周和一个月的点击率

并计算用户观看历史的状态( user_activity_fg ):

  • recently_viewed
    • 每个用户最近观看的 N 个视频
  • last_login
    • 用户最后登录的时间戳
  • mean_session_duration
    • 上周用户会话的平均时长
  • std_session_duration
    • 上周用户会话时长的标准差

我们将使用 Feldera 计算流式特征管道,它也可以在回填模式下运行,以处理历史交互数据。

视频的特征(不包括视频使用统计)存储在 video_attrs_fg 中。它包含取自湖仓中 videos 表的视频名称、描述、类型和评分等特征。它还包含用于检索阶段相似性搜索的向量索引。你需要用批处理向量嵌入管道定期更新 video_attrs_fg,如图 15-5 所示。

图示:说明向量嵌入特征管道,它使用注册表中的模型,以视频详情和统计信息为输入来更新 video_attrs_fg。

原书插图

我们使用向量嵌入模型(在我们的交互数据上训练,见下一节)计算向量嵌入,输入来自 videos(名称、描述、类型、时长、评分)以及来自 video_stats_fg 的视频观看统计。这种特征组合让我们的检索阶段不仅可以根据视频的静态属性(名称、描述、类型、评分)选择视频,还可以根据动态属性(如它们的热度分数(trending score))来选择。如果视频的流行度突然变化怎么办?检索阶段只会在向量索引条目更新时才适应视频流行度的变化。动态属性还会增加向量索引的写入负载和管道的计算需求。你的管道程序可能会从 GPU 中受益,因为与 CPU 相比,GPU 在计算向量嵌入时应该能带来约 10 倍的吞吐量提升。不过,你的管道随后可能会遇到写入向量索引的瓶颈。例如,Hopsworks 使用 OpenSearch 的向量索引,使用批量 API 每秒可以处理几万次更新。如果我们用一堆 worker 运行 Spark 向量嵌入管道,我们可能不需要 GPU,因为 OpenSearch 将是瓶颈,添加 GPU 不会让更新更快。例如,如果你有 1 亿个视频,每秒可以更新 1 万次,那么更新所有条目需要 150 分钟。这为刷新向量索引的频率设定了一个上限。不过,你也许不需要在每次增量更新时都更新所有条目——你可以为视频流行度的变化设置一个阈值,只有当视频的流行度高于/低于阈值时才更新条目。这将把需要更新的视频数量减少两三个数量级,让你能以高得多的节奏更新条目。

另一个批处理特征管道更新 user_profile_fg(位置、年龄、性别等),使用从 users 湖仓表计算出来的大多是静态的特征,并且只做有限的特征工程(例如,出生日期被转换为年龄)。该特征组是在线的,因为我们将在在线推理管道中使用它的预计算特征。由于该管道数据变化缓慢,可以每天调度一次做增量更新,但它也可以以回填模式运行。对于这个特征管道和之前的特征管道,你应该添加数据验证规则,比如第8章中介绍的 Great Expectations。例如,用户画像和视频属性不应有缺失值。

从这些特征组中,我们可以创建包含三个模型(用户/查询嵌入模型、视频嵌入模型和排序模型)将使用的特征的特征视图。

训练管道

我们将使用由四个不同特征组构建的单一训练数据集来训练用户嵌入模型和视频嵌入模型。为此,我们创建一个特征视图,从我们的交互数据集开始,把它挂载为外部 interactions 特征组,其中存储我们的标签 interaction_score,以及指向 user_idvideo_id 的外键。我们通过从 user_profile_fgvideo_attrs_fgvideo_stats_fguser_activity_fg 连接更多特征来创建特征视图。

类似地,我们从 interactions 创建 ranking_fv,同样使用 interaction_score 作为标签。我们可以使用许多相同的特征,但也可以使用实时特征,包括按需特征和在流式特征管道中计算的特征。排序模型可以更快地响应热门视频和用户行为的变化。图 15-6 展示了检索和排序特征视图如何分别用于为嵌入模型和排序模型创建训练数据。

图示:说明使用特征存储中的特征视图创建训练数据集的过程,涉及用检索和排序特征视图生成用户和视频嵌入,并将模型注册到模型注册表。

原书插图

我们将训练数据从特征存储物化为 CSV 文件,因为数据量可能太大,无法在训练管道中存储在内存中。

双塔嵌入模型

到目前为止,在本书中我们只研究了预训练嵌入模型,例如把文本转换为维度为 d(浮点数数组的长度)的稠密向量表示的 sentence-transformers

我们想用双塔模型架构训练我们自己的自定义嵌入模型,使用交互数据、用户数据和视频数据。交互数据告诉我们,具有特定画像和观看历史的用户观看了具有某种类型、描述和流行度的视频。交互数据还应包括负样本,即用户没有观看该视频的情况,以及用户点赞或分享视频的情况。我们将使用交互数据以及用户和视频特征,训练两个把这两种不同模态(用户和视频)联系起来的嵌入模型。

双塔模型架构的输入是来自用户-视频交互数据集的样本(行),以及每个交互的分数作为样本的标签。我们将准备训练数据集,为它连接以下列:

  • 用户特征
    • 来自用户画像和用户观看历史
  • 视频特征
    • 画像、观看统计和视频

用户特征和视频特征被输入两个独立的神经网络(塔),一个处理用户特征,一个处理视频特征。每个塔中可以包含的特征和层的一些示例:

  • 用户嵌入层
    • 用户 ID 和用户类别特征
  • 视频嵌入层
    • 视频 ID 和视频类别特征
  • 前馈层
    • 归一化的数值特征,如用户年龄和视频时长
  • Transformer 块
    • 文本特征,如视频描述,以及序列特征,如用户历史
  • CNN
    • 图像特征

用户塔接收用户特征(用户条目),先经过任意初始层到嵌入层(用户和视频 ID 的嵌入查找表),再经过前馈层,输出一个向量:长度为 d 的用户嵌入。视频塔接收视频特征(视频条目),先经过初始层到嵌入层,再经过前馈层,输出长度为 d 的视频嵌入。图 15-7 展示了从训练数据到两个嵌入塔、再到输出和损失函数的架构。

图示:说明双塔神经网络模型,它将用户和视频交互处理为嵌入,并使用点积进行比较以预测互动结果。

原书插图

用户嵌入和视频嵌入使用相似性函数(如点积或余弦相似度)进行比较。我们把输出压缩为两类之一: = 强或弱互动(1、2、3), = 无互动(0)。双塔模型用于检索阶段,其目标只是找出任何潜在有趣的候选传给排序阶段。细粒度的偏好(例如"点赞"与"分享")更适合在排序模型中处理,排序模型可以获取更丰富的特征并进行个性化评分。

正或负的结果使用对比损失(contrastive loss)函数(如信息噪声对比估计(InfoNCE)或采样 softmax)与二元标签(正或负)进行比较。计算出的损失用于更新用户塔和视频塔网络中的权重。更大的损失会导致更大的权重更新,驱动嵌入塔优化相似度分数,使正样本排在负样本之上。

Note

推荐模型需要负采样吗?如果推荐服务本身还没有上线,没有交互数据怎么办?如果你有一些正样本(观看、点赞),你可以使用随机采样之类的策略——把用户条目与随机视频组合成负数据,引导训练数据。

构建视频向量索引

双塔模型训练完成后,你需要编写一个向量嵌入管道,它可以从交互数据集回填向量索引,也可以增量处理交互数据集中的新条目。向量嵌入管道将为它从交互数据集中处理的每一行创建一个视频向量嵌入,并把它写入向量索引。

当推荐系统要为某个用户查询检索候选视频时,它首先用用户嵌入模型从用户特征计算用户向量嵌入。然后,它使用向量索引上的 ANN 搜索,检索与给定用户嵌入最相似的前 N 个(通常为 50-1,000 个)候选视频。返回的候选视频应该使用排序模型进行排序。

排序模型

排序模型把 N 个候选视频作为输入,使用更丰富的特征(包括用户和视频之间的显式交叉特征,这是双塔模型难以处理的)精确地重新排序。排序器还可以使用更多实时特征(按需特征或在流式特征管道中计算的特征),使其对视频流行度和用户行为的近期变化更敏感。例如,排序模型把"热度分数"作为每个视频的众多输入特征之一,并学习"热度"对每个用户有多重要。排序模型也需要正样本和负样本(观看和未观看),并且可以预测更细粒度的交互,如点赞和分享。排序器的例子包括 Wide & Deep、DCN 和 DeepFM。

一种被广泛使用的排序指标是归一化折损累积增益(NDCG)。它把排序结果与所有相关物品都排在列表顶部的理想顺序进行比较。另一个流行的排序指标是平均倒数排名(mean reciprocal rank,MRR)。前 K 平均精度均值(mean average precision at K,MAP@K)是一种排序指标,用于评估推荐系统中的排序质量。它既衡量推荐物品的相关性,也衡量系统把更相关的物品放在顶部的能力。

在线推理管道

在线推理管道是一个部署在 KServe 上的 Python 预测器脚本,作为 FastAPI Python 服务器运行。它接受预测请求,在执行完第 2 到第 6 步后返回排序后的推荐视频列表,如图 15-8 所示。

图示:说明部署在 KServe 上的在线推理管道,突出显示视频播放器、预测器和排序模型之间的各个组件与数据流。

原书插图

在线推理管道是一个部署对象,带有一个部署 API,把会话内特征和实体 ID 作为参数。它执行以下步骤:

  • 1. 检索
    • user_id 从特征存储读取用户特征,并与按需特征和传入特征合并。这些 user_features 被传给用户嵌入模型,返回用户嵌入,然后发送到向量索引,返回 200 个候选视频。
  • 2. 过滤
    • 我们用 ranking_fvvideo_ids 读取 200 个候选视频的特征。现在我们有了候选视频的特征,就知道了每个视频的评级,因此可以过滤掉不适合用户年龄的视频。
  • 3. 排序
    • 最后,我们对包含过滤后候选视频的 DataFrame 执行 model.predict()。模型使用所有可用的 CPU 核并行执行这些预测,使总延迟最小化。

在线推理管道(预测器脚本)的伪代码如图 15-9 所示,包括对特征存储的调用,以及每一步延迟的一些估计。

图示:模型部署图,展示用户模型和排序模型使用特征存储进行候选检索与特征增强,并给出总计 45 毫秒的延迟分解。

原书插图

图中显示了 45 毫秒的 P95 延迟目标,每一步的分解如下:

  • 检索用户特征是一次主键查找,需要约 1 毫秒,用户嵌入计算需要约 4 毫秒,这一步总计约 5 毫秒。
  • 向量索引上的 ANN 搜索需要约 10 毫秒(如果你有数亿个视频,你的查询和向量索引将需要认真调优才能把延迟保持在这个水平)。
  • 在 Python 中内存中过滤掉不合适的视频,应该不到 1 毫秒。
  • 在特征存储中对视频特征进行一次批量主键查找需要约 23 毫秒。
  • 排序模型为每个候选视频估计排序分数,在所有可用 CPU 核上并行执行预测,需要约 5 毫秒。
  • 异步记录输入特征和预测需要约 1 毫秒。

我们假设按需特征的计算时间不到 1 毫秒,总计约 45 毫秒。如果你的向量索引和特征存储查找的标准差很高,你应该警惕规模下的长尾(tail at scale)现象,此时 p99 延迟可能会显著增加。

由于我们记录了排序模型的所有特征和预测请求,我们可以通过编写模型监控作业来监控它的性能,就像我们在第14章中所做的那样。结果会在交互数据中产生(你应该等几分钟,让用户观看或不观看推荐内容),你可以轻松地将预测与结果进行比较。如果预测性能下降,你将需要重训排序模型或重新设计它。或者,预测性能下降可能是检索阶段上游问题的结果,在这种情况下,你可能需要重训或重新设计嵌入模型。

视频的智能体搜索

你的实时推荐系统是摇钱树,应该让用户在视频播放器上停留更长时间。但现在,你想用新的 AI 驱动功能让用户惊叹。你可以扩展系统,允许用户用自由文本搜索视频。你还可以添加新的特征管道来转录视频、从视频中提取帧,并允许用户附加描述视频关键时刻的标签。图 15-10 展示了一个由 LLM 驱动的智能体的架构,它能够提供这种自由文本搜索能力。

图示:说明 AI 驱动的视频搜索系统架构,展示用于转录和帧提取的 Whisper 和 YoloX 管道等组件,以及集成 LLM 来解释用户查询以检索相关视频标签。

原书插图

用户可以观看一个视频并询问关于视频中某个时刻或场景的问题。然后我们可以使用当前的 video_id 检索该视频的 video_tags,由 LLM 根据标签的描述判断哪个最合适,并把视频中的偏移量改到所选 video_tags 行中的 pos_ms。当用户正在观看视频时,智能体(由 LLM 驱动)会解释自然语言查询,检索当前 video_id 的所有 video_tags,并选择最相关的一个。然后系统会跳转到与该标签关联的 pos_ms 时间戳。

类似地,用户可以询问关于所有视频的问题,对 transcripts 向量索引进行 ANN 搜索,找到最相似的视频转录,然后播放匹配的视频。对于跨所有视频的查询,智能体可以对 transcripts 向量索引或 videos 向量索引执行 ANN 搜索,找到语义相似的内容片段或完整视频,然后播放最佳匹配。

案例研究到此结束,我将用一些关于不要做什么的建议来结束本书。这是对贯穿全书许多经验教训的总结,并加上了一点诙谐。

MLOps 的十二大谬误

MLOps 从业者经常犯一些谬误(错误的假设),导致 AI 系统永远无法上线。我们在前面的章节中已经讲过这些谬误,但在这里把它们作为复习呈现,让你看看如果陷入某个谬误会发生什么:

  • 1. 把所有事情都放在一个单体 ML 管道中完成
    • 我们看到,批 ML 系统可以写成单个单体管道(参数化后在训练或推理模式下运行)。但是,你不能用单个 ML 管道运行实时 ML 系统,也不能用单个程序构建智能体 RAG 系统。*这一谬误的影响及如何克服:*如果没有构建 AI 系统的统一架构,构建每一个新的批处理或实时 AI 系统都像是从零开始。这让开发人员难以从构建一种类型的 AI 系统过渡到另一种。你可以通过把 AI 系统分解为特征/训练/推理管道(FTI 管道),并把它们连接起来构成你的批处理/实时/LLM AI 系统来克服这一挑战。
  • 2. 用于 AI 的数据是静态的
    • 学会用静态数据集训练模型的数据科学家习惯于模型只做一次预测、只创造一次价值。在现实世界中,AI 系统使用动态数据源,并随着新数据的到来反复创造价值。*这一谬误的影响及如何克服:*如果开发人员不具备从动态数据源提取和管理数据的技能,他们就难以处理动态数据源。开发人员难以区分按计划做预测的批 ML 系统和响应预测请求做预测的实时 ML 系统。你可以在构建 AI 系统时遵循 FTI 架构来克服这一点。
  • 3. 用于 AI 的所有数据变换都是一样的
    • 数据变换并不都一样。与模型无关的变换在特征管道中创建可复用的特征数据。与模型相关的变换在从特征存储读取数据之后执行,需要在训练和推理管道中一致地实现。按需变换使用请求时数据创建特征。它们在特征管道用历史数据回填时以及在在线推理管道处理请求时数据时都会执行。按需变换在特征管道和在线推理管道中的实现不应有任何偏差。*这一谬误的影响及如何克服:*如果你不支持与模型相关的变换,你就无法在特征存储中复用特征。如果你不支持按需变换,你就没有相同的代码来从预测请求参数计算实时特征和在特征管道中回填特征数据。如果你既不支持与模型相关的变换也不支持按需变换,你就难以构建一个可观测的、记录/监控可解释特征的 AI 系统。解决方案是把你的数据变换梳理为与模型无关的、与模型相关的和按需变换三类。
  • 4. 不需要特征存储
    • 特征存储是连接特征管道与训练/推理管道的数据层。如果你不关心复用特征,并且愿意自己实现治理、血缘、特征/预测日志和监控的解决方案,那么在没有特征存储的情况下构建批 ML 系统是可能的。但是,如果你处理的是时序数据,你还得自己实现从表中创建时间点正确训练数据的方案。如果你正在构建实时 ML 系统,你需要一个特征存储(或自己构建一个)来为在线模型提供预计算特征(作为上下文/历史)。特征存储还确保离线变换和在线变换之间没有偏差。简而言之,没有特征存储,你也许能推出你的第一个批 ML 系统,但之后每增加一个批模型的开发速度都不会提高。对于实时 ML 系统,你需要特征存储来为在线模型提供历史/上下文,并需要基础设施来确保特征正确、受治理、可观测。*这一谬误的影响及如何克服:*你最终会自己构建特征存储的能力,花大量时间研究如何正确处理可变数据、如何创建时间点正确的训练数据,以及如何同步列式数据存储与面向在线推理的低延迟行式存储中的数据。由于把特征做成预计算特征需要付出努力,你在在线模型中会使用更少的特征。你不会规范化你的数据模型(雪花模式),因为那太难了。构建和部署每个新模型的成本总是很高,并且不会随时间下降。解决方案是使用特征存储。
  • 5. 实验跟踪是 MLOps 所必需的
    • 许多团队错误地认为安装实验跟踪服务是构建 AI 系统的起点。实验跟踪会拖慢你到达第一个 MVPS 的速度。实验跟踪是 MLOps 中的过早优化。你可以把模型注册表用于运营需求,如模型存储、治理、模型性能/偏差评估和模型卡。实验跟踪是模型训练的研究日志。*这一谬误的影响及如何克服:*就像猴子拉绳实验(猴子会不断殴打任何试图爬绳的猴子,尽管没有猴子知道为什么不允许爬绳)一样,许多 ML 工程师认为 MLOps 项目的起点是安装实验跟踪服务。解决方案是从模型注册表开始,存储模型及其训练运行所需的元数据,直到你真正需要实验跟踪服务(大多数 ML 工程师可能永远不需要)。
  • 6. MLOps 只是面向 ML 的 DevOps
    • 与 DevOps 一样,MLOps 需要对管道源代码进行自动化测试;但与 DevOps 不同,MLOps 还需要对输入数据进行版本管理和测试。数据验证测试可以防止垃圾进、垃圾出。同样,模型验证测试在 DevOps 中没有对应物。还有一个区别是,由于数据和模型漂移,AI 系统的性能往往会随时间下降。*这一谬误的影响及如何克服:*没有数据测试,你的训练或推理数据可能会被污染。没有模型测试,你的模型可能有偏差或性能不佳。由于缺乏特征监控和模型性能监控,你的 AI 系统性能可能会随时间下降。请遵循 MLOps 最佳实践,进行离线数据验证、模型验证以及特征/模型监控。
  • 7. 对模型进行版本管理就足以安全升级/回滚
    • 对于有状态的实时 ML 系统,模型部署与为其提供预计算特征的带版本特征视图紧密耦合。当你升级模型部署时,仅仅更新模型版本是不够的。你可能还需要升级模型部署所使用的特征视图的版本。*这一谬误的影响及如何克服:*如果不把模型部署版本与特征版本耦合,你可能会引入难以察觉的 bug。例如,如果你的新部署使用旧的特征版本,但新的特征组版本与旧版本模式兼容,系统看起来会像以前一样工作。然而,它的性能会下降,而且这将是一个很难找到的 bug。解决方案是把模型部署的版本与喂给它的特征视图紧密耦合。
  • 8. 不需要数据版本管理
    • 训练数据的可复现性需要数据版本管理。*这一谬误的影响及如何克服:*没有数据版本管理,如果你重新创建一个训练数据集,而自第一个训练数据集创建以来有延迟到达的数据,那么延迟数据将被包含在后续的训练数据集创建中。这是因为没有为延迟到达的数据记录摄入时间戳。解决方案是支持数据版本管理,就像湖仓表那样,包括为数据点记录摄入时间戳。这让你能够精确地重新创建训练数据在最初创建时的样子。
  • 9. 模型签名就是模型部署的 API
    • 实时 ML 系统使用*模型部署来响应预测请求做预测。客户端发送给模型部署 API的参数通常与模型的输入参数(模型签名,model signature)不同。这一谬误的影响及如何克服:*开发人员可能会把模型部署 API 误认为模型签名。如果没有对部署 API 的显式支持,开发人员将被迫阅读源代码来推断它。你需要为一个部署显式定义 API(或模式)。
  • 10. 在线预测延迟就是模型预测所花的时间
    • 当你在网络端点后面服务模型时,你通常需要在最终用最终特征向量调用 model.predict() 之前执行大量操作。*这一谬误的影响及如何克服:*你不能假设网络托管模型的预测延迟只是模型预测所花的时间。你必须把所有预处理(构建特征向量、RAG 等)和后处理(特征/预测日志)的时间都包括在内。
  • 11. LLMOps 与 MLOps 不同
    • LLM 在推理和微调时需要 GPU。同样,LLM 需要可扩展计算、可扩展存储和可扩展模型服务的支持。然而,许多 MLOps 平台既不支持 GPU 也不支持扩展,结果 LLM 常常被视为 MLOps 之外的东西,属于一门新的 LLMOps 学科。但是,LLM 仍然遵循同样的 FTI 架构。如果你的 MLOps 平台支持 GPU 和扩展,LLMOps 就只是带 LLM 的 MLOps。特征管道用于为指令和对齐数据集分块、清洗和评分文本。它们还用于计算存储在向量索引中供 RAG 使用的向量嵌入。训练管道用于微调和对齐基础 LLM。分词是一种与模型相关的变换,需要在训练和推理之间保持一致——没有平台支持,用户常常会出错,在推理时为他们的 LLM 使用了错误版本的分词器。智能体和工作流存在于在线推理管道中,通过 RAG 和函数调用调用外部系统也是如此。你的 MLOps 团队应该能够把与批处理和实时 ML 系统相同的架构和工具应用于 LLM 系统。*这一谬误的影响及如何克服:*如果你支持与 MLOps 栈分离的 LLMOps 栈,你可能会重复建设 AI 基础设施。如果你把 LLMOps 当作大规模的 MLOps 对待,开发人员应该能够轻松地从批处理/实时 ML 系统过渡到 LLM AI 系统——如果你遵循 FTI 架构的话。
  • 12. 运行 ML 管道需要 ML 编排器
    • 你并不需要一个 ML 专用的编排器(如 Kubeflow/Metaflow/ZenML/SageMaker Pipelines)来运行你的 ML 管道。ML 编排器是为批 ML 系统设计的,通常只能运行少数几种不同的数据处理和 ML 框架。例如,你不能在 Kubeflow 中运行 Spark 特征管道。而且,ML 编排器不能运行流式特征管道。如果你想在一个平台上支持批处理、实时甚至 LLM AI 系统,并不是所有的 ML 管道或服务都能由你的 ML 编排器管理。这意味着 ML 编排器并不了解所有 AI 系统的所有血缘信息。相比之下,数据层(特征存储、模型注册表)了解所有类别的 ML 管道的所有血缘信息,通常应该是血缘的真相来源。这样你就可以自由使用最适合你的 FTI 管道需求的编排器。*这一谬误的影响及如何克服:*自诞生以来,MLOps 一直与 ML 编排器(如 Kubeflow)联系在一起。但最近批处理和流处理数据引擎的寒武纪大爆发意味着你可能想为特征管道使用专业框架,如 Apache Flink、Feldera 或 Polars。ML 编排器跟不上。它们最初也被设计为存储血缘信息。如果你在 ML 编排器之外运行 ML 管道,血缘信息将对它丢失。相反,血缘信息应该由特征存储和模型注册表管理,而不是由编排器管理。你可以自由地为每个 ML 管道使用最好的编排器。

AI 构建者的伦理责任

最后,说说你在构建 AI 系统时的伦理责任。在投入构建 AI 系统之前,你应该始终考虑系统的任何潜在负面影响。你不仅负有遵守法律和法规的责任,还有确保不造成直接或间接伤害的责任。例如,个性化推荐系统必须是负责任的 AI 系统。爱尔兰 RTÉ 电视台《黄金时间》(Prime Time)栏目在 2024 年 5 月的一项调查发现,“滚动浏览一小时后,TikTok 的推荐系统向它认为是 13 岁的用户展示了一连串几乎全部与抑郁、自残和自杀念头有关的视频。“如果你在一家构建这种 AI 系统的公司工作,要么修复系统,要么离开公司并举报。构建合法但不道德的软件并不光荣。

我们可以从历史中吸取教训,瑞典瓦萨号(Vasa)战舰的故事对各地的工程师来说既是警告也是教训。国王古斯塔夫二世·阿道夫想要一艘配备 64 门重炮的战舰(1627 年世界上最多的)。专家告诉他这不可能。尽管如此,造船工人还是造了它,尽管知道他们的工作既徒劳又危险。工程师们和这艘船一样软弱无力。瓦萨号在下水时就沉没了,约 30 人丧生。不要成为那个构建有害 AI 系统的开发者。我们可以一起让 AI 成为一股向善的力量,但如果没有法律的帮助,我们需要一个公认的伦理准则才能做到这一点。遵循这个伦理准则并帮助执行它,当你日后回顾自己的人生时,你会感谢自己。

小结

本章介绍了一个案例研究:构建你自己的类似 TikTok 的个性化视频推荐服务。它涵盖了检索-排序架构,该架构建立在用于检索的双塔嵌入模型和用于个性化推荐的排序模型之上。我们介绍了系统的流式、批处理和向量嵌入特征管道;用户嵌入模型、视频嵌入模型和排序模型的训练管道;以及实现用户请求检索和排序的在线推理管道。最后,我们还锦上添花,添加了一个由 LLM 驱动的智能体来支持跨视频和视频内的自由文本搜索。最后,我们以 MLOps 和 LLMOps 的十二大谬误结束全书,如果你想在构建 AI 系统方面取得成功,就应该避免这些谬误。而历史上没有任何时候比今天更适合构建 AI 系统。鉴于改进的速度,今天永远会是构建 AI 系统最重要的一天。去创造吧,愿原力与你同在。

索引

A

B

C

D

E

F

G

H

I

J

K

L

M

N

O

P

Q

R

S

T

U

V

W

X

关于作者

**Jim Dowling 是 Hopsworks 的首席执行官,曾任瑞典皇家理工学院(KTH Royal Institute of Technology)副教授。他领导了 Hopsworks 的开发,包括第一个用于机器学习的开源特征存储。他在数据与 AI 的交汇领域拥有独特的背景。在数据方面,他曾就职于 MySQL,之后领导了 HopsFS 的开发——这是一个分布式文件系统,于 2017 年荣获 IEEE 规模奖(IEEE Scale Prize)。在 AI 方面,他的博士论文引入了协作强化学习(collaborative reinforcement learning),并于 2016 年在瑞典开发并讲授了第一门深度学习课程。他还发布了一门广受欢迎的、使用 Python 的无服务器机器学习在线课程,网址为 serverless-ml.org。这种数据与 AI 相结合的背景,帮助他实现了基于通用编程语言构建机器学习特征存储的愿景,而不是像 Uber 早先在 DSL 上开展的特征存储工作。他是特征存储最早的布道者,通过行业会议(如 Data/AI Summit、PyData 和 OSDC)上的演讲以及关于特征存储的教育文章,帮助开创了特征存储这一产品类别。他是年度特征存储峰会(feature store summit)大会和 featurestore.org 社区的组织者,同时也是 PyData 斯德哥尔摩(PyData Stockholm)的联合组织者。

版权说明(Colophon)

《使用特征存储构建机器学习系统》(Building Machine Learning Systems with a Feature Store)封面上的动物是一只红胸侏儒鹦鹉(red-breasted pygmy parrot,学名 Micropsitta bruijnii),原产于马鲁古群岛(Maluku Islands)和美拉尼西亚(Melanesia)。

这只鹦鹉属于体型最小的鹦鹉属,平均体长八厘米(略超过三英寸)。与许多其他侏儒鹦鹉不同,它生活在高海拔环境中,在树洞或树桩中筑巢。它以地衣为食,行动短促而急促,经常沿着树皮攀爬。与许多鸟类一样,红胸侏儒鹦鹉表现出性别二态性(sexual dimorphism),雄性与雌性外观不同:两者都是绿色,但雄性有红色胸部和粉橙色喉咙,而雌性主要为绿色,带有蓝色冠羽和白色面部。

它的寿命与其他小型鹦鹉相似,最长可达十年。与某些其他鹦鹉不同,这个物种在人工饲养下表现不佳。其 IUCN 保护等级为无危(Least Concern)。奥莱利(O’Reilly)封面上的许多动物都处于濒危状态;它们对世界都很重要。

封面插图由 José Marzan Jr. 绘制,基于《利德克皇家自然史》(Lydekker’s Royal Natural History)中的一幅古董线雕版画。系列设计由 Edie Freedman、Ellie Volckhausen 和 Karen Montgomery 完成。封面字体为 Gilroy Semibold 和 Guardian Sans;正文字体为 Adobe Minion Pro;标题字体为 Adobe Myriad Condensed;代码字体为 Dalton Maag 的 Ubuntu Mono。