
很多人学AI是从跑通一个Kaggle notebook开始的train完之后看着accuracy不错觉得AI好像也就这么回事。但真到公司里或者自己搭项目你会发现训练模型只是整个AI工程里最小的一块。数据源头乱七八糟实验复现不出来模型上线后表现和离线测试完全不一样这些问题才是从零开始做AI工程的真正主战场。这篇东西我想用自己实际带项目、从零搭建完整AI工程的经验讲清楚一条靠谱的路径从需求拆解、数据管道到模型训练评估再到部署监控和持续迭代。适合刚入行、需要在真实项目里把AI落地的人也适合那些已经把模型训出来了、但不知道后面怎么继续的人。1. 理解AI工程它和炼丹是两码事1.1 AI工程到底在解决什么问题我见过太多人把“训练一个模型”等同于“做一个AI项目”。训练模型当然重要但它只解决“从数据到权重”这一段。真正到了生产环境你要面对的是数据从哪来、怎么清洗、特征怎么保持一致、模型怎么部署、线上效果怎么评估、坏了怎么回滚、业务变了怎么重训。这一整套东西才是AI工程。打个比方模型像发动机AI工程是整车。发动机再猛没有变速箱、底盘、油路、仪表盘车也跑不起来。很多人从零做项目上来就盯着一台发动机改参数最后发现它根本装不上车就是因为没把工程问题想清楚。所以AI工程解决的核心问题我总结成四个词可复现、可维护、可观测、可持续迭代。可复现意味着任何人拿着代码和数据都能跑出同样的结果可维护意味着三个月后你还能看懂自己写了什么可观测意味着模型上线后你能知道它什么时候开始变差可持续迭代意味着改数据、加特征、换模型不是一次重写而是一个流水线里的正常步骤。1.2 一个完整AI工程的技术栈全景既然要做工程化脑子里先得有一张全貌图。我习惯把一个AI工程拆成五层层级解决什么问题常见选型数据层采集、清洗、存储、版本管理SQL、Parquet、DVC、Airflow特征层从原始数据生成和保存特征Pandas、Feature Store、自定义pipeline训练层训练、调参、保存模型scikit-learn、PyTorch、LightGBM、MLflow服务层把模型包成接口对外提供服务FastAPI、Flask、Triton、ONNX Runtime观测层监控指标、告警、日志Prometheus、Grafana、自定义日志这张表不用照着抄但你心里得有这个分层意识。从零开始的时候每一层不一定都要上最重的工具。我见过很多小项目一上来就上K8s、上Flink结果项目还没跑通先被基础设施淹没了。正确的做法是每一层用当前阶段最小的工具满足需求但接口和目录结构要按最终形态预留。比如你暂时不用DVC但数据文件的命名和目录逻辑也要统一后面迁移成本就低。2. 从零起步路线图与核心选型2.1 先搞清楚项目类型再动手开工之前最重要的一件事不是选框架而是确定这个AI项目到底属于哪一类。是监督学习里的分类、回归还是排序、检索还是生成式任务每一类的数据组织方式、评估指标、部署方式都不一样。我建议做一个简单分类分类问题如垃圾邮件识别、图片分类核心是类别平衡和阈值选择回归问题如销量预测、价格预估核心是误差分布和异常值处理排序问题如搜索推荐核心是相对顺序和NDCG这类指标生成式问题如文本摘要、代码生成核心是效果评估困难需要强评测集无监督/异常检测如用户分群、日志异常核心是业务先验很重。很多新手一上来就说“我要上一个深度学习模型”但其实业务需求可能用一个线性回归或者规则就解决了。AI工程不是堆模型而是选最合适的方式解决问题。我现在的习惯是先定一个最简单的baseline哪怕用规则或者统计方法把整个流程先跑通再决定要不要上更复杂的模型。2.2 框架选型为什么我建议按阶段选框架选择是每天会被问一次的问题。我的观点很直接按项目阶段选按团队熟悉度选不要为了追新而选。如果是快速验证业务scikit-learn就够特征多、表格数据为主LightGBM和XGBoost效果经常好过深度模型而且训练快、解释性强真正需要处理非结构化数据或者模型需要自定义结构才上PyTorch。PyTorch是我日常的主力训练框架但怎么上线是另一回事我不会直接把PyTorch模型塞进生产环境而是导出成ONNX或者TorchScript用专门的推理引擎跑性能和稳定性都好很多。选型还有一个原则成熟稳定优先于新潮。公司里做项目最怕的是一环新工具半年不维护或者跟上游库冲突。我看一个框架适不适合上生产会看三个指标社区活跃度、版本迭代是否平滑、回滚是否容易。实测下来PyTorch、scikit-learn、FastAPI、MLflow这套组合在中小团队里非常稳不是因为它们都是最好的而是因为它们之间配合成熟、踩坑资料多、社区里能找到真实案例。2.3 代码结构从第一天就按工程规范组织从零做AI项目目录结构是最容易被忽略、却最影响后续效率的事情。我见过把所有脚本堆在一个notebook里模型放到乱七八糟路径下的项目三个月后连作者本人都不敢再跑。这里给大家一份我常用的、从轻量到重量都适用的目录组织project/ ├── config/ # 所有配置文件YAML或JSON ├── data/ # 原始数据与加工后数据按批次分目录 ├── features/ # 特征工程代码与特征定义 ├── models/ # 模型定义、训练脚本、保存的模型文件 ├── services/ # 对外服务接口如FastAPI应用 ├── tests/ # 单元测试与数据校验 ├── scripts/ # 一键跑通全流程的shell脚本 ├── notebooks/ # 探索性分析不能进训练流水线 ├── requirements.txt / pyproject.toml └── README.md # 必须有写清怎么跑、依赖什么这份结构的关键点有两个一是配置和代码分离超参数、路径、数据库连接串都放config里改参数不用改代码二是探索代码和工程代码分离notebook只用来做分析和画图训练和推理必须写成可执行的脚本不能在notebook里点来点去。只有这样自动化才有可能。3. 数据管道与特征工程从零开始最容易翻车的部分3.1 数据清理里的几个关键细节我做过好几个项目模型效果差追根溯源都是数据清洗没做好。这里有几个坑是高频翻车点。缺失值处理不能“一招鲜”。均值填充、中位数填充、删除行看起来只是方法选择问题实际上和业务含义强相关。比如用户年龄缺失可能是因为注册时没有要求填写缺失本身可能代表“新用户”这个信息你直接填一个均值等于把信号抹掉了。我的做法是先分析缺失比例、缺失模式和相关业务解释再决定填充方式填充之后一定加一列is_missing标记让模型自己学缺失这个状态。重复样本要分场景。普通表格里去重没问题但时间序列数据里的“重复”可能是同一个用户在多个时间点的合理记录随便去重等于破坏了时序结构。我带项目时吃过这个亏把用户行为日志按user_id去重结果训练集和线上分布完全对不上。数据划分是重灾区。训练集、验证集、测试集必须按时间顺序切分尤其是有时间依赖的业务。我第一次做销量预测的时候用随机切分把未来数据混进了训练集离线指标漂亮得不行上线直接崩。这个教训让我养成了一个习惯所有涉及时间的数据先看时间戳再决定怎么切分。3.2 特征工程好的特征胜过好模型特征工程的意义怎么强调都不过分。同样的模型特征做得好效果能差好几个百分点。我常用的特征类型和处理方式是这样数值特征先做缺失处理再做标准化或者分箱。标准化特别注意拟合对象只有训练集能fit验证集和测试集只能transform用的参数必须是从训练集学到的。类别特征低基数用One-Hot高基数用目标编码或者哈希。用目标编码一定要注意防止标签泄漏必须在训练集内做交叉验证式编码。时间特征从时间戳提取星期几、是否节假日、距上一个事件的间隔等这些对很多业务预测帮助巨大。文本特征简单场景用TF-IDF复杂场景才用Embedding。我提一个很实用的建议每生成一个特征就在旁边写清楚它的含义、来源和计算逻辑。别觉得这是形式主义三个月后你会回来感谢自己。我见过多少人复用旧数据的时候完全想不起来某个特征是怎么算出来的最后只能重新推演浪费时间还容易出错。3.3 数据就是资产数据版本必须管理模型可复现性很大程度取决于数据可复现性。你不仅要能回答“这个模型是哪份代码训练的”还要能回答“这个模型是用哪些数据训练的”。只放在云端随便一个目录里肯定不行。轻量方案是给每份数据集算一个hash值训练的时候把hash记录到实验日志里。标准方案是用DVC这类数据版本管理工具它可以像Git管理代码一样管理数据文件切换分支就能切换对应版本。我实操的体会是小团队一开始不必上太重的东西但至少要建立一个“数据快照目录”的习惯例如data/20240115_v3/这样命名然后在一个data_manifest.csv里记录每份数据的路径、hash、生成时间。这样做的好处在排查线上问题的时候尤其明显。模型效果变差了你首先能确认是不是因为训练数据跟当前线上数据的分布差异太大。如果没有数据版本记录排查第一步就卡死。4. 模型训练与评估把实验变成工程资产4.1 训练脚本的工程化设计训练阶段最容易犯的错是“一把梭”所有参数写死在代码里跑一次实验改一堆地方。工程化的做法是把每次训练变成一个可重放的独立过程。我的训练脚本通常包含四件事读取配置、设置随机种子、执行训练、保存产物。配置用YAML管理大概长这样model: name: lgbm_classifier params: max_depth: 7 learning_rate: 0.03 n_estimators: 500 data: train_path: data/20240115_v3/train.parquet val_path: data/20240115_v3/val.parquet seed: 42 output_dir: artifacts/run_20240115_1030/代码里统一用seed控制全局随机性每个框架都要设不只是设定一次。PyTorch要设置torch.manual_seed和torch.cuda.manual_seed_all还要在DataLoader里设置generator。不然你会发现同样的代码每次跑出来的结果不一样实验对比毫无意义。每次训练生成的模型、训练日志、配置文件副本都应该原样保存到一个独立的output_dir里。这样你以后看任何一个实验目录就能完整复现当时的一切。我自己踩过的坑是只保存模型权重没保存预处理器的参数结果推理的时候数据标准化用的还是旧的均值和方差模型效果直接打了折扣。4.2 评估指标正确比花哨更重要模型评估最大的坑就是只看准确率。二分类问题里如果正样本只占5%你全预测成负样本准确率也有95%但这个模型毫无价值。我自己在做风控类项目时深有体会必须盯着precision、recall和F1而且要根据业务代价选一个侧重方向。比如误杀用户代价高就提高precision漏掉风险代价高就提高recall。不同任务类型的指标选择我一般这样定任务类型常用指标注意点二分类Accuracy、Precision、Recall、F1、AUC不平衡时优先关注PR曲线多分类Macro/Micro F1、混淆矩阵小类别的单独指标必须看回归MAE、RMSE、MAPE先看误差分布别只看均值排序NDCG、MRR关注相对顺序不是绝对分数生成式BLEU、ROUGE、人工评测自动指标和人工判断可能明显不一致还有一个容易被忽略的点指标阈值不是模型训出来的是业务权衡出来的。分类模型输出的是概率最终怎么划分类别取决于业务上precision/recall的取舍。我会在模型上线前画一张阈值曲线和业务方一起定阈值而不是默认用0.5。4.3 实验记录给每一个模型建立档案实验记录这件事很多人觉得麻烦但它是AI工程里最值得投入的事情之一。原因很简单模型迭代是常态不做记录你没法知道A模型比B模型好在哪、为什么好。团队规模小的时候用一个固定的日志格式就够了。我会给每个实验生成一行JSON或者CSV记录包含实验名、时间、数据版本、特征版本、超参数、指标结果、模型产物路径、备注。后面上了MLflow也只是把同一套信息存得更结构化而已。关键不是工具而是**“每次实验必须有完整档案”这个纪律**。我还会给实验命名建立规范比如[项目]_[模型]_[数据版本]_[第几次]像sales_lgbm_v3_002。命名规范的好处是排序清晰找实验的时候一眼就能按项目、版本、轮次定位不用打开日志一张张翻。这个习惯帮我省了非常多时间。5. 模型部署与服务化这是整个流程真正的分水岭5.1 从训练到推理模型导出不只是换个格式模型上线的第一步是把训练好的模型导出成适合推理的格式。这一步很多人以为是在做文件格式转换其实它是在做“训练环境”和“生产环境”的解耦。我经常用的方案是PyTorch模型导出成ONNXLightGBM和sklearn模型用原模型文件加joblib或pickle保存。关键点是你必须同时保存配套的预处理逻辑包括标准化器的均值、方差、特征列顺序、类别编码映射。这些信息在训练代码里是隐式的但在推理服务里是显式依赖。在线推理服务有个容易被忽略的问题特征顺序必须和训练时完全一致。我用Pandas处理数据时列顺序会因为多种原因变化如果不显式固定特征列表推理服务上模型可能拿到乱序的输入结果全错。我的做法是训练时就把特征列名保存成一个feature_names.json推理时按这个顺序重新取列。5.2 推理接口用FastAPI快速搭建服务部署模型的接口设计我不追求花哨框架FastAPI是实测最稳的选项之一原因是自带请求校验、异步支持和自动文档。一个简单的模型推理服务核心逻辑并不复杂from fastapi import FastAPI from pydantic import BaseModel import joblib import pandas as pd app FastAPI() model joblib.load(models/lgbm/model.joblib) preprocessor joblib.load(models/lgbm/preprocessor.joblib) class PredictRequest(BaseModel): feature_1: float feature_2: float # ... 按特征列表逐个声明 app.post(/predict) def predict(req: PredictRequest): # 按训练时的特征顺序组装 DataFrame raw pd.DataFrame([req.dict()])[feature_columns] processed preprocessor.transform(raw) prob model.predict_proba(processed)[0, 1] return {probability: prob}这里我想强调一个细节线上推理的特征组装逻辑必须和训练时的特征组装逻辑是同一套。最稳妥的办法是把特征工程代码抽成公共函数或独立的包训练和推理都引用它。我自己早期是训练和推理各写了一份处理逻辑后来对不上排查花了整整两天教训非常深刻。接口层还要考虑输入校验和异常处理。不能用户传了一个负数、一个空值服务就直接500。要做明确的参数边界校验返回给调用方可读的错误信息。生产环境里还要加请求日志和延迟监控不然线上出问题你连现场都没有。5.3 性能、监控与模型更新上线只是开始模型上线之后真正的AI工程工作才刚开始。如果不做监控你不知道模型什么时候开始变差。我常用的监控指标是这几个推理延迟、QPS、错误率这些反映服务是否健康输入特征分布比如某个特征的均值突然漂移说明线上数据在变化预测结果分布比如模型输出的平均值突然升高可能是业务环境变了人工反馈数据比如用户投诉率、审核通过率这是最接近真实效果的信号。模型更新也需要一套流程。我不建议一有新数据就立刻重训并全量上线稳妥做法是先走影子模式让新模型和老模型同时跑一段时间比较预测结果和真实表现再决定切流量。复杂一点的团队可以做A/B测试和灰度发布但小团队至少要保留“旧模型随时可回滚”的能力。我会在服务里保存最近两到三个版本的模型文件并给接口加一个model_version参数线上出问题能秒切回旧版本。6. 我踩过的坑与排查速查表6.1 五个高频踩坑现场第一个坑是训练和推理的预处理不一致。训练时你做了标准化、删了缺失值、转了编码线上推理服务里却没有这些步骤或者顺序变了出来的结果自然是错的。这个问题最难查因为模型不会报错只会给出奇怪的结果。第二个坑是数据泄漏。常见形式有三种用全局统计量填充缺失值导致训练集和验证集信息重叠特征里包含了未来信息比如用当天全量数据统计出均值再预测当天随机切分时间序列数据把未来样本放进了训练集。每次模型离线表现好得反常的时候我第一反应就是查有没有泄漏。第三个坑是超参数和随机种子没记录。跑了几十次实验最后回看时完全不知道哪个配置得出的结果。这个问题等到你要写论文或者做项目复盘的时候会特别痛苦。解决方式就是我前面说的每次实验的配置和产物放同一目录养成习惯。第四个坑是忽略类别不平衡。有些场景正负样本比例可能到1:100直接训模型模型会学会“全部预测为负类”。处理方式包括重采样、调整样本权重、换评估指标、设计适合不平衡场景的训练目标。我一般先做方案评估再考虑上采样或者下采样。第五个坑是环境依赖混乱。AI项目牵扯的库特别多numpy、pandas、torch、sklearn版本稍微不一样结果就可能出现偏差。现在我所有项目都用虚拟环境管理依赖并且把requirements.txt连同实验产物一起保存。这个习惯让我从“在我机器上明明能跑”的泥潭里挣脱了出来。6.2 排查速查表现象可能原因排查思路训练时指标很高线上明显变差数据泄漏、特征不一致、线上分布漂移先查特征顺序和处理逻辑再对比训练与线上数据分布模型输出全是同一个值预处理后特征全为常量、激活函数饱和、类别严重不平衡打印推理预处理后的特征分布看是否有全零或全相同每次训练结果都不一样随机种子设置不全面、多卡并行未固定检查所有随机源包括DataLoader、shuffle、CUDA模型加载后推理报错依赖版本不一致、模型文件损坏对比训练和推理环境版本确认模型文件完整性预测概率整体偏高/偏低训练样本分布变化、阈值设置错误看训练集的label分布重新校准概率或阈值新数据来了模型不更新缺少自动重训触发机制检查数据管道是否正常产出模型更新流程是否被阻塞这张表不能解决所有问题但它是我排查问题的起点。真正定位问题的过程还是从“最近改了什么”开始查这个习惯比任何工具都管用。最后分享一点个人体会。做AI工程和做算法研究不一样它的成就感不是来自某个模型精度刷了零点几而是来自整套系统稳定、可靠、可迭代地跑起来。我建议所有刚开始做AI工程的人先跑通一个最小闭环一份小数据、一个简单模型、一个推理接口、一个监控日志先不求大而全让整个链路从零到一立起来。后面每次只改进一个环节这套系统就会越来越稳。再分享一个小技巧在项目一开始就写一个run_all.sh脚本一条命令完成数据预处理、训练、评估、启动服务全流程。“一键复现”这四个字会在你项目做到第三个月的时候成为你最感谢自己的决定。