构建机器学习系统

想象一下,你被指派为即将到来的财年制作一份财务预测。你决定使用机器学习(machine learning,ML),因为有大量可用数据,但不出所料,这些数据分散在许多不同的地方——电子表格里,以及数据仓库中许多不同的表里。你已经在同一家组织工作了几年,这不是你第一次接到这个任务。迄今为止的每一年,你模型的最终输出都是一份展示财务预测的 PowerPoint 演示文稿。每一年,你都训练一个新模型,你的模型只做一次预测,然后你就和它告别了。每一年,你几乎都是从零开始。你必须(再次)找到数据源,重新申请数据访问权限来为你的模型创建特征,然后翻出去年的 Jupyter notebook,用新数据和对模型的改进来更新它。

然而今年,你意识到或许值得投入时间为此项目搭建脚手架,这样明年的工作量就会更少。因此,你决定不再交付 PowerPoint,而是构建一个仪表盘(dashboard)。你不再申请一次性的数据访问权限,而是构建特征流水线(feature pipeline),从数据源提取历史数据,并计算模型中使用的特征(和标签)。你有一个洞见:特征流水线可以做两件事:既计算用于训练模型的历史特征,也计算将作为已训练模型输入的特征,模型据此输出预测。现在,训练完模型后,你可以把它连接到特征流水线上做出预测,为你的仪表盘提供动力。当你只需要调整这个ML 系统——增/改/删特征并训练新模型——时,你暗自庆幸。你毫不费力地把财务预测的频率更新为每季度一次。你把节省下来的时间——原本花在枯燥的数据获取、清洗和特征工程上——用来研究新的 ML 框架和模型架构,最终得到一个大幅改进的财务模型,这让你的老板非常高兴。

这个例子展示了在静态数据集上训练模型做一次性预测,与构建批处理 ML 系统(batch ML system)之间的区别——批处理 ML 系统是一个自动化系统,负责从数据源读取数据、把数据转换为特征、训练模型、用模型对新数据执行推理(inference),并用模型的预测更新仪表盘。仪表盘就是模型交付给利益相关者的价值。

如果你想让模型产生重复价值,模型就应该不止一次地做预测。这意味着,当你在从静态数据集划分出的测试集上评估了模型性能之后,工作并没有结束。相反,你必须构建ML 流水线(ML pipeline),即把原始数据转换为特征、把特征喂给模型以便轻松重训练、并把新特征喂给模型让它做出预测的程序——模型每做出一个新预测,就产生一份价值。

通过这本书,你将踏上同样的旅程:从在静态数据集上训练模型,到构建ML 系统——从决策树到深度学习,再到由大语言模型(LLM)驱动的智能体(agent)。这段旅程最重要的部分是处理动态数据。这意味着从静态数据(比如 Kaggle.com 上 ML 竞赛中使用的人工整理数据集,以及为 LLM 编写提示词)出发,过渡到按一定间隔(每小时、每天、每周、每年)更新的批处理数据,再到构建智能交互应用所需的实时数据。

机器学习系统的剖析

构建 ML 系统时你将面临的主要挑战之一,是管理用于训练模型的数据和模型用来做预测的数据。我们可以根据 ML 系统如何处理用于预测的新数据来对它们分类。ML 系统是按计划做预测(例如每天一次),还是 24/7 运行、响应用户请求做预测?

Spotify 的每周发现(Discovery Weekly)就是批处理 ML 系统的一个例子:它是一个推荐引擎,每周预测一次你可能想听的歌曲并把它们加入你的播放列表。在批处理 ML 系统中,ML 系统读取一批数据(以 Spotify 为例,来自全部 575M+ 用户),并用训练好的推荐 ML 模型对这批数据中的所有行做预测。模型接收所有输入特征(比如你听音乐的频率和你听的音乐类型),为你预测出接下来一周的 30 首"最佳"歌曲。预测随后被存入数据库(Cassandra),当你登录时,Spotify 的每周推荐列表就从数据库下载下来,以推荐的形式显示在用户界面中。

另一方面,TikTok 的推荐引擎以近乎实时地根据你的点击和观看调整其短视频推荐而闻名。TikTok 的推荐服务是一个实时 ML 系统(real-time ML system)。它在你滑动和观看视频时预测该向你展示哪些视频。特斯拉前 AI 负责人 Andrej Karpathy 曾说 TikTok 的推荐引擎“好得吓人。它就是数字毒品。“TikTok 是第一个包含实时推荐的在线视频平台,这给了它相对于老牌平台的竞争优势,使它得以建成全球第二受欢迎的在线视频平台。

Lovable 是一个编码助手,通过其网站上的聊天窗口构建 Web 应用。它是达到 1 亿美元收入增长最快的软件公司,只用了八个月。Lovable 是一个智能体 AI 系统(agentic AI system):它接收你的指令,使用 LLM 把你的 Web 应用创建并运行为 TypeScript 代码,外加 CSS 样式和可选的内置数据库。智能体系统具有自然语言界面。你给它们一个高层目标或要执行的任务,它们以高度的自主性工作来实现你的目标或任务。智能体系统更多是交互式系统而非批处理系统,但两者都有可能。

本书围绕 ML 流水线,为构建这三类 ML 系统——批处理、实时和 LLM 应用——提供统一架构。具体来说,本书解决构建 ML 系统中的数据挑战。大多数 ML 系统需要处理来自不同数据源的不同类型数据,既用于训练模型,也用于做预测(推理)。例如,当 TikTok 向你推荐视频时,它使用你最近的观看行为(点击、滑动、点赞)、你的历史观看行为和偏好,以及聚合信息,比如现在像你一样的用户、在你附近的用户在看哪些热门视频。在 ML 流水线中大规模处理所有这些数据是一项重大的工程挑战,我们将在本书中介绍。

机器学习的类型

ML 系统中使用的主要机器学习类型有:监督学习、无监督学习、自监督学习、强化学习和上下文学习(in-context learning):

  • 监督学习(Supervised learning)
    • 在监督学习中,你用包含特征和标签的数据训练模型。训练数据集中的每一行都包含一组输入特征值和一个标签(给定输入特征值时的结果)。监督 ML 算法学习标签(也叫目标变量)与输入特征值之间的关系。监督 ML 用于解决分类问题(classification problem),即 ML 系统回答是/否问题(这张照片里有热狗吗?)或做多分类(这是哪种热狗?)。监督 ML 也用于解决回归问题(regression problem),即模型用输入特征值预测一个数值(例如,给定面积、状况和位置等输入特征,估算公寓价格)。最后,监督 ML 还用于使用开源 LLM 微调聊天机器人。例如,如果你用法律行业的提问(特征)和回答(标签)训练一个聊天机器人,你的聊天机器人就可以被微调得说话像个律师。
  • 无监督学习(Unsupervised learning)
    • 相比之下,无监督学习算法在没有标签的情况下从输入特征学习。例如,你可以用信用卡交易训练一个异常检测系统,如果一笔异常的信用卡交易到来,你可以把它标记为可疑的、可能欺诈的交易。
  • 自监督学习(Self-supervised learning)
    • 自监督学习涉及从一个完全无标签的数据集生成带标签的数据集。生成带标签数据集的主要方法是掩码(masking)。对于自然语言处理(natural language processing,NLP),你可以提供一段文本并掩码掉单个词(通过掩码语言建模,masked language modeling),然后训练模型预测缺失的词。在这里,你知道标签(缺失的词),所以你可以用任何监督学习算法训练模型。在 NLP 中,你还可以用下一句预测(next-sentence prediction)掩码掉整个句子,这可以教会模型理解句子间的长期依赖。语言模型 BERT 在训练中同时使用了掩码语言建模和下一句预测。类似地,对于图像分类,你可以掩码掉每张图像中(随机选择的)一小部分,然后训练模型尽可能高保真地还原原始图像。
  • 强化学习(Reinforcement learning)
    • 强化学习(reinforcement learning,RL)是另一种 ML 算法(本书不涉及)。RL 关注的是学习如何做出最优决策。
  • 上下文学习(In-context learning)
    • 监督 ML、无监督 ML 和 RL 只能从它们训练所用的数据中学习。然而,足够大的 LLM 表现出另一种 ML:上下文学习,即通过在给 LLM 的提示词中提供上下文(示例)来学习解决新任务的能力。LLM 表现出上下文学习,尽管它们只以下一个词预测(next-token prediction)为目标进行训练。智能体建立在上下文学习之上,但它们需要上下文工程(context engineering)来把相关数据放进 LLM 的提示词中。通过上下文学习,新学会的技能在 LLM 的上下文窗口清空后立即被遗忘——不像模型训练期间那样更新任何模型权重。

ChatGPT 是一个使用多种 ML 组合的 AI 系统的很好例子。ChatGPT 包含一个用自监督学习预训练的 LLM、用监督学习微调基础模型以创建特定任务模型(如聊天机器人),以及用(带人类反馈的)RL 使特定任务模型与人类价值观对齐(例如,去除聊天机器人的偏见和粗俗)。最后,LLM 可以通过上下文学习从输入提示词中的数据学习。

数据源

原则上,ML 系统的数据可以来自任何可用的数据源。话虽如此,某些数据源和数据格式作为 ML 系统的输入更受欢迎。在本节中,我们介绍企业计算中最常遇到的数据源。1

表格数据

表格数据(tabular data)是以包含列和行的表形式存储的数据,通常存储在数据库中。作为 ML 数据来源的数据库主要有两种类型:

  • 行导向存储(Row-oriented stores)
    • 包括关系型数据库和 NoSQL 数据库。它们的存储布局针对读取和写入数据行进行了优化。
  • 列导向存储(Column-oriented stores)
    • 包括数据仓库(data warehouse)和数据湖仓(data lakehouse)。它们的存储布局针对读取和处理数据列进行了优化(例如计算一列的最小值/最大值/平均值/总和)。

作为开发者,你需要熟悉行导向和列导向存储的 API 和查询语言。例如,SQL 和对象关系映射(object-relational mapper,ORM)用于关系型数据库(MySQL、Postgres),键值 API(Cassandra、RocksDB)和 JSON 存储 API(MongoDB)。列式存储通常支持用 SQL 和 DataFrame API(Spark、Pandas、Polars)读写数据。

在企业中,应用生成的大部分数据存储在行导向存储中。大多数企业有大量这样的数据库,它们通常不会直接在原地分析数据,而是采用数据流水线把部分或全部运营数据传输到集中式、可扩展的列式存储中。这使得分析师可以在一个平台上处理整个公司的所有历史数据。这种分析数据也是企业中 AI 系统最常见的数据来源。

事件数据

事件数据(event data)包含在特定时间点发生的离散事件或动作的记录,比如网站上的点击或传感器的读数。事件流平台(event-streaming platform),如 Apache Kafka,是一个数据平台,用于收集和临时存储事件数据,供事件数据的下游(downstream)消费者使用。消费者的例子有:存储原始事件数据以供后续分析的列式数据存储,以及流处理程序——它们使你能够构建实时 ML 系统,在你点击或滑动其网站后的一秒内做出反应。

图数据

图数据(graph data)用节点(实体)和边(关系)来表示。图数据库支持高效地存储和检索复杂的、相互关联的图数据。图中固有的丰富连接性和属性使 ML 模型能够用于链接预测和欺诈检测。LLM 也可以把图数据库作为结构化知识来源,以改进推理和问答。

非结构化数据

有模式(schema)的数据(SQL 表、JSON 对象或图数据)称为结构化数据(structured data)。所有其他类型的数据都归入其反义词类别非结构化数据(unstructured data)。这包括文本(PDF、文档、HTML、markdown)、图像、视频和音频数据。非结构化数据通常存储在文件中,有时是非常大的文件,达到 GB 级或更多,存储在文件系统或对象存储中,比如 Amazon S3。深度学习在解决非结构化数据的预测问题方面取得了巨大进步。图像标注服务、自动驾驶汽车、语音转录系统以及许多其他 AI 系统都是用海量人工标注的非结构化数据训练出来的。

API 抓取的数据

越来越多的数据被存储和处理在软件即服务(software-as-a-service,SaaS)系统中,因此,能够使用其公共 API 从这类服务检索或抓取(scrape)数据变得越来越重要。同样,随着社会日益数字化,网站上可抓取的数据越来越多,这些数据可以作为 AI 系统的数据源。有一些低代码软件系统了解流行 SaaS 平台(如 Salesforce 和 HubSpot)的 API,可以把数据从这些平台拉入数据仓库,比如 Airbyte。但有时,外部 API 或网站没有数据集成支持,你需要自己抓取数据。在第3章中,你将构建一个空气质量预测 ML 系统,它从离你居住地最近的公共空气质量传感器数据源抓取数据(如今互联网上有数以万计这样的传感器——其中有一个可能比你想象的离你更近)。

可变数据

尽管处理数据通常被视为构建和运营 ML 系统的大部分工作,但现有的 ML 课程通常只使用最简单的数据形式:不可变数据集(immutable dataset)。较小的数据集(最多几 GB)通常存储在逗号分隔值(comma-separated values,CSV)文件中,而较大的数据集(GB 到 TB)通常以更具压缩性的文件格式提供,比如 Apache Parquet。2

例如,著名的泰坦尼克号 乘客数据集由以下文件组成:3

  • train.csv
    • 训练集,你应该用它来训练模型
  • test.csv
    • 测试集,你应该用它来评估训练好的模型的性能

数据是静态的,你的工作是训练一个 ML 模型,预测给定乘客能否在泰坦尼克号沉没中幸存。你的第一个任务是对数据做基本的特征工程。例如,有一些缺失值需要你填充(或插补,impute),你还需要删除那些对预测给定乘客能否在泰坦尼克号沉没中幸存毫无能力的列。泰坦尼克号数据集很受欢迎,因为你可以学习数据清洗、把数据转换为特征以及把模型拟合到数据的基础知识。

注意

不可变文件不适合作为企业中的记录数据层(data layer of record)——在欧盟的《通用数据保护条例》(General Data Protection Regulation,GDPR)或《加州消费者隐私法》(California Consumer Privacy Act,CCPA)要求下,用户必须被允许删除和更新其数据,并跟踪其数据的使用和来源。

然而,泰坦尼克号上不会有新乘客到来。所以你不用担心新乘客到来时把他们加入数据集,不用担心因为近亲的 GDPR 请求而把乘客从数据集中移除,也不用担心因为无法或不想在所有可用数据上训练模型而选择可用乘客的子集作为训练数据。你还将需要从你选为训练数据的任何行中重新创建训练集和测试集。

生产级 ML 系统通常处理可变数据(mutable data)。可变数据通常存储在行导向或列导向存储中,支持高效的插入、追加、更新和删除。这给那些只用 Python 读写特征数据的数据科学家带来了挑战。过去,他们必须学习 SQL 并直接操作数据库,但现在,他们也可以用 Python 和 DataFrame API 读写可变数据,这是本书的主要焦点。

可变数据给特征工程带来了挑战。有许多数据转换——比如聚合、分箱和降维,传统上称为"数据准备步骤”——可以在把特征数据存入数据库(或特征存储,feature store)之前执行。然而,也有一些数据转换是由训练数据参数化的,比如把(分类的)字符串编码为数值表示,以及归一化数值变量。由于在从数据存储中选择并读取训练数据之前,你并不知道训练数据是什么,这些数据转换发生在从数据存储读取之后。在第2章中,我们将介绍一个面向 ML 的数据转换分类法,帮助你确定应该在保存特征数据之前还是从特征存储读取数据之后执行数据转换。在第6章和第7章中,我们将深入探讨为批处理、实时和 LLM ML 系统创建特征的数据转换细节。

机器学习系统简史

在 2010 年代中期,革命性的 ML 系统开始出现在消费互联网应用中,比如 Facebook 的图像标注和 Google Translate。第一代 ML 系统要么是按计划做预测的批处理 ML 系统(见图 1-1),要么是响应用户动作做预测的交互式在线 ML 系统。

单体批处理 ML 系统示意图,说明其组件:来自历史数据的训练数据、特征创建、模型训练、模型注册表和针对新数据的推理预测。

原书插图

构建批处理 ML 系统的一个挑战是确保为训练数据创建的特征与为批处理推理创建的特征保持一致。这可以通过构建一个以训练模式或推理模式运行的批处理程序(或流水线)来实现。单体架构确保运行相同的"创建特征"代码来创建训练数据(来自历史数据)和推理数据(来自新数据)。

图 1-2中,你可以看到一个交互式 ML 系统,它接收来自客户端的请求并实时返回预测。在这种架构中,你需要两个独立的系统——一个离线训练流水线和一个在线模型服务(model-serving)服务。你不再能通过单个单体程序来确保训练和推理之间的特征一致。早期解决这个问题的方法是给特征创建源代码做版本管理,并确保训练和服务使用相同的版本。

交互式 ML 系统架构示意图,具有独立的离线训练和无状态在线推理流水线,突出特征创建、模型训练和注册以及预测过程。

原书插图

无状态在线 ML 系统过去有用,现在在某些情况下仍然有用。例如,图像标注程序可以接收照片作为输入,图像分类模型预测图像中识别出的对象的边界框和标签。第一批使用 LLM 的聊天机器人就是无状态在线 ML 系统。聊天机器人服务器把用户输入作为预测请求接收,并把用户输入追加到系统提示词(system prompt)中。系统提示词是由聊天机器人开发者添加的文本,通常指示 LLM 要乐于助人、不要辱骂、不要泄露敏感信息。合并后的提示词被发送给 LLM,LLM 返回一个响应。LLM 的响应只是对合并提示词之后最可能出现的字符序列的预测。

然而,无状态在线 ML 系统受其训练数据的限制。图像分类器只能识别其训练数据中固定数量的标签。聊天机器人无法回答关于其训练数据创建之后发生的事件的问题。你可以通过把历史和上下文信息作为模型输入来克服这个限制。例如,在线推荐模型可以把您最近查看或喜欢的产品作为输入,以预测要推荐给你的产品。也就是说,把你最近的历史作为输入特征传递,就足以让模型用最近的数据做预测——你不需要用你最近订单的信息重训练模型。类似地,我们将看到,你也可以检索并把上下文信息添加到 LLM 的提示词中,这样它就能回答关于其训练截止时间之后发生的事件的问题。例如,一个 2024 年训练的 LLM,如果你在提示词中包含关于 2025 年 NBA 总决赛的维基百科文章,它就能告诉你谁赢得了 2025 年 NBA 总决赛。

但是这些上下文和历史从哪里来呢?请求预测的客户端可以把它作为参数传递,但更多时候,客户端是一个状态存储在数据库中的应用。例如,我们的推荐模型可能需要根据用户最近的活动和历史订单创建输入特征。但特征的源数据不能由客户端发送,因为它存储在客户端的数据库中。如果特征是由一个独立的、有状态的系统创建和存储的,而在线模型在预测请求到来时只需读取这些特征,会怎么样呢?

构建有状态在线 ML 系统的普遍问题最早由特征存储解决,Uber 在 2017 年通过其关于内部 Michelangelo 平台的文章将特征存储作为一种新的平台类别引入。特征存储把上下文和历史的转换与存储管理为特征,在线模型可以轻松使用这些特征(见图 1-3)。

机器学习流水线中数据流和模型训练示意图,显示特征流水线、训练流水线和在线推理流水线与数据源和预测消费者之间的交互。

原书插图

特征流水线从一个或多个数据源读取历史或新数据,把它转换为特征,并把特征数据存储在特征存储中。在线推理程序使用 API 调用检索预计算的特征数据,然后传递给模型进行在线预测。随着特征存储随时间收集特征数据,它也用于创建训练模型所用的训练数据。特征流水线可以是按计划运行的批处理程序,但特征数据的时效性只能达到最近一次运行的程度。如果你需要把非常近期的事件(比如最近 10 分钟内的用户活动)作为特征使用,你可以把特征流水线写成流处理程序。批处理和流式特征流水线分别在第8章和第9章介绍,而特征存储和数据转换在第4章到第7章中探讨。ML 流水线是一个统称,指构成 ML 系统的任何特征流水线、训练流水线和推理流水线。

无状态 LLM 应用,比如第一批聊天机器人,面临与无状态 ML 系统类似的挑战——它们需要把相关且及时的上下文作为输入纳入,不仅包括训练数据截止时间之后发生的事件,还包括 LLM 训练时未抓取的私有数据。解决方案是在系统提示词中包含在请求时检索到的上下文数据。第一个被广泛采用的方法是使用向量数据库的 RAG(见图 1-4)。

LLM 的检索增强生成(RAG)过程示意图,说明向量嵌入流水线、向量数据库、提示词模板和检索器等组件。

原书插图

第一批由 RAG 驱动的 LLM 应用把用户输入作为字符串,用输入字符串查询向量数据库,通过近似最近邻(approximate nearest neighbor,ANN)搜索返回与输入相似的文本块。你想包含在系统提示词中的任何上下文信息都必须先写入向量数据库,而且你需要一个向量嵌入流水线来保持这些数据是最新的。该流水线把源数据转换为文本块,然后使用嵌入模型把文本块转换为向量嵌入。向量嵌入随后被写入向量索引,以便以后用于 ANN 搜索。系统提示词不再是静态的,因为它是一个提示词模板,既包含指令,也包含用从向量索引检索到的文本填充的空槽。提示词的大小也是有限的,由 LLM 的上下文窗口(context window)决定。上下文窗口同时存储 LLM 的输入和输出,最近的 LLM 的上下文窗口从几 KB 到几 MB 不等。为 LLM 准备和检索上下文数据的挑战被称为上下文工程(context engineering)。上下文工程的目标是从用户输入和上下文数据构造一个提示词,使 LLM 对给定输入的输出性能最大化。

第一批 LLM 应用是高度聚焦的助手,帮助编码、回答医疗问题,甚至创建烹饪食谱。随着 LLM 应用承担越来越复杂的任务,它们在查询哪些数据和执行哪些任务方面需要更多的自主性。智能体(agent)是一类 LLM 应用,在如何查询多样化的数据源(向量索引、搜索引擎、特征存储等)以检索相关上下文数据,以及如何规划并执行任务以实现目标方面,具有一定程度的自主性。Anthropic 将智能体定义为“LLM 动态地指导自己的流程和工具使用、保持对如何完成任务的控制权的系统”。智能体代表了从人机交互到以机器-机器交互为主的范式转变。用户设定高层目标,开发者向智能体提供实现这些目标所需的工具和上下文。

图 1-5展示了 LLM RAG 应用如何演变为智能体 AI 系统,其中在线推理程序变成了智能体程序。智能体有一个统一标准,称为模型上下文协议(Model Context Protocol,MCP),用于从各种数据源检索 RAG 数据,并把内部和外部 API 用作工具。

智能体 AI 系统中数据处理流水线和模型上下文协议集成示意图,用于准备上下文数据和决策。

原书插图

在 LLM 应用和智能体架构中,LLM 的训练都是可选的,但可以通过使用特征存储中的指令数据微调基础 LLM 来添加;见第10章。你会遇到与 ML 系统相同的智能体工程挑战,比如如何预计算上下文数据和向量嵌入并使它们可查询。我们在第5章和第6章介绍向量嵌入流水线,在第12章介绍 RAG 和上下文工程。

注意

是 ML 系统还是 AI 系统?ML 系统是一种通过 ML 算法和统计模型从数据中学习的 AI 系统。AI是一个更广泛的术语,还包括搜索、记忆以及用于构建智能体的许多技术。因此,我们经常使用批处理 ML 系统实时 ML 系统智能体 AI 系统这些术语来描述不同的 AI。我们将使用最通用的术语AI 系统,除非我们指的是特定类别的 ML 系统。

MLOps 与 LLMOps

这里描述的 ML 系统架构从无状态到有状态的演变并非发生在真空中。它发生在一个称为机器学习运维(machine learning operations,MLOps)的 ML 工程新领域内,这个领域可以追溯到 2015 年,当时 Google 的作者发表了一篇题为《机器学习系统中的隐藏技术债》(“Hidden Technical Debt in Machine Learning Systems”)的经典论文。这篇论文在 ML 开发者心中巩固了一句格言:构建 ML 系统的工作中,只有一小部分是训练模型。大部分工作是数据管理以及构建和运营 ML 系统基础设施。

受软件工程中 DevOps 运动的启发,4MLOps是一套用于构建可靠、可扩展 ML 系统的实践和流程,这些系统可以快速、增量地开发、测试,并尽可能使用自动化部署到生产环境。MLOps 实践应该帮助你收紧开发循环,缩短你对软件或数据做出更改、测试更改、然后把更改部署到生产环境所需的时间。许多具有数据科学背景的开发者会被 MLOps 对自动化、测试和运维的系统性关注吓到。相比之下,DevOps 的北极星是尽快做出最小可行产品(minimum viable product,MVP),然后迭代改进那个 MVP。在第2章中,我们将介绍构建 MVP 的流程。

从构建 MVP 到拥有可靠的 ML 系统,涉及的测试层级比传统软件系统更多。输入数据或代码中的小 bug 很容易导致 ML 模型做出错误预测。ML 系统需要大量的工程投入来测试,并确保它们产生高质量、无偏见的预测。测试发生在 ML 系统开发的各个阶段,从特征工程到模型训练再到模型部署。在传统软件系统中,你必须测试代码和集成。在 ML 系统中,你还需要对输入数据和模型进行测试和监控。

开发时运行的测试包括:

  • 单元测试(Unit tests)
    • 验证特征逻辑(特征逻辑的更改可能污染训练数据)。
  • 集成测试(Integration tests)
    • 验证 ML 流水线,帮助发现 Python 代码中的错误。
  • 模型验证测试(Model validation tests)
    • 检查良好的性能和偏见。
  • 评估(Evals)
    • 用于 LLM 应用和智能体的安全性、可靠性和性能。

生产 ML 中的监控和测试包括:

  • 数据验证测试(Data validation tests)
    • 防止坏数据进入你的系统。
  • 模型性能监控(Model performance monitoring)
    • 大多数模型的性能会随时间下降。
  • 特征漂移检测(Feature drift detection)
    • 检查推理时的输入数据是否与模型的训练数据在统计上显著不同。
  • A/B 测试(A/B tests)
    • 在把新版本的模型部署到生产环境之前对其运行。
  • 护栏(Guardrails)
    • 对 LLM 输入和输出运行,防止有害的响应。

这份 ML 系统测试和检查清单,与 MLOps 社区围绕一套共享价值观和信念的形成同步增长。他们的 MLOps 原则是什么?

MLOps 从业者相信,测试应该对开发速度产生最小的摩擦。自动化执行测试有助于提高你的生产力。有许多持续集成(continuous integration,CI)平台可以自动执行开发测试。流行的 CI 平台有 GitHub Actions、Jenkins 和 Azure DevOps。CI 不是开始构建 ML 系统的先决条件。如果你有数据科学背景,全面的测试可能是你没有经验的东西,花时间逐步把测试加入你的武器库和你构建的 ML 系统中是完全可以的。你可以从函数的单元测试、训练流水线中的模型性能和偏见测试,以及所有 ML 流水线的集成测试开始。你可以通过添加 CI 支持来自动化测试,每当你把代码推送到源代码仓库时就运行你的测试。你可以在验证了你的 MVP 值得维护之后添加自动化测试。

MLOps 从业者喜欢那种当你推送源代码更改时,你的 ML 工件或系统被自动部署的感觉。部署通常与开发(dev)、预生产(preprod)和生产(prod)环境的概念相关联。ML 资产在 dev 环境中开发,在 preprod 中测试,在部署到 prod 环境之前再次测试。虽然最终可能需要人工签字批准把 ML 工件部署到生产环境,但各个步骤应该在称为持续部署(continuous deployment,CD)的流程中自动化。在本书中,我们遵循这样的理念:你可以在 dev、preprod 或 prod 环境中构建、测试和运行你的整个 ML 系统。你的 ML 系统可以访问的数据可能取决于你部署在哪个环境中(dev 可能无法访问生产数据)。我们将在第13章中详细研究 CD。

MLOps 从业者通常信奉数据库界那句著名的格言:“垃圾进,垃圾出”(garbage in, garbage out)。许多 ML 系统使用的数据几乎没有质量保证,盲目地摄入垃圾数据会导致训练得很好的模型仍然预测出垃圾结果。在第6章中,我们将为特征流水线设计和编写数据验证测试。我们将详细说明如果你发现数据不正确、缺失或损坏,可以采取的缓解措施。

MLOps 从业者梦想有一个用于升级系统的大绿按钮,和一个用于回滚有问题的升级的大红按钮。特征和模型的版本管理是 A/B 测试以及无停机升级/降级 ML 系统的必要先决条件。版本管理使你能够快速把更改回滚到较早的正常工作的模型版本,以及喂给它的带版本的特征。

MLOps 从业者不喜欢他们的 LLM 或智能体的新版本(比如 Amazon Q 的一个版本,一个编码智能体,可以把用户的文件系统清空!)引入意外行为时出现的惊吓。在第13章中,我们将研究设计和运行评估(evals),以在 LLM 应用和智能体的更改进入生产环境之前评估它们。

MLOps 从业者喜欢了解他们的系统表现如何。生产 AI 系统应该收集指标来构建仪表盘和告警,用于:

  • 相对于某个业务关键绩效指标(key performance indicator,KPI)监控模型预测的质量
  • 监控新到达的数据是否存在漂移
  • 衡量 ML 平台(模型服务、特征存储、向量索引、LLM 和 ML 流水线)的性能(吞吐量和延迟)

MLOps 从业者需要来自运维服务的日志来调试和改进 AI 系统。肉眼检查模型日志是 LLM 错误分析的有力技术,如第14章所述。在经典 ML 系统中,他们还需要日志来调试错误和理解模型性能。

请注意。本书对 MLOps 采取了一种非传统的方法。你不会学习用 Terraform 把基础设施编程为代码,不会学习编写 Dockerfile 和容器化流水线,也不会成为 Kubernetes 专家。相反,你将学习测试、版本管理、运营和监控为你的 AI 系统提供动力的 ML 流水线。

AI 系统的统一架构:特征、训练和推理流水线

软件中的模块化(modularity)是把系统分解为更小、更易管理的模块的能力,这些模块可以被独立开发并组合成完整的软件系统。模块化帮助我们构建更高质量、更可靠的软件系统,因为它允许模块被独立测试。AI 系统也可以从模块化中受益,因为它使团队能够更快地构建更高质量的 AI 系统。

实现模块化涉及把你的 AI 系统结构化,使其功能被分离为可以独立开发、运行和测试的独立组件。模块应该保持小巧,易于理解和文档化。模块应该支持 AI 系统中功能的重用、团队之间工作的清晰划分,以及通过共享对 AI 系统中概念和接口的理解,改善团队之间的沟通。

在本章前面,我们为批处理、无状态实时、有状态实时、RAG LLM 和智能体 AI 系统展示了五种不同的 AI 系统架构。这些是你在开发新 AI 系统时可以使用的有用架构模式。然而,这些架构非常不同,开发者在一种架构和另一种架构之间切换,或者把一种架构的学习成果迁移到另一种架构,是有挑战的。

幸运的是,我们可以做得更好。有一个用于开发所有 AI 系统的统一架构,它遵循把任何 AI 系统自然分解为特征创建、模型训练和推理流水线的思路。在 KTH(瑞典皇家理工学院),我的学生以团队形式做项目构建 AI 系统,尽管他们构建了各种不同的 AI 系统,但他们可以很容易地划分构建系统的工作,并用这种特征/训练/推理(feature/training/inference,FTI)分解来沟通他们的系统架构。在企业中,不同的团队可以负责不同的部分:特征创建可能需要数据工程师的帮助,模型训练是数据科学家的领域,推理可能涉及 IT 运维人员。ML 工程师被期望为所有三类流水线做出贡献。

三种不同的 ML 流水线有清晰的输入和输出,可以独立开发、测试和运营:

  • 特征流水线(Feature pipelines)
    • 以数据为输入,产出可复用的特征数据作为输出。
  • 训练流水线(Training pipelines)
    • 以特征数据为输入,训练模型,输出训练好的模型。
  • 推理流水线(Inference pipelines)
    • 以特征数据和模型为输入,输出预测和预测日志。

模块化只有在模块能被轻松组合成可运行系统时才有效。好的例子是 Web 应用:30 年后仍然用独立的展示、业务逻辑和数据库模块构建。另一方面,微服务架构在微服务过多时会受影响,因为当它们组合成单个系统时,运营复杂性会增加。对于我们的 AI 系统分解,我们可以自然地用三种类型的 ML 流水线组成 AI 系统,方法是让它们成为独立的程序,通过一个由特征存储和模型注册表组成的共享数据层连接起来。

特征存储把实时数据存储在行导向存储中,供在线推理流水线和智能体低延迟访问;把历史数据存储在列式数据存储中,用于训练模型和批处理推理;把向量嵌入存储在向量索引中,供推理流水线和智能体使用。

我们现在可以把 AI 系统定义为一组通过特征存储和模型注册表连接起来的独立特征流水线、训练流水线和推理流水线(见图 1-6)。

AI 系统示意图,显示通过特征存储和模型注册表连接的特征、训练和推理流水线,促进数据转换、模型训练和预测过程。

原书插图

特征流水线摄入回填(backfill)和生产数据,计算特征数据并作为表格数据存储在特征存储中。特征流水线既可以是批处理程序,也可以是流处理程序。训练流水线从特征存储读取训练数据,并把它们产生的任何训练好的模型存储在模型注册表中。推理流水线使用模型(从模型注册表下载或通过 API 获取)和新特征数据(从特征存储预计算和/或在预测请求时从可用数据计算)输出预测。

ML 流水线可以在几乎任何计算引擎上运行。流行的批处理计算引擎包括数据仓库中的 SQL、Spark、Pandas、Polars 和 DuckDB。流行的流处理引擎包括 Flink、Spark Structured Streaming 和 Feldera。训练流水线最常用 Python 实现,在线推理流水线和智能体也是如此。批处理推理流水线通常用 PySpark、Pandas 和 Polars 编写。

带特征存储的 AI 系统类别

AI 系统由其如何计算预测来定义,而不是由消费预测的应用类型来定义。带特征存储的 AI 系统可以分为:

  • 实时(交互式)ML 系统(Real-time (interactive) ML systems)
    • 响应用户请求做预测。它们可以从预测请求参数按需计算特征,和/或从特征存储或其他外部系统读取预计算的特征。流处理通常用于预计算新鲜(fresh)的特征,与批处理特征流水线相比,使交互式 ML 系统能更快地对用户动作做出反应。
  • 智能体工作流(Agentic workflows)
    • 这是用户引导的 AI 系统,以一定程度的自主性,通过使用 LLM 和工具(即在外部系统上执行动作,并通过使用向量索引、行导向数据存储、列导向数据存储和外部 API 等数据源获取上下文信息)来实现目标。特征流水线、向量嵌入流水线和实时特征工程创建供智能体使用的上下文数据。
  • 批处理 ML 系统(Batch ML systems)
    • 按计划运行批处理推理程序。它们接收新特征数据和模型,输出预测,预测通常存储在某个下游数据库中(称为推理存储,inference store),供以后某个支持 ML 的应用消费。
  • 流处理 ML 系统(Stream processing ML systems)
    • 使用嵌入式模型对流式数据做预测,无需用户输入。它们通常是机器对机器的 ML 系统。例如,网络入侵检测 ML 系统可以使用流处理从网络流量中提取特征,并用模型预测网络入侵。

实时 ML 系统和智能体工作流都是交互式系统:提供预测请求 API、处理并发预测请求,并使用模型做预测。我们使用的区分是:实时 ML 系统有一个自定义训练的模型(不是 LLM,而可能是决策树或深度学习模型),托管在内部的模型服务基础设施上,并有一个相对简单的在线推理流水线。相比之下,智能体工作流有一个更复杂的在线推理流水线程序——智能体程序,它既使用工具,也使用通常通过外部 API 访问的 LLM。

嵌入式/边缘 ML 系统

一种本书不涉及的流行 ML 系统类型是嵌入式或边缘(embedded or edge)ML 系统。它们通常使用嵌入式模型,并从输入数据计算特征,不使用特征存储中的预计算特征。边缘 ML 系统是运行在资源受限、脱离网络的设备上的实时 ML 系统。例如,Tetra Pak 公司生产纸包装,使用图像分类器识别工厂车间纸箱中的异常。也就是说,没有数据离开工厂车间——所有数据都在网络边缘处理。

以下是我们将在本书中构建的 AI 系统:

  • 批处理 ML 系统
    • 第3章中,你将构建一个空气质量预测仪表盘,显示你附近地点的空气质量预测。它将使用公共传感器的空气质量观测值和天气数据作为特征。你将训练一个模型,用天气预报数据预测空气质量。
  • 实时 ML 系统
    • 第4章开始,我们将开发一个信用卡欺诈检测 ML 系统。它将接收一笔信用卡交易,从特征存储检索关于该信用卡近期使用的预计算特征,然后构建一个特征向量,发送给你训练的决策树模型,预测该交易是否涉嫌欺诈。在第15章中,我们将构建一个类似 TikTok 的视频推荐系统,基于检索-排序(retrieval-and-ranking)架构。它将使用流处理从用户动作(如点击和滑动)创建特征,用一个双塔(two-tower)嵌入模型进行检索,用一个更快的极端梯度提升(eXtreme Gradient Boosting,XGBoost)模型进行排序。
  • 智能体 AI 系统
    • 我们将为空气质量预测系统和 TikTok 推荐系统添加 LLM 能力,并给出 LlamaIndex 中智能体的例子。

本书使用的 ML 框架和 ML 基础设施

在本书中,我们将用 Python 编写的程序构建 AI 系统。鉴于我们的目标是构建 AI 系统,而不是支撑它们的 ML 基础设施,我们必须决定在本书中涵盖哪些平台。考虑到篇幅限制,我们必须把自己限制在一组动机充分的选项中。

对于编程,我们选择 Python,因为它对开发者友好,是数据科学的主导语言,并且在数据工程中越来越重要。我们将使用 Python 的开源框架,包括用于特征工程的 Pandas 和 Polars、用于 ML 的 Scikit-Learn 和 PyTorch,以及用于模型服务的 KServe。Python 可以用于从原始数据创建特征、模型训练,到为我们的 AI 系统开发用户界面的一切。我们还将使用预训练的 LLM——开源基础模型。在适当的时候,我们还会提供使用企业中广泛使用的其他编程框架或语言的示例,比如用于可扩展数据处理的 Spark 和 dbt/SQL,以及用于实时 ML 系统的流处理框架。话虽如此,本书中展示的示例 AI 系统在开发时只要求具备 Python 知识作为先决条件。

为了把我们的 Python 程序作为流水线在云端运行,我们将使用无服务器平台,比如 ModalGitHub Actions。GitHub 和 Modal 都提供免费层(尽管 Modal 需要信用卡注册),使你能够运行本书介绍的 ML 流水线。如果你有一个专用的 Hopsworks 集群,你也可以在那里运行你的 ML 流水线。如果你有任何其他运行 Python 作业的平台,这里的 ML 流水线示例也应该可以工作。

对于探索性数据分析、模型训练和其他非运营性服务,我们将使用开源的 Jupyter Notebooks。最后,对于托管在云端的(无服务器)用户界面,我们将使用 Streamlit,它也提供免费的云层。替代方案是 Hugging Face Spaces 和 Gradio。

我们将使用 Hopsworks 作为无服务器 ML 基础设施,利用其特征存储、模型注册表和模型服务平台来管理特征和模型。Hopsworks 是开源的,是第一个开源且面向企业的特征存储,其无服务器平台有免费层。使用 Hopsworks 的另一个原因是我(作者)是它的开发者之一,所以我可以作为具有代表性的 ML 基础设施平台,对其内部工作原理提供更深入的见解。借助 Hopsworks 的免费无服务器层,你可以零成本地部署和运营你的 AI 系统,无需安装或运营 ML 基础设施平台。话虽如此,鉴于所有示例都使用常见的开源 Python 框架,你可以轻松修改提供的示例,用现有特征存储(如 Feast)、模型注册表和模型服务平台(如 MLflow)的任意组合替换 Hopsworks。或者,你可以使用 Databricks、Google Cloud Platform(GCP)Vertex 或 Amazon Web Services(AWS)SageMaker。

总结

在本章中,我们介绍了带特征存储的批处理、实时和 LLM AI 系统。我们介绍了 AI 系统的主要属性、它们的架构,以及为它们提供动力的 ML 流水线。我们介绍了 MLOps 及其作为开发和演进 AI 系统的一套最佳实践的历史演变,并提出了一种新的 AI 系统架构:由特征存储连接的 FTI 流水线。在下一章中,我们将更仔细地研究这种用于构建 AI 系统的新 FTI 架构,以及你如何把 AI 系统构建为连接的 FTI 流水线,从而更快、更可靠地构建 AI 系统。

1 企业计算(enterprise computing)指企业用于运营、分析和数据科学的信息存储和处理平台。

2 Parquet 文件以列式格式存储表格数据——每列的值存储在一起,支持更快的列级聚合操作(比如数值列的平均值)和更好的压缩,使用了字典编码和游程编码

3 泰坦尼克号数据集是 ML 中二元分类问题的著名例子,你必须训练一个模型预测给定乘客是否会幸存。

4 维基百科指出,DevOps 集成并自动化软件开发(Dev)和 IT 运维(Ops)的工作,作为改进和缩短系统开发生命周期的手段。