尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI工程从零搭建:环境、数据、训练、部署与监控全链路实践

AI工程从零搭建:环境、数据、训练、部署与监控全链路实践 几年前我第一次把一个模型送上线的时候最大的感慨不是“神经网络真厉害”而是“这玩意怎么这么难伺候”。模型在笔记本上跑得好好的到了服务器上就报错实验记录全靠Notebook里手动翻上线一周后没有监控预测结果开始悄悄变差没人能第一时间察觉。这些问题单拎出来每一个都不难难的是把它们串成一条完整的、可复用、可交底的能力链。这几年带着不少从零起步的工程师一起做项目也陆陆续续整理了一套自己的路径——就是标题里这个ai-engineering-from-scratch。它要表达的意思不是“从零写一个训练脚本”而是把 AI 工程这件事完整地立起来从环境、数据、实验、训练、部署到监控每个环节都有章法每个坑都有记录。这篇文章我想把这条路拆开讲清楚。适合正在转向算法工程方向的后端开发者也适合刚入门、打算把 AI 做成正经系统的同学哪怕是带过一段时间 AI 项目但总觉得流程别扭的朋友应该也能在里面找到几个能直接拿来用的思路。下文没有特别深的数学核心是工程结构和实操习惯目标就一个让大家看完之后能顺着这条线自己从零搭出一套靠谱的 AI 工程底座。1. 先搞清楚一个事实AI工程和调算法根本不是一回事1.1 三种角色别把自己定位错了我见过太多人一开始就钻错方向。想搞“AI工程”结果一上来就死磕 transformer 源码或者在 Kaggle 上刷了两个月的精度排行榜。不是说这些没用而是它们解决的完全是另一个问题。业内常被混在一起的其实是三类人算法研究员负责发明或改进模型结构拼的是论文和基准分数数据分析师/数据科学家负责从业务数据里找规律、做分析、提特征拼的是对业务的理解和统计功底AI 工程师负责把算法真正变成线上系统里稳定运行的模块拼的是工程化能力——数据能不能重复、训练能不能重现、模型能不能上线、上线后能不能感知异常。ai-engineering-from-scratch关注的是第三类。这里最核心的分界线是研究角色追求“上限”工程角色追求的是“下限”。同一个模型A 组在理想环境里跑出 0.91 的 AUCB 组在肮脏数据、混乱版本、无监控的线上环境里跑出 0.84 还要保证不崩。后面的工作往往比前面的调参更难也更值钱。1.2 AI工程要解决的最基本四个问题把这四个问题想明白基本就理解了这个领域为什么存在可复现三个月前的实验我还能不能原样跑出来同样的结果数据、代码、依赖、超参数缺一个这个目标就完不成。可协作三个人同时在一个模型项目上干活谁改了训练集、谁改了特征、谁动了超参数能不能有迹可循地查清楚可上线模型训练完不是终点从.ipynb变成线上服务中间那条路像不像一个正规软件项目的发布流程可观测上线之后只能祈祷模型永远正常工作吗特征分布变了谁发现预测结果异常了怎么报警这四个问题任何一个做不好项目都会在某个阶段翻车。翻车的方式各不相同可能是同事离职后没人敢动他的训练代码可能是模型上线一周后悄悄劣化被业务方投诉也可能是线上推理延迟暴涨导致服务超时。1.3 先给自己做一个自测清单行动之前建议先做一张自测表。不用工具就拿你现在手头的一个 AI 项目回答以下问题检查项如果答不出来说明缺口在哪这个模型用的训练数据是哪天导出的数据版本管理当前模型的 AUC/准确率是谁跑的怎么证明实验记录与评估标准化训练脚本依赖的 Python 包能一键恢复环境吗依赖隔离与容器化预处理、特征计算和线上推理用的是同一份代码吗训练/推理一致性如果模型效果下降了你怎么知道监控与告警如果公司换一台 GPU 服务器能半小时内重新跑出线上模型吗环境可迁移与自动化如果发现自己有超过一半的格子是空白的那这篇文章正好就是你要的起点。2. 环境与工具链第一块基石怎么打最稳2.1 从零起步的技术选型逻辑很多人一上来就会问我这些工具我应该全都要吗要不要学 Kubernetes要不要上 Feature Store我的回答通常是不要为了工具而工具从最小的闭环开始然后在痛点上加工具。从零搭建的时候我建议先定三个原则能用标准化的不自己造。模型训练代码、Web 推理服务都有成熟的框架别总想着从底层手写。配置是一切复现的起点。任何超参数、路径、数据版本原则上都走配置文件不写死在代码里。环境必须一次性构建成功。不管你在自己电脑上、GPU 服务器上还是同事的机器上都应该有一条命令把环境从零复原。2.2 Python 环境管理的真实建议Python 是 AI 领域逃不开的语言但 Python 的包管理是个著名的坑。我个人的经验是分两个阶段第一本地快速开发阶段直接用uv一个用 Rust 写的包管理器或conda创建隔离环境按项目管理依赖不要直接装在系统 Python 里。uv的好处是速度极快而且能用锁文件锁定依赖版本。第二项目交付或部署阶段必须升级为容器镜像。因为不管本地环境管理得再好容器才是能跨越“开发机”和“生产机”之间差异的最小单位。我常用的组合是python:3.11-slim作为基础镜像加上项目级requirements.txt或pyproject.toml来安装依赖。2.3 GPU、CUDA、cuDNN 的版本对齐问题这是从零起步的工程师最容易踩的第一个大坑。很多人用的显卡是 NVIDIA 的安装 GPU 版本 PyTorch 之后莫名其妙发现程序报CUDA error: no kernel image is available for execution on the device。这个问题绝大概率不是代码错了而是驱动、CUDA runtime、PyTorch 编译版本、GPU 算力之间的兼容性出了问题。我实践下来比较稳的组合参考如下以 2025 年常见环境为例具体小版本以各框架官方文档为准组件推荐版本示例说明NVIDIA 驱动560.x 及以上驱动版本决定 CUDA 版本上限CUDA Toolkit12.x向下兼容 11.x 的多数算子训练框架会带 runtime不一定需要整包安装PyTorch2.x对应 CUDA 12.x 的预编译版本用官方--index-url安装别用默认源Python3.10 或 3.11太新的版本可能等不到所有算子编译适配另外很多人在服务器上管理 GPU 环境时会纠结到底要不要单独装 CUDA Toolkit我的经验是如果完全用 PyTorch 这样的框架通常不用单独安装完整 CUDA Toolkit因为框架自带的 runtime 已经够用。真要装也建议通过 conda 管理而不是直接用系统包管理器乱装否则很容易污染系统环境。2.4 用 Docker 把环境固定下来我的习惯是在项目的第一个星期就引入 Docker。原因很简单让协作的人不用再回答“我的环境为什么跑不起来”这种问题。一个最小可用的 AI 训练/推理环境Dockerfile 大致长这样实际项目不需要特别复杂FROM nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04 # 这里可以改成 python:3.11-slim 用于纯 CPU 推理 RUN apt-get update apt-get install -y --no-install-recommends \ python3.11 python3.11-venv git curl \ rm -rf /var/lib/apt/lists/* WORKDIR /workspace COPY requirements.txt . RUN python3.11 -m venv /opt/venv \ /opt/venv/bin/pip install --upgrade pip \ /opt/venv/bin/pip install -r requirements.txt ENV PATH/opt/venv/bin:$PATH这里有个实用细节想分享给大家GRGPU 训练容器体积很大如果只是做推理尽量用 CPU 小镜像。把训练和推理的环境拆成两套好处是部署快、攻击面小、资源占用低。训练服务器和推理服务本来就该是两套不同的资源池。3. 从零搭建一条可复用的AI流水线五件套有了环境只是地基。真正让 AI 工程和“随便跑个脚本”拉开差距的是下面这条流水线的设计。我一般称它为 AI 工程“五件套”。3.1 数据版本化模型可以回滚数据不能先提一个问题模型上线后效果变差了算法同学说“我什么都没改”。那你怎么办如果发现不是代码问题大概率是训练数据变了。所以数据和代码一样必须纳入版本管理。传统思维是用 Git 管理数据但数据文件往往很大不适合塞进 Git 仓库。行业里的常规做法是用专门的工具我常用的是DVCData Version Control。它的工作方式其实很好理解数据文件存储在本地或云对象存储S3、OSS、NAS 等里DVC 只记录文件的元信息和哈希值然后把元信息文件提交到 Git。这样大家都从 Git 拉代码再执行一次dvc pull就能把对应版本的数据拉下来。实际项目里我习惯在数据目录下建立清晰的文件组织data/ ├── raw/ # 原始数据只增不改 │ ├── 2025-01-01.parquet │ └── 2025-01-08.parquet ├── processed/ # 清洗后的数据按版本输出 │ ├── v1.0.0.parquet │ └── v1.1.0.parquet ├── features/ # 特征数据可按特征组合命名 │ └── feature_set_a.parquet └── .dvc/ # DVC 元信息目录关键习惯是原始数据永远只增不改。哪怕发现原始数据里有脏数据也一定要保留最原始的那份只在其上派生出清洗后的新版本。这样任何分析都能追溯到源头而不是等到出问题时才发现原始数据早被覆盖了。3.2 实验管理每个实验背后是一整套配方跑实验的时候只记录一个model_final_v2.ipynb是不够的。实验管理的本质是记录“配方”数据文件哈希、代码 commit、环境依赖、超参数、评估指标、模型产物路径。缺任何一项实验就等于没做。我的做法比较朴素训练脚本 配置文件 MLflow 记录。配置用 Hydra 或纯 YAML 维护。比如一个config.yamldata: raw_path: data/raw/2025-01-08.parquet processed_version: v1.1.0 test_split: 0.2 random_state: 42 model: name: gradient_boosting params: n_estimators: 500 learning_rate: 0.05 max_depth: 5 experiment: name: churn_prediction_v1训练代码里用 MLflow 把这些信息自动记录起来import mlflow import yaml with open(config.yaml) as f: config yaml.safe_load(f) mlflow.set_experiment(config[experiment][name]) with mlflow.start_run(): # 记录超参数 mlflow.log_params(config[model][params]) # 记录指标比如 AUC、F1 mlflow.log_metric(auc, val_auc) # 记录模型 mlflow.sklearn.log_model(model, model) # 记录数据版本信息可以是一个自定义字符串 mlflow.log_param(data_version, config[data][processed_version])这样三个月后想回溯每一条实验记录都能告诉你用了哪份数据、什么样的参数、产出了什么效果、模型文件在哪。这个稳定性带来的价值在项目协作时体现得最明显。3.3 特征工程独立成模块很多人写 AI 项目习惯把特征处理和训练代码搅在一起训练脚本里一手 pandas 清洗一手拉模型。这种做法在单机演示没问题但一旦要上线就会出现最经典的问题训练时的特征处理和线上推理时的特征处理不一致。比如训练时你用的是df.fillna(0)线上你却忘了处理缺失值训练时你做了标准化线上却没有存那组均值和方差。更稳的做法是把“特征计算”封装成一个独立模块训练和推理都复用它。推荐的组织结构src/ ├── features/ │ ├── builders.py # 从原始数据构造特征的逻辑 │ └── schema.py # 特征名、特征类型定义 ├── models/ │ ├── baseline.py │ └── train.py ├── serving/ │ ├── api.py # 推理服务接口 │ └── preprocess.py # 线上请求进来时的特征转换 └── configs/ └── config.yaml哪怕是一个很小的项目也值得把特征逻辑抽出来。因为线上服务最怕的不是模型不准而是模型用的输入和训练时完全不一致这种错误不是精度问题是事故级别的问题。3.4 训练脚本的工程化结构一个训练脚本要满足“别人也能跑、以后也能跑”的要求至少要包含五个部分读配置超参数、路径统一从配置读入数据加载从原始数据处理到训练/验证集训练循环模型拟合、早停、交叉验证评估在独立测试集上输出完整指标保存产物模型文件、预处理器、指标 JSON。这里有一个我踩过的坑想特别提醒随机种子。不管做线性模型还是深度模型都要在脚本开头固定所有随机源Pythonrandom、NumPy、PyTorch/框架的 seed否则两个环境各跑一次结果不一样谁都说不清是不是代码改动导致的。这不是形式主义这是工程判断的问题。3.5 模型评估与注册为上线准备好“身份”训练完成后模型不能只是磁盘上一个.pkl或.pt文件。要让它成为一个可上线的“候选”至少要完成两件事一是在一个固定的测试集上记录完整评估报告二是给模型一个版本号。MLflow 的 Model Registry 是一个不错的选择。可以给模型打上Staging、Production、Archived这样的阶段标签。上线不是把文件复制过去就行应该是从注册中心拉取指定版本 → 验证产物完整性 → 部署 → 在监控看板上标记当前线上版本。这个过程一旦固化下来整个团队的协作节奏都会顺畅很多。4. 一次完整的端到端实践从原始表格到在线API前面讲了这么多框架和概念现在来一个能直接运行的骨架。这一节我带大家把一个最简单的回归任务做成一条完整的链路原始数据 → 训练 → 导出模型 → FastAPI 在线推理 → Docker 部署。整个过程不需要 GPU在普通开发机上就能跑通。虽然它很简单但完整体现了训练和推理分离、特征一致、服务化部署这三个工程要点。你可以把它当成自己的第一个模板以后换成真实数据和真实模型改改接口就行。4.1 场景定义为了不让示例复杂化我们就做一个虚拟的业务根据 5 个数值特征比如用户行为统计、金额、频次等预测一个连续指标比如 LTV 用户生命周期价值。数据集用随机数生成——真实项目里替换成业务数据即可。4.2 训练侧产出模型产物训练脚本的核心流程是生成数据 → 分割样本 → 训练梯度提升回归模型 → 转成 ONNX 格式 → 保存。# train.py import numpy as np from sklearn.ensemble import GradientBoostingRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error from skl2onnx import to_onnx # 1. 用随机数据模拟业务样本 rng np.random.default_rng(42) X rng.random((2000, 5)).astype(np.float32) y ( X np.array([1.5, -2.0, 0.3, 4.0, -1.0], dtypenp.float32) rng.normal(0, 0.1, 2000) ) # 2. 切分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 3. 训练模型 model GradientBoostingRegressor( n_estimators200, learning_rate0.05, max_depth4, random_state42 ) model.fit(X_train, y_train) # 4. 评估模型 pred model.predict(X_test) print(fMSE: {mean_squared_error(y_test, pred):.4f}) # 5. 导出 ONNX供线上推理使用 initial_input {X: X_train[:1]} onnx_model to_onnx(model, initial_input, target_opset17) with open(model.onnx, wb) as f: f.write(onnx_model.SerializeToString())这里导出 ONNX 而不是直接存 sklearn 的.pkl是有意识的设计选择。ONNX 是跨平台的标准格式不依赖 Python 里的特定库版本推理时用 ONNX Runtime 就可以部署环境和训练环境完全脱钩。这是训练/推理分离的关键一步。4.3 推理侧FastAPI 接住请求线上服务的职责很纯粹接收请求 → 特征转换成模型输入格式 → 调用 ONNX Runtime 推理 → 返回结果。# main.py import numpy as np import onnxruntime as ort from fastapi import FastAPI from pydantic import BaseModel app FastAPI() sess ort.InferenceSession(model.onnx, providers[CPUExecutionProvider]) class Payload(BaseModel): features: list[float] app.post(/predict) def predict(payload: Payload): if len(payload.features) ! 5: return {error: expected 5 features} x np.array(payload.features, dtypenp.float32).reshape(1, -1) output sess.run(None, {X: x})[0] return {prediction: float(output[0][0])}这个服务在本地启动后可以直接用 curl 验证curl -X POST http://127.0.0.1:8000/predict \ -H Content-Type: application/json \ -d {features: [0.5, 1.2, 0.3, 2.5, -0.4]}如果一切正常你会收到一个{prediction: ...}的 JSON 响应。这里的核心是特征顺序必须和训练时完全一致。真实项目里建议在推理代码里记录特征顺序的 schema并在请求进来时做校验不要把这种一致性寄托在“大家记性好”上。4.4 Docker 部署一条命令启动服务最后把推理服务打成镜像让它在任何机器上都能一条命令启动。FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY model.onnx main.py . EXPOSE 8000 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]requirements.txt只需三行fastapi uvicorn onnxruntime构建并运行docker build -t ai-demo-serving . docker run -p 8000:8000 ai-demo-serving到这里一个最小闭环就跑通了。前面讲的数据版本化、实验管理、监控实际都是在真实项目里围绕这个闭环逐渐丰满的。先把骨架立起来再往里面填血肉这是我从零搭过多个系统后最深的体会。4.5 跑通之后的验证清单骨架跑通不代表系统工程化就算完成了。我通常还会让团队按这张清单自查一遍删掉整个 Python 虚拟环境只用 Docker 镜像能否从零启动服务如果新增 10 个特征从改特征模块到重新部署需要动哪几个文件线上服务的模型版本能不能在注册中心一一对应到训练实验压测 1000 个并发请求时响应延迟和内存占用是否在预期范围内如果这些问题都要想很久说明系统还只是“能跑”距离“工程化”还有差距。5. 实测中踩过的真实坑位工程化学费记录下面这部分想写成一份踩坑笔记。这些都是我在实际项目中遇到过、也帮别人排过的问题几乎每个都曾在不同团队里反复上演。5.1 数据泄露交叉验证写错的经典案例有一次团队训练用户流失预测模型离线 AUC 高达 0.98业务方高兴坏了。上线后效果惨不忍睹。查了两个星期最后发现问题出在交叉验证代码数据做归一化时用了全量数据的均值和方差而不是只从训练集上 fit。这就造成验证集的信息偷偷漏进了训练过程等于考试时把答案放在了草稿纸里。正确的做法是from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression # 关键点scaler 只 fit 训练部分验证/测试部分只能 transform pipeline Pipeline([ (scaler, StandardScaler()), (model, LogisticRegression()), ]) pipeline.fit(X_train, y_train) # scaler 和 model 都只在训练折上 fit y_pred pipeline.predict(X_test)这个坑之所以常见是因为很多人把特征处理当成“全局准备工作”习惯在整个数据集上先算一遍再切分。任何利用全局统计信息做预处理的方式都要反复检查数据泄露的可能。5.2 训练和推理的特征不一致还有个更隐蔽的问题训练时的预处理是一套代码线上服务里为了“图方便”又写了一套。比如训练时对日期字段做了“年初至今天数”转换线上请求进来的日期却没有对应转换训练时把空字符串填成 0线上却直接传空值。这类问题不会报错模型不会崩但预测结果从根上就是错的且极难定位。解决思路前面提过把特征处理逻辑做成唯一模块训练时和推理时都 import 同一份代码。真实系统里哪怕做不到完全动态加载至少要把特征转换函数做成纯函数不依赖全局状态这样两边调用结果天然一致。5.3 实验复现失败随机种子和依赖版本还有一次团队有人想在原来的模型基础上加一个新特征于是他把旧代码从 Git 仓库里检出之后重新训练。结果发现指标比记录的好了一截他以为特征有效开心地准备上线。我让他先去查三件事Python 包的版本是否和旧实验一致、随机种子是否固定、训练数据哈希是否相同。查完发现数据文件已经被另一个人重新生成了虽然内容看起来“差不多”但实际分布已经变了一点。这个问题如果不发现后面的评估对比全部失真。所以我在每个实验的 MLflow 记录里除了超参数一定会额外记录三项数据文件哈希、依赖锁定文件哈希、Git commit ID。这三项齐全才算一个可复现的实验。5.4 上线后预测开始漂移监控怎么做很多人有个误解模型上线校验完效果就完事了。其实真正的考验才刚开始。业务环境的数据分布是实时变化的用户习惯会变、突发活动会变、外部环境也会变。模型是静态的世界是动态的。我常用的监控方式是双管齐下第一层输入特征分布监控比如每日统计每个特征的平均值、标准差、分位数用 PSIPopulation Stability Index对比训练时的分布。PSI 超过阈值一般 0.25 以上算剧烈漂移就触发告警。第二层业务指标兜底如果模型对应的是转化率、留存率等业务可观测指标直接统计线上真实结果和模型预测结果之间的偏差趋势。监控不一定要上很重的平台我推荐可以先从 Evidently 这样的开源库起步每天跑一个定时脚本输出报告并发送告警。等规模大了再考虑接入监控平台。5.5 并发与延迟推理性能的常见短板最后一个坑来自线上服务本身。很多人训练时用的是 PyTorch 模型部署时直接把整个 PyTorch 框架加载进来做推理结果内存占用巨大、并发能力差。实际上推理阶段根本不需要训练框架的全部能力。把模型转换为 ONNX 并用 ONNX Runtime 推理是最简单的性能优化手段——体积更小、启动更快、CPU 上部署也很稳。另外如果服务面对较高并发要记得使用异步框架如 FastAPI的时候推理部分如果是 CPU 密集型仍然会阻塞事件循环。这时候建议把在线推理放到独立进程/独立线程池里去跑或者用专门的推理框架。这个细节不处理压测一上来服务就会大面积超时。我自己在压测一个小模型时单请求未优化前是 15ms切换到 ONNX Runtime 后降到 3ms再做一版批量处理整体吞吐提升了近十倍。优化的顺序永远是先转标准格式再考虑批处理还没有必要就暂时不去动架构。最后再分享一个小技巧从一个“能跑的脚本”到一个“能交底的系统”我个人体会最深的不是某个工具的使用而是尽早把端到端链路跑通。哪怕第一版模型效果很差、数据很脏、服务很简陋也一定要先让“数据 → 训练 → 部署 → 推理 → 监控”整个闭环转起来。因为只有闭环存在后面每一步的改进都能被观测到你才知道自己改的东西有没有用。从零开始的话我建议按这样的时间线去推进第一周把端到端骨架跑通就参考第 4 节这个模板第二周把数据版本化和实验管理补上让实验可复现第三四周再把监控和服务化完善。这样四周下来你手里的就不是一个模型而是一条产线。这条路我走过好几次了是真正能把 AI 项目从“科研状态”拉到“工程状态”的捷径。
返回列表