ARTICLE DETAIL

资讯详情

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

从零开始做AI工程:环境、数据、部署、监控的完整实战路线

从零开始做AI工程:环境、数据、部署、监控的完整实战路线 两年前我第一次看到ai-engineering-from-scratch这个仓库名时第一反应是这大概又是个把论文公式抄一遍的学习笔记。真按着里面的路线完整走了一遍之后我才发现自己完全想错了——它解决的压根不是“理论不会”而是那个更折磨人的问题“理论好像会了却不知道从哪下手把整个系统跑起来”。“AI工程”这个词已经被讲烂了但真正从零开始把一个模型送进生产环境、稳定运行几个月的人其实没那么多。绝大多数人卡在中间某个环节环境装不好、数据管不住、训练结果复现不了、部署出来接口一压就挂。这篇内容就是围绕这套从零开始的AI工程路线记录我自己一路踩坑、拆解、填坑的完整过程特别适合那些算法基础有一点、工程经验几乎为零的人参考。文章里讲到的所有结构和操作都可以直接抄进你自己的项目里。1. 先想清楚这个标题里的from scratch到底指什么1.1 不是从神经网络起源讲起很多人一看到from scratch第一反应是“从感知机、反向传播推导开始”。但这套路线图的切入点完全不同它默认你已经具备基本的机器学习概念知道什么是有监督学习、损失函数、梯度下降但接下来就不知道该干什么了。岗位名称“AI工程师”听起来很光鲜实际工作内容却很杂数据要自己清洗、训练脚本要自己写、模型要自己封API、线上指标掉了要自己排查。任何一环薄弱整个系统都会在线上暴露问题。所以这套路线把AI工程定义成一句话把一个模型从实验环境搬到生产环境并持续维护下去的全套能力。它不是某一项技能而是环境、数据、模型、部署、监控五件事的组合。1.2 算法能力与工程能力是两套思维算法同学关注的是“模型精度还能不能往上提一点”而AI工程关注的是“整个系统能不能稳定复现、快速迭代、长期运行”。这两者的思维方式差异非常大不提前意识到会吃大亏。举个例子算法阶段做实验用Jupyter Notebook跑完一个单元格看到准确率变高就开心地继续调下一个超参数。但工程路线要求的是同一个实验必须能被别人或三个月后的你一键复现固定随机种子、锁定依赖版本、记录数据标识、保存训练配置。这些工作不像调参那样有即时反馈但恰恰是系统稳定性的基石。我在项目里专门把“可复现性”放在了环境阶段的第一位原因很简单不可复现的实验等于没做。1.3 这个项目仓库的目录结构怎么排我按“环境→数据→模型→部署→监控”五个阶段来组织整个项目每个阶段配一个最小可运行示例而不是长篇大论的文档。目录结构大概长这样ai-engineering-from-scratch/ ├── 01-environment/ │ ├── environment.yml │ └── Dockerfile ├── 02-data/ │ ├── download_data.py │ └── validate_data.py ├── 03-training/ │ ├── train.py │ └── evaluate.py ├── 04-serving/ │ ├── app.py │ └── test_request.py └── 05-monitoring/ ├── drift_detection.py └── alert_rules.md为什么这样分因为每个阶段都有独立的知识体系和排错方法。如果混在一起你遇到一个报错都分不清该查环境、查代码还是查数据。先划清边界每个阶段单独打通最后再串完整链路这个顺序对初学者是最友好的。2. 第一关把运行环境从“玄学”变成“可复现”2.1 Python、CUDA、PyTorch三方版本匹配AI工程与普通后端开发最大的差异在于GPU依赖。普通后端服务你只要pip install一下依赖代码基本能跑但涉及深度学习框架Python版本、CUDA驱动、PyTorch版本、cuDNN版本必须精确匹配任何一个对不上轻则Warning重则直接段错误崩溃。我这里直接给你一个经过大量项目验证的最小稳定组合组件推荐版本说明Python3.10生态兼容性最好第三方库几乎都有预编译包CUDA Toolkit11.8PyTorch官方预编译轮子支持最稳的版本PyTorch2.1.x训练推理都稳定API设计也合理显卡驱动 525驱动版本决定CUDA运行时能用哪个等级环境创建建议用conda锁一层pip再锁一层。我用的是这样的操作流程conda create -n ai-scratch python3.10 -y conda activate ai-scratch conda install cudatoolkit11.8 -c conda-forge -y pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118提示先执行nvidia-smi确认驱动版本。如果显卡驱动太老即使conda环境完全正确程序也会在torch.cuda.is_available()返回False时静默改用CPU跑训练速度瞬间慢几十倍。2.2 容器化是对未来的一种投资很多初学者觉得Docker学习成本高先跳过。这个想法在自娱自乐的小项目里没问题但一旦项目要交接给同事、部署到服务器、或者换一台新电脑重新跑你立刻会体会到处处是坑。容器化解决的不是“跑起来”而是“在任何地方跑起来”的问题。Dockerfile写得极简也能达到目的FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu20.04 ENV PYTHONUNBUFFERED1 \ PIP_NO_CACHE_DIR1 RUN apt-get update apt-get install -y python3.10 python3-pip WORKDIR /workspace COPY requirements.txt . RUN pip install -r requirements.txt COPY . .关键点是基础镜像选带CUDA和cuDNN的这样就不用再从零装一遍GPU栈。写清楚PYTHONUNBUFFERED1是为了让日志实时输出后面排查线上问题时救命。2.3 最小可复现骨架的必备文件一个环境阶段合格的项目至少要包含三个文件。第一是requirements.txt必须用pip freeze生成精确版本号而不是手动写“torch2.0”这种宽泛约束。你看一次“昨天还能跑今天跑不了”的惨案就会明白精确锁版本有多重要。第二是environment.yml记录conda层面的通道和包。第三是README.md但里面不能只写“安装依赖”要写清楚复现步骤、预期结果、常见报错的解决方式。我在这套环境阶段里栽过最大的跟头是换了台新机器从零搭环境CUDA装了12.0结果项目依赖的某个旧库直接编译失败。后来全部改成在项目根目录放一个setup.sh脚本一键完成“创建虚拟环境装依赖跑通测试”才彻底摆脱每次迁移环境都要和报错搏斗半天的局面。3. 第二关数据工程被严重低估的第一生产力3.1 真实项目里数据工作的真实比重外界对AI的想象是“写模型”实际项目里数据工作往往占到一半以上时间。模型结构可以复用开源成果但数据必须自己处理。我在这个项目的数据阶段整理了三个高频动作采集、清洗、校验。清洗不是把空值删掉那么简单。数值型特征有空值要决定是填中位数还是用模型预测填充类别型特征有非法取值要统一归一化到合法集合文本特征有编码混乱要先检测再修复。每一个决定都会影响模型表现。更麻烦的是这些决定如果不记录几周后根本想不起来当时为什么这么处理。所以我强烈建议从一开始就养成习惯每个清洗步骤都写成独立函数并且配上一个验证函数。比如“删除缺失率超过80%的列”就要写一个断言来确认这个规则真的被应用了。3.2 数据版本化给数据打上可追溯的标签代码可以用Git管理数据却不适合塞进Git——大文件会让仓库膨胀到没法用。业界一般用DVC这类数据版本管理工具操作逻辑其实和Git很像。我第一次完整跑通数据版本化是在一个图像分类项目里用了DVC。流程非常简单dvc init dvc remote add -d myremote s3://my-bucket/dvc-store dvc add data/raw_images/ git add data/raw_images.dvc .dvc/config git commit -m add raw images v1后续任何人想复现这个模型只需要dvc pull就能把完全一致的数据拉下来。数据漂移导致模型指标下降时你还能精准对回到某一天的某份数据而不是凭记忆猜测“当时用的好像是8月份那份”。3.3 数据集划分别让数据泄漏毁掉整个评估数据集划分看着简单里面藏着大坑。最常见的是时间序列数据没有按时间切分而是在全体数据上随机划分。这样训练集里混着未来信息验证集指标会异常好看一上线就被现实毒打。正确的做法是数据一旦有时间顺序划分时要用前者训练、后者验证。比如交易数据、日志数据、股票数据都必须严格按时间截断。train_df df[df[timestamp] cutoff_date] valid_df df[(df[timestamp] cutoff_date) (df[timestamp] cutoff_date2)] test_df df[df[timestamp] cutoff_date2]这套路线里我一直反复强调“先验证集再训练集”的反直觉习惯模型心里只认训练数据当验证集表达的含义与训练集差异过大时说明你的采样方式或数据预处理有严重问题要先解决这个再谈调参。4. 第三关训练与实验管理把调参从玄学变成科学4.1 一个最小可用的训练循环训练代码是AI工程中最容易写、也最容易写烂的部分。最小可用的PyTorch训练循环长这样import torch def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0.0 total_samples 0 for x, y in loader: x, y x.to(device), y.to(device) optimizer.zero_grad() out model(x) loss criterion(out, y) loss.backward() optimizer.step() batch_size x.size(0) total_loss loss.item() * batch_size total_samples batch_size return total_loss / total_samples这段代码有几个容易忽略的细节一是model.train()和model.eval()的切换影响Dropout和BatchNorm的行为很多新手忘了最后会得到完全不同的推理结果二是optimizer.zero_grad()必须在每个batch迭代前调用否则梯度会累积三是loss要按样本数加权平均而不是简单算所有batch的平均因为最后一个batch可能很小且分布不均。真正线上规模比较大的训练脚本还会加这几样东西Early Stopping只保存验证集损失最低的checkpoint、学习率调度器在loss平台期自动降学习率、训练日志记录每个epoch的耗时和GPU显存占用。我在项目里都提供了对应模板照着填就行。4.2 实验追踪工具别让实验记录靠脑子训练不只写循环还要管实验结果。10次实验之后人脑就废了根本记不住哪次用了什么超参、哪次用了哪份数据。我一开始用手写CSV记录跑到第50次实验彻底放弃转用MLflow。MLflow最核心的用法就三行import mlflow mlflow.set_experiment(my-project) with mlflow.start_run(): mlflow.log_params({lr: 0.001, batch_size: 32}) mlflow.log_metric(val_loss, 0.42) mlflow.log_artifact(best_model.pt)这样每次实验的参数、指标、模型文件自动关联浏览UI就能看到哪组超参表现最好。对比一下手动记录和实验管理工具差别非常直观能力手动CSV记录实验管理工具MLflow/WB参数与指标自动关联易错自动复现同一实验难一键多人协作混乱按实验隔离可视化对比自己做图表内置对个人项目我推荐你先用MLflow的本地模式不引入服务器。等团队协作需要共享实验记录再上中心化部署。4.3 过拟合与欠拟合的诊断顺序初学者一上来就谈防过拟合加正则、加Dropout、数据增强但很多时候模型压根是欠拟合的。正确的诊断顺序一定是先看训练集损失再看验证集损失。如果训练集损失就已经很高说明模型容量不够或优化不充分这时候把所有正则手段全加上只会雪上加霜。如果训练集损失很低、验证集损失很高这才是过拟合才需要上Dropout、权重衰减、数据增强。我现在遇到模型效果差第一反应永远是画两条曲线训练损失曲线和验证损失曲线。曲线形态直接告诉你下一步该往哪个方向使劲比瞎改超参高效太多。5. 第四关部署推理从notebook到生产服务的最后一公里5.1 模型封装成API注意这几个容易翻车的点部署阶段的第一件事是把模型完整封装成一个HTTP服务。我在项目里用FastAPI做了一套最小模板因为它在性能和易用性之间取得了最舒适的平衡点。from fastapi import FastAPI import torch from pydantic import BaseModel app FastAPI() model torch.load(model.pt, map_locationcpu) model.eval() class Payload(BaseModel): features: list[float] app.post(/predict) def predict(payload: Payload): with torch.no_grad(): tensor torch.tensor(payload.features).unsqueeze(0) prob torch.sigmoid(model(tensor)).item() return {probability: prob}这段代码里最坑的地方是.item()。如果你忘记调用它直接返回一个GPU上的Tensor对象FastAPI会抛出序列化错误。此外模型加载应该放在模块导入阶段而不是请求内部否则每个请求都要重新载入大模型延迟直接翻几十倍。注意模型服务接口返回的一定是Python原生类型float、int、list、dict不要直接返回Tensor、numpy数组这类对象。这是新手在部署阶段最容易犯的错误之一。5.2 推理性能优化批处理与量化模型上线后最大的性能瓶颈通常不是网络而是单个请求的推理耗时。优化优先级的排序非常重要先做批处理再做半精度推理最后才考虑量化。批处理的意思不是让调用方传多个样本而是把并发请求攒起来一次性输入模型。比如服务期间来了10个请求每份都是单条数据你可以把这10条拼成一个batch喂给GPU吞吐量能提升数倍。实现方案用FastAPI的并发队列或者Redis队列都可以核心思路是“攒批再推理”。半精度推理几乎零成本model torch.load(model.pt, map_locationcuda) model.half()显存占用减半、推理略快、精度损失在大部分任务上可忽略。量化效果好但工程复杂度高一般项目可以放到后面再考虑。5.3 线上稳定性超时、重试、并发、降级模型服务的线上问题大多是工程问题而不是模型精度问题。我在项目里专门列了一个检查清单问题典型表现解决方法单请求推理超时客户端一直转圈设置合理的请求超时比如2秒请求积压服务内存不断上涨控制并发数排队超过上限直接拒绝GPU显存溢出CUDA out of memory批处理大小设上限定期释放缓存模型返回异常后端500一片接口加异常捕获返回统一错误结构上游依赖故障调用外部API超时熔断降级返回备用结果我踩过最深的坑是模型服务没有设置超时保护客户端100个并发打进来GPU显存瞬间爆掉进程直接崩溃恢复还要手动重启。后来老老实实加了并发限制、请求超时、HealthCheck探活才让服务真正能无人值守地跑过夜。6. 第五关模型监控与持续迭代系统稳定跑一个月才算开始6.1 数据漂移检测模型变差的预警系统模型精度不可能永远不变。你训练时用的数据分布和线上真实的数据分布之间会有缓慢的漂移。今天是95%的准确率三个月后可能悄无声息掉到85%而且没有人发现。我在项目里写的第一个漂移检测脚本用的指标是Population Stability IndexPSI。它的核心思路是把训练集的特征分布和最近一个月线上数据的特征分布拉到同一个区间算每个区间占比差异。PSI大于某个阈值就发出警告。import numpy as np def compute_psi(expected, actual, bins10): expected_percent, _ np.histogram(expected, binsbins, range(0, 1)) actual_percent, _ np.histogram(actual, binsbins, range(0, 1)) expected_ratio expected_percent / expected_percent.sum() actual_ratio actual_percent / actual_percent.sum() actual_ratio np.where(actual_ratio 0, 1e-6, actual_ratio) psi ((actual_ratio - expected_ratio) * np.log(actual_ratio / expected_ratio)).sum() return psi简化的实现里PSI小于0.1表示分布稳定0.1到0.25表示轻度漂移大于0.25就需要考虑重新训练模型。这个脚本不需要额外部署每天定时任务跑一遍、把结果推到告警系统就行。6.2 自动重训练不要无脑每天重训“监控发现漂移就自动重训”听起来很智能实际操作里会踩大雷。最典型的坑是数据量太小、标签质量太差、重训后模型反而比旧版还差。所以我在项目里采用的策略是“阈值触发人工确认”漂移指标超过轻度阈值先发告警系统自动准备最新数据和检验集训练离线评估对比新旧模型的验证集指标新模型有明显提升才进入灰度发布流程。自动重训听起来是AI工程兜底能力但本质上它也是工程问题需要先做好回滚机制再谈自动化。6.3 回滚与灰度发布模型上线不是“一把梭”把所有流量切过去就完事。我亲身经历过一次新模型验证集上表现正常上线后线上数据分布跟验证集完全不一样A/B测试指标直接崩了。如果当时没有灰度发布机制整个线上服务都会遭殃。标准做法是保留最近的N个模型版本存在带版本号的文件或模型仓库里models/ ├── model_20240101.pt ├── model_20240115.pt └── model_latest.pt服务端启动时加载model_latest.pt日常运维时先加载新模型跑一小部分流量10%观察指标确认稳定后再逐步调高到50%、100%。如果有任何异常立马切回上一个稳定版本全程5分钟以内完成。这套机制不需要复杂的架构只要在API服务里加一个读取配置文件和模型文件版本号的步骤就能实现。别等线上出了问题才想起回滚机制存在。7. 一趟走下来比技能更重要的三个习惯整套路线跑完专业技能确实提升了很多但真正让项目变稳的是三个很朴素的工作习惯。第一每次遇到诡异报错先花时间写一个最小复现脚本。这个问题看似“浪费”半小时实际上能大幅缩短排查时间。我那个环境问题、部署问题凡是能快速定位的都是先写了10行以内的最小脚本确认问题边界而不是在几千行代码里大海捞针。第二所有操作命令、踩坑结论、处理思路全部记到项目的docs/troubleshooting.md里。这个文档现在已经有几十条记录了常见问题打开就能查到解决方案。知识只存在于自己脑子里是很容易丢失的写下来才有复用价值。第三所有实验必须可复现。不能复现的结果不管当时看起来多好都是无效的。这个习惯一开始觉得麻烦甚至多余但经历过项目交接和回滚事故之后我才意识到“可复现性”才是工程世界里报销一切的凭证。从零开始做AI工程说难很难难点全在坑多且散说容易也容易因为每个坑都有明确解法和前人总结。这套路线我走了两遍第一遍摸出一道道的坑第二遍把所有坑都标成上了路标的绕行方案。照着这份经验走我相信你会比我当年快得多。
返回列表