
ai-engineering-from-scratch 这个标题翻译过来就是从零开始搞AI工程化。说实话我在这个领域摸爬滚打了几年见过太多做算法的同学和研究员的项目模型在笔记本上跑得活蹦乱跳一上生产就浑身不自在。这个现象背后缺的不是模型知识而是工程化的那一整套东西。这篇文章我想拆开讲讲一个人或者一个小团队在没有专人做MLOps的情况下怎么用手边最基础的工具把AI项目从能跑通整治成能长期稳定跑整个过程不依赖那些重型平台纯手工搭建顺便把每一个环节的选择理由也讲清楚。先说清楚一个边界我讲的AI工程化重点在数据、实验、部署、监控这条链路不涉及从头训练大模型、不涉及复杂的分布式训练调度。你可能是做NLP的、做CV的、做推荐的或者搞了个回归模型预测销量这套方法论都一样适用。核心诉求就两个第一别人或者两个月后的自己能轻松复现当前这个模型是怎么来的第二模型离开Jupyter Notebook之后能稳定对外提供服务就算出问题也能快速定位。我梳理了一下一套可以长期运转的AI项目最少需要走通六个环节下面按顺序拆开讲。1. 价值的起点不是算法是问题定义和拆分很多从零开始的AI项目头一个月就翻车的核心原因不是技术不够是问题没有想清楚。我见过有人花了三周时间给模型调参最后发现业务方需要的只是一个简单的规则脚本准确率反而更高。所以从零开始做AI工程第一步不是装PyTorch而是把问题用一句话写下来然后不断追问它。1.1 用一句话定义模型存在的理由这句话的模板可以是在【什么场景】下输入【什么数据】模型需要输出【什么结果】用【什么指标】衡量好坏当前【没有模型/旧的方案】的基线是多少。举个例子。你要做一个客服工单自动分类系统这句话就是在工单系统里输入工单标题和描述文本模型输出对应的类别标签用宏平均F1衡量当前人工规则的准确率是70%。写清楚之后你才知道后续做的每一件事清洗数据、选模型、定阈值、设计接口全都是在为这句话服务。这个环节还要逼自己做一个动作拆分模型边界。用户上传的工单截图里可能同时有文字和图片你是不是真的要做一个多模态模型多数场景不需要。文字走文本模型图片走OCR工具然后把结果拼接起来工程上更稳。AI工程的一个核心思维是能不交给模型的就不交给模型能用规则、函数、数据库查询解决的绝不塞进神经网络。1.2 建立第一个朴素的baseline在写任何深度学习代码之前先逼自己搭一条最笨的链路。文本分类就用TF-IDF加上逻辑回归CV任务就用一个已经训练好的分类器直接跑推荐系统就先用热门榜或规则召回。这条baseline的价值有三层。第一它会给你一个真实的性能下限后面所有花哨模型都必须超过这个下限否则就是白忙。第二它会暴露数据管道里的脏东西比如字段缺失、标签错乱、重复样本这些问题在复杂模型里会被隐藏很久但简单模型会立刻表现出异常。第三它直接形成一个可运行的最小系统前端、接口、监控全都可以先接上这就是迭代的雏形。这一步跑通之后你对整个项目的复杂度会有非常真实的感知数据量大概多大、清洗逻辑有多脏、预测延迟需求是多少。后边的所有工程决策都基于这些感知来定而不是拍脑袋。2. 环境与依赖从在我机器上能跑到在哪都能跑AI项目一个臭名昭著的问题就是环境问题。换了机器依赖冲突驱动版本不对模型推理结果直接变掉。谁都不想花两天时间复现别人代码最后死于CUDA版本对不上。所以从零开始环境的工程化就要定好规矩。2.1 把Python环境当成一次性用品我强烈建议用虚拟环境conda或者venv都行但关键规矩是创建环境便宜、销毁环境不可惜环境里的一切配置都必须由代码或文件声明而不是靠人记着在环境里敲过什么命令。换句话说你的环境必须可重建。实操上每个项目在根目录放一份environment.ymlconda或pyproject.tomlpoetry/uv所有依赖都钉在文件里。包管理工具有很强的版本锁定能力它生成的lock文件里记录着每一个依赖包的精确版本。这么做之后换机器、加新人、上线到服务器都是同一个命令恢复环境不再有你先跑一下pip install xxx试试这种低效对话。说一下我在Python版本和依赖管理上踩过的坑如果把numpy这种底层库的版本随便放了过了几个月它升级之后老代码可能出现诡异的运行结果变化但又不报错——这是最阴的。所以lock文件不只是给你看的是给未来的机器和环境看的必须提交进Git仓库。2.2 GPU环境的检查脚本要固定下来如果你的项目涉及深度学习训练GPU是不可少的。我试过在三台不同配置的机器上复现同一个训练脚本会话记录里显示NVIDIA驱动都是最新版但实际型号差距很大。torch在A100上默认用某种算子实现到消费级显卡上又会回退到另一种实现速度天差地别。所以我在每个AI项目的根目录放一个env_check.py脚本会输出Python版本、CUDA版本、GPU型号和显存、PyTorch/CUDA是否匹配、关键算子能不能正常执行。每次换机器跑项目先跑这个脚本再动手三分钟把环境问题扼杀在源头。这个脚本还有一个隐藏的岗位价值让实习生或者新同事收敛那个在我电脑上能跑啊的bug黑洞。环境信息一目了然之后问题都变成可复现、可对照的了。2.3 统一入口一个Makefile解决所有操作一个AI项目可能会出现这些频繁操作创建环境、跑数据清洗、启动训练、跑评估、启动API服务。依赖人脑去记这些命令一定会漏或者记混。我的习惯是项目顶部放一个Makefile把高频操作全部暴露成短命令。setup: pip install -e . data: python -m src.data.prepare train: python -m src.train --config configs/exp1.yaml evaluate: python -m src.evaluate --config configs/exp1.yaml serve: uvicorn src.api:app --host 0.0.0.0 --port 8000命令变短了靠记忆的东西变少了项目入口统一了。这也算工程化一个很朴素的体现人对项目的理解成本越低出错的概率就越小。3. 数据管道比模型更值得花时间的部分一个残酷的事实是模型选型造成的性能差距往往在几个点以内但数据质量造成的差距可以是数量级的。从零开始做AI工程数据工程要占掉大概六成精力。3.1 原始数据的落盘规矩很多项目一开始就是从某个数据库里select一下拿到数据然后直接在Notebook里洗。这在探索期可以一旦进入工程期必须改成脚本化处理。我的规矩是每一份原始数据都整份落盘保存路径、时间戳、来源写清楚不做任何改动。后面所有的清洗、采样、增强都是在这个只读的原始数据层上另起炉灶。这么做的原因很现实数据会更新、源头表结构会变如果你直接在源头数据上改来改去那你的模型复现流程里就埋着一个不定时炸弹——今天能跑通明天源头数据变了跑出来的特征分布也就变了。我在实际项目中就遇到过一次上游数据表悄悄加了几个新字段下游代码读进来之后特征顺序乱了模型预测结果批量错乱最后排查了半天才发现数据源头变过。落盘格式我用过不少parquet是目前最顺手的压缩率高、带schema、读取速度快处理大一点的数据集完全不虚。CSV适合人眼瞄一眼不适合作为工程管线的主格式。3.2 用最朴素的方式管住三个版本数据管道里最烦的三个东西是代码版本、原始数据版本、清洗后数据集版本。代码用Git管这个没问题。关键是后两个。我不建议一上来就上重型的数据版本管理工具小团队学习成本太高。用最朴素的方案完全可以每个清洗后的数据集文件夹命名带上日期和来源数据的hash值例如train_20250110_v2.parquet中的v2即对应原始数据某一次处理的第二版。清洗脚本里固定输出一个dataset_meta.json里面记录清洗规则、输入文件路径、输出行数、每个字段的均值/缺失率/唯一值数量。这样一来哪怕没有专门的DVC也不是大问题。三个月之后你想知道训练数据是不是已经过期了直接对比文件夹命名时间和Git记录立刻清楚。3.3 数据质量门禁哪怕只是几个if清洗完的数据要过一个质量门禁这个门禁不复杂通常是一段脚本加上若干断言断言缺失率不超过设定阈值。断言目标字段的分布和上一版不能有明显偏移用简单的卡方检验或者直接看分布图采样对比。断言字段类型正确比如价格列没有出现负数或字符串。断言训练集和验证集没有泄重叠或者泄重叠的比例控制在可控范围。这些检查跑完脚本再输出一份报告看到报告绿灯了才允许进入训练环节。这个步骤大部分人会跳过但从工程视角看它相当于生产流水线的质检工位。没有质检后面所有环节的异常都变成薛定谔的锅——说不清是数据坏了还是模型坏了。再加上一句踏实话数据管道是最值得加日志的地方每一批数据的处理时间、处理量、异常量全都要留下痕迹。AI项目的可观测性永远从数据源头开始。4. 训练实验的工程化告别黑色NotebookJupyter Notebook是探索的利器但直接作为工程载体后患无穷。我见过一个团队把最终训练代码放在Notebook里跑然后往Git里提交了一个几百MB的.ipynb文件里面塞满了代码片段和输出连哪一段是最终用的都看不出来。从零开始搞AI工程训练这条线必须脚本化。4.1 一切实验都可脚本化、可传参训练入口长这样一段可以被命令行参数控制的Python脚本例如python train.py --config config.yaml。关键参数学习率、batch size、模型尺寸、数据路径、损失权重等都不该写死在代码里而是通过一个配置文件传入。我更习惯用Hydra这类配置管理库支持配置继承、命令行覆盖、每次实验自动生成文件夹。用大白话说就是我一次实验跑完所有参数和代码状态都被自动记录在一个文件夹里。这样做的直接收益是实验不再靠人脑记上次好像用了0.001的学习率效果还不错。每一个模型权重都自带完整的参数清单和使用的数据版本。这大大降低了调参过程中试了十几个配置、最后忘了最佳配置是什么的翻车概率。4.2 固定随机种子但不迷信随机种子深度学习训练里有一个经典话题设了随机种子但实验结果还是不完全可复现。原因是显卡算子本身有非确定性、多进程数据加载的顺序也可能影响结果。所以我的做法是脚本里设置所有能设的随机种子Python、NumPy、PyTorch并且在日志里记录当前机器的GPU型号和线程数方便定位环境因素。有一个实操经验如果对复现性有硬需求比如某些需要用生成的合成数据来评估的业务建议把数据加载改成固定顺序不开shuffle或者自己先打乱再固定。模型权重初始化一般也建议固定。我试过开了不设种子的实验同一份代码隔两天Trainer跑出来指标差了两个点——于是我们整个团队养成了一个习惯所有待公布的对比实验全部设置固定种子并且每次都启动新进程跑避免缓存残留影响结果。4.3 检查点策略别只留一份最终模型.bin训练中途崩掉、存储被占满、复盘时想恢复某个中间状态这三件事都靠检查点checkpoint兜底。我的策略是每轮或每隔固定步数保存一个检查点。检查点文件夹里除了权重还有优化器状态、当前学习率、当前epoch/step、随机数生成器状态。至少保留最近两个检查点和一次最佳验证指标的检查点磁盘允许的话保留更多。检查点策略还有一个通用的经验模型的指标不是只看验证集爆出来的那个数最好同时保存验证集上每个样本的预测结果。这样后面回看bad case、做误差分析时不需要重新推理一遍省时间省得不是一星半点。5. 评估与离线反馈闭环模型好不好不能凭感觉很多项目在训练结束、看几个指标就急着部署结果模型上线被用户一顿吐槽原因往往在于评估环节太薄。从零开始做工程至少要在评估环节建立三个层次。5.1 离线评估的三个不能只看第一个不能只看平均指标。分类模型宏观F1再高可能某一个不常见类别上表现极差。建议评估报告里带上每个类别的精确率、召回率、F1以及样本数。回归模型不能只看MSE具体业务组里存在多少误差特别大的离群样本也很关键——例如预测价格标准误差很小但某个区间段的产品售价全预测偏了。第二个不能只在一个测试集上评估。要切时间切片测试集、更难样本集、特定细分场景集。最好准备一份挑战集或对抗集专门选那些老模型预测错的样本、边界模糊的样本。模型在这份挑战集上的分数往往反映了真实世界的表现边界。第三个不能只看预测准确的过程。输出置信度分布也要看。很多模型在低置信度时准确率掉得飞快。真做工程时我们可以设置一个低置信度拒判规则把这些样本抛给人工或者规则兜底线上实际效果比不分青红皂白全自动跑要高不少。5.2 建立一份golden test set我强烈建议项目从第一天起就维护一份金标测试集这份数据原则上不参与训练、不参与调参是专门在发版前用来做最终验收的。哪怕一开始只有几百条也要把它单独锁起来。它的价值在于不同版本的模型用同一份金标测试集评估分数才有可比性。为了对比方便每次训练结束的评估脚本要把输出预测值存成一个标准格式的表格里面至少包括样本id、标签真值、模型预测值、置信度。有了这个后面分析模型升级带来的真实收益/回退时可以直接对比预测结果不用重跑模型。5.3 用shadow模式做离线到在线的脚手架很多人会把上线直接等同于切流量。工程化更稳的做法是上影子模式模型在线上实时接收真实的请求数据并完成预测但预测结果不直接返回给用户而是回放到日志系统里供离线评估去对比如果是新模型当时的预测是什么。影子模式跑上一两周拿真实流量校准一下评估结论再灰度切流整个上线进程会从容得多。这其实是把离线评估和在线评估的鸿沟填上的一种做法。影子模式的日志数据也会积累成宝贵的再训练样本。双赢。6. 部署、监控与迭代模型上线才是工程的开始如果训练算造车部署和监控就是上路跑。很多人觉得模型train好扔到服务器上就完事了实际上路之后的问题远比训练阶段更磨人。6.1 一个轻量级的模型服务怎么搭如果你的推理延迟要求不那么极端毫秒级高并发是另一套玩法一个FastAPI应用就足够撑起一个小模型服务。它可以直接加载模型检查点封装一个接口接收原始JSON数据做特征处理返回预测结果。我把这种设计叫胖服务瘦调用——服务内部负责数据清洗、特征构造、预测、输出解释接口层保持简单。服务启动前还应该有一个自检接口health check除了返回我是活的之外应该返回一些模型健康信息比如当前加载的是哪个检查点版本、最后训练时间、数据schema的版本号码。这样监控系统想确认线上服务和最新的工件是否同步直接打这个就知道。6.2 推理性能和安全必须过一遍的东西模型上线至少要把这三件事做一遍用batch推理还是单条推理要实测延迟和吞吐量不要拍脑袋。有的模型单条延迟100ms看着还行但并发一上来CPU排队会爆炸。输入异常怎么处理。空字符串、超长文本、未知类别字段必须提前设计好行为返回错误码还是走兜底规则。我见过线上一个空值导致模型输出乱码下游直接给用户推送了垃圾内容教训惨痛。推理输出要不要做后处理。比如分类模型阈值怎么定、回归模型下限怎么截断都必须成文写进服务代码而不是散落在某个人的笔记本里。如果推理延迟压力很大可以用ONNX Runtime或者TensorRT把模型转换加速一下。绝大多数PyTorch模型导出到ONNX之后延迟能有比较可观的下降。这个可以作为项目稳定之后的优化方向不急着第一步就上。6.3 监控什么五类信号五个警报模型监控不是盯着CPU和内存就够的。我常用的监控清单长这样监控对象信号来源预警阈值示例数据漂移请求数据的特征分布 vs 训练时分布特征均值偏差超3个标准差预测分布漂移模型预测类别概率分布的变化热门类别的频次突变超50%业务反馈用户点击率/订单转化率等业务指标连续3天日同比下降超10%延迟与错误API响应时间、5xx/4xx错误比例P99延迟超1秒错误率超1%工件状态线上模型版本与最新评估通过的版本是否一致版本不一致即告警具体怎么监控可以用一个脚本定时跑把指标推到Kafka、日志系统或者简单的时序数据库里再配上告警。不一定非要重型监控平台哪怕是定时任务加邮件通知也比出了问题被用户投诉才发现来得强。6.4 模型迭代的正式流程哪怕只有两个人从零开始的团队也要有迭代流程否则就是没有章法的改改再发。最小可用的流程长这样新数据到达跑数据质量门禁。在保留的已验证环境里重训练自动生成新一轮检查点。用金标测试集评估对比和当前线上模型的差异。评估通过更新模型工件版本号同时更新线上服务的加载配置。灰度放量先放5%流量观察监控警报平稳之后再放开。全量之后继续跑影子模式留着新旧对比观察一到两周。这个流程不复杂但它保证了模型的每一次更新都有依据、有记录、有回退方案。实际经验是这套流程哪怕一开始执行得糙一点也比没有强——因为它强制所有人对模型行为负责。还有一条容易被忽略的心得给模型起名和版本号一定不能用final_final_v2这种命名。我见过太多项目毁在这个才是绝对最终版的文件夹里。正确的模型工件命名应该是模型名_训练数据版本_时间戳_评估指标例如cls_v1_data20250110_20250201_f085。从工件名就能读出信息在协作里省下的沟通成本非常可观。7. 写在最后AI工程化的本质是反脆弱一个人搞AI项目最容易把全部精力放在模型结构上沉浸在调参的快感里。但决定一个项目能不能长期活下去的永远是当它出错的时候你能不能快速定位、快速回滚、快速修好。从零开始搞AI工程本质上就是不断给自己上保险数据的保险、实验的保险、部署的保险、监控的保险。我在实际项目里反复验证过一件事那些看着不起眼的小规则比如每次训练前自动核对数据schema发布后自动检查预测分布偏移长期价值远远大于精心挑选的那个模型网络层数。AI工程不是锦上添花而是项目从实验室样品走向产品化过程中必需的地基。地基打得越扎实后续加新功能、换新模型、接新数据源的时候你越能睡得着觉。如果你现在正打算从零开始搞一个AI项目我的建议是先不要急着写模型代码按这篇文章的思路把骨架搭起来——哪怕有些步骤看起来没必要、有些流程先做得糙一点都胜过硬着头皮直接开卷训练。等你走过一整个从数据到监控的闭环之后回头看这个从零开始你会感谢当初愿意花时间搭建脚手架的自己。