最近两个月,我一直在做一件在别人看来有点“自找麻烦”的事:把一个 AI 需求从零开始,完完整整地做成能上线跑的业务系统。这里说的从零,不只是从空目录开始写代码,而是从业务问题定义、数据盘点、模型选型,一直做到部署上线和线上监控,全程没有套现成的模板。之所以想写这篇文章,是因为我发现身边不少同学手里有很好的模型思路,却卡在工程落地上。模型调参、notebook 里画图都熟练,一碰上数据管线、服务封装、监控告警就手忙脚乱。
这篇内容就是我从零搭建 AI 工程的一套完整心法,包含总体设计、数据工程、模型训练、部署监控和问题排查五个部分。适合那些有一定机器学习基础、但还没完整做过一个工程化 AI 项目的朋友;哪怕你只是刚开始接触 AI 工程,也能照着里面的流程搭出第一版系统。我把踩过的坑、改过的代码和最终验证过的方案都放在里面,照着做能少走很多弯路。
1. 从零搭建 AI 工程的总体设计思路
1.1 先别急着写模型:需求定义和数据契约
很多从零开始的 AI 项目,第一个坑不是环境装不上,而是团队根本没想清楚要解决什么问题。我见过一个团队花了两周调模型,最后发现业务方要的其实是“判断用户会不会流失”,而不是“给用户打一个情感分”。所以在动手之前,我会先做两件事:把业务问题翻译成机器学习问题,再跟数据方和业务方签一份“数据契约”。
业务问题翻译成机器学习问题,核心是确定三点:预测目标是什么,输入信息有哪些,线上怎么使用预测结果。比如用户流失预测,目标是“未来 30 天内是否取消订阅”,输入是近 90 天的行为日志、画像和订单数据,线上使用方式是每天凌晨跑批量预测,把高风险用户名单推给运营。这三个点定下来,数据采集、特征工程和模型评估就都有了明确边界。
数据契约听起来很虚,但它是工程上最管用的约束。我会把每张表、每个字段、取值规则、更新频率和负责人写清楚。举个例子,订单表里的 status 字段,有人用数字编码,有人用字符串;日期字段有的是字符串、有的是 datetime;这些不一致在建模阶段还只是报错,一旦上了线就是静默的错误预测。所以数据契约里至少要包含字段名、类型、允许取值范围、缺失值处理规则、数据更新时间和数据负责人这六项。
1.2 技术选型:为什么我不追新框架
工程化项目里,技术选型的原则从来不是“哪个模型效果好”,而是“哪个方案能稳定跑一年”。我这次的基础选型是:Python 3.11 + PyTorch 2.x 作为模型训练框架,LightGBM 作为基线模型,MLflow 做实验管理,PostgreSQL 存元数据,Docker + FastAPI 做模型服务。这套组合单看每一项都不新,但它们组合起来,恰恰是团队协作和线上运维成本最低的。
为什么第一版只用了 PyTorch 而没上大规模分布式训练?因为绝大多数从零开始的项目,数据量在千万级别以下,单卡 A100 或者两张消费级显卡就够用了。分布式训练带来的通信开销、容错和调试复杂度,对早期项目来说是纯负债。先把单卡训练跑通,再用批量推理或者异步任务补吞吐,是我在中小规模项目上的固定打法。新框架可以关注,但不要在核心链路里当小白鼠。
1.3 最小可行架构:先跑通再完善
架构上我坚持“最少组件、最快闭环”的思路,第一版只保留四个环节:数据管道、训练评估、模型服务、监控告警。这四个环节形成一条从数据到业务反馈的闭环,任何一个环节缺了,项目都只能停留在 notebook 阶段。很多人的第一版 AI 系统只有一个训练脚本,结果模型再好也交付不了,因为没有人知道它该以什么形态被业务调用。
最小可行架构的意思是,所有组件都必须能在三天内搭完。比如数据管道第一版就是一个 Python 脚本加 cron 定时任务,不需要上 Airflow;模型服务第一版就是 FastAPI 包一层模型推理,不需要上 K8s。等数据量上来、调用方多了,再把脚本换成编排工具,把单机服务拆成多副本。这个顺序能帮你把精力花在真正影响业务结果的地方,而不是一开始就纠结基础设施。记住,先让系统跑起来,再谈完善。
2. 数据工程:AI 项目最容易翻车的地方
2.1 数据获取与清洗的实操细节
数据获取环节,我踩过最深的坑是“拿全量数据直接建模”。听起来很合理,但真实业务数据里,各种脏东西会直接把模型带偏。先说清洗要处理的几类问题,再给一个可以照着改的脚本。
第一类是缺值与空值。对于数值型特征,先区分“业务上本该为空”和“采集失败为空”两种情况。例如用户最近一次登录时间,用户从未登录过,这个字段为空是正常的,我会填一个很久之前的时间戳,并单独加一个“是否登录过”的标记位;如果是日志采集失败,就用中位数或者分位数填充。第二类是异常值。业务指标里经常出现 9999、-1 这种占位值,必须通过字段取值规则过滤掉。第三类是重复数据,尤其是日志类表在 join 时产生笛卡尔积。每次清洗后都要检查行数,和上一版本做对比,防止清洗规则误伤正常数据。
import pandas as pd import numpy as np df = pd.read_parquet("raw_events.parquet") # 1. 过滤占位值 df = df[~df["amount"].isin([9999, -1])] # 2. 区分真实缺失和采集缺失 df["is_first_time"] = df["last_login_ts"].isna() df["last_login_ts"] = df["last_login_ts"].fillna( pd.Timestamp("1970-01-01") ) # 3. 去除重复行 df = df.drop_duplicates(subset=["user_id", "event_time"]) # 4. 行数对比 print(f"清洗后行数: {len(df)}")这里有个容易被忽视的细节:清洗规则本身也要版本化。后面排查线上预测异常时,有时候问题不在模型,而在某一天数据清洗逻辑改了,导致特征分布突变。我会把清洗脚本纳入 git 管理,并且每次跑批都输出一份数据质量报告,记录缺失率、异常值占比和关键字段的统计量。
2.2 标注与版本管理:让数据可追溯
监督学习必须有标注,标注过程最容易被当成“体力活”而忽视,但标注质量决定了模型上限。我建议至少抽 10% 的标注样本做二次审核,计算标注员之间的一致率。如果一致性低于 85%,说明标注标准本身有歧义,定出来的模型指标没有意义。我在文本分类项目里通常先让两个人标同一批数据,逐条对比分歧样本,把分歧点细化成标注文档的补充规则。
数据版本管理是和代码版本管理同等重要但经常被忽略的一环。训练数据一旦变更,模型效果可能变化,线上行为也可能变化。如果不知道线上模型是拿哪一版数据训练的,排查问题就无从谈起。我用的是 DVC,配合对象存储存放数据快照,同时在 MLflow 里记录每次训练的 data_version、代码 commit 和特征列表。这样任何一个模型都能反查出它的完整血缘:用了哪份数据、哪版代码、哪些超参数、产出了什么指标。
2.3 特征工程与数据切分的坑
特征工程的核心不是堆特征数量,而是保证每个特征在训练时和推理时都能拿到。最容易出问题的特征是时间相关的:比如“距上次登录天数”,训练阶段和线上推理阶段计算差值的时间基准不同,很容易引入数据泄漏。另外,用户历史行为特征要严格控制截止时间,只能用预测时刻之前的数据。我会在特征代码里加一个 as_of 参数,所有特征聚合都以这个参数为截止时间,保证“不偷看未来”。
数据切分也有讲究。很多新手直接随机切分训练集和测试集,这在用户 ID 强相关的业务里会造成严重的数据泄漏。同一用户的多次行为记录可能同时落在训练集和测试集,模型相当于记住了这个用户的答案,测试指标虚高。我习惯用时间切分:按时间排序,前 80% 的时间窗口做训练,后 20% 做验证,再留一个最新时间窗做最终测试。这样做贴近真实的线上预测场景,也能更早发现特征随时间漂移的问题。
3. 模型训练与评估:从基线到可上线
3.1 基线模型选择和训练参数设定
我建模的第一步永远是先做基线模型,基线模型的作用不是拿上线,而是给后续复杂模型提供一个比较基准。我通常先跑一个 LightGBM 基线,因为它对特征缩放不敏感、能处理缺失值、训练速度快,拿到的结果能快速暴露数据问题。如果 LightGBM 在验证集上的指标就够用了,深度学习模型就没有必要上,工程成本差很多。
如果业务确实需要深度学习,比如文本、图像或者高维稀疏特征,我才会切到 PyTorch。第一版训练参数遵循几个保守原则:batch size 不宜过大,可以先用 32 或者 64 跑通,再观察 loss 曲线决定要不要加大;学习率先设 1e-4 到 3e-4 区间;训练轮数不设死,而是通过验证集早停来控制。组合使用学习率衰减和早停,既能让训练过程稳定,又不会让模型过拟合。
from torch.optim.lr_scheduler import ReduceLROnPlateau optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4) scheduler = ReduceLROnPlateau( optimizer, mode="min", factor=0.5, patience=2 ) best_val_loss = float("inf") best_state = None for epoch in range(100): train_loss = train_one_epoch(model, train_loader, optimizer) val_loss = evaluate(model, val_loader) scheduler.step(val_loss) if val_loss < best_val_loss: best_val_loss = val_loss best_state = {k: v.clone() for k, v in model.state_dict().items()} torch.save(best_state, "best_model.pt") if epoch > 5 and val_loss > best_val_loss * 1.2: break这里我要提醒一点:基线模型不是越简单越好,而是必须和最终方案共享同一套评测流程。很多人基线用随机划分、复杂模型用时间划分,两套指标根本没法比。我会从第一版开始就把数据切分和评估脚本固定下来,后续所有模型都用同一套流程评估,这样模型迭代才有可比性。
3.2 训练过程中的监控与中断恢复
训练过程不是把脚本丢到后台就完事。我吃过一次大亏:训练跑了 22 个小时,手滑把终端关了,全部白费。所以训练工程的第一要务就是检查点机制和中断恢复。至少每 5 个 epoch 保存一次模型权重和优化器状态,同时把 loss、学习率、显存占用实时记录到一个日志文件里。检查点文件命名里带上 epoch 和验证指标,方便恢复时直接选最优的那版。
第二个要点是设置损失曲线的“体检项目”。loss 不下降、loss 为 NaN、验证集指标和训练集指标差距拉大,这三类异常要第一时间触发告警。我会在训练脚本里加一个简单的判断:如果连续 10 个 batch 的 loss 全是 NaN,直接退出并把最后一段日志发到消息通知。不要等早上起来才发现训练早就崩了。这些监控逻辑看起来很基础,但能省下大量试错时间。
3.3 模型评估指标选择:不要只盯准确率
模型评估最大的误区是只看准确率。在用户流失这类正负样本不平衡的业务里,准确率可能是 90%,但模型只把所有用户都判成不流失,业务上毫无价值。我一般先看混淆矩阵,再结合业务场景确定主指标。比如流失预警场景,我更关心召回率,因为漏掉一个高价值用户比打扰一个普通用户更贵。
离线评估要做三层:第一层是统计指标,比如 AUC、F1;第二层是业务指标模拟,比如用验证集预测结果反推运营触达名单的覆盖率;第三层是抽样人工检查,把预测结果按分数排序,人工看高分样本是否合理。第三层最容易被忽略,但它能发现数据泄漏和标注错误,比任何统计指标都敏感。我几乎每次上线前都会拉一批高分样本和错分样本给人看,哪怕只看 50 条,也能发现很多问题。
4. 部署上线与监控闭环
4.1 模型部署:封装、接口、容器化
模型训练完成只是开始,真正产生价值的是部署上线。我常用的部署方案是 FastAPI 封装推理逻辑,再用 Docker 打包成镜像。这里有一个容易忽略的点:模型加载和推理要分开,进程启动时加载一次模型权重,之后每次请求只做 forward,不要在请求里加载模型。模型服务接口必须包含输入校验、超时控制和错误处理,避免把模型内部的异常直接抛给调用方。
服务化之后,并发和时延需要压测。我见过新手直接把模型放到一个没有配置并发管理的 FastAPI 进程里,四五个请求就把请求排队到几十秒。我的经验是用 Gunicorn 加 Uvicorn worker 跑同步接口,worker 数根据 CPU 核数和 IO 等待时间调。推理服务不是越多的 worker 越好,worker 太多反而会增加上下文切换和显存占用。压测时关注 p95 时延和错误率,不要只看平均时延。
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY model/ /app/model/ COPY server.py /app/server.py EXPOSE 8000 CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"]4.2 线上监控与数据漂移检测
模型上线后,监控比模型本身更重要。模型效果不是永久不变的,用户行为发生变化、上游数据口径调整、季节性影响,这些都会让模型离线评估指标和线上真实表现脱节。所以我至少做两类监控:一类是系统监控,包括接口时延、错误率、推理请求量;另一类是数据监控,重点看特征分布和预测分数分布有没有明显偏移。
特征分布偏移我常用 PSI(Population Stability Index)来量化。PSI 的计算不复杂:把线上最近一天的特征分布和训练集的特征分布分成相同区间,计算每个区间占比差异的加权和。PSI 超过 0.2 就说明分布发生了显著变化,需要拉响告警排查。预测分数分布的漂移也有同样含义,用户群变了、行为变了、或者上游数据出错了,都会最先反映到特征分布和预测分布上。
4.3 迭代机制:离线评估和在线反馈
模型运维是一个持续迭代的过程,不是上线就结束。迭代的基本循环是:收集线上表现数据,沉淀为新的训练样本,然后在旧模型和新模型之间做离线对比评估,最后灰度发布。这里我最看重的两个实践是金丝雀发布和模型回滚。金丝雀发布就是让 10% 的线上流量走新模型,其他 90% 走旧模型,对比两者的业务指标,稳定后再逐步放量。回滚就是保留上一版模型的镜像和参数,新模型出现问题后,能在分钟级切回旧模型。
在线反馈闭环也至关重要。比如推荐场景,用户点击、收藏、下单等行为成了新模型的训练标注。这个过程里最容易忽视的是反馈延迟:用户点击行为可能延迟几小时甚至几天才被上报,如果不处理延迟,模型会误把“还没反馈”当成“负反馈”。我会在生成训练样本时,给每条样本加一个“观测截止时间”,只统计截止时间前的反馈,新样本要等完整的反馈窗口过了再进入训练集。
5. 常见问题排查实录
5.1 显存不足与训练中断
显卡显存溢出是我遇到最多的训练问题。显存不足的直接表现是 CUDA out of memory,常见的解决办法有三类。第一类是把 batch size 调小,每次前向反向传播占用的显存和 batch size 基本线性相关。第二类是开启梯度累积,用几个小 batch 累积梯度后再更新一次参数,相当于模拟大 batch 但省显存。第三类是检查模型和数据加载器,数据加载时经常会有意外的中间变量占住显存,减少不必要的 tensor 存储能改善不少。
训练中断的恢复依靠检查点。我的标准做法是把检查点保存到独立目录,文件名包含 epoch 和验证指标,训练脚本启动时检查有没有可恢复的检查点。恢复时要同时加载模型参数、优化器参数、当前 epoch 轮数。代码里常犯的错是只保存了模型权重没保存优化器,这会导致学习率状态混乱,恢复后模型效果退化。另外,数据加载器如果用了随机种子,恢复时最好也把随机状态存下来,否则恢复后的采样顺序会和原来不一致。
5.2 过拟合和数据泄漏
过拟合最典型的信号是训练集指标很高,验证集指标明显偏低。解决思路按性价比排序:先简化模型结构,降低模型容量;再加大正则化,比如 dropout、L2 权重衰减;最后才是增加数据量或数据增强。我反对一上来就堆数据增强,因为它会掩盖模型能力不足的真相,反而拖慢迭代速度。在固定数据规模和训练时间的约束下,模型复杂度应该和有效数据量匹配。
数据泄漏和过拟合症状相似,但根源完全不同。常见泄漏场景包括:特征里包含未来信息、切分时随机切分导致同用户样本同时出现在训练集和验证集、用全量数据做归一化统计量。排查数据泄漏的方法是人工抽查高分样本和预测错误样本:如果发现模型对某些“可疑特征”响应特别强烈,比如订单金额为 0 的样本几乎都被预测为流失,就要检查这个特征是否在真实场景中提前可知。
5.3 线上和线下指标不一致
离线评估 AUC 0.85,上线后业务指标却没提升,这种不一致几乎是每个 AI 项目的必经阶段。原因通常有三类:第一类是离线评估用的是历史数据,但线上用户行为已经漂移;第二类是离线指标和线上业务指标本来就不同维,离线 AUC 提升 0.01 不代表转化率提升;第三类是推理链路引入了偏差,比如请求特征拼接错误、缺失值默认填充不对。
排查不一致问题时,我会先做请求日志回放。把线上最近一天的真实请求日志重新灌给离线模型,对比线上预测分数和离线复现预测分数,如果两者不一致,说明推理代码和训练代码的特征处理有差异。这是最常见也最隐蔽的 bug 来源,值得在每次迭代时用一小部分线上数据做回归测试。我在项目里专门维护了一个“复盘脚本”,输入一组线上请求,自动输出特征拼接结果和预测分数,每次部署新模型前先跑一遍。
5.4 需求变动导致返工
最后说说需求变动。AI 工程项目里最贵的成本不是 GPU,而是需求反复。业务方一开始说“预测流失”,后来又说“预测高价值用户的流失概率”,再后来希望把预测结果排序并配一个解释原因,每一次需求调整都会推到特征、标注和评估指标。我的应对办法是前期把需求确认会开扎实,把数据样本和预测结果做成可视化看板,让业务方对着实例讨论,而不是对着 PPT 讨论。给业务方看 20 条真实预测样本,比讨论 2 小时指标定义都有效。
排期上我也会刻意留出缓冲:数据清洗比模型调参更花时间,标注评审比训练实验更容易延期。给业务方一个包含数据、模型、评估、上线、监控的完整排期,比只给一个模型训练完成时间靠谱得多。毕竟 AI 工程的目标不是训练出完美的模型,而是让一个 AI 能力稳定服务于业务。我在一次次返工里学会的最重要一课,就是在动手前多花两天把问题定义清楚,后面省下来的往往是以周为单位的时间。
最后再分享一个小技巧:我在所有 AI 工程项目的根目录下,都会放一个 README 文件,里面只写三件事——这个系统解决什么问题、数据从哪来、模型怎么上线和回滚。项目成员变动或者半年后再回来看代码,这个文件是最有用的导航。别小看这三行信息,它比任何架构图都更能防止项目变成“只有一个人能维护的黑盒”。
我个人在实际操作中的体会是:AI 工程从零开始,难的不是单个环节,而是把数据、模型、部署、监控串成一条能持续迭代的链路。只要这条链路是通的,哪怕第一版模型效果一般,后续也能快速迭代;反过来,如果链路残缺,再好的模型也只能躺在 notebook 里。希望这篇文章能帮你把第一条链路顺畅地搭起来。