ARTICLE DETAIL

资讯详情

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

从零搭建AI工程:数据管道、训练闭环与部署实战

从零搭建AI工程:数据管道、训练闭环与部署实战 做AI工程最容易被忽略的一个问题恰恰是从零开始这四个字。过去一年我见过太多团队拿着现成的框架、模板和开源项目直接开跑模型训得飞快效果也好看但一到线上就各种翻车——数据分布一变就崩、推理延迟压不下来、模型更新一次要烧掉整个周末去排查链路。原因不复杂大家太习惯用别人搭好的积木反而不知道自己的工程底座是怎么来的。所以这篇文章想把ai-engineering-from-scratch这件事讲透。不是让你所有代码都从汇编开始写而是把AI系统从数据、训练、评测到部署的每一条链路都亲手捋一遍。我会按照我自己从零搭建一套AI工程的完整过程来展开包括怎么选型、怎么搭底座、怎么设计数据管道、怎么跑训练闭环、怎么做评测迭代以及最后部署上线时真正会踩的坑。这套路径适合两类人一类是手里有业务问题、不想被现成工具绑架的工程师另一类是刚入行想建立整体工程思维的学习者。1. 为什么值得从零搭一套AI工程重建逻辑链的价值1.1 现在抄作业太容易反而丢了基本功我见过不少团队的做法是拿到一个业务需求先去HuggingFace找一个效果最好的模型然后拉一个微调框架配上数据集跑几个epoch就上线。这套流程的确快快到你一周就能做demo。但问题也藏在这种快里面——你完全不知道模型为什么效果好也不知道换一个场景它为什么就失灵了。AI工程和传统软件工程最大的区别在于传统软件的行为是确定的你写了什么逻辑就执行什么逻辑而AI系统的行为是概率性的你给它喂什么样的数据、用什么策略训练、在什么分布下评测都会直接影响最终结果。如果你连自己的数据管道和评测逻辑都是别人框架里默认的你就丧失了判断模型好坏的能力。从零搭一套AI工程不是要拒绝所有工具而是要把每一层都拆开看一遍重新选择、重新组装建立属于自己的逻辑链。这条逻辑链包含了数据从哪来、怎么清洗、怎么切分、用什么格式进入训练、评测指标到底在测什么、上线后怎么观测模型行为。1.2 从零搭与调包侠的路线差异同样的目标——做一个中文文本分类模型两条路线的做法完全不同环节直接调包路线从零构建路线数据获取找现成公开数据集采集业务数据并设计标注规范数据清洗用框架默认处理逻辑针对业务场景自定义清洗链路模型选型直接用效果最好的模型根据算力、延迟、数据量综合选择训练框架用一条命令跑默认配置自己搭建训练循环理解每一行代码评测看框架输出的准确率设计业务指标和切片评测集部署用现成服务化组件自己设计推理接口和弹性策略调包路线不是不能说但对于工程来说它有一个致命的问题当系统出问题时你根本不知道问题出在哪一层。是数据污染了是训练代码的bug是评测集分布不合理还是部署环境不一致直接调包的时候你面对的是一个巨大的黑盒。而从零构建一遍哪怕最终生产环境用的还是那些成熟框架你对整个系统会有一种底气——出了问题你能定位到具体环节能复现、能回滚、能优化。1.3 什么项目适合从零起步也不是所有项目都值得从零搭。我自己的判断标准是看三个条件第一业务场景没有完全对标的现成方案。比如做通用的情感分类公开数据集够用直接调包完全合理但如果做的是小众领域——比如某个专业领域的文本结构化、特定设备信号的异常检测——现成方案覆盖不了这时候从数据管道开始搭建反而省时间。第二团队有长期维护和迭代的计划。AI系统上线只是开始后续要不断加数据、调策略、换模型。如果这套系统只跑一次demo没必要投入从零搭建的精力如果打算运行两年以上前期把基础打扎实就是最大的返工预防。第三对成本和延迟有硬性要求。现成方案往往为了通用性牺牲了效率和成本从零搭建时你可以针对自己的数据规模和业务特点做专门优化比如用更小的模型、更精简的数据管道、更高效的推理方案。2. 先搭底座环境、依赖与代码仓库的工程化从零搭建第一步不是写模型代码而是建立工程底座。这块做得不好后面每走一步都在还债。2.1 最小依赖原则别为了省事引一堆包我见过很多刚起步的项目一个文本分类就引入了十几个依赖包其中有几个其实根本没用到。依赖越多环境越不可复现版本冲突的概率也越高。我的原则是核心代码只依赖最必要的库其余能力自己实现或延迟引入。一个从零搭建的中小型AI工程依赖甚至可以精简到下面这些Python 3.10基础运行时NumPy数组运算几乎绕不开模型训练框架PyTorch或TensorFlow二选一推荐PyTorch调试直观数据处理小工具Pandas或纯Python够用看数据规模Transformers用于加载预训练模型和分词器前提是你决定用预训练模型其他像可视化训练曲线的TensorBoard、做数据增广的各种库完全可以等到确定需要时再加。不要迷信全家桶每次加依赖都要问自己这个功能我自己写要多久引入这个包会带来什么维护成本2.2 用Python虚拟环境起手保证可复现在做任何训练之前第一件事是锁定Python环境。这一步经常被忽略但线上复现不了实验结果、队友机器跑不出同样指标往往都是环境不一致造成的。我使用的方案是结合venv和requirements.txt锁定环境# 创建虚拟环境 python -m venv ai-engineering-env source ai-engineering-env/bin/activate # 安装核心依赖 pip install numpy pandas torch transformers # 导出当前环境依赖快照 pip freeze requirements.txtpip freeze导出的requirements.txt包含了当前环境中所有包的具体版本号别人拿到它就能复现一模一样的环境。这一步看似简单却是后面所有实验能够对比的基石。更进一步建议用pip-tools管理依赖——它会把顶层依赖和传递依赖分开管理升级时不会把整个环境搞得乱七八糟。当然如果你团队已经全面容器化直接用Dockerfile固化环境更彻底虚拟环境适合单人开发和快速迭代期。2.3 数据、代码、模型分别放在哪里从零搭建工程的时候最容易出现的就是所有东西堆在一个仓库的情况。后面你会发现数据、代码、模型文件的更新频率完全不同放在一起会互相污染。我的习惯是把工程分成三个区域ai-engineering-from-scratch/ ├── src/ # 核心代码 │ ├── data/ # 数据加载与清洗逻辑 │ ├── train/ # 训练循环与模型定义 │ ├── eval/ # 评测逻辑与指标计算 │ └── serve/ # 推理服务与接口 ├── experiments/ # 实验记录 │ ├── run-001/ │ └── run-002/ └── artifacts/ # 训练产物模型权重、词表、配置 ├── models/ └── data/代码部分用Git管理模型权重和数据文件原则上不进入Git——它们体积大且更新频繁放进Git会拖慢clone和commit速度。我自己会用一个内部存储区或者云对象存储专门放模型和数据集用文件路径和版本号关联。数据也要分三层原始数据不可修改、中间数据清洗后、实验数据切分后的训练/验证/测试集。每层都记得带上时间戳或版本号否则迭代到第五版的时候你根本不知道上一次跑出那个好指标用的是哪份数据。3. 最小可用的数据管道样本、清洗与格式编排3.1 样本格式设计别用一行一条糊弄过去很多入门教程喜欢用最简单的方式组织数据——每行一条样本tab分列。小规模试验无所谓但一旦涉及真实业务你一定会需要更完整的样本结构。我推荐在从零搭建阶段就使用JSON Lines格式每行一个JSON对象。它比纯文本带更多结构化信息又不像完整JSON文件那样一次性加载进内存适合流式读取。{id: sample-0001, text: 这家店的菜品很新鲜服务态度也特别好。, label: positive, source: review-2024-05-01} {id: sample-0002, text: 等了一个小时还没上菜体验极差。, label: negative, source: review-2024-05-01}给每条样本放一个id和source字段这个习惯在后期排查问题时非常关键。当你发现训练集和测试集之间存在数据泄露或某类样本被模型系统性搞错时能快速追溯到这些样本的来源。3.2 清洗脚本的策略宁可保守不要激进数据清洗是从零搭建里最不起眼但影响最大的环节。很多团队的痛点不是没有清洗逻辑而是清洗逻辑太激进——把原本有用的信息也顺带删了。我自己总结了一套保守的清洗优先级去掉完全无效的样本空文本、纯HTML标签残留、乱码处理明显异常的编码统一为UTF-8处理中文全半角符号规则性修正去掉URL、用户等与业务无关的内容但先确认是否真的无关去重不只是完全重复还有近似重复——比如只差一个标点的两个句子长度过滤过短的样本可能信息量不足过长的可能包含太多噪声设上下限每一条清洗规则都要留下日志记录清洗前后各删了多少条样本、为什么删。那些被删掉的样本不要直接扔了归档到一个单独的目录。如果后面模型在某种输入上表现异常你还能回头查是不是清洗规则误伤了这个类型的样本。3.3 数据版本化做一个简单的Dataset类从零搭建阶段不需要上很重的数据管理平台但一个简单的Dataset封装必不可少。它的职责是提供统一的读取接口、记录数据版本、支持按配置切分训练/验证/测试集。class DatasetRegistry: def __init__(self, raw_data_dir, version): self.version version self.raw_path f{raw_data_dir}/raw-{version}.jsonl self.cleaned_path f{raw_data_dir}/cleaned-{version}.jsonl def load_cleaned(self): with open(self.cleaned_path, r, encodingutf-8) as f: return [json.loads(line) for line in f] def split(self, train_ratio0.8, val_ratio0.1, seed42): samples self.load_cleaned() random.seed(seed) random.shuffle(samples) n_train int(len(samples) * train_ratio) n_val int(len(samples) * val_ratio) return (samples[:n_train], samples[n_train:n_train n_val], samples[n_train n_val:])这个封装看起来简单但它让整个实验过程都建立在确定的数据版本确定的划分逻辑之上。同一个实验可以复现不同实验可以对比不会出现你用的训练集跟我用的不一样这种争论。4. 训练闭环从预训练到微调的技术选型4.1 基座模型怎么选不是越大越好从零搭建AI工程第一个纠结的问题就是选什么模型。我的判断标准很简单从业务约束倒推。数据量少几千条级别选择参数量小的模型比如经典的中文B/S架构模型参数量1亿级别或者训练更充分的密集模型参数量4亿级别不容易过拟合训练成本也可控。数据量中等几万条级别可以选择中等规模的密集预训练模型参数量6亿左右微调效果和推理速度的平衡最好。数据量大且算力充足再考虑解码器大模型参数量70亿或以上用参数高效微调方式适配比如LoRA低秩适配或QLoRA量化低秩适配。有一个容易被忽略的问题模型不是越大越好而是越匹配越好。典型的例子是在某垂直领域做短文本分类任务实测下来参数量2亿的模型在F1指标上可能比参数量70亿的模型还高而且推理速度差了一个数量级。原因很简单——大模型需要更多数据才能发挥能力数据不足时它只是在背训练集而不是在学模式。4.2 微调方式对比全量微调、LoRA、还是只用预训练如果决定用预训练模型从零搭建时面临的另一个选择是怎么微调。我把三种常见方式放在一起对比微调方式适用场景训练成本显存占用效果预期全量微调数据量充足任务与预训练分布差异大高高上限最高但容易过拟合LoRA数据量适中任务接近预训练分布低低接近全量微调稳定实用冻结特征数据量极少或只做特征提取最低最低稳定但效果上限有限我的建议是第一版从LoRA开始。它的训练速度快、显存占用低跑一轮实验的成本小便于快速验证数据管道和评测逻辑是否正确。等整个工程链路都跑通了、确认方案有效再考虑要不要上全量微调提升上限。4.3 训练循环中的三个高频坑训练脚本本身网上有大把模板但真正从零搭建时会踩到三个高频坑第一个坑是学习率设置不当。很多刚起步的人喜欢直接用默认学习率结果模型要么不收敛要么过拟合。经验法则是先跑一个很小的实验几百条样本、1~2个epoch用不同学习率试看loss曲线是否平滑下降。常见的中文预训练模型微调学习率在2e-5到5e-5之间比较合适但具体要观察训练集loss和验证集loss的差距来调。第二个坑是批次大小与显存的平衡。批次大小太小梯度噪声大训练不稳定批次太大显存不够且容易收敛到尖锐极小值泛化变差。我通常的做法是在显存允许范围内先用16或32配合梯度累积gradient accumulation模拟更大的批次。accumulation_steps 4 total_batch_size batch_size * accumulation_steps optimizer.zero_grad() for i, (inputs, labels) in enumerate(train_loader): outputs model(inputs) loss criterion(outputs, labels) loss loss / accumulation_steps loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()第三个坑是评估频率太低。很多入门模板每个epoch才跑一次验证集这对小数据集还行数据集稍大就容易跑偏——你根本不知道模型在训练过程中哪一步开始过拟合。我的做法是设定一个步数间隔比如每500个batch跑一次验证记录loss和关键指标后面可以很清晰地画出训练曲线找到最佳checkpoint。5. 评测与迭代没有指标的工程等于玄学训练完成不代表项目完成评测才是从零搭建AI工程真正体现功力的环节。5.1 评测集怎么建先定业务指标再定数据集很多团队犯的错误是反着来——先指定一个公开数据集然后跑一个公开的指标。这样评测结果好看但和业务目标没什么关系。从零搭建评测体系的正确顺序是先想清楚业务上什么叫好用再把它翻译成可计算的指标最后才构建评测数据集。比如一个文本分类项目业务目标可能是减少用户投诉被误分到无关类别的比例。那么核心指标就要看混淆矩阵重点关注投诉类是否被分错而不是只盯着整体准确率。评测集至少要覆盖三类情况正常情况代表真实业务中占大多数的一般样本边界情况容易混淆的样本比如贵在价格语境和性价比语境下的不同情感异常输入空文本、超长文本、夹杂大量符号的噪声文本这三类样本在评测集里比例多少取决于你的业务对错误有多敏感。5.2 评测脚本要可复现固定随机种子与确定性推理评测环节最常见的坑是指标忽高忽低。原因往往出在推理阶段没有固定随机种子或者模型处于training模式而不是eval模式导致BatchNorm和Dropout还在起作用。模型测试时务必显式调用.eval()方法并且固定推理时的随机种子import torch import random import numpy as np def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) model.eval() set_seed(42) with torch.no_grad(): predictions model(test_inputs)这一步做扎实之后同一份测试集跑出来的指标才是稳定可比的。否则你会发现跑一次结果变一次根本没有办法判断改动是好是坏。5.3 迭代节奏小步快跑还是大版本重训AI工程的迭代和传统软件的迭代不太一样。传统软件改一行代码可以立刻发版AI系统改动数据或训练策略后需要重新训练、评测、上线周期明显更长。我自己的节奏是小步快跑阶段Version每个迭代周期比如一周内只改动一个变量——要么换数据、要么换超参、要么换模型结构不要同时改多个。每次改动都记录到实验日志里包括数据版本、模型配置、训练参数、评测结果。每个阶段比如每个月产出一个稳定版本放到独立的artifacts目录中。这样到了上线阶段你完全知道当前模型的来龙去脉用的哪版数据、在哪份训练配置下跑的、评测集上表现的边界是什么。6. 部署与成本从实验到可用的最后一公里6.1 推理服务怎么设计先按吞吐量定方案很多从零搭建的AI工程在前半段都跑得很顺一到部署就卡壳。原因是训练和推理对资源的需求曲线完全不同训练阶段是长时高吞吐推理阶段是低延迟高并发。如果业务的高峰QPS在50以下最简单的方案是用FastAPI把模型包成一个HTTP服务用Gunicorn配合少量worker就够了。from fastapi import FastAPI from pydantic import BaseModel class InferenceRequest(BaseModel): text: str app FastAPI() app.post(/predict) def predict(request: InferenceRequest): result model_pipeline.predict(request.text) return {label: result.label, confidence: result.confidence}如果峰值QPS到几百甚至上千就要考虑批处理推理把多个请求攒一批送入模型、模型量化INT8或FP16、以及把模型放到GPU服务而不是CPU服务上。部署方案不是越复杂越好而是匹配你的真实流量特征。6.2 成本估算训练一次到底烧多少钱成本核算是从零搭建AI工程里最容易低估的部分。很多人只看见GPU的租赁价格忽略了数据和评测的成本。我以一个中等规模的微调项目为例大致算一笔账成本项估算方式一个月的典型开销GPU租用训练单卡A100每小时约20元训练72小时约1440元GPU租用推理单卡T4每小时约5元常驻约3600元数据标注按条计几千条样本数千元到上万元存储与带宽模型文件数据集每月几百元人工成本工程开发评测迭代视团队情况而定这个账算完之后你会发现推理服务的成本往往比训练还高——因为训练是一次性的推理是持续性的。这也是我从零搭建时倾向于用参数量较小模型的原因训练成本可能只差一倍但推理成本在长期运行下可能差一个数量级。6.3 观测与告警模型不是发版就结束了AI系统上线之后最怕的不是效果差而是效果有没有变差你不知道。数据分布会漂移线上输入的文本格式会变化用户的行为模式也会演化。如果只靠发版那一瞬间的评测系统随时可能悄悄劣化。从零搭建时就可以内嵌一个简单的观测机制记录每条线上请求的输入长度、耗时、模型输出的置信度分布定期每天或每周抽样线上样本人工标注后与模型预测对比设定告警规则比如连续几小时平均置信度下降超过20%就触发复审这些观测数据存下来就是下一轮迭代最可靠的训练和评测依据。很多团队等到模型效果崩了才开始找数据手忙脚乱。而工程化做得好的团队早就在日常观测里发现了蛛丝马迹。7. 最后想说的几点经验做完这一整套从零搭建之后我最大的体会有三件小事直接决定项目成败但教程里很少提到。第一件是关于失败记录的。每跑完一组实验不管效果好坏把结论和推测写下来。效果好的实验你可能说不清为什么好效果差的实验你更说不清为什么差。如果复盘时只有结果没有过程猜测和验证那下一轮迭代就是在碰运气。我自己每轮实验会顺手写几行notes哪怕歪歪扭扭后面回头看都有用。第二件是关于复盘的节奏。从零搭建的工程前两周一定是最痛苦的——处处受挫连个能看的指标都跑不出来。但不要急着怀疑方案先检查基础链路数据清洗有没有bug、训练代码有没有用错API、评测逻辑是不是测错了对象。我见过太多团队在这种时候慌慌张张换模型换方案结果发现是源头问题。第三件是够用就好价值观。从零搭建AI工程的最终目标不是做一个复杂炫酷的系统而是做一个能稳定解决业务问题、持续可迭代的系统。我之前帮一个团队优化过对账文本的分类处理用最朴素的数据清洗加一个小模型的组合就比他们之前用大模型配合大量调参的效果更稳定、成本更低。工程不是给模型炫技而是给业务兜底。
返回列表