“ai-engineering-from-scratch”,翻译成大白话就是“把AI工程从零开始搭起来”。这个提法听起来有点硬核,像编程圈里“手写一个操作系统”那种感觉,但本质上它是一种态度:不依赖现成的套壳平台,亲手把一个AI应用从数据采集、特征处理、模型训练、评估、封装、部署到线上监控,完整地跑通闭环。很多想入行的朋友问过我:学完了Python,刷了不少机器学习教程,到了真做项目的时候还是不知道怎么下手。差距往往就出在“工程”这两个字上——模型训练跑通只算完成了20%,剩下80%都在数据管理、版本控制、服务封装、性能调优和持续迭代这些不起眼却又绕不开的环节里。这篇文章我就用自己从零搭一个文本情感分析项目的真实过程,聊聊AI工程从入门到落地值得关注的那些事,适合准备入门或者刚踩进门槛的开发者当路线图读,也可以给已经入行但总觉得自己“只会调模型”的朋友做个对照参考。
1. 先想清楚:AI工程到底在解决什么问题
1.1 为什么说AI工程不等于机器学习
很多人误会了“AI工程”这个词,觉得它就是“训练一个很牛的模型”。这里面的坑,我在早期踩得够深。
打个比方:算法是菜谱,工程是餐厅。菜谱写得再好,没有稳定的供应链、没有干净的后厨流程、没有上菜速度和顾客反馈体系,餐厅照样开不下去。AI工程做的就是“开餐厅”这摊事——把一堆算法实验变成一个能持续提供价值、异常时能预警、新数据来了能更新的服务系统。
实际工作里算法模型只占很小的比重。一个典型的AI项目会包含数据管道(采集、清洗、标注、校验)、训练平台(代码、环境、超参数、产物版本)、模型服务(接口、并发、容灾、降级)、观测体系(预测监控、数据漂移检测、性能分析)、以及再往上一层跟业务系统的结合逻辑。任何一个环节掉链子,整个系统都可能跑不起来。
第一次训练出一个准召还不错的模型时,我兴奋了两小时,随后就体会到什么叫“工程的地狱”:模型文件动不动几百MB,同事那边怎么都复现不了我的效果;换了一批数据,准确率直接崩掉;接口并发一上来,服务延迟飙升十倍。这些都不是算法本身出了问题,而是工程能力没跟上。搞清楚了这一点,你就会明白为什么越来越多的招聘岗位把“AI工程”拆得这么细。
1.2 从零开始,最值得投入的五个环节
结合我的实战经验,一个完整的AI工程闭环,至少包含以下五个环节。每一项都有对应技能树,缺一不可:
第一,数据工程。数据清洗、去重、归一化、样本均衡、标注质量评审。这部分活儿最琐碎,但决定模型上限。垃圾进垃圾出在任何领域都不是一句空话。
第二,训练与调优。包括框架选择、代码结构、超参数搜索、训练稳定性处理、实验记录。这部分是大家最熟悉的,但也是最容易“自嗨”的,光看loss下降不看评估指标,最后常常白忙一场。
第三,评估与验证。离线指标(准确率、F1、AUC等)、错误分析、切片评估(按类别、按场景细分看效果),以及模拟真实分布的压测。很多项目上线出事故,都是离线评估没做扎实。
第四,部署与集成。把模型转成可服务的格式,封装成API或批处理任务,写好缓存、限流、降级逻辑,再用容器或Serverless方式发布出去。
第五,观测与迭代。上线后监控预测值分布、输入特征分布、业务指标。模型不是一锤子买卖,数据漂移一来,不更新的模型会悄悄变笨。
这五个环节,我建议想走AI工程路线的人全部至少亲手做一遍,哪怕做的项目很小。这条路走通了,后面做任何AI项目都会顺手很多。
1.3 什么样的项目适合作为“从零”的第一个项目
我觉得不是所有场景都适合拿来做“from scratch”训练。比如一上来就搞大语言模型微调或超大规模推荐系统,环境搭建都可能劝退。更适合的是那种数据量可控、评价指标清晰、业务价值直观的任务。
我自己选的是中文电商评论情感二分类:判断一段评论是正向还是负向。选它有三个原因:一是数据集好找,公开的标注数据很多,网上随便一搜就有几万条;二是模型不会太复杂,用基于预训练模型的微调(比如BERT或轻量级蒸馏模型)就能拿到不错的效果,但又能真实暴露工程问题;三是部署形态典型,这个任务天然适合做成HTTP接口,能顺带训练容器化、并发处理、监控这三样本事。
数据量可以控制在2万条以内,本地一张普通显卡就能跑,没有显卡用云GPU按时计费也就几十块钱。这种“体量小、链路全”的项目,最适合用来建立对AI工程的整体认知。
2. 动手前的准备:环境与工具选型解析
2.1 硬件与软件栈的选择逻辑
先聊硬件。很多人第一步就卡在“要不要买块好显卡”。我的建议是初期先别买。做小规模项目和跑通流程,CPU就能应付;真要训练模型,云GPU按时租不心疼,还能逼自己养成按需付费的工程习惯。等到你明确自己几个月内都要高频跑训练,再考虑本地显卡不迟。
我的开发环境非常简单:一台没有独立显卡的MacBook,加上一个远程Linux服务器(带一张消费级显卡),PyCharm用来写代码,终端跑训练。数据存放在服务器本地,代码通过git管理,实验产物(模型权重、tokenizer、配置文件)按日期和版本号归档。整套环境价格不高,但足够支撑一条完整链路。
软件栈方面,Python是AI工程的地基,生态无可替代。第二层是深度学习框架,第三层是服务框架和运维工具。核心选型如下:
| 用途 | 我用的工具 | 备选项 | 备注 |
|---|---|---|---|
| 深度学习框架 | PyTorch | TensorFlow/JAX | 社区活跃,调试直观 |
| 数据处理 | Pandas、NumPy | Polars | 量级不大时足够,量级大再迁移 |
| 预训练模型 | Transformers库 | 框架自带模型库 | 生态好,省大量时间 |
| API服务 | FastAPI | Flask、Django | 自带文档、类型校验,适合AI服务 |
| 实验记录 | MLflow | TensorBoard、W&B | 能管参数、指标、模型产物 |
| 版本管理 | Git + DVC | 纯Git | DVC专门管大文件数据集和模型 |
| 部署 | Docker + 云服务器 | 容器服务、Serverless | 先Docker,后面再考虑编排 |
别把工具选型看成收集癖,够用、顺手、团队能接手比“最新最火”重要得多。
2.2 框架之争:为什么我推荐PyTorch作为起点
在我刚开始做的时候,还有前辈推荐TensorFlow。但现在再看,PyTorch几乎成了学术和工业界的默认选择,我觉得新人从PyTorch入门最不容易走弯路。
原因有三条。第一,调试体验好。PyTorch的eager execution是“边算边看”,打印张量形状、中途断点调试、修改逻辑都很自然。TensorFlow虽然也有eager模式,但历史包袱重,各种历史接口容易让人困惑。第二,生态社区一骑绝尘。现在主流预训练模型、最新论文代码、第三方库基本都是PyTorch优先。少踩一个“别人用的库跟我的框架不兼容”的坑。第三,从研究到生产路径短。TorchScript、ONNX导出、TorchServe这些工具链,把“研究prototype”变成“生产模型”的成本一降再降。
我也不是全盘否定TensorFlow,如果你所在的公司长期用它维护老系统,那肯定以公司技术栈为准。个人新起项目,选PyTorch是更省心的决策,尤其适合“从零开始”的学习者。
2.3 项目脚手架与依赖管理
很多初学者把精力全花在写模型上,依赖管理和项目结构一团乱,这其实是工程上的大忌。我建议每个AI项目都按一套标准模板起步:
sentiment_project/ ├── data/ # 数据目录(原始数据、处理脚本、最终特征) ├── configs/ # 配置文件(超参数、路径、训练参数) ├── scripts/ # 数据处理、训练、评估、部署脚本 ├── src/ # 核心代码(模块化) │ ├── data_loader.py │ ├── model.py │ ├── train.py │ └── predict.py ├── models/ # 产出的模型权重、tokenizer ├── tests/ # 单元测试与数据校验脚本 ├── api/ # API服务代码 ├── Dockerfile ├── requirements.txt # 或使用poetry/pipenv └── README.md依赖管理方面,不要只扔一个requirements.txt就完事,至少要锁版本。我的习惯是项目开始时用虚拟环境隔离,再用pip freeze把精确版本写入requirements.txt,或者直接用poetry、uv这类工具同时管虚拟环境和依赖解析。
这里有个实战经验:环境做不到可复现,其他都是零。你上周训练出的好结果,本周代码一更新就复现不了,这种痛苦几乎所有人都会遇到,原因八九不离十是版本浮动或环境不一致。养成“记录Python版本、CUDA版本、核心依赖版本”的习惯,能帮你省掉非常多排查时间。
2.4 数据是怎么“喂”给模型的
到这一步,很多教程就开始念代码了。但我觉得有必要先把“数据流向”讲清楚,这决定了你对后面每一步的理解深度。
原始数据(比如一堆没处理过的文本)需要走完一条流水线才能进模型:原始数据 → 清洗 → 标注/整理 → 划分训练、验证、测试集 → 文本预处理(分词、去停用词等,视任务而定) → 编码(转成model输入格式) → 构建DataLoader批量供给模型。
任何一步处理逻辑不一致,都会导致训练和预测的“数据口径”对不上。比如训练时你删了网址,预测时没删,模型看到没见过的新模式,效果就会波动。所以工程上有一个习惯:数据处理函数必须由训练和推理两条路径共用,绝不能各写一份。
3. 核心实现:从数据集到可部署服务
3.1 第一步:数据清洗与特征工程
我的情感分析项目里,原始数据是从公开渠道收集来的电商评论,大概3万多条。不过直接拿原始数据训练肯定不行——缺头少尾、表情符号、重复文本、无关字符都需要处理。
我写的清洗函数长这样,注意它是会被训练和推理共用的:
import re import unicodedata def clean_text(text: str) -> str: if not isinstance(text, str): return "" # 统一Unicode,避免全角半角混乱 text = unicodedata.normalize("NFKC", text) # 去掉网页URL,这类噪声对情感判断没有帮助 text = re.sub(r"http\S+|www\.\S+", "", text) # 去掉@提及和话题标签(按场景可保留) text = re.sub(r"@\w+|#\w+#?", "", text) # 连续空白压缩 text = re.sub(r"\s+", " ", text).strip() return text清洗之后还需要做样本去重。我遇到过同一段评论在数据集中出现几十遍的情况,如果不处理,模型会过拟合这些重复样本,导致验证集指标虚高。去重很简单,直接对清洗后文本做hash去重就行。
下一步就是划分数据集。训练集、验证集、测试集我按8:1:1划分。这里很多人会忽略一个关键点:必须保证测试集完全隔离,不能参与任何调参和特征工程。我一般把测试集数据单独放文件,不到最后评估绝不打开,这就是在工程里保住“数据洁癖”。
3.2 第二步:模型训练与超参数调试
模型方面,我直接基于预训练的中文BERT变体做微调。全量BERT训练比较慢,我用的是一套轻量蒸馏模型(大约110M参数),对2万条训练数据来说完全够用。代码结构上我会把训练主流程抽象成下面这个样子:
from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer, ) def build_model(model_name: str, num_labels: int = 2): tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained( model_name, num_labels=num_labels ) return tokenizer, model在训练参数设计上,我第一个容易犯的错就是一上来把学习率跑得飞快。预训练模型微调的学习率一般是2e-5到5e-5,这个量级比从头训练低两三个数量级。如果你从零训练一个模型,学习率可能要去到1e-3甚至更高,而微调阶段用这么大的学习率,几乎必然导致灾难性遗忘——模型把预训练学到的语言知识忘了个精光。
我的超参数是这样设的:batch_size=32(用梯度累积凑够等效大batch)、learning_rate=3e-5、epochs=3、warmup_ratio=0.1。跑一轮大概3分钟,训练加验证不到15分钟。对于这种体量的任务,贪心跑几次就够用了,不需要上贝叶斯搜索、网格搜索这些重武器。
训练阶段一定要记录所有指标:不仅看loss,还要看验证集F1、精确率、召回率。用MLflow记录的好处是,后面想回看不同实验的超参数和指标,一条命令就行,不用靠脑记文件名。
3.3 第三步:评估指标到底看什么
二分类问题,最土也最常见的评估指标就是准确率。但情感分析这种正负样本不平衡的场景,准确率经常骗人:假设90%是正向评论,你什么都不做全预测正向,准确率都有90%。所以我更关心F1分数,尤其是少数类别的F1。
我在项目里还做了一个切片评估:把验证集按评论长度、按商品类型切片,分别计算指标。为什么要做这一步?因为一个模型在平均指标上好看,不代表在某个细分场景下不崩。比如模型对长评论识别差,但长评论恰恰是投诉风险最高的,这种问题你要不切开来根本看不见。
在最终评估时,我用测试集算了一次最终的F1,大概0.93。这个数字对于一个从零搭建的工程来说足够有说服力,接下来要考虑的就是怎么把这个模型“卖”给业务方——部署成能调用的服务。
3.4 第四步:把模型包装成API服务
模型训练完只是一个文件,不是产品。为了让别人能用,我把模型加载进FastAPI服务,封装了一个简单的POST接口。
一个极简但可用的服务端代码如下:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI(title="sentiment-analysis") model, tokenizer = None, None class ReviewRequest(BaseModel): text: str @app.on_event("startup") def load_model(): global model, tokenizer model_path = "./models/sentiment_model" # 这里用你已经保存好的模型和tokenizer model, tokenizer = load_local_model(model_path) @app.post("/predict") def predict(req: ReviewRequest): inputs = tokenizer( clean_text(req.text), truncation=True, max_length=256, padding=True, return_tensors="pt", ) logits = model(**inputs).logits label = int(logits.argmax(-1)[0]) prob = float(logits.softmax(-1)[0][label]) return {"label": label, "prob": round(prob, 4)}这里有几个工程细节值得展开。
第一,模型加载放到startup钩子里,而不是每次请求都加载。否则并发一上来,光模型加载就能卡死服务。第二,prediction输入必须走同一个clean_text,这对应前面说的“数据口径一致”。第三,超时和最大长度要限好。我在tokenizer里把max_length设为256,超过部分截断。这个操作既保护服务性能,也防止长文本把模型跑崩溃。
写完之后,用uvicorn启动服务。我习惯本地先跑一遍,curl一下接口验证能通,再进入Docker化环节。
3.5 第五步:Docker化与上线
AI服务上线,我最常用的方式是Docker。好处是把Python版本、依赖、模型文件全部锁进一个镜像,杜绝“在我电脑上能跑”这种经典问题。
Dockerfile写起来也不复杂:
FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]镜像构建好后,我先把服务在本地跑起来,再用curl发几个样例请求验证。没问题就把镜像推送到云服务器的容器环境中,映射端口后配置一个最简单的Nginx反向代理,再把HTTP改成HTTPS。一套流程下来,一个可以对外提供服务的AI接口就上线了。
这里有个建议:模型文件不要打进镜像,尽量从对象存储或模型仓库拉取。这样镜像变小,发布更快,模型更新也不用重新构建整个镜像。我第一次就把模型装进了镜像,一个镜像2G多,每次发布都痛苦得不行。
4. 工程化落地:模型版本管理、CI/CD与监控
4.1 模型版本管理与实验记录
模型就是一个二进制文件,但它同样需要版本管理。传统git存几MB的代码没问题,模型动辄几百MB,直接推git仓库会被拒绝。现在我的习惯是:代码走git,数据走DVC(Data Version Control),模型走MLflow或云对象存储。
DVC的原理很好理解:它不把大文件放进git,而是记录文件的哈希值和存储地址,git里只保存这些小的元数据文件。别人拉取代码后执行一条dvc pull命令,就把对应版本的数据和模型拉回本地。这样就做到了整套环境可复现。
MLflow我最常用的三块是Tracking(记录参数和指标)、Registry(管理模型阶段:Staging/Production/Archive)、以及Model Serving辅助功能。每次训练完我把精确率、F1、AUC、训练配置全部记录进去。回看历史实验时,用UI界面或API搜一下比翻流浪的日志文件高效得多。
4.2 自动化测试与持续集成——不只是测代码
AI项目的CI/CD和传统软件有个重要区别:不能只测代码逻辑,还要测数据和模型。我在项目里加了三层测试。
第一层是数据校验测试。写一个脚本检查数据schema对不对,比如text列是否存在、标签值是否只有0和1、数据量是否达到阈值、有没有过度重复。数据一变化,CI就跑这套测试,防止脏数据悄悄流进训练管道。
第二层是模型输出测试。不追求全量验证,而是准备一组固定的冒烟样本,每个样本的预测结果作为一个pytest断言,比如一句明显表达不满的评论,模型必须输出负向。这样可以及时发现模型退化。
第三层是服务接口测试。模拟调用API,检查响应格式、状态码、时延是否在预期内。接口改了没改坏,第一时间就知道。
这三层测试加入持续集成平台后,每次提交代码或数据变更都会自动跑到。刚开始会烦,但经历过“改了一行代码把整个预测搞坏、上线了才被发现”的惨痛教训后,你会爱死这层保护。
4.3 线上服务监控与模型漂移
很多人以为模型上线就万事大吉。让我用真实教训告诉你:模型上线只是开始。我遇到过最经典的问题就是数据漂移。
业务发展、用户习惯变化、外部环境变化,都会导致线上真实数据和训练数据分布不一致。初期模型跑得很好,三个月后准确率悄悄下滑,用户投诉增多,而你对着监控面板一脸懵。解决这个问题的核心思路是“漂移检测”。
我在项目中做两件事。一是对模型输入特征做分布监控,比如评论长度的分布、高频词分布、预测概率分布。二是把预测结果导出来做抽检,人工或半自动验证预测正确性。用Prometheus这类工具可以画出预测类别比例、平均置信度等指标的变化曲线,一旦出现明显趋势偏离,说明数据漂移已经发生,就需要触发重新训练。
再补充一个我认为很多人忽略的细节:预测日志一定要能回溯。我在API里给每个预测请求生成了唯一请求ID,并记录了请求原文、处理时间、模型版本和返回结果。出了纠纷、需要复盘的时候,有这个日志链能省掉无数推诿时间。
5. 踩坑实录:新手最容易翻车的那些问题
我把自己踩过和被身边人问过的坑整理成了一份速查表,很多都是教程不会写的细节。
5.1 数据类问题速查
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 训练指标很高,线上很差 | 数据处理口径不一致 | 训练和推理共用清洗函数,严禁两套代码 |
| 验证集和测试集结果相差巨大 | 数据泄露,测试集参与调参 | 测试集隔离,接近上线前才用 |
| 模型学不到东西,loss下不去 | 标签错位或严重噪声 | 抽50条数据人工检查,别急着调模型 |
| 训练过程反复震荡 | 样本不均衡或batch太小 | 检查类别分布,用类别加权或调大batch |
数据问题还有一个隐藏陷阱:标注质量。如果你用众包或外部标注数据,一定要抽检一致性。置信区间低、标注者之间分歧大的样本,往往是模型困惑的根源。
5.2 训练阶段问题速查
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 不收敛或梯度爆炸 | 学习率过大,或模型初始化不当 | 降低学习率,加梯度裁剪 |
| 复现不出别人的结果 | 随机种子未固定、框架版本不同 | 固定seed,记录环境版本 |
| 显存OOM | batch_size过大或序列过长 | 减小batch,用梯度累积,缩短max_length |
| 模型总把样本预测成多数类 | 类别不平衡 | 用class_weight,或对少数类过采样 |
梯度爆炸这个问题我在用预训练模型时会特意留意。虽然大部分官方transformer会自带一定稳定性,但一旦用了自定义loss或加了额外层,梯度裁剪从“可选项”变成了“必需项”。
5.3 部署与服务问题速查
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 接口首次请求特别慢 | 初始化代码写在请求路径里 | 移到startup钩子,用预热机制 |
| 并发一高就超时 | 没有加并发控制或排队 | 引入消息队列或异步任务,加限流 |
| GPU显存被慢慢占满 | 服务存在显存泄漏 | 检查推理路径中是否保留张量引用,及时释放 |
| 模型文件无法跨机器加载 | 路径或tokenizer版本不一致 | 用相对路径,模型包内带上tokenizer配置文件 |
服务部署还有一个很容易忽略的点:依赖大小写和版本在生产环境会咬人。我的习惯是构建前在干净容器里验证依赖安装无冲突,不要指望在本地能跑生产就没问题。
5.4 我的排障方法论
排查AI工程问题,我总结了一个简单的排查顺序:数据 → 环境 → 代码 → 模型。不按这个顺序,很容易在错误层面浪费几小时。
如果指标不对,我先怀疑数据质量而不是模型结构。打印几条训练样本看数据长什么样,检查标签顺序和编码映射。如果数据没问题,再检查环境版本和随机性。版本不一致导致的结果漂移,是最难排查也最常见的一类问题。代码层面的bug看报错栈基本能定位。最后才去考虑模型结构、超参数是否合理。这个顺序能覆盖大约90%的“鬼故事”。
6. 写给想系统学习的人:一条可复用的学习路径
6.1 第一个月怎么走
如果你是从零开始,不建议一猛子扎进大模型或者全栈架构。更务实的顺序是“先跑通小闭环,再扩展外围”。
第一周:学Python基础(重点在NumPy、Pandas),理解基本的机器学习概念(特征、标签、训练测试集)。第二周:上手PyTorch,跑一个最简单的多层感知机分类任务,把张量操作、DataLoader、训练循环搞明白。第三周:引入Transformers库,微调一个预训练句子分类模型,感受“站在巨人肩膀上”的威力。第四周:完成一个像我这样的情感分析项目,要求是完整跑通训练到API的流程。
这一个月下来,你对AI工程的全貌就有了“手感”。不要贪多,不要同时学图像、NLP、强化学习,一个任务吃透胜过十个任务都会一点点。
6.2 后面还可以往哪个方向走
走完第一个闭环,你可以根据兴趣选一个方向去挖。比如做系统设计方向:把服务改成多模型路由,加缓存、加异步处理、加降级策略,研究吞吐量和延迟的平衡;做数据方向:深入研究数据处理流水线、数据版本管理、数据质量监控,很多公司需要这种能独挡一面的人;做算法方向:回去补统计学习和经典机器学习,为读论文和实现新模型打基础。
我自己的体会是:工程能力越到后面越值钱。因为算法开源和模型平权的速度太快了,但能把一个模型稳定、安全、低成本地跑起来的人,始终缺。这也可以解释为什么“AI工程”的热度越来越高——它本质上是把实验转成生产力的能力。
6.3 什么时候可以不必“从零”
“from scratch”是一剂很好的强心针,但不是万能的。如果公司已经有成熟的AI平台、现成的特征平台和部署管道,那你要做的不是推翻重来,而是学会在平台约束下高效交付模型。工程能力的一个重要维度恰恰是“用合适的成本解决合适的问题”。
当小规模闭环你都亲手跑过一遍以后,你就有能力判断:哪些环节该复用平台、哪些环节该自己造轮子、哪些环节可以直接接第三方API。这个判断力,比从零搭建本身更值钱。
最后分享一点个人体会
这个情感分析项目我从编码到上线,前后断断续续做了两三个星期,中间踩的坑数不过来。但回头看,最有价值的其实不是那个0.93的F1,而是我把数据口径、版本控制、测试死角、监控盲区这些“工程债”都亲手背了一遍。从那之后,再接手任何AI项目,我心里始终有一张完整的地图:模型训练只是其中一个节点,前后左右全是工程问题。如果你也在学AI工程,我建议你挑一个自己感兴趣的小任务,照着这条路完整走一遍,不用急着求大求全。跑完第一个闭环你会猛然发现,原来“AI工程”并没有那么玄,它就是一套围绕模型展开的、严谨且耐心的系统功夫。