拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI工程从零到生产:环境、数据、部署与监控实战指南

AI工程从零到生产:环境、数据、部署与监控实战指南

几年前我给自己定了一个目标:把ai-engineering-from-scratch变成现实。那会儿我对AI工程的理解非常粗暴——把PyTorch官方示例跑通,再套一个Flask接口,就算“会做AI”了。结果第一个真实项目就在生产环境翻车,训练集准确率看着还行,上线后面对真实请求直接崩掉。后来我走了很多弯路,才慢慢理解AI工程不是“模型代码”,而是模型周围那一圈容易被忽略的管线:环境、数据、实验、部署、监控,缺一环都不行。

如果你正准备开始自己的AI项目,或者已经跑过几个模型但卡在“demo能跑、生产不可用”这个阶段,这篇文章就是我一路从零踩过来之后的结构化复盘。我会把每一步为什么这么选、具体命令怎么做、以及那些没人提前告诉你的坑,尽量原原本本写出来。目标只有一个:让你少走我走过的弯路。

1. 先打破幻觉:AI工程不是“写模型”,而是一整套系统工程

1.1 为什么我一开始的“AI项目”全部失败

我做过一个文本分类工具,当时的感觉非常良好:GitHub上克隆BERT示例,改改数据路径,训练几个epoch,准确率92%,然后我用Flask包了一个接口,感觉自己已经是“AI工程师”了。结果这个工具一放到真实业务数据上,准确率直接掉到70%左右。最奇怪的是我本地测试还好好的,上了服务器就变样。

后来排查出三个致命原因,每一个都和模型结构无关:

  • 训练数据分布和真实数据分布完全不一致。官方demo用的是新闻语料,我的业务数据是客服工单,词汇、句式、长度都不一样,模型在demo上看到的“经验”在真实场景里基本不适用。
  • 代码、预处理、模型版本全都没有记录。我今天改了分词逻辑,明天又换了模型权重,到了复盘的时候根本不知道哪个版本对应哪次实验结果。
  • 推理性能完全没有设计。单个请求在CPU上要3秒多,根本没法在生产环境用。

这三个问题,没有一个是“换个更好的模型”能解决的。它们全是工程问题。

1.2 AI工程与AI研究的本质区别:稳定性 > 酷炫

我以前以为AI工程就是AI研究的一个子集,后来发现这两个领域的目标差异很大。

AI研究追求在公开数据集上的指标上限,算法创新是核心;AI工程追求的是在真实、受限、多变的环境里,让模型稳定、可控、可住地输出结果。工程里的“住”包括可复现、可监控、可回滚。

我做过一个不算恰当的类比:研究像是实验室里造一辆能跑500km/h的赛车,工程则是造一辆在城市里每天通勤、耐造、不出毛病的家用车。赛车很酷,但大多数时候我们需要的是后者。

所以我给自己的第一条原则是:把对“新模型”的痴迷先压一压,把注意力放到模型之外的管线上。管线不牢,模型再炫也没用。

2. 从零搭建AI工程的第一步:环境与项目骨架

2.1 我最终选定的技术栈:Python、PyTorch、MLflow、Docker

技术选型的原则是“先用最主流、最成熟的东西”,不要在起步阶段追求独特。我的最终选择是:

组件作用为什么选它
Python 3.10+主语言生态最全,AI相关库基本都围绕它
PyTorch深度学习框架动态图调试友好,社区活跃,自定义模型心智负担低
MLflow实验跟踪与模型管理能记录参数、指标、产物,复现实验的标配
Docker环境打包解决“我本地能跑,你环境跑不了”的核心矛盾
DVC数据版本控制数据集和代码一样需要版本化追踪

为什么不选TensorFlow?不是它不好,而是对于工程调试来说,PyTorch的“运行时才建图”方式让我更容易定位问题。你如果更熟悉TensorFlow也没问题,关键在于把整个工程管线搭起来,而不是纠结框架本身。

2.2 用Cookiecutter搭建可复制的项目模板

很多新手拿到数据就开始写train.py,写到一半文件路径乱飞,连自己都找不到昨天生成的结果。我的建议是先搭一个可复制的项目骨架,推荐用Cookiecutter。

安装:

pip install cookiecutter

然后可以用现成的AI项目模板,比如:

cookiecutter https://github.com/drivendata/cookiecutter-data-science

它会生成一个包含关键目录的结构,我最终沉淀下来的是这样:

. ├── configs/ # 项目配置,yaml或json ├── data/ │ ├── raw/ # 原始数据,只读不修改 │ ├── processed/ # 清洗后数据 │ └── external/ # 外部引入数据 ├── notebooks/ # 探索性分析,不参与线上流程 ├── src/ │ ├── data/ # 数据处理代码 │ ├── features/ # 特征工程 │ ├── models/ # 模型定义和训练 │ └── api/ # 推理服务 ├── models/ # 产出的模型文件 ├── tests/ # 测试代码 └── Makefile # 一键执行常用流程

这个结构最重要的原则是:数据、代码、产物分离。你不会在改代码的时候误伤数据,也不会在训练完不知道该把模型文件放到哪里。

2.3 环境依赖管理的坑:conda、pip、poetry该怎么选

环境管理是第一个容易翻车的地方。我早期直接用pip freeze > requirements.txt打包环境,后来发现这个文件里混了很多不该锁的间接依赖,换一台机器装完经常起冲突。

后来我采用的方案是:

  • 用conda管理基础环境(Python版本、CUDA、系统级库);
  • 用poetry管理项目级别的依赖,并提交poetry.lock文件。

创建环境:

conda create -n ai-engineering python=3.10 conda activate ai-engineering pip install poetry poetry init poetry add torch transformers fastapi uvicorn mlflow dvc

为什么非要用poetry?因为poetry.lock会把所有依赖的精确版本和传递依赖都锁住。只要有这个文件,任何人在任何机器上执行poetry install,得到的环境都和我一样。conda也能锁,但它的环境解析和跨平台支持有时候会让人抓狂。

注意:不要只写一个requirements.txt然后手动维护。我第一次项目就是手动维护,结果某次升级torch之后,模型结果变了但没人知道,查了三天才发现是依赖版本变了。

3. 数据工程:模型好不好,早在数据处理时决定了

3.1 拿到原始数据后我做了什么(清洗、去重、格式归一)

很多AI项目的时间分配里,数据整理占掉的精力比模型训练多得多。我自己的经验是,一个项目里50%以上的时间都会花在数据上,不是在“玩数据”,而是在“让数据变成模型能吃的样子”。

我拿到原始数据后的固定流程:

  1. 缺失值处理:先看每一列缺失比例,缺失过多的字段要么丢弃,要么用“unknown”之类的占位符,绝不能用随机数强行填充。
  2. 去重:这一步要定义清楚“重复”是什么。文本数据里,一行字完全相同是比较容易判断的重复;但语义上相近但表达不同的样本,比如“订单没到”和“我的包裹还没送过来”,不能简单去重,需要业务规则或人工判断。
  3. 格式归一:日期统一成YYYY-MM-DD,文本统一成小写(除非大小写有语义),标点符号做统一处理。
  4. 标签分布检查:打印每个类别的样本数量,如果有类别特别少,要提前决定是否做采样、加权或者收集更多数据。
  5. 异常样本剔除:比如单条文本长度超过正常范围100倍,多半是复制粘贴错误,保留反而会干扰模型。

我做文本分类时,原始数据里有大量长度超过2000字的“垃圾样本”,实际上是页面爬取时把导航栏和广告也抓下来了。清洗之后,模型的表现提升非常明显——这个变化远大于我换一个预训练模型带来的提升。

3.2 标注质量怎么把控:双人标注+抽检

如果你的项目需要自建标注集,质量控制是关键。我最早是自己标,标了一个下午,觉得挺简单。后来发现人的注意力会下降,到了后半段,同一个样本的标签可能跟前面都矛盾。

推荐的方案是双人标注加抽检:

  • 每个样本至少两个人独立标注;
  • 计算两人标注的一致性,如果一致性低于某个阈值(比如75%),说明标注指南本身有问题,要先修改指南而不是继续标;
  • 对已标注数据定期抽检,抽检比例建议不低于5%。

一致性指标可以简单算:

agreement = sum(1 for a, b in zip(labels_1, labels_2) if a == b) / len(labels_1)

只看准确率不够,还要看每个类别的召回和精确度,尤其是少数类。类别不平衡是文本分类里最常见的问题,如果不处理,模型会“聪明地”把所有样本都预测成多数类,整体准确率看着挺高,但少数类全错。

3.3 数据版本管理:为什么我不用CSV直接覆盖

代码有Git版本管理,数据也必须要有。我早期经常干的事情是:把data.csv改一下,重新训练,过两天觉得效果还不如之前,想回退到旧数据,然后发现旧数据已经被覆盖了,彻底找不到。

DVC(Data Version Control)解决的就是这个问题。它不把数据本身放到Git里,而是用指针文件记录数据的版本。

常用命令:

dvc init dvc add data/processed git add data/processed.dvc git commit -m "更新清洗后的数据"

以后想切换到任意一个历史数据版本,只需要:

git checkout <commit> dvc checkout

数据和代码的版本就一起出来了。这个操作看起来多花了几分钟,但在项目周期长的时候价值极大。我至少有两次靠DVC救回了被覆盖掉的旧数据集,不然整个实验对比都要重做。

4. 模型训练:从“能跑通”到“能用的”关键转折

4.1 选定基线模型后,先做一次完整训练而不是急着调优

新手最容易犯的错是一上来就上最强模型,比如直接上几十层的大模型,指望效果一步到位。我的经验是反过来的:先做一个最简单的基线模型,走通整个流程,再逐步升级。

基线模型可以是逻辑回归、小型CNN或者一个小号的预训练模型。它的作用不是拿出去打比赛,而是帮你确认:

  • 数据预处理和加载管线是否正确;
  • 训练流程能否完整跑通;
  • 评估方式是否符合业务目标。

我第二个项目就学乖了,先用TF-IDF + 逻辑回归跑了一遍,虽然准确率不如BERT,但从数据处理到评估接口全部跑通之后,我再把模型换成BERT,很快就能看到提升。如果一开始就直接上BERT,出了问题都不知道是数据的问题还是模型的问题。

4.2 训练中的指标监控:loss、验证集、过拟合判断

训练时不能只盯着终端里刷新的loss,要同时看训练集和验证集的曲线趋势。最简单有效的判断规则:

现象结论
训练集loss下降,验证集loss也下降还算正常,继续观察
训练集loss下降,验证集loss不降或上升过拟合信号,需要加正则/减小模型
训练集loss和验证集loss都高欠拟合或模型容量不足、特征不够

评估分类模型时,只看accuracy会骗人。我处理过一个类别不平衡项目,正负样本比1:9,模型把所有样本都预测成负类,accuracy是90%,看起来很高,但正样本一个都没抓到。这种场景必须看precision、recall、F1,甚至画PR曲线。

建议在训练时把评价指标定义成一个独立模块,至少包含:

  • loss
  • accuracy
  • macro F1 / weighted F1
  • 少数类的precision/recall

这些指标不光要打印,还要通过日志记录下来。

4.3 实验记录:MLflow怎么帮我记住每次超参数

训练一次模型,涉及的数据版本、代码版本、超参数、随机种子、评估结果,信息量很大。如果不记录,三天之后你就会忘掉“当前这个模型到底是怎么来的”。

我用的是MLflow Tracking。基本用法:

import mlflow with mlflow.start_run(): mlflow.log_param("model_name", "bert-base") mlflow.log_param("learning_rate", 2e-5) mlflow.log_param("batch_size", 16) mlflow.log_metric("eval_f1", 0.87) mlflow.log_artifact("model.bin")

跑完之后,在浏览器打开MLflow UI,能看到每一次实验的参数和指标,一眼就能对比哪个实验更好。有了它,我再也不会出现“这个结果好像是用某个版本跑出来的,但我忘了是哪个版本”这种状态。

对于团队协作,MLflow还能做Model Registry,可以给模型打上Staging、Production的标记——这个后续部署特别有用。

5. 部署与上线:真正让我崩溃的是这里

5.1 本地能跑和线上能跑是两个世界

模型训练完之后,本地跑通只是第一步,上线才是真正的考验。我吃过的亏包括:

  • 本地有GPU,服务器是纯CPU,模型推理慢得没法用;
  • tokenizer版本不一致,线上环境的tokenizer和训练时不是同一个版本,导致文本被切成不一样的分词,预测结果直接变化;
  • 本地内存够大,服务器容器内存只有2GB,模型加载完直接OOM。

所以“本地能跑”和“线上能跑”完全不是一回事。部署前一定要明确线上环境的硬件限制:CPU还是GPU,内存多少,允许的推理延迟是多少。这些参数直接决定你要不要做模型量化、剪枝或者换更小的模型。

5.2 用Docker打包模型服务的完整流程

Docker解决的是环境一致性问题。我的标准做法是把推理服务做成一个Docker镜像,确保任何环境行为一致。

一个最小的FastAPI推理服务:

# app.py from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class PredictRequest(BaseModel): text: str @app.post("/predict") def predict(req: PredictRequest): # 这里加载模型,做预处理和推理 result = {"label": "positive", "score": 0.98} return result

对应的Dockerfile:

FROM pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY src/ ./src/ COPY models/ ./models/ EXPOSE 8000 CMD ["uvicorn", "src.api.app:app", "--host", "0.0.0.0", "--port", "8000"]

构建镜像:

docker build -t ai-engineering-service . docker run -p 8000:8000 ai-engineering-service

注意:模型文件比较大的时候,不建议直接塞进镜像,因为镜像会变得特别大,每次推送都很慢。更好的做法是用共享存储(比如S3或NFS),在容器启动时把模型加载进来。

5.3 推理服务的性能优化:批处理、缓存、动态batch

上线之后,你很快会发现“单个请求推理耗时”不是唯一指标。还有两个常见指标:吞吐量(每秒能处理多少个请求)和延迟(单个请求从发出到返回需要多久)。这两个指标往往要权衡。

几个我在实践中最有效的优化手段:

  • 缓存:对完全相同的请求直接返回之前结果。文本分类里,重复问题特别多,加一个简单的缓存能减少一半以上的计算量。
  • 动态batch:不要让每个请求单独跑一次推理,而是把并发请求攒起来,凑成一批再推理,能显著提高GPU或CPU利用率。实现起来可以用队列或者FastAPI的BackgroundTasks。
  • 模型序列化优化:如果只用推理,可以导出为TorchScript或ONNX格式,比直接跑原始PyTorch模型更快,内存占用也更低。

我做过一个经验数据:同样的BERT模型,单条请求CPU推理耗时3.2秒;用动态batch(一次处理8条)之后,平均每条降到0.8秒左右。这就是工程优化的力量。

5.4 上线后的监控:数据漂移和模型退化

模型上线不是终点,而是开始。我负责过的模型上线后,最怕的就是“静默退化”——线上指标慢慢变差,但没人知道。

最基础的监控包括两件事:

  • 输入数据监控:记录线上输入的特征分布(比如文本长度、关键词频率、缺失值比例),定期和训练集分布比较。如果分布变化明显,说明发生了数据漂移。
  • 预测分布监控:统计模型输出的预测类别比例,如果某天某个类别的预测比例突然飙升,大概率是线上数据变化了,需要告警。

简单的漂移检测不一定非要复杂统计模型,可以先看均值、方差、分位数的变化。比如训练集里文本平均长度是120个字符,线上如果连续几天变成200个字符,说明业务场景或数据来源变了。

我建议上线前至少把“输入特征统计摘要”和“预测结果统计摘要”落库,并配置一个简单的阈值告警。哪怕只有日志和定时脚本,也比完全没有强。

6. 工程化复盘:从个人Demo到团队协作

6.1 代码、数据和模型的版本管理如何统一

一个人做项目可能还能靠记忆力,但多人协作必须把代码、数据、模型三者关联起来。我的做法是:

  • Git管理代码,每次实验切换对应commit;
  • DVC管理数据,用data/processed.dvc记录数据版本;
  • MLflow管理模型,每次训练都会记录Git commit、DVC数据版本、超参数、评估指标。

这样一套打通后,任何人拿到一次实验的run_id,都能复现出完全一样的模型。这种能力在交接工作时特别重要——别人不用来问你“当时的预处理是什么样的”,直接查记录就行。

6.2 自动化测试在AI项目中的特殊做法

很多人以为AI项目没法写测试,其实不是。我通常把AI项目测试分成三层:

  • 数据测试:检查数据列是否存在、标签是否在合法集合内、缺失值比例是否超过阈值。数据测试在每次数据更新时都会跑。
  • 特征转换测试:同样的输入,特征转换函数在不同版本上要保证输出一致。比如tokenizer版本升级后,要测试同一句话的分词结果是否和升级前一样。
  • 模型输出格式测试:模型接口返回的JSON结构、score范围、label集合要固定。上游调用方依赖这些契约,不能今天返回label明天变成prediction。

我踩过一个经典坑:某次重构把模型输出里的分数含义从“正类概率”改成了“得分减去0.5之后的偏移量”,下游团队没收到通知,结果线上业务逻辑直接错乱。写个输出格式测试就能避免这种问题。

6.3 我对AI工程新人最想说的几点经验

我从零走完这条路之后,有几条很实在的心得:

  • 先跑通端到端最小闭环,再优化指标。一开始就用最简单的模型把“数据→训练→部署→请求”走通,这个闭环就是你的及格线。之后每一步提升,都是在闭环上做加固。
  • 多用Makefile或脚本把流程串起来。我后期会在项目根目录写Makefile,把make data、make train、make serve都固化下来。手动敲命令越少,越不容易出错。
  • 日志一定要多打。模型推理时记录输入的摘要和输出的摘要,出了问题才能回溯。没有日志的AI服务等于在黑夜中闭眼开车。
  • 不要盲目追逐最新模型。真实业务先解决的是可靠性和成本,新模型如果能带来明显收益再换,否则老模型维护得好同样有价值。

这些经验多数不是从书上学来的,而是踩坑之后总结的。我希望你能在看完这篇文章后,把“从零开始AI工程”变成一条清晰可见的路径:环境、数据、训练、部署、监控,每一步都不神秘,但每一步都需要认真对待。

返回列表