☰
AI工程从零开始:数据、训练、部署与运维全链路实战
2026/10/1 18:05:53 网站建设 项目流程

如果你看到一个叫ai-engineering-from-scratch的仓库,大概能猜到作者想表达什么——把AI工程从零开始完整地搭一遍。我最近就在做这件事,边做边把经验整理成了这个项目。这篇博文算是项目的中期总结,也是我踩了不少坑之后的实战笔记。内容既适合打算转行AI工程、之前只跑过notebook的朋友,也适合已经在做算法、但想搞清楚“模型上线之后到底发生了什么”的人。我会把AI工程拆成数据、训练、部署、运维四段来讲,每一段都会给出我能直接复用的思路和代码片段,以及那些文档里不会写、只有亲手做过才知道的细节。

1. 先搞清楚“AI工程”和“From Scratch”到底是什么

1.1 AI工程不是炼丹,是一条完整的流水线

很多人对AI工程的理解是“训练一个模型”,这是最大的误区。训练模型只是中间的一个环节,甚至不是最耗时的环节。一个真正能跑起来的AI项目,至少包含数据采集与清洗、特征构建、实验管理、模型训练、评估验证、服务化部署、线上监控、模型更新这些环节。任何一个环节断了,模型都落不了地。

我见过太多这样的情况:算法同学在notebook里把准确率调到了97%,兴冲冲地要把模型交给工程团队,结果发现训练脚本依赖一堆全局变量、数据路径写死了、模型文件不知道对应哪份数据集,部署时需要重写整个预处理逻辑。这就像你做了一道菜,但菜谱只有你自己看得懂,别人连锅都找不到。AI工程要做的,就是把“实验室里能跑的代码”变成“别人也能维护、线上也能稳定跑的系统”。

1.2 From Scratch不是重造轮子,是不当黑盒调包侠

提到from scratch,有一类人会走进另一个极端:什么都要自己手写,连梯度反向传播都要从头实现一遍。我尊重这种学习方式,但工程上这么做既不经济也不必要。PyTorch、FastAPI、PostgreSQL这些成熟的工具,本身就是经过大规模验证的“轮子”,你不需要重造它们,但你需要知道它们是怎么工作的、在什么情况下会出问题、怎么把它们的性能发挥出来。

在我看来,ai-engineering-from-scratch的真正含义是:不把任何一个环节当黑盒。你用PyTorch训练没问题,但你要清楚DataLoader是怎么把数据喂给模型的;你用FastAPI做服务没问题,但你要知道模型是怎么加载进内存的、为什么每次请求不能重新load一次模型;你用Docker部署没问题,但你要明白镜像层的缓存机制会给迭代带来什么影响。

那么,哪些东西值得自己动手写?我的标准是:核心业务逻辑和评估逻辑必须自己掌控。数据清洗规则、特征构造逻辑、评估指标的计算,这些决定了模型的上限和可信度,不能拿现成函数一套就完事;实验记录、模型版本管理、前后处理代码,这是工程化的骨架,必须自己搭建。而训练框架、推理引擎、部署基础设施,能用成熟的就直接用,省下来的时间拿去调模型比什么都值。

2. 从零搭项目:技术栈、目录与数据管理

2.1 技术栈选型:先问自己一个问题,再选工具

选技术栈之前,先问自己一个问题:这个项目是个人练手项目、团队内部工具,还是面向外部用户的高并发服务?三种场景的答案完全不同。个人项目可以把所有东西塞进一台服务器,用Python全栈搞定;团队内部工具要考虑可维护性和交接成本;高并发服务则要认真考虑服务化拆分和性能优化。

我给ai-engineering-from-scratch定的最小技术栈是:Python 3.11作为主语言,PyTorch做模型训练,FastAPI做推理服务,PostgreSQL存业务数据,Redis做缓存(如果需要的话),Docker做部署。这个组合的好处是:每一层都是当前生态里社区最活跃、踩坑资料最多的选择,遇到问题搜一下基本都有答案。不要一上来就上Kubernetes、Spark、Kafka这些重型武器,新手阶段它们带来的复杂度远大于收益。

再说说为什么选FastAPI而不是Flask或Django。FastAPI原生支持异步、自带接口文档、基于Pydantic做请求校验,写出的代码结构天然清晰。做模型推理服务时,绝大多数操作其实是I/O瓶颈——接收请求、调用模型、返回结果,异步支持能显著提升并发能力。Flask虽然简单,但很多“高级功能”需要自己拼插件,Django又太重了,适合做业务系统,不适合做轻量推理网关。

2.2 目录结构:用一套能“长出来”的组织方式

项目一开始就要把目录结构定好,否则写到第300行代码就开始乱了。我现在的推荐结构长这样:

ai-engineering-from-scratch/ ├── configs/ # 训练、推理的配置文件 │ ├── train_config.yaml │ └── serving_config.yaml ├── data/ │ ├── raw/ # 原始数据,只读不写 │ ├── processed/ # 清洗后的数据 │ └── versions/ # 数据版本快照 ├── src/ │ ├── data/ # 数据加载与预处理 │ ├── features/ # 特征构建 │ ├── models/ # 模型定义与训练逻辑 │ ├── serving/ # 推理服务 │ └── utils/ # 日志、通用工具 ├── tests/ # 单元测试与冒烟测试 ├── scripts/ # 一键训练、一键部署脚本 ├── notebooks/ # 探索性分析,禁止放生产代码 └── docker/ # Dockerfile 与编排文件

这个结构的核心思路是:数据、代码、配置三者分离。数据放在数据目录,配置放进configs,代码按职责分包。每次实验只需要改配置文件,不需要改代码;数据变更用版本号管理,模型训练时记录用哪个版本的数据,才能做到实验可复现。

有一点要强调:notebook只用来做探索性分析和可视化,永远不要把生产逻辑写进notebook。我见过有人把预处理逻辑写在notebook里,训练脚本直接读取notebook导出的文件,一旦单元格执行顺序变了,结果就完全不可复现。正确的做法是,把所有核心逻辑沉淀到src/下的Python模块里,notebook只是调用它们。

2.3 数据管理:模型效果变差的头号元凶是“数据变了”

数据是AI工程的隐形地基。很多时候模型效果变差,不是模型代码出了问题,而是数据悄悄变了:上游系统改了字段含义、样本分布发生漂移、标注标准换了。如果数据没有版本管理,你将面临一个经典的难题——“这个模型是用哪份数据训出来的?”

我的做法很简单:每一份进入训练流程的数据集,都打上版本标签。小项目不需要上DVC这种重量级工具,最简单的方案是:原始数据文件以train_v1.csv、train_v2.csv这种方式命名,不覆盖、不删除;训练配置里写明数据版本号;同时记录一下数据文件的md5校验值。这样出现任何诡异问题,第一反应就是去对比两个版本的数据差异,八成能快速定位。

数据泄漏是另一个必须从源头防住的坑。举一个我真实遇到过的例子:做一个用户流失预测模型,离线AUC高达0.98,上线后却完全不能用。排查之后发现,训练集和测试集的拆分没有按用户ID去重,同一个用户的不同时间记录被分到了两边,模型等于见过“将来”的信息。另一个经典泄漏是特征里包含了目标信息本身,比如给“是否取消订阅”建模,特征里竟然有“当月已申请退订”这种本末倒置的字段。建立数据管线时,必须把“按实体拆分”“特征时间窗口限定在预测时刻之前”这些规则当成硬约束写进代码里。

3. 训练阶段:先跑通闭环再谈优化

3.1 第一个里程碑不是“高精度”,而是“完整跑通”

我在这个项目上最深刻的一个经验是:先别急着调精度,先把整条链路跑通。哪怕拿10%的数据、只训练1个epoch、用一个最简单的线性模型,也要让数据从原始文件走到接口返回预测结果。这个最小闭环一旦打通,你就有了一条“基线轨道”,后续所有优化都是在轨道上迭代,而不是每次都在不同地方翻车。

跑通闭环之后,立刻建立基线模型。基线模型可以很蠢,甚至可以直接用逻辑回归,或者简单统计规则。它的价值不在于效果多好,而在于给你一个参照系:后续换了复杂模型,提升到底有多少?很多项目做到最后发现,复杂模型只比baseline高1个百分点,却带来了成倍的部署和运维成本,这时候你需要一个清醒的决策依据。

3.2 实验管理:好记性不如烂笔头,机器也一样

训练过程中最忌讳的事情是:同一个模型跑了好几个版本,最后分不清哪个效果最好、用了什么参数、基于哪份数据。没有实验记录的AI项目,回头的每一步都是“重现不了的玄学”。

我带这个项目的习惯是,每次实验至少记录以下几项:实验编号、模型结构、超参数配置、数据版本、代码分支或commit号、评估指标、模型文件保存路径、备注。

实验编号模型学习率数据版本准确率F1备注
exp-001LogisticRegression0.01v10.910.87基线
exp-002XGBoost0.01v10.950.91特征标准化后
exp-003XGBoost0.02v20.940.90数据版本升级,指标反而降

如果你愿意折腾,可以用MLflow写一次代码就自动记录这些信息。但即使只用Excel记录,也比什么都不记强得多。实验记录的核心是“可复现”,别人拿着你的记录能跑出同一份结果,这个项目才真正算工程化。

3.3 超参数与随机种子:为什么你的结果总是“忽高忽低”

调参是训练阶段最容易浪费时间的环节。我的建议是分阶段粗调和细调:第一步,用大跨度网格覆盖关键参数,比如学习率直接试0.1、0.01、0.001,找到大致方向;第二步,在表现最好的区域附近做小范围搜索。不要一上来就上贝叶斯优化等高级算法,先理解每个参数对结果的影响方向,高级工具用起来才有意义。

另一个必踩的坑是随机种子。深度学习训练中,数据加载的随机打乱、模型参数的随机初始化、dropout的随机行为都会影响最终结果。如果你不固定随机种子,同样的代码跑两次结果完全不同,你根本分不清是代码改动带来的变化还是随机波动。早期的项目每到调参就头疼,后来我强制要求所有训练脚本固定seed——包括Python的random.seed、NumPy的np.random.seed、PyTorch的torch.manual_seed,才算把实验间的“噪声”降下来。

固定随机种子并不会让结果完全一致,但至少能让主要趋势稳定表现出来。这时候你再调参,看到的效果差异才是真实可信的。

3.4 评估指标:准确率高不代表你的模型真的有用

模型评估是训练阶段最容易自我欺骗的地方。我做过一个文本分类项目,整体准确率97%,但把混淆矩阵摊开一看,两个低频类别的召回率只有30%——用户最关心的恰好是这两个类别。只看单一指标,你会以为模型很强大,实际上它在关键场景里基本是废的。

所以要建立一套多维评估体系:切分类别看精确率和召回率,画混淆矩阵,找典型错误样本人工复盘。另外,我强烈建议维护一个固定的“黄金评测集”——大约几百条人工标注、覆盖所有业务场景和疑难边界的样本。每次迭代都拿同一份评测集跑一遍,模型效果升降一目了然。黄金评测集还能有效防止过拟合,因为它是独立于训练集和测试集之外的“第三方裁判”。

4. 部署与上线:让模型变成可用的服务

4.1 部署方案选型:不是所有模型都需要GPU推理

很多人一提到部署就要上GPU集群,这是巨大的成本浪费。部署方案应该由延迟要求和模型复杂度共同决定。

部署方案适用场景优点缺点
FastAPI在线服务实时预测、低延迟部署简单、调试方便需要维护服务进程
批处理任务离线批量预测实现简单、可重试延迟高,不适合实时
ONNX/TFLite嵌入式移动端、边缘设备离线可用、资源占用小算子支持有限、模型结构受限

我的经验是:先把模型压到一台CPU机器上测一下推理延迟。如果模型不大(比如几百MB以内的树模型或蒸馏后的小模型),CPU推理延迟两三毫秒,完全没必要上GPU。只有大模型或高并发场景才需要考虑GPU和推理加速方案。

4.2 写一个能上生产的推理服务:就这么几件事

我用FastAPI写推理服务,核心逻辑只有几十行。但有几件事是必须做对的。

第一,模型只加载一次。模型文件在服务启动时加载进内存,之后每次请求直接复用,千万不能在请求函数里反复加载模型。第一次加载可能要几秒钟,之后每次推理只需几毫秒,这就是缓存带来的效果。

from fastapi import FastAPI from pydantic import BaseModel import joblib import numpy as np app = FastAPI() model = joblib.load("models/baseline.joblib") class PredictRequest(BaseModel): features: list[float] @app.get("/health") def health(): return {"status": "ok"} @app.post("/predict") def predict(req: PredictRequest): x = np.array(req.features).reshape(1, -1) pred = model.predict(x)[0] return {"prediction": int(pred)}

第二,接口要做好输入校验。Pydantic的BaseModel自动完成字段类型校验,请求里传了空数组或错误类型,直接返回400,不会让脏数据进到模型层。这能省去大量防御性代码。

第三,线上模型前后处理逻辑必须和训练时完全一致。很多部署事故出在“训练和推理时对数据的预处理不一致”:训练时特征做了log变换,线上忘了做;训练时填充缺失值的策略是均值填充,线上用的是0填充。我踩过一次这种坑后,把预处理逻辑统一封装成一个函数,训练脚本和推理服务都调用同一个函数,从根源上杜绝不一致。

4.3 Docker化与CI/CD:让“在我电脑上能跑”这句话失效

“在我电脑上能跑”是工程协作里最让人崩溃的一句话。Docker把环境、依赖、代码一起打包,基本消除了这个问题。我项目的Dockerfile很简单:

FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn", "src.serving.main:app", "--host", "0.0.0.0", "--port", "8000"]

有几个坑值得提醒。一是基础镜像别用带完整桌面环境的版本,python:3.11-slim这种精简镜像能把体积缩到原来的十分之一。二是容器里尽量用非root用户运行,避免安全风险。三是依赖锁定要精确到版本,甚至用hash校验,否则过几个月再来构建,依赖更新可能导致行为变化。

CI/CD对AI项目来说,不只要跑代码层面的单元测试,还要跑训练冒烟测试和推理一致性测试。我现在的流程是:每次提交代码,先跑一遍预处理模块的单元测试,然后用极小数据量跑一个epoch验证训练脚本不崩,最后加载已训练模型对固定样例做推理,断言输出和上次一致(允许极小误差)。这样绝大部分问题在CI阶段就能暴露,而不是上到生产环境才炸。

5. 上线之后:监控、回滚与持续维护

5.1 模型监控:模型上线只是开始,不是结束

模型部署上线,很多人的心态是“大功告成”。实际上,模型上线之后才是真正考验工程的阶段。模型的性能会随着线上数据的分布变化而衰退,用户行为变了、市场环境变了、上游数据变了,都可能让一个原本表现良好的模型慢慢失效。你需要一套监控体系来发现这些“生病”的迹象。

我最推荐的监控指标是PSI(群体稳定性指数),它衡量线上特征分布和训练时特征分布的差异。简单理解就是:训练时模型见到的和线上实际遇到的分布差了多少。PSI小于0.1表示稳定,0.1到0.25之间需要警惕,超过0.25就说明分布发生了显著变化。实现上,可以对每个特征分箱,分别统计两个分布的占比,再逐箱计算差异累加。如果你不想自己写,很多机器学习平台都内置了PSI计算工具,但自己实现一遍能加深理解。

除了特征分布,还要监控模型的预测分布本身:预测结果的均值有没有突变?预测为某个类别的比例是不是从30%突然跳到了60%?这些都能直观地反映线上环境的变化。

5.2 日志、告警与业务反馈闭环

线上服务的日志要记录哪些内容?我的标准是:请求ID、模型版本号、输入特征摘要、预测结果、推理耗时、时间戳。这些日志是事后排查问题的“案发现场”。有一次线上模型效果异常,正是靠日志里记录的模型版本号发现流量被路由到了旧版本的服务,一秒定位。

告警不能什么都不设,也不能天天乱响。我刚开始做得比较粗暴,设了一堆阈值,结果天天被告警打断,最后形成“狼来了”效应。后来学乖了:只设少量真正需要人介入的告警,比如服务错误率飙升、延迟超过SLO、PSI超过警戒线。每条告警都带上当前指标值、历史趋势、可能的原因和检查指引,收到告警的人才知道下一步该干什么。

业务反馈闭环是AI工程最容易缺失的一环。模型预测完了,结果到底对不对,对业务有没有帮助?这需要产品侧把用户反馈、业务结果数据回传过来。没有反馈闭环,模型就永远是“盲人摸象”——你只能看到预测值,却不知道预测得对不对。

5.3 模型更新与回滚:做好最坏的打算

模型更新比想象中更危险。新模型离线评估指标更好,上线后却可能因为某个边缘case翻车。这时候如果直接全量切流量,影响范围就会很大。成熟的团队会做金丝雀发布或者影子部署:新模型先接收一小部分流量(比如5%),观察几分钟到几小时,预测分布是否正常、服务质量是否达标,再逐步放量。

我建议从第一天起就在接口返回值里带上模型版本号,并且让模型文件名携带版本信息,比如model_v3.joblib。遇到问题想回滚时,直接切回旧版本的模型文件就行,不需要重新部署服务。这种设计在一开始只需要多写一行代码,带来的却是灾难时刻只剩按钮可按的从容。

6. 常见问题与排查经验:踩过的坑都在这里

我把新手做AI工程时最常遇到的问题整理成一个速查表,这算是这个项目到目前为止最有价值的一份沉淀。

现象可能原因排查方法
训练loss不降学习率过大/数据标签错乱/代码bug先在小数据上训练,看是否能过拟合到100%
离线指标很好,线上效果差数据分布漂移/特征泄漏/前后处理不一致计算PSI,对比训练和线上数据分布
推理延迟很高模型每次请求重复加载/batch没做确认模型是否只加载一次,开profile分析耗时
内存持续增长模型多次加载/缓存无上限检查加载逻辑,确认请求级对象是否被持续持有
同样的代码结果复现不了随机种子未固定/依赖版本漂移固定所有seed,锁定依赖版本
看起来预测很准但业务没增长指标与业务目标没对齐回到业务目标重新设计评估指标

排查问题的套路,我从多次翻车中总结成一句话:先复现,再定位,最后修复。线上出了问题,不要急着改模型,先用同样的输入在本地跑一遍,确认是线上推理的问题还是模型本身的问题。如果不是模型的问题,再去查服务、查数据链路。这个过程听起来简单,但能帮你避开至少一半的无效改动。

再分享一个实战经验:AI项目出问题,先查数据,再查代码。数据变了、数据错了、数据格式和预期不符,占了AI事故的一大半。有一次我的模型在凌晨突然预测结果全变,查了一个小时代码才发现,上游数据源那天开始返回了一位小数精度更高的数值,虽然看起来只是“略微精确”,但特征值变化直接改变了预测分布。从那以后,我的服务里多了一个给模型输入特征做异常检测的逻辑:特征区间、缺失率、均值一旦超过阈值就告警。这个逻辑用几行代码就能实现,带来的安全感却很实在。

最后说一点我对“从零开始”这个短语的最新体会:真正难的从来不是写出第一行代码,而是坚持把整条链路走完。数据、训练、部署、监控,每一环都有无数个“看起来可以省掉的步骤”,但恰恰是那些步骤决定了项目能不能持续跑下去。这个仓库里存的不只是代码,更是这一路上所有的错误和修正。如果你也准备从零开始做一个AI项目,不要贪多,先拿一个最简单的场景,把上面这条链路完整走通一遍,你会比那些只会调参的人对AI工程的理解深得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询