很多人问我"AI工程到底怎么入门",我通常反问他一句话:你现在跑通了一个MNIST手写数字识别,就真的敢说自己是AI工程师了吗?我见过太多人被"五分钟训练一个人脸识别模型"这种标题带偏,以为AI工程就是装个GPU驱动、跑个开源仓库、改两行训练参数。等到真正进入现实项目,数据是脏的、标签是错的、GPU显存不够用、模型上线之后精度和离线评测完全两回事——这时候你才发现,自己连"从零开始"这四个字都没真正理解。
这篇文章我想聊聊我对"ai-engineering-from-scratch"这件事的理解和完整实践路径。它不是一个课程目录,而是一份真实摸爬滚打后的经验地图——包括了基础数学该补到什么程度、为什么要手写神经网络、工程化环节有哪些你迟早要填的坑,以及整个学习顺序怎么排才不浪费时间。如果你正处于"看过很多教程但拼不成一张完整地图"的阶段,这篇文章应该能帮你把碎片拼起来。
1. AI工程不是"跑模型",而是一整套解决问题的体系
1.1 先把"从零开始"的定义说清楚
我见过两种理解。第一种人觉得"from scratch"就是不看任何高级框架,从纯Python手写梯度下降、手写反向传播。第二种人觉得"from scratch"就是从数据处理开始,到调用一个大模型API做应用为止。我的看法是,这两种都只摸到了一条腿。
真正的AI工程"从零开始",指的是你拿到一个业务问题之后,有能力从问题定义出发,独立走完数据获取与清洗、模型选型与训练、效果评估与调优、系统部署与线上监控这一整条链路。你不需要从第一行CPU指令开始写,但你需要知道每一层在干什么、出了问题该去哪一层找原因。就好比一个真正的汽车工程师不一定自己铸造螺丝,但他一定清楚发动机、变速箱、转向系统各自负责什么,互相之间怎么配合。只会启动车、踩油门的人,那不叫工程师,只能叫用户。
所以这篇文章里我反复强调一件事:先建立系统认知,再往深处钻细节。上来就闷头啃《动手学深度学习》或者直接刷三分之一的李航《统计学习方法》,很容易陷入"越学越焦虑、越学越觉得自己什么都不会"的状态。正确顺序应该是:先做两三个小项目搞清楚全链路长什么样,再回头补数学和理论,最后再做大项目深挖瓶颈。
1.2 从问题到交付:AI工程的完整环节
一个标准的AI工程项目,拆开看大概是七个环节:
- 问题定义:明确这个业务问题能不能用AI解决、用什么形式解决。比如"预测用户流失"和"自动给工单分类"是两类完全不同的问题结构。
- 数据获取与探索:拿到原始数据,做探索性分析(EDA),搞清楚字段含义、缺失情况、分布形态。
- 数据处理与特征工程:清洗脏数据、处理缺失值、做特征变换和筛选。这一步决定效果上限。
- 模型训练:选模型、定损失函数、设计训练验证流程、做超参数调优。
- 效果评估:用合适的指标衡量模型,不止看准确率,还要看精度、召回、AUC、业务收益等。
- 部署上线:把模型做成可调用服务,处理延迟、吞吐、版本更新问题。
- 监控与迭代:线上数据分布漂移了怎么办、模型效果衰退了怎么办、要不要重新训练。
很多人学AI多年只盯着第四步,就是"模型训练",其他六个环节几乎空白。这带来的直接后果是:你在Kaggle上能拿银牌,但在公司里连一个最简单的预测报表都交付不了。Kaggle给的是消毒过的干净数据,真实项目里的数据可能是接口返回的JSON字段错乱、可能是日志里有大段乱码、可能是标签本身定义就有歧义。处理这些"脏乱差"的能力,恰恰是AI工程里最值钱的部分,也是"从零开始"时最容易忽略的部分。
2. 数学补课怎么补,才算补到刀刃上
说到AI工程,很多人第一反应是"数学太难了"。我的真实体会是:AI工程日常用到的数学,远没有考研数学那么复杂,但它对直觉的要求很高。你不需要会手工算积分,但必须理解梯度是什么、矩阵乘法在干嘛、概率分布怎样描述不确定性。
2.1 微积分和线性代数:不是学数学,是学模型的语言
我建议你想清楚一件事:学微积分和线性代数,目的是读懂模型里发生的运算,不是去考试。
以最基础的神经网络为例。一个全连接层做的事情,本质上就是一次矩阵乘法加一次逐元素的非线性变换:output = activation(input @ weight + bias)。你要看懂这条式子,需要的线性代数包括:
- 矩阵乘法中维度的匹配规则,也就是为什么输入是
[batch, features]、权重是[features, hidden]就能得到[batch, hidden]这样的输出; - 转置、逐元素运算、矩阵相加这些基本操作的含义;
- 特征空间的概念:每个隐藏层的每个节点其实是在对输入做一种新的表示。
微积分方面,核心就一个:链式法则。反向传播算法本质上就是链式法则在多层复合函数上的反复应用。你不需要会求复杂的超越积分,但一定要亲手推一遍loss = (y - (wx + b))^2对w和b的偏导。这一步推完,你对"梯度下降为什么能更新参数"的理解会和背公式完全不一样。
概率统计也是同理。重点不是背各种分布的函数表达式,而是理解:模型输出的不是一个点,而是一个分布。交叉熵损失为什么能衡量预测和真实的差距?逻辑回归为什么用sigmoid输出概率?这背后都是条件概率和分布拟合的思想。我见过太多人把softmax当成一个"把分数变成0到1之间数字的工具",完全不知道它本质上是多项式分布的规范参数化——这种理解偏差会在你调试模型时变成认知盲区。
2.2 我的补课方式:边写代码边补数学
空谈数学容易劝退,我的建议是"以代码为锚点补数学"。具体做法:每学一个模型,先从代码层面把它跑通,然后挑三五个关键公式,倒推它们背后的数学直觉。
比如学线性回归时,你可以做三件事:
- 用最小二乘法闭式解求一次参数;
- 用梯度下降手写一次迭代过程,对比两种方式的结果是否一致;
- 把损失函数随迭代次数的变化画出来,亲眼看到loss下降曲线的形态。
这三件事做完,你对"线性回归"这个模型的理解,就会比任何只看公式的人扎实得多。因为你已经亲眼验证了数学公式和代码行为是对应的——这种"对上了"的感觉,才是真正理解信号,而不是"我好像学过"的错觉。
| 数学模块 | 推荐投入 | 核心关注点 |
|---|---|---|
| 微积分 | 2~3周 | 链式法则、梯度、偏导的计算直觉 |
| 线性代数 | 2~3周 | 矩阵乘法、维度匹配、特征空间概念 |
| 概率统计 | 2~3周 | 分布、期望、似然、交叉熵的直觉 |
| 最优化 | 1~2周 | 梯度下降、学习率、局部最优 |
上面这个投入量是按"每天1~2小时"估算的,不需要脱产学习。重点是不要零散地学数学,而是跟模型学习穿插着来——学到反向传播就回去补链式法则,学到注意力机制就回去补内积和softmax,学到损失函数就回去补信息论和概率。数学给代码提供解释能力,代码给数学提供应用场景,两者互为拐杖。
3. 手写一个最小神经网络:打通任督二脉的必修课
如果你只想从这篇文章里拿走一条行动建议,那就是:无论你将来做CV还是NLP,都要在初学阶段手写一个不带任何深度学习框架的最小平多层神经网络。这一步是"从零开始"的核心,也是我最舍不得省的经验。
3.1 为什么要手写:框架给你的黑盒要拆开看
用PyTorch写一个nn.Linear加nn.ReLU加nn.CrossEntropyLoss,十行代码搞定。但正因为太简单,你完全可以不假思索地跑通一个模型。糟糕的不是跑通,是跑通之后你对网络内部发生了什么依然毫无感知。
手写一个最小神经网络,最难的不是前向传播,而是反向传播。拿一个两层网络举例:
import numpy as np def sigmoid(x): # 数值稳定版本 return np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x))) def sigmoid_grad(y): return y * (1 - y) # 网络结构:2->4->1,用batch size=3 np.random.seed(42) X = np.random.randn(3, 2) # 输入:3个样本,2个特征 y = np.array([[0], [1], [1]]) # 二分类标签 W1 = np.random.randn(2, 4) b1 = np.zeros((1, 4)) W2 = np.random.randn(4, 1) b2 = np.zeros((1, 1)) learning_rate = 0.5 for step in range(200): # 前向传播 z1 = X @ W1 + b1 a1 = sigmoid(z1) z2 = a1 @ W2 + b2 a2 = sigmoid(z2) # 损失:二分类交叉熵 loss = -np.mean(y * np.log(a2 + 1e-15) + (1 - y) * np.log(1 - a2 + 1e-15)) # 反向传播 dz2 = (a2 - y) / y.shape[0] # 输出层梯度,推导自交叉熵+sigmoid的简化 dW2 = a1.T @ dz2 db2 = np.sum(dz2, axis=0, keepdims=True) da1 = dz2 @ W2.T dz1 = da1 * sigmoid_grad(a1) dW1 = X.T @ dz1 db1 = np.sum(dz1, axis=0, keepdims=True) # 更新参数 W1 -= learning_rate * dW1 b1 -= learning_rate * db1 W2 -= learning_rate * dW2 b2 -= learning_rate * db2 if step % 50 == 0: print(f"step {step}, loss {loss:.6f}")这里最关键的一行是dz2 = (a2 - y) / y.shape[0]。新手最大的困惑来源就在这:为什么输出层的梯度这么简单?答案是,这行代码是交叉熵损失函数和sigmoid激活函数在链式法则下"巧合抵消"的结果。如果你单独用sigmoid_grad去算dz2,得到的梯度会比这个小一个量级,而且数值上几乎没法用。这种"看似简单实则经过推导"的地方,如果不手写一遍,你可能永远不知道框架替你做了什么。
3.2 手写过程中必然遇到的三个认知突破
我把手写过程中的心理变化总结成三个认知突破,每一个都发生在某一件事上:
第一个突破:理解"数值梯度"验证法。反向传播写完最怕手滑写错。怎么验证?用数值梯度。原理很简单:梯度的定义就是函数变化率,那你直接给参数加一个极小的epsilon,用(loss(x+eps) - loss(x-eps)) / (2*eps)近似算出梯度,再和反向传播算出来的结果对比,误差在1e-5以内基本就是对的。这个方法极其朴素,却是我见过调试神经网络前期最好用的工具。我在手写网络时每写一个算子都会用数值梯度验一遍,这习惯一直保留到今天。
第二个突破:理解学习率的作用边界。手写简单的梯度下降时,学习率设大了loss直接发散变成nan,设小了loss下降慢得让人怀疑人生。你会被迫去观察loss曲线的形态,从而真正理解"学习率是超参数中最需要先调的一个"。这个经验是逃不掉的,框架不会手把手教你。
第三个突破:理解激活函数为什么必须在非线性。如果你把一个隐藏层的激活函数换成线性的(或者忘加了),你会发现无论网络多深,多层线性变换永远等价于一层线性变换。手写网络时忍不住试一次,看到验证集精度永远停在某个值上不去,这种"物理感受"比看十遍理论解释都管用。
这三个突破做完,你对神经网络的理解会从"调用工具"变成"理解系统",之后的迁移学习、调参、看论文,都会明显顺滑很多。这也是我坚持认为"手写训练代码是AI工程入门必修课"的根本原因。
3.3 手写之后:至少要能徒手推一次梯度
我给自己定的一个小目标:不看任何参考资料,纯靠字符串推导写出两个全连接层加softmax交叉熵的反向传播代码。这个过程大概需要两天,中间会错很多次,但一旦推对,你再看PyTorch的autograd就不会觉得是黑魔法了——它做的就是你手写的那些事,只不过它用计算图和自动微分把过程工业化了。
这一步做完,就可以放心用框架了。用框架节省时间是一种理性选择,但节省的前提是你知道自己在节省什么——把精力从重复的梯度计算中解放出来,投入到更复杂的数据处理和模型设计上。如果你压根不知道自动微分帮了你什么,那用框架就变成了"盲用",迟早会在某个报错信息面前不知所措。
4. 从模型到系统:数据、评估、上线的一整套工程动作
手写神经网络解决的是"模型内部怎么工作"的问题。但AI工程更广阔的战场在模型之外。我见过不少模型调得很溜的人,一碰上数据处理和上线部署就露馅。这一章我专门讲"模型之外"的工程动作。
4.1 数据管线:AI工程真正的主战场
业内有句老话:模型的作用被高估了,数据的作用被低估了。我自己的体感是,一个真实的AI项目,数据处理通常要占掉一半以上时间。这不是因为模型简单,而是因为数据处理的场景千奇百怪,每个项目都不重样。
常见的数据问题包括:
- 格式混乱:同一个字段有的是字符串"123",有的是浮点数123.0,有的是空值;
- 逻辑冲突:注册时间晚于登录时间,年龄字段存在负数;
- 标签噪声:人工标注的数据里,标注员之间的标注一致性可能只有80%左右;
- 数据泄漏:不小心把未来信息放进特征里,比如用全量数据的均值做归一化,导致验证指标虚高。
我最想强调数据泄漏问题,因为它是新手最不容易察觉、但后果极其严重的坑。举个典型例子:做销量预测时,如果先用全量数据算出平均销量作为特征,再切训练集测试集,那测试集里已经混入了训练集的统计信息,评估出来的指标会漂亮得离谱。正确做法是:所有需要统计信息的特征变换(标准化、归一化、缺失值填充)都只能在训练集上拟合,测试集只能用训练集学到的参数去变换。
解决这类问题的标准姿势是一套数据管线工具,比如scikit-learn的Pipeline配合ColumnTransformer,或者更工程化地用Feast做特征存储。但工具只是表,核心是你脑子里要有"训练/验证/测试三套数据必须完全隔离"这根弦。
4.2 评估:不要被准确率这个单一指标骗了
模型训练完,最怕你只看准确率一个数就宣布成功。我见过一个项目,99%的样本属于负类,模型只要全部预测为零类就能拿到99%准确率,看起来无比强大,实际上一点业务价值都没有。这就是典型的"类别不平衡"下的准确率陷阱。
正确的做法是根据业务场景选取合适的指标。二分类问题至少要看:
- 精确率与召回率:精确率回答"预测为正的里面有多少是对的",召回率回答"真正的正样本里被找出来多少";
- F1值:精确率和召回率的调和平均,两者都要兼顾时的综合指标;
- PR曲线/AUC:不依赖阈值设定的整体性能度量;
- 业务指标:这个模型省了多少成本、多赚了多少钱、减少了多少人力——这往往才是老板真正关心的。
我自己的习惯是,每次训练完模型都打印一张评估矩阵,不只是看数值,还要画出混淆矩阵、看每个类别分别的表现。很多模型的"整体指标不错"其实掩盖了某个小众类别效果极差的问题。一次只给一个总结数的评估,等于把自己蒙在鼓里。
4.3 上线与监控:模型是系统的入口而不是终点
把模型部署上线,最常见的问题有四个:延迟、吞吐、版本、漂移。
延迟和吞吐需要你选择合理的服务化方式。简单场景可以用Flask/FastAPI包一个HTTP接口,模型跑在GPU上;高并发场景考虑用ONNX Runtime或TensorRT做推理优化,配合批处理能显著提升吞吐。我的经验是,模型推理优化的第一原则是:先分析瓶颈在哪,再谈优化。很多情况下瓶颈根本不在模型计算,而在数据加载和预处理——你就算把模型推理时间砍掉一半,整体的收益也可能只有5%,因为时间大头在图片解码和特征提取。
版本方面,一定要做模型版本管理。线上跑的那个模型是什么时候训练的、用的哪份训练数据、在哪个评测集上达到多少分,这些至少要有一个表能查到。业内常用MLflow或者简单的数据库记录。别把它当成形式主义——当你三个月后需要回滚模型时,会感激当年的记录。
漂移监控是最容易被忽视的环节。模型上线后,用户行为、业务结构会随时间变化,数据分布慢慢漂移,模型精度会悄悄下滑。你需要监控两类东西:一是输入特征的分布变化(比如某个特征的平均值突然升高),二是模型预测输出分布的异常(比如某个类别的预测比例突然暴涨)。一旦触发告警,就启动人工评估,必要时重训模型。
| 生命周期环节 | 核心问题 | 常用工具/方法 |
|---|---|---|
| 数据管线 | 清洗、隔离、特征变换 | scikit-learn Pipeline、特征存储 |
| 评估 | 多指标、类别不平衡 | 混淆矩阵、PR曲线、F1 |
| 推理服务 | 延迟、吞吐 | FastAPI + ONNX Runtime |
| 版本管理 | 可回滚、可追溯 | MLflow、模型注册表 |
| 监控告警 | 漂移检测 | 特征分布监控、预测分布监控 |
5. 学习路线复盘:优先级排序与踩坑记录
文章最后,我把自己走完这条路之后的学习顺序和踩坑经验完整复盘一遍。如果你正打算建立自己的"from-scratch"计划,这份清单的优先级排序可能比很多课程大纲更实用。
5.1 我的阶段划分
第一阶段(2~3周):建立全链路直觉。找一个简单的标签数据集(房价预测、垃圾邮件分类都行),从数据探索开始,完成数据清洗、特征工程、模型训练,用scikit-learn的现成模型先完整跑一遍。这个阶段的目标不是精度多高,而是知道一件事从零到落地长什么样。
第二阶段(3~4周):拆开模型黑盒。手写最小神经网络,用数值梯度验证,实现前向和反向传播。这个阶段我前面已经详细说过,不再重复。
第三阶段(4~6周):用主流框架做真实任务。上手PyTorch,把之前手写的网络用框架重写一遍,再扩展到CNN做图像分类、RNN/Transformer做一个序列任务。边做边补数学——哪个公式读不懂,就回到第二阶段的代码里去验证。
第四阶段(2~3个月):做自己的完整项目。找一个真实场景的数据集,自己设计问题、做数据清洗、建模、评估、写部署Demo。别用Kaggle的现成干净数据,强迫自己用结构不完美的原始数据。这个阶段是最痛苦的,也是脱胎换骨的阶段。
5.2 三个最容易让人放弃的坑
坑一:贪多求快,四处浅尝。今天看机器学习的视频,明天读深度学习的书,后天刷大模型的帖子,结果半年过去了,一条线都没走完。AI领域的学习资料实在太多,你的任务不是"足够的输入",而是"有限的深度"。我建议你给自己定一个纪律:一段时间内只看一本主教材加一个主项目,其他内容不管多火都先存起来。
坑二:只读不动手,代码全复制。很多人看教程时"眼睛会了,手不会"。破解方式很简单:每看一章,必须不看答案、从头敲一遍代码。敲不出来的地方,才是你真正卡住的地方。复制粘贴不是学习,重写才是。
坑三:被"调包侠"心态绑架。总觉得自己是在重复造轮子,花时间手写基础实现没有价值。我的态度是:工具会不断换代,但概念不会过期。今天你学的梯度下降,将来在大模型预训练里依然在起作用;今天你手写的最小网络,会帮你读懂以后所有新架构的论文。基础技术是复利资产,框架技术才是消耗品——这种长期主义的心态,才是支撑你走完"从零开始"这条路的关键。
5.3 我个人现在的体会
走完这一整套流程后,我最深的体会是:"AI工程从零开始"不是一条"学会所有知识"的线性路径,而是一个"反复在系统和细节之间切换视角"的循环过程。你需要在做项目时看到系统的全景,在出bug时钻到参数的细节,在不断切换中建立起"拆解问题→定位瓶颈→逐层攻破"的肌肉记忆。这个过程没有捷径,但也没有想象中那么陡峭——只要每一步都有代码落地、都有真实反馈,你就是在一条正确的路上往前走。希望你也能早一点开始,少走一点我走过的弯路。