
1. 项目定位AI工程到底在解决什么问题先说一个很多人都踩过的坑。我在刚接触 AI 项目时以为核心工作量在于训练出一个神乎其神的模型。结果第一次把一个看似效果不错的模型推向真实业务时才发现自己完全没想过数据会漂移、接口该怎么做鉴权、模型每次重新训练之后如何保证输出稳定、甚至日志应该怎么设计才能定位线上问题。那一刻我才意识到AI 工程和训练一个模型是两个完全不同的概念。这个ai-engineering-from-scratch项目实际上就是把从建模到上线、从上线到持续迭代这套完整链路从零开始走了一遍。它不是某个具体的大模型项目也不是某个开源框架的教程而是围绕生产可用这个目标梳理了一套可复制的 AI 工程落地方法论。核心关键词就一个ai-engineering。看这个标题的人大概率不是想学某个算法细节而是想弄清楚别人怎么把 AI 系统支撑起来的。这套东西适合谁适合那些已经能跑通一个模型训练代码、但没有真正交付过 AI 服务的人。比如刚转行的算法工程师、做后端想拓宽技能边界的开发、还有想系统了解 AI 工程全流程的产品和技术负责人。它的价值在于把零散的碎片拼成一张图让你知道每一步为什么这么做、和下一步有什么关系而不只是贴出几段能跑的代码。整个项目我拆解后核心可以浓缩成四个环节数据工程怎么打底、模型训练与评估怎么较真、部署与推理服务怎么稳健、MLOps 闭环怎么让系统越跑越顺。下面逐个展开每个环节我都会把设计和实现思路、参数怎么选、坑在哪里说清楚。2. 整体设计思路为什么 AI 工程的核心不在模型而在系统2.1 从零开始的真实含义你需要跨过三道门槛很多人把从零开始理解为从最底层的数学推导开始学。但从工程角度真正的零指的是你面前只有一堆业务问题和原始数据没有任何现成的训练脚本、推理服务或监控面板。这时你面对的第一道门槛根本不是模型选型而是问题定义。同一个业务场景给内容打标签和给内容打分排序在系统设计上就是两条完全不同的路。前者关注分类阈值如何定后者关注排序指标怎么优化。第二道门槛是数据落位。业务方的数据通常散布在数据库、日志文件、第三方接口里甚至是一堆手工维护的Excel里。你需要做的第一件事是把这些来源不同、格式不一的数据统一成模型能吃的形态。这个过程听着简单实际上大多数项目的工期延误都发生在这一截因为脏数据会以一种看起来没什么问题的方式悄悄吃掉你的模型效果。第三道门槛是上线后的工程韧性。模型在离线评测集上表现良好计算出来了、准确率也不低但一旦承接真实流量可能出现训练分布和线上分布不一致、单次推理耗时抖动、内存缓慢泄漏这类问题。离线做的实验越少考虑这些变量线上爆雷的概率就越高。所以说这个项目把从零开始作为一个系统工程来设计而不是写一段训练代码就结束这是它和普通教程最本质的区别。在设计整个项目的技术路线时我遵循了三条原则渐进式搭建先在本地用少量数据跑通全流程小闭环再逐步横向扩展数据规模和服务化能力。可观测优先每个环节从第一版就埋好日志和指标采集点避免后期黑盒运行。稳定压倒一切能用一个可靠的简单方案就不会为了炫技引入一个复杂的分布式框架。2.2 技术栈选型的取舍逻辑不同人看到AI 工程这四个字下意识会联想到不同的技术栈。我的选择不一定是最新潮的但一定是在复杂度和功能之间最平衡的快速梳理一下环节选型选型理由基础语言Python 3.10AI 生态最成熟团队协作成本低数据处理Pandas Polars视数据量切换既能灵活探索又能在数据量大时提速训练框架PyTorch调试灵活、生态强遇到自定义算子也方便推理服务FastAPI原生异步支持自带 OpenAPI 文档上手快部署运维Docker 云主机/内网服务器降低环境差异带来的不可控问题任务编排Airflow轻量可换为 Cron 脚本实现训练、评估、同步的定时触发和重试监控告警Prometheus Grafana指标采集标准可视化直接元数据管理MLflow仅用于实验追踪记录参数、指标、模型版本减少跑的哪个版本这类混乱这里有一个关键判断选工具不是为了看起来专业而是为了减少维护负担。比如 Airflow如果团队只有两三个任务用它的调度器和一套监控系统反而成本过高。我更倾向于先用脚本加 Cron 把流程跑稳定确认流程不会经常失败之后再引入专用编排工具。另外模型版本管理这块很容易被忽略。没有模型注册表机制你迟早会遇到这个生产模型的准确率汇报是哪一版跑出来的线上模型和评测模型参数对不上这类的纠纷。MLflow 虽然开源项目很久没有大版本更新但单纯记录训练参数、指标、模型的溯源场景依然够用不必在这上面追求复杂。3. 数据工程占工作量 70% 但最容易被低估的环节3.1 原始数据探查与清洗的关键动作我在这个项目中对数据工程投入的精力远超模型调参。一个基调要先定下来模型再好也救不了烂数据这不是口号而是无数项目检验过的事实。数据探查阶段我建议做成标准动作清单不要凭感觉随意跑几个 describe() 就完事。完整清单至少包含以下内容字段完整性与缺失率每列的缺失值占比、缺失是否具有时间聚集性。如果缺失集中在某个时间段往往说明数据上传链路在那个时段有问题。唯一性与重复度表的主键是否真的唯一重复样本是直接删除还是要做聚合处理。数值分布异常不仅看均值标准差还要用箱线图和分位数检查极端值。业务上用户下单金额为负数这种异常如果不处理模型第一个就学歪。时间字段跨度训练集和测试集的时间分布是否重叠避免未来数据泄露。目标变量的稳定性如果你做的是分类任务正负样本比例是多少这个比率是否随时间变化如果今天训练用的数据分布和三个月前完全不同模型上线就会遭遇凭空多出来的预测偏差。清洗阶段有一个我反复强调的原则每一步转换都必须留下可回溯的代码或配置禁止手工改数据文件。比如有一批数据的 category 字段全部是小写另外一批是首字母大写理论上可以用 Excel 统一但一旦手改后续想追溯为什么线上预测结果和当时实验不一样就完全无从下手。清洗过程中我常用的处理顺序是去重 - 类型纠正 - 异常值过滤 - 缺失值填补 - 构造衍生特征。其中缺失值填补有个容易忽略的细节不能在全量数据上算出均值再去填训练集和测试集这样会造成信息泄露。正确做法是先只基于训练集计算统计值再把这个统计值分别应用到训练集和测试集。这个点我在许多开源代码里都见过写错的属于看着小、影响深的典型。3.2 特征工程的工程化表达很多项目在做特征工程时都有一个坏习惯在 Notebook 里定义了特征函数训练时手动运行一遍预测时又复制粘贴一遍。短时间看没问题时间一长被复制的那份代码可能改了一行逻辑训练和推理就开始悄悄偏离。AI 工程里管这叫train-serve skew训练-服务偏差是生产事故的高发根源。解法其实不复杂基本原则只有一条让训练和推理走同一套特征处理代码。我在项目里把所有特征转换逻辑封装成一组纯函数或类比如FeatureTransformer训练脚本直接调用它处理 DataFrame推理服务则把这条样本先过同一个 transformer 再交给模型。核心代码可以简化成这种思路class FeatureTransformer: def __init__(self, config: dict): self.config config self.statistics {} def fit(self, df_train): # 仅在训练集上计算所需统计量 self.statistics[mean_amount] df_train[amount].mean() return self def transform(self, df): # 训练和推理共用同一套逻辑 df df.copy() df[amount_feat] df[amount] - self.statistics[mean_amount] return df def fit_transform(self, df_train): self.fit(df_train) return self.transform(df_train)封装之后训练时调用fit_transform推理时调用transform参数和逻辑天然对齐。你可能会问这不过是把代码组织得更规整了一点值得单独拿出来说吗我的体会是大多数规模不大的 AI 项目死掉都不是死在模型复杂度上而是死在这种逻辑散落、口径不一的细节上。另外特征存储也值得注意。不要把所有特征一股脑塞进一个大文件。最好按特征来源和更新频率分层离线批量特征放特征表在线实时特征放 Redis 或内存缓存模型读取时做特征拼接。这个做法能让特征更新独立于模型发布也方便排查为什么线上算出的特征和训练时算的不一致。3.3 数据漂移与质量监控的落地姿势数据漂移这个概念看起来高大上本质就是线上数据变了而你不知道。比如模型训练时正样本占比是 8%上线三个月后升到 20%如果没做监控你根本不知道模型的打分分布已经整体上移了。可以不用一上来就上复杂的漂移检测算法。我在项目里采取的是两层监测方案第一层是基础分布监测。对每个核心特征每天统计均值、分位数、缺失率、唯一值数量和训练基线做对比。如果某个特征的分布偏移超过阈值就触发告警。这层监测实现成本极低一个定时脚本加一张报表就能完成。第二层是预测分布监测。模型打分或分类结果的分布一旦出现突然的形态变化比如原来集中在 0.3-0.6现在大量落在 0.85 以上就要警惕线上数据分布异常或业务逻辑变更。这一层用的是模型输出本身作为监控对象实施起来更直接。数据质量这块我自己踩过一个记忆深刻的坑。有次模型效果突然衰减查了整整一天最后发现是上游数据源新增了批量补录功能把几个月前的历史数据重新推了过来。特征表里的记录被重复累积统计特征严重失真。从那之后我在数据管线里固定加了一道数据新鲜度检测:每个批次数据必须带业务时间戳处理程序发现异常大跨度补录时先拒绝写入只告警不静默处理。4. 模型训练与评估从能跑通到能扛事4.1 训练 Pipeline 的设计与实验管理模型训练这件事单看算法并不复杂真正复杂的是稳定复现和快速迭代这两个目标同时成立。训练脚本如果只是孤零零一份参数靠手改跑完只留一个.pth文件那下一次实验基本等于从零开始摸黑。我在项目里将训练 Pipeline 拆成了四个独立阶段一个明确的 pipeline 至少包含以下阶段数据切分按时间序列切分训练/验证/测试集而不是随机切分防止时间穿越。特征生成调用前面说的 FeatureTransformer 完成 fit/transform。模型训练记录超参数、损失曲线、各项评估指标。模型评估与导出在测试集上计算最终报告生成模型卡片并注册版本。关于实验管理我强烈建议哪怕再小的项目也从上手第一天启用 MLflow 或类似工具。你想啊跑几十组实验后如果只靠文件夹里的model_v8_final_再改一次.pth这种命名来记忆用不了几天就分不清哪个是哪个了。实验追踪做的事本质上是给每次训练建立一份档案包括参数、代码版本、数据集版本、指标结果这样才能保证严谨的对比和回溯。每次实验运行前应该把关键超参数记录在配置文件中而不是散落在命令行。配置格式用 YAML 或 JSON 都可以关键是固定化、版本化。训练脚本启动时自动把配置哈希后作为运行标识的一部分这样每跑一次实验产物文件名都唯一且可追溯到配置。4.2 多维度评估体系别只盯着 Accuracy只用一个准确率来衡量模型效果是新手最常见的问题。在正负样本不平衡的场景下98% 准确率可能意味着模型把所有人都预测为负样本实际一个正例都抓不住。所以在我的评估模板里从不只报单一指标而是一个矩阵评估维度指标集合用途整体正确性Accuracy, Top-1/Top-5快速了解模型整体水平类别均衡度Precision, Recall, F1分类别统计发现模型对少数类的忽视排序能力AUC, NDCG, MRR用于推荐、搜索类业务预测可信度Calibration Error, Brier Score评估模型输出概率是否可信稳定性多次重复训练的指标方差判断训练过程和模型结构是否可靠最后一项稳定性容易被忽略。同参数跑两次指标如果忽高忽低说明模型或数据本身有较大随机性这种模型上线后容易表现得时好时坏。我在项目里每次配置跑至少两次观察指标方差方差过大的模型宁可重新调参也不会上线。另外还有一点值得写进评估流程不只看测试集整体指标还要看细分群体的表现。按用户活跃度、内容类目、时间段等维度分别算指标。整体 AUC 很高但新用户群体的 AUC 接近随机概率这种虚假的优秀如果不拆开看根本发现不了。线下的问题往往就是这样带着隐患上线的。4.3 调试与调参的实战心得模型不收敛或收敛效果差时很多人的第一反应是调学习率或换模型结构。但我的调试顺序是固定的从低成本到高成本逐层排查先检查数据是否对齐。特征列顺序是否和训练一致、标签是否错位、归一化参数是否套错。大多数玄学问题本质都是数据问题。再检查训练动态。把训练集的 loss 画出来如果训练集 loss 都降不下去说明模型容量或学习率设置有问题此时调整数据增强没有意义。然后检查过拟合程度。训练集指标远高于验证集说明模型在死记硬背需要加强正则化、降低模型容量或增大数据量。最后才考虑换模型结构或加特征。关于学习率我一般用的判断方法很朴素从一个中间值开始比如 3e-4跑少量步数观察 loss 曲线的下降斜率。如果 loss 纹丝不动则调大学习率如果 loss 出现震荡发散则调小。这个少量步数试探法比盲目套一组最优参数更实用因为不同数据规模、不同任务的最优学习率差异很大。还有一个高频踩坑点梯度累积和批次大小的关系。很多人用梯度累积来模拟大 batch但忘了同步缩放学习率。比如从 batch size 32 改成 128学习率通常也需要相应调大。我当时为了省显存把 batch size 降到 16 并开了梯度累积凑成 64却完全没动学习率结果收敛速度肉眼可见地变慢。这类坑文档里通常不会写但实际运行时非常常见。5. 部署与推理服务模型落地最硬核的一公里5.1 从训练产物到可服务化接口的完整链路模型文件本身不能直接对外提供服务至少还需要一个推理服务把模型计算包装成HTTP 接口。这里要设计的内容不止是加载模型然后预测还包括输入校验、特征转换、结果后处理、日志记录和异常兜底。我常用的方案是 FastAPI 加 uvicorn原因是它自带异步能力也在生态和性能之间取得了一个合适的平衡。一个典型的推理接口结构如下app.post(/predict) async def predict(request: PredictRequest): try: feature_vector transformer.transform(request.dict()) result model.predict_proba(feature_vector) log_prediction(request, result) return {prediction: round(result, 4), version: model_version} except Exception as exc: logger.exception(inference failed) raise HTTPException(status_code500, detailstr(exc))这段代码看着简单但关键点都在暗处模型加载之后必须做一次预热推理防止线上第一个请求触发冷启动超时接口响应里要带上模型版本号这样后续分析和排查问题才有明确标识输入用 Pydantic 做严格类型校验不符合结构的数据直接返回 4xx不让脏数据进模型。关于并发和缓存也有讲究。如果模型的推理耗时在几十毫秒级别单机部署完全够用。需要考虑的是模型预热和缓存设计高频的特征可以加一层 Redis 缓存但模型本身和特征转换器的版本要绑定好一旦模型更新缓存不能还在用旧版本的特征逻辑。这个问题曾经让一个朋友的项目在切换模型后线上指标抖动了好几天最后才发现是缓存未失效导致的旧特征串味。5.2 推理优化的实用做法推理速度和资源占用直接决定部署成本。我常用的优化手段按投入产出比排序如下小批量推理batchingGPU 或 CPU 推理时把多个请求拼成一个 batch能显著提升吞吐。但要注意延迟约束不能为追求吞吐而不顾单个请求等待时间。模型量化压缩把 FP32 转成 FP16 或 INT8显存占用大幅下降速度提升明显。PyTorch 自带量化接口市场上也有成熟的推理优化框架。量化后精度损失通常可控但必须在实际数据上验证后再部署。避免重复计算有些业务逻辑可以在特征层做到一次计算多处复用不必每个请求都从头跑。选择合适的容器规格CPU 和内存配对了成本能降不少。建议先压测再定规格而不是凭感觉分配资源。部署环节还有一个不容忽视的细节回滚预案。每次发布新模型都应该同步保存旧模型的版本和配置并在发布脚本里支持一键回滚。模型和代码不一样出问题时不一定是报错也可能是静默地给用户推了错误结果这种问题发现得越晚影响越大。5.3 压测与扩容的正确姿势上线前的压测不是随便拿个脚本打几百个请求看看响应时间就完事。我自己整理的压测流程分三步走第一步单机单副本压测找出最大可承载 QPS 和对应的 P95/P99 延迟。此时把 CPU、内存、网络 IO 的数据都记录下来确定当前实例的瓶颈在哪个维度。第二步用多副本压测验证横向扩容效果。比如 1 副本最大 50 QPS2 副本应该达到接近 100 QPS。如果扩容后 QPS 没翻倍说明可能存在连接数、数据库或特征存储这类共享瓶颈。第三步做长时间稳定性测试至少跑 30 分钟以上观察内存是否持续增长、CPU 使用率是否平稳、有没有句柄泄漏。很多服务上线后越跑越慢都是慢速内存泄漏导致的短时间压测看不出来。关于自动扩容我持保守态度。流量不可预测的服务当然要配置自动伸缩但如果业务流量相对平稳固定副本数反而更容易排查故障。自动伸缩的触发阈值设置不当可能会在流量波动时频繁扩容缩容造成资源浪费和额外的不稳定因素。6. MLOps 闭环上线只是开始迭代才是常态6.1 监控体系搭建从指标采集到告警通知模型服务上线后很多人以为万事大吉实际上真正的考验才刚开始。线上推理日志和指标如果只用等出问题再看通常等到的是用户投诉和业务方质问。我的监控体系从四个层面展开业务指标监控预测结果的分布、业务核心指标如推荐点击率、审核准确率是否异常变化。系统资源监控CPU、内存、磁盘 IO、网络带宽这些都是基础设施层的基础需要紧盯的项目。推理延迟监控P50、P95、P99 分位数。P95 能够反映大多数用户的真实体验P99 超时则需要关注。数据质量监控线上输入特征的有效率、缺失率、取值分布和训练基线偏差。告警规则我建议宁精勿滥。告警太多会造成狼来了效应团队最后会无视所有告警。我通常只设置三层告警一是有服务宕机风险时立刻告警二是指标明显偏离阈值时告警并附上简要排查建议三是趋势异常但未达阈值时记录日志观察不打扰人。6.2 模型更新的闭环策略手动胜自动轮询确定什么时候更新模型是一个策略问题。很多团队在自动每周训练发布和手动按需更新之间纠结。我的方案比较务实先手动成熟后自动但永远保留一键回滚。第一版模型上线后先手动积累反馈数据人工分析数据漂移和模型衰减规律。这个周期一般持续两到四周你会逐渐知道数据更新的节奏也知道业务方大概多久提供一次标注反馈。这时候再判断是否值得自动化模型重训流程。盲目自动化的结果是你甚至不知道自动重训后的新模型是否比旧版好就贸然发布出去风险不值得冒。重训触发条件我一般设置为多条件组合比如新标注数据量超过 N 条且距上次训练超过 M 天且数据漂移指标超过阈值三者同时满足才触发训练。训练完成后先进入影子评估阶段新模型离线评测优于线上模型一定幅度后才切换流量。这个过程可以用一个简单的状态机来表达训练完成 - 离线评估 - 低位灰度(10%流量) - 全量发布 - 持续监控 | ---- 不达标则回滚这个流程的好处在于每一步责任清晰每一步都可以回退不会出现推到生产环境跑几天才发现效果变差的问题。6.3 灰度发布与回滚机制的经验谈灰度发布这个概念听起来大落地时只需要一个开关。比如用配置中心里的一个变量控制新模型服务承接流量的百分比。10% 灰度跑一到两天观察核心业务指标是否异常。如果没问题再逐步调高到 50% 和 100%。这里最忌一步到位全量切换哪怕离线评估再好线上真实环境的变量总是离线实验考虑不到的。灰度期间需要关注的不只是业务正确性指标还要关注一个微妙的东西新模型带来的结果分布变化。比如用户收到的推荐内容和以前差异很大即使点击率指标没变也可能会引发体验上的陌生感。这个变量不在模型评估的范围内但要在灰度观察期通过人工抽样评阅来感知。关于回滚我在项目里强制要求发布系统保留旧模型快照至少两个版本。不光是待定模型文件还包括配套的特征转换配置和推理代码版本。曾经发生过一次新模型发布后核心指标暴跌的情况回滚时才发现模型文件换了但特征配置忘了切回去。偏离散得非常严重等我反应过来已经过去了好几个小时。回滚必须是整体回滚代码、模型、配置三者版本一致这一点务必在发布文档里写清楚。7. 常见问题排查与避坑心得7.1 高频故障速查表把项目过程中遇到过的故障整理成一张表能帮自己和团队少走很多弯路。下面是我梳理的最有价值的排查清单现象首选排查方向常见根因离线指标好线上效果差特征一致性、数据分布差异train-serve skew、数据漂移推理延迟突增是否发生 CPU/内存争抢、并发增高缓存失效、服务扩容不及内存持续上涨是否在请求处理中持有大对象特征缓存无过期、模型重复加载预测结果周期性异常上游数据是否有定时任务写入数据新鲜度监控缺失模型重训后效果倒退新数据分布是否变化、标签是否有误缺少数据漂移校验和影子评估同一请求不同时间返回不同结果特征是否用了非确定性逻辑随机采样特征未固定种子排查问题有一个前提日志必须足够完整。我在每个推理请求里都记录了时间戳、模型版本、输入的关键摘要字段、预测结果和处理耗时。虽然会多占一些存储但线上出问题时如果没有这些字段排查就像在蒙着眼找针。7.2 三个花真金白银换来的教训第一个教训永远不要跳过影子评估。有一次新模型 AUC 提升明显我直接切了全量流量结果核心业务转化率反而下降。原因很简单AUC 反映的是排序能力但业务关心的是某一档位的转化率两者并非完全对齐。从那以后新模型上线必须先在影子模式跑一段时间也就是线上真实请求同时过新旧两个模型但新模型的结果只记录不返回给用户用真实流量下的逻辑对齐验证效果。第二个教训训练数据的时间窗口不能太随意。有一次图省事把一年前的旧数据全塞进了训练集模型对最近半年的新用户模式完全无感知。从那以后训练集的构造一律按业务理解来切分时间窗口还要在配置里注明数据起止时间。第三个教训不要用生产环境调试代码。包括在线上服务器的 Notebook 里跑实验、直接在生产数据库上做聚合分析、用线上推理服务代替测试环境。这些事情在没出事故的时候看起来节省了大量搬数据的时间一旦出现问题审计和排查都会陷入被动。我现在所有实验都先在隔离的测试环境完成数据通过受控流程导入导出。7.3 给新手的渐进式实操路线建议如果你打算从零起步搭建自己的 AI 工程能力不用照搬大型团队的整套方案。我建议按这个顺序渐进式推进第一步完成单机全流程小闭环在本地或单台服务器上用一份真实数据集走通数据清洗、特征工程、模型训练、FastAPI 推理服务、简单监控脚本。目标是把每个环节的操作熟练起来理解链路依赖关系。第二步加一层实验管理把训练参数和指标记录到 MLflow 或简单数据库让每次实验有据可查。这个小改动会让你的迭代速度和复盘能力飞速上涨。第三步部署和容器化规范化把环境依赖锁定到 Dockerfile让部署不再依赖只有在某台机器上才能跑的偶然性。同时配置一个基本的健康检查和资源使用采集。第四步引入监控与告警至少在推理接口和资源层面埋好基础指标。能回答现在服务健康吗这个问题的系统才算跨入了 AI 工程的门槛。第五步再谈自动化重训和灰度发布。到这一步时你已经有了在线运行的系统、足够的监控数据和清晰的迭代需求此时引入 Airflow 或 CI/CD 才是水到渠成的事情。我在实际体验踩过这些流程后最大的感受是AI 工程能力的提升主要来自一次次真实故障的复盘和反思其次才是抽象的方法论。先把能跑通的最小闭环搭起来然后逐层叠加稳定性、可观测性和自动化能力每一层都要解决真实的问题而不是为了堆名词。最后再分享一个小技巧从一开始就建立一个运行手册文档把已上线的模型版本、数据源表、特征逻辑、监控面板链接、常见问题排查步骤都放在里面。这个文档不需要排版多漂亮但一定要真实可用。我见过不少团队在这方面吃尽苦头核心负责人一请假线上出了一点小问题都没人知道从哪里开始查。把运行手册当成工程的一部分来管理后续所有迭代和协作都会顺畅很多。