我真正下决心做一次“ai-engineering-from-scratch”,把整套AI工程链路从零开始完整走一遍,是因为一次被生产事故狠狠教育了。那个项目的模型在离线测试里跑得漂漂亮亮,上线一周后准确率直接对折,连同事都开始怀疑我的测试集是不是调包调错了。排查到最后,问题居然出在数据预处理和训练代码的版本不一致上——一个半年前就存在的隐蔽bug。当时我就想,与其依赖各种封装好的框架和速成课,不如亲手把整个AI工程链路从零搭一遍,把所有环节的细节都摸透。于是有了这篇“ai-engineering-from-scratch”的完整记录。
这篇内容适合三类人:准备入行AI工程、想从算法岗转工程岗的研发同学;已经在用现成AI能力、但想搞清楚底层链路的产品或后端开发者;以及那些跑通过不少Demo、却始终觉得自己在“调包”而不是“做工程”的初学者。我会从工程视角而不是算法研究视角来拆解问题,围绕数据、训练、评估、部署四个核心环节展开,里面所有操作都是我自己真实跑过的路径,踩过的坑也都会讲清楚来龙去脉。
1. 从零开始之前:AI工程到底在“工程”什么
1.1 模型只是冰山一角
很多人把AI工程等同于模型训练。实际上,在一个投入生产的AI系统中,真正的模型代码往往只占很小一部分。数据获取、清洗、标注、存储、特征工程、训练流水线、评估体系、上线部署、监控告警,这些环节加起来才是工程的主体。用数据说话:我曾经把一个文本分类服务拆开统计代码行数,模型相关代码占比不到15%,剩下的全是数据管道、接口、缓存、日志、回滚逻辑。
所以从零开始,第一步不是学最新的Transformer架构,而是建立“系统思维”。你写的每一段代码,最终都要服务于一个能稳定对外输出价值的系统,而不是服务于一次实验。这也是“from scratch”和“调包”的本质区别:调包只要知道接口签名,做工程必须理解数据怎么流动、模型在哪一环失效、系统如何降级。
1.2 把AI能力拆成可训练的技能清单
从零开始最怕的是目标模糊。我建议把AI工程需要的能力拆成一张清单,按顺序逐个击破:
- 数据技能:采集、清洗、标注、增强、版本管理
- 建模技能:经典机器学习模型、深度学习模型、损失函数与优化器选择
- 训练技能:训练循环、分布式训练、断点续训、超参调优
- 评估技能:离线指标设计、线上指标映射、A/B测试
- 部署技能:模型导出、接口封装、容器化、GPU服务优化
- 运维技能:监控、告警、模型版本回滚、数据漂移检测
我当时就是照着这张清单,给自己定了一个“三个月从零搭出一个可用的文本分类服务”的目标。这样一个具体目标,比单纯刷论文和教程有用得多。每完成一个环节,就相当于在清单上打一个勾,进度可见,信心也稳得住。
这个技能清单还有一个作用:防止你过早陷入某一个细节。比如我见过很多人花了大量时间研究某个最新的模型结构,结果数据管道一塌糊涂,样本泄漏、标签错误,模型再强也是白搭。从零开始做工程的正确顺序,永远是先把骨架搭稳,再考虑锦上添花。
2. 最小闭环:从裸机到一个能用的AI服务
2.1 环境选型:用最稳的,而不是最火的
动手第一步是搭环境。这里我吃过一次亏:一开始直接用最新的Python版本和最前沿的训练框架,结果第三方库兼容性连环报错,光环境就折腾了两天。后来总结出一条经验:选环境的第一标准是稳定兼容,不是你拿到了多新的特性。
我的建议组合:
- 操作系统用Ubuntu 20.04或22.04 LTS,稳定且社区支持最全
- Python版本用3.8到3.11之间的长期支持版本,不要刚发布新版本就在生产项目里冲锋
- 训练框架选一个主流的稳定分支,优先看它的LTS版本
- 虚拟环境用conda或venv都行,但整个项目内必须统一,避免不同依赖互相污染
配置好环境之后,我强烈建议做一件事:把依赖导出为可复现的文件,连同版本号一起锁死。这一步看似简单,实际操作时很多人会漏掉。等三个月后你想复现自己的实验结果,就会感谢当时锁版本的决定。我当时用的是pip freeze重定向到requirements.txt,同时把关键包的版本号记录在一个表里,方便回溯。
2.2 数据从哪里来:别用干净数据集骗自己
很多初学者喜欢用Kaggle上处理得干干净净的数据集来练手。如果你只是想熟悉模型代码,这没问题;但如果你要练的是AI工程,请立刻换成“脏数据”。真实业务场景里的数据一定是:字段缺失、格式混乱、标签有噪音、正负样本极不均衡。
我从零搭建时,选择了一个我熟悉的业务场景——企业内部工单分类。我拿到的原始数据是一堆CSV文件,里面不仅有重复行,还有大量空值、乱码和错误标签。我自己写了清洗脚本,做了以下处理:
- 缺失字段:区分“正常缺失”和“异常缺失”,分别设计处理策略
- 重复样本:用文本hash去重,同时保留一份去重前的备份
- 标签噪音:抽样100条人工复核,统计标签错误率并建立修正规则
- 类别分布:统计每个类别的数量,发现有一个类别占了将近70%,明显不均衡
这些都是工程里的真实问题。数据质量直接影响模型上限,这句话说了无数遍,但只有亲手处理过一批脏数据才能理解它的分量。我当时花在数据清洗上的时间,占整个项目周期的大约40%,而模型训练只占不到20%。这个比例一度让我怀疑自己是不是在浪费时间,但后来事实证明,数据清洗的每一分钟都是值得投入的。
2.3 第一个可运行模型:跑通不代表结束
数据准备好之后,我推荐先用一个最简单的基线模型跑通整个流程,而不是直接上大模型。我的第一版是一个基于TF-IDF加逻辑回归的文本分类模型,代码量很小,训练也快,几分钟就能跑完。它的意义在于把数据加载、模型训练、指标评估这条链路完整打通,让每一环节都有真实的输入输出。
我在这步踩过一个很典型的坑:训练脚本能跑通,但保存模型后重加载,预测结果全是同一个类别。排查了半小时,最终发现是因为保存模型时把词表也一起序列化了,但加载时没有统一词表,导致新的文本被映射到错误的id。这个问题的根子在于“训练与推理不一致”。从零搭建时,一定要把模型和预处理逻辑绑定保存,并且在加载后做一个冒烟测试:用训练时的样本文本走一遍预测,确认结果一致。
跑通基线模型之后,我才开始尝试复杂模型。这个过程让我深刻体会到一件事:基线模型的价值不只是“交差”,它为你后面所有复杂的改动提供了一个对照基准。后面无论换上什么模型,都要拿它和基线对比,如果提升不明显,说明问题不在模型结构,而在数据或特征。
2.4 让模型开口说话:最小化推理接口
模型训练好了,接下来要把它变成一个能对外提供服务的接口。这里不用上来就搭微服务,先做一个最小可用的推理接口就够了。我用FastAPI写了一个简单的REST接口,接受文本输入,返回分类结果和置信度。
这个环节有几个容易被忽略的点:
- 请求数据校验:不能假设调用方一定会传合法格式,要做字段校验和错误提示
- 超时控制:模型推理可能很慢,接口必须设置超时,防止请求堆积拖垮服务
- 并发与排队:如果同一模型同时来很多请求,要么加锁排队,要么上消息队列,否则显存和内存会被打爆
我当时的做法是先做同步处理,加了一个简单的并发控制,限制同时推理的请求数量。后来量大了才换成异步队列。这个演进思路,就是从零搭建时应该有的节奏——先把最小闭环跑通,再根据真实压力去优化。
3. 训练、微调与部署:三个最容易“翻车”的进阶环节
有了上一节的最小闭环基础,接下来就要进入真正拉开差距的三个进阶环节。这三个环节我都翻过车,而且翻得非常彻底,所以想用这一节把话说透。
3.1 训练不收敛时,先查数据再查模型
第一次训练深度学习模型时,我盯着屏幕上的损失曲线,发现它一直不降。第一反应是调整模型结构,加大学习率,尝试各种优化器,结果毫无改善。后来一个老前辈提醒我:先检查数据,再检查模型。
我按他的建议排查,发现第一个问题是标签从1开始编号,但我忘了做偏移,模型默认从0开始预测,等于所有样本的预测结果天生错位。修正之后损失立刻开始下降。从那以后,我养成了一套固定的排查顺序:
- 数据维度:标签是否正确对齐、是否归一化、样本是否打乱
- 模型输出维度:输出维度是否等于类别数、最后一层激活函数是否合适
- 数值稳定性:有没有除以0、有没有溢出,打印中间变量的张量形状和数值范围
- 超参维度:学习率是否过大,批量大小是否小得离谱
这个教训对“from scratch”尤其重要,因为当你自己亲手写训练循环时,到处都是出错的可能。框架封装的API帮你避免了很多低级错误,但也让你缺失了排查的直觉。自己从零写一遍训练循环,哪怕最后还是会换成封装好的库,你至少知道报错信息背后的原因是什么。
3.2 微调开源模型时,我最后悔没早做的三件事
有了一定基础后,我把工单分类模型升级成了基于预训练模型的微调版本。这一步让我真正理解了“微调”是怎么回事。这里说三件我最后悔没早做的事。
第一,没提前冻结和检查tokenizer的输出。微调模型和基线模型最大的差别之一,就是文本必须经过tokenizer处理成token id,而这个环节一旦出错,整个训练都是在乱学。我一开始直接用默认分词器处理中文文本,结果很多专业术语被切得四分五裂。后来我专门配置了词典,并在训练前打印一批样本的人工可读结果,肉眼确认分词质量。
第二,没做训练前的“数据形状冒烟测试”。把数据送进模型之前,先跑一个batch,检查张量形状是否匹配、标签是否在同一设备上。这个冒烟测试能避免90%的枯燥debug时间。我后来每次写新模型都会先跑这一步。
第三,过早追求大模型。我最初试图直接微调一个很大的模型,结果单卡显存不够,训练速度极慢,还没跑完一个epoch就OOM。后来我换成小一号的模型先跑通流程,验证了效果之后,再做模型规模扩展。这个顺序可以帮你快速定位瓶颈到底在算法层面,还是在工程层面。
3.3 部署推理时绕不开的延迟与显存账
微调好的模型要真正发挥价值,最后一步是部署上线。这里需要算两笔账:一笔是延迟账,一笔是显存账。
延迟账:用户从发出请求到拿到结果,这个时间是多少?如果模型推理要600毫秒,而业务要求的P95要控制在300毫秒内,你就不能简单做个同步调用。要么模型精简量化,要么加缓存,要么把模型拆成多个小模型并行推理。
显存账:模型参数量对应的显存占用,不等于参数量乘以精度字节数。实际部署时,中间激活值、CUDA上下文、推理框架的额外开销都要算进去。我当时部署一个中等规模模型,参数占用大约2GB,但实际启动后显存占用超过7GB。这个差距如果不提前估算,生产环境很容易OOM。
涉及量化时,我的经验是优先做精度无损的优化:把模型转成高效的推理格式,往往能同时降低延迟和显存。如果还不够,再考虑量化,但必须注意精度回退。我当时用一个小型验证集对比量化前后的效果,确保指标不掉再上线。
4. 从零搭建必须知道的翻车现场与排查链路
下面这三段,都是我从零搭建过程中真实遇到、并且花了很长时间才解出来的问题。我之所以不直接给答案,是想把整个排查链路完整还原出来,因为思路比答案值钱得多。
4.1 损失函数输出NaN的完整排查过程
完整记录一次排查过程,比看十篇博客都有用。我在训练过程中遭遇过一次损失突然变成NaN的情况,训练到第1200步,loss从2.1骤降到NaN,整个模型直接废了。当时的排查链路是这样的:
第一步,缩小范围。我把数据集从几万条缩小到200条,把训练步数缩小到200步,快速复现这个现象。结果200步内必定出现NaN,说明问题不在随机性,而是确定性bug。
第二步,打印中间值。我在训练循环里加了几行打印逻辑,检查每一层的输出、梯度和损失值,重点看哪一步开始出现“inf”或“NaN”。结果发现是某一层特征经过计算后数值爆炸,从几百直接变成几亿。
第三步,定位根源。数值爆炸通常要么是学习率过大,要么是数据范围过于极端。我先尝试把学习率从1e-4降到1e-6,NaN推迟出现但仍在;再把输入数据做标准化,NaN消失。这说明问题正出在输入数据的数值范围上。
第四步,修复与验证。我给特征增加标准化处理,并把归一化逻辑写进了预处理管道,保证训练和推理一致。修复后重新跑完整数据,loss稳定下降到收敛。
这个案例里最有价值的经验是:出问题时先缩小范围快速复现,再逐层打印定位,而不是迷茫地调整各种超参数。复现、定位、修复、验证,四步走完,bug才能被清清楚楚干掉。
4.2 训练准确率100%但线上效果打骨折
这个坑我开头提到过。现象是:训练集准确率接近100%,验证集也有95%以上,一上线就崩。排查过程如下:
首先检查训练和推理的预处理一致性。我写了一个回归测试:用同样的输入文本,走训练时的预处理和走服务接口的预处理,对比输出token id是否一致。结果发现,服务端的文本清洗逻辑与训练脚本不完全一致,比如做大小写归一化的顺序不同,导致同一个句子最终进入模型的形式变了。
接着检查采样逻辑。训练时我是从长时间段的数据里随机采样,但线上流量集中在很短的时间窗口,存在明显的时间分布偏移。模型见过的样本分布和真实样本分布不同,效果自然打折。
最后检查特征与标签定义。服务端调用方理解的“类别”和训练用的“标签”存在细微差别,比如边界情况的归类不一致。修正统一口径之后,正确率明显回升。
这类问题的本质是“测试环境不等于生产环境”。处理方式就是建一套冒烟测试集——一组专门覆盖各种边界情况的样本,每次部署前后都跑一遍,输出一份报告。我从那以后坚持这个习惯,它至今帮我拦下了好几次线上回归事故。
4.3 数据泄漏:藏在“清洗”里的隐形杀手
数据泄漏是我觉得最隐蔽、最容易被忽略的一个问题。它的意思是:训练阶段模型“看到”了本不应该看到的未来信息或测试集信息,导致评估指标虚高。
我做的一个模型就翻过这个车:我用的特征里包含了一个“是否最近活跃”的字段,这个字段是通过全量数据计算得到的,但在真实预测时,它依赖于未来时间窗口的数据。也就是说,我的训练代码“偷看”了未来。评估时指标好看得离谱,但真实场景根本没有这个字段的可用版本,效果自然崩溃。
后来我建立了一套数据版本管理机制:每次生成训练特征,都记录“当前可见时间点”的截止时间;所有特征必须在推演时也能用相同逻辑从当前达到的数据计算出来。这一条被我写进了项目规范,之后再也没有出现过同类泄漏。
5. 最后还想说的几句实在话
从零开始学AI工程,最难的不是某个知识点,而是整个过程中持续不断的小挫败。你会遇到环境装不上、数据对不上、模型不收敛、服务一上线就崩等各种问题。但正是这些挫败,帮我把那些“看起来会”变成“真正会”。
我个人最推荐的一条路线:找一个你熟悉的业务场景,做一个端到端的小项目,从数据采集开始,一直到接口上线。整个过程不要依赖一键部署的封装平台,每一个环节都亲手做一遍。这个过程里你会踩到各种坑,但每个坑都会让你对这个领域的理解上一个台阶。
如果时间有限,优先花在数据和评估上。这两个环节是AI工程里最“脏”但也最决定成败的地方。模型结构可以换、超参数可以调,但如果数据和评估的口径不正,所有努力都会变成空中楼阁。
还有一个小技巧想分享:给自己的项目写一份“工程日志”,每踩一个坑就记录三样东西——现象、排查过程、最终原因。三个月后你会发现,这份日志的价值远超任何一本教材。这基本上就是“from scratch”最好的副产品:你不仅拿到了一个能用的系统,还收获了一套属于自己的问题排查方法论。