ML系统设计¶
ML系统设计将文件01-03中的基础设施模式应用于机器学习的特定挑战。本文件涵盖ML生命周期、数据管理、训练基础设施、模型评估、推理策略、特征工程、ML流水线和监控。
- 系统设计面试中遇到"为YouTube设计一个推荐系统"这样的问题,不是在让你描述推荐算法。而是要求你设计整个系统:数据流水线、特征工程、模型训练、评估、推理服务、监控和迭代。本文件提供了这个框架。
ML系统生命周期¶
- 每个ML系统都遵循相同的生命周期,无论它是垃圾邮件分类器还是基础模型:
问题定义 → 数据 → 特征 → 训练 → 评估 → 部署 → 监控 → 迭代
↑ │
└────────────────────────────────────────────────────────────────────┘
问题定义¶
-
在接触数据或模型之前,先明确:
- 预测什么?(点击概率、下一个token、物体边界框)
- 用户是谁?(最终用户、内部分析师、其他ML模型)
- 约束条件是什么?(延迟<100ms、离线批量可接受、必须在设备端运行)
- 业务指标是什么?(收入、参与度、准确度)以及ML指标与之如何关联?
- 基线是什么?(启发式、基于规则的系统、现有模型)——你必须超越基线才能证明ML系统的价值。
-
常见错误:在理解问题之前就跳到模型架构。"我们应该用Transformer"不是系统设计答案。正确的回答是:"我们需要在200ms内为1000万个候选项预测点击概率,因此需要一个两阶段系统:快速检索后接小型排序模型。"
数据管理¶
数据收集和标注¶
-
显式标签:人类标注数据(点击/未点击、物体边界框、对话质量评分)。昂贵(每个标签约\(0.02-\)10,取决于复杂度)、速度慢且主观。
-
隐式标签:从用户行为派生出标签。点击、停留时间、购买、跳过。便宜且丰富但有噪声(点击不意味着满意;跳过不意味着不喜欢)。
-
编程式标注(Snorkel):编写标注函数(启发式、正则表达式、现有模型),对每个样本投票。统计聚合投票结果生成概率标签。可扩展到数百万样本,精度中等。
-
主动学习:模型识别其最不确定的样本,请求人工为这些样本标注。这最大化标注效率:1000个主动选择的标签可以达到10000个随机标签的效果。
数据质量¶
-
数据验证:检查每批新数据的schema违规(缺失字段、错误类型)、分布偏移(平均值显著变化)和容量异常(预期100万行,实际只有50万行)。
-
Great Expectations和TFX Data Validation是定义数据期望并在违反时告警的工具。
-
数据版本控制:每轮训练都应该可复现。DVC(第15章)跟踪代码旁边的数据文件。每个数据集版本获得一个哈希值;训练配置引用该哈希值。
特征存储¶
-
特征存储(第15章)为训练和推理提供一致的特征。关键概念:
-
离线特征:由批处理流水线(Spark)计算,存储在数据仓库中。在训练和批量推理时使用。示例:用户过去30天的平均会话时长、商品的累计购买次数。
-
在线特征:实时计算或预计算后从低延迟存储(Redis、DynamoDB)中提供。在实时推理时使用。示例:用户最近5次操作、当前购物车内容。
-
训练-推理偏差:如果特征计算在训练和推理之间不同,模型在推理时看到的特征值与训练时不同。特征存储通过对两者使用相同的计算来消除这种偏差。
-
训练基础设施¶
-
对于本书读者,分布式训练已在第6章中深入介绍(数据并行、模型并行、混合精度、扩展定律)。这里我们聚焦系统方面:
-
实验跟踪(W&B、MLflow——第15章):每次训练运行记录超参数、指标、git commit、数据版本和硬件。这相当于模型的版本控制。
-
超参数调优:自动搜索超参数。方法:网格搜索(穷举、昂贵)、随机搜索(出人意料地有效)、贝叶斯优化(对目标函数建模,在可能改进最大的地方采样)和ASHA(异步连续折半:启动多个试验,尽早淘汰表现不佳的)。
-
训练流水线编排(Airflow、Kubeflow——第15章):自动化以下序列:数据准备 → 训练 → 评估 → 注册。安排每日重训练。失败时告警。
模型评估¶
离线评估¶
-
留出测试集:在模型训练时从未见过的数据上评估。标准方法,但如果测试集不能代表生产数据可能会误导。
-
分片评估:按子组评估(按用户人口统计、内容类型、语言、时间段)。一个整体准确率95%的模型在特定少数群体上可能只有70%——这是不可接受的。
-
回测:对于时间序列或顺序预测,按时间顺序在历史数据上评估。在时间\(t\)之前的數據上训练,在\(t\)到\(t+\Delta t\)的数据上评估。避免使用未来数据训练导致的数据泄露。
在线评估¶
-
A/B测试:随机将线上流量分为对照组(旧模型)和实验组(新模型)。以统计显著性比较业务指标(收入、参与度、留存率)。评估ML变更的黄金标准。
-
样本量:你需要足够的数据来检测预期的效应大小。点击率0.1%的改进需要数百万次展示才能显著检测到。
-
持续时间:至少运行一个完整周期(大多数产品1-2周)以捕捉星期几效应。
-
护栏指标:在监控目标指标的同时,监控那些不应变化的指标(页面加载时间、错误率、崩溃率)。一个增加收入但也增加崩溃率的模型是净负面的。
-
-
影子部署:在生产环境中让新模型与旧模型同时运行。两者收到相同的请求,但只有旧模型的预测被发给用户。比较输出结果。这在不给用户带来风险的情况下发现bug和质量问题。
-
交叉排名(Interleaving):对于排序问题,将新旧模型的结果交叉混合在一个列表中。用户与交叉列表交互,你衡量哪个模型的结果获得更多参与度。相比A/B测试需要更少的用户即可达到显著性。
模型推理服务¶
批量推理 vs 实时推理¶
-
批量推理:为所有可能的输入预计算预测。存储在数据库/缓存中。从缓存中提供。适用于:输入空间有限(每晚为所有用户计算推荐)、实时性要求不高(每日预测可以接受)和延迟容忍度高。
-
实时推理:为每个请求按需计算预测。适用于:输入空间无限(任意用户查询)、实时性重要(为此特定查询即时预测)和延迟必须低。
-
许多系统两者都用:批量预计算一组候选项(便宜、覆盖80%流量),实时处理其余部分(昂贵、覆盖尾部查询和新用户)。
模型版本管理和注册中心¶
-
模型注册中心(MLflow、W&B、SageMaker)存储训练好的模型及元数据:
- 版本号和训练日期。
- 训练配置和数据版本。
- 评估指标(准确度、延迟、内存使用)。
- 阶段:开发 → 预发 → 生产 → 已归档。
-
回滚:如果新模型在生产中使指标退化,立即回滚到之前的版本。注册中心使这成为一键操作。
特征工程¶
- 特征工程将原始数据转换为模型所需的输入。它往往是ML中杠杆效应最高的活动:更好的特征改善所有模型,而更好的模型受限于所接收的特征。
在线特征 vs 离线特征¶
-
离线特征是预计算的且变化缓慢(用户人口统计、30天聚合指标)。由批处理流水线(Spark)计算,存储在特征存储中。
-
在线特征反映当前状态且变化快速(购物车中的商品、最近操作、当前位置)。从事件流实时计算或从快速存储中查找。
-
特征新鲜度:某些特征需要秒级新鲜(欺诈检测:根据最近5笔交易判断此交易是否异常?)。其他特征可以允许数小时的陈旧(推荐:基于历史记录,此用户偏好什么类型?)。更新鲜的特征计算和提供的成本更高。
常见特征模式¶
- 计数特征:时间窗口内的事件计数(过去7天的购买次数、过去24小时的登录次数)。
- 嵌入特征:为类别变量学习到的嵌入(用户嵌入、物品嵌入、查询嵌入)。这些是双塔模型和类似架构的输入。
- 交叉特征:两个或多个特征的组合(用户年龄×物品类别)。捕捉单一特征遗漏的交互。
- 时间特征:距上次操作的时间、星期几、一天中的时间。捕捉时间模式。
- 聚合特征:数值特征在组内的均值、中位数、最小值、最大值、标准差(此卖家的平均评分)。
ML流水线¶
- ML流水线编排从数据到部署模型的整个工作流:
-
每个步骤是编排器(Airflow、Kubeflow、Metaflow——第15章)中的一个任务。流水线:
- 按计划运行(每日重训练)或触发运行(新数据可用)。
- 是幂等的(重新运行产生相同结果)。
- 具有重试逻辑(如果特征计算失败,最多重试3次并退避)。
- 产生工件(训练好的模型、评估报告、特征统计),这些工件被版本化并存储。
-
Metaflow(Netflix/Outerbounds)特别适合ML:它将代码、数据和模型一起版本化,支持本地开发和云执行使用相同的代码,并与K8s和AWS集成。
监控¶
- 我们在第15章中介绍了监控基础(Prometheus、Grafana、告警)。这里我们聚焦ML专用监控:
数据漂移¶
-
数据漂移发生在新数据的分布相对于训练数据发生变化时。在夏季数据上训练的模型可能在冬季数据上表现不佳(不同的用户行为、不同的产品可用性)。
-
检测:使用统计检验比较新特征分布与训练分布:
- KS检验(Kolmogorov-Smirnov):比较两个经验分布。检验它们是否来自同一底层分布。
- PSI(群体稳定性指数):衡量分布偏移了多少。PSI < 0.1为稳定,0.1-0.25为中等偏移,>0.25为显著偏移。
- 嵌入漂移:使用质心距离或MMD(最大均值差异)比较新查询与训练集的嵌入分布。
概念漂移¶
-
概念漂移发生在输入和输出之间的关系发生变化时。特征看起来一样,但正确的预测不同。示例:用户偏好在文化事件、疫情或产品变更后发生变化。
-
概念漂移比数据漂移更难检测,因为它需要标注数据。监控代理指标:点击率、转化率、用户满意度评分。持续下降表明概念漂移。
模型退化¶
-
模型随时间退化有多种原因:数据漂移、概念漂移、特征流水线bug(某个特征开始返回空值)和上游数据变更(第三方API更改响应格式)。
-
响应:检测到退化时,根据严重程度采取行动:
- 轻微:在最近数据上重训练(计划内的定期重训练会处理这种情况)。
- 中等:调查根本原因(哪个特征变了?哪个用户群体受影响?)。
- 严重:立即回滚到之前的模型版本,然后调查。
反馈循环¶
-
ML系统创造反馈循环:模型的预测影响用户行为,用户行为又成为下一个模型版本的训练数据。这些循环可以是良性的也可以是恶性的。
-
正反馈循环(危险):推荐模型大多展示热门内容 → 用户点击热门内容(因为只看到这些)→ 模型学到热门内容更热门 → 多样性崩塌。模型创造了确认其偏见的数据。
-
负反馈循环(同样危险):欺诈检测模型捕获了所有A类欺诈 → 没有A类欺诈进入训练数据 → 下一个模型没学到检测A类 → A类欺诈卷土重来。
-
缓解措施:
- 探索:展示一些模型不确定的内容(epsilon-贪婪、Thompson采样)。这生成多样化的训练数据。
- 反事实日志:记录模型本会预测什么,而不仅仅是用户看到了什么。在反事实数据上训练以去偏。
- 留出集:随机将一部分流量排除在模型过滤之外。未过滤的数据为评估模型质量提供真实标签。
- 延迟标签:在使用数据训练之前等待真实结果。今天点击的推荐明天可能后悔。欺诈预测必须等待拒付窗口(30-90天)。
嵌入表管理¶
-
大规模ML系统通常有1亿+条目的嵌入表(每个用户、物品、广告或实体一个嵌入)。在规模上管理这些是一个系统挑战:
-
存储:1亿个实体 × 256维 × float16 = 50 GB。放不下GPU内存。解决方案:存储在CPU内存中并在GPU端缓存,跨多机分片,或使用哈希嵌入(将实体哈希到固定大小的表,接受冲突)。
-
更新:模型重训练时嵌入会变化。将新的嵌入表部署到推理服务需要:在不中断线上流量的情况下加载50 GB到内存,验证正确性,在指标退化时回滚。对嵌入表使用蓝绿部署。
-
陈旧性:新创建的用户没有嵌入(冷启动问题)。解决方案:使用默认嵌入,通过特征到嵌入的模型从用户特征推导嵌入,或回退到非个性化模型。
公平性和偏见¶
-
ML系统可能系统性地以不同方式对待不同群体,通常反映了训练数据中的偏见。公平性监控是一种责任,不是可选功能。
-
需要监控的指标:
- 人口均等:正面预测率在不同群体(性别、种族、年龄)之间是否存在差异?
- 机会均等:真正例率在不同群体之间是否存在差异?(招聘模型应同样善于识别所有群体中的合格候选人。)
- 校准:如果模型对A组说P(合格)=0.7,那么A组中70%的人实际上合格吗?B组也一样吗?
-
实际步骤:
- 在分片(子组)上评估模型性能,而不仅仅是整体指标。
- 在模型评估流水线中纳入公平性指标(提高整体准确度但退化特定群体的模型不应未经审查就部署)。
- 记录已知的限制和失败模式。
- 为部署在敏感领域(招聘、贷款、刑事司法、医疗)的模型建立审查流程。