搞了好几个月的ai-engineering-from-scratch项目,现在回头看,最庆幸的是当初没有直接套一套现成的MLOps模板,而是从最底层一步步把AI工程链路搭了起来。这个项目说白了,就是完完整整地经历一遍“AI能力从0到生产环境”的全过程:从原始数据到特征工程,从模型训练到模型服务化,从上线监控到持续迭代。今天把我踩过的坑、验证过的方案、还有最终沉淀下来的工程结构全部整理出来,不管你是刚开始接触AI工程的初学者,还是已经在做算法但没做过工程的工程师,这篇文章应该都能帮你节省至少两个月的摸索时间。
先说这个项目到底解决什么问题。很多人学AI都是从notebook开始的,但到了真要把它变成一个能稳定运行、能监控、能迭代的系统时,会发现处处都是坑:训练好的模型怎么给业务方调用?数据变了模型效果下降怎么发现?新的实验怎么管理?GPU资源怎么分配?这就是“AI工程”和“AI算法”最大的区别。这个项目就是用一套完整的实践路径,逼着你去面对这些问题、解决这些问题。
1. 为什么我从零开始搭AI工程,而不是直接抄一套现成的
1.1 “from scratch”不是重复造轮子,而是理解轮子
我相信很多人看到“from scratch”这个词的第一反应是:是不是在自讨苦吃?明明有现成的MLflow、Kubeflow、Airflow这些平台,为什么还要自己从零搭一遍?
我的看法不一样。用现成工具和从零搭建,本质上是两种学习深度。用现成工具就像是租房子,住起来舒服,但水管坏了你不会修;从零搭建就像是自己盖房子,虽然费时费力,但每一根承重墙、每一条线路你都心里有数,出了问题能快速定位。
举个例子。我在项目里用了MLflow做实验追踪,这是一个非常成熟的工具,安装完就能用。但是如果只是“安装、使用”,你永远不会理解实验管理到底在解决什么问题。直到有一天我发现同事跑了几百个实验之后根本不知道哪个模型效果最好,超参数配置全靠笔记本记录,才真正理解实验追踪的核心价值是“可复现性”。这个理解,只有当你从零开始手动记录实验、忍无可忍之后才引入MLflow时,才能真正获得。
所以这个项目虽然叫“from scratch”,但并不是说什么都要自己写代码实现,而是说要把从原始数据到模型服务的整条链路自己亲手搭一遍。链路中该用现成工具的地方就用现成工具,但是你要理解每个工具解决什么问题、为什么放在这个环节、换成别的行不行。
1.2 自建技能栈的三个核心原则
在这个项目里,我给自己定了三条原则,也是在后续踩坑过程中反复验证过的。
第一,不求大而全,但求全链路打通。很多教程上来就教你怎么用Kubernetes部署模型,但如果你连一个最基础的FastAPI服务都没写过,直接上K8s只会让你一头雾水。我的做法是先用最简单的方式把整条链路跑通:数据用Pandas处理,模型用Scikit-learn训练,部署用Flask写个API。等全链路通了之后,再组件化地替换成更强力的工具。
第二,每个环节都要留下可以直接运行的代码或配置。AI工程项目最怕的就是“看懂了但跑不起来”。所以我每做完一个环节,都会把这个环节的所有配置、代码、依赖打包保存。这既是工程的规范,也是对自己学习成果的固化。
第三,监控和评估从第一天就设计进去。很多项目都是模型上线了才发现没有日志、没有效果监控、没有数据回看机制。我在这个项目里的做法是,从写第一行代码开始就记录每个环节的指标,哪怕是简单的print输出,也要保证信息可追溯。
1.3 哪些现成工具值得保留,哪些必须自己实现
说到工具选型,我得强调一个观点:工具本身没有高下之分,只有和你的情境合不合适。
以我的项目为例,以下工具是我验证过值得保留的:
- Python环境管理:conda或者venv,二选一就好,关键是必须用。不要嫌麻烦,环境冲突的问题后期会折磨到你怀疑人生。
- 实验追踪:MLflow。它的UI虽然不算好看,但胜在功能全面,实验比较、模型注册、模型部署都有成熟的方案。
- 数据版本管理:DVC(Data Version Control)。这个工具能很好地解决“数据变来变去,模型结果对不上”的问题。
- 容器化部署:Docker。这是整个部署环节的基础,无论如何都要掌握。
而需要自己动手的部分,其实集中在业务逻辑层:特征处理代码、训练脚本、推理服务、评估逻辑、监控脚本。这些是项目真正的核心资产,不能靠现成工具替代。
还有一个重要认知:不要陷入“工具收集癖”的陷阱。我看到很多人囤了一堆工具教程,实际根本用不上。AI工程的真正能力是能够在正确的时间用正确的工具解决正确的问题,而不是会用所有工具。
2. AI工程需要掌握的技术栈,一张“知识地图”帮你梳理
2.1 核心底座:Python工程化 + Linux + Git,缺一不可
很多准备入门AI工程的朋友上来就学深度学习,这其实是本末倒置。AI工程的最底层基础是三个看上去不太炫、但决定你走多远的东西。
Python工程化,指的不仅仅是能用Pandas处理数据,更重要的是代码组织能力:写模块化的函数和类、管理依赖、用好虚拟环境、封装公共组件。我见过太多的项目是一堆notebook拼起来的,一旦需求变化,改代码跟拆炸弹一样。在ai-engineering-from-scratch项目里,从第一天起我就把代码按src/tests/configs/这样的标准目录结构组织好,这个习惯后来帮了大忙。
Linux基础同样重要。在本地Windows上写模型和到Linux服务器上跑模型完全是两回事。你需要掌握基本的文件操作、进程管理、查看GPU状态(nvidia-smi)、设置环境变量、看日志。这些技能看着基础,但到了排查线上问题的时候,就是决定性的胜负手。
Git的作用就更加不言而喻了。模型迭代、代码回滚、多人协作,每一步都离不开它。尤其是训练脚本,改了一行参数效果变化巨大,必须有版本管理才能追踪。我在项目里用的是比较标准的Git Flow模式,分支从main拉出,每个实验一个分支,验证通过后再合并。
2.2 机器学习建模:框架选择与大模型时代的调整
这两年大模型很火,但我要给新手一个明确的建议:AI工程的基础还是经典的机器学习理论和深度学习框架的掌握。ChatGPT之类的大模型能力强,但它是一个“产品”,不是你应该从零去“训练”的对象。你真正要掌握的是特征工程、模型结构、损失函数、优化器、评估指标这些核心概念,然后才是用框架把这些概念落实成代码。
框架选型上,我的建议是:
- 如果是练手项目,优先选Scikit-learn,它简单稳定,能让你的重心放在算法而非环境配置上。
- 如果做深度学习,选PyTorch而不是TensorFlow。不是我偏心,而是PyTorch对调试更友好,动态图的特性让人能一步步追踪数据流转。而且现在社区、预训练模型生态都是PyTorch占优势。
- 如果是为了特定部署场景(如移动端),可以考虑TensorFlow Lite和ONNX Runtime,但它们更适合作为推理阶段的事情来处理。
建一个合格工程需要你有能力去定义完整的实验流程,包括数据划分(train/validation/test)、固定随机种子(保证可复现)、用EarlyStopping防止过拟合、保存最佳模型权重。这些东西在框架文档里都有,但串起来的方式,就是工程经验。
2.3 部署与运维:从notebook到生产系统的跨越
很多算法工程师在建模上很强,但到部署环节就开始头疼。我见过太多模型在notebook里预测准得飞起,一上线直接被QPS打垮。原因就是你缺少了一个“服务化”的思维转变。
AI工程里讲“部署”,本质上是回答几个问题:
- 模型怎么对外提供接口?——我用的方案是FastAPI写REST API,配合Pydantic做请求参数校验。FastAPI比Flask更现代,有自动接口文档,性能也好。
- 模型运行在什么环境里?——用Docker打包成一个镜像,里面装好Python环境和所有依赖。这样不管是开发机还是服务器,跑起来的逻辑完全一致。
- 有没有考虑过推理速度?——本地随便predict一次可能要几百毫秒,你再想想线上接口并发的时候,这个速度能不能接受。通常要做模型量化、批处理(batch inference)、甚至上GPU推理。
- 模型参数怎么加载?能不能高效地服务多个模型?——这就涉及到推理服务的架构设计,简单场景可以直接加载到内存里。
在ai-engineering-from-scratch项目里,最终部署的形态是一个Docker容器,外面套一层Nginx做负载均衡,模型推理服务用FastAPI实现,支持单条预测和批量预测两种接口模式。
2.4 数据工程:AI工程里常常被低估的前置环节
数据工程,说白了是70%的时间花在处理数据上。而这件事的重要性几乎人人都承认,却几乎人人都做不好。
数据工程的完整链路包括:采集、清洗、结构化、存储、标注、版本管理。我这边的做法是:
- 采集阶段:根据业务需求写爬虫或对接API,把原始数据落到本地或云存储。
- 清洗阶段:处理缺失值、离群点、重复样本、类别不平衡。所有步骤必须写成脚本,不能靠手工操作Excel。
- 结构化阶段:把清洗好的数据统一成特征矩阵,每个特征要有明确的定义。用一个
feature_config.json文件管理特征清单,防止后面特征漂移了都不知道。 - 存储与版本管理:数据文件用DVC追踪版本,跟代码仓库关联,保证任何历史模型都能调到当时训练用的数据。
如果这些环节全都认真做好了,后面建模和部署都是水到渠成的事。反过来,很多项目后面出问题,根源往往都追溯到这里:训练集和线上数据分布不一致、特征命名混乱、用了未来数据导致数据泄漏。
3. 从零实现一个端到端AI工程项目的完整记录
接下来我挑一个完整的例子,把整条链路实际怎么走说一遍。假设我们要做一个“电商商品评论情感分类器”,输入是评论文本,输出是正面/负面/中性三分类。这个问题不复杂,但刚好能覆盖AI工程的全部核心环节。
3.1 目标定义:先定指标,再定模型
工程的第一步不是选模型,而是定义清楚“什么叫做好了”。在这个项目里,我定下了几个明确的指标:
- 准确率不低于85%,且三个类别的F1分数不低于0.80。这个要求能防止模型只讨好大类样本,直接忽略小类。
- P95推理延迟不超过150毫秒(单条文本,CPU环境)。这是给线上服务质量留的余量。
- 数据回流时间不超过24小时。也就是说,今天新增的用户反馈,明天就能变成新数据进入训练集。
我把这些指标写在项目README的第一页,这是整个项目的第一份设计文档。后续每个选择,小到清洗规则,大到模型选型,都是在为这些指标服务。
3.2 数据管线搭建:采集、清洗、标注、版本管理
数据方面,我构造了一个模拟业务场景的流程:从公开的评论数据源拉取原始数据,然后经过清洗和标注作为初始训练集。
因为我做的是全流程复现,所以要用脚本实现全自动化的数据清洗:
def clean_review(text: str) -> str: # 1. 去除HTML标签和多余空白 text = re.sub(r'<.*?>', '', text) text = re.sub(r'\s+', ' ', text.strip()) # 2. 去除URL、数字串、@提及(针对文本噪声) text = re.sub(r'http\S+|www\.\S+', '', text) text = re.sub(r'\d+', '', text) # 3. 去除无意义的标点符号,保留中英文和基础标点 text = re.sub(r'[^\w\u4e00-\u9fa5,。!?、]', '', text) return text这个函数看着简单,但在实战里很多细节会让它不断膨胀,比如表情符号处理、繁体转换、专有名词保护等。我建议一开始把清洗步骤拆成多个小函数,每一步都单独测试,后续才好迭代。
数据版本管理用的DVC。每次数据有任何变化,我用dvc add data/raw把新的数据版本记录下来,然后跟代码一起提交到Git。这样模型训练完成之后,随时可以追溯“这个模型是用哪一版数据训练出来的”。
对于标注环节,因为没有人工标注预算,我用了启发式规则加少量人工修正来生成伪标签。这里必须强调,真实项目中标注质量直接决定模型上限。如果你有预算,一定要做标注规范培训、标注一致性评估(比如Kappa系数),这个钱不能省。
3.3 模型训练:超参数选择与实验管理(MLflow)
模型选型上,我用了经典的TF-IDF + 朴素贝叶斯做baseline,然后换成BERT做深度学习方案。这个对比不是为了炫技,而是要让你理解两个关键事实:简单模型往往可以快速验证流程,深度学习模型在此基础上才能体现出增量。
训练脚本用PyTorch实现,核心配置放在一个config.yaml里:
model: name: "bert-base-chinese" num_labels: 3 training: batch_size: 16 learning_rate: 2e-5 epochs: 3 seed: 42 max_length: 128这里有一个特别容易踩的坑:batch_size和learning_rate是绑在一起的。你把batch_size从16改成32,那学习率通常也要按比例调整,否则收敛效果会变差。我一开始不知道这个,在一个项目里把batch_size调大之后忘了改学习率,模型性能直接掉了好几个点,排查了很久。
每次训练跑完之后,我用MLflow记录所有超参数和指标:
import mlflow with mlflow.start_run(): mlflow.log_params(config["training"]) mlflow.log_metrics({"accuracy": acc, "f1_macro": f1}) mlflow.pytorch.log_model(model, artifact_path="model")MLflow的自动记录功能也特别方便,mlflow.autolog()就能把框架自动检测到的参数和指标都记录下来。但有一个坑:它记录的指标可能不够详细,比如没有每个类别的F1分数,所以关键的自定义指标最好还是手动记录。
训练之后一定要做模型评估报告,不仅仅看总体准确率,还要看混淆矩阵、每个类别的精确率和召回率、以及从错误样例中发现改进方向。比如我发现模型把“物流太慢了”预测为负面,这是对的,但把“包装完好,物流一般”预测为负面,那就是一个错误——因为“一般”这个词在中文语境下有时是中性偏轻微负面,模型很难区分。这提醒我后续应该增加更多这种带转折结构的中性样本。
3.4 模型部署:FastAPI + Docker + GPU推理服务
模型训练好之后,下一步是把它变成一个可以被业务系统调用的服务。我用FastAPI搭建了一个简单的推理服务:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class ReviewRequest(BaseModel): text: str class PredictionResponse(BaseModel): label: str probabilities: dict @app.post("/predict", response_model=PredictionResponse) def predict(req: ReviewRequest): proba = model.predict_proba([req.text])[0] label = labels[int(proba.argmax())] return PredictionResponse( label=label, probabilities={labels[i]: float(p) for i, p in enumerate(proba)} )这里有几个容易被忽视的细节:
- 服务启动的时候要花几十秒加载模型。为了不让每次首次请求都等很久,我选择在模块加载时就提前
load_model(),而不是在请求处理时才加载。 - 请求的输入必须做校验。Pydantic帮你解决了类型问题,但业务上还要考虑文本超长、空文本这些情况,防止模型被脏数据打崩。
部署使用Docker,Dockerfile如下:
FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]这里我用的是CPU镜像,因为一开始不需要GPU推理,简化部署复杂度。等积累到更高并发需求时,再切GPU服务和更复杂的推理优化。基础架构搭建阶段,不要太早优化。
3.5 上线后的监控与迭代:日志、指标、漂移检测
很多项目死在“上线即终点”。模型上线之后效果一定会随时间变化,这是AI工程里必须接受的事实。所以我在部署服务的同时,把监控体系也一起搭起来。
监控分三层:
- 系统层:CPU、内存、请求量、延迟。用Prometheus + Grafana就能解决。
- 服务层:每个接口的调用量、单次推理耗时、异常率。这个在应用代码里打日志就能记录。
- 模型层:真实线上数据分布变化、预测置信度变化、回流标签与模型预测的一致性。
模型层里最有价值也最难做的,是数据漂移检测。一个简单有效的方法是,记录训练集的特征分布(比如TF-IDF向量的均值),然后在线上实时对比实时数据的分布差异。差异超过阈值就告警。
另外一个实操技巧是:每次模型服务收到的预测请求和预测结果都要落库,并定期抽取一部分给人工打标。这些数据攒够一批,就是下一轮模型迭代的训练集。这就是“数据飞轮”。没有这个机制,你的模型就是一瓶不改配方的药,迟早失效。
4. AI工程里的常见陷阱与排查速查表
这部分我整理了我在这个项目中遇到的30多个坑,精选出最值得说的几类,做成一张速查表。对标你自己的项目,一条条对照排查。
4.1 环境与依赖
| 问题 | 症状 | 解决方案 |
|---|---|---|
| Python环境冲突 | 安装A包导致B包不能用 | 立刻用conda创建独立环境,每开新项目就建新环境,不要偷懒 |
| CUDA版本不匹配 | PyTorch检测不到GPU | nvidia-smi看到的CUDA版本是驱动支持的版本,torch.cuda.is_available()才是实际能用,用PyTorch官方推荐的CUDA版本安装 |
| 依赖没有锁定版本 | 过一阵子重跑安装了一堆新版本,结果行为完全变了 | 用requirements.txt或Pipfile严格锁定版本,最好用poetry管理 |
4.2 数据与特征
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 数据泄漏 | 训练集指标高得离谱,线上效果差 | 严格按时间序列划分数据,确保特征中没有使用未来信息 |
| 特征分布漂移 | 线上预测结果集中偏向某一类 | 定期比较特征均值/方差,设置漂移告警 |
| 数据版本不清晰 | 模型和当时的训练数据对不上 | 引入DVC,数据版本跟Git提交强关联 |
4.3 训练与优化
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 过拟合 | 训练集指标高,验证集指标低 | 加正则化、数据增强、早停或降低模型复杂度 |
| 学习率跟batch size不匹配 | 模型loss震荡不降 | 记住lr与batch size大致呈线性缩放关系 |
| 随机种子不一致 | 每次训练结果差很多 | 在数据划分、数据加载、模型初始化三处都设置固定seed |
4.4 部署与性能
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 首次请求特别慢 | 服务起来后第一次调用要等很久 | 在启动阶段就加载模型,不要懒加载 |
| GPU显存不够 | OOM、CUDA out of memory | 减小batch size、用梯度累积、换更小模型或做模型量化 |
| CPU推理太慢 | 并发上来后延迟直线上升 | 上batch inference,或用ONNX Runtime加速推理 |
4.5 工程协作与迭代
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 模型复现困难 | 同事跑同一个脚本结果不同 | 代码、数据、超参数、环境四方面全部锁定 |
| 没有模型版本管理 | 线上模型和实验模型搞混了 | MLflow Model Registry,注册每个生产模型版本 |
| 缺少回滚机制 | 模型上线后发现效果差无法快速回退 | 部署时保留前一版模型,用负载均衡做灰度发布 |
个人实操中的一点体会
说了这么多,最后我想讲一个我在整个过程里感触最深的事情。ai-engineering-from-scratch这个项目最难的部分,从来不是某个模型算法的数学原理,而是面对不确定性的决策能力:数据到底要清洗到哪一步算够?模型是花时间调参还是赶紧部署上线再看效果?线上数据漂移了是先补数据还是先降阈值?这些问题的答案没有对错,只有基于数据的判断。而工程化能力,本质上是把你做判断的依据沉淀成可验证、可回溯、可迭代的流程。
最后分享一个小技巧:每周固定留一小时,看一遍线上服务的日志和指标。不要等报警了才去看。你会从中发现很多有意思的信号——比如某个用户群体反复调用、某个特征值分布已经悄悄改变、某个接口的响应时间在缓慢爬升。这些细节,才是你下一步迭代方向的来源。AI工程的功夫,一半在搭建时,一半在维护时。