说实话,我第一次看到“ai-engineering-from-scratch”这个项目名时,第一反应是怀疑:从零开始搞AI工程,得先补多少年数学,刷多少套网课,才有资格说自己懂工程?但真正走完一遍以后,我的结论刚好相反。这个项目名里的“from scratch”,重点不是让你从微积分第一性原理重新推导,而是让你亲手把AI落地的整条链路趟一遍,把知识变成能用的系统。
我给自己定的这个项目,目的很明确:不靠低代码平台,不闭眼套开源全家桶,而是从数据集清洗、特征工程、模型训练、效果评估到服务部署,每一步都用自己手写的代码和脚本走通。如果你正准备入行、想转岗,或者已经会写Python但始终没跑通一条完整的AI链路,这篇内容整理了我完整的过程、技术选型、踩坑记录和沉淀下来的方法。有人问我为什么放着成熟方案不用,偏要自己折腾一遍,答案在后面。这和学车不一样,自己动手组装过一辆车,才知道哪些部件容易出问题、哪些地方不能省钱。
1. 先把“AI工程”这四个字拆开看
1.1 我理解的AI工程,和你以为的可能不是一回事
很多人一说到AI工程,下意识就想到训练模型、调Loss、刷榜。这些只是实验室视角。工程视角完全不同。你要操心的不是“这个模型在测试集上能不能再涨两个点”,而是“这个模型放进真实业务流程以后,能不能稳定跑三个月甚至三年”。
我这次从零开始做下来,越深入越觉得,AI工程本质上是一门不确定性管理。数据会变,今天用规则清洗出来的样本,明天可能因为上游业务调整全部失效;模型会退化,昨天AUC还有0.85,今天线上效果掉到0.81,但你说不清是哪一天开始掉的;依赖会碎,上周还能正常跑的代码,这周因为某个底层库升级,直接崩给你看。这些才是真正每天都在面对的“工程问题”。
所以我在这个项目里,刻意没有一上来就装一套大而全的AI平台,而是从最原始的Python裸环境起步,一步一步引入工具链。这个过程确实慢,但非常值,因为每引入一个工具,你都很清楚它在解决什么问题,而不是盲目相信“大家都说好,所以我也装”。
1.2 为什么从零开始,比直接上框架更重要
我见过太多人,包括以前的我自己,一上来就上了PyTorch Lightning、Transformers、MLflow全家桶套餐。表面上看训练代码写得很简洁,界面也漂亮,但只要出一次问题,整个人就懵了。因为你根本不清楚每一个抽象层背后到底发生了什么。
举个例子:用HuggingFace的Trainer训练BERT,几行代码就能跑起来。但如果Loss变成NaN,你知道应该先查学习率、再查数据里有没有异常值、还要确认混合精度下的loss scaling策略是否正确吗?如果不知道,那你只是在用框架,而不是在做AI工程。
我给自己定的三不依赖原则:
- 不依赖自动调参工具,前期几十个实验全部手动设置超参数
- 不依赖可视化平台,先用命令行加简单日志把流程跑通
- 不依赖现成示例代码,数据加载和训练循环自己亲手写
这套原则执行下来,速度确实慢,但我对训练、推理、评估和部署几个环节的理解,比之前刷十套教程都要扎实。AI工程不是会调用接口,而是出了问题你能定位、能修复、能避免它再次出现。
2. 从零起步必须打牢的四块地基
2.1 数学和编程,到底要补到什么程度
先拆掉一个劝退神话:不需要把《统计学习方法》从头翻烂才开始动手。AI工程最常用的数学知识集中在三块。线性代数,用来理解张量运算、矩阵乘法和维度变换;概率统计,用来理解分布、期望、方差和最大似然估计;微积分,用来理解梯度、反向传播和优化过程。
这三块学到“够用”就很好了。“够用”的具体标准是:看到公式能大致还原成代码逻辑,能解释超参数为什么这样设置。比如你看到交叉熵损失函数,要能说出它度量的是预测分布和真实分布之间的差异,并且知道它在训练初期梯度形式带来的实际影响。
编程方面,Python是绝对核心,但不用花大把时间去抠高级语法。你应该优先掌握的是NumPy的数组操作和广播机制、Pandas的数据筛选与分组聚合、PyTorch的Dataset、DataLoader、nn.Module、autograd,以及会用argparse配置超参数、会用logging记录训练日志。这些掌握了,日常AI工程开发里80%的代码场景都能直接应对。
2.2 先跑通一个模型,再回头补理论
纯理论驱动学习的最大问题是,你在学知识,但不知道这些知识到底对应什么实际问题。我特别推荐反着来:先用一个现成数据集跑通最简单的模型,再带着“为什么效果不好”“为什么收敛这么慢”的问题去补理论,印象会深得多。
这个项目中我选的第一个练手模型,是全连接网络做房价预测。代码量很少,但麻雀虽小五脏俱全,数据加载、特征归一化、模型定义、训练循环、验证评估、超参数调整全都要涉及。第一版跑完后效果很一般,但就在那时我突然理解了一个极其重要的概念:特征归一化对梯度下降速度的影响。不做归一化的时候,模型训练损失几乎不下降;做了归一化之后,几十个epoch就能看到明显收敛。这种体会,只看书真的得不到。
2.3 数据集三划分,别只盯着一个准确率
从零开始做AI工程,特别容易陷入一个误区:拿模型在测试集上的准确率当唯一的评价标准。实际上,工程场景里准确率只是及格线,你真正关心的是泛化能力和稳定性。
这个项目里,文本分类是主线,中间还穿插了结构化数据回归的小练习。做下来最大的收获,是要把数据集严格梳理成三部分:训练集用来更新参数,验证集用来调超参数,测试集只能在最终评估时碰一次。而且验证集的分布要尽量贴近线上真实场景,否则你调的模型很可能在自己的验证集上表现良好,一到实际使用就崩。
更要小心的是,划分时不能有信息泄露。比如做时间序列预测时,如果你把数据随机打乱,模型就会偷看“未来”,验证分数的参考价值直接归零。按行随机切分看似公平,但在很多业务场景里根本不成立,后面我会专门讲一次我踩的坑。
3. 技术栈和工具链,如何从零配出一套能用的环境
3.1 用Docker把依赖锁死在容器里,而不是污染本机
刚开始我完全不用Docker,直接在本地pip install了一堆包。结果大约一个月后,项目跑不起来了,因为某个库升级之后破坏了另一个库的依赖。从那以后,我所有AI项目都从Docker镜像开始。依赖环境如果锁不住,后面所有工作都是在流沙上盖房子。
一个最基础的镜像写法大概长这样:
FROM python:3.10-slim RUN pip install --no-cache-dir numpy pandas scikit-learn RUN pip install --no-cache-dir torch==2.1.0 --index-url https://download.pytorch.org/whl/cu118 RUN pip install --no-cache-dir fastapi uvicorn WORKDIR /app镜像里固定住核心依赖,代码通过挂载目录进容器。这样既保证可复现,又不影响本机环境。特别是涉及GPU训练的时候,Docker能把CUDA版本不一致带来的问题一次性框死在镜像里,换台机器也能稳定复现。
3.2 训练与调试的工程化习惯
很多人训练模型习惯直接打开一个Notebook,让单元格泡一晚上。探索阶段可以,但真正做项目,效率太低,也容易出错。我后来沉淀了一套比较简单的工程化习惯:
- 每个实验单独建一个文件夹,保存config.json、训练日志、模型权重和评估结果
- 用统一的命令行参数启动实验,方便后续批量跑参数扫描
- 训练脚本里加上自动保存最佳模型的逻辑,按验证集指标判断
- 所有随机种子固定,确保实验结果可以复现
听起来简单,但种子固定这个细节特别容易翻车。你要同时固定数据加载、模型初始化、PyTorch dataloader的随机种子,尤其是写成shuffle=True的时候不设种子,每次跑出来的结果都不一样,你根本没法判断一次改动到底有没有效果。固定好种子以后,哪怕只是微调了一层网络结构,也能通过对比日志明确看出变化方向。
3.3 显存不够、训练发散,先排查这几个参数
最实际的问题是显存不够。我踩过一个典型坑:batch size设太大导致显存溢出,我直接调小batch size,结果训练效果变差。查了半天发现,学习率没跟着调整。batch size变了之后,梯度估计的噪声特性也会变化,通常需要按比例调整学习率。
后来我总结了一套调试顺序:
- 先用很小的batch size把代码跑通,确认没有逻辑错误
- 逐步增大batch size,找到显存合理上限
- batch size固定后,按batch size的变化比例调整学习率
- 遇到显存溢出,优先试着减小输入尺寸或开启混合精度,而不是一味调小batch size
混合精度值得单独说一句。开启torch.cuda.amp后,训练速度提升明显,显存占用也能降不少。但新手很容易在这个环节遇到Loss变NaN。这不一定是你代码写错,可能是amp的梯度缩放策略不匹配。排查方法很简单,先关掉amp跑一轮,如果Loss恢复正常,问题基本就锁定在混合精度的策略配置上。
| 训练环境方案 | 优点 | 缺点 |
|---|---|---|
| 本机裸跑 | 上手快,无需额外学习 | 依赖污染严重,结果难复现 |
| Docker容器 | 环境隔离,依赖可复现 | 镜像和挂载需要学习成本 |
| 云GPU实例 | 算力弹性,按需付费 | 数据和代码传输需要额外管理 |
4. 第一个完整实战项目的设计与实现
4.1 项目选题:千万不要一上来就做对话系统
很多新手被聊天机器人吸引,觉得做出来在朋友圈很有面子。但从工程角度看,对话系统的链路太长了,意图识别、槽位填充、对话管理、知识库检索,随便哪一环都需要大量标注数据和专门的评估方案。没个几十万条对话数据,很难做出真正可用的效果。
我强烈建议第一个完整项目选一个结构化程度高、数据量适中的任务。比如文本分类、结构化数据的回归预测或者推荐排序里的一个子任务。这次项目我最终选的是多类别文本分类:把客户反馈自动分成几个预定义类别,训练数据是几百条公开的中文评论。数据规模不大,但整条AI应用链路是完整的。
4.2 数据清洗、模型训练和评估的完整链路
数据清洗阶段,我做了三件事。第一是去重,检查发现重复样本大约占5%,不处理的话,模型会对重复模式过拟合;第二是标准化,统一繁体字、去掉特殊符号、修正明显错别字;第三是标签平衡,有些类别样本占比特别少,我用了简单的过采样方式补充。
模型层面,我对比了TF-IDF加逻辑回归和BERT微调两条路线。BERT效果确实更好,但训练和推理成本高了一个数量级。最后的结论是:在数据量不大、又强调可解释性的场景里,传统机器学习方法完全不虚。工程选型不是越复杂越好,而是在效果、成本、可维护性之间找平衡点。
评估阶段的关键点在于,不能只看整体准确率。因为类别不平衡,一个把所有样本都预测成大多数类的模型,准确率可能也很高,但小类全错。所以我还逐类计算了Precision、Recall、F1,并画了混淆矩阵。训练脚本的核心循环结构大致长这样:
# 核心训练循环 model.train() optimizer.zero_grad() logits = model( batch["input_ids"], token_type_ids=batch["token_type_ids"], attention_mask=batch["attention_mask"] ) loss = criterion(logits, batch["labels"]) loss.backward() optimizer.step() lr_scheduler.step()这个循环写起来不难,但每一次拆解它,你都会更理解反向传播、优化器和学习率调度是如何协同工作的。
4.3 部署上线:从模型权重到线上API
模型训练结束后,工程链路下半场才刚刚开始。你要从“能训练”切换到“能服务”。我选用了FastAPI写一个最小的API服务,启动时加载训练好的模型和分词器,接收HTTP请求,返回分类结果和置信度。
部署时踩了一个很经典的坑:本地测试完全没问题,但一到容器里启动就报缺少依赖。查来查去,问题出在模型保存方式上。我保存了整个模型对象,这个对象里包含了自定义类,而新环境里没有这个类的定义。正确做法是保存模型的state_dict()即权重文件,配合模型定义代码来加载。权重、代码、依赖版本三者必须一一对应。
API写完后,我做了一次简单并发测试。用三个并发请求连续打100次,发现返回耗时的最大波动,从正常情况放大到了十倍。排查后发现问题出现在分词器的padding配置上——每次请求都会根据当前输入的长度动态调整padding,导致极大开销。固定padding参数后,延迟波动立刻消失。这类问题如果不做压测,只在单条请求上验证,是根本发现不了的。
5. 从零开始时最容易翻车的坑,我替你踩过了
5.1 CUDA版本错配与依赖地狱
这个坑几乎是每个AI工程师都躲不掉的。明确说,你本机装的CUDA版本和PyTorch包要求的CUDA版本,并不是一回事。PyTorch的whl包通常自带运行时,但某些编译过的c扩展会依赖系统级CUDA库,对不上就直接报undefined symbol或者加载失败。
解决思路很简单:不要依赖系统级全局CUDA安装,用Docker镜像把CUDA运行时版本锁死。安装任何包之前,先确认它支持的Python版本和PyTorch版本,不要盲目追求最新版。在AI工程里,“最新版”往往是最大的不确定性来源。用自己验证过的固定版本,比追赶新功能更重要。
5.2 验证集划分不合理,导致“假性能”
前面提过数据集划分,这里展开讲一个我亲身踩过的具体案例。第一版里,我把数据直接按行随机切分,验证集和测试集看起来都挺合理,模型效果也不错。但后来拿着模型去另一批真实数据上做测试,效果直接掉了一大截。
查了两天,才定位到原因:同一个用户可能提交了多条反馈,数据里有大量同源样本。我按行随机切分后,同一个用户的不同反馈会同时出现在训练集和验证集里。模型相当于提前见过这个用户的说话风格,验证分数当然好看。
解决办法是改成按用户ID分组切分,保证同一个用户的数据只出现在一个集合里。改完之后,验证结果立刻真实了很多。所以划分数据集时,一定要从业务角度分析信息的最小独立单位是什么,不能只看有没有重复行。
5.3 部署后的内存优化:模型量化不是牺牲精度换速度
部署环节绕不开模型量化。我之前对量化有误解,以为它纯粹是牺牲精度换速度。实际操作后我发现,在文本分类这类任务里,把模型从FP32降到INT8,准确率几乎没掉,推理速度和内存占用却能改善不少。
这个现象让我想明白了一件事:很多工程优化手段,本质上都在“能力冗余”里找空间。模型参数量大,并不意味着所有参数对最终输出都有同等重要的贡献。但量化也要看任务,如果类别边界本身就很模糊,或者对单条样本错误极其敏感,量化造成的精度损失可能就难以接受。我的建议永远是:用实验数据说话,不要凭感觉做决定。
| 优化手段 | 实施难度 | 收益 | 适用场景 |
|---|---|---|---|
| 混合精度训练 | 低 | 中 | 几乎全部训练场景 |
| 批量推理batch推理 | 低 | 高 | 离线批量处理任务 |
| 模型量化 | 中 | 高 | 在线低延迟服务 |
| 模型蒸馏 | 高 | 高 | 大模型压缩到小模型 |
6. 学习节奏、资源选型与心态管理
6.1 时间分配方式,决定了你能不能坚持下来
从零开始最怕的不是学不会,而是战线太长,中间彻底断掉。我的节奏是工作日每天强制两小时,周末集中半天。工作日用来做小步快跑,读几页书、改一段代码、跑一个小实验;周末半天处理一个大任务,比如部署一个API或做一轮完整数据清洗。
这两种节奏搭配起来,既保持连续性,又不至于长期高压。我还特别建议记录每个实验的实验日志:改了什么东西、效果变化是什么、原因推测是什么。不记录的话,过两周回头看自己写的代码,完全不记得当时的思路。日志写得越清楚,后来定位问题的速度越快。
6.2 资源选型:什么值得精读,什么只用于查阅
线上课程、书籍、博客多到根本看不完。我的原则是,精读的内容必须帮你理解核心机制,查阅类的内容只在遇到具体问题时打开。
值得精读的,一是《机器学习》周志华版,前几章反复读;二是《Deep Learning》花书,作为工具书常备;三是PyTorch官方教程,最直接也更新最快。只用于查阅的,包括HuggingFace文档,接口变化快;各种技术博客观点容易过时,不作为深度依据;Stack Overflow,遇到具体报错再去查。
还有一个我的个人原则:避免只收藏不实践。收藏夹里吃灰的教程,不如认真跑完一个官方demo。每学一个新技术点,我强制要求自己在现有项目上落地一个对应改动。学到的知识必须产出实际东西,这个原则帮我保持了每个技术点的真实理解,而不是“看着眼熟,不会手写”。
做完这个项目,我的代码量其实算不上多,但每一行都对应过一次踩坑或一次权衡。我个人最大的体会是,ai-engineering-from-scratch真正的门槛,既不在环境搭建,也不在数学推导,而在于你愿不愿意面对系统性的失败。模型不收敛、效果不及预期、部署后性能下降,这些才是AI工程师真正每天都在做的工作。如果你也想从零开始走这条路,务必不要把“模型跑通”当作终点,而要把它当作一个完整工程的开端。多去折腾数据、评估、部署这些不性感但绕不开的环节,你一定会比那些只刷模型的人走得更远。