1. 从零开始做AI工程,先搞清楚这件事到底是什么
我最早看到“ai-engineering-from-scratch”这个项目名时,第一反应是:又一个把教程堆进 GitHub 的仓库。但真正打开内容、跟着跑了一遍之后,我发现它想解决的并不是“怎么调一个 API”,而是更底层的问题——一个没有 AI 背景的工程师,如何系统性地建立起 AI 工程的能力闭环。这里的“从零开始”不是指从 Python 语法开始,而是指从“知道 AI 项目长什么样”到“能独立交付一个可用的 AI 功能”的完整路径。
这个项目适合谁?我觉得最典型的是三类人。第一类是后端或前端工程师,已经被业务方反复问过“能不能加一个智能客服”“能不能做个图片识别”,但自己心里没底;第二类是刚转行做算法、却发现自己只会跑通 Notebook 的数据分析师,模型在 Jupyter 里很漂亮,一上生产就崩;第三类是技术管理者,想评估团队做 AI 工程化需要哪些能力、哪些环节最容易翻车。
我自己跑完整个项目后最大的感受是:AI 工程和传统软件工程最大的区别,不在于模型有多复杂,而在于不确定性从哪儿来。传统代码的逻辑是确定的,输入输出可预期;而 AI 系统的行为由数据分布驱动,你无法靠读代码来预测它在真实数据上会怎样表现。这导致整个开发流程、测试方法、部署策略、监控体系全都得变。这个项目恰恰就是沿着这条主线展开的,所以它的价值不是教你几个模型,而是帮你建立一套应对不确定性的工程习惯。
2. 内容架构拆解:为什么它按“数据-模型-部署-运维”四段式来组织
2.1 四段式结构的逻辑关系
打开仓库目录,会发现它没有按模型类型(CNN、RNN、Transformer)来组织,而是按工程链路分成四个大块:数据工程、模型训练与评估、部署与推理优化、监控与迭代。这个设计非常关键,因为初学者最常见的误区就是把 AI 工程等同于“训练模型”,把超过 80% 的精力花在调参上,最后发现数据质量不行、上线后推理延迟太高、模型漂移没人管,整个项目根本落不了地。
数据工程放在第一位是有道理的。机器学习圈有一句老话叫“Garbage in, garbage out”,但在实际项目里,数据问题往往要到模型上线后才会暴露。比如你做一个文本分类系统,训练集里 A 类样本占了 90%,B 类只有 5%,模型看似准确率很高,实际上只是学会了“永远猜 A”。这类问题在离线评估阶段可以用混淆矩阵发现,但很多新手根本不看。项目把数据环节单独拎出来,就是要强迫你先把数据分布、标签质量、采样策略想清楚,再碰模型。
模型训练与评估这一块,项目刻意没有追求 SOTA。它用了几个非常经典的模型作为载体,比如 TextCNN、LSTM、BERT 的简化版本,然后花了大量篇幅讲评估指标怎么选。为什么要这样?因为工程落地的核心不是模型越新越好,而是模型的能力边界是否匹配业务需求。你需要知道准确率在什么场景下会骗人,精确率和召回率的业务含义分别是什么,A/B 测试怎么做,这些才是工程决策的基础。
部署与推理优化是好多自学者的盲区。Notebook 里跑通模型只完成了 20% 的工作,剩下 80% 是让模型在真实环境和真实流量下稳定工作。项目从模型序列化、推理服务封装、批量预测与实时预测的选择,讲到量化、剪枝、批处理这些优化手段,最后落到 GPU 与 CPU 的选型权衡。这部分内容特别适合后端工程师阅读,因为它把“模型变成服务”这件事讲得非常工程化。
监控与迭代往往是企业里 AI 项目失败的头号原因。模型上线不是终点,而是开始。数据分布会变、用户行为会变、业务目标会变,模型的表现随之下降。项目里讲了如何记录推理日志、如何计算数据漂移指标、如何设计模型版本更新机制,甚至讨论了什么时候该重新训练、什么时候该回滚。这一块内容在其他教程里很少见,但恰恰是生产环境最需要的能力。
2.2 为什么项目用“场景驱动”而不是“算法驱动”来教学
还有一个值得注意的设计:项目里的每个知识点都挂在一个具体业务场景下。比如讲数据增强,它挂在“电商评论情感分析”的情境里;讲模型量化,它挂在“移动端实时翻译”的情境里。这种安排比“算法驱动”的教学方式更贴近真实工作。因为在实际工作中,你永远是从问题出发找方案,而不是从方案出发找问题。
举个例子,做客服工单自动分类,你首先要想清楚工单有多少个类别、各类别样本量是否均衡、用户表述和标准标签之间的差异有多大、线上请求的延迟上限是多少。这些问题的答案直接决定了你用词向量还是用预训练模型、用单分类器还是用层级分类、用 CPU 部署还是 GPU 部署。场景驱动的方式逼着你先做工程分析,而不是上来就 torch 一行 import。
3. 实操过程全记录:跟着项目跑通一个最小可用的分类系统
3.1 Step 1:先搞定数据管道,别急着训练
任何 AI 项目的第一个坑都不是模型,而是数据获取和数据清洗。项目里用了一个非常经典的公开数据集——IMDB 电影评论情感分类,英文语料、二分类、规模适中,非常适合做端到端演练。我按照它的指引,先写了一个数据加载模块,把原始文本转换成模型可用的张量。
第一步是文本清洗。IMDB 原始评论里有 HTML 标签、多余的标点、大小写混用。项目提供了示例代码,但我强烈建议你自己动手写一遍清洗逻辑,哪怕只是简单的正则替换,这样才能体会到“干净的语料”对后续步骤的影响有多大。我在实践中会额外做两步:把所有换行符统一成空格,把连续重复的标点合并。这些细节不处理,分词结果会出现大量无意义的 token,拉高词汇表大小,还拖慢训练。
第二步是构建词表。这里有个容易踩坑的参数叫max_vocab_size,它控制词表的最大容量。设太小会丢失低频词的信息,设太大内存吃不消,而且会产生大量只出现一两次的噪声词。项目里默认设成了 50000,我在复现时觉得这个值偏保守,实际用 30000 到 40000 就能覆盖 95% 以上的 token。怎么判断?统计一下训练集里不同词的累计频率覆盖曲线,选拐点处的值最合适。
第三步是构建 DataLoader,关键参数是batch_size和max_len。max_len决定每条评论截断或补齐到多长。评论长度分布不均,有的只有十几个词,有的上千词,但绝大多数集中在 200 词以内。我按项目建议设成 256,在准确率和速度之间取平衡。如果设成 512,训练时间会翻倍,但准确率提升不到 0.5%,不划算。batch_size则受显存限制,我用单张 12GB 显存的卡,batch 32 到 64 比较稳,再大就容易 OOM。
3.2 Step 2:训练一个简单模型,建立基准线
项目在这一步让你训练一个相对简单的 TextCNN 模型,而不是直接上 BERT。这个安排非常明智。TextCNN 结构简单、训练速度快、收敛稳定,非常适合作为 baseline。真正的工程实践也是这个思路:先用一个简单模型跑通整套链路,建立基准线,再逐步升级模型。
TextCNN 的原理一句话就能讲清楚:把词向量矩阵当成一个“图像”,用多个不同尺寸的卷积核去提取 n-gram 特征,然后池化、拼接、过全连接层分类。它的关键超参数有三个:卷积核大小、卷积核数量、词向量维度。项目里用的配置是filter_sizes = [3,4,5],这表示同时抓取 3 元组、4 元组、5 元组的局部特征,类似在看评论时同时关注相邻三个词、四个词、五个词的语义单元。这种多尺度特征组合非常适合短文本分类。
训练过程中我重点观察了两个指标:训练损失和验证损失。如果两者同步下降,说明模型在学习,没问题;如果训练损失下降但验证损失升高,说明过拟合了,需要加 dropout 或降低模型容量。TextCNN 在这个数据集上的收敛速度很快,大概 5 到 10 个 epoch 就能到 85% 左右的验证准确率,再往后提升就非常缓慢了,继续训练反而有过拟合风险。
我在复现时加了一个小小的修改:把嵌入层的freeze属性设为 False。默认情况下,预训练词向量可以冻结,也可以参与训练。冻结的话训练更快,但词向量无法根据任务微调;不冻结效果略好,但训练时间增加。对于 IMDB 这种中等规模数据集,我建议开启动态更新,效果提升明显,代价完全可以接受。
3.3 Step 3:用 BERT 简化版做升级,感受模型容量带来的差异
跑完 TextCNN 后,项目引入了基于 Transformer 的模型,但不是完整 BERT,而是做了裁剪的迷你版本,隐藏层只有 128 维,层数只有 2 层。这是一个非常聪明的教学选择:如果直接上完整 BERT,新手很容易被巨大的参数量和训练开销吓退,而且难以在个人电脑上完成实验。裁剪版既保留了 Transformer 的核心机制——自注意力、位置编码、残差连接,又能让入门者直观感受到“注意力机制到底在干什么”。
我把 TextCNN 和这个 Mini-BERT 的测试结果做了对比:Mini-BERT 的验证准确率比 TextCNN 高 2 到 3 个百分点,但训练时间大概是TextCNN 的 4 到 5 倍。这个对比非常有教育意义,它让读者明白:模型容量的提升确实能带来精度收益,但收益不是线性增长的,而是边际递减的。在实际业务中,你需要根据延迟预算和硬件成本来权衡到底用复杂模型还是简单模型,而不是一味追求精度指标。
学习 Transformer 时,我强烈建议花点时间手动推导一下 attention 的计算过程。自注意力的本质是对序列中每个 token,根据它与所有其他 token 的相似度重新加权聚合信息。你不需要从头实现,但要理解 Q、K、V 三个矩阵的维度变化,以及 mask 的作用。项目里没有展开讲数学公式,而是用示意图和伪代码解释了整个流程,适合没有数学背景的人入门。我自己还做了一个小实验:把注意力权重视觉化,看模型在分类一条正面评论时重点关注了哪些词。结果发现它确实会更关注“excellent”“amazing”这类带有强烈情感色彩的词汇,这是非常直观、非常有趣的体验。
3.4 Step 4:把模型封装成推理服务,迈出生产的第一步
训练完模型的终点是把它变成能被业务调用的服务。项目在这一步提供了两种思路:一种是离线批量预测,比如每天对全量数据进行一次分类,结果写入数据库;另一种是实时推理,通过 HTTP 接口对外提供服务。两种思路的适用场景完全不同。离线批量适合时效性要求低、数据量大的场景,如日报自动归类;实时推理适合延迟要求高的场景,如在线客服机器人。
在实际操作中,我用 FastAPI 封装了一个最小可用的推理服务。关键步骤有三步。第一步是把训练好的模型权重用torch.save保存为.pt文件,同时把词表保存为 JSON。第二步是在服务启动时加载模型和词表,并调用model.eval()切换为推理模式。这一步特别容易被忽略,不切 eval 模式的话,Dropout 仍会生效,导致每次预测结果随机波动。第三步是处理输入文本的预处理逻辑。注意,服务端必须和训练时用同一套预处理流程,否则训练和推理的数据分布不一致,预测结果会非常离谱。
推理延迟是个在开发环境里很容易被忽视的问题。我在本地用 CPU 测试 Mini-BERT 的单条推理延迟,大概是 30 到 50 毫秒,看起来很快。但如果把并发拉到 100,CPU 直接被打满,延迟飙升到几百毫秒。这就是为什么真实系统中要做推理优化。项目里示范了最简单的优化方法——动态批处理,把高并发的请求攒起来,凑够一定数量再进模型,显著提高了吞吐量。另外还可以用 ONNX Runtime 做模型转换加速。我在一个文本分类任务上试过,ONNX 导出后的推理速度比 PyTorch 原生提高 1.5 到 2 倍,而且内存占用更低,几乎没有精度损失。
3.5 Step 5:建立监控体系,发现“模型悄悄变笨”的时刻
模型上线后并不代表万事大吉。几个月后,你可能发现分类准确率不如从前了,但原因是多方面的:用户表达方式变了、业务规则变了、数据分布漂移了。如果没有监控,你根本无法定位问题。项目里实现的监控方案包括两部分:统计监控和性能监控。
统计监控关注输入数据的分布变化。具体做法是记录线上请求中文本的长度分布、词汇覆盖率、类别置信度分布等指标,定期和训练集指标做对比。如果发现线上数据的平均句子长度从 120 词变成 80 词,或者出现了大量训练集里没见过的生僻词,就要警惕分布漂移了。性能监控则关注模型预测结果的业务指标,比如准确率、召回率。但生产环境中常常没有实时标注好的真值,一个常用的替代方案是人工抽检或滞后标注。先把预测结果存储下来,两三天后由人工或下游系统给出真实标签,再离线计算准确率。
我的实操体会是:监控体系的价值不在“出问题时报警”,而在“出现缓慢劣化时给你留出干预时间”。很多模型退化过程非常缓慢,不设置监控阈值,系统表现依然能看,但业务方会慢慢发现推荐不够精准了、审核不够严格了。越早发现,处理成本越低。所以我在实际项目里不仅仅做指标监控,还会保留两个东西:一个是模型版本号,每次上线都记录版本、时间、训练数据范围;另一个是回滚脚本,一旦新模型表现异常,能在分钟级内切回旧版本。这些细节项目里都有提到,但确实容易被赶工期的人忽略。
4. 工具选型解析:为什么是 PyTorch + FastAPI + MLflow
4.1 框架选择的工程逻辑
项目主代码用 PyTorch 实现,这是目前 AI 工程领域的主流选择之一。和有静态图限制的老牌框架相比,PyTorch 的动态图机制更容易调试,也更容易实现复杂的模型结构。对于工程团队来说,生态成熟度更重要。PyTorch Hub、TorchScript、TorchServe 这些周边工具,让模型从训练到部署的整个生命周期都有成熟的解决方案。另外,社区里有大量预训练模型和示例代码,遇到问题基本都能搜到方案,这对工程项目的推进速度至关重要。
推理服务选择 FastAPI 而不是 Flask,原因很直接:FastAPI 天然支持异步、自动生成 OpenAPI 文档、内置数据校验。对 AI 服务来说,异步支持特别重要,因为模型推理是 CPU/GPU 密集操作,异步可以避免服务被长时间占用。文档自动生成能让前后端联调效率大幅提升。我实测过,同样的接口逻辑,FastAPI 的代码量约为 Flask 的一半,而且性能更好。
对于实验管理和模型版本控制,项目引入了 MLflow。它能记录每次训练的指标、参数、模型产物和代码状态,相当于给实验打上了可追溯的标签。在真实团队协作中,MLflow 几乎是必需品,因为你会发现“两星期前的那个 88% 准确率的模型”到底用的什么参数、什么数据,完全无法从记忆里提取,只能依靠系统记录。
4.2 本地开发和云上部署的资源权衡
很多人问我:学这个项目需要什么样的电脑配置?我的回答是:纯学习完全可以用 CPU。项目里的 TextCNN 和 Mini-BERT 都是裁剪版,在 CPU 上训练几分钟到十几分钟就能完成。但如果你想把 Mini-BERT 扩展到完整 BERT,那就需要 GPU 了。不需要自己去买,可以用云 GPU 按小时租用,训练完关掉即可,成本很低。学习阶段最重要的是把流程跑通,而不是堆硬件。
部署方面,项目建议用 Docker。我这里补充一个经验:容器化不仅仅是部署手段,也是环境复现的保障。AI 项目的依赖版本极其敏感,numpy从 1.x 升到 2.x、torch从 1.13 升到 2.3,都可能导致结果变动。Docker 镜像把 Python 版本、CUDA 版本、依赖库版本全部锁定,能避免“在我电脑上明明能跑”这类经典问题。写Dockerfile时注意两点:基础镜像不要用latest,要固定版本号;依赖安装用requirements.txt锁住精确版本,不要用>=写法。
5. 常见问题与避坑经验实录
5.1 数据预处理不统一导致的“训练好、上线差”
这个问题是我见过最多的。训练时,数据清洗流程写在 notebook 里,包含各种手工操作;上线时,服务端重新写了一遍预处理,但可能少了一步大小写归一化,或者分词方式不同。导致线上输入分布和训练分布错位,模型表现大跌。解决办法是从一开始就把预处理逻辑封装成独立函数,训练和推理都调用同一个函数,并且在服务代码里写单元测试,保证线上流程的一致性。
5.2 类别不平衡没人发现,准确率虚高
分类任务最常见的陷阱。如果正样本占 95%,负样本占 5%,模型只用“全部预测为正”就能拿到 95% 准确率。很多新手看到这个数字就以为模型做得很好,实际上模型啥也没学到。正确做法是先看混淆矩阵、看各类别的 precision/recall/F1。项目里专门强调了这一点,但实际工作中还是经常有人踩。我的一般做法是:如果类别分布严重不平衡,第一选择是收集更多少数类样本;做不到的话就用 class weight 或过采样/欠采样技术。
5.3 推理阶段忘记model.eval()
这个错误很隐蔽,因为不会报错。训练时有 Dropout 和 BatchNorm,推理时必须关闭,否则模型的每一步输出都会带有随机性。用 PyTorch 写服务时,在加载模型后立刻调用model.eval(),并且写在加载权重之后、进入循环之前。我还见过有人因为多次加载模型,加载一次调一次 eval,但顺序搞错导致部分层仍在训练模式。最简单的方法是用 PyTorch 的torch.inference_mode()上下文管理器包住推理逻辑,它会自动关闭梯度计算和训练模式,更安全。
5.4 并发压力下服务崩溃
本地单请求测试一切正常,一上生产并发一高就超时甚至崩溃。原因通常是推理过程是同步阻塞的,一个请求占用一个 worker,而默认 worker 数量太少。解决方案是使用异步接口,同时配合批处理逻辑。批处理的意思是:多个请求到达后,把它们的输入拼成一个 tensor,一次前向计算输出多个结果。这在 GPU 上能显著提升吞吐,在 CPU 上也能提高缓存利用率。项目里有动态批处理的实现参考,我建议至少理解它的思想,不要直接照抄,因为队列长度、最大等待时间需要根据流量模型调整。
5.5 模型版本混乱,难以回滚
团队协作时,模型文件通常存在共享目录,大家都往里面放新版本,过几天就分不清哪个是线上正在跑的。规范做法是:每次训练产出模型时,文件名带上版本号和时间戳,例如textcnn_v3_20250115.pt;每个版本对应一份配置文件,包含超参数和评估结果;在数据库或配置中心里记录当前线上版本号。这样不管什么时候出问题,都能立即切换回上一个稳定版本。MLflow 能自动化这个过程,但在没有条件引入的时候,手工命名规范也能避免大部分混乱。
6. 如何在真实项目里从“跑通”走向“可用”
6.1 从项目模板演化到自己的代码库
我建议把这个项目当成一个脚手架,而不是终极答案。完成复现后,接手一个真实业务需求时,不要直接复制代码,而是先画出新的流程图:数据从哪里来、标签怎么定义、评估标准是什么、部署到哪里、监控哪些指标。每一步可能都和项目里的示例不同,但你会发现问题没有想象中复杂,因为核心链路是一样的。真实的业务需求往往比教程里的更脏:用户提的文本可能是错别字、中英文混合、甚至只有一两个词。这些都需要你在数据清洗环节自己去处理,而处理思路就是项目里那套:统计、过滤、归一化、构造词表。
还有一个重要的工程实践:把数据集也纳入版本管理。不要只存代码,不存数据。数据集的版本变化会导致模型不可复现,这在审计、迭代、协作时都是大问题。如果你没有专门的数据版本管理工具,至少要做到两点:一是记录数据集的来源、下载时间、清洗脚本的 commit id;二是制作一个数据集的校验和文件,每次变更都能追踪。这个习惯能拯救无数个下午。
6.2 我认为最有价值的学习路径
如果你完全没有 AI 背景,我建议按这个顺序推进:先花一周时间把 Python 基础和 PyTorch 基础过一遍,不需要深入,能写简单的张量运算即可;然后跟着这个项目把 TextCNN 的完整链路跑通,重点是理解数据、模型、评估、部署、监控之间的联系;之后拿一个简单的真实业务数据练手,比如公司内部的工单分类、评论情感标注,从头到尾做一遍;最后再回去看 Transformer 相关的理论,你会发现自己理解速度快了很多。不要一开始就沉迷 Transformer 论文,那更像语言学家在研究语法,而你现在需要的是会说这门语言。
我在实际项目里体会到,AI 工程最核心的能力不是背公式,而是在无数的权衡中做出合理决策。该用简单模型还是复杂模型?该用实时推理还是批量预测?该花时间调参还是先排查数据?这些问题没有标准答案,只有基于工程约束的判断。这个项目给了你一套完整的骨架,剩下的血肉需要你在真实业务中慢慢填充。
最后分享一个小技巧:在你跑通这个项目后,尝试把其中某个环节替换掉,比如把 TextCNN 换成别的轻量模型,或者把 FastAPI 换成另一个服务框架,再或者把 IMDB 数据换成你自己的业务数据。每做一次替换,你对整个系统的理解都会更深刻一层。这种“刻意制造变化”的学习方法,比从头到尾只按教程跑一遍有效得多。