☰
从零构建AI工程:数据、模型、部署全流程实战指南
2026/9/30 3:58:31 网站建设 项目流程

开头

把ai-engineering-from-scratch作为项目名挂在仓库里的时候,我心里很清楚:这不是又一场“三天速通机器学习”的热血尝试,而是一次把 AI 从“调库跑通”推向“能交付、能维护、能迭代”的系统工程。说白了,ai-engineering 这条路上,最难的不是看懂 Transformer 论文,也不是学会一行model.fit(),而是怎么在没人给你铺好路的情况下,把数据、模型、训练、部署、评测这一整条链路的每个环节都亲手打通。

这篇文章就写给那些想从零开始搞 AI 工程的人——不管你是刚转行的后端工程师,还是在校学生,或者已经在用现成 API 做应用、想往底层走一步的开发者。我会用一套我自己跑通过的路线,把整体思路、核心知识点、实操步骤和踩坑记录全部摊开来讲。内容不会绕弯子,也不会只贴理论,每个环节都尽量给出可以直接照着做的方案,以及我当初为什么这么选、换了别的方案会踩什么坑。

先摆个结论:从零开始做 AI 工程,真正的门槛不是数学,也不是显卡,而是“你能否独立把一个完整项目闭环跑完”。只要你能让一个模型从数据到线上服务全程不依赖别人,那 ai-engineering 的基本功就算立住了。

1. 整体路线设计与思路拆解

1.1 为什么这条路线必须“从零开始”

市面上的 AI 学习资料不是少,而是太多了。但大部分资料都假设你已经具备某些前置条件:要么默认你会 Linux 和 Docker,要么默认你数学底子扎实,要么直接甩给你一个 Kaggle notebook 让你跑完就算学会。真到自己动手做一个 ai-engineering 项目时,才会发现到处是窟窿——数据长什么样你不知道,模型怎么部署你不知道,线上推理慢了 300 毫秒你连从哪儿查起都不知道。

所以我才坚持“from scratch”的思路:每个环节都自己搭一遍,不跳步。这里的“从零”不是指连 Python 都要重新学,而是指对 AI 工程链路里每一个关键节点都有亲手实现过的经验。比如数据清洗,不是直接调pandas的dropna()完事,而是要想清楚缺失值出现在训练集和测试集分布不同时怎么办;比如模型部署,不是跑个uvicorn就结束,而是要考虑并发、超时、显存回收这些工程问题。

1.2 四个阶段的递进逻辑

我把整个学习路径拆成四个大阶段,每个阶段解决一类问题,并且上一阶段是下一阶段的必要前提,顺序不能乱。

第一阶段是基础能力建设,包括 Python 工程习惯、数据处理、数学直觉、深度学习框架的基本功。这个阶段的目标不是“学会 API”,而是建立对数据形状、张量运算、梯度传播的敏感度。

第二阶段是模型能力培养,从最简单的线性模型一路做到 Transformer。这个阶段的核心不是追 SOTA,而是亲手实现关键组件、亲手训练并观察训练曲线,建立“模型为什么会这样”的判断力。

第三阶段是工程化改造,把训练好的模型变成系统的一部分。这涉及模型序列化、推理服务封装、性能优化、依赖管理、评测回归,是从“跑通 notebook”到“交付服务”的分水岭。

第四阶段是完整项目闭环,把前三个阶段串成一个端到端项目,从数据采集到上线监控全部自己搞定。这个阶段最接近真实工作中的 ai-engineering 任务,也是我收获最大的一段。

这条路线和人云亦云的“三个月成为 AI 工程师”最大的区别是:它不追求速度,追求每个环节的“可解释性”。比如你训练完模型后,能不能说清楚 loss 曲线为什么是这样、学习率为什么从 1e-3 开始、为什么选择这个损失函数。如果这些问题答不上来,那说明这个环节还没吃透,往后硬走大概率会翻车。

2. 基础能力建设:四门必修课

2.1 Python 工程能力:不只是写脚本

AI 工程对 Python 的要求比一般开发岗位要高一个层级。它不是写几个函数跑通就完了,而是要写出结构清晰、可复用、可测试的代码。我自己在重构一个早先的实验脚本时,最大的感受是:训练代码里如果到处是全局变量、魔法数字和顺序执行的逻辑,那后面做实验对比时你会痛苦到想弃坑。

建议从一开始就养成工程习惯:使用dataclass或pydantic定义配置对象,用argparse或配置文件管理超参数,把数据处理、模型定义、训练循环、评估函数拆分成独立模块。配置项不要散落在代码里,比如learning_rate = 0.001这种,必须收拢到统一配置中,否则跑过几十组实验后,你根本记不清哪组结果对应哪组参数。

另外一定要上手pytest。至少给数据预处理函数和评估指标函数写好单测。我这边的经验是:数据预处理是最容易出错且出错后最致命的地方,比如标签错位、特征顺序被 shuffle、归一化参数混用训练集和测试集统计量。这类 bug 用单测能挡掉大半。

2.2 数学直觉:用到哪补到哪

很多初学者卡在数学上,一看到矩阵求导就头大。但真实做 ai-engineering 并不需要你会手工推导所有公式,你更多需要的是“直觉级理解”。我给自己定的标准是:看到公式能知道它大概在算什么、结果当大当小、参数变化会影响什么。

举例来说,交叉熵损失函数不需要你背展开公式,但你要理解它为什么鼓励模型输出高置信度的正确类别;反向传播不需要你推导链式法则的每个中间项,但你要清楚梯度是 loss 对每个参数的敏感度,学习率乘上梯度就是参数更新的步长。有了这些直觉,你在调参时就不会像无头苍蝇一样乱试。

我实际用到数学最多的场景是处理数据维度:做 batch 运算、处理序列 padding、矩阵乘法形状不匹配。建议把 NumPy 和 PyTorch 的张量操作练熟,理解 broadcasting 规则,这会比刷一堆公式有用得多。我的经验是:先动手写代码,写不下去再回头补数学,效率远高于先把数学书啃完再动手。

2.3 选择一个主力深度学习框架

框架选择本身也是一次工程决策。我在对比 TensorFlow 和 PyTorch 后,最终选了 PyTorch,原因有三:一是动态计算图对调试友好的特性,让新手能直观地查看每个张量的形状和值;二是生态里的模型库、工具链几乎都以 PyTorch 优先;三是社区活跃度高,遇到问题搜解决方案的成本低。

不过我建议你在学习时不要“只认”一个框架,而是把框架当工具而不是信仰。比如我现在部署模型时经常导出为 ONNX 格式,再用 ONNX Runtime 跑推理,这时候框架差异已经不重要了,模型本身才是核心资产。

PyTorch 的学习路线我推荐从三个层次走:第一步,用torch.Tensor做各种张量运算,掌握自动求导机制;第二步,用nn.Module搭建模型并走通训练循环;第三步,用DataLoader、optimizer、scheduler等组件搭建完整的训练配置。这个过程大概需要两周左右的业余时间,但打下的基础会一直复用。

2.4 开发环境与依赖管理

环境问题是 ai-engineering 里最容易被低估的坑。Python 的包依赖版本会让你崩溃:昨天能跑的代码,今天装了个新库就不行了。我的习惯是每个项目用虚拟环境隔离,并锁定主要依赖的版本号。训练相关的核心依赖我会明确固定大版本,比如torch>=2.0,<2.2,因为 CUDA 版本和 PyTorch 版本是强耦合的,随便升个小版本都可能遇到算子不兼容的问题。

GPU 环境方面,我的建议是先搞清楚自己的硬件和驱动再装框架。nvidia-smi看一下支持的 CUDA 版本,然后按这个版本选 PyTorch 的安装命令。别一上来就无脑装最新版,最新的不一定最稳。没有 GPU 的时候也别慌,先用 CPU 跑小模型调通流程——我用 CPU 跑了整整两个多月的实验,模型照样能收敛,只是慢一些,这反而逼着我更注重代码效率和提前确认数据质量。

3. 模型能力培养:从经典架构到 Transformer

3.1 经典模型必须先亲手跑通

我见过很多一上来就啃 Transformer 代码的初学者,结果看了一个月还是云里雾里。原因在于直接一步到位会跳过太多基础概念。正确的姿势是先把金字塔底座打好:线性回归、多层感知机、卷积神经网络、循环神经网络,这些模型虽然“老”,但它们是理解现代架构的词汇表。

以我个人的实验顺序为例:先用 MLP 在 MNIST 上做分类,观察 loss 下降过程、学习率对收敛速度的影响;然后换成 CNN 在同样数据集上跑,对比参数效率和准确率差异;再上 RNN 或 LSTM 做文本分类,体验序列数据处理的细节,比如pad_sequence、pack_padded_sequence这些实际操作。每个实验都亲手写训练循环、画 loss 曲线,至少跑完一个能说服自己的结果再进入下一项。

这一步的关键不是做出 SOTA 成绩,而是积累“训练时的体感”。比如你会发现 BN 层能显著加速收敛、LSTM 对长序列有梯度问题、CNN 在图像任务上就是比 MLP 参数少效果好。这些体感在后面调优真实项目时是决定性优势。

3.2 Transformer 核心组件一定要吃透

到了 Transformer 这一步,很多人又陷入“懂概念但写不出代码”的困境。我的经验是:不要只看别人的封装,要手写一个能跑的最小实现。哪怕性能差一些、代码丑一些,但当你亲手写出注意力头,把Q、K、V的维度算清楚,把 mask 的作用搞明白,你就真正把这块硬骨头啃下来了。

我在自己实现时踩过一个很经典的坑:注意力分数的缩放因子sqrt(d_k)没除干净,结果训练时 loss 直接起飞。这个细节在论文里只有一行公式,但只有自己动手实现才会真正理解它的意义。代码实现之外,还要理解位置编码和 mask 的作用机制。位置编码负责给序列注入顺序信息,所以你直接操作输入 embedding 时必须额外加上它,这一点是 Transformer 和 RNN 最本质的区别。

3.3 训练与调参的实操要点

模型训练有太多变量,但真正的关键点集中在以下几个方面。学习率是重中之重,我通常会从1e-3或1e-4起步,观察前面几百步的 loss 曲线,如果 loss 震荡剧烈就降到3e-4,如果下降太慢就适度提高。梯度裁剪在训练大模型或 RNN 时基本是标配,我一般把 max norm 设置为 1.0。

另一个很容易被忽略但影响极大的细节是随机种子固定。数据 shuffle、模型初始化、dropout 都会引入随机性,如果 seed 不固定,同一套代码两次训练结果可能差异很大,会严重影响实验对比的可靠性。我在所有训练脚本里都会固定 Python 的random.seed、NumPy 的np.random.seed和 PyTorch 的torch.manual_seed。另外 PyTorch 里还要设置torch.backends.cudnn.deterministic = True和torch.backends.cudnn.benchmark = False,不然 GPU 上的卷积操作仍然会有随机性。

关于 batch size,它也直接影响模型收敛效果,我经历下来的规律是:batch size 越大,梯度更新越稳定,但泛化性能不一定更好,而且显存压力大;batch size 太小,loss 曲线会很吵。所以在显存允许的情况下,我一般从 16 或 32 起步,逐步观察训练稳定性再调整。

4. 工程化落地:让模型真正变成系统的一部分

4.1 数据管线的工程化设计

模型训练中最容易翻车的环节不是模型结构,而是数据。我把数据管线的工程化总结为三个核心原则:可复现、可校验、可追溯。

可复现意味着数据处理逻辑是确定性的,同一份原始数据走同一个脚本必须得到同一份训练集;可校验意味着每个处理步骤都有输出检查,比如类别分布是否符合预期、文本长度有没有异常;可追溯意味着任何一份训练数据都能回溯到它的原始来源和处理版本。

我在实操中会维护一份数据集版本清单,包含原始数据路径、清洗脚本版本、预处理参数、输出文件的哈希值。这样即使后来发现数据有问题,也能准确定位是哪个环节出的问题。另外,训练集和验证集的划分一定要在预处理之前完成,避免数据泄漏。这里有个很常见的错误:先对整个数据集做标准化,再切分训练集和验证集,导致验证集的信息提前泄露进训练过程。正确的做法是先划分,再分别计算均值和方差。

4.2 模型序列化与推理服务化

训练结束只算完成了 40% 的工作,真正让模型产生价值的是把它部署成可以对外提供服务的接口。我常用的方案是用 FastAPI 封装一个 REST API,通过 POST 请求接收输入数据,内部完成预处理、推理、后处理,最后返回结构化结果。

部署时最关键的工程决策是模型格式。我推荐在正式上线时优先考虑 ONNX,因为 ONNX Runtime 的推理性能通常优于直接用 PyTorch 的 eager 模式,而且部署环境更轻,不依赖完整的 PyTorch 包。转换过程很简单,关键是把模型的输入输出维度固定下来,用torch.onnx.export时指定input_names和output_names,再验证一下导出的模型和原模型输出差异是否在可接受范围内。

推理服务的性能优化方面,我摸索出的经验是:尽量用同步接口配合批量推理,单条请求延迟高但吞吐可观;对于高并发场景,服务内维护一个线程池,模型预热到显存后再开始接受请求。显存不足导致的 OOM 在处理大模型时尤其棘手,我会用显存监控脚本跟踪推理服务的占用,设置CUDA_VISIBLE_DEVICES隔离环境,在推理请求集中时做好显存回收。

4.3 评测、监控与持续集成

模型上线后并非万事大吉。真实业务场景下,线上数据分布会漂移,模型效果会退化,所以我习惯从上线第一天就建立监控体系。最简单的方案是记录每个请求的输入特征和预测结果,周期性评估预测置信度分布、类别分布和响应延迟。一旦发现异常,比如置信度普遍降低或某类结果占比突变,就要及时告警。

评测环节要特别注意离线评测和线上效果的差异。我在一个实际项目里就遇到过离线 AUC 很高但线上效果很差的情况,后来排查发现是线上请求的文本长度分布和训练集差异太大,模型面对长文本时的泛化能力不足。所以评测数据集不能只看平均分,还要按长度分段、按场景拆分来看效果。另外,新模型上线前我会准备一个影子评测流程,把新模型的预测结果和线上模型的结果同时对跑一段时间,比较差异后再决定是否全量切换。

5. 端到端实操:一个从头构建的文本分类项目

5.1 项目选型与技术方案

学习 ai-engineering 最忌讳的就是只学不练,所以我建议你挑一个“麻雀虽小五脏俱全”的项目来闭环。我自己的练手项目是用新闻标题做情感分类,数据规模大概 5 万条,标签分为正面、负面、中性三类。之所以选这个题目,是因为它同时覆盖了典型的 NLP 处理流程,又不至于需要大规模算力。

技术方案我最终定为:使用预训练的 BERT 中文模型做文本表示,在顶部加一层分类头,用 PyTorch 训练,导出 ONNX 后通过 FastAPI 部署。这个组合既能覆盖微调预训练模型的完整流程,又能体现工程化的部署细节,对从零起步的人来说是性价比很高的选择。

5.2 从数据清洗到训练完成的完整流程

数据清洗阶段,我先做了这几件事:去除 HTML 标签和异常字符、统一全半角符号、过滤过短文本、检查标签均衡性。这些看起来琐碎,但对模型效果影响极大,尤其注意过滤空文本和重复样本,否则数据泄漏会严重虚高验证集分数。

预处理完成后,我按 8:1:1 划分训练集、验证集、测试集。这里必须强调:划分应当在任何统计特征计算之前完成,避免信息泄漏。分词方面,我直接用 BERT 自带的 tokenizer,它按字切分并自动加上[CLS]和[SEP]标记,省去了额外维护分词词典的负担。

模型部分,我没有从零训练 BERT,而是加载了预训练权重做微调。训练配置我用了一个相对保守的设置:序列最大长度 128,batch size 32,学习率 2e-5,训练 3 个 epoch。这个配置在多数文本分类任务上都能拿到不错的结果,特别是学习率,微调预训练模型时如果设太大(超过 5e-5)很容易破坏预训练学到的知识。

训练完成后,我在测试集上做了最终评估,并记录了每个类别的精确率、召回率和 F1 分数。除了总体指标,我还专门对短标题和长标题分段评测,因为这种细节能提前暴露线上泛化问题。

5.3 部署与接口测试的现场记录

部署阶段,我把模型导出为 ONNX 格式,再封装 FastAPI 服务。核心的接口逻辑很简单,但有几个关键细节我吃了亏:第一,预处理逻辑必须和训练时完全一致,包括 tokenizer 的参数、截断长度、归一化方式,这个一致性靠人工保证容易出错,所以我直接把预处理函数放到部署代码里复用,并且写了一个对比测试,确保线上推理结果和训练时输出一致;第二,接口返回的预测结果要包含置信度,方便上游系统做兜底;第三,服务启动时先做一次 dummy 推理,触发模型加载和显存分配,避免第一个真实请求延迟过高。

部署完成后,我用locust做了简单的压测,观察 QPS 和延迟。在原生的 CPU 部署下,单条请求的 P99 延迟大概是 80 毫秒,完全够用。如果后续并发上来了,再考虑用 GPU 推理或模型蒸馏。

5.4 一个可复现的配置参考

下面是我在这个项目里用到的关键配置,你可以直接作为初始参数参考。但请记住,没有一组参数是万能的,这些数字的意义在于给了你一个合理的起点。

配置项值备注
模型bert-base-chinese预训练中文模型
序列最大长度128超过部分截断
batch size32显存不够时降到 16
学习率2e-5BERT 微调常用范围
优化器AdamW配合 weight decay
weight decay0.01防止过拟合
训练轮次3观察验证集 F1 是否上升
随机种子42保证实验可复现

6. 常见问题与排查技巧实录

6.1 训练不收敛或 loss 异常

这个现象大多发生在刚把代码写完的阶段。排查顺序我总结成一条链:先看数据是否正确,再看模型输出是否合理,最后才怀疑优化器配置。数据方面最常见的坑是标签错位,导致模型学到的完全是噪音;模型输出方面,如果 loss 初始值就对不上,比如二分类问题初始 loss 应该接近log(2),如果差得很远,那模型或数据多半有问题。另外,输入数据没有归一化到合理范围也会导致梯度异常。

loss 变成 NaN 的时候不要慌,按这个顺序排查:学习率是否过大、是否存在除零(特别是注意力权重或归一化层)、是否加载了损坏的预训练权重。我用 PyTorch 时会给训练循环加上torch.isnan(loss)的检查,一旦捕获就保存当前参数快照并终止训练,方便后续调试。

6.2 过拟合与数据泄漏

模型在训练集上表现好、验证集上表现差,这就是过拟合。常见解法有多增加正则化、数据增强、降低模型容量、早停。但我实际中见过最多的情况是“伪过拟合”——看似是过拟合,其实是数据泄漏。比如做图像分类前对整张图做了归一化,这个操作本身没泄漏,但如果你先用整批数据算好了均值方差再做划分,就泄漏了。

数据泄漏的坑极其隐蔽。我在一个处理表格数据的项目里,直接用fillna填充了整个数据集的缺失值后再切分训练集和验证集,导致验证分数虚高,上线后被泼了一盆冷水。正确做法是:所有从数据中学习到的统计量,如均值、方差、缺失值填充值,都必须在训练集上计算完成后再应用到验证集和测试集。

6.3 随机种子与实验复现性

在调节超参数时,如果每次结果起伏巨大,大概率是随机种子没控制好。这个问题在 GPU 环境下特别严重,因为 cuDNN 的算法选择本身是有随机性的。固定随机种子的方案我在前面已经列过代码,但还有一个小细节:DataLoader的shuffle=True也会引入随机性,需要给它的generator也传入同一个随机种子。

复现性问题还有一个隐性来源是浮点累加误差。分散在不同批次上的顺序不同,梯度的浮点累加结果会有细微差异,所以即使种子相同,在不同 GPU 硬件上也可能得到略有不同的结果。对于实验对比来说,只要差异在一个较小的范围内,一般不影响结论判断。

6.4 显存不足与推理延迟优化

OOM 是最常见的工程问题。朴素的解法是调小 batch size,但如果你想在不大改代码的情况下做出更多优化,可以试试torch.cuda.amp混合精度训练,直接把显存占用压缩到原来的 60% 左右,而且在大部分任务上精度几乎没有损失。梯度累积配合小 batch size 也是常用手段,可以让有效 batch size 保持在一个较大值的同时降低显存峰值。

推理延迟方面,我实测下来 ONNX Runtime 比 PyTorch eager 模式快不少。另一个容易被忽略的优化是:预处理不要写在推理函数内部。把文本转 token ID 的操作放进请求解析阶段,避免推理线程反复执行转换逻辑。同时,模型的参数加载后要放到内存中复用,不要在每次请求时重新加载。

写在最后的一点私人体会

走完这条 from scratch 的路,我最大的感触是:ai-engineering 的能力不是“学”出来的,是“修”出来的。每解决一个训练不收敛的问题、每定位一次线上推理变慢的原因、每修复一个数据泄漏的隐患,你对整个系统的理解就加深一层。那些看似最不起眼的细节,比如种子设置、预处理顺序、配置文件管理,恰恰是区分业余玩具和可交付工程的分水岭。

如果你正打算开始自己的 ai-engineering 之路,我给的建议只有一条:不要贪多,不要跳步,用一个小项目把全链路趟一遍。哪怕最终模型精度只是“还行”,服务也只是单机部署,这整个过程带来的经验密度也远高于刷十份教程。第一步往往最难,但从写好第一个数据类、跑通第一个训练循环开始,后面的一切都会顺理成章地展开。

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

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

立即咨询