拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零构建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这类高级框架,我还是建议至少手写一次训练循环。

手写一个训练循环,核心步骤五个:

  1. 拿到一个batch的数据,放到设备上(CPU/GPU);
  2. 将优化器的梯度清零;
  3. 模型前向传播,得到预测值;
  4. 计算损失函数,然后反向传播得到梯度;
  5. 调用优化器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这类天梯型指标。

我这里想重点讲一句:线下评估和线上效果的差距,往往不是模型的问题,而是评估方式没有模拟真实场景。比如你做的是时间序列预测,如果用随机划分而不是按时间划分,你就把未来的信息泄露进来了,线下指标一定会虚高。正确做法是严格按照时间顺序切分训练集和测试集。

另一个容易被忽略的是数据分布漂移检测。训练时用一个分布,上线后用户行为变了,特征分布就变了。一个简单的做法是把模型在预测时候的特征分布,与训练时的特征分布做统计检验,比如PSI(Population 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_workers=4,shuffle=True,训练时每个epoch结束重新shuffle一次,增强泛化性。

训练脚本有几个工程化的关键点:

第一点是超参配置外置。我用了yaml文件管理所有超参数,包括模型名称、学习率、batch_size、训练轮数、weight_decay和早停的patience。每次实验跑出的结果,MLflow里都能对应到这一份配置。调参就变成改文件和看日志,不再有"这个数跑出来的结果最好但我忘了是不是调过"这种状态。

第二点是学习率调度。我用的是带warmup的线性衰减调度器。warmup比例0.1,意思是前10%的步数里学习率从0线性升到初始值,之后线性衰减。这个细节很影响收敛速度和最终效果,很值得保留。

第三点是梯度累积。因为显存限制,单次batch_size只能设为16,但我希望等效batch_size是32。做法是设gradient_accumulation_steps=2,两个batch的梯度累加后再更新一次参数。注意梯度累积时,要在正确处理梯度缩放或loss除法,保证整体梯度的scale是对齐的。

训练过程中我在每个epoch结束后,会在验证集上跑一遍,计算准确率、F1和AUC,然后保存最优模型。这里的"最优"我定义的是"验证集F1最高的epoch对应的模型权重",而不是最后一个epoch的权重。

我从这个项目学到的另一个经验是:不要急着上大模型。先用一个小模型把流水线跑通,确认所有环节没问题,再换更大模型。因为大模型训练慢,调试时根本等不起。

3.4 第四阶段:模型评估与上线实测

训练完成后,我做了一次比较全面的评估,结果如下:

  • 准确率0.94,F1分数0.91,AUC为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交付链条的掌控感。项目再大,它的骨架也无非就是这些。这个教训,是我真正做完这个完整流程之后才刻进脑子里的。

返回列表