ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零搭建AI工程:可复现、数据管道与模型部署实战

2026/9/29 11:43:12 拓冰建站 浏览量
从零搭建AI工程:可复现、数据管道与模型部署实战 经常有人拿着在某个论文仓库里跑通的模型来找我问为什么换个数据集就废了。也有同学在Kaggle拿了奖一进公司却发现连数据管道都搭不利索。“ai-engineering-from-scratch”这个题目我特别想聊因为“从零开始”这四个字不是说要从写神经网络开始也不是说要从调库开始而是要从“把一个模型变成一个稳定服务”这条完整链路开始。这篇文章就是把自己从零搭建AI工程能力的路线捋一遍适合有一定Python基础、跑过几个模型但没完整做过工程项目的朋友。放心不劝退只把最容易被忽略的地方讲透。AI工程听起来很宽但落到日常工作中无非是这几件事把数据准备好、把模型训练得可复现、把推理服务部署得稳定、把线上问题排查得清楚。这些事听着普通可每个环节都有反直觉的坑。我会按照一个完整项目流程来拆解从环境搭建、数据管道、训练实验追踪到服务化部署最后用一个情绪识别项目把所有环节串起来。1. 从零开始前先搞懂AI工程的本质是“可复现”1.1 为什么模型跑通不算工程我的第一个AI工程噩梦发生在刚入行那年。同事给我一份训练好的模型权重说是效果AUC能到0.9结果我拿自己的数据一跑直接报错提示特征维度不匹配。看代码才发现模型的输入特征里有一个字段是当时临时拼进去的后来删了但权重文件里对应层的维度没变代码也没同步。这种情况在学术demo里很常见但在工程环境里就是事故。“模型跑通”和“能上线”之间隔着很远的距离。代码在你自己机器上能跑不代表在别人机器上能跑数据在你当前文件夹下能读不代表换一台服务器还能读模型训练出的权重存下来了但不记录超参数、不记录数据集版本你根本不知道它是在什么情况下训练出来的。很多新人觉得AI工程就是“训练调参”其实工程化的核心是三个字可复现。不可复现的模型约等于没做。可复现的意思是随便换一个人拿到你的代码、数据、环境描述在另一台机器上也能复现出相同的结果。只有做到这一步后续的优化、比较、回滚才有意义。否则你今天调参调出一个好结果明天想再试一次没了那你的“调参经验”也就是薛定谔的玄学。1.2 工程化的三根支柱数据、代码、环境一个真正可复现的AI项目需要同时管住三样东西代码、数据、环境。这三者缺一不可。代码用Git管理这个大家都会但要注意的是训练代码和推理代码必须同源最好放在同一个仓库里别搞成两个割裂的项目。数据用DVCData Version Control这类工具管理它能把大文件的数据集、模型的版本变化记录成类似Git的方式不占用普通Git仓库的体积也能随时切回某个旧数据版本。环境用Docker管理因为Python依赖的坑实在太多了。别人的机器上没有你的CUDA版本、没有那一个特定版本的scikit-learn你的代码就跑不起来。我用一个简单类比来解释这三根支柱模型是汽车代码是图纸数据是燃料环境是厂房。图纸改了但燃料换了或者厂房里的工具版本不对汽车造出来可能就是另一台车。1.3 从零开始该有的心理建设想从零开始学AI工程最重要的不是先去学Kubernetes也不是一上来就搞多机多卡训练。我的建议是先用一个小项目把“代码-数据-环境”这条闭环打通。你完全可以先做一个Boston房价这样的练手项目但要做完整写训练脚本、记录数据版本、固定依赖环境、写一个简单的API服务甚至带上一个测试。这个过程不酷但特别有用。心理建设方面你要接受一个现状AI工程里真正和“算法模型”直接相关的时间可能不到20%剩下80%都在跟数据、环境、部署、监控打交道。这不是坏事反而是工程落地不可或缺的部分。如果你觉得自己天生只想写模型、不想处理这些“杂事”那可能更适合做研究或纯算法岗但如果你想做AI工程就必须先爱上这80%的过程。2. 搭建一个不会跑崩的AI工程环境从Python到容器2.1 Python环境管理建议用conda pip的取舍Python环境是所有AI项目的起点但恰恰是最容易出乱子的地方。很多人图省事直接用Anaconda的base环境今天装一个包明天又装一个包最后环境里几百个依赖根本分不清哪个是哪个。这种情况我见得太多一升级依赖某个老接口变了整个项目就废了。我个人的习惯是给每个项目单独建一个conda环境并结合pip管理依赖。具体操作是这样的conda create -n ai-eng python3.10 -y conda activate ai-eng pip install torch2.1.0 --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt这里有几个细节要注意。第一能用pip就用pipconda在安装大型包时解析依赖很慢而且容易把Python版本搞乱第二项目里的requirements.txt必须定期更新别只是初始时生成一次就不动了新增的依赖要随时补进去第三不要为了图快直接装最新版的大包特别是深度学习框架版本固定非常重要。2.2 Dockerfile里该装什么、不该装什么conda环境解决了你本机的问题但换一台机器还是要重新搭建。更通用的方案是使用Docker把包括CUDA在内的所有环境都固化在镜像里。下面是我常用的一份基础Dockerfile用来跑PyTorch训练任务# 推荐直接用官方pytorch镜像省去配CUDA的麻烦 FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime WORKDIR /workspace # 先装requirements利用docker layer缓存 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再拷贝源码 COPY src/ ./src/ COPY scripts/ ./scripts/ ENV PYTHONPATH/workspace/src CMD [bash]这份Dockerfile看起来很短但背后有几个我踩过的坑值得说一下。第一个坑是最开始我用的是FROM python:3.10然后手动安装CUDA相关依赖结果因为CUDA和PyTorch版本不匹配跑RNN时莫名其妙报错查了半天才意识到底层是CUDA的Runtime库不一致。后来我直接换成官方PyTorch的镜像省了一大堆麻烦。第二个坑是关于layer缓存如果你把源码全部COPY完之后再装依赖每一次代码更新都会触发依赖重新安装。把requirements.txt放在前面代码放在后面能大幅缩短迭代时间。2.3 用Makefile把常用命令固化有了Docker你还需要一个命令入口来简化日常工作。很多人记不住那一长串docker run参数每次都要翻文档这时候一个Makefile就能派上用场。.PHONY: build train evaluate serve build: docker build -t ai-eng:latest . train: docker run --gpus all -v $(PWD)/data:/workspace/data -v $(PWD)/models:/workspace/models ai-eng:latest python scripts/train.py evaluate: docker run --gpus all -v $(PWD)/data:/workspace/data -v $(PWD)/models:/workspace/models ai-eng:latest python scripts/evaluate.py serve: docker run -p 8000:8000 -v $(PWD)/models:/workspace/models ai-eng:latest python scripts/serve.pyMakefile的好处是把复杂的命令打包成简单的单词新人同事拿到项目后不用问“怎么跑”直接执行make train就行。这算是工程化体验的一小步却能避免很多沟通成本。3. 数据管道模型质量的上限在源头3.1 数据获取与清洗的常规操作模型界有句话叫“垃圾进垃圾出”。AI工程里数据处理是真正决定模型上限的环节。我见过很多人花一周时间调模型却只给数据写一个简单的dropna()就完事了。实际上数据清洗里值得反复确认的事情非常多。以文本分类为例清洗流程至少包括去除HTML标签、统一编码格式、处理重复样本、过滤异常短文本、做分词或字符切分等。每一步看起来简单但操作的顺序和粒度都会影响模型。比如先去除重复再划分训练验证集和反过来操作两者在验证集上的污染程度完全不同。我的建议是把数据处理整成独立的模块并保证可重复执行。比如下面这段代码就是这样设计的# src/data/clean.py import re from typing import List def clean_text(text: str) - str: text re.sub(r[^], , text) # 去除HTML text text.replace(\u3000, ).replace(\xa0, ) # 全角空格 text re.sub(r\s, , text).strip() return text def filter_short_texts(texts: List[str], min_len: int 2) - List[str]: return [t for t in texts if len(t.split()) min_len]注意清洗函数不要写成一堆脚本直接在训练时临场调用最好是先离线处理生成一份干净的缓存数据训练代码只消费缓存数据。这样能保证你在调试时不会因为变换清洗逻辑而拿到不同的输入。3.2 用Pydantic或Great Expectations做数据验证数据工程的另一个重点是“验证”也就是确保进入训练时的数据格式与你预期的一致。这一点很多初学者会忽略直到模型上线后才发现线上数据格式跟训练时根本不一样准确率瞬间崩塌。我在小项目里推荐用Pydantic做轻量级数据验证因为它在定义模型Schema的同时就能完成数据类型检查。比如定义一个样本结构from pydantic import BaseModel class ReviewItem(BaseModel): review_id: str text: str label: int 0在读取数据时只要用ReviewItem.parse_obj(item)格式不对就会立刻抛异常而不会让脏数据悄悄混进来。如果是更复杂的表格数据我会用Great Expectations生成数据校验套件对字段的缺失率、数值范围、文本长度分布等做断言并集成到流水线里。这一步看着繁琐但一旦数据源发生变化你能第一时间发现而不是等模型训练完才发现效果奇差。3.3 DVC数据版本化的实际用法数据也要版本化原因很简单当你调整了数据清洗逻辑或者从上游获得了一份新数据你不想把旧数据直接删掉也不想在代码分支里保留一堆数据副本。DVC就是用来解决这个问题的。常用命令大致是这样# 初始化DVC并配置远程存储可以是NAS、S3等 dvc init dvc remote add -d myremote /path/to/storage # 把数据目录纳入版本管理 dvc add data/raw git add data/raw.dvc .dvc/config git commit -m add raw data v1 # 之后要切换不同数据版本时用git切分支再dvc checkout git checkout dev dvc checkoutDVC并不存储数据内容它存储的是一个指向远程数据文件的指针。好处是Git仓库不会因为大数据文件而膨胀同时数据和代码可以做到同样的版本记录。我在实际项目中会连模型产物也用DVC管理这样在复盘某个事故时一句“切到当天那个模型的版本”就能还原现场。4. 训练实验管理让人工智能变得可追溯4.1 最小可用的训练脚本结构训练脚本是AI工程师的“手工作坊”但很多人的训练脚本写成了一坨没有任何结构的线性流程。一旦要调整模型结构就要在代码里到处搜索。我推荐的最小结构是配置、数据加载、模型构建、训练循环、验证循环、指标保存。这六部分可以相对独立但要在入口文件里清晰串联起来。看一个简化示例# scripts/train.py import argparse import torch from src.data.loader import get_dataloaders from src.models.classifier import SentimentClassifier from src.trainer import Trainer from src.utils.logger import get_logger def main(): args argparse.ArgumentParser() args.add_argument(--config, typestr, defaultconfigs/base.yaml) cfg load_config(args.config) logger get_logger(train) train_loader, val_loader get_dataloaders(cfg.data_path, cfg.batch_size) model SentimentClassifier(cfg.vocab_size, cfg.embed_dim) trainer Trainer(cfg, model) trainer.fit(train_loader, val_loader) torch.save(model.state_dict(), cfg.save_path) if __name__ __main__: main()这里有一个很关键的点get_dataloaders必须从固定路径读取数据不要从外部临时传入一个文件名。因为可复现性不仅要求代码确定还要求数据的顺序、增强方式都确定。最好连随机种子也在入口处固定好。4.2 实验记录的四个层级模型跑起来之后最容易被忽略的就是实验记录。很多人的记录方式是Excel里贴一行没过几天就忘了哪行对应哪个代码版本。我的做法是把实验记录拆成四个层级超参数层学习率、batch size、embedding维度、loss权重等。用统一的配置文件保存不要散落在训练脚本里。指标层训练集的loss、验证集的精确率召回率F1以及推理时延、显存占用等。每跑完一个epoch都要记录下来。产物层模型权重文件、tokenizer文件、预处理配置要一起打包保存一个都不能少。代码环境层Git提交的commit号、Docker镜像的tag记录这一版模型是用哪份代码、哪个构建出来的。使用现成的工具可以省很多事。我个人用过MLflow和Weights Biases。MLflow比较适合内网部署它的mlflow run能把参数、指标和产物自动记录下来mlflow run . -P lr0.001 -P batch_size32你也可以手动记录但核心是“不记录就等于没跑”。别觉得这是老生常谈我见过太多团队返工查问题时只能靠猜。4.3 模型产物命名与保存规范最后一个容易出问题的点是模型文件的命名和保存。很多项目里模型文件叫model_final.pth过两天又改成model_best.pth到最后谁也分不清final和best有什么区别。我建议按“项目名日期commit号指标值”的格式命名比如sentiment-20240418-7f3a2c1-acc0.91.pth这个命名里包含的信息有项目名、训练日期、Git commit短哈希、关键指标值。只要看到这个名字你大概就能知道这个模型是哪个时间、哪次提交、大概什么水平不需要打开Excel去查。另外保存模型时不要只保存state_dict建议把整个模型定义所依赖的配置文件一起保存到产物目录里。这样即使后来代码重构了你也能用这个模型权重重新还原出当时的推理图。一个简单的做法是torch.save({ model_state_dict: model.state_dict(), config: cfg, tokenizer: tokenizer, }, cfg.save_path)虽然会增加一点磁盘占用但换来的可恢复能力远远值得。5. 部署上线从Notebook到API的惊险一跃5.1 离线与在线推理的架构区分部署是很多算法工程师的“死亡之地”。Notebook里做推理一切都很顺利一到上线就会发现模型怎么吃不到数据了响应怎么这么慢并发一高怎么内存爆了先分清两种推理场景离线批量推理比如每天晚上对全量用户做一次情感分类和在线实时推理用户发送一条评论立刻返回结果。这两种场景对延迟、吞吐、资源的要求完全不同。离线推理适合用任务队列加多个Worker并行处理在线推理则要封装成HTTP服务或gRPC服务并考虑超时、限流和优雅启动。我自己做项目时通常会先把离线推理流程做通确认结果稳定再抽出一个在线推理服务。这样做的好处是如果在线服务出了问题可以用离线重跑来兜底。5.2 FastAPI封装推理服务的完整示例在线推理我推荐用FastAPI因为它自带异步支持也方便自动生成接口文档。一个最小可用的服务代码长这样# scripts/serve.py import torch from fastapi import FastAPI from pydantic import BaseModel from transformers import AutoTokenizer from src.models.classifier import SentimentClassifier app FastAPI() model None tokenizer None class InferRequest(BaseModel): text: str class InferResponse(BaseModel): label: int confidence: float app.on_event(startup) def load_model(): global model, tokenizer checkpoint torch.load(models/sentiment-20240418-7f3a2c1-acc0.91.pth, map_locationcpu) model SentimentClassifier(checkpoint[config]) model.load_state_dict(checkpoint[model_state_dict]) model.eval() tokenizer checkpoint[tokenizer] app.post(/predict, response_modelInferResponse) def predict(req: InferRequest): inputs tokenizer(req.text, return_tensorspt, truncationTrue, max_length128) with torch.no_grad(): logits model(**inputs) probs torch.softmax(logits, dim-1) label int(probs.argmax(dim-1)) confidence float(probs.max(dim-1).values) return InferResponse(labellabel, confidenceconfidence)这段代码有几个容易踩坑的地方。首先模型加载最好放在startup事件里不要在每次请求时重复加载否则显存或内存会直接被打满。其次tokenizer要和模型权重一起保存因为如果线上代码tokenizer版本变了出来的token id可能完全不同预测结果自然对不上。如果你想追求更极致的性能还可以用ONNX导出模型再用ONNX Runtime加载或者用Triton做服务化。但对于大多数项目PyTorch FastAPI已经足够。5.3 上线前后的监控、回滚与性能优化部署不止是把API跑起来还要想清楚三件事监控、回滚、性能。监控上至少要记录每个请求的输入长度、延迟、预测置信度以及标签分布。我建议把请求的原始文本和返回结果都“抽样”存储一份方便日后排查。很多时候线上模型效果不好不是模型错了而是线上输入分布和训练集完全不同这时如果没有抽样日志你根本没法定位。回滚机制也不可少。最简单的做法是保留最近N个模型版本在配置中心里切换当前线上模型的版本号。一旦出现大面积badcase立刻切回旧版本。别用“改代码重新部署”这种办法几分钟的操作时间会让故障扩大。性能优化方面第一步是检查有没有重复计算。例如tokenizer每次请求都把原始文本重新切分如果文本长度较长就可以做缓存如果频繁做前向推理可以考虑开启PyTorch的torch.compile()或使用TensorRT进行推理加速。但说来说去最影响性能的还是批量推理如果业务允许尽量把多个请求合并成一个batch吞吐能提升不少。6. 一次完整实战评论情绪识别背后的AI工程细节6.1 需求拆解与数据准备前面讲了很多理论和零碎操作这里用我以前做过的“评论情绪识别”项目把全部环节串一遍。这个任务看起来简单但工程化改造时依然有不少门道。需求定义是要判断一条用户评论是正面还是负面同时输出置信度。第一件事不是找模型而是明确数据来源。我们当时从业务库里导出了近半年的用户评论有上百万条但其中一半是空文本、无意义符号或者重复刷屏。清洗下来有效样本只剩二十来万。在这个阶段我把数据按时间顺序排序后用前80%做训练集后20%做验证集而不是随机划分。目的是模拟线上模型面对未来数据的表现。这个划分方式特别重要如果你随机划分模型会被未来信息“剧透”线上效果自然高估。6.2 训练、验证与打包因为任务本身是文本分类我选择了一个轻量级的预训练模型用前几层冻结的迁移学习方案。训练脚本结构就是前面提到的配置、数据加载、模型、训练循环、验证循环。我用MLflow记录每次实验每个epoch的准确率和F1都会记录。训练过程中遇到最典型的问题是验证集在第三个epoch后开始提升不明显但训练集还在继续下降。这说明模型开始过拟合。我们的做法不是直接减少epoch而是加上early stopping以验证集F1为监控指标连续3个epoch不上升就停止并自动保存最优权重。训练完成后把最终的权重、tokenizer、配置文件一起打包命名为sentiment-20240418-7f3a2c1-acc0.91.pth。6.3 部署后遇见的真实问题和解决过程上线后第一周我们遭遇了一个非常经典的问题接口稳定返回但业务方反馈预测结果不准。我们拉出了抽样日志发现线上评论里有大量表情符号和繁体字比如“”和“開心”而我们的训练数据里几乎没有这些。虽然表情符号本身可能传达强烈情绪但我们的预处理脚本把它们直接过滤掉了导致模型在这些样本上几乎随机输出。解决办法是重新定义清洗逻辑保留表情符号并映射为特殊token加入繁体转简体步骤。随后我们更新了数据管道重新训练一版模型并迅速灰度切换。这件事给我最大的教训是模型上线前一定要先分析目标用户群的文本分布而不是默认训练数据就代表线上数据。另一个问题是延迟波动。白天高峰期P99延迟超过1.5秒排查发现是因为我们当时用的是CPU推理而且没有开启批量推理。后来借助Triton把模型部署成GPU服务并对请求做动态分batchP99延迟降到了200毫秒以内。这一步优化带来的体验提升比改模型结构更明显。7. 关于路线图与心态的最后提醒7.1 坚持“端到端”地做小项目对于从零开始学AI工程的人我的核心建议是不要只盯着模型排行刷分而是每个项目都端到端地走一遍“数据-训练-部署-监控”的闭环。哪怕是一个豆瓣评论情绪识别的小项目只要你把Docker、DVC、MLflow、FastAPI全部用上并给自己布置一个“换台机器也能完整复现”的挑战你学到的会比刷十道LeetCode还多。你可以循序渐进地扩展项目边界。第一次只做一个分类任务第二次增加一个数据版本回滚场景第三次加上监控和AB测试。每个阶段都去解决一个实际工程问题四五个项目下来你的经验就能覆盖大多数中小型AI项目的需求。7.2 三个必须长期养成的习惯在我带过的新人里成长快的人几乎都有三个习惯。第一个习惯是“每次训练前先确认实验记录属性”代码commit号、数据版本、环境tag少一个不跑。不要为了省这几分钟而在以后花几个小时追溯。第二个习惯是“线上出问题先看日志和监控再怀疑模型”。很多所谓模型badcase最后都指向数据漂移、上游字段错误甚至前后端版本不一致。学会用监控数据层来定位问题会让你在一次事故中少走很多弯路。第三个习惯是“写文档不嫌麻烦”。这里说的文档不是长篇大论而是把每个命令怎么用、为什么这样设计记在项目的README里。哪怕是给三个月后的自己看也值回票价。我吃过太多亏写了一段非常复杂的SQL清洗逻辑结果三个月后没人看得懂只能从头来。从零开始做AI工程真的不需要一开始就啃大厂技术栈也不需要掌握多高深的算法。你只需要做好把一个个模型真正用起来、跑起来、优化起来这件事。先把这个闭环打通再往上搭建更大的系统时就会发现那些被视作“底层杂活”的基础能力恰恰是AI工程中最难替代的部分。