从零搭一套 AI 工程链路,到底难在哪?
如果你平时逛 GitHub 或者关注技术社区,应该没少见过ai-engineering-from-scratch这种标题。说实话,这类项目特别容易让人产生两种极端反应:要么觉得“又是标题党,无非是调库拼流程”,要么觉得“从零开始,那岂不是连线性代数都要自己实现一遍?”。
我自己第一次点进去之前,也带着这种偏见。但真正顺着它的思路走完一遍之后,我得说:这个“ from scratch ”并不是让你从数学公式开始推,也不是让你手写 TensorFlow。它的核心是——在不依赖现成 AI 平台、低代码工具、乃至大厂封装好的一切黑盒服务的前提下,把一条 AI 工程链路从数据到部署完整地亲手搭起来。这个过程里,你会被迫面对很多过去用托管服务时根本看不见的问题:数据版本怎么管理、特征怎么上线、模型怎么评测、线上推理延迟怎么压。
这篇文章就是我基于这个项目思路,结合自己实际落地 AI 项目时踩过的坑,整理的一份“非官方”解读与实践笔记。适合那些已经会跑 Python、用过几轮 sklearn 或 PyTorch,但没真正从零独立负责过一条 AI 生产链路的同学。你会发现,AI 工程不是算法模型的堆叠,而是一套关于“如何让模型稳定地活在真实系统里”的手艺。
1. 整体设计与思路拆解:为什么非要从零开始
1.1 “From Scratch”的真实含义:拒绝黑盒,但并非重复造轮子
很多人对“从零开始”有误解,以为就是什么都自己写。实际上,ai-engineering-from-scratch这类项目强调的是“不依赖一体化解决方案,而是亲手组装各个组件”。就像你不会从炼钢开始造一辆汽车,但你会自己设计底盘、选发动机、调悬挂,而不是直接买一辆整车。
在真实的 AI 工程里,这意味着几件事是你必须亲手做的,而不是让平台帮你“隐形”处理掉:
- 数据管道:从原始日志、数据库、文件系统里把数据捞出来,做清洗、校验、分割,而不是靠平台自动给你一份“干净 dataset”。
- 特征工程:特征的存储、计算、上线、回填,每一步都需要显式的设计和代码实现。
- 模型训练与验证:不是点一下“训练”按钮,而是自己管理实验、记录参数、评估指标,甚至要理解反向传播过程中梯度消失这种底层问题对结果的影响。
- 部署与监控:把模型打包成服务、压测、上线、监控数据漂移,这些环节在托管平台里被抹平了,但在真实项目中每一个都能让你熬掉几层皮。
从我个人的实践经验看,这种“亲手搭一遍”的最大价值,不是让你成为一个更好的算法调参师,而是让你成为一个真正懂“系统”的 AI 工程师。你会开始理解:为什么训练时 loss 降得很好,上线后却表现稀烂?为什么离线评测 AUC 很高,线上 A/B 却打不过旧策略?这些问题,如果你只用现成的 AI 平台,大概率连排查的入手点都找不到。
1.2 这种方案解决了什么问题,又避开了哪些坑
先说避开的坑。市面上现成的 AI 平台,比如各种 AutoML、低代码 ML 服务,确实能让你在几小时内跑通一个 demo。但它们的致命问题是隔离了你与底层机制。当项目进入生产环境、遇到性能瓶颈或诡异行为时,你完全没有抓手。你不知道数据在管道里经历了什么、特征是怎么对齐的、模型服务又是如何做资源调度的。
而 from scratch 的路径,强制你关注每一个环节的输入、输出和状态。它解决的正是“黑盒失控”的问题。你自己搭的管道虽然丑,但每一个阀门你都心里有数。
它避开的另一个坑是“过早依赖”。我见过太多团队,项目一开始就上了重量级框架,结果需求一变,框架反而成了束缚。从零搭建的核心组件通常足够简单、足够透明,后续你想往哪个方向演化,都有充分的自由度。有个很形象的类比:买精装房和清水房自己装修的区别。精装房拎包入住,但你想砸墙改格局的时候,发现全是承重墙;清水房前期费事,但每一面墙你都清楚能不能动。
1.3 整体技术栈与模块划分:建一栋 AI 工程的“骨架楼”
顺着 from scratch 的思路,一条完整的工程链路可以拆成六个核心模块。这个划分是我自己多年实践下来的习惯,跟项目里的结构也很接近:
| 模块 | 核心职责 | 典型技术选型区间 |
|---|---|---|
| 数据接入层 | 从业务源头获取数据,完成格式统一 | Kafka、Flink、Airflow、或者简单的 Python 定时任务 |
| 存储层 | 原始数据、中间数据、特征数据分层存放 | 对象存储、HDFS、ClickHouse、PostgreSQL、Redis |
| 特征平台 | 特征的离线计算、在线获取、一致性保障 | Feast、或者自研特征服务 + Redis |
| 训练层 | 实验管理、模型训练、超参调优、评估 | PyTorch、LightGBM、MLflow、Optuna |
| 推理服务层 | 模型加载、推理优化、服务编排 | FastAPI、Triton、ONNX Runtime、Docker + K8s |
| 监控与反馈层 | 线上指标、数据漂移检测、模型迭代闭环 | Prometheus、Grafana、SageMaker 监控、自定义漂移检测脚本 |
这张表里的每一项,你在 from scratch 的实践里都会亲手触碰一遍。哪怕最后你只会用其中一部分,这套全貌视角也能帮你极大地减少“盲人摸象”的感觉。
2. 核心细节解析与实操要点:从数据到特征的魔鬼细节
2.1 数据管道设计的核心原则:别让脏数据进入模型
数据管道是整个工程链路的“地基”。地基不牢,上层模型再花哨也白搭。我自己见过太多项目挂在数据一致性上,而不是模型精度上。
做数据管道,首先要暴力执行一条原则:输入约束大于事后清洗。意思是,你最好在数据源头就做格式和取值范围的校验,而不是等数据进了仓库再写一堆清洗脚本。源头一个字段是字符串还是数字,直接决定了你后面所有特征逻辑怎么写。我在项目里习惯性地在接入层做三层校验:
- 第一层:格式校验,字段类型、枚举值范围、时间格式是否合法。
- 第二层:完整性校验,必填字段是否缺失、主键是否唯一。
- 第三层:业务校验,比如“年龄”取值 0-120,“消费金额”必须非负。
这三层校验写起来不难,但能帮你省掉后面无穷无尽的 debug 时间。有一个非常经典的坑:上游改了字段类型,从 int 改成了 string,下游特征代码没有同步更新,模型上线后直接拿到一堆被当成类别特征的连续变量,AUC 掉得一塌糊涂。源头校验就是防这种“低级但致命”的问题的。
另外,数据管道一定要可重放。也就是说,任何一段历史数据,你给我一个时间范围,我要能重新跑一遍管道得到完全相同的结果。这要求你在管道里记录数据的版本、处理代码的版本、甚至依赖库的版本。实操上可以用类似 DVC(Data Version Control)或者 LakeFS 这类工具做数据版本管理,哪怕你不用工具,也得自己有一套“时间戳+版本号”的命名规范。
2.2 特征工程与特征存储:离线在线一致性是生死线
特征工程是 AI 工程里最“手工”的部分,但也是最能体现工程师价值的部分。from scratch 的实践里,你需要亲手实现特征的计算逻辑,而不是靠平台自动生成。
这里有三个要点,我认为是决定成败的:
第一,特征一定要有明确的定义和唯一标识。我在实践中会给每个特征一个全局唯一的 ID,类似user_avg_order_amt_7d这种语义化名字。所有离线计算和在线计算都引用同一个 ID,避免同一个特征在离线表里叫avg_7d、在线接口里叫user_avg_7d_order,最后对不上。
第二,离线在线特征一致性必须用代码保障。这是 AI 工程里最臭名昭著的坑。离线训练的时候,你可以拿到未来数据做统计;但在线推理时,你只能用到截止当前时刻的数据。如果不小心把未来信息泄露进训练集,线下指标会好看到爆炸,线上运行则完全失灵。解决思路是:离线和在线共用同一套特征计算代码,比如封装成一个FeatureCalculator类,离线用 DataFrame 批量算,在线用单条请求逐个算,但内部逻辑完全一致。
第三,特征存储要分层。原始特征、加工后的特征、实时特征,建议分开存放。实时特征放 Redis 这类 KV 存储,用来扛高并发;离线特征放列式存储如 Parquet 文件或 ClickHouse,用于训练时批量读取。这么做的好处是:训练和推理不会被彼此的存储压力拖累。
2.3 模型训练与实验管理的工程化思维:从玄学变成科学
模型训练在 from scratch 的语境下,不仅是一个算法问题,更是一个工程管理问题。很多人训练模型是靠“感觉”和“运气”:调一下学习率,跑一版;改一下网络结构,再跑一版;但从不记录每次实验的具体配置和结果差异。这么做的问题在于,你根本不知道哪一个改动真正起了作用。
我推荐的做法是,从一开始就引入实验管理工具,就算不用 MLflow 这类重量级框架,也得自己维护一套实验记录表——用 Markdown 或 CSV 都行。记录的关键字段至少有这六项:实验编号、代码版本、数据集版本、超参数全文、最终指标、关键备注。做到这一步,你才敢说自己的模型迭代是“可复现”的。
训练时要特别注意两个问题:
- 过拟合的识别时机。不要等测试集指标都出来了才判断过拟合,那样太晚了。你要在训练过程中就观察验证集 loss 曲线的拐点,一旦验证 loss 连续 N 个 epoch 不再下降而训练 loss 还在降,就应该考虑 early stopping 或正则化手段。
- 评估指标的选取要有业务含义。AUC、LogLoss 这类指标适合做模型筛选,但最终决策还得看业务指标,比如转化率、留存率、客单价。我见过不少团队沉迷于把 AUC 从 0.85 提到 0.86,结果线上 A/B 没有任何提升,白白浪费了两周算力。评估模型好不好,永远要回到“它到底帮业务解决了什么问题”这个原点。
2.4 推理服务与性能优化:让模型跑得快、跑得稳
训练好的模型只是一堆权重文件,它要产生价值,必须以服务的形式暴露给业务方调用。这个环节,from scratch 的实践通常会要求你自己搭建推理服务,而不是用现成的 Model Serving 平台。
最基本的做法是用 FastAPI 封装一个 HTTP 接口。这个接口内部做三件事:特征拼装、模型推理、结果后处理。听起来简单,实际有一堆性能坑等着你。
第一个坑:模型加载方式。千万不要在每次请求里去加载模型文件,那样 IO 开销会直接把延迟打爆。正确的做法是在服务启动时加载一次,把模型常驻内存。PyTorch 的话,推理时还需要 wrap 在torch.no_grad()里,并且把模型切到eval()模式,否则 BatchNorm 和 Dropout 在推理时会产生诡异行为。
第二个坑:推理的批量处理。单个请求推理一次,吞吐量上不去。如果业务允许,可以在服务端做动态 batching:攒够 N 个请求或等待 T 毫秒,再一起喂给模型。GPU 推理时这个优化尤其明显,吞吐量能提升好几倍。代价是单请求延迟会稍微增加,适合对延迟不极敏感的业务。
第三个坑:性能压测必须前置。不要等服务上线了才发现 QPS 扛不住,然后又匆匆忙忙加机器。我建议服务上线前就写好压测脚本,用 Locust 或 wrk 模拟真实流量形态,把吞吐、延迟、错误率都测一遍。有个很反直觉的经验:一条推理链路的最大瓶颈往往不在模型本身,而在特征拼装时的网络 IO 或数据库查询。如果不压测,你根本想不到瓶颈在哪。
3. 实操过程与核心环节实现:一次从零到线上的完整走读
3.1 需求分析与基线建立:先定“好”的标准,再动手
从零做一个 AI 工程,第一步不是写代码,而是「把业务问题翻译成机器学习问题」并确立基线。这一步骤的价值在于,它决定后续所有工作的方向和评估标准。很多人跳过了这一步,直接开始建模,最后做完才发现做偏了方向。
第一步,明确业务目标。比如一个电商推荐场景,业务目标是“提升用户的点击率”。那么对应的机器学习问题就是“给定用户和商品上下文,预测用户点击概率”。预测目标是二分类,评估指标首选 AUC,但业务决策看的是线上点击率提升幅度。
第二步,建立基线。基线模型能多蠢就多蠢,比如预测所有用户的点击率等于训练集的平均点击率。基线存在的意义不是生产线上用它,而是给后续所有模型一个“及格线”。如果你的复杂模型连基线都打不过,要么是特征没做好,要么是模型实现有 bug,要么是评估方法有泄漏,这时候就不要急着调参了,回去检查流程。
第三步,定义成功标准。离线指标和线上指标要同时定义清楚。比如离线 AUC 要达到 0.8 以上,线上点击率相对基线提升 5% 才算有效。没有这些数字,你后面的迭代就失去了方向,容易陷入无休止的“优化陷阱”。
3.2 数据准备与特征计算落地:从原始日志到一张训练表
当你拿到一堆原始日志后,你面对的是一片蛮荒之地。原始日志可能是嵌套 JSON,可能是多张表 join 出来的宽表,也可能只是简单 CSV。这里需要你亲手把它处理成「一张标准的训练表」——每一行是一个样本,每一列是一个特征,最后一列是标签。
我常用的流程是四步走:
- 探查数据。用 pandas 的
info()、describe()快速摸清字段类型、缺失率、取值分布。这个阶段的目标是发现“意外”,比如某个字段 90% 是空值、某个分类特征有上万种取值,这些都会影响后续特征设计。 - 编写特征脚本。把上一节提到的
FeatureCalculator类写出来,每个特征对应一个方法。方法内部处理缺失值填充、类型转换、归一化或分箱。这里建议所有特征逻辑写在同一个类里,方便后续复用和排错。 - 生成训练集和验证集。注意划分方式不能是纯粹随机划分,必须按时间切分,比如前 80% 的时间段做训练,后 20% 做验证。这模拟了“用过去预测未来”的真实场景,比随机划分更贴近线上表现。
- 数据质量校验。看一下训练集和验证集的特征分布是否相似,如果差异很大,说明样本选择有偏差,模型泛化会出问题。这个校验可以简单到对比每个特征的均值和方差,也可以复杂到用 PSI(Population Stability Index)来度量分布偏移。
数据准备这一步,没有任何捷径。它的目标就一个:确保喂给模型的数据是干净、对齐、无泄漏的。我认识的所有资深 AI 工程师,绝大多数时间都耗在这一步,而不是在调模型结构。
3.3 模型训练与评估流程:PyTorch 还是 LightGBM?
表格数据场景下,我自己最先尝试的总是 LightGBM,因为它在中小规模数据上训练快、效果稳、调参不敏感。但如果你要处理的是文本、图像、序列这类非结构化数据,PyTorch 才是必然选择。from scratch 的实践通常两种都会涉及,我建议你先根据自己的场景选一个主攻方向。
以 LightGBM 为例,训练的核心流程相当简洁。下面是一段我压箱底的训练代码骨架,你可以直接拿去改:
import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit # 假设 train_df 已经准备好了,X 是特征列,y 是标签列 feature_cols = [c for c in train_df.columns if c != 'label'] X = train_df[feature_cols] y = train_df['label'] # 时间序列交叉验证,模拟真实上线预测场景 tscv = TimeSeriesSplit(n_splits=5) best_params = None best_score = 0 for train_idx, valid_idx in tscv.split(X): X_train, X_valid = X.iloc[train_idx], X.iloc[valid_idx] y_train, y_valid = y.iloc[train_idx], y.iloc[valid_idx] params = { 'objective': 'binary', 'metric': 'auc', 'learning_rate': 0.05, 'num_leaves': 31, 'max_depth': 7, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 1, 'verbose': -1, 'seed': 42 } model = lgb.train( params, lgb.Dataset(X_train, label=y_train), num_boost_round=500, valid_sets=[lgb.Dataset(X_valid, label=y_valid)], callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)] ) score = model.best_score['valid_0']['auc'] if score > best_score: best_score = score best_params = params best_model = model print(f"Best AUC: {best_score:.4f}")这里有两个细节值得展开:
第一是TimeSeriesSplit的使用。很多人会用 KFold 做交叉验证,但 KFold 是随机分割,会把未来数据混进训练集。在时间序列预测或 CTR 预估这类场景里,这种“未来的泄露”会让模型评估过于乐观。TimeSeriesSplit 严格按时间顺序划分,前一段训练、后一段验证,更贴近真实线上环境的预测行为。
第二是关于early stopping。如果你只靠设定固定迭代次数,容易欠拟合或过拟合。early stopping 会在验证集指标连续 N 轮不再提升时自动停止,既能节省训练时间,也能防止过拟合。50 轮的 patience 是我实践下来比较平衡的取值,你也可以根据数据规模适当调整。
训练完成后,记得把所有实验记录归档。哪组参数跑出了最优结果、对应的数据版本是什么、特征版本是什么,这些信息在你要回滚模型或复盘时特别有用。
3.4 模型部署上线:把权重文件变成一个稳定 API
模型部署是从“能跑”到“能用”的关键一跃,也是无数理论派栽跟头的地方。最朴素也最可靠的部署方式,是用 FastAPI 把模型包成一个HTTP接口。FastAPI 的好处是性能好、自带请求校验、文档自动生成,而且对异步请求支持很友好。
下面是一个基本的部署骨架:
from fastapi import FastAPI from pydantic import BaseModel import lightgbm as lgb import numpy as np import joblib # 启动时加载模型和特征处理器 model = lgb.Booster(model_file='model.txt') feature_cols = joblib.load('feature_cols.pkl') app = FastAPI(title="CTR Model Serving") class PredictRequest(BaseModel): user_id: int item_id: int context: dict class PredictResponse(BaseModel): score: float @app.get("/health") def health_check(): return {"status": "ok"} @app.post("/predict", response_model=PredictResponse) def predict(req: PredictRequest): # 这里用同一个 FeatureCalculator 计算特征 feature_vector = feature_calculator.calculate(req) # 转成 2D 数组,LightGBM 要求输入是矩阵 X = np.array([feature_vector]).reshape(1, -1) score = model.predict(X)[0] return PredictResponse(score=float(score))这里有个关键点:@app.get("/health")这个健康检查接口一定不能省。有了它,K8s 的探活机制才能正常工作,负载均衡器才能判断服务是否“活着”。没有健康检查的话,就算模型服务已经挂了,网关层还是会把流量打进来,然后用户就收到了 502。
部署时的环境问题也要重视。本地训练环境和线上推理环境的依赖版本必须严格对齐。我踩过一个特别典型的坑:本地装的是lightgbm 3.3.0,线上环境自动装了4.0.0,结果序列化格式不兼容,模型加载直接报错。后来我学乖了,把线上依赖用pip freeze > requirements.txt锁定版本,再配合 Docker 镜像去保证环境完全一致。
3.5 监控、反馈与模型迭代:上线不是终点,而是起点
模型上线后,你只走完了 60% 的工程链路,剩下 40% 是监控与迭代。很多团队把模型上线当成了项目结束,结果就是模型上线一个月后性能悄悄衰退,业务方投诉说“推荐越来越不准了”。
监控至少要覆盖四个维度:
- 服务健康指标:请求量、延迟、错误率。这决定了系统本身稳不稳定。
- 模型性能指标:预测分数的分布、均值、方差。如果均值从 0.4 突然飙到 0.6,说明输入分布发生了巨大变化,模型可能已经不适用了。
- 特征数据分布:每个核心特征的取值分布是否和训练时一致。这里可以用 PSI 来做量化监控,PSI 超过 0.25 就通常意味着显著漂移,需要告警。
- 业务指标:点击率、转化率、GMV 等最终业务指标的变化。这是模型迭代最直接的决策依据。
监控工具链上,我比较轻量级的方案是:
- Prometheus + Grafana:收集系统指标并可视化,告警规则用 Alertmanager 配置。
- 定时脚本:每天凌晨跑一次数据分布漂移检测,生成一份简报表推送到团队 IM 群。
- 日志结构化:线上推理日志统一 JSON 格式,方便后续做离线 replay 分析,也能快速回溯某个请求的完整链路。
模型迭代的节奏我建议是“小步快跑”:数据变化不大,就只做特征更新或超参微调;业务目标变化,才考虑重新做模型结构选型或换算法。不要隔三差五就推一个新模型,每次上线都要做 A/B 测试来验证真实效果,空口说“效果更好”都是耍流氓。
4. 常见问题与排查技巧实录:这些坑我替你踩过了
4.1 离线指标与在线效果不一致
这是 AI 工程里被问得最多的问题:线下 AUC 明明 0.85,线上一测完全没效果,怎么回事?
排查方向按优先级排列,这样最省时间:
- 特征不一致。先检查在线特征拼装是否和离线计算完全一致。这是头号嫌疑犯。我在项目里专门写过一个“特征一致性校验脚本”,随机抽取一批线上请求,用离线管道重算特征,对比两者是否一致。这一个检查就揪出过好几次特征命名错位、时区处理不一致的问题。
- 样本偏差。训练数据的分布和线上真实分布是否接近?比如离线用了大量历史流量,但线上场景已经有了新玩法,数据分布早就变了。解决思路是尽量用近期的、贴合线上场景的数据做训练。
- 评估方式有泄漏。特征里是否包含了未来信息?比如用当天的数据预测当天的点击,而特征里已经包含了用户当天是否购买这种“未来”信息。这种泄漏通常来自于表 join 时的日期窗口没有卡对。检查方法也很直接:把所有时间相关特征从模型里去掉,看看指标下降多少。如果 AUC 掉得不多,那原指标里的水分一定很大。
4.2 推理延迟高居不下:如何定位瓶颈
推理延迟高的原因千奇百怪,但定位思路是有迹可循的。我建议用分段计时的办法把耗时拆开:
- 特征拼装耗时(可能包含多次 Redis 或 DB 查询)
- 模型 preprocess 耗时(文本向量化、图像缩放等)
- 模型推理耗时(纯 forward 计算)
- 后处理耗时(阈值调整、排序等)
用 Python 的time.perf_counter()在每一段前后打点,跑 1000 个请求,统计每一段的 P99 耗时。瓶颈通常一眼就能看出来:如果特征拼装占了整体耗时的 80%,那你去优化模型结构纯属浪费时间;如果模型推理耗时高,才考虑模型量化、剪枝、换更轻量的网络结构、或 GPU 动态 batching。
有一个场景我特别想吐槽:有一次业务方反馈线上接口超时率飙升,我排查到最后发现根本不是模型推理慢,而是特征服务里有个 Redis 连接池配置过小,高并发下线程全堵在 etc. 排队拿连接上。所以说,延迟排查不能只盯着模型,整个链路的每一个 IO 点都可能是瓶颈。
4.3 数据漂移:每次都在“上新模型的前夜”爆发
数据漂移是 AI 工程中最隐蔽的杀手,因为它不是 bug,而是数据本身随业务演化的自然结果。比如电商大促期间,用户行为模式完全变了;又比如新上线了一个功能模块,用户的流量分配发生了结构性改变。
我处理数据漂移的经验是三步法:
- 建立 PSI 监控。每天自动计算核心特征的 PSI,超过 0.2 就预警。不需要全特征都监控,选对业务影响最大的 top 20 个特征即可。
- 漂移告警后立即做“快速复盘”。判断漂移来源:是数据采集链路出问题了?还是业务确实变了?如果链路问题,立即修复数据源;如果业务变化,就要评估是否需要重训模型。
- 建立定期重训的节奏。不要等指标崩了才重训,而是固定每周或每两周增量重训一次。增量训练可以只使用最近的数据,配合验证集评估性能,如果验证集指标明显好于线上模型,再安排灰度上线。
4.4 一个小众但致命的坑:时区处理
时区问题看似琐碎,却会让你的特征“差之毫厘,谬以千里”。尤其是涉及跨时区业务的时候,同一个用户行为,在 A 时区是今天,在 B 时区已经是昨天了,如果特征计算没有统一时区基准,离线特征和在线特征就会不一致。
我现在所有的数据管道统一用UTC 时间存储,展示层才转本地时区。特征计算中涉及“今天”“近 7 天”之类的窗口,一律以 UTC 日期为基准。这个约定写进团队规范里,从根上杜绝了时区带来的混乱。
写在最后:AI 工程能力是靠“折腾”出来的
回到开头那个问题:从零搭一套 AI 工程链路,到底难在哪?现在我可以给出一个比较笃定的回答:难在它逼着你做一个“完整的人”,而不是流水线上的一个螺丝钉。写模型的人很多,能独立把数据管道、特征平台、训练评估、部署监控全链路串起来的人,始终是稀缺的。
我个人实际体会最深的一点是:很多工程的“手感”,光看文档是学不会的,必须自己在真实项目里撞几次墙。就像我上面写的那些坑——特征一致性校验、时区统一、动态 batching、PSI 监控——没有一次是我从教科书里读来的,全是线上故障和业务投诉教会的。
所以如果你也想走通ai-engineering-from-scratch这条路,我的建议很简单:不要只刷课程和教程,找一个真实场景的数据集,哪怕很小,亲手把这条链路从头到尾搭一遍。第一次跑通,你就有了属于自己的“AI 工程骨架”;在此基础上每次加一点细节、修一个故障,这套骨架就会长成你能倚靠的能力树。