1. 先把“从零开始做AI工程”这件事想清楚
几年前我第一次看到“ai-engineering-from-scratch”这个项目代号时,第一反应是:这不就是再走一遍“Python + 机器学习 + 调参侠”的老路吗?后来真正完整带过几个从0到1的AI项目后,我才意识到自己当时想得太简单了。所谓“从零开始”,不是在沙滩上堆一个模型沙雕,而是要系统地把一条数据流、训练流、部署流、监控流全部打通。
AI工程(AI Engineering)的本质是一套围绕模型生命周期的系统工程,而“from scratch”意味着你没法靠现成的部署模板或全自动平台混过去,必须亲自动手把每个环节做得“能解释、能被运维、能迭代”。这个过程适合三类人:一是刚入行的算法工程师,发现自己写的模型在别人机器上跑不起来;二是被大模型工具搞得有点焦虑的传统研发,想搞懂线上AI应用到底是怎么串起来的;三是带团队的技术负责人,需要一张可执行的工程路线图,而不是零散教程。
如果你现在手头有个“做一个智能客服”“做一个图片分类系统”的任务,这篇文章恰好对应那个从零到落地的全过程。我不会只讲理论,会把工具选型、完整实操、踩坑记录、排查思路全部摊开。你看完至少能获得一条可以照着走下去的技术主路径,以及哪些坑不必再亲自踩一次。
2. 从零起步的AI工程,到底要打哪些基础
2.1 AI工程和算法研究的分工,先别混为一谈
很多初学者会把“会训练模型”等同于“会做AI工程”,这是第一个认知误区。算法研究的重点是探索新的模型结构、损失函数或者新的SOTA,评价标准是论文或者Benchmark;而AI工程的重点是稳定、可复现、可扩展地让模型在真实业务里产生价值,评价标准是线上的准确率、延迟、成本、可维护性和监控覆盖度。
我举个例子:你在Jupyter Notebook里用笔记本跑通了一个情感分类模型,预测效果很好。然后你要把这套东西交给部署同事,才发现特征处理是写死在Notebook全局变量里的、模型路径是写死的、依赖包版本没锁、连数据切分随机种子都没指定。这种状态离“AI工程”还差得很远。工程化要求你把训练脚本、配置、数据、模型产物、推理服务全部做成模块化、版本化、可重放的流程。
也就是说,从零开始学AI工程,第一条要纠正的认知是:你的交付物不是一个模型文件,而是一套“再培训或再部署”也能跑通的系统。
2.2 编程、数学和数据结构,哪些必须补
从零起步,基础三件套绕不开:Python、线性代数/概率统计、基础数据结构与算法。
Python里需要重点掌握的是虚拟环境管理、面向对象、类型注解、装饰器,以及pandas/numpy/matplotlib的基本操作。虚拟环境建议一开始就养成习惯,我见过太多人把不同项目依赖装到一个环境里,最后连启动都报错。类型注解看起来麻烦,但对工程协作帮助巨大。装饰器在写推理服务的鉴权和缓存逻辑时会经常用到,建议至少理解@wraps和带参数装饰器。
数学部分不用钻太深,但有一条底线:你能够看懂模型输出的分布和损失曲线的含义,理解为什么交叉熵适合分类、为什么召回率和精确率经常此消彼长。线性代数掌握矩阵乘法、维度变化、向量相似度计算就够了。在后续做向量检索或者多模态特征对齐时,这些知识会随时派上用场。
数据结构也不是为了面试,而是为了让你理解数据在内存里如何组织。比如你要处理一亿条用户行为日志,用Pandas来读会导致内存爆掉,这时候你就需要知道分块读取、列式存储、索引等基本手段。类似这些场景不是靠刷题,而是靠实际工程中反复接触才能建立起来的直觉。
2.3 机器学习与深度学习基础:该懂到什么程度
不少教程一上来就让人啃“花书”或者读原始论文,这对从零开始的人是一种劝退。我比较推荐先掌握“最小的系统性闭环”:先从最简单的线性回归和逻辑回归开始,亲手用sklearn跑一遍;然后自己动手实现一个多层感知机在MNIST上分类,不需要写反向传播(那是研究者的工作),但要清楚每一层的输入输出维度变化。
进入深度学习训练时,要真正理解几个概念:损失函数、优化器、学习率、Batch Size、正则化、验证集与测试集、过拟合与欠拟合。这些是工程调优的基础,你现在不搞明白,之后面对“训练Loss不降”“测试集比训练集好太多”这些bug时就会一头雾水。
在大模型时代,我们确实可以调现成的模型,但“从零开始”的意义在于:当你调用某个预训练模型或者云端大模型API时,你至少能读懂它的输入输出、知道ReRanker在链路上的作用、理解温度参数对生成的影响。如果连基础都没有,那么后续所有高级技巧都会变成空中楼阁。
3. 技术栈与工具选型:我的AI工程“舰队”
3.1 语言与深度学习框架:为什么首选Python和PyTorch
Python是AI工程的默认语言,没有太多解释空间。生态基本都长在Python上:数据处理、模型训练、服务器部署、云平台API,切换语言的成本远大于收益。
深度学习框架我在实际项目里只推荐PyTorch,原因很具体:一是动态图方便调试,也能逐层打印Tensor的形状变化;二是社区现在几乎以PyTorch为主,看开源项目源码相比TensorFlow好理解得多;三是Hugging Face生态和PyTorch深度绑定,微调大模型基本是默认模式。如果你接手的项目是TensorFlow老代码,不要立刻重构,先稳定跑通再考虑迁移。
还有一个小建议:框架掌握程度不用追求“手写注意力机制”,但一定要会看报错信息、会调试DataLoader、会用torch.utils.tensorboard记录指标。这些能力在真正做项目时比背模型结构更有用。
3.2 数据处理与实验管理:别在看不见的地方省力
数据处理工具上,中小数据量我直接用Pandas加NumPy;数据量一旦超过单机内存,就会换成Polars或者Spark。Polars是后来者,优点是Rust写的高性能DataFrame,语法比Pandas更严格也更清晰。不过Pandas的资料和教程仍然最多,新手可以先用Pandas,等遇到性能瓶颈再迁。
实验管理是很多从零开始的人最容易忽略的。我强烈建议从第一个正式项目开始就使用MLflow或者W&B这类工具。MLflow是开源的,能记录每次实验的代码、数据版本、超参数、指标、模型产物,对团队协作特别重要。W&B的UI做得更舒服,适合个人使用。为什么要记录实验?因为你不可能一次调好,回头想复现“前几天那个效果不错的模型”时,没有记录就只能凭记忆猜,这种痛苦相信谁体验过谁知道。
3.3 MLOps基础组件:Docker、Git与云端服务
从零到上线,绕不开Docker和Git,这两个基础工具决定你的项目能否被其他人接手。
Docker解决的是“在我机器上能跑”问题。我的习惯是:项目根目录放Dockerfile,将所有依赖锁版本并安装,启动时执行训练或推理脚本。这样做的好处是,不管在一台新服务器还是同事电脑上,只要构建镜像就能复现环境。Git则在团队协作时承担代码和配置的版本管理,分支策略不用太复杂,main分支保持可部署,feature分支开发完合并即可。
云端服务方面,如果预算有限,个人学习不用一开始就上Kubernetes,一台带GPU的云服务器就够用了。你需要掌握基本操作:SSH连服务器、安装NVIDIA驱动与CUDA、创建conda环境、把服务用systemd或docker run托管起来。把这些基础打牢之后,再上Kubernetes或者其他容器编排平台,会顺畅很多。
3.4 大模型应用组件:从预训练权重到云端API
近几年AI工程添加了不少大模型相关组件,如果你是纯从零,这部分可以分成两条路走:一是开源权重微调路线,涉及HF Transformers、PEFT、量化和推理优化(如vLLM);二是云端模型API路线,主要涉及Prompt工程、函数调用、RAG和向量数据库。
个人学习时,我建议先把开源路线走一遍,因为你会更深入理解底层机制,也便于本地调试。可以选一个7B参数级别的开源模型做领域微调,尝试LoRA技术,再用vLLM提供OpenAI兼容的接口。这个过程会把你对模型加载、tokenizer、KV Cache的理解补齐。之后再去用云端模型API,会感觉很轻松,因为你已经知道背后大致发生了什么。
4. 完整实操:从零搭建一个可上线的文本分类系统
4.1 场景拆解与任务选型
这里我以一个“客服工单自动分类”项目为例,因为它数据容易构造、任务边界清晰,非常适合做端到端演示。需求简单说:输入一段用户反馈文本,系统输出它属于哪个问题类别,比如“账号问题”“支付问题”“退换货”“其他”,并且得分超过阈值才进入自动化处理,否则转人工。
选这个任务是因为它既传统又现代:可以用BERT这类分类模型做得很稳,也可以换成大模型API的方式;既能体验完整工程链路,又不会像训练大模型那样需要夸张资源。
关键步骤是先把非功能需求定下来:预测延迟在500毫秒以内,分类准确率在0.92以上,支持QPS 50;异常输入要返回“无法分类”而不直接报错。有了这些指标,后续所有技术选型就有了依据,避免为了炫技而过度设计。
4.2 环境准备与数据规范化
我先创建项目目录ai-engineering-from-scratch,并初始化conda环境:
conda create -n aieng python=3.10 -y conda activate aieng pip install torch transformers datasets scikit-learn pandas fastapi uvicorn docker mlflow数据规范化是工程里最琐碎但最要命的环节。原始工单文本可能是全角半角混用、大小写不一致、甚至带有HTML标签或手机号。我先做基础清洗函数,统一转小写、去空白、保留中英文标点。然后划分训练集、验证集和测试集,比例用8:1:1,切分时固定random_state=42。
这一步看起来简单,但我要提醒一个容易忽略的问题:必须按“用户维度”切分,不能直接按行切分。否则同一个用户的多条工单会被同时分到训练集和测试集,造成数据泄漏,线上效果与测试效果差距巨大。这是我从真实项目里踩过的第一个有代表性的坑。
4.3 模型微调:从BERT到分类头
由于是中文文本分类,我选择bert-base-chinese作为基础模型。使用Hugging Face的Trainer来管理训练循环,能够省掉很多样板代码。
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments from datasets import Dataset tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") model = AutoModelForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=4) def tokenize(example): return tokenizer(example["text"], truncation=True, max_length=128) train_dataset = Dataset.from_pandas(train_df).map(tokenize) eval_dataset = Dataset.from_pandas(eval_df).map(tokenize) training_args = TrainingArguments( output_dir="./checkpoints", evaluation_strategy="epoch", learning_rate=2e-5, per_device_train_batch_size=16, num_train_epochs=3, logging_dir="./logs", save_strategy="epoch", load_best_model_at_end=True, metric_for_best_model="accuracy", ) trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, ) trainer.train()这里有几个参数值得展开说。学习率设成2e-5,是因为BERT这类预训练模型在微调时对学习率非常敏感,太大会导致灾难性遗忘,直接在几个epoch内把原有知识冲掉;Batch Size取16是根据单卡显存大小定的,如果显存不足可以先改成8,同时把梯度累积设置为2,保持等效批量不变。max_length=128是平衡速度和效果,工单文本平均长度大多不超过100个字符,设得越长,计算量越大,收益也有限。
我在训练过程中会重点关注验证Loss和准确率,准确率是一个粗指标,还要多看每个类别的召回率和精确率。如果某一类样本太少,比如“退换货”只有50条,就需要加权采样或修改损失函数权重。从零开始的团队容易在这里翻车,只看总准确率以为效果很好,线上一跑才发现少数类全错了。
4.4 模型评估与测试集校验
训练结束后,不能急着部署。先在测试集上做完整评估,不仅要看整体准确率,还要看混淆矩阵。
from sklearn.metrics import classification_report, confusion_matrix preds = trainer.predict(test_dataset) y_true = test_df["label"].values y_pred = preds.predictions.argmax(-1) print(classification_report(y_true, y_pred)) print(confusion_matrix(y_true, y_pred))如果发现绝大多数错误集中在某两个相似类别之间,比如“支付问题”和“账号问题”大量互相混淆,那解决方案往往不是继续调模型,而是去看训练数据标注质量。我见过很多次所谓“模型效果不够好”,其实是标注人员本身对类别边界理解不一致。这时候你应该找业务方重新确认标注规则,必要时清洗或重标一批数据。工程问题往往最后落到数据而不是模型上,这个经验很重要。
另外要检查预测置信度分布。如果所有样本的置信度都很高,但测试集错误照样存在,说明模型的置信度校准有问题,后续在做自动化和人工转派时,就不能单纯依赖置信度阈值。一种辅助方式是额外训练一个校准器,或者直接用temperature scaling,这属于投入产出比较高的工程技巧。
4.5 服务化部署:FastAPI加Docker
模型要对外服务,我用FastAPI搭一个轻量接口。核心部分是把推理逻辑封装起来,并加上简单的输入校验和错误处理。
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification app = FastAPI() tokenizer = AutoTokenizer.from_pretrained("./model_export") model = AutoModelForSequenceClassification.from_pretrained("./model_export") model.eval() class Item(BaseModel): text: str @app.post("/predict") def predict(item: Item): if len(item.text.strip()) == 0: raise HTTPException(status_code=400, detail="empty text") inputs = tokenizer(item.text, truncation=True, max_length=128, return_tensors="pt") with torch.no_grad(): logits = model(**inputs).logits prob = torch.softmax(logits, dim=-1)[0] label = int(prob.argmax(-1)) confidence = float(prob.max()) return {"label": label, "confidence": confidence}这里有个工程细节:推理时一定要加上torch.no_grad(),否则会构建计算图,内存占用巨大且推理变慢。另外,tokenizer和model在服务启动时已经加载到内存,不要在每个请求里重复加载模型,否则延迟会飙升。FastAPI的异步特性也能帮你撑住一定并发。
Docker部署同样简单,Dockerfile大致如下:
FROM pytorch/pytorch:2.0.0-cuda11.8-cudnn8-runtime WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]构建镜像并跑起来:
docker build -t intent-classifier:v0.1 . docker run -p 8000:8000 --gpus all intent-classifier:v0.1如果服务器没有GPU,可以用CPU推理,但要给model加.to("cpu"),并且后续考虑ONNX或OpenVINO加速。从零开始,建议先用简单Docker部署跑通,不要一上来就搞Kubernetes,等你有多个服务需要编排时再上。
4.6 上线前的监控与版本管理
上线前要做两件常被忽视的事:模型版本登记和线上监控。模型文件要放到统一的模型仓库,比如MLflow Model Registry,记录每次模型的指标、数据版本、训练时间、负责人。网络上只保存一份model_export目录,要能追溯到由哪个训练脚本生成。
监控方面至少要采集三类指标:接口QPS与延迟、预测结果分布、输入文本长度与关键词分布。后两类指标用来感知线上数据有没有发生漂移。比如用户表述方式突然变了,分类置信度会不会整体下降,某一类别的占比会不会异常上升。这才是AI工程里“运维”的核心,没法一劳永逸,只能越早建立越好。
5. AI工程中的常见坑与排查诀窍
5.1 数据泄漏:测试集好到不真实
数据泄漏是我在团队里反复强调的问题。除了前面说的按用户切分,还有几种高频泄漏方式:数据预处理时用了全量数据的统计值去做归一化,应该只在训练集上计算;做文本清洗时无意间用了包含标签信息的字段;做样本去重度时按行去重但没有考虑同类重复文本。
排查数据泄漏有个很简单的信号:测试集结果明显高于验证集,或者模型对训练集过拟合到几乎100%但测试集也接近满分。在你认为模型没有足够建模能力做到这种效果时,八成是数据串了。这时候不要急着加正则化,先回头检查数据处理流程,对比训练和测试样本的特征分布。
5.2 显存溢出与训练速度过慢
显存溢出有三个直接原因:Batch Size太大、序列长度太长、梯度累积中间变量占太多。最直接的办法是把per_device_train_batch_size降半,同时把gradient_accumulation_steps加倍,以保持等效Batch Size不变。如果还是溢,就用梯度裁剪和混合精度训练。
PyTorch的混合精度非常简单:
from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() with autocast(): outputs = model(batch["input_ids"], attention_mask=batch["attention_mask"]) loss = criterion(outputs.logits, batch["labels"]) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()通常混合精度能把显存占用降低30%左右,同时训练速度提升。第一次用的时候会有很多细节,但一旦跑通,基本会成为固定操作。还要检查CPU与GPU之间的数据加载瓶颈,把DataLoader的num_workers设为4或8,pin_memory=True,就能明显改善GPU空转。
5.3 训练与线上预处理不一致
这是最隐蔽的坑之一。你在训练时对文本做了某种清洗,比如去掉表情符号,但部署推理的时候忘了在预处理函数里加同样的清洗逻辑。线上一条“工单,客服态度差??”传到模型里,在分字阶段就乱掉了,预测结果自然不稳定。
解决办法只有一条:把预处理函数单独抽成模块,训练和推理共用同一个函数。并且在部署前,用几个代表性离线样本对比训练预处理结果和线上预处理结果,必须完全一致。这是投入时间最少、避免事故最有效的检查。
5.4 线上数据漂移:模型明天就不再优秀
模型上线后不会一直好用,这是AI工程与普通软件工程最大的区别。普通代码只要输入输出格式不变,逻辑就不会变;但模型的输入分布会跟着用户行为、运营策略、季节变化而漂移。所以从第一天起就要监控输入分布和预测分布。
我自己习惯在每个预测样本的日志里带上原始文本长度、关键词命中数量、预测类别、置信度,每天统计分布。一旦发现今天“支付失败”相关文本明显增多,而模型预测置信度大幅下降,就说明可能出现了新的语义模式,需要补充训练数据或者重新微调。这个监控能力看似简单,却是很多从零团队一直到踩坑才想起来补的。
5.5 问题排查速查表
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
| 训练Loss不下降 | 学习率过大或过小、数据标签噪声 | 打印梯度范数、降低学习率、抽查训练数据 |
| 测试集准确率远高于验证集 | 数据泄漏、训练时间过长 | 检查数据切分与清洗逻辑、减小训练轮数 |
| 线上预测延迟突然升高 | 并发导致排队、GPU利用率不足 | 压测、加缓存、启用动态批处理 |
| 相同输入偶尔返回不同结果 | 推理时开了dropout | 推理前调用model.eval() |
| 模型文件巨大无法加载 | 未使用模型压缩 | 考虑量化、蒸馏、剪枝 |
| Docker镜像构建太慢 | 依赖版本未锁定导致反复下载 | 使用固定版本并配置镜像缓存 |
6. 从文本分类延伸:更复杂的AI工程体系
6.1 从单一模型到多模型串联
当你跑通一个模型服务之后,下一步就该考虑多模型协作。比如智能客服工单分类只是第一步,后面可能要再接一个“实体抽取”模型来提取订单号,再接一个“情感分析”模型来判断用户是否极度不满,最后用一个规则引擎决定交给哪个团队处理。这时候你需要设计一个工作流引擎,把多个模型服务串起来,还要管理依赖关系、超时重试、熔断降级。
我用的方案是先把每个模型部署成独立服务,然后由上一层编排服务负责调用。暂时不需要上专门的编排平台,用简单的异步任务队列或asyncio就能撑住小规模流量。等流程多了,再考虑引入专门的Workflow引擎。
6.2 大模型和传统AI工程的结合
在文本分类之后,你可能会想引入大模型。比如用大模型做小样本数据标注,或者对已有模型的不确定样本做补充判断,又或者直接用RAG替代一部分检索流程。这部分确实是现在AI工程的大趋势。
我的建议是:先把基础的传统模型链路跑通,再上大模型。这样你会更清楚什么时候该用规则、什么时候该用小模型、什么时候该用大模型。大模型不是银弹,它带来更好的语义理解能力,但同时也带来成本高、延迟高、输出不稳定的问题。工程上更稳妥的做法是“传统模型做初筛,大模型做兜底或复杂判断”。
6.3 成本与性能的平衡
训练成本和推理成本是AI工程落地时最大的压力来源。一个GPU实例小时费用不低,如果团队不关注成本,实验会很轻易失控。从零开始可以做几件小事:限制每个实验任务的最长运行时间;及时关闭闲置的GPU实例;用混合精度和LoRA降低显存;在推理阶段使用量化模型。
我习惯在项目文档里记录每次实验消耗的GPU小时数和费用,成本透明化之后,团队成员会自然变得更谨慎,也会更愿意先做数据分析而非盲目训练。这个习惯坚持下来,能帮团队省下超过一半的模型迭代成本。
6.4 团队协作里的“隐形工程”
最后想说的是,AI工程项目推进最不顺利的时候,往往不是模型出问题,而是代码风格混乱、实验无法复现、文档缺失。从零开始阶段就应当规定代码格式、PR评审、注释规范。每完成一个阶段,就写一个README记录技术选型和关键决策。以后你回头看这些记录,会发现它们是比代码更珍贵的资产。
我见过太多项目因为关键成员离职,模型代码无人敢动,所有实验记录都锁在老员工的笔记本里。等到我们自己吃了一次这种亏,才真的把“实验记录”当作项目交付物来管理。这怎么说呢,凡是吃过亏的地方,后来都变成了团队的硬规矩。
回头再看“ai-engineering-from-scratch”这七个词,我更愿意把它理解成一套必须亲手搭建的能力底座:数据规范化、实验追踪、模型微调、服务部署、线上监控,每一环都缺一不可。如果你正处在从零起步的阶段,不要急着收集新框架、新模型,先把我上面整理的这条主路径完整走一遍,你会发现自己对“AI工程”的理解完全不一样了。