ARTICLE DETAIL

资讯详情

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

从零开始搭建AI工程:数据、模型、部署与监控全流程实战

从零开始搭建AI工程:数据、模型、部署与监控全流程实战 1. 为什么说AI工程不是“跑通模型”那么简单我最早接触AI工程这个概念是在一次内部项目评审会上。当时一个算法团队花了三个月把准确率从92%提到了93%准备上线结果发现模型推理延迟超出预期、GPU显存吃紧、数据特征在线上和线下不一致整个项目卡在“能跑”和“能用”之间。这个场景我相信很多做过AI落地的人都有共鸣。所谓AI工程ai-engineering就是从模型之外的那部分工程化能力——数据管道、训练基础设施、模型部署、监控反馈、版本迭代——把一个人手中的“玩具代码”变成一个稳定、可维护、可扩展的线上系统。如果你打算从零开始走一遍这条路这篇内容就是为你准备的。这个标题“ai-engineering-from-scratch”很真实它意味着你不是从一个现成的平台起步而是自己动手把环境、数据、训练、部署、监控等环节一个个搭起来。适合谁看适合那些已经会写Python、懂一点机器学习基础但没真正把模型送上线的开发者和算法工程师也适合想从纯算法转型做AI平台/MLOps的人。我要分享的不是某个具体框架的三分钟速成而是整套工程化思维怎么建立、关键环节怎么落地、以及我实际踩过的坑和解决办法。2. 项目初期的总体架构与选型思路2.1 先理清AI工程的正交切片数据、模型、算力、平台从零开始最容易犯的错误就是把AI工程当成“装个深度学习框架写个训练脚本然后用Flask包个接口”这么一条线。实际上一个可用的AI工程体系至少包含四个正交的维度数据、模型、算力和平台。数据采集、清洗、标注、版本化、质量校验、特征存储。数据不是“喂给模型的东西”而是一条需要持续运维的供应链。模型算法选型、训练实验追踪、超参调优、模型评估、模型注册。算力GPU集群、资源调度、分布式训练、弹性伸缩。平台任务编排、CI/CD、监控告警、模型上线/回滚、线上服务治理。这四个维度交叉在一起就是AI工程的日常。如果你的项目只是单机训练一个模型那么可以简化但一旦涉及多人协作、多模型版本、持续迭代这四个维度任何一个没考虑后面都会以各种奇怪的方式找你麻烦。我建议从零开始时不要急着上Kubernetes、上全自动MLOps平台而是先用最朴素的工具把一条端到端链路跑通理解每个环节的职责然后再逐步做工程化升级。直接上一堆抽象层你会被工具本身淹没。2.2 为什么推荐“先跑通、再解耦”的策略很多人喜欢一上来就设计一个天衣无缝的架构数据湖、特征平台、训练平台、推理平台、监控平台。这个思路本身没问题但问题在于“从零开始”的过程中你对问题的理解还不完整架构很容易设计过度或者遗漏关键点。我推荐“先跑通、再解耦”的策略首先用最简单的方式在单机或者一两台GPU机器上把从数据到模型再到HTTP服务的完整流程走一遍。这一步的目的是验证可行性和发现真正的瓶颈。等到链路稳定后再根据实际痛点去解耦模块、引入分布式和自动化。打个比方就像先用手工做出一辆能跑的电动车然后再考虑怎么把它改造成流水线生产。手工样车阶段你能最直观地了解每个部件的受力状态直接上流水线你可能连电机怎么装都搞不清楚。在我的实际项目里这个“先跑通”阶段通常会限定在一周内完成。具体目标很简单有一个能训练出不错效果的脚本一个能加载模型并返回预测结果的API以及一套能看到训练过程和预测日志的可视化面板。这三个东西有了立刻做架构演进比一开始就铺大平台要有效得多。3. 数据工程AI工程的隐形地基3.1 数据清洗与标签质量最容易被低估的环节如果让我说AI工程里最脏最累但价值最高的工作数据清洗绝对排第一。很多算法模型效果不好根本原因不是模型结构不够先进而是训练数据里有大量噪声重复样本、错误标签、特征分布不均、时间序列错位等。我在做文本分类项目时一度发现模型在验证集上表现不错但上线后对新数据的预测完全不行。排查到最后发现训练数据里有一条规则某个特定来源的样本被错误地标注成了另一个类别数量还不少模型直接学歪了。这种问题靠调参根本无解只能回到数据层面清理。所以从零开始做AI工程第一件事不是选择多么先进的模型而是建立一套数据管线的“质检”逻辑。主要包括缺失率统计每个字段缺失比例超过阈值就告警。重复检测基于样本哈希或关键字段去重。标签分布监控无论是分类还是回归标签分布如果相比历史基线发生剧变要警惕标注错误或数据采集来源漂移。数据漂移检测用PSIPopulation Stability Index或KS统计量定期比较训练特征分布和线上实时特征的分布。样本级异常检测通过聚类或异常检测模型发现那些不符合整体规律的样本人工抽检。这些步骤不需要一开始做得很重可以先写几个脚本定期跑或者用类似Great Expectations的工具来定义数据期望。在“from-scratch”的阶段我更推荐先用Pandas写简单的校验函数因为上手快、看得明白后面再迁移到分布式计算框架。3.2 数据版本化保证可复现性的基础模型的可复现性是AI工程和AI科研最大的区别之一。科研里跑出一个分数可能只是记录一下生产环境里模型上线后如果出问题必须能快速回退到之前的某个“已知良好”状态。这意味着数据和模型一样需要版本化。数据版本化的思路类似于Git但不是用Git管理大数据文件。常见方案有两种一种是用专门的数据版本控制工具例如DVC它通过描述文件记录数据集的元信息和位置训练时再拉取对应版本另一种是把它集成到对象存储和数据库里每次生成数据集时记录哈希值、生成时间和来源。我个人经验从零开始时用DVC最合适它轻量和Git配合得很好。你把数据文件放在本地的某个目录DVC会为它生成一个.dvc文件里面是数据文件的哈希和存储位置。训练脚本指定读取某个.dvc文件或者直接通过dvc pull获取对应数据版本。这样你在做实验时可以随时回到“上周三那份数据”的状态而不是靠记忆去翻目录。需要强调一点模型版本和数据版本是强绑定的。训练出某个效果的模型不光要记录模型参数文件还要记录训练所用的数据集版本、代码提交哈希、环境依赖版本。这就是后面讲模型注册表时的重要前提。4. 模型训练与实验管理的实操细节4.1 训练脚本的结构化设计从零开始训练模型时很多人习惯把数据处理、模型定义、训练循环、评估逻辑全部写在一个几百行的脚本里。这样做的坏处是当你要改一个评估指标时可能不小心动了数据处理逻辑当你要复现实验结果时光看这个脚本很难知道当时的参数和依赖环境。我在工程化训练脚本时通常会拆成四个层次配置层用YAML或JSON保存所有超参数、数据路径、输出路径。命令行参数只负责覆盖少量关键项例如--config、--resume。数据层数据集加载、预处理、增强的独立模块提供统一的Dataset类接口。模型层网络结构定义不废弃原有结构保留通过配置文件切换不同backbone的能力。训练与评估层训练循环、checkpoint保存、评估逻辑。这部分不依赖固定的数据格式所有数据从Dataset接口取。这样做的好处是每个模块职责单一方便复用和测试。例如数据层单独测试时可以从已配置好的数据目录快速加载模型层不用关心数据从哪来只要接住张量就行。4.2 实验追踪不是可选项而是必备品做AI工程实验追踪不是“记录一下loss变化”那么简单。你需要知道哪个代码版本、哪份数据、哪个参数组合、在哪个环境下跑出了这个结果。如果没有系统性的实验追踪复现结果全靠运气出了bug没人能定位。我常用的方案是MLflow它足够轻量支持本地模式也支持远端服务。在训练开始时把代码git地址和commit号、关键参数、数据集版本号记录到MLflow的run里每个epoch记录loss和metric训练结束后记录模型文件的路径。这样你只要输入mlflow run的ID就能看到那个实验的全部关联信息。如果你不想引入额外依赖也可以用一个简单的CSV表格记录时间、实验名称、数据版本、代码commit、关键参数JSON、结果指标、模型文件路径。但对于多人协作还是建议用MLflow或Weights Biases这类工具。实际操作中还有个容易被忽视的点随机种子要固定且记录。特别是训练深度学习模型时如果不固定随机种子每次运行结果会有差异实验对比就失去了公平性。从零开始就要养成这个习惯在代码开头设置random.seed(42)、np.random.seed(42)以及PyTorch/TensorFlow的相应seed。不同的库需要分别设置有的还需要设置PYTHONHASHSEED环境变量。4.3 超参数优化与评估策略超参数优化是AI工程里一个反复出现的主题。很多人一上来就做网格搜索但网格搜索在高维参数空间里效率很低。我建议按这个顺序来做先手动试几组关键参数比如学习率、batch size找到能收敛的范围。定了范围后用随机搜索或者贝叶斯优化推荐Optuna跑一轮自动化搜索。每次搜索时用早停来控制训练时间。设定patience值比如验证集指标连续5个epoch不提升就停止。评估时要固定一个“评估基准集”这个基准集在超参搜索期间不改动防止模型过拟合到验证集上。评估策略上我坚持使用分层抽样的方式划分训练集和验证集特别是分类任务保证每个类别的比例在训练集和验证集保持一致。还要注意在时间序列数据上要按时间先后顺序划分不能随机打乱否则会引入未来信息。另外对于小样本场景交叉验证可能比固定的单次划分更可靠。但从工程角度考虑交叉验证会增加训练成本所以在“从零开始”过程中可以先做一次单次划分把流程跑通后续再考虑更严谨的评估方案。5. 模型部署从notebook到线上服务的最后一公里5.1 在线推理服务与批处理任务的取舍模型部署的前提是搞清楚业务需求到底要什么。有的场景需要毫秒级实时响应比如推荐系统、风控审核这种适合在线推理服务有的场景只需要定期跑一批全量数据比如日报生成、离线标签计算这种用批处理任务更合适。如果选择在线推理最简单的方式确实是写一个FastAPI或Flask应用把模型加载到内存提供一个/predict接口。但这个方式在工程上会有几个问题并发请求高时模型推理是CPU/GPU密集操作容易造成阻塞。需要合理配置线程池/进程池或者直接使用TorchServe、Triton这类推理服务器。模型结构升级和线上服务耦合过紧回滚麻烦。需要把服务接口和模型文件解耦。我最常用的轻量方案是用FastAPI封装一个服务内部加载经过torch.jit.script或ONNX Runtime转换后的模型同时用Redis或内存缓存做请求级缓存。如果并发要求高再在前层加一个负载均衡多个服务实例共享同一张GPU卡或者独立部署。对于批处理任务用Airflow或者调度系统定时触发一个Python脚本读取一批数据形成预测结果再写入目标存储。批处理任务要重点考虑的是失败重试、数据分片、幂等性和监控。5.2 模型格式与推理优化模型格式直接影响推理性能和部署灵活性。PyTorch训练出来的.pth文件是Python环境下的序列化格式不适合直接跨语言、跨设备部署。两点建议如果只用Python并且保留训练框架可以用torch.jit.script或torch.jit.trace把模型转为TorchScript格式减少动态图带来的解析开销。如果模型要跑在不同硬件平台、异构环境优先导出为ONNX格式。ONNX是开放的模型交换格式能转成Triton、TensorRT、ONNX Runtime等多种后端。还要注意算子兼容性不是所有PyTorch算子都支持ONNX导出比如某些动态控制流的操作。推理优化上我踩过的坑包括未做batch动态填充导致不同长度的输入需要多次调用性能很差没有开启推理模式model.eval()和torch.no_grad()导致计算图被构建显存暴涨。这些细节看起来小但在生产环境会直接决定你能不能扛住线上流量。这里分享一个最朴素的性能测试方法用abApacheBench或者wrk对接口做简单压测观察QPS、P99延迟、GPU使用率。没有压测就上线和没有测试就提交代码一样危险。5.3 模型服务的优雅退出与预热在Kubernetes这类容器编排环境下部署模型服务时有一个容易被忽略的问题容器被杀掉时当前正在处理的请求会被粗暴中断客户端直接收到连接错误。比较标准的做法是给服务配置优雅退出逻辑在收到SIGTERM信号后先停止接收新请求等待当前请求完成设置一个超时时间再退出进程。另一个值得注意的点是模型预热。模型文件通常在服务启动时才从磁盘加载到内存/显存这个过程可能需要几十秒到几分钟不等。如果服务一启动就对外暴露流量探针检查是在模型加载完之前完成的那么最初一段时间的请求会直接失败。解决方式在启动流程里显式加载模型加载完成后再去注册到服务发现中心或者让健康检查接口返回“未就绪”状态直到模型加载完毕。这部分是我从一次部署事故里学到的。当时上线一个基于BERT的文本匹配服务模型文件接近2GB启动加载需要20多秒。容器探针设置的是5秒间隔6次失败就认为启动失败结果服务一直起不来。后来把探针的initialDelaySeconds和阈值调大并且在应用代码里加了“就绪”开关问题才解决。6. 监控与告警AI系统的“照妖镜”6.1 模型性能监控指标只是开始模型上线不是终点而是长期维护的起点。模型在训练时表现良好但线上数据会漂移、用户行为会改变、上下游系统升级也可能影响输入特征。没有监控模型劣化你根本无法感知。监控要分几个层面基础设施监控CPU、内存、GPU利用率、网络IO、磁盘空间。这些属于常规运维但AI工程里要特别关注GPU显存和利用率。显存不够会导致OOM利用率低说明可能在等待数据或者模型结构有问题。服务性能监控请求QPS、延迟百分位P50/P95/P99、错误率。这些可以直接接PrometheusGrafana。业务指标监控比如推荐系统的点击率、搜索的NDCG、风控的拒绝率。这些指标最能反映模型对业务的实际价值。数据质量与漂移监控训练数据和线上特征之间的分布差异样本缺失率、类型异常等。模型性能监控这块我用过开源的Evidently AI它能很方便地计算数据漂移、模型指标变化生成HTML报告也用过Prometheus custom metrics把模型预测的统计量比如平均置信度、预测类别分布等暴露出来配合阈值告警。这里有个经验模型输出的平均置信度突然下降是一个很强的预警信号。它不一定代表模型坏了但很可能代表输入数据分布变了需要人工检查特征数据是否有问题。例如我遇到过某个渠道的数据编码规则调整数值范围被压缩模型置信度从0.9掉到0.6业务效果也明显下降幸好监控面板上及时发现才避免更大损失。6.2 告警策略少而精准告警不是越多越好告警疲劳会让人忽略真正重要的问题。我从头开始搭监控时告警规则写了一二十条结果每天响个不停真正有价值的指标反而被淹没。后来我把告警收敛到这几个核心维度服务可用性5xx比例 1%连续5分钟触发告警说明可能存在故障。P99延迟 阈值比如3秒持续10分钟告警。数据漂移超过容忍范围比如PSI 0.25而且不是业务方主动变更导致。模型输出分布剧烈变化比如预测为正类的比例与历史基线偏差超过某个百分比。GPU显存使用率持续 95%可能导致任务排队或OOM。告警方式上优先使用Webhook接入企业群或邮件。要设置分级的值班机制但“from-scratch”阶段可以先做到“有人收到告警”就好不要过度设计调度、升级、值班。先把最核心的告警跑通后面再完善。6.3 模型回滚与灰度发布在AI工程里模型更新也必须遵循发布流程。最忌讳的是“训练完新模型直接把线上服务停掉换成新模型”这种方式风险极高。推荐的流程是灰度发布先部署一个新模型服务实例与旧模型共存。让一小部分流量比如5%打到新模型上对比两边的业务指标、延迟、错误率确认没问题后逐步增加流量比例。如果发现新模型效果明显不如旧模型可以随时将流量切回旧服务不需要重新启动。实现灰度发布可以有两种方式一是在网关层按比例转发流量二是在服务内部做一个“双跑”机制即把同一个请求都发给新旧两个模型只返回旧模型的预测结果旁路记录新模型的预测差异用来离线评估。第二种方式对用户无感但会消耗更多资源适合比较模型效果差异阶段。回滚逻辑要特别强调模型服务要保留最近N个“已知良好”的版本不能只留最新版。我遇到过一次情况新模型上线后发现线上指标变差但检查模型文件时发现旧的权重文件已经被覆盖了完全找不回来只能重新训练非常痛苦。所以模型注册表里要保留历史版本并且与数据版本对应起来。7. 常见问题与排查技巧这部分整理一下我从零开始搭建AI工程时最常遇到的几个“坑”以及对应的排查思路。7.1 训练时GPU利用率低卡在“数据加载”上表现训练时nvidia-smi显示GPU利用率只有30%~50%显存占满却算不快。大概率是数据加载变成了瓶颈尤其是数据增强、预处理器在CPU上执行GPU在空等数据。排查方法在数据加载的Dataset代码里单独计时看单次__getitem__耗时。增大num_workers但不要盲目调大太多worker会消耗大量内存和CPU。使用prefetch_factor和pin_memoryTrue让数据以异步方式预取到GPU侧。如果数据本身很大可以先打包成TFRecord或类似格式减少小文件随机IO。我之前有个项目数据是几万张高清图片每次训练前实时做裁剪、旋转、色彩增强GPU利用率只有20%。后来把图像解码改为多进程预取同时把大部分增强操作改成GPU上完成利用率提升到85%以上。7.2 模型训练结果与线上效果不一致这个问题原因很多训练时的数据预处理与线上推理时的预处理不一致。比如线上可能对文本做了不同方式的分词或者图像归一化时像素范围没对齐。特征缺失值处理不一致。训练时用全局均值填充线上没有保存这个均值。模型服务加载时没有切换到eval模式。训练时使用了数据增强推理时也要做对应的TTATest-Time Augmentation但没实现。排查路径拿线上真实请求数据用离线处理流程做成一个样本喂给线上模型和本地模型对比输出是否一致。如果一致那就是数据分布问题如果不一致重点检查预处理代码。7.3 依赖环境不一致导致的“在我机器上跑得好”训练环境的依赖经常升级Python包版本变化可能导致模型效果波动。实践上一定要把环境固定在容器镜像里。我习惯的做法是训练代码目录下放一个requirements.txt或者environment.yml然后每次训练用同一个镜像在镜像标签里标明代码版本。问题排查时先看镜像标签和代码git commit是否匹配。如果是运行在本地或单机上也可以用conda env export生成环境快照。但效果不如容器好因为操作系统底层库的差异也会影响行为。7.4 服务内存泄漏模型服务运行一段时间后内存持续上升最终崩溃。常见原因在请求处理函数中不小心持有了Tensor的引用没有调用del或者让它离开作用域。使用PyTorch时没有使用torch.no_grad()推理过程中保存了梯度历史。全局缓存无限增长比如把每个请求的数据都存进了一个Python字典。排查方式在服务启动时先跑一段时间压测过程中定时用memory_profiler或者tracemalloc抓取内存分配情况。同时检查是否在每个请求结束后释放了显存torch.cuda.empty_cache()只建议在必要的时候调用因为它会清空整个缓存影响性能。8. 从零到一的最小闭环一个可复用的工程模板8.1 最小闭环要包含哪些内容我可以把“ai-engineering-from-scratch”落地成一套可以复用的工程模板。这个模板的目的是让你拿到任何一个新的机器学习项目时能按照一套标准流程快速搭建和迭代而不需要每次从空白脚本开始。最少需要包含这样几个模块project/ configs/ # 所有超参和路径配置 data/ # 数据下载、清洗、切分脚本 features/ # 特征工程逻辑 models/ # 模型定义、训练、评估脚本 deploy/ # Dockerfile、推理服务代码、K8s部署yaml monitor/ # Prometheus配置、Grafana dashboards tests/ # 单元测试和集成测试 pipeline/ # 端到端pipeline编排每个目录里都要有注释和README说明该部分的功能和输入输出。工程化的关键不是代码复杂度而是每个模块都清晰、可独立测试、可追溯。8.2 我用这套模板做了一个文本分类服务为了具体说明这里展示一个我在实际项目里用过的简化示例文本分类模型的训练与部署全流程。第一步配置文件configs/train.yamldata: train_path: ./data/train.csv val_path: ./data/val.csv text_col: content label_col: label model: name: bert-base-chinese max_length: 128 num_labels: 10 train: batch_size: 32 learning_rate: 2e-5 num_epochs: 5 seed: 42 early_stop_patience: 2 output: model_dir: ./artifacts/model mlflow_uri: ./mlruns第二步训练脚本models/train.py核心逻辑如下import yaml from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments config yaml.safe_load(open(./configs/train.yaml)) # 省略数据处理细节只强调这里读取数据、构建Dataset train_args TrainingArguments( output_dirconfig[output][model_dir], per_device_train_batch_sizeconfig[train][batch_size], learning_rateconfig[train][learning_rate], num_train_epochsconfig[train][num_epochs], seedconfig[train][seed], save_strategyepoch, evaluation_strategyepoch, ) trainer Trainer(...) trainer.train() trainer.save_model()第三步部署服务。这里我推荐用ONNX Runtime来加速推理。把BERT模型转为ONNX后服务代码里用onnxruntime.InferenceSession加载模型输入tokenizer后的input_ids和attention_mask最后输出logits取softmax作为结果。整个服务用FastAPI封装from fastapi import FastAPI import onnxruntime as ort from transformers import BertTokenizer app FastAPI() session ort.InferenceSession(./model.onnx) tokenizer BertTokenizer.from_pretrained(./tokenizer) app.post(/predict) def predict(text: str): inputs tokenizer(text, max_length128, truncationTrue, return_tensorsnp) result session.run(None, { input_ids: inputs[input_ids], attention_mask: inputs[attention_mask], }) return {label: int(result[0].argmax()), probs: result[0].tolist()}第四步加监控指标。在FastAPI中把预测结果分布、延迟等指标暴露为Prometheus格式from prometheus_client import Histogram, Counter, generate_latest PREDICT_DURATION Histogram(predict_duration_seconds, Time for predict) LABEL_COUNTER Counter(predicted_label_count, By label, [label]) app.post(/predict) def predict(text: str): import time start_t time.time() outputs ... LABEL_COUNTER.labels(labelstr(outputs[label])).inc() PREDICT_DURATION.observe(time.time() - start_t) return outputs这套模板给我最大的收益是新项目起步时间从几天缩短到半天。你不需要重新考虑结构只需要改配置、换数据、调模型。9. 一点来自实战的经验建议如果只能用一句话总结AI工程从零开始的路径我会说先把最简单的闭环跑通再逐步增加复杂度。别一开始就挑战“全自动MLOps”。我见过很多团队在起步阶段花了很大精力搭建一个看似完美的平台结果平台跑起来了业务模型反而不出成绩。原因很简单平台是为业务服务的业务模型都还没验证成功平台就是空中楼阁。给你一个具体的路线图可以按照这个节奏推进第一周写一个简单的Python脚本读取CSV训练一个sklearn或PyTorch模型评估并保存模型文件。第二周用FastAPI把这个模型包成HTTP服务写一个测试脚本并发请求。第三周加入MLflow实验跟踪、代码版本管理、DVC数据版本管理。第四周把训练和部署都用Docker容器化。第五周配置Prometheus Grafana监控服务和模型指标。第六周如果有需要再把服务放到Kubernetes做灰度发布。按照这个路线走完你基本就掌握了AI工程的骨架。后面无论是做CV、NLP还是推荐系统底层思路都是相通的。最后再分享一个小技巧训练脚本里始终保留一个--limit参数可以只取前几百条数据快速跑通代码。这个小参数可以帮你省下大量调试时间因为模型训练一次全量数据可能需要几小时但用少量数据验证代码只需要几分钟。我在每个项目的训练入口都会加上这个参数已经成了习惯。
返回列表