尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从零搭建AI工程链路:数据管道、可复现训练与部署实践

从零搭建AI工程链路:数据管道、可复现训练与部署实践 前一阵整理自己的学习笔记翻到半年前那个命名为ai-engineering-from-scratch的文件夹。项目名起得挺嚣张实际内容其实很朴素——我没去重写 Transformer也没从零手撸反向传播而是给自己定了一个目标从空目录开始不依赖任何拖拽式 AI 平台、不套现成的全流程框架亲手搭出一条能训练、能评估、能部署、能持续迭代的完整 AI 工程链路。整个过程踩了不少坑也推翻过好几次方向。今天把这段经历复盘出来分享给两类读者一类是刚入门机器学习想跳过“只会调包”阶段、直接建立工程能力的人另一类是已经在业务里做算法或全栈、但觉得 AI 项目“能跑”和“能上线”之间隔着一堵墙的人。文章不会讲高深算法推导重点放在那些文档里很少写清楚的工程决策和实操细节。1. 为什么“from-scratch”不是让你重造轮子很多人看到“从零开始”就紧张觉得得从矩阵求导、手写神经网络开始。这个理解把大量想入门的人劝退了。1.1 先把我说的“从零”定义清楚ai-engineering-from-scratch里的 from-scratch我指的是工程链路的从零不是算法原理的从零。你要控制的是一条完整流水线数据怎么来、洗干净没有、特征怎么对齐、模型怎么训练、怎么评估、怎么部署、上线之后怎么发现坏样本、怎么迭代。这些环节在现成的 AutoML 平台里可能一键就完成了但正因为太顺滑使用者往往说不清楚每一层发生了什么。打个比方用料理包做饭和从买菜、洗菜、切菜、调味到自己炒出来是两种能力。前者解决“今晚吃什么”后者解决“任何食材到我手里都能变成菜”。AI 工程也一样平台能帮你出一版模型但当你遇到数据分布变了、推理延迟不达标、bad case 不知道怎么回流时平台能给你的只有配置项没有判断力。1.2 调包调参救不了项目的后期我见过不少团队模型最初用某平台一键训练跑出 95% 准确率很兴奋。等上线三个月准确率掉到 88%想排查问题的时候发现数据预处理在平台的黑盒里特征工程是别人写好的模板训练日志不完整版本管理完全没有。最后只能全部回退重新用代码手搭一套。这不是极端案例是很多项目的真实结局。所以这个项目的核心判断是AI 系统的失败大多发生在工程层而不是算法层。与其把精力花在研究新模型结构上不如先把数据、训练、部署这条主干道打通让它稳定、可复现、可观测。算法后面可以慢慢加工程骨架一旦歪了重来成本极高。2. 我给自己画的四阶段路线图制定这条路线图之前我走过弯路。最早是按“Python - 机器学习 - 深度学习 - 部署”这种知识顺序学结果学到部署时发现前面的东西忘光了。后来改成按“项目阶段”分不追求一次学完而是每个阶段产出一个能用的成果用成果倒逼自己补齐知识。2.1 第一阶段先搭一个能反复训练的环境这个阶段不写任何算法只做一件事让“训练”这个动作可以一键重复执行。硬性要求是把环境固化成代码我当时用了 Docker 加一个简单的训练镜像。具体操作用python:3.10-slim做基础镜像装 PyTorch CPU 版和必要的库把依赖写成requirements.txt但不是简单pip freeze而是手动整理出顶层依赖写一个scripts/train.sh固定设置随机种子、指定数据集路径、超参文件路径把train.sh和代码一起打进镜像训练时直接docker run。这个阶段最容易忽略的是版本锁定。很多人pip install torch装最新版过了几个月再看代码连不上老 API或者训练结果无法复现。我的习惯是把四个版本号锁死python3.10.x、torch2.0.x、numpy1.24.x、pandas2.0.x并且每次实验在日志里记录当前 commit hash。2.2 第二阶段跑通一条“小而全”的端到端流程第一阶段是把环境搭好第二阶段是选择一个极小的任务把整条链路走完。我当时选的垃圾短信二分类公开数据集好找模型用个简单的文本分类网络就够了。但流程上的每个节点都不省下载数据 - 做基础清洗去重、统一编码、切分训练验证集训练模型 - 保存最佳 checkpoint写一个predict.py- 把 checkpoint 和预处理逻辑一起封装成推理接口用 FastAPI 起一个服务 - 本地 curl 验证把服务打进第二个镜像。这条链路走完你对“模型从哪来、怎么被使用”会形成直觉。很多人学了一堆模型却从没让任何模型通过 HTTP 接口被真实请求过这是巨大的盲区。跑通的标准不是“准确率多高”而是整个流程能在新环境里零人工干预地从头跑一遍。我把这个标准叫“冷启动可复现”后面的所有项目都在沿用。2.3 第三阶段把数据管道当一等公民到第三阶段我开始不满足于“能跑通”而是要把数据问题显性化。数据管道需要回答三个问题这份数据满足什么样的格式约束字段、类型、取值范围数据处理逻辑是否和训练逻辑严格一致训练时预处理 推理时预处理数据更新后如何知道分布发生了变化从而判断是否要重新训练我做的第一个改造是引入数据校验。训练前先跑脚本检查空值率是否超过阈值、类别分布是否异常、文本长度是否出现极端值。很土的办法但能挡住一半以上的“模型效果突然变差”问题。第二个改造是把预处理逻辑统一成一个模块训练和推理共用同一份代码彻底消除“训练时洗了数据、推理时忘了洗”这种低级事故。2.4 第四阶段从部署到持续迭代前三个阶段本质是“把一个模型做出来”第四阶段是“让模型在线上持续工作”。我在这里补上了三件事模型服务的并发与延迟控制、线上输入数据的日志、坏样本的回流标注机制。这一阶段也是最“工程”的部分后面专门用一整节展开讲。四阶段走完我的一个强烈体感是AI 工程的知识密度不高但坑位密度很高。知识点三周能学完坑位可能需要半年才踩得完。所以别怕慢每踩一个坑就把它写进项目笔记这是最快的成长方式。3. 我在前三次“从零”尝试里踩过的最贵教训有些坑书上不会写只有亲手踩一遍才长记性。我分享一下最贵的前三个都是真金白银的时间换来的。3.1 第一次失败沉迷算法实现忽略系统闭环项目开始的头一个月我花大量时间读 attention 源码、自己写多头注意力模块写到最后训练 loss 一直不降陷入 NaN 泥潭。后来回头想这个项目的目标根本不是发明新模型我却把 80% 精力放在模型实现上导致数据、评估、部署一步都没迈出去。这个坑的本质是把学习算法原理的冲动误当成了做工程项目的路径。现在我的建议是算法理解靠读好代码、按论文复现小实验来完成工程项目里模型直接用成熟的 torch 实现不要重写。重写模型只会让你陷入数值细节远离系统的整体迭代。3.2 第二次失败清洗逻辑与训练逻辑脱节有段时间我认真洗数据去重、去 emoji、纠正全半角、过滤 noise label花了两周。结果训练脚本里读取数据的入口是原始文件不是清洗后的文件。等于我辛辛苦苦做的清洗训练过程完全没吃到。更隐蔽的是评估时用的又是另一份略微不同的数据导致离线指标虚高。教训有两条。第一数据清洗必须写成一个可执行的数据管道输入是原始数据输出是标准化的训练文件中间每一步都有日志。第二训练脚本只从约定好的数据入口读数据禁止直接散落在 notebooks 里改数据。这样做还有一个附加好处换数据源时只改管道不改训练代码训练流程的稳定性会高很多。3.3 第三次失败评估指标自欺欺人第三个教训来自一个分类任务。类别严重不平衡负样本占 98%。我拿 accuracy 做评估结果 98% 的准确率让我以为自己已经做完了。后来看混淆矩阵才发现模型只是把全部样本预测成负类真正关注的正类召回率是 0%。这个坑的修复方案不是换一个指标那么简单而是把评估体系搭完整要观察什么用什么指标常见误区整体分类能力accuracy / F1看类别分布只看准确率关键少数类别precision / recall / F1 按类别拆分只看宏平均不看具体类别排序能力AUC分类阈值一变就失效线上表现业务指标转化、误杀率离线指标不等于线上收益现在我的默认动作是每个分类项目先画混淆矩阵再看每个类别的 precision/recall最后才看宏平均。评估指标不是装饰它决定了模型迭代的方向指标选错后面全白干。4. 一个可复用的目录结构AI 工程的脚手架环境搭好了、流程跑通了、坑也踩过了我就开始整理自己的项目脚手架。好的目录结构不是为了好看而是让人在三个月后能快速回答三个问题数据在哪、训练入口在哪、这个实验用了什么配置。4.1 按“数据 / 代码 / 实验 / 服务”四层切分我目前的推荐结构长这样ai-engineering-from-scratch/ ├── configs/ # 所有可配置项 │ ├── base.yaml │ └── experiment_001.yaml ├── data/ # 数据单向流动 │ ├── raw/ # 原始数据只读不写 │ ├── processed/ # 清洗后数据可复现生成 │ └── golden/ # 回归测试集不许随意改动 ├── src/ │ ├── data/ # 数据加载、清洗、校验 │ ├── features/ # 特征工程 │ ├── models/ # 模型定义、训练逻辑 │ ├── evaluation/ # 指标计算、混淆矩阵 │ └── serving/ # 推理服务、API 封装 ├── scripts/ │ ├── train.sh │ ├── evaluate.sh │ └── serve.sh ├── experiments/ # 每次实验的日志和指标 │ ├── 20240101_baseline/ │ └── 20240115_v2/ ├── notebooks/ # 分析探索用不承载生产逻辑 └── docker/ ├── train.Dockerfile └── serve.Dockerfile我把所有数据操作都限制在src/data里把特征、训练、评估分开目的是降低“牵一发动全身”的风险。改特征逻辑时不影响训练入口改模型结构时不影响数据处理。很多人项目写到后面变成一个大 notebook就是没在早期做这层切分。4.2 配置、实验记录、随机种子可复现的三件套目录结构定了之后可复现的关键就是把“每次实验是拿什么配置、什么代码跑的”完整记录下来。我在experiments/里每个实验文件夹放三样东西config.yaml本次实验用的具体超参、数据版本、模型名metrics.json训练和评估指标的输出git_commit.txt代码仓库的 commit hash。这三样的价值在于过两个月发现模型效果回退你可以直接回到当时的 commit复现当时的实验对比差异。没有这套记录你根本无法判断是数据变了、代码变了、还是环境变了导致结果波动。随机种子方面我推荐在三个位置固定Python 的random.seed、NumPy 的np.random.seed、PyTorch 的torch.manual_seed。GPU 上还需要设置torch.backends.cudnn.deterministic True。这只能做到算法层面的可复现不能保证不同硬件下逐 bit 一致但已经足够支撑日常迭代。4.3 数据层为什么要强调“单向流动”上面的结构里raw只读不写processed可以通过脚本重新生成golden是人工锁定的回归测试集。我称之为“单向流动”原始数据进来加工后变成训练数据任何上游改动都通过脚本重新生成下游而不是手工修改下游文件。养成这个习惯之后你会发现出问题时排查路径很短模型效果变差先问数据管道有没有改再看配置有没有动最后才怀疑模型代码。没有单向流动约束数据会被 notebook 东改一下西改一下最终变成一笔糊涂账。5. 模型训练之外的另一半工程数据反馈闭环很多教程到“训练出模型 部署 API”就结束了。但真正上线之后麻烦才开始。模型会老化、数据分布会漂移、用户行为会变这时候靠的是数据反馈闭环不是重新训练一次。5.1 从静态数据集到数据飞轮我在部署第一个服务后加的机制很简单把线上请求日志全部落盘包括输入文本、模型预测结果、置信度。然后每周抽一批低置信度样本做一次人工标注把标注过的样本汇入一个新目录data/human_feedback/。每隔一段时间把这些样本加进训练集重新训练一轮。关键在于每次迭代都要能回答“这次为什么变好、为什么变差”。如果只是把新数据丢进去重新训练不做归因分析那模型优化就只能靠玄学。我的做法是每次迭代跑同一个 golden set对比指标变化再抽 bad case 看具体失效模式。5.2 可观测性不是可有可无模型上线后我最常用的监控指标不是模型准确率而是以下三个输入分布漂移比如文本长度均值、类别预测分布的熵这几个指标如果突然变化大概率是线上数据变了推理延迟P95 和 P99 比均值重要监控系统里要单独看失败率超时、异常、空输出的比例。这些指标不需要复杂的监控平台先用日志加定时统计脚本就能跑起来。重要的是“先有再优”。我见过太多团队一上来就想搭完整的监控平台结果连日志都没打全平台搭完也是空的。5.3 评估集和基准也要版本化为了能持续比较不同版本的模型我维护了一个data/golden/目录里面的数据是人工挑选、覆盖各关键场景的固定样本。每次训练完先跑 golden set再跑完整数据集评估。golden set 的数据要尽可能固定不能用户反馈什么加什么否则评估基准本身漂移模型间的横向对比就失去意义了。我一般把 golden set 按场景分成几百到一千条覆盖边界情况、正常情况、错误输入三类。这个基准集的作用类似软件工程里的单元测试回归时不用每次都跑全量先跑基准集不过关就说明有回归。6. 部署运行时的真实挑战到了这一节我默认前面的数据、训练、评估流程都稳了。部署阶段最烦人的往往不是模型计算本身而是工程细节并发控制、延迟保障、失败兜底。6.1 并发、延迟和批处理先想清楚再写代码模型推理服务和普通 Web 服务最大的区别是推理是计算密集型操作而且单次延迟不低不能像查询 Redis 一样无限并发往上怼。我早期用异步任务队列结果 GPU 显存被并发请求打爆服务直接 OOM。后来改成固定并发数加请求排队每个请求等推理完成后再返回问题就解决了。对于需要低延迟的场景我推荐用动态批处理把多个请求攒在小窗口内一起送到 GPU但对外仍然是逐个返回。这个实现不难排队时带上时间戳攒够 N 条或超时 20ms 就触发一次推理。别一上来就搞复杂的服务网格先保证单实例稳定。CPU 推理和 GPU 推理的选择也很重要。我的判断标准是看单次推理的延迟预算如果预算在 100ms 内且请求并发不高CPU 够用如果面对高并发或者模型较大才上 GPU。很多人上来就申请 GPU最后发现瓶颈在 IO 而不是计算白白烧钱。6.2 模型版本与预处理逻辑的强绑定部署时最容易忽视的一个细节是同一个模型必须绑定它训练时的预处理方式和特征工程版本。我见过一个团队换了新的分词器版本旧模型没重新导出线上推理结果全部变化但模型参数完全没动。这属于典型的“模型没变效果变了”。我的做法是把预处理模块的版本号写进模型打包的元数据里模型导出时生成一个model_meta.json记录模型文件的 hash、预处理代码的 hash、训练数据的版本。服务启动时做一次校验不一致就拒绝加载。这个机制成本很低但能挡住一大批“线上怎么乱套了”的诡异问题。6.3 失败兜底模型也不是永远可靠再好的模型也会遇到没见过的情况所以推理服务必须设计降级路径。我在服务里加了三种兜底超时兜底单次推理超过 2 秒直接返回一个固定结果并记录日志置信度兜底预测概率低于 0.6 时不回绝用户走规则判断或人工流程服务异常兜底模型服务不可用调用方自动切到 nginx 静态页面或人工客服。这套兜底的逻辑和传统后端没什么区别但很多 AI 项目的开发者会把所有信任都押在模型上导致线上一个小异常就放大成事故。我在实际项目里吃过一次亏后把兜底逻辑列成了上线检查清单每一项都要在部署前实测一遍。7. 给后来者的个人经验整段复盘下来我最想说的是ai-engineering-from-scratch这件事真正难的不是任何一个单独技术点而是把每个环节用工程纪律串起来。环境能复现、数据可追溯、评估有基准、部署可降级这四件事做到你的 AI 项目才算是“工程”而不是“实验”。如果你正在走这条路我建议把规模压缩到极小先用一个小数据集完整走十遍闭环比在一个大模型上反复调参更有价值。踩坑不可怕怕的是没有记录、没有沉淀。把这个项目当成你长期维护的“作品”每次变化都写进笔记半年后你回头看推进会非常明显。
返回列表