
1. 从零开始做AI工程先搞清楚这件事到底是什么我最早看到“ai-engineering-from-scratch”这个项目名时第一反应是又一个把教程堆进 GitHub 的仓库。但真正打开内容、跟着跑了一遍之后我发现它想解决的并不是“怎么调一个 API”而是更底层的问题——一个没有 AI 背景的工程师如何系统性地建立起 AI 工程的能力闭环。这里的“从零开始”不是指从 Python 语法开始而是指从“知道 AI 项目长什么样”到“能独立交付一个可用的 AI 功能”的完整路径。这个项目适合谁我觉得最典型的是三类人。第一类是后端或前端工程师已经被业务方反复问过“能不能加一个智能客服”“能不能做个图片识别”但自己心里没底第二类是刚转行做算法、却发现自己只会跑通 Notebook 的数据分析师模型在 Jupyter 里很漂亮一上生产就崩第三类是技术管理者想评估团队做 AI 工程化需要哪些能力、哪些环节最容易翻车。我自己跑完整个项目后最大的感受是AI 工程和传统软件工程最大的区别不在于模型有多复杂而在于不确定性从哪儿来。传统代码的逻辑是确定的输入输出可预期而 AI 系统的行为由数据分布驱动你无法靠读代码来预测它在真实数据上会怎样表现。这导致整个开发流程、测试方法、部署策略、监控体系全都得变。这个项目恰恰就是沿着这条主线展开的所以它的价值不是教你几个模型而是帮你建立一套应对不确定性的工程习惯。2. 内容架构拆解为什么它按“数据-模型-部署-运维”四段式来组织2.1 四段式结构的逻辑关系打开仓库目录会发现它没有按模型类型CNN、RNN、Transformer来组织而是按工程链路分成四个大块数据工程、模型训练与评估、部署与推理优化、监控与迭代。这个设计非常关键因为初学者最常见的误区就是把 AI 工程等同于“训练模型”把超过 80% 的精力花在调参上最后发现数据质量不行、上线后推理延迟太高、模型漂移没人管整个项目根本落不了地。数据工程放在第一位是有道理的。机器学习圈有一句老话叫“Garbage in, garbage out”但在实际项目里数据问题往往要到模型上线后才会暴露。比如你做一个文本分类系统训练集里 A 类样本占了 90%B 类只有 5%模型看似准确率很高实际上只是学会了“永远猜 A”。这类问题在离线评估阶段可以用混淆矩阵发现但很多新手根本不看。项目把数据环节单独拎出来就是要强迫你先把数据分布、标签质量、采样策略想清楚再碰模型。模型训练与评估这一块项目刻意没有追求 SOTA。它用了几个非常经典的模型作为载体比如 TextCNN、LSTM、BERT 的简化版本然后花了大量篇幅讲评估指标怎么选。为什么要这样因为工程落地的核心不是模型越新越好而是模型的能力边界是否匹配业务需求。你需要知道准确率在什么场景下会骗人精确率和召回率的业务含义分别是什么A/B 测试怎么做这些才是工程决策的基础。部署与推理优化是好多自学者的盲区。Notebook 里跑通模型只完成了 20% 的工作剩下 80% 是让模型在真实环境和真实流量下稳定工作。项目从模型序列化、推理服务封装、批量预测与实时预测的选择讲到量化、剪枝、批处理这些优化手段最后落到 GPU 与 CPU 的选型权衡。这部分内容特别适合后端工程师阅读因为它把“模型变成服务”这件事讲得非常工程化。监控与迭代往往是企业里 AI 项目失败的头号原因。模型上线不是终点而是开始。数据分布会变、用户行为会变、业务目标会变模型的表现随之下降。项目里讲了如何记录推理日志、如何计算数据漂移指标、如何设计模型版本更新机制甚至讨论了什么时候该重新训练、什么时候该回滚。这一块内容在其他教程里很少见但恰恰是生产环境最需要的能力。2.2 为什么项目用“场景驱动”而不是“算法驱动”来教学还有一个值得注意的设计项目里的每个知识点都挂在一个具体业务场景下。比如讲数据增强它挂在“电商评论情感分析”的情境里讲模型量化它挂在“移动端实时翻译”的情境里。这种安排比“算法驱动”的教学方式更贴近真实工作。因为在实际工作中你永远是从问题出发找方案而不是从方案出发找问题。举个例子做客服工单自动分类你首先要想清楚工单有多少个类别、各类别样本量是否均衡、用户表述和标准标签之间的差异有多大、线上请求的延迟上限是多少。这些问题的答案直接决定了你用词向量还是用预训练模型、用单分类器还是用层级分类、用 CPU 部署还是 GPU 部署。场景驱动的方式逼着你先做工程分析而不是上来就 torch 一行 import。3. 实操过程全记录跟着项目跑通一个最小可用的分类系统3.1 Step 1先搞定数据管道别急着训练任何 AI 项目的第一个坑都不是模型而是数据获取和数据清洗。项目里用了一个非常经典的公开数据集——IMDB 电影评论情感分类英文语料、二分类、规模适中非常适合做端到端演练。我按照它的指引先写了一个数据加载模块把原始文本转换成模型可用的张量。第一步是文本清洗。IMDB 原始评论里有 HTML 标签、多余的标点、大小写混用。项目提供了示例代码但我强烈建议你自己动手写一遍清洗逻辑哪怕只是简单的正则替换这样才能体会到“干净的语料”对后续步骤的影响有多大。我在实践中会额外做两步把所有换行符统一成空格把连续重复的标点合并。这些细节不处理分词结果会出现大量无意义的 token拉高词汇表大小还拖慢训练。第二步是构建词表。这里有个容易踩坑的参数叫max_vocab_size它控制词表的最大容量。设太小会丢失低频词的信息设太大内存吃不消而且会产生大量只出现一两次的噪声词。项目里默认设成了 50000我在复现时觉得这个值偏保守实际用 30000 到 40000 就能覆盖 95% 以上的 token。怎么判断统计一下训练集里不同词的累计频率覆盖曲线选拐点处的值最合适。第三步是构建 DataLoader关键参数是batch_size和max_len。max_len决定每条评论截断或补齐到多长。评论长度分布不均有的只有十几个词有的上千词但绝大多数集中在 200 词以内。我按项目建议设成 256在准确率和速度之间取平衡。如果设成 512训练时间会翻倍但准确率提升不到 0.5%不划算。batch_size则受显存限制我用单张 12GB 显存的卡batch 32 到 64 比较稳再大就容易 OOM。3.2 Step 2训练一个简单模型建立基准线项目在这一步让你训练一个相对简单的 TextCNN 模型而不是直接上 BERT。这个安排非常明智。TextCNN 结构简单、训练速度快、收敛稳定非常适合作为 baseline。真正的工程实践也是这个思路先用一个简单模型跑通整套链路建立基准线再逐步升级模型。TextCNN 的原理一句话就能讲清楚把词向量矩阵当成一个“图像”用多个不同尺寸的卷积核去提取 n-gram 特征然后池化、拼接、过全连接层分类。它的关键超参数有三个卷积核大小、卷积核数量、词向量维度。项目里用的配置是filter_sizes [3,4,5]这表示同时抓取 3 元组、4 元组、5 元组的局部特征类似在看评论时同时关注相邻三个词、四个词、五个词的语义单元。这种多尺度特征组合非常适合短文本分类。训练过程中我重点观察了两个指标训练损失和验证损失。如果两者同步下降说明模型在学习没问题如果训练损失下降但验证损失升高说明过拟合了需要加 dropout 或降低模型容量。TextCNN 在这个数据集上的收敛速度很快大概 5 到 10 个 epoch 就能到 85% 左右的验证准确率再往后提升就非常缓慢了继续训练反而有过拟合风险。我在复现时加了一个小小的修改把嵌入层的freeze属性设为 False。默认情况下预训练词向量可以冻结也可以参与训练。冻结的话训练更快但词向量无法根据任务微调不冻结效果略好但训练时间增加。对于 IMDB 这种中等规模数据集我建议开启动态更新效果提升明显代价完全可以接受。3.3 Step 3用 BERT 简化版做升级感受模型容量带来的差异跑完 TextCNN 后项目引入了基于 Transformer 的模型但不是完整 BERT而是做了裁剪的迷你版本隐藏层只有 128 维层数只有 2 层。这是一个非常聪明的教学选择如果直接上完整 BERT新手很容易被巨大的参数量和训练开销吓退而且难以在个人电脑上完成实验。裁剪版既保留了 Transformer 的核心机制——自注意力、位置编码、残差连接又能让入门者直观感受到“注意力机制到底在干什么”。我把 TextCNN 和这个 Mini-BERT 的测试结果做了对比Mini-BERT 的验证准确率比 TextCNN 高 2 到 3 个百分点但训练时间大概是TextCNN 的 4 到 5 倍。这个对比非常有教育意义它让读者明白模型容量的提升确实能带来精度收益但收益不是线性增长的而是边际递减的。在实际业务中你需要根据延迟预算和硬件成本来权衡到底用复杂模型还是简单模型而不是一味追求精度指标。学习 Transformer 时我强烈建议花点时间手动推导一下 attention 的计算过程。自注意力的本质是对序列中每个 token根据它与所有其他 token 的相似度重新加权聚合信息。你不需要从头实现但要理解 Q、K、V 三个矩阵的维度变化以及 mask 的作用。项目里没有展开讲数学公式而是用示意图和伪代码解释了整个流程适合没有数学背景的人入门。我自己还做了一个小实验把注意力权重视觉化看模型在分类一条正面评论时重点关注了哪些词。结果发现它确实会更关注“excellent”“amazing”这类带有强烈情感色彩的词汇这是非常直观、非常有趣的体验。3.4 Step 4把模型封装成推理服务迈出生产的第一步训练完模型的终点是把它变成能被业务调用的服务。项目在这一步提供了两种思路一种是离线批量预测比如每天对全量数据进行一次分类结果写入数据库另一种是实时推理通过 HTTP 接口对外提供服务。两种思路的适用场景完全不同。离线批量适合时效性要求低、数据量大的场景如日报自动归类实时推理适合延迟要求高的场景如在线客服机器人。在实际操作中我用 FastAPI 封装了一个最小可用的推理服务。关键步骤有三步。第一步是把训练好的模型权重用torch.save保存为.pt文件同时把词表保存为 JSON。第二步是在服务启动时加载模型和词表并调用model.eval()切换为推理模式。这一步特别容易被忽略不切 eval 模式的话Dropout 仍会生效导致每次预测结果随机波动。第三步是处理输入文本的预处理逻辑。注意服务端必须和训练时用同一套预处理流程否则训练和推理的数据分布不一致预测结果会非常离谱。推理延迟是个在开发环境里很容易被忽视的问题。我在本地用 CPU 测试 Mini-BERT 的单条推理延迟大概是 30 到 50 毫秒看起来很快。但如果把并发拉到 100CPU 直接被打满延迟飙升到几百毫秒。这就是为什么真实系统中要做推理优化。项目里示范了最简单的优化方法——动态批处理把高并发的请求攒起来凑够一定数量再进模型显著提高了吞吐量。另外还可以用 ONNX Runtime 做模型转换加速。我在一个文本分类任务上试过ONNX 导出后的推理速度比 PyTorch 原生提高 1.5 到 2 倍而且内存占用更低几乎没有精度损失。3.5 Step 5建立监控体系发现“模型悄悄变笨”的时刻模型上线后并不代表万事大吉。几个月后你可能发现分类准确率不如从前了但原因是多方面的用户表达方式变了、业务规则变了、数据分布漂移了。如果没有监控你根本无法定位问题。项目里实现的监控方案包括两部分统计监控和性能监控。统计监控关注输入数据的分布变化。具体做法是记录线上请求中文本的长度分布、词汇覆盖率、类别置信度分布等指标定期和训练集指标做对比。如果发现线上数据的平均句子长度从 120 词变成 80 词或者出现了大量训练集里没见过的生僻词就要警惕分布漂移了。性能监控则关注模型预测结果的业务指标比如准确率、召回率。但生产环境中常常没有实时标注好的真值一个常用的替代方案是人工抽检或滞后标注。先把预测结果存储下来两三天后由人工或下游系统给出真实标签再离线计算准确率。我的实操体会是监控体系的价值不在“出问题时报警”而在“出现缓慢劣化时给你留出干预时间”。很多模型退化过程非常缓慢不设置监控阈值系统表现依然能看但业务方会慢慢发现推荐不够精准了、审核不够严格了。越早发现处理成本越低。所以我在实际项目里不仅仅做指标监控还会保留两个东西一个是模型版本号每次上线都记录版本、时间、训练数据范围另一个是回滚脚本一旦新模型表现异常能在分钟级内切回旧版本。这些细节项目里都有提到但确实容易被赶工期的人忽略。4. 工具选型解析为什么是 PyTorch FastAPI MLflow4.1 框架选择的工程逻辑项目主代码用 PyTorch 实现这是目前 AI 工程领域的主流选择之一。和有静态图限制的老牌框架相比PyTorch 的动态图机制更容易调试也更容易实现复杂的模型结构。对于工程团队来说生态成熟度更重要。PyTorch Hub、TorchScript、TorchServe 这些周边工具让模型从训练到部署的整个生命周期都有成熟的解决方案。另外社区里有大量预训练模型和示例代码遇到问题基本都能搜到方案这对工程项目的推进速度至关重要。推理服务选择 FastAPI 而不是 Flask原因很直接FastAPI 天然支持异步、自动生成 OpenAPI 文档、内置数据校验。对 AI 服务来说异步支持特别重要因为模型推理是 CPU/GPU 密集操作异步可以避免服务被长时间占用。文档自动生成能让前后端联调效率大幅提升。我实测过同样的接口逻辑FastAPI 的代码量约为 Flask 的一半而且性能更好。对于实验管理和模型版本控制项目引入了 MLflow。它能记录每次训练的指标、参数、模型产物和代码状态相当于给实验打上了可追溯的标签。在真实团队协作中MLflow 几乎是必需品因为你会发现“两星期前的那个 88% 准确率的模型”到底用的什么参数、什么数据完全无法从记忆里提取只能依靠系统记录。4.2 本地开发和云上部署的资源权衡很多人问我学这个项目需要什么样的电脑配置我的回答是纯学习完全可以用 CPU。项目里的 TextCNN 和 Mini-BERT 都是裁剪版在 CPU 上训练几分钟到十几分钟就能完成。但如果你想把 Mini-BERT 扩展到完整 BERT那就需要 GPU 了。不需要自己去买可以用云 GPU 按小时租用训练完关掉即可成本很低。学习阶段最重要的是把流程跑通而不是堆硬件。部署方面项目建议用 Docker。我这里补充一个经验容器化不仅仅是部署手段也是环境复现的保障。AI 项目的依赖版本极其敏感numpy从 1.x 升到 2.x、torch从 1.13 升到 2.3都可能导致结果变动。Docker 镜像把 Python 版本、CUDA 版本、依赖库版本全部锁定能避免“在我电脑上明明能跑”这类经典问题。写Dockerfile时注意两点基础镜像不要用latest要固定版本号依赖安装用requirements.txt锁住精确版本不要用写法。5. 常见问题与避坑经验实录5.1 数据预处理不统一导致的“训练好、上线差”这个问题是我见过最多的。训练时数据清洗流程写在 notebook 里包含各种手工操作上线时服务端重新写了一遍预处理但可能少了一步大小写归一化或者分词方式不同。导致线上输入分布和训练分布错位模型表现大跌。解决办法是从一开始就把预处理逻辑封装成独立函数训练和推理都调用同一个函数并且在服务代码里写单元测试保证线上流程的一致性。5.2 类别不平衡没人发现准确率虚高分类任务最常见的陷阱。如果正样本占 95%负样本占 5%模型只用“全部预测为正”就能拿到 95% 准确率。很多新手看到这个数字就以为模型做得很好实际上模型啥也没学到。正确做法是先看混淆矩阵、看各类别的 precision/recall/F1。项目里专门强调了这一点但实际工作中还是经常有人踩。我的一般做法是如果类别分布严重不平衡第一选择是收集更多少数类样本做不到的话就用 class weight 或过采样/欠采样技术。5.3 推理阶段忘记model.eval()这个错误很隐蔽因为不会报错。训练时有 Dropout 和 BatchNorm推理时必须关闭否则模型的每一步输出都会带有随机性。用 PyTorch 写服务时在加载模型后立刻调用model.eval()并且写在加载权重之后、进入循环之前。我还见过有人因为多次加载模型加载一次调一次 eval但顺序搞错导致部分层仍在训练模式。最简单的方法是用 PyTorch 的torch.inference_mode()上下文管理器包住推理逻辑它会自动关闭梯度计算和训练模式更安全。5.4 并发压力下服务崩溃本地单请求测试一切正常一上生产并发一高就超时甚至崩溃。原因通常是推理过程是同步阻塞的一个请求占用一个 worker而默认 worker 数量太少。解决方案是使用异步接口同时配合批处理逻辑。批处理的意思是多个请求到达后把它们的输入拼成一个 tensor一次前向计算输出多个结果。这在 GPU 上能显著提升吞吐在 CPU 上也能提高缓存利用率。项目里有动态批处理的实现参考我建议至少理解它的思想不要直接照抄因为队列长度、最大等待时间需要根据流量模型调整。5.5 模型版本混乱难以回滚团队协作时模型文件通常存在共享目录大家都往里面放新版本过几天就分不清哪个是线上正在跑的。规范做法是每次训练产出模型时文件名带上版本号和时间戳例如textcnn_v3_20250115.pt每个版本对应一份配置文件包含超参数和评估结果在数据库或配置中心里记录当前线上版本号。这样不管什么时候出问题都能立即切换回上一个稳定版本。MLflow 能自动化这个过程但在没有条件引入的时候手工命名规范也能避免大部分混乱。6. 如何在真实项目里从“跑通”走向“可用”6.1 从项目模板演化到自己的代码库我建议把这个项目当成一个脚手架而不是终极答案。完成复现后接手一个真实业务需求时不要直接复制代码而是先画出新的流程图数据从哪里来、标签怎么定义、评估标准是什么、部署到哪里、监控哪些指标。每一步可能都和项目里的示例不同但你会发现问题没有想象中复杂因为核心链路是一样的。真实的业务需求往往比教程里的更脏用户提的文本可能是错别字、中英文混合、甚至只有一两个词。这些都需要你在数据清洗环节自己去处理而处理思路就是项目里那套统计、过滤、归一化、构造词表。还有一个重要的工程实践把数据集也纳入版本管理。不要只存代码不存数据。数据集的版本变化会导致模型不可复现这在审计、迭代、协作时都是大问题。如果你没有专门的数据版本管理工具至少要做到两点一是记录数据集的来源、下载时间、清洗脚本的 commit id二是制作一个数据集的校验和文件每次变更都能追踪。这个习惯能拯救无数个下午。6.2 我认为最有价值的学习路径如果你完全没有 AI 背景我建议按这个顺序推进先花一周时间把 Python 基础和 PyTorch 基础过一遍不需要深入能写简单的张量运算即可然后跟着这个项目把 TextCNN 的完整链路跑通重点是理解数据、模型、评估、部署、监控之间的联系之后拿一个简单的真实业务数据练手比如公司内部的工单分类、评论情感标注从头到尾做一遍最后再回去看 Transformer 相关的理论你会发现自己理解速度快了很多。不要一开始就沉迷 Transformer 论文那更像语言学家在研究语法而你现在需要的是会说这门语言。我在实际项目里体会到AI 工程最核心的能力不是背公式而是在无数的权衡中做出合理决策。该用简单模型还是复杂模型该用实时推理还是批量预测该花时间调参还是先排查数据这些问题没有标准答案只有基于工程约束的判断。这个项目给了你一套完整的骨架剩下的血肉需要你在真实业务中慢慢填充。最后分享一个小技巧在你跑通这个项目后尝试把其中某个环节替换掉比如把 TextCNN 换成别的轻量模型或者把 FastAPI 换成另一个服务框架再或者把 IMDB 数据换成你自己的业务数据。每做一次替换你对整个系统的理解都会更深刻一层。这种“刻意制造变化”的学习方法比从头到尾只按教程跑一遍有效得多。