ML 设计案例 学 ML 系统设计最好的办法就是看完整案例。本文件走查七个完整设计:推荐系统、搜索排序、广告点击预测、欺诈检测、内容审核、对话式 AI,以及大规模图像搜索 每个案例都遵循一致的框架: 问题定义:我们在造什么、用户是谁、约束是什么? 数据:我们有什么数据、怎么收集、怎么标注? 特征:模型需要哪些特征? 模型:什么架构和训练方法? 服务:模型怎么部署和服务? 评估:怎么衡量成功? 迭代:随着时间推移,我们会做哪些改进? 推荐系统(如 YouTube、Netflix、Spotify) 问题定义 目标:给用户看他们会喜欢的内容,最大化参与度(观看时长、收听次数、点击)。 规模:10 亿+ 用户、1 亿+ 物品、每秒 1 万+ 次推荐。 延迟:整个推荐流水线 <200ms。
学 ML 系统设计最好的办法就是看完整案例。本文件走查七个完整设计:推荐系统、搜索排序、广告点击预测、欺诈检测、内容审核、对话式 AI,以及大规模图像搜索
1 亿物品 → 候选生成(快、粗) → 1000 个候选 → 排序(慢、精) → 100 个排好序的物品 → 重排(业务规则) → 20 个展示给用户的物品
物品嵌入索引:1 亿物品 × 256 维 × float16 = 50 GB。HNSW 索引再加约 2 倍开销 → 约 100 GB。能塞进一台 128 GB 内存的机器,或分片到 4 台 × 32 GB 的机器上。
用户嵌入计算:每个用户约 5ms(用户特征上的小 MLP)。在 1 万 QPS 下,需要约 50 个模型副本来扛负载。
ANN 搜索:用 HNSW 从 1 亿向量中找 top-1000 约 2ms。在 1 万 QPS 下,每个索引副本处理约 500 QPS → 需要 20 个副本。
排序模型:1000 候选 × 每候选约 0.1ms = 每请求 100ms。在 1 万 QPS 下,每秒需要 1000 GPU 秒 → 光排序就约要 10 张 A10G GPU。
基础设施总量:约 20 个嵌入索引副本 + 约 50 个用户嵌入服务器 + 约 10 张排序 GPU + 缓存 + 负载均衡器。成本:云价格下约 $50K-$100K/月。
新用户(无历史):用人口统计特征、设备/位置上下文和基于热度的推荐。交互 5-10 次后,切到个性化模型。
新物品(无交互数据):用基于内容的特征(标题、描述、缩略图嵌入)。分配一个探索预算:把新物品展示给一部分用户,以快速收集交互数据。加权期过后仍无交互的物品会被降权。
冷启动是一个系统问题:特征存储必须优雅地处理缺失特征(返回默认值,而非报错)。模型必须用带缺失特征的数据训练(训练时对用户历史特征做 dropout 来模拟新用户)。
在召回之前,先处理原始查询以改善结果:
拼写纠错:"reccomendation systm" → "recommendation system"。用编辑距离模型,或在搜索日志的(拼错,纠正)对上训练的序列到序列模型。
查询扩展:加入相关词以提高召回。"Python ML" → "Python machine learning scikit-learn pytorch"。用同义词典、词嵌入,或用 LLM 生成扩展。
意图分类:判断用户想要什么。"buy Nike shoes" 是交易型(transactional)(展示商品页)。"How does backpropagation work" 是信息型(informational)(展示文章)。"facebook.com" 是导航型(navigational)(直接跳到该网站)。不同意图应触发不同的召回策略和结果布局。
实体识别:从查询中抽出实体。"best restaurants near Times Square" → 地点:"Times Square",实体类型:"restaurants"。路由到一个位置感知的搜索流水线。
BM25(传统):基于倒排索引的词项匹配召回。快,对关键词查询有效。没有语义理解("dog food" 匹配不到 "canine nutrition")。
稠密召回(dense retrieval):用双编码器(如 DPR 或 ColBERT)把查询和文档编码成嵌入。用 ANN 搜索召回。能捕捉语义相似("dog food" 能匹配 "canine nutrition")。比 BM25 慢,但对自然语言查询更好。
混合召回(hybrid retrieval):结合 BM25 和稠密召回。BM25 找精确关键词匹配,稠密召回找语义匹配。合并并去重。两全其美。
排序学习(learning to rank):一个模型为每个 (query, document) 对打分。三种方法:
交叉编码器(cross-encoder):一个把 [query, document] 作为输入、输出相关性分数的 transformer。比双编码器(独立编码查询和文档)更准,因为它能捕捉细粒度交互。但对整个语料库太慢——只用于对召回得到的 top 100-1000 候选做重排。
特征工程 是广告系统的核心。特征包括:
模型:历史上用逻辑回归(简单、快、可解释)。现代系统用深度学习:一个 DLRM(深度学习推荐模型),类别特征走嵌入表,密集特征走 MLP。
校准(calibration):预测概率必须准确(如果模型说 P(click) = 0.05,那么这类曝光中实际真该有 5% 被点击)。校准至关重要,因为预测概率直接决定出价金额。
探索-利用(exploration-exploitation):永远只展示预测最好的广告,长期是次优的(你永远不会发现某条新广告可能更好)。Thompson 采样或 \epsilon-greedy 探索确保一部分曝光流向不太确定的广告,以收集数据。
梯度提升树(XGBoost、LightGBM)是表格型欺诈检测的标准。它们能处理混合特征类型、可解释(特征重要性),且训练快。
处理不平衡:欠采样多数类、过采样少数类(SMOTE),或在损失函数里用类别权重。Focal loss(第 8 章)会降低简单负样本的权重。
代价矩阵:一个假阳性(拦截合法交易)有它的代价(用户挫败、流失订单)。一个假阴性(漏掉欺诈)有不同的代价(经济损失)。决策阈值应当最小化总期望代价,而不是最大化准确率。
多模态分类:为文本、图片和视频分别设模型,再用一个融合层把它们的信号合并。
文本审核:微调过的语言模型把文本分类到各类(骚扰、仇恨言论、虚假信息、垃圾)。多语言模型能处理 100+ 种语言。
图片审核:视觉模型检测:露骨内容(裸露、暴力)、图片中的文字(OCR + 文本分类器)、以及已知有害内容(与已知 CSAM 数据库做哈希匹配)。
视频审核:按固定间隔采样帧,对每帧跑图像分类器,再结合音频转写(ASR → 文本分类器)。
策略即代码(policy-as-code):审核策略被定义成结构化规则,把模型输出映射到动作:
if text_model.hate_speech_score > 0.9: action = "remove" # 移除 elif text_model.hate_speech_score > 0.7: action = "human_review" # 人工复审 else: action = "allow" # 放行
主动式(proactive,发布前):在内容上线前跑分类器。高置信度违规被自动拦截。这能防止有害内容被任何人看到,但会增加发布延迟,且有假阳性风险(拦截合法内容)。
反应式(reactive,发布后):内容立即上线。用户可以举报违规。举报触发分类器 + 人工复审。对发布者延迟低,但有害内容在被发现前是可见的。
多数平台两者并用:对高严重性类别主动式(CSAM:零容忍,发布前拦截),对需要细致判断的类别反应式(虚假信息:需要人工判断,举报后复审)。
对于已知的有害内容(CSAM、恐怖主义宣传),用感知哈希(perceptual hashing):算出一个对小幅修改(裁剪、缩放、压缩)稳健的图像/视频哈希。与已知有害内容数据库(NCMEC 的哈希库、GIFCT 共享哈希库)对比。命中 → 立即移除,无需分类器。
PhotoDNA(微软)是 CSAM 检测的标准感知哈希。在许多司法管辖区它是法律义务,不只是技术选择。
规模:每天 10 亿帖 = 约 1.2 万帖/秒。每帖需要:文本分类(约 5ms)、图片分类(约 20ms)、哈希匹配(约 1ms)。在 1.2 万 QPS 下:需要约 60 个文本分类器、约 240 个图片分类器、约 12 个哈希匹配器(外加冗余)。
人工复审:如果 2% 的帖子被标记送审 = 每天 2000 万帖。每个复审员每天 100 帖,需要 20 万复审员(这就是为什么自动化准确率如此重要:假阳性每降 0.1%,每天就省 100 万次复审)。
延迟预算:主动式审核必须在发布流水线内完成(约 500ms)。文本(5ms)+ 图片(20ms)+ 哈希(1ms)+ 开销 = 远在预算内。视频是例外:即便对一段 10 分钟视频每秒采样 1 帧,也要 600 次分类器调用 → 异步处理。
自动移除 → 申诉人工复审 → 专家复审(法律、文化专家)→ 模糊案例交策略团队。每一级处理的案例更少,但判断更细。
反馈给模型:人工复审的判定是重训的最高质量标签。模型与复审员意见不一致的案例会优先用于主动学习——它们代表了模型处理得最差的那些情况。
用户查询 → 查询嵌入 → 向量搜索(文档库) → Top-K 个片段 ↓ 用户查询 + 检索到的片段 → LLM → 回答(带引用)
文档摄入:把文档切块并嵌入。切块策略(chunking strategy) 影响很大:
固定大小切块(fixed-size chunking):每 N 个 token 切一刀(如 500),带 M 个 token 重叠(如 50)。简单、块大小可预测,但可能在句子或段落中间切断,丢失上下文。
语义切块(semantic chunking):在段落或小节边界切。每块是一个连贯的信息单元。大小可变(有的块 100 token,有的 800),需要检索系统能处理可变长度。
递归切块(recursive chunking):先尝试在段落边界切。段落太长就在句子边界切。句子太长就按固定大小切。在连贯性和大小一致性之间取得最好平衡。
嵌入(embedding):用文本编码器(如 E5、BGE、Cohere embed)嵌入每个块,存进向量数据库。
检索:嵌入用户查询,在向量库里搜最相似的 k 个块(通常 k = 5-10)。可选地用交叉编码器重排以提高精度。
生成:把检索到的块作为上下文构造提示词:
System: You are a helpful assistant. Answer based ONLY on the provided context. If the answer is not in the context, say "I don't know." Context: [chunk 1] [chunk 2] ... User: {question}
护栏(guardrails):防止 LLM 回答产品领域外的问题、生成有害内容,或与检索到的上下文矛盾。实现为:输入过滤(拒绝跑题查询)、输出过滤(把回答和检索到的上下文核对)、以及宪法式提示(constitutional prompting,指示模型拒绝某些请求)。
对话记忆(conversation memory):保留最近 n 轮对话,放进提示词,让模型理解追问("那价格呢?" → 需要之前关于哪个产品的上下文)。
用户常常问含糊的追问:"那价格呢?"(什么的价格?)。查询改写(query rewriting) 用对话历史产出一个独立的查询:
这个改写后的查询才是被嵌入并拿去向量库搜索的对象。不改写的话,检索会无上下文地搜"价格",返回无关的块。
查询改写可以用一次小 LLM 调用(约 50ms)或一个微调过的序列到序列模型(约 5ms)完成。
查询图像 → 嵌入模型(ViT/CLIP) → 512 维向量 → ANN 搜索 → Top-K 结果
模型:一个预训练的视觉编码器(ViT、CLIP 的图像编码器、DINOv2)。必要时在特定领域(时尚、电商、医学影像)上微调。
训练:对比学习(第 10 章)。正样本对 = 同一图像的不同视图(或图像 + 匹配文本)。负样本对 = 随机图像。模型学会为相似图像产出相似嵌入,为不同图像产出不同嵌入。
离线:把全部 10 亿张图嵌入并建一个 ANN 索引。对 HNSW(第 03 节),建索引要几小时,索引存在内存里(10 亿 × 512 维 × float16 + 图开销 ≈ 128 GB)。
分片:把索引切分到多台机器上。每台持有一个分片。查询时并行搜索所有分片,合并 top-K 结果。
增量更新:新图像(上传、新品)必须加进索引。HNSW 支持无需重建的增量插入。向量数据库(Milvus、Pinecone)原生支持这一点。
嵌入服务:一台跑 ViT 模型的 GPU 服务器。延迟:每图约 20ms。多个查询合并成 batch 以提高吞吐。
搜索服务:ANN 索引服务器。在 10 亿向量上做 top-100 搜索延迟约 10ms(用 HNSW)。
缓存:对热门查询缓存结果。对于重复内容检测,缓存最近上传图像的嵌入,让新上传先和缓存比对,再去搜全量索引。
澄清需求(2-3 分钟):问清楚规模、延迟、一致性要求和边界情况。"多少用户?可接受的延迟是多少?故障时怎么办?"
高层设计(5-7 分钟):画出主要组件及其交互。从"快乐路径"(happy path)开始。用第 01-03 节里的那些模式。
深入(15-20 分钟):挑一个最有意思/最有挑战的组件做详细设计。这是你展现深度的地方。对 ML 系统来说,深入往往在:模型架构、特征流水线,或服务架构。
评估与监控(3-5 分钟):怎么衡量成功?什么会出错?怎么检测和应对问题?
迭代(2-3 分钟):给你更多时间/资源,你会改进什么?这能体现你懂权衡、会排优先级。