
1. 从零搭建AI工程能力为什么大多数人卡在“会调包”这一步“ai-engineering-from-scratch”这个标题我第一次看到的时候脑子里蹦出来的不是某个具体项目而是一类人他们能熟练地pip install各种框架能照着教程跑通一个图像分类或者文本生成的 demo但一旦让他们从零搭一个能用的 AI 工程流水线就立刻卡壳。这不是个别现象而是当前 AI 学习路径里一个非常普遍的结构性断层。我自己带过不少刚入行的同学也见过很多工作两三年、想从传统后端或数据分析转 AI 工程的人。他们最大的问题不是不懂模型原理而是不懂“工程”这两个字在 AI 场景下到底意味着什么。学术界教你的是如何设计一个网络、如何调参让指标涨两个点但工业界要的是数据怎么进来、特征怎么存、模型怎么训、训完怎么部署、部署完怎么监控、监控到异常怎么回滚。这一整条链路才是 AI 工程的核心。所以这篇内容我想把“从零构建 AI 工程能力”这件事拆开揉碎讲清楚。它适合三类人第一类是完全没接触过 AI 工程、但有一定编程基础想入行的第二类是会跑 demo 但没做过完整项目的第三类是有后端或数据工程经验、想补齐 AI 侧工程能力的。我会尽量用从业者的视角把每个环节“为什么这么做”“不这么做会怎样”“实际踩过什么坑”都讲透而不是只给一堆工具名和命令。需要先明确一个认知AI 工程不等于机器学习。机器学习关注的是模型本身AI 工程关注的是让模型在真实业务里稳定、可维护、可扩展地跑起来。这两者的关系有点像“造发动机”和“造整车”——发动机再强没有传动、底盘、电控也上不了路。很多人学 AI 就是一直在学怎么造更好的发动机却从来没想过整车怎么组装。2. 环境与工具链的从零搭建别一上来就装一堆用不上的东西2.1 为什么我不建议新手直接上全套 MLOps 平台很多教程一上来就让你装 MLflow、Kubeflow、Feast、Airflow 这一整套仿佛不搭个“平台”就不叫 AI 工程。我实测下来的结论是如果你连一个完整的训练-推理闭环都没跑通过装这些只会让你在配置上耗掉两周然后放弃。正确的做法是分层搭建。第一层是语言和基础库Python 3.10 以上、NumPy、Pandas、scikit-learn。第二层是深度学习框架选一个就行PyTorch 目前生态最友好。第三层是实验管理先用最轻量的方式——比如手动记录到 CSV 或者用 TensorBoard。第四层才是部署和服务化。第五层才是自动化和监控。这个顺序不能乱。我见过太多人跳过前三层直接搞第四层结果模型本身都没调明白就开始折腾 Docker 和 K8s最后两头都没落地。2.2 虚拟环境与依赖锁定一个被严重低估的工程习惯从零做 AI 工程第一个必须养成的习惯是每个项目独立虚拟环境并且锁定依赖版本。这不是洁癖是血泪教训。AI 领域的库版本兼容性极其脆弱PyTorch 2.0 和 2.1 在某些算子上的行为可能就不一样transformers 库一个小版本升级可能就改了默认参数。我推荐用conda或者venv建环境然后用pip freeze requirements.txt或者conda env export锁定。但这里有个坑pip freeze会把所有间接依赖都写进去有时候反而导致跨平台装不上。更稳的做法是用pip-tools维护一个requirements.in只写直接依赖然后编译出锁定的requirements.txt。# 推荐做法 python -m venv .venv source .venv/bin/activate pip install pip-tools # requirements.in 里只写直接依赖 pip-compile requirements.in -o requirements.txt pip-sync requirements.txt这样做的价值在于半年后你或者同事要复现这个项目能保证装出来的环境和你当时一模一样。AI 项目最怕的就是“在我机器上能跑”。2.3 目录结构工程能力的第一个外显标志一个人是不是真的做过 AI 工程看他项目目录结构就能猜个八九不离十。新手通常把所有代码堆在一个main.py或者几个 notebook 里有工程经验的人会有清晰的分层。我常用的结构是这样的project/ ├── configs/ # 配置文件yaml 或 json ├── data/ │ ├── raw/ # 原始数据只读 │ ├── interim/ # 中间处理结果 │ └── processed/ # 最终训练用数据 ├── src/ │ ├── data/ # 数据加载与预处理 │ ├── features/ # 特征工程 │ ├── models/ # 模型定义 │ ├── train.py # 训练入口 │ ├── predict.py # 推理入口 │ └── utils/ # 通用工具 ├── notebooks/ # 探索性分析不参与生产 ├── tests/ # 单元测试 ├── requirements.in └── README.md这个结构的关键在于data分三层src按职责分模块notebooks明确隔离。很多人把 notebook 里的代码直接复制到生产这是大忌。notebook 适合探索但它的执行顺序是隐式的、状态是混乱的不能作为工程代码。注意data/raw目录一定要设为只读任何清洗和转换都输出到interim或processed。这样你永远可以回溯到原始数据不会因为一次错误的覆盖而丢失数据源。3. 数据管道的工程化AI 项目 80% 的坑都在这里3.1 数据版本管理为什么 git 管不了数据做过传统软件工程的人转 AI第一个不适应就是代码可以用 git 管数据怎么办一个数据集动辄几个 G放 git 里直接把仓库撑爆。但数据又必须可追溯——你三个月前训的那个模型用的是哪一版数据我试过几种方案。最简单的是用 DVCData Version Control它把大文件存在远程存储git 里只存指针。另一种是用时间戳或哈希命名数据快照配合一个元数据表记录。小团队我推荐后者简单直接data/processed/ ├── train_20240115_a3f2c1.parquet ├── train_20240120_b7e4d9.parquet └── metadata.csv # 记录每个文件的来源、处理脚本版本、行数、字段metadata.csv里至少要有文件名、生成时间、上游数据版本、处理脚本的 git commit、样本数、关键字段的统计摘要。这样出问题时能快速定位是哪一步引入的。3.2 数据校验别等模型训完才发现数据有问题这是我最想强调的一点。很多人拿到数据直接喂给模型训了几个小时发现 loss 不降回头一查发现某列有大量空值或者异常值。数据校验必须前置。我常用的校验维度包括字段是否存在、类型是否正确、取值范围是否合理、缺失率是否超标、类别分布是否偏移。可以用pandera或great_expectations这类库也可以自己写简单的断言。import pandera as pa from pandera import Column, DataFrameSchema, Check schema DataFrameSchema({ user_id: Column(int, Check.greater_than(0)), age: Column(int, Check.in_range(0, 120), nullableTrue), label: Column(str, Check.isin([A, B, C])), }) # 校验不通过直接抛异常 validated_df schema.validate(raw_df)关键理念是数据校验失败应该让流程中断而不是打个 warning 继续跑。因为脏数据进入训练产出的模型就是不可信的后面所有工作都白费。3.3 特征存储与训练/推理一致性最隐蔽的坑AI 工程里有一个非常隐蔽但杀伤力极大的问题训练时特征计算方式和推理时不一致。比如训练时你用全量数据算了一个归一化的均值方差推理时却用单条数据自己算结果分布完全对不上模型效果暴跌。解决这个问题的核心思路是把特征计算逻辑抽成独立的、可复用的模块训练和推理都调用同一份代码。更进一步用特征存储Feature Store来统一管理。小团队不一定上 Feast 这种重型工具但至少要有一个features.py里面每个特征函数都是纯函数输入原始数据输出特征值。# features.py def compute_age_bucket(age: int) - str: if age 18: return minor elif age 60: return adult else: return senior # 训练和推理都 import 这个函数还有一个细节归一化用的均值、方差、分位数这些统计量必须作为模型 artifact 一起保存推理时加载而不是重新计算。我习惯把它们存成一个preprocessor.pkl和模型文件放一起。4. 模型训练与实验管理让每一次实验都可复现4.1 配置驱动把超参数从代码里赶出去新手写训练脚本超参数通常直接硬编码在代码里改一次跑一次。这样做的后果是你跑了二十组实验最后记不清哪组对应哪个结果。正确做法是配置驱动所有超参数写进 yaml训练脚本只读配置。# configs/exp_001.yaml model: name: resnet18 num_classes: 10 train: batch_size: 64 lr: 0.001 epochs: 30 optimizer: adam data: train_path: data/processed/train_20240115_a3f2c1.parquet val_split: 0.2训练入口接收配置路径把配置内容、git commit、开始时间、环境信息一起记录到实验日志。这样任何一次实验都能精确复现。4.2 实验追踪轻量方案往往比重型平台更实用实验追踪工具我用过不少MLflow、Weights Biases、TensorBoard。我的建议是个人或小团队先用 TensorBoard 加一个 CSV 日志就够了。MLflow 适合需要集中管理多人的场景但它本身也需要维护一个服务有额外成本。不管用什么工具要记录的核心信息是固定的超参数、每轮的训练/验证指标、最终模型路径、数据版本、代码版本。我习惯在训练脚本里加一段import json, subprocess, datetime run_meta { config: config, git_commit: subprocess.check_output([git, rev-parse, HEAD]).decode().strip(), start_time: datetime.datetime.now().isoformat(), data_version: config[data][train_path], } with open(fruns/{run_id}/meta.json, w) as f: json.dump(run_meta, f, indent2)这个meta.json就是实验的身份证任何时候都能查。4.3 随机种子与可复现性别小看这一行代码AI 实验的不可复现性很大一部分来自随机性。Python 的 random、NumPy 的 random、PyTorch 的 random、CUDA 的随机全都要固定。而且要注意即使全部固定某些 CUDA 算子仍然是非确定性的需要额外设置。import random, numpy as np, torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsecudnn.deterministic True会牺牲一点性能但换来可复现性做实验阶段非常值得。上线推理时可以关掉。提示可复现性不是绝对的跨不同硬件、不同驱动版本仍可能有微小差异。工程上追求的是“同一环境下可复现”而不是“任何环境都完全一致”。5. 从模型到服务部署环节最容易翻车的地方5.1 模型序列化pickle 不是唯一选择也不总是好选择训练完的模型怎么存很多人直接用torch.save(model, path)存整个模型对象。这样做的问题是它依赖模型类的定义如果代码重构了类名或结构加载就失败。更稳的做法是只存state_dict加载时先实例化模型结构再 load。# 保存 torch.save(model.state_dict(), model.pt) # 加载 model MyModel(config) model.load_state_dict(torch.load(model.pt)) model.eval()对于跨框架或者需要长期归档的场景ONNX 是更好的选择它把模型结构和权重一起序列化不依赖原始训练代码。但 ONNX 对某些自定义算子支持有限转换时可能报错需要权衡。5.2 推理服务FastAPI 是性价比最高的起点部署推理服务我强烈推荐从 FastAPI 开始。它轻量、异步支持好、自带文档几十行代码就能起一个可用的服务。from fastapi import FastAPI from pydantic import BaseModel import torch app FastAPI() model load_model() class Request(BaseModel): features: list[float] app.post(/predict) def predict(req: Request): x torch.tensor([req.features]) with torch.no_grad(): out model(x) return {prediction: out.argmax().item()}但这里有几个工程细节必须处理模型加载只做一次放在启动时不要每次请求都加载、输入校验pydantic 帮你做了、异常处理模型推理失败要返回明确错误而不是 500 堆栈、批处理支持高并发下单条推理效率极低。5.3 容器化Docker 不是可选项是必选项“在我机器上能跑”这个问题的终极解法就是容器化。AI 项目的 Docker 镜像有个特殊难点CUDA 和深度学习框架的版本匹配。我建议直接用官方的基础镜像比如pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime不要自己从 ubuntu 开始装。Dockerfile 的写法也有讲究依赖安装和代码复制分开利用镜像层缓存。依赖不变时改代码不需要重装依赖。FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY src/ ./src/ COPY configs/ ./configs/ CMD [uvicorn, src.serve:app, --host, 0.0.0.0, --port, 8000]镜像大小也是个实际问题。训练镜像可能几个 G推理镜像要尽量精简用runtime而不是devel基础镜像能省不少空间。6. 监控、回滚与持续迭代上线只是开始6.1 数据漂移监控模型不会突然变差是数据先变了模型上线后效果下降绝大多数情况不是模型坏了而是输入数据的分布变了。这叫数据漂移。监控数据漂移核心是监控输入特征的统计分布和训练时的基准分布做对比。常用的指标是 PSIPopulation Stability Index或者 KL 散度。PSI 计算简单工程上好落地def psi(expected, actual, buckets10): breakpoints np.percentile(expected, np.linspace(0, 100, buckets 1)) expected_counts np.histogram(expected, breakpoints)[0] / len(expected) actual_counts np.histogram(actual, breakpoints)[0] / len(actual) expected_counts np.clip(expected_counts, 1e-6, None) actual_counts np.clip(actual_counts, 1e-6, None) return np.sum((actual_counts - expected_counts) * np.log(actual_counts / expected_counts))PSI 小于 0.1 认为分布稳定0.1 到 0.25 需要关注大于 0.25 就要告警了。这个阈值不是绝对的要根据业务敏感度调整。6.2 模型性能监控没有标签时怎么办有监督模型上线后真实标签往往延迟才能拿到甚至拿不到。这时候怎么监控模型性能两个思路一是监控代理指标比如推荐系统的点击率、风控系统的通过率二是监控预测分布的稳定性如果模型输出的分布突然偏移往往意味着输入有问题。我实际项目里的做法是预测分布监控 业务代理指标 定期人工抽样评估三者结合。单靠任何一个都不够可靠。6.3 回滚机制上线前就要想好怎么退这是很多团队忽略的一点新模型上线效果不好怎么快速回滚如果每次回滚都要重新部署、重新加载那故障时间会很长。我的建议是模型版本化管理服务启动时加载多个版本通过配置或接口切换。这样回滚只是改一个配置项秒级生效。models/ ├── v1.0.0/ │ ├── model.pt │ └── preprocessor.pkl ├── v1.1.0/ │ ├── model.pt │ └── preprocessor.pkl └── current - v1.1.0 # 软链接切换版本就是改这个链接配合一个健康检查接口新版本上线后先小流量灰度观察指标正常再全量。这套流程听起来重但真出问题时能救命。7. 我在从零构建 AI 工程能力过程中踩过的几个真实坑第一个坑是过早优化。我刚开始做 AI 工程时总想着一步到位搭一个“完美”的流水线结果花了两周搭架子模型本身还没跑通。后来我学乖了先用最土的办法把闭环跑通哪怕数据是手动拷的、模型是脚本硬编码的先让整个链路能跑。跑通之后再逐步替换每个环节用工程化的方式重构。这个顺序非常重要因为只有闭环跑通了你才知道每个环节真正的痛点在哪。第二个坑是忽视数据质量。我曾经做过一个项目模型在验证集上指标很好上线后效果一塌糊涂。排查了三天最后发现是训练数据里有一批样本的标签是错的而验证集恰好没覆盖到那批数据。从那以后我养成了两个习惯一是数据校验必须前置且严格二是训练集和验证集的划分要按时间或业务维度不能简单随机分。第三个坑是低估了推理性能的重要性。训练时 batch size 开大GPU 跑满感觉很爽。但上线后是单条请求延迟要求 100ms 以内这时候才发现模型太大、预处理太慢。所以从项目一开始就要考虑推理场景的约束模型选型不能只看精度还要看延迟和吞吐。第四个坑是文档和交接。AI 项目的人员流动很常见如果代码没有文档、实验没有记录、数据没有说明接手的人要从头猜。我现在坚持每个项目至少有三份文档README 讲怎么跑、DATA.md 讲数据来源和处理、EXPERIMENTS.md 记录关键实验和结论。这三份文档花不了多少时间但能省下后面无数沟通成本。8. 给想从零入门的同学一条可执行的路径如果你现在完全没做过 AI 工程我建议按这个顺序走每一步都要动手做出来不要只看。第一步用 scikit-learn 做一个完整的分类项目从数据加载、特征处理、训练、评估到保存模型全部写在一个脚本里。目标是理解 AI 项目的基本流程。第二步把第一步的脚本重构成模块化的代码拆成 data、features、models、train 几个模块用配置文件管理参数。目标是理解工程化组织。第三步用 FastAPI 把模型包成服务写一个简单的客户端调用。目标是理解训练和推理的差异。第四步用 Docker 把服务容器化确保在另一台机器上能一键跑起来。目标是理解环境一致性。第五步加数据校验、实验记录、监控指标。目标是理解生产级 AI 系统需要什么。这五步走完你对 AI 工程的理解会超过大部分只会调包的人。每一步都会遇到问题而解决问题的过程就是能力真正增长的过程。工具会变框架会更新但这条链路的工程思维是不变的。