ARTICLE DETAIL

资讯详情

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

从零开始AI工程化:数据、训练、部署与监控全链路实战

从零开始AI工程化:数据、训练、部署与监控全链路实战 1. 这套从零开始的AI工程化到底在解决什么问题过去两三年只要聊到AI大家讨论的几乎都是算法、模型、算力。但真正上过生产环境的人心里都清楚把模型跑通只是万里长征第一步。一个能用的AI系统牵扯到数据怎么管理、特征怎么复用、模型怎么评估、推理服务怎么扛住流量、反馈怎么回流这整条链路才是工程化要解决的硬骨头。所谓ai-engineering-from-scratch其实就是把这一整套东西从只有一个想法开始一步步搭成一条可靠的流水线。我见过太多团队卡在同一个地方本地Notebook里demo跑得飞起一换到线上环境就各种崩。不是模型不行是工程链路缺失——数据没有版本管理实验没有记录训练脚本和环境依赖靠口头传承上线部署全靠手敲命令。这种状态基本等同于在沙地上盖楼。所以从零开始做AI工程化的关键不是去追新模型而是把数据、训练、评估、部署、监控这套地基先夯实。这篇内容主要面向两类人一类是在传统软件团队里想引入AI能力但不知道从哪下手的后端工程师或技术负责人另一类是算法出身、想补齐工程短板让自己能独立把模型推到生产环境的算法工程师。我会用一套完整的、可落地的示例项目来串联整个工程链路直接照着做就能起步。2. 整体设计与技术选型为什么是这条路2.1 从零搭建的核心理念From scratch这个说法容易让人误解成什么都自己造轮子。实际上工程领域讲究的是站在巨人的肩膀上组装乐高。这里的从零指的是从空的代码仓库、空的数据目录开始把一个项目从0推到1的完整过程。我们需要的不是重新发明数据库、重写训练框架而是建立一套标准和流程把这些成熟工具粘合起来。在设计整个体系时我给自己定了几条铁律第一任何环节都要可追溯代码有版本、数据有版本、模型有版本连实验参数都得留痕第二任何环节都要可重跑今天跑的Pipeline三个月后同样能复现出同样的结果第三任何环节都要可观测数据质量、训练指标、推理延迟、模型漂移全都要有监控。这三点看起来朴素但绝大多数失败的AI项目都是栽在这上面。2.2 技术栈选型实操技术选型这件事最怕的就是什么火用什么。我自己的原则是优先选社区活跃、生态完善、团队学习成本低的方案。下面这套组合我踩过很多坑之后沉淀下来的适合做以Python为主的技术栈覆盖从数据处理到模型服务的全流程环节推荐工具核心理由备选方案项目管理Poetry依赖锁定精准跨环境复现能力强pipenv、uv数据版本管理DVC和Git类比学习成本低支持云存储lakeFS、Delta Lake实验追踪MLflow开箱即用支持本地和远程部署Weights Biases特征存储Feast解决训练和推理特征一致性的痛点自建Redis特征服务模型服务FastAPIONNX Runtime轻量、高性能、部署灵活Triton、vLLM编排调度Prefect相对于AirflowPython原生更友好Airflow、Temporal监控告警PrometheusGrafana生态成熟云原生标配ELK、Sentry选这套组合最核心的考量是降低维护的心智负担。像DVC和MLflow概念简单上手快却解决的是AI项目中两个最致命的问题数据管线和实验过程不可复现。Prefect又是一个靠普通Python函数就能定义工作流的调度工具工程师不用专门学领域特定语言。3. 核心细节解析与实操要点3.1 项目结构设计从代码组织开始防止混乱一个AI项目之所以容易烂掉很多时候是从目录结构开始的。我见过不少项目的代码训练脚本和数据处理逻辑混在一起、配置文件和Notebook散落各地最后谁也理不清。一套清晰的目录结构本身就是工程化的第一步。下面是我常用的项目结构以保险理赔文本信息抽取为例insurance-claim-nlp/ ├── configs/ # 所有配置文件 │ ├── data_config.yaml # 数据路径、切分比例 │ ├── train_config.yaml # 模型参数、学习率、Epoch数 │ └── serve_config.yaml # 推理服务参数、模型路径 ├── data/ │ ├── raw/ # 原始数据只读由DVC管理 │ ├── processed/ # 清洗后的数据 │ └── features/ # 特征工程产物 ├── src/ │ ├── data/ # 数据加载、清洗、校验代码 │ ├── features/ # 特征工程代码 │ ├── models/ # 模型定义代码 │ ├── evaluation/ # 评估指标计算代码 │ └── serving/ # 推理服务代码 ├── pipelines/ # Prefect工作流定义 │ ├── training_pipeline.py │ └── inference_pipeline.py ├── experiments/ # MLflow实验记录 ├── notebooks/ # 探索性分析按日期主题命名 ├── scripts/ # 命令行入口 │ ├── run_train.py │ └── run_evaluate.py ├── tests/ # 单元测试和集成测试 ├── pyproject.toml # Poetry依赖定义 └── dvc.yaml # DVC数据管道定义这套结构的设计逻辑是按功能划分而不是按流程划分。数据、模型、服务、工作流各司其职各自有清晰的边界。配置文件统一放configs里方便对不同场景做参数切换也方便做实验对比。3.2 数据管理DVC和你的第一道护城河数据是AI项目里最容易失控的部分。原始数据可能是业务方发过来一个压缩包也可能是从数据库导出的CSV。如果不加管理过两个月谁也说不清模型是用哪份数据训练出来的。DVCData Version Control解决的就是这个问题。它不存数据本身而是存数据的元信息和哈希值用Git来管理这些指针文件。每次数据更新DVC会生成新的哈希并在Git里留下记录。说到底就是给数据也上了版本控制和代码一样可以回溯。实际使用的时候我会这样组织原始数据丢到data/raw目录注册为DVC管理处理后的数据放data/processed同样用DVC跟踪。DVC支持本地文件系统、S3、OSS等多种存储后端团队协作时可以共享同一个远程存储。配合dvc repro命令数据变更后能自动重新执行下游处理步骤确保每个环节都是最新的。注意DVC的远程存储一定要设置好。如果只在一台机器上做开发本地存储就够用但团队协作时千万不能把远程存储指向某台个人电脑——数据还依赖那台电脑在线工程上就是灾难。3.3 实验追踪MLflow给每次训练上户口算法工程师最常干的傻事是改了一版模型训练出来效果不错但忘记记录用什么参数跑出来的。几周之后想复现只能靠猜。MLflow就是要终结这种事情。我在项目中用MLflow做三件事记录参数、记录指标、记录模型产物。每跑一次训练MLflow会自动生成一条实验记录包含用的什么数据集文件、什么模型结构、什么超参数以及最终的准确率、召回率、F1值这些指标。跑完之后还能把模型注册到Model Registry里方便管理模型的版本和状态。操作上只需要在训练脚本里加几行代码import mlflow mlflow.set_tracking_uri(http://localhost:5000) with mlflow.start_run(run_namebert_base_v1) as: mlflow.log_param(learning_rate, 3e-5) mlflow.log_param(batch_size, 32) mlflow.log_param(model_type, bert-base-chinese) mlflow.log_metric(f1_score, 0.892) mlflow.log_metric(precision, 0.901) mlflow.log_artifact(model.pkl, artifact_pathmodel)跑完训练后浏览器打开MLflow UI就能看到一条清晰的实验记录。想做参数对比直接在UI上勾选几次实验就能看到指标对比。这比拿Excel记录参数体验好太多了。MLflow的Model Registry还能管理模型的整个生命周期刚训练完的是Staging状态测试通过后标记为Production后面发现问题可以再标记回Archived。这个机制让模型上线这件事有了软件工程里的发布管理味道。3.4 实验复现Docker镜像包住所有运行环境环境不一致是复现问题的一大根源。有些模型本地跑得好好的换台机器就各种报错——多半是Python版本、CUDA版本、依赖库的锅。工程化做法是把环境一次性固化成Docker镜像谁拉下来都是一样的环境。我通常用Dockerfile定义整个训练环境基于PyTorch官方镜像再把项目代码和依赖都打进去。核心是让镜像构建可重复也就是说基础镜像要固定标签不要用latest系统依赖和Python包都明确版本号。Poetry在这里就派上大用场了它生成的poetry.lock文件会把每个依赖的版本都锁死让镜像每次构建的内容都一致。启动容器训练时我通常这样跑docker build -t claim-nlp-train:0.1.0 . docker run --gpus all \ -v /path/to/data:/workspace/data \ -v /path/to/mlruns:/workspace/mlruns \ claim-nlp-train:0.1.0 \ python scripts/run_train.py --config configs/train_config.yaml通过挂载卷的方式把数据和实验记录映射到宿主机这样容器销毁也不怕数据丢失。镜像版本编号和代码Git提交号对应起来基本就能做到任何时刻、任何机器、复现任何一个历史实验。4. 实操过程与核心环节实现4.1 端到端示例从零开发保险理赔信息抽取系统为了把抽象的工程化步骤讲透我以一个具体的项目为例保险公司的理赔申请文本信息抽取。原始输入是一段案情描述文本目标是抽取三个关键信息事故日期、事故地点、驾驶员姓名并判定责任类型。这个项目虽然不大但麻雀虽小、五脏俱全有数据准备、模型微调、评估、推理上线、反馈闭环整条链路能完整走通。用这个案例串起整个实操过程读者可以直接跟着一步步实现。4.2 数据准备与校验别让脏数据毁掉模型第一步是数据收集。因为涉及隐私这里我用模拟数据替代。一共2000条模拟理赔文本每条包含完整的案情描述和三个待抽取信息的标注。数据量不大但足够演示完整的工程流程。拿到原始数据后先做数据质量检查。我会写一个简单的脚本输出数据的基本统计信息总条数、字段缺失数量、文本长度分布、标签分布。然后把明显有问题的数据挑出来空文本、标签残缺、文本和标签对不上的。import pandas as pd df pd.read_csv(data/raw/claims.csv) print(f总条数: {len(df)}) print(f事故日期缺失: {df[claim_date].isna().sum()}条) print(f驾驶员姓名缺失: {df[driver_name].isna().sum()}条) print(f文本长度分布:\n{df[text].str.len().describe()})这段脚本看起来简单但能做到在动手训练模型之前先对数据有一个基本体检。我见过太多项目因为数据太脏模型训练结果一路飙升的假象背后是模型在拟合标注错误。清洗完数据后把数据切分成训练集、验证集、测试集比例是80%、10%、10%。注意切分的时候一定要固定随机种子保证每次切分结果一致。这个看似不起眼的点在后面的实验对比中非常关键——如果每次切分的数据不一样模型指标差异就没法归因于模型改动。4.3 基于自有数据微调模型让通用模型学会你的业务示例项目里我用一个开源的中文预训练模型BaseModel作为底座通过在自有数据上微调让它学会抽取理赔文本的关键信息。现在大模型很火但实际工程中很多中小团队面对的是特定领域的结构化抽取任务用中等规模的预训练模型做微调成本可控、效果好、还便于部署反而是最务实的路径。微调的代码结构这样设计模型定义、训练逻辑、评估逻辑分离。模型定义在src/models/里训练和评估脚本在scripts/下。训练时通过配置文件控制超参而不是把参数硬编码在代码里。训练命里有一个关键细节输入的文本长度必须要做处理。理赔文本有的很短、只有几十个字有的可能超过512个字。预训练模型通常有最大输入长度限制超出部分会被截断。工程上我会做两件事一个是对文本长度做统计确定合理的截断长度另一个是在处理Pipeline里做动态padding让一个批次内的数据长度对齐节省计算资源。在训练时我会重点关注损失曲线和验证集的指标。如果训练损失下降但验证损失不降反升说明过拟合了这时候应该加早停或者正则化。早停我一般监控验证集的F1值连续三个Epoch没有提升就停掉省时省力。4.4 模型评估与验证别只看一个指标很多新手评估模型时只看准确率Accuracy。这在类别不均衡的场景下会严重误导。理赔信息抽取里责任类型这个标签可能全责占70%同责占20%无责占10%。如果模型把所有样本都预测成全责准确率也有70%但显然是个废模型。正确的做法是看精确率Precision、召回率Recall、F1值尤其要看每个类别的独立指标。我用sklearn.metrics里的classification_report来做这件事from sklearn.metrics import classification_report print(classification_report(y_true, y_pred, target_names[全责, 同责, 无责]))输出表格会详细列出每个类别的精确率、召回率和F1值一眼就能看出模型在哪类样本上表现差。如果无责这类样本的召回率很低说明模型对这类样本识别不足可能需要更多训练数据或者让模型在易混淆的样本上加大权重。评估还有一个容易被忽略的环节错误分析。我会把预测错的样本专门抽出来整理成表格看看模型具体错在哪里。是标注本身有歧义还是文本里有特殊情况比如日期格式是去年而不是具体日期。这种定性的分析往往比看指标更能指导下一步优化方向。4.5 推理服务上线从模型文件到高可用API训练和评估做完模型还躺在MLflow里不算真正用起来。下一步是把模型包成一个推理服务让业务方通过HTTP接口拿到预测结果。我推荐用FastAPI加ONNX Runtime这套组合。先说一下为什么用ONNX训练框架比如PyTorch推理时有额外的运行开销转化成ONNX格式后模型的计算图被优化过推理速度更快部署时也不需要安装完整的深度学习框架一个几百MB的ONNX Runtime库就够了。转换和部署的流程大概是这样的先把模型导出为ONNX格式然后写一个FastAPI应用加载模型、定义接口最后用uvicorn启动服务。import onnxruntime as ort from fastapi import FastAPI from pydantic import BaseModel app FastAPI() session ort.InferenceSession( models/claim_extractor.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider] ) class ClaimRequest(BaseModel): text: str app.post(/predict) def predict(req: ClaimRequest): inputs tokenizer(req.text, return_tensorsnp) outputs session.run(None, dict(inputs)) return parse_outputs(outputs)服务上线前有几件必须做好的事模型预热服务启动后先发几个请求把显存里的模型激活避免第一个真实请求延迟过高。超时控制接口要设置超时时间防止推理卡住导致请求堆积。批量推理如果请求量很大考虑把多个请求拼成一个batch一次推理吞吐量能提升好几倍。版本兼容Model Registry里保留旧版本模型新版本上线出问题时可以快速回滚。部署方式我一般用Docker容器配合Kubernetes做自动扩缩容。如果团队规模小先跑在一台云服务器上、用systemd守护进程管住服务也行——工程的关键不是用多复杂的技术而是掌握何时需要多复杂的技术。4.6 监控与运维模型上线只是开始很多人以为模型上线就万事大吉了其实真正的挑战在线上。模型在训练数据上表现好不代表在真实数据上一样好。数据分布会漂移业务方使用习惯会变模型的表现会随时间下滑。我在项目里会监控几类指标分三个层面监控层面指标告警条件系统层API响应时间、GPU利用率、内存占用P99 500ms 或 GPU利用率长期 20%数据层请求文本长度分布、标签分布分布和训练集差异超过阈值模型层预测置信度、业务方反馈结果平均置信度持续下降或负反馈增多监控和告警的基础设施我用Prometheus加Grafana。FastAPI有专门的Prometheus中间件能直接暴露指标接口然后配置Prometheus定期拉取Grafana做可视化大盘。比监控更关键的是建立反馈闭环。我特别建议在业务系统里加一个结果反馈按钮——标注人员看到模型抽取出的事故日期如果不对在界面上纠正一下。这些纠正过的样本攒下来定期回流到训练数据集里持续迭代模型。这个循环一旦转起来模型的效果会随着时间越来越好。这是工程化和实验demo最本质的区别。5. 常见问题与排查技巧实录5.1 依赖冲突困局与依赖锁定方案在AI项目里依赖冲突几乎人人都遇到过。最常见的是训练时用的transformer库版本和推理时不一样导致模型加载失败或者某个库升级后行为变了结果复现不了。解决办法只有两个字锁定。用Poetry之后pyproject.toml加pyproject.lock文件把依赖锁死。Docker镜像构建时用这个lock文件安装依赖保证谁构建出来的环境都一样。我还见过团队用了更牛的做法把训练和推理环境分别写Dockerfile互不干扰。模型训练时用PyTorch大镜像部署时只打包ONNX Runtime和必须的库镜像小了不止一个量级。5.2 训练和推理特征不一致的隐患这个坑特别隐蔽。训练时对特征A做的是缺失值填充为0推理时如果忘了做同样的处理模型效果就会莫名变差。NLP场景里特征不一致还体现在分词器上——训练时用的分词器和推理时必须是同一个版本否则同样的文本切出来的Token完全不同。解决这个问题最靠谱的方式是把特征处理函数封装成独立模块训练和推理共用同一份代码。我从不用训练Notebook里写一份、部署代码里再写一份的方式因为这两个地方只要有一行不一致线上效果就分分钟打脸。锅底的做法是加一个一致性校验做完特征处理后在训练和推理各跑一个相同输入样本比对输出是否一致。这一步可以直接写进CI流程里每次改完代码自动跑。5.3 推理延迟优化三个立竿见影的手段线上对推理延迟非常敏感我曾遇到过模型P99延迟超过800ms业务方明确反馈太卡了的情况。针对延迟优化我常用的三板斧是第一把推理框架切到ONNX Runtime。之前PyTorch直接推理时单条样本耗时大约40ms把模型转成ONNX后延迟降到约12ms。关键是加开启CPU或GPU的优化参数比如graph_optimization_level调成ORT_ENABLE_ALL。第二开启请求级别的批处理Dynamic Batching。单个请求一次推理GPU利用不充分。在服务层加一个小队列把毫秒级时间窗口内到达的请求攒起来拼成一个batch再送进模型。这样吞吐量提升明显延迟增加却能控制在可接受范围。第三模型蒸馏。如果业务允许把大模型蒸馏成小的版本量级差距会很大。蒸馏后的模型部署成本低延迟也能降的更狠。当然这是锦上添花基础工程问题没解决前别急着动这一步。5.4 GPU利用率极低导致资源浪费明明上了GPU训练时显卡利用率却只有10%甚至更低——这个问题背后的原因大多是数据加载太慢GPU在等着CPU喂数据。我的排查顺序是先看CPU和内存情况确认数据加载是不是瓶颈然后用Profiler工具分析训练循环里每个操作的耗时占比最后检查数据加载的配置。解决起来一般就是加大num_workers数据加载的进程数、开启prefetch_factor预取因子、把数据格式改成内存映射的二进制格式比如TFRecord或WebDataset。在我的项目里光是这几步GPU利用率就从30%提升到了85%以上训练速度提升将近3倍。注意num_workers不是越大越好设太多可能会因为进程间通信开销过大而适得其反。一般按CPU核心数的一半到三分之二来设需要实测调优。5.5 常见问题速查表现象可能原因排查/解决思路模型复现不了随机种子未固定、数据版本不一致固定所有随机种子用DVC确保数据版本一致训练和推理效果差异大特征处理不一致、分词器版本不同统一特征模块代码做一致性校验API响应越来越慢内存泄漏、请求堆积定期重启检查长连接做压测线上指标和离线评估差异明显数据分布漂移监控数据分布定期重训模型Docker构建后模型加载失败模型文件没有正确拷贝到镜像里检查Dockerfile的COPY路径和模型路径6. 工程化思维与落地实践建议在真正动手之前有几个工程化层面的认知调整我个人觉得比技术本身还重要。第一个认知是模型是一个需要持续维护的活物不是一个一次性的交付物。软件行业常说代码上线才是开始AI项目这句话的分量更重。模型上线后数据还在持续变化业务还在持续变化模型必须跟着迭代。这个认知没建立起来后续的工程体系建设就会迷失方向。第二个认知是自动化不是目的降低变更成本才是。DVC、MLflow、CI/CD这些工具或机制本质上都是在降低修改一项东西所引发的连锁成本。只要改动数据和代码的成本够低团队就敢于频繁迭代频繁迭代模型效果才能真正上去。第三个认知是从小处起步别一上来就上大体系。如果只是一个几人的小团队做试点项目硬上Kubernetes加Feast加全套监控反而会把自己拖垮。我在很多项目里用到的组合其实非常朴素一台GPU服务器、Docker、MLflow、DVC、FastAPI已经可以撑住一个中型项目的需求。等业务规模大了再往分布式方向演进。渐进式的方案是工程里最稳的路线。在具体落地时我给读者一个路线图上的建议先把最简单的端到端流程跑通——数据能进、模型能训、接口能调、指标能看。这时候别想优化先把经脉打通。第二步把流程固化到代码和脚本里做到一键执行。第三步加入版本管理和实验追踪让每一次改动都可回溯、可对比。第四步再把监控、告警、反馈闭环加上。这套节奏基本上能让你在两个月内从一个空仓库走出一条完整的AI流水线而且每一步踩下去都是实的。
返回列表