
做AI工程技术的人多了但大多数人的起点是某一个现成框架的教程而不是问题的本质。我自己带了几个项目之后感受特别深框架用得很溜的人不少可一旦模型上线不收敛、指标起不来、显存炸了能讲清楚“为什么”的人寥寥无几。所以当看到“ai-engineering-from-scratch”这个主题时我第一反应是太对味了——真正扎实的AI工程能力从来都不是从某个深度学习框架的文档开始的而是从最底层的原理和一条条亲手踩出来的经验开始的。这篇文章我就把自己从零构建AI系统的完整路径写出来包括每个阶段踩过的坑、必须补的原理、真正管用的工程实践希望能给正在走这条路的人一张靠谱的地图。1. 先搞清楚一件事:AI工程不是调包1.1 为什么“会用框架”不等于“会AI工程”随便打开一个深度学习框架的首页你都能在五分钟内跑通一个手写数字识别。两行代码定义模型三行代码开始训练再两行代码评估准确率。很多同学的“AI工程”就停在了这里。但说句不中听的这种状态下你并没有在做AI工程你只是在“调包”。AI工程和调包的关键区别在于调包只关心“什么能跑通”工程关心的是“为什么能跑通、什么条件下会跑不通、跑通了之后怎么稳定地反复跑”。举个例子框架里一个模型定义、一个优化器、一个loss函数三个对象拼起来就能训练。但训练过程中梯度是怎么流动的学习率对收敛的影响有多大为什么有时候loss降了几个epoch就停滞了框架不会告诉你这些答案它只会忠实地执行你的错误指令然后眼睁睁看着模型训坏。我自己带新人的时候最爱问的一个问题是你不用框架手写一个两层神经网络的梯度下降能不能写出来能写出来的人调参速度快非常多。因为他在脑子里能“看见”loss的梯度在怎么流动知道该往哪个方向调。写不出来的人只能靠试运气好试对了运气不好就在deadline前崩溃。1.2 from scratch要重建的四个基础栈我理解的“from scratch”不是让你从砂子开始造芯片而是让你把AI工程的地基一层层重新夯实。按我的经验真正值得下功夫的有四个基础栈理论栈线性代数、概率统计、最优化、信息论。不用学到数学系的程度但矩阵乘法、偏导、链式法则、期望方差、梯度下降这些概念必须有直觉。为什么因为它们直接决定了你对模型行为的预判能力。比如你知道ReLU在负数区域的梯度是0就知道一个学习率设置不当的网络为什么会出现“死神经元”现象——这不是玄学是数学。工具栈Python、NumPy、PyTorch或TensorFlow、Docker。Python是胶水层NumPy让你理解张量底层是什么深度学习框架加速你的迭代Docker保证“在我机器上能跑”变成“在谁的机器上都能跑”。工程栈Git、CI/CD、实验管理、监控告警、日志系统。这部分在课程里没人教但实际项目里一半以上的时间花在这上面。模型训练不是写论文是一次次实验、一回回比较、一层层部署叠出来的。没有工程栈你的工作流就是一堆叫“v2_final_改改改2”的文件夹。系统栈GPU原理、显存管理、推理引擎、模型压缩。这些决定了你的模型能不能从实验室走进生产环境。一个在云端GPU上跑得飞快的模型换到普通CPU服务器或者手机端可能慢到不可用这其中的差异就是系统栈知识解决的问题。1.3 一个反直觉的事实手工实现反向传播是值得的在我带的团队里有一类新人特别吃亏他们啃了很多论文张嘴就是Transformer、注意力机制但让他手动算一个3层网络的梯度他写不出来。结果就是这类同学虽然知道很多新模型却连一个简单的调参问题都定位不了。手工实现反向传播确实慢但它有一个不可替代的价值它逼迫你理解每一行代码背后的数学在干什么。这就像学做饭。你跟着菜谱炒一盘番茄炒蛋很容易但你不理解火候、盐量、食材水分之间的相互作用就永远没法自创菜式也没法在菜谱出错时自救。理解反向传播就是理解火候和调味的底层逻辑。好消息是这个投入产出比非常高。你只需要用一个周末的时间就能从零手写一个支持两层网络的完整训练循环。有了这个底子后面学任何框架、任何新模型都会比别人快非常多因为你有了“第一性原理”的视角。2. 从“11”起步手写一个可运行的最小系统2.1 选型为什么从线性回归而不是神经网络开始“从零开始”最容易犯的错误就是好高骛远。很多人一上来就想手写Transformer写了两天发现注意力矩阵全都算不对自信心碎一地。我的建议是第一课选线性回归不要选神经网络。为什么因为线性回归麻雀虽小五脏俱全。它包含了任何一个机器学习系统的所有关键组件特征、模型、损失函数、优化器、评估、部署。你把线性回归这套完整链路跑通一遍后面换成复杂的神经网络只是替换“模型”这一个组件罢了。链路本身——数据→模型→损失→优化→评估→迭代——是通吃所有AI项目的通用骨架。而且线性回归还有一个优势它的数学是可解析的。你可以用梯度下降去求参数也可以直接用正规方程一步到位算出最优解两者一对比马上就能直观理解“迭代优化”和“解析求解”的差别这是理解后面所有深度学习优化算法的出发点。2.2 30行代码一个完整的最小训练系统下面这段代码是我在项目里带新人必讲的最小实现。它没有任何框架封装完全靠NumPy手写了一个线性模型从数据到参数的完整闭环import numpy as np import matplotlib.pyplot as plt # 制造一份带噪声的线性数据真值 w2.0, b0.5 np.random.seed(0) x np.linspace(-1, 1, 100).reshape(-1, 1) w_true, b_true 2.0, 0.5 y w_true * x b_true 0.1 * np.random.randn(100, 1) # 初始化参数 w 0.0 b 0.0 learning_rate 0.1 # 训练循环 for step in range(200): # 前向传播计算预测值 y_pred w * x b # 计算损失均方误差 loss np.mean((y_pred - y) ** 2) # 反向传播手动计算梯度 # d(loss)/dw mean(2 * (y_pred - y) * x) grad_w np.mean(2 * (y_pred - y) * x) # d(loss)/db mean(2 * (y_pred - y)) grad_b np.mean(2 * (y_pred - y)) # 参数更新梯度下降 w - learning_rate * grad_w b - learning_rate * grad_b if step % 20 0: print(fstep {step:3d}, loss {loss:.4f}, w {w:.4f}, b {b:.4f})这段代码跑完你会看到loss在200步内从初始的高位一路降到趋近于0.01附近w会逼近2.0b会逼近0.5。别小看这个玩具它是你理解一切深度学习的基础预测→算损失→算梯度→更新参数这四个动作就是所有AI系统的训练内核。2.3 梯度下降的物理直觉与超参数敏感度分析为什么这段代码里每次更新都朝着“loss下降最快”的方向走我用一个类比解释梯度下降就像是站在一个浓雾覆盖的山坡上你想下山但看不见整座山只能靠脚感受当前站的地方哪边坡度最陡然后朝那个方向迈一步。反复这个动作你最终就能到达山脚。这里的“坡度”就是梯度“每一步迈多大”就是学习率。学习率这个超参数是新手遭遇的第一个“坑中之坑”。我在代码里用了0.1这个值在这个玩具问题上刚好合适。但你把learning_rate改成1.0试试loss会像过山车一样震荡甚至越跑越大直接炸掉。改成0.001呢训练会变成蜗牛爬200步远远不够。实际项目里的调参经验是优先把学习率调大直到loss开始震荡或发散再往回调一个数量级。很多人一上来就选很小的学习率结果训练非常慢以为是模型结构的问题折腾半天才发现是学习率量刑过轻。另外SGD后面衍生出的Adam、RMSProp这些优化器核心灵感就是根据梯度的历史信息自动调整每一步的步长让“下山”过程更稳当。这就是为什么框架里往往一行optimizer.Adam(model.parameters())就能替代手动设置学习率调参——但如果你不理解梯度本身遇到Adam效果不好时依旧不知道怎么排查。3. 真正拦住你的不是模型是数据、GPU和实验管理3.1 数据是最容易被忽视的工程瓶颈我接触的几乎每一个AI项目时间分配都惊人地一致80%的时间在处理数据只有20%的时间在训练模型。很多从课程里出来的人完全没这个预期他以为AI工程的重头戏是搭模型、写网络结构结果入职第一个月全在写数据清洗脚本心态差点崩了。数据工程的核心技能可以拆成四块数据清洗缺失值怎么办、异常值怎么处理、重复样本怎么去重、不同来源的同字段单位不统一怎么对齐。这些看起来琐碎但直接影响模型上限。我见过一个项目模型怎么调准确率都上不去最后定位到是某个传感器的数据单位换了从毫米变成了厘米模型一直被脏数据带偏。标注体系如果你的任务需要监督学习怎么制定标注规范让不同标注员的标注保持一致直接决定了你训练集的质量。工业界有个词叫“标注一致性”多个标注员对同一条数据标的答案高度一致这个数据集才可靠。我在项目里常用的方法是对每个标注员抽检一部分数据计算他与众包的标注一致性低于阈值的标注结果直接废弃重标。数据增强图像领域裁剪、旋转、加噪声文本领域同义词替换、回译。增强的度要拿捏好过度的增强反而会把原始分布带偏导致模型学到的不是关键特征而是噪声模式。数据版本管理数据会迭代模型必须能精确对应到它是用哪个版本的数据训练的。数据仓库里一份数据改了三个字段结果所有下游模型的指标全部波动这个事故在AI团队里一点都不罕见。DVC这类工具就是给数据上“Git”每次改动都留下版本指纹。3.2 GPU资源管理与性能调优模型的训练速度受GPU影响很大但很多人以为“买张贵的卡就行了”。实际上工程上训练速度的快慢很大程度取决于你怎么用这张卡。我踩过的几个坑列出来供参考Batch Size不是越大越好。增大Batch Size能提高GPU利用率没错但显存是有上限的一次性塞太大就会爆显存。工程上常用“梯度累积”的方法每算完一个小Batch的梯度不马上更新攒满N个Batch的梯度才做一次参数更新这样等效于用一个很大的Batch Size训练但显存压力被摊开了。混合精度训练是性价比之王。默认的PyTorch训练用的是float32每层参数和梯度都占4字节。混合精度把一部分能“凑合”的张量换成float16显存占用几乎减半而且新一些的GPU跑float16还有硬件加速。注意为了防止精度损失导致梯度消失混合精度会把loss乘上一个缩放因子再动态调整。这个细节框架已经帮你处理好了但你得知道它存在。数据加载是隐形瓶颈。很多训练跑得慢瓶颈根本不在GPU算力而是CPU喂数太慢GPU在空转等数据。解决办法是用DataLoader的num_workers参数开启多进程加载配合pin_memoryTrue把数据直接放在GPU专用的页锁定内存里。这一套做完训练速度提升20%到30%很常见。3.3 实验管理的“军规”从拍脑袋到可复现我在不同的团队见过同一个悲剧训练了几十个模型文件夹命名依次是“v2_final”“v2_final_改”“v2_真最终”“v2_最终确定不改了”。过了两周任何人都说不清每个模型对应的数据版本、超参数、训练脚本是什么。这本质上是实验管理缺失导致的灾难。不用复杂的平台最基本的三条军规立起来就够用唯一实验ID每一次训练启动时自动生成一个时间戳ID例如exp_20241115_140523所有产物权重、日志、指标、配置文件都放到这个ID的目录下。环境快照把每次运行的Python版本、依赖包版本、GPU信息、随机种子全部记录到一个env.yaml文件里。你没法保证三年后还能依赖环境100%复现但至少一年内是可追溯的。变更台账每跑一个实验前用一句话写下“这次改了什么、为什么改”。我强烈建议直接用WB或MLflow这类工具它们把日志、指标、图表都自动聚合并可视化。如果是小团队懒得搭系统哪怕是纯文本README也要写。这套军规的回报是当模型出了问题你能像一个侦探一样回溯“哪个变化导致了哪个结果”而不是对着十几份“改”字辈文件夹发愁。3.4 评测你离“有用”只差一个正确的评测指标模型训出来了评估的时候很多人的第一反应是“看准确率”。但在真实业务里准确率经常是个陷阱。举个我在电商搜索场景遇到的例子。做商品排序模型如果拿准确率当唯一指标你会被假象坑得很惨。因为搜索结果里用户真正点击的商品永远是少数你写一个全部返回“不推荐”的模型准确率也能有90%以上——毕竟绝大多数商品确实不会被点击。但这显然是一个废物模型。排在这种场景里更科学的是召回率、精确率、F1、以及排序类指标NDCG和AUC。指标选错了你这套AI系统是否“有用”可能根本没有被正确地度量过。想要接下来评测体系一步一步接地气我习惯按三层去搭离线指标层在固定测试集上算准确率、召回率、F1、AUC等用来快速筛选模型。在线评估层做A/B测试。把用户流量随机分给模型A和模型B看核心业务指标的表现差异。业务指标层这一步最重要——AI工程的终点永远是业务目标搜索模型看转化率推荐模型看留存率。离线指标再漂亮业务指标不动意义也不大。这三层少一层模型都可能是在自嗨。4. 六个月路线图从零到能用4.1 第1-2周补数学但只补“用得上”的数学很多人一听学数学就想重头啃完整个大学教材这完全没有必要。你要做的AI工程不是数学研究你需要的是一种“有手段看懂模型结构里的数学符号”的阅读能力。具体来说以下主题够了矩阵乘法与维度兼容规则几乎每个模型都在做矩阵乘偏导数与链式法则反向传播的全部基础期望、方差与常见的概率分布理解数据分布和损失函数梯度下降及其变体SGD、Momentum、Adam的原理正则化的数学直觉L1、L2为什么能让模型参数更规整两周够了不用多。核心是这些概念你能在纸上画出来而不是会背公式。4.2 第1-2月从零实现一个小型模型动手写一个完整的小模型不依赖深度学习框架只用NumPy。具体来说实现一个可以处理二分类的逻辑回归实现一个带一个隐藏层的MLP并手写反向传播在一个公开小数据集比如MNIST上让准确率跑到95%以上要求是不做任何封装直接写循环。过程中你一定会遇到各种报错、维度不匹配、梯度算错的问题但这正是目标——通过这些错误把“前向、反向、更新”的肌肉记忆刻下来。我特别建议在这段时间做一个小实验分别用学习率0.01、0.1、1.0训练同一个模型画一下loss曲线。同时观察梯度量级的变化。你会清楚地看见“太大”“太小”和“刚好”的差别到底长什么样。4.3 第2-4月用框架重写一切但要保留“解剖”能力有了手写的基础这时候切到PyTorch或TensorFlow是驾轻就熟的过程。框架帮你压缩了大量样板代码也帮你自动算了梯度。这个阶段的目标不是“用框架跑通模型”而是**“用框架重写之前手写的模型然后逐行对比理解框架到底替你做了什么”**。推荐定一个具体目标用PyTorch实现之前手写的MLP并对比两套实现的训练曲线是否一致。如果不一致找到原因。这个过程能帮你彻底搞懂框架的backward()是怎么通过计算图自动求梯度的也能让你在框架的调试信息面前不慌。然后可以开始工程化你的代码给训练脚本加上命令行参数解析、日志输出、模型checkpoint保存、断点续训功能。把训练过程从“一个笔记本文件”变成“一个可重复执行的程序”。4.4 第4-6月项目驱动做一个真正会被用户使用的服务课程和项目的最大差别在于课程读起来有预期项目是真实的反馈。这个阶段我建议选一个你真正关心的场景比如搭建一个“文本摘要API服务”。完整流程是这样的选一个预训练模型比如基于BERT或T5的认清任务边界收集或标注一小批有代表性的数据目标至少几百条做微调同时完整记录实验并评测各项指标把模型封装成HTTP服务用Docker打包加上输入输出的校验逻辑设计一个最简监控请求延迟、请求量、错误率以及关键业务指标的变化叠加等这个服务上线即使只有你一个人在用你也会体验到AI工程的全流程——“从零到用到”的完整感就在这一步落地。5. 常见卡点与排错笔记5.1 卡点一损失不降这是AI工程实践中排名第一的问答题我不夸张地说几乎每个新手都在这个坑里待过。loss不降的原因很多但排查的顺序决定了你是半小时定位还是三小时定位。我建议按这个顺序查数据是否归一化很多模型对特征的尺度极其敏感。如果某个特征的数值范围是0到10000另一个是0到1梯度会被大尺度的特征带偏训练过程表现为loss震荡或缓慢爬升。解决办法是对特征做标准化让它们都落在差不多的尺度上。学习率是否过大或过小这一步我见过最多。遇到loss不降别急着换模型结构先看一眼学习率是不是太小了导致更新量微弱或者太大了导致loss在震荡。是否有梯度消失或梯度爆炸检查梯度值本身直接打印模型各层梯度的均值和标准差。如果梯度全是0说明链式法则已经断流如果梯度的数值量级在指数级增长那就是爆炸了需要做梯度裁剪或用残差结构。模型是否退化成了恒等映射尤其注意最后一层初始化某些初始化方案会在初期让预测值稳定在目标均值附近看上去loss变化不大其实是在做缓慢的冷启动。5.2 卡点二过拟合才是“标配”很多项目在模型训练初期训练集loss一路下降测试集loss却开始走高。这种经典症状就是过拟合。我想说看到过拟合曲线不用慌这说明你的模型有条件去“背”数据了接下来我们要把它拉回客观现实的轨道。我对付过拟合有四个习惯动作正则化L2权重衰减是个经典方案。这个“衰减”在PyTorch里是weight_decay参数它会惩罚大权重逼着模型学会更简单健壮的规律。数据增广在合法范围内扩增训练样本的多样性。图像裁剪、翻转文本的随机mask都让模型难以简单地记住答案。早停盯住验证集loss一旦它开始连续N个epoch不降就先停掉别让模型硬着头皮继续学。Dropout随机让一部分神经元在前向时失活迫使网络学到更鲁棒的特征分布。注意推理时必须关闭Dropout否则模型输出会不稳定。5.3 卡点三同一个程序两次结果不一样“我的程序明明是同一份代码为什么两次运行的结果完全不一样”这是我经常被问到的问题其实答案很简单深度学习几乎处处有随机性。可能的随机源包括参数随机初始化、Dropout的随机失活、数据加载器的shuffle顺序、多GPU并行时的采样偏差、以及某些硬件算子本身具备的微小不确定性。要做到可复现最直接的方法就是设置随机种子import random import numpy as np import torch def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)在训练脚本的最前面调用一次set_seed(42)。这只是第一步。如果用了多线程数据加载还得设置worker_init_fn不然每个子进程的随机序列依然无法精确对齐。把这个写进你的固定模板里每次训练前无脑执行省下的全是排错时间。5.4 卡点四进入生产环境的“最后一公里”模型在测试集上表现挺好结果一上线就翻车。这类事故基本都是两个原因一是训练与推理的不一致比如训练时做了数据增强、推理时忘了处理或者训练时输入做了归一化而推理接口没有同步二是数据分布偏移模型在学习时见到的世界和线上真实的世界本身可能已经不同了。给一个很实用的操守在训练结束之前把模型权重复制一遍在没有任何数据增强和随机性的推理模式下重新跑一遍测试集确定指标不掉。这一步不能省。它会替你拦截大量输送到线上的脏数据格式问题。写在最后六条从我真实项目里长出来的经验最后说说几年下来我觉得最值钱的认知全是从真实项目一个坑一个坑里长出来的第一“调包”不是问题问题是不理解你调的那个包在做什么。只要你愿意在某个周末手写一次全流程的小模型很多“框架玄学”当场就踏实了。第二数据质量是惩罚项。模型再花哨也架不住脏数据。别把数据清洗的时间当浪费时间这笔时间大多数人最后都会补交。第三指标选错项目归零。离线指标再好也要盯住业务目标不然你只能做出“指标漂亮但没用”的模型。第四实验必须可复现。养成每次训练前记录一句话“改了什么、为什么改”的习惯时间越久你会越感谢这个习惯。第五学习路线要按自己的目标裁剪。我不需要你每个数学概念都精通但搞懂使用链路里的关键术语和直觉遇到问题就多一层判断力。第六尽量早做端到端项目。哪怕一开始又小又粗糙完整经历数据、训练、评测、部署、监控这个循环比你学2个月“先进论文”管用得多。这一路走过来我最大的体会是AI工程里那些扎实的基本功不来自于python的封装技巧、不来自于speedrun式的调参而来自于你真正把一个系统从零开始跑明白的过程中积攒的手感和判断力。希望这份经验能帮你少走一些弯路。