ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零构建AI工程:数据管理、训练部署与监控全解析

从零构建AI工程:数据管理、训练部署与监控全解析 接触AI这行的人越来越多但真想上手把一个模型工程化地落地而不是停留在Notebook里跑通教程成了很多人的一道坎。ai-engineering-from-scratch——从零开始构建AI工程看着像是个GitHub仓库名对我来说它更像是一条完整的学习路径把AI从能跑通变成能上线、能维护、能迭代。这篇内容不是讲某个具体算法的调参技巧而是把AI工程化的整个骨架拆给你看从数据怎么管、训练怎么写得像工程代码到模型怎么部署上线、上线之后怎么监控。适合谁看如果你有一定的机器学习基础会调模型但总觉得项目一复杂就手忙脚乱或者你刚接手一个AI项目想知道除了模型之外还有哪些隐形工作量这篇文章能给你一份比较系统的路线参考。1. 先搞清楚什么是AI工程为什么要从零开始1.1 AI工程和算法调参是两件事我见过不少同学对着Kaggle上的高分Notebook说这不就是个二分类嘛我调调参也能做。他们在单机单卡上确实能跑出不错的线下分数但一旦到了公司、到了生产环境问题就来了数据在哪里能不能每天自动更新模型重新训练要多长时间上线后效果变差了怎么发现谁来监控这才是AI工程回答的问题。它不关心你用Transformer还是XGBoost它关心的是整个系统能不能稳定、高效、可重复地运转。打个比方算法比赛像做一道拿手菜食材、调料、火候都在你掌控之中做得好吃就行AI工程则像开一家连锁餐厅你得让每家分店、每个厨师、任何时候做出来的菜都是一个水准还得控制成本、处理食材供应链、应对停电停水。这是完全两种能力。所以当你看到ai-engineering-from-scratch这个名字我的第一反应是它强调的不是再走一遍算法理论而是把工程化的能力从地基开始补齐。从代码结构、数据版本管理、实验记录到部署上线每一步都得能过问为什么而不是能用就行。1.2 从零开始意味着什么很多教程会从安装PyTorch、跑一个预训练模型开始这样学完遇到真正工程问题还是无从下手。从零开始的意思是把那些框架帮你做好的事、隐藏起来的细节亲手拆开看看。比如数据处理时你会自己写一个数据集类理解batch、shuffle、num_workers这些参数为什么会影响训练速度训练时你不直接调model.fit这种黑盒API而是自己写for循环亲手做梯度清零、反向传播、参数更新这几个步骤。当你理解这些底层之后再回到高级框架里心态完全不一样——你看到的不是魔法而是一套可以被替换的组件。我当时给自己定了一条规则每个组件都可以用手写实现替换。不是为了重复造轮子而是为了在真正选型的时候知道每一个轮子为什么存在。这是从零开始最大的价值。2. 从零搭建AI工程的四个核心模块2.1 数据模块一切模型的起点数据是AI工程最容易翻车的地方却不是最被重视的地方。很多人把数据准备当成一个一次性脚本跑完就删等到模型要复现、要排查数据问题时发现找不到了。一个合格的数据模块起码要做到三件事数据血缘可追溯这一份数据是从哪来的、谁处理的、用了什么清洗逻辑。每一列特征如果发生了转换你得知道转换规则是什么。数据版本可回滚训练数据每个版本都要有标记。我见过太多团队因为忘了当前用的是哪份数据导致训出模型的A/B对比根本不成立。用DVC来管理数据版本或者最简单的方式每次数据集改动都打一个带日期的目录名都比data_final_v2最最新版.csv这种命名强。数据质量可见至少要有对数据做统计概览的脚本例如缺失率、取值分布、目标列的比例。把这些统计信息随版本记录下来能让你在模型效果异常时快速定位是不是数据变化引起的。我踩过最大的坑是数据清洗时用了全量统计量。比如做标准化时我直接对全量数据算均值和方差然后划分训练集测试集。这在离线评测时没感觉到了线上预测因为均值方差是全量算的导致线下指标虚高。正确做法是先只对训练集计算统计量再同样应用到验证集和测试集。数据泄露这个问题非常隐蔽后面会专门展开。2.2 训练模块从手写循环到框架封装训练模块是从零开始最值得下功夫的部分。哪怕你最终会用PyTorch Lightning或者Keras这类高级框架我还是建议至少手写一次训练循环。手写一个训练循环核心步骤五个拿到一个batch的数据放到设备上CPU/GPU将优化器的梯度清零模型前向传播得到预测值计算损失函数然后反向传播得到梯度调用优化器step更新参数。注意顺序很重要梯度清零一定要在loss.backward()之前否则梯度会累积在旧参数上导致更新方向偏离。我第一次写的时候顺序反了训练loss一直震荡就是不下降排查了很久才发现是这个原因。手写过一次之后再来用PyTorch Lightning你就知道它帮你封装的Trainer里本质上就是在正确的时机执行了这些操作。你也就理解为什么有时候一个参数设置不对训练就莫名其妙炸了。这不是结论只有一句玄学而是你缺少对底层细节的把控。训练脚本的工程化改造建议做到以下几条从Notebook迁移到Python脚本让训练可以重复执行自动化执行用配置文件管理所有超参数而不是每次在Notebook里手动改单元格。Hydra或者简单的yaml都可以核心原则是参数不进代码增加日志记录。每个epoch的loss、准确率、学习率、显存占用等都要记录下来。目的不是为了好看是出了问题能回溯到哪个阶段开始异常的固定随机种子。包括Python、NumPy、PyTorch和CUDA的随机种子不然你连复现自己上次的训练结果都做不到。2.3 评估与验证模块不能只盯着准确率工程级评估远远不止accuracy一项。首先要区分不同任务的核心指标。分类任务里如果正负样本不平衡光看accuracy就是自欺欺人Precision、Recall、F1、AUC才是关键回归任务里MAE对异常值不敏感RMSE对异常值惩罚更狠你得根据业务场景决定更在意哪一边排序场景比如推荐系统更看重NDCG、MAP这类天梯型指标。我这里想重点讲一句线下评估和线上效果的差距往往不是模型的问题而是评估方式没有模拟真实场景。比如你做的是时间序列预测如果用随机划分而不是按时间划分你就把未来的信息泄露进来了线下指标一定会虚高。正确做法是严格按照时间顺序切分训练集和测试集。另一个容易被忽略的是数据分布漂移检测。训练时用一个分布上线后用户行为变了特征分布就变了。一个简单的做法是把模型在预测时候的特征分布与训练时的特征分布做统计检验比如PSIPopulation Stability Index或者KS检验。如果漂移严重即使模型准确率没变你也要怀疑预测结果是否还可靠。我自己的做法是写一个评估报告脚本每次模型评估后自动生成一份报告内容包含核心指标、混淆矩阵、特征分布对比、错误样本抽样和几个固定case的预测结果。这份报告不是给机器看的是给人做决策时用的——它可以作为模型能不能上线的重要参考。2.4 部署与服务化模块模型不是跑通就结束从零开始做到这里应该是最后一个环节却是很多算法工程师最少接触的环节。模型跑通了怎么让别人用起来三个常见档次最低档是把模型保存下来写一个预测脚本给别人传数据返回预测结果但这种交互方式不适合线上实时场景。好一点的方案是用FastAPI封装成一个HTTP服务支持POST请求这大概是最常见的轻量部署方式。再往上走要考虑模型性能、扩展性、资源隔离这时就需要做模型优化和容器化部署。关于模型优化我说几个直接有效的技巧量化把FP32的权重压到FP16或INT8。对很多模型来说精度损失很小推理速度和显存占用改善非常明显。PyTorch里用torch.quantization能很方便地做这块。ONNX导出把PyTorch模型导出成ONNX格式可以脱离PyTorch环境部署而且ONNX Runtime通常比原生PyTorch推理更快。批推理如果场景允许尽量把请求凑成一批再推理GPU或者CPU的吞吐量都会好很多。代价是延迟变大所以需要权衡。容器化部署是用Docker把Python环境和模型打成镜像推到服务器上直接跑避免在我电脑上能跑的尴尬。Dockerfile写得干净点依赖锁定版本这些都是基础工程素养。3. 实操过程记录一个从零构建AI工程的完整实例光讲架构容易让人觉得虚我用一个文本分类的小项目把上面这些模块具体串一遍。这个项目是一个垃圾评论分类器目标是识别某社区评论里哪些是垃圾内容。3.1 第一阶段环境与基础设施选型我先确定几个基础决策Python版本选3.10因为当时PyTorch对它支持最好。虚拟环境用Conda简单可控。团队里如果多人协作我会推荐Docker因为镜像能统一所有人的环境。实验追踪工具用MLflow记录每次训练的指标和参数后面做模型对比和回溯会方便非常多。硬件先说清楚训练机器是单张RTX 4090部署用CPU因为线上QPS不高用CPU可以大幅降低成本。基础设施不追求大而全的工具链关键是能覆盖最基本的可复现、可追溯需求。很多刚起步的项目用Excel记录训练参数也能运转但一旦实验次数上来你就知道Excel根本顶不住。3.2 第二阶段数据流水线落地原始数据是一个CSV文件大概10万条评论带一个标签列1表示垃圾评论。数据流水线的第一步是把原始数据存成一个快照目录我命名为data/raw/20250315_raw.csv保证原始数据不被污染。清洗脚本处理这几件事去除HTML标签只留纯文本去除重复评价——这个很关键如果不处理重复数据会同时进入训练集和测试集算是一种静默的数据泄露去除空文本标准化文本编码统一转为UTF-8。清洗完成之后把结果存到data/processed/20250315_cleaned.csv同时生成一份数据质量报告包含行数、重复率、标签分布、平均文本长度等。然后是划分数据集。我这里强调一个细节划分之前需要先做shuffle然后再切分成训练集70%、验证集15%、测试集15%。shuffle的随机种子固定为42。如果不shuffle原始数据里某种类型的评论恰好排在一起划分出来的分布就会偏离整体。最后把所有数据切分结果用DVC跟踪版本。这不是为了赶时髦是为了你在三个月后还能说清楚这个模型是用哪一批数据训练出来的。3.3 第三阶段训练脚本的工程化改造模型我选的是一个轻量级的预训练中文BERT变体因为任务本身不太复杂用大模型纯属浪费算力。我自己写了一个PyTorch Dataset类在__getitem__里完成tokenizer的编码并且把padding截断的逻辑都处理好。在DataLoader里设置num_workers4shuffleTrue训练时每个epoch结束重新shuffle一次增强泛化性。训练脚本有几个工程化的关键点第一点是超参配置外置。我用了yaml文件管理所有超参数包括模型名称、学习率、batch_size、训练轮数、weight_decay和早停的patience。每次实验跑出的结果MLflow里都能对应到这一份配置。调参就变成改文件和看日志不再有这个数跑出来的结果最好但我忘了是不是调过这种状态。第二点是学习率调度。我用的是带warmup的线性衰减调度器。warmup比例0.1意思是前10%的步数里学习率从0线性升到初始值之后线性衰减。这个细节很影响收敛速度和最终效果很值得保留。第三点是梯度累积。因为显存限制单次batch_size只能设为16但我希望等效batch_size是32。做法是设gradient_accumulation_steps2两个batch的梯度累加后再更新一次参数。注意梯度累积时要在正确处理梯度缩放或loss除法保证整体梯度的scale是对齐的。训练过程中我在每个epoch结束后会在验证集上跑一遍计算准确率、F1和AUC然后保存最优模型。这里的最优我定义的是验证集F1最高的epoch对应的模型权重而不是最后一个epoch的权重。我从这个项目学到的另一个经验是不要急着上大模型。先用一个小模型把流水线跑通确认所有环节没问题再换更大模型。因为大模型训练慢调试时根本等不起。3.4 第四阶段模型评估与上线实测训练完成后我做了一次比较全面的评估结果如下准确率0.94F1分数0.91AUC为0.97。看起来不错。我进一步抽了一些预测错误的样本看发现一个模式很多被漏掉的垃圾评论是那种看起来很正经但实际上是广告的长软文。这种文本单看词汇很难识别需要上下文语义信息模型已经做得很不错了但确实还有提升空间。之后我把模型导出成ONNX格式部署在一个FastAPI服务里。服务逻辑不复杂接收JSON字符串返回得分但有几个工程细节值得注意输入要做合法性校验不能为空文本不能超长服务的超时时间要设置好避免某个异常请求把CPU线程卡死输出要做日志记录每次预测的输入脱敏后、输出、耗时都要落日志作为线上监控的数据来源。我额外做了一个简单的PSI监控脚本每周自动对线上预测样本做特征分布漂移检测如果PSI大于0.2就发告警邮件。这个小脚本后来真的救过我一次——某个渠道的评论内容风格骤变模型准确率掉了不少告警在用户投诉之前就帮我发现了问题。4. 常见问题与排查技巧实录4.1 数据泄露问题最隐蔽的坑数据泄露可以理解成训练时模型提前看到了测试集的信息。表现是线下指标极高上线效果却非常差。常见的泄露路径有这么几类预处理泄露清洗数据时对全量数据计算统计量再切分训练测试比如标准化里的均值和方差去重遗漏重复样本同时存在于训练集和测试集导致测试集被背下来了时间穿越做时间序列任务时测试集特征里包含了测试时间点之后才产生的信息特征泄露某些特征本身就是目标的结果比如用是否退款来预测是否退货。排查经验是如果真的怀疑数据泄露把模型预测的置信度拉出来看。如果模型对大多数样本都给出极高置信度而业务上你觉得不应该这么容易那么大概率有泄露。这时候最好的做法是回到数据管线逐环节核对这个信息在预测时刻是否已知。4.2 训练与复现不一致从环境开始查你可能会遇到这种情况上周训练出的F1是0.91这周复跑结果只有0.87。首先查的就是环境一致性。常见问题依赖库版本变了哪怕是patch版本更新对浮点运算结果都会有影响GPU型号变了不同GPU对浮点运算的舍入规则不完全一样随机种子没有固定。我之前专门写过一段代码在main的最开头一次性固定Python、NumPy、PyTorch和CUDA的种子同时禁掉cuDNN的自动benchmark模式因为benchmark模式会引入随机性。依赖版本问题最好的解法就是用Docker锁定环境或者用poetry这类带lockfile的依赖管理工具。不要相信requirements.txt里那种不带版本号的写法我在生产环境里已经栽过好几次了。4.3 线上效果和线下不一致分布漂移与监控这是所有AI工程上线后必然遇到的问题。线下测试集反映的是训练时刻的分布线上数据是动态变化的。用户习惯、内容生态、推荐策略等改变都会导致特征分布发生漂移。我的建议从上线第一天就要建好这四类监控数据质量监控特征缺失率、数值范围、分类特征的取值分布模型输出监控预测值的分布、置信度统计、正样本比例有没有突变性能监控推理延迟、吞吐量、超时率业务指标监控点击率、转化率、留存这些下游指标。如果业务指标掉了第一件事不是重训模型而是看监控数据是哪一类特征漂移了是输入数据出了问题还是上游某个系统改了行为。我之前遇到过看着模型输出都正常但业务指标却下滑的情况最后发现是上游推荐系统的排序策略变了导致进入这个模型的数据分布整体变了。你要是没有监控这种问题根本无从下手。4.4 排查思路总结从外到内先看数据再看模型我自己的排查顺序一般是这样先看线上监控数据判断是不是数据分布发生了变化再检查数据管线的日志看特征来源是否异常然后看最近一次的模型评估报告在时间线上对比各项指标的变化最后才考虑重训模型或者调整模型结构。很多人一出问题就想着调参重训这个习惯很不好——绝大多数线上问题都不是模型权重的问题而是数据和系统的问题。当前这个小型分类项目的日志和监控脚本后来是我每次启动新项目时的基础模板它让我自己少走了很多弯路。我个人非常推荐体验一次从零开始的过程哪怕只是一个小任务完整地手写一次训练循环、完整地把数据管线和部署流程都走通。这个过程收获的不是某一个模型更高的准确率而是对整个AI交付链条的掌控感。项目再大它的骨架也无非就是这些。这个教训是我真正做完这个完整流程之后才刻进脑子里的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进