ARTICLE DETAIL

资讯详情

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

从零开始AI工程:数据到模型部署的完整链路实践

从零开始AI工程:数据到模型部署的完整链路实践 1. 为什么选从零开始而不是直接上手现成框架先交代一下背景我在这里说的ai-engineering-from-scratch并不是让你去从零实现一个Transformer或重写CUDA算子而是指——不借助那些帮你屏蔽细节的完整脚手架从最底层的数据准备、模型训练脚本、评估逻辑到服务部署一步一步亲手搭起一条AI工程链路。如果你在搜索引擎里查AI工程入门跳出来的绝大多数教程都是这么教的装好PyTorch加载一个预训练模型跑通一个inference脚本然后说你已经完成了一个AI项目。这话没错但也很坑——因为它跳过了所有真正会在实际工作中绊倒你的环节。等你去公司实习或者接手真实业务发现数据不是下载一个CSV就能用的模型跑完不是输出一个精度数字就结束的你要把模型包成接口、监控它的漂移、处理它上线后每天收到的几十种异常输入这个时候从脚手架学会的东西就完全不顶用了。所以from-scratch的定位是用一种笨但扎实的方式把AI工程里每一个被黑盒化的环节亲手做一遍。哪怕你最后还是会回到PyTorch、回到HuggingFace、回到Docker和K8s但你回去之后的心态完全不同——因为你亲手写过那些已经被封装成三行API调用的底层逻辑会用和懂为什么这么用是两种体验。如果你正处在下面某个阶段这篇文章大概率帮得上忙已经能跑通教程里的模型训练但换一个数据集就手足无措想转行做AI工程或算法工程但简历上只有调库经验心里发虚自己做了一个小模型demo想上线却完全不知道从哪一步开始纯粹好奇AI工程完整链路长什么样想找个不靠项目模板的路径。我接下来要讲的不是一套噱头而是我按从零开始的方式完整走了一遍AI工程全流程之后沉淀下来的实操记录。每一章都对应了一个真实环节按顺序读下来再按顺序做一遍你会比背熟了三个框架的API的人更接近工程这个词的本质。2. 动手前先想清楚从零到一的工程骨架长什么样很多人的问题不是不懂得努力而是不知道一条完整的AI工程链路到底由哪些环节组成。结果就是东一榔头西一棒子今天学个网络结构明天看一篇损失函数优化的文章后天复制一段数据增强代码半年过去还是拼不出一个完整闭环。2.1 一条最小可用链路的六个环节我把自己从零搭建AI工程的过程画成了一根主线数据→特征→模型→训练→评估→服务化。这六个环节看起来平淡无奇很多人甚至觉得就这。但如果你真的亲手走一遍会发现每个环节内部都藏着足够让人崩溃半天的细节。以我自己做的一个文本分类项目为例任务是给定一段用户对产品的评价判断情感极性正向、负向、中性。这是个再经典不过的入门任务但一旦不从Kaggle的干净CSV出发而是从模拟的真实业务环境出发事情很快就变了。下面这张表是我在动手前整理的最小链路清单每一项对应了什么交付物、什么验收标准目标是一天之内跑通一个不需要任何框架封装的最小闭环环节具体要做什么验收标准数据采集、清洗、标注、切分拿到可训练的干净数据集分布基本合理特征文本分词、去停用词、向量化或序列化模型能读入张量维度与词表一致模型构建一个结构完整可解释的模型输入一个batch能前向传播输出维度正确训练损失计算、梯度回传、参数更新loss曲线下降指标逐步上升评估在验证集和测试集上计算指标指标能反映真实业务水平不止看准确率服务化封装接口、部署运行、监控日志能用HTTP调用模型返回有意义的预测结果你可能注意到了这张表里没有提到用哪个框架。这是从零开始练习时最重要的一条原则别让框架替你决策。在这个阶段PyTorch、TensorFlow这些工具都可以用但默认配置之外的每一步——数据集怎么切、batch怎么设、评价指标选什么、模型参数怎么初始化——都要自己问自己一声为什么。真正动手做的时候就明白了框架帮你省掉的是重复劳动不是理解成本。2.2 环境搭建的省力思路我见过太多人卡在环境搭建这一步装了删、删了装最后在群里问为什么我import torch一直报错。我的建议是既然目标是练习工程能力就用一套主流的Python环境管理方案搭底越简单越好。我用的是conda加venv的组合用conda管理Python版本用venv隔离项目依赖。不要一上来就学Docker那是后面服务化阶段的事现在给自己增加负担没有意义。具体步骤不复杂conda create -n ai-eng python3.10 conda activate ai-eng pip install numpy pandas scikit-learn torch --index-url https://download.pytorch.org/whl/cpu第一周不需要GPUCPU已经完全能跑通文本分类的练手项目。等到你真的进到训练大模型阶段再配CUDA那时候你已经知道自己需要什么显卡、什么驱动版本、什么算力服务了——每一步都在需要时才引入新复杂度这才是从零开始的正确节奏。2.3 数据先行没有好数据后面全是白干做AI工程和做算法竞赛最大的不同是竞赛的数据集是别人准备好的工程里的数据集是你要自己养大的孩子。我模拟了一个非常现实的数据收集场景产品评价分散在几个渠道里格式不统一有的带评分、有的纯文字、有的夹杂大量表情符号和错别字。为了不让第一天就卡死在数据清洗上我做了三件事写一个统一的读取脚本把不同格式拉平、写一份简单的标注规范文档、用程序自动做初版标签再利用人工校验。这套流程跑下来我才真正理解为什么前辈常说数据和特征决定了模型的上限算法只是逼近这个上限。我当时用的模型非常简单——就是一个词向量加权平均之后接一个逻辑回归但经过了认真清洗和合理的停顿词/标点处理之后准确率直接干到了86%。而如果我什么都不洗把原始文本扔给一个LSTM准确率就只有72%左右。所以从零开始练习时请把至少一半的时间花在数据上。别嫌枯燥这是AI工程这条路上性价比最高的时间投入。3. 用最小手写模型理解训练机制前向、反向与损失的本质很多教程一上来就让用nn.Linear、nn.Embedding搭模型三个类拼起来就号称搭建了一个神经网络。初看没什么问题但直到某天面试官问我梯度更新时为什么要把梯度清零我才发现自己对训练的理解全是断层的。3.1 既然要从零就试着手写一个MLP为了不在工程的起步阶段就被框架的抽象吓退我做的第一个模型不靠任何深度学习框架的自动层封装而是用一个纯NumPy实现的多层感知机MLP来做情感分类的baseline。这个选择看起来倒退了但对理解训练的本质帮助极大。我不需要处理什么复杂的东西只需要实现三部分前向传播线性变换加激活函数逐层计算反向传播按链式法则把误差从输出层传回输入层梯度下降用梯度更新每层的权重和偏置。给你看一下我当时写的关键片段示意非完整代码import numpy as np def relu(x): return np.maximum(0, x) def softmax(x): e_x np.exp(x - x.max(axis1, keepdimsTrue)) return e_x / e_x.sum(axis1, keepdimsTrue) def cross_entropy_loss(y_pred, y_true): m y_true.shape[0] p np.clip(y_pred, 1e-12, 1.0) return -np.sum(y_true * np.log(p)) / m # 前向输入 [batch, input_dim] → 隐层 → 输出 z1 np.dot(X_batch, W1) b1 a1 relu(z1) z2 np.dot(a1, W2) b2 a2 softmax(z2) # 反向第2层到第1层的误差传播简化版 dz2 a2 - y_batch dW2 np.dot(a1.T, dz2) / batch_size # 更新 W2 - lr * dW2当时跑通这个代码让我想明白了很多原来如此的事为什么softmax后面要跟log损失——因为它们两个配合起来梯度形式极其干净就是预测概率减真实标签为什么激活函数选ReLU而不是sigmoid——因为链式法则传回来之后sigmoid的导数在两端接近0梯度会消失网络更新不动为什么训练的时候每个epoch要把数据打乱——因为如果每轮都按固定顺序喂数据模型会记住顺序里的规律而不是数据本身的规律。这些东西在PyTorch里都被封装得看不见摸不着但你要真出了训练不收敛的问题回头排查的时候脑子里有没有这个图景决定你是看报错猜原因还是顺着计算链路直接定位。3.2 从手写模型切回现代框架的正确姿势有了一个手写MLP的体验之后再回到PyTorch会顺手得多。你不必把框架里的每个算子都手动实现一遍但从此你会带着一种我知道你底层想干什么的状态去读错误信息、去设计网络结构。第一次切回PyTorch时我刻意对比了一下同样是实现一个带embedding的文本分类网络框架里要建的东西变成了三块词表映射、Embedding层、分类头。看似多了东西但核心思路一点没变还是把文本变成向量把向量过几层把结果变成概率。只是现在每一层都有人给你写好了前向和反向你只需要决定层与层怎么组合。这样做的意义在于你不再把模型当作一个魔法黑箱来看待而是当作一套你亲手装起来过、现在只是换用标准零件重装一次的机器。遇到问题的时候你会拆到具体某个零件去排查。4. 从Notebook到训练脚本我踩过的工程化深坑模型能在Jupyter Notebook里训起来了只是热身完成。AI工程真正的分水岭出现在把Notebook改写成训练脚本这一步——大量数据、多轮运行、需要稳定复现的结果。我在这段吃过不少苦头挑两个最典型的说。4.1 第一个坑固定随机种子比你想的更复杂一开始我把Notebook里的代码原封不动搬进训练脚本设置了一个random.seed(42)就以为万事大吉。结果发现每次运行出来的指标都在小幅波动甚至在数据加载逻辑不变的情况下两次结果相差能到1.5个百分点。排查了老半天终于找到三个漏网之鱼NumPy有自己的随机数生成器PyTorch有自己的torch.manual_seedPython内置random是三套独立状态其次数据要做shuffle用的还是Python内置random而跨进程数据加载的时候还要额外设PyTorch的worker_seed。总之要真正可复现需要在脚本开头把所有随机源都固定一次import random import numpy as np import torch def set_all_seeds(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)这个坑很小但它让我意识到什么叫训练脚本的工程素养你不仅要图模型跑通还要保证任何人在任何环境里跑你的代码都能得到同一份结果。没有可复现性的实验脚本本质上是还没进入工程状态。4.2 第二个坑早停、学习率调度与训练完的定义在Notebook里训练模型结束的标准往往是loss已经很低了或者我不想等了。但工程里的训练需要有明确终止条件否则你没法自动化、没法对比不同实验。我当时的处理是在训练脚本里加入三项机制验证集早停每个epoch结束在验证集上算一次指标如果连续若干个epoch没有改善就提前停止并保存最佳模型权重学习率调度训练初期学习率较高快速下降后期降低学习率做精细收敛用的是常见的阶梯式衰减或余弦退火检查点保存每个epoch都保存一份最新的权重文件文件名带上epoch数和验证指标防止中途断掉后从头再来。这三个机制的实现并不难但每加一个都会逼你思考一个问题你评估一个模型好坏的信号到底是什么是训练loss还是验证集上的某项指标一旦你把好的定义从loss很低细化成了在分布接近真实场景的验证集上F1分数最高你就会开始认真认识评估环节的价值。4.3 训练基础设施的轻量搭建我不会一上来就让你接触分布式训练、MLflow、Kubeflow这些重型武器。轻量级的方案完全够用来理解工程化的核心脚本化、可配置、可追踪。我的做法是训练参数全部用argparse或简单的配置文件管理不硬编码在代码里每一轮实验的日志写到一个固定的logs目录标注时间戳最终指标记录到一个CSV文件里方便横向比较不同参数组合的效果。这套轻量方案让我在一周内跑了上百组小实验养成了每次修改都有记录每个结果都可回溯的工作习惯。后来看到别人用重量级平台做的事情本质上就是把这套手工流程做成了系统和可视化——但如果你连手工流程都没走通过那些平台在你手里也只是徒增概念负担。5. 评估指标里最容易骗人的东西我为什么不再只看准确率训练跑通了脚本稳定了模型的准确率到了86%左右当时我觉得这个项目已经成功了大半。直到我把预测结果按真实标签逐条打开看才出了一身冷汗。5.1 类别不平衡会怎样玩弄你的准确率我那个情感分类数据集里正向评价占了大约60%负向和中性的加起来只有40%。一个永远预测正向的傻瓜模型准确率可以到60%。而我的模型虽然整体准确率86%看起来不错但仔细一拆混淆矩阵正向的召回率接近95%但负向的召回率只有68%中性的更是掉到55%也就是说用户在一条极其不满的评价里用了比较隐晦的表达我的模型很可能把它归成中性甚至正向这在业务上是无法接受的因为愤怒的用户需要被优先发现、优先响应。这是AI工程里最经典的评估误区只看准确率。准确率只在类别分布均衡、且各类别错误代价相同的时候才有参考价值而真实业务几乎永远不满足这两个条件。我最后改用了三个指标的组合精确率、召回率、F1并且分别算每个类别的宏平均和加权平均。在此之上还额外记录了一份严重错误率——把真正的负向评价预测成正向的比例——这个指标直接反映业务上最不想看到的情况。5.2 训练集、验证集、测试集这样切才是对的另一个评估相关的坑是数据切分。我最初用train_test_split一股脑切了80/20训练过程中又截了一部分当验证集做早停。结果问题马上就来了因为验证集是从测试集里偷的我相当于拿着考试题当模拟题反复练习最后的测试指标虚高。正确的做法是先切出一块完全不见过的测试集把剩下部分再切出验证集。整个过程应该是数据一加载就先完成中间不能有任何代码看到测试集的信息。文本类数据还需要额外考虑一点——如果同一用户的多条评价有相关性还需要按用户ID分组切分防止数据泄漏。我一开始没注意后来发现同一用户的评价同时出现在训练集和测试集里模型指标虚高了好几个点。所以数据切分不是随便切两份就完了先想清楚你的数据里什么单位是不可切割的原子再动手。6. 服务化上线模型能跑只是开始能一直跑才是工程到这一步模型已经过了评估这一关理论上可以上线了。但上线这个词在AI工程里含义比大多数人想象的大得多。你要从一个model.pt权重文件出发让一个完全不懂模型的Web服务能稳定地加载它、接收请求、返回结果并在线上环境持续运行不崩溃。6.1 API服务的三种实现路径我的选型和理由我实际尝试过三种方式把模型变成接口方式优点缺点适合场景Flask/FastAPI 直接写Web接口简单直接快速跑通需要自己管并发、生命周期、模型预热极小流量或内部工具模型服务框架如TorchServe、Triton自带并发、批处理、监控配置复杂定制不灵活生产环境中等以上流量ONNX Runtime FastAPI推理性能好跨环境兼容需要转模型调试维度单一对延迟和硬件兼容有要求我自己的练习项目流量不大也不追求极限性能选了第一条路径——用FastAPI做服务封装。这不代表我否定另外两条路径而是在练习这个阶段最简单的方案能暴露最多工程细节不会用框架的默认行为掩盖掉问题。当时封装服务的核心代码逻辑大致是这样加载权重 → 建立词表映射 → 接收请求文本 → 预处理 → 预测 → 构造响应。from fastapi import FastAPI from pydantic import BaseModel app FastAPI() model, vocab load_model_and_vocab() class PredictRequest(BaseModel): text: str app.post(/predict) def predict(req: PredictRequest): vec text_to_tensor(req.text, vocab) prob model.predict_proba(vec) label int(prob.argmax()) return {label: label, confidence: float(prob[label])}这段代码看着简单但上线后冒出来的问题全在细节里请求偶尔带有非法字符导致预处理报错、模型首次加载需要好几秒导致第一个请求超时、日志里没有任何关于预测内容的信息导致出了问题没法复盘。6.2 被夸大的部署容器化第一次给了我正反馈说实话在动手之前我一直觉得Docker是一个很重的东西学起来肯定要命。但真正给AI服务写了个Dockerfile、把镜像跑起来之后才发现它其实是帮你从本机能跑走向别的地方也能跑的最短路径。Dockerfile本身简单得让人意外FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY ./app ./app COPY ./weights ./weights CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000]写到这里其实我只花了一上午。真正改变我工作方式的是那个晚上我在本地跑得好好的服务部署到一个只有2GB内存的服务器上之后直接内存溢出崩了。一查原因是模型加载的时候向量矩阵太占内存而我测试环境的内存是开发机的四倍。Docker在这里最大的价值不是更方便地发版而是能用一行命令在模仿生产环境的容器里提前暴露环境差异问题。从那以后我再也不敢只在本机对着Notebook说跑通了。6.3 日志和监控没有它们线上事故等于盲人摸象模型进入线上服务之后最常被忽略的工程环节就是日志。线上一旦出了问题你手里没有任何信息是谁的请求、什么文本内容、模型输出了什么、耗时多少这种时候你只能去猜而AI应用的玄学问题又多猜是猜不出来的。我后来给自己的服务加了三套日志访问日志记录每个请求的文本长度、处理耗时、预测结果数据分布日志定期统计线上输入的文本长度分布、高频词和训练集做对比用来发现数据漂移异常日志记录所有预处理报错和未知输入形态。这让我从一个上线即焦虑的状态变成了出问题能在一小时内定位大概率方向的状态。特别是数据漂移这个点真实业务里的输入会随着季节、活动、用户群体变化而改变分布模型用久了自然会变钝。如果没有数据分布日志你只能等用户投诉变多了才发现模型出了问题有了日志你能在指标还没明显下滑的时候就意识到线上文本的长度分布已经和训练集差了一大截。7. 给同样想从零开始的人我的路径复盘和避坑清单写到这里整个从零开始的AI工程链路已经串完了一遍。如果你按顺序把我前面说的六个环节都亲手做过一遍最短一个周末可以跑通最小闭环认认真真做一周可以做到服务化。如果只挑一个最重要的心得来说那就是——不要贪快每个环节亲手做一遍远远胜过把所有环节用框架黑盒跑十遍。我把这一路踩过的最有价值的坑整理成了一份清单方便你实操前先扫一眼犹豫时先不要引入新工具能用内存加载解决的就别急着上数据库能用脚本解决的就别急着上框架等瓶颈真实出现再做技术升级任何环节先定义什么叫完成再动手数据的完成是清洗后统计分布可解释模型的完成是可复现的实验记录加指标服务的完成是持续稳定处理请求而不是能够返回一次结果指标只信拆分后的明细不信聚合后的数字整体准确率会骗你按类别拆开的精确率和召回率不会给日志留一席之地没有日志的服务就像一个没有仪表盘的驾驶舱——不出事故一切都好一出事故你连从哪开始排查都不知道环境差异永远是事故高发区本地跑通不是真跑通在一个干净的环境里重新部署一次才算。我个人的体会是AI工程这个领域最大的门槛不是数学不是编程甚至不是算法而是对完整链路的掌控感。很多人卡在某个环节学不下去往往是因为他只盯着眼前这一小段看不到它在整条链路里的位置。而from-scratch的这轮练习恰恰把你强行按在那条链路的起点逼着你一路走到终点——这个过程产生的掌控感值得每一个想入行AI工程的人亲自体会一次。最后多分享一个我当时收尾时做的小事把训练、评估、服务三段脚本分别用三个README串起来让一个完全没看过我代码的人照着文档能完整复现整条链路。写文档的过程又一次暴露了我代码里的含糊之处而修完之后我才敢说这个项目真正从零开始走完了。
返回列表