很多人以为AI工程就是从训练一个模型开始的但真正从零把一套AI系统落到线上最花时间的反而是模型之外的那一圈东西。我过去几年接过不少“算法在手、上线发抖”的项目模型在Notebook里跑得挺好到了生产环境就处处卡壳——数据延迟、特征不一致、推理资源吃紧、评估口径对不上。这篇文章就是围绕“ai-engineering-from-scratch”这套思路把我从空项目到部署上线的完整路径拆开讲清楚。适合正在从算法脚本走向工程化、或者打算独立搭一套AI服务的开发者参考。1. AI工程化到底在解决什么问题先说一个最容易踩的误区AI工程不等于调参炼丹。调参只是中间的很小一环真正衡量一个AI项目能不能落地的是数据链路通不通、训练流程能不能重现、模型上线之后能不能稳定跑。所谓“from scratch”我的理解是抛开现成的平台封装从代码、数据、部署环境这一层开始亲手把所有环节搭起来。工程化的本质是把“能跑通的代码”变成“能稳定运行的系统”。这中间涉及数据版本管理、特征一致性校验、模型实验追踪、服务化封装、监控告警、模型更新策略。任何一个环节缺失都会在某个深夜变成事故。拿一个最常见的场景举例你今天在Notebook里训练了一个分类模型准确率90%高高兴兴把模型文件交给后端同学部署。然后后端问你要特征工程代码你说在Notebook里他又问数据清洗逻辑你说在另一个脚本里再问线上推理时缺失值怎么处理你发现当时是手动填的。这就是典型的“从脚本到工程”的鸿沟。解决这个鸿沟需要建立三个基本意识数据是一等公民模型只是数据的消费者。数据管道不稳定模型再强也白搭。训练环境要可复现。今天能跑通不算数三个月后还能跑通才算数。上线只是开始。监控、回滚、重训才是长期运营的核心。2. 技术栈选型从零搭起一套AI工程基座技术栈这件事没有绝对最优但有一个原则在你团队熟悉度的范围内选择生态成熟、资料多、坑有前人踩过的组合。我从零搭建时优先考虑的不是“最新最酷”而是“能找到人问、能查到文档、能稳定迭代”。2.1 语言与基础框架怎么定Python依然是AI工程的主语言这不只是偏好而是生态决定。数据处理有pandas、polars训练有PyTorch、TensorFlow、LightGBM服务化有FastAPI、Flask调度有Airflow、Prefect几乎整个AI工具链都用Python串起来。但要注意一个容易踩的坑Python在训练阶段的便利性会在线上推理阶段变成负担。所以工程上我习惯做“开发用Python、推理看场景”的分离策略。如果模型是深度学习模型优先用PyTorch导出TorchScript或ONNX如果是树模型用ONNX或者原生C推理接口。线上服务不一定非要用Python用Go、Java甚至可以扛更高并发但调试成本会高。个人项目的初期阶段整个服务都用PythonFastAPI就够了等到QPS真的上来了再考虑拆推理服务。2.2 数据管道与存储选什么数据管道是整个AI工程最容易拖后腿的部分。很多项目挂在“我有一堆日志数据想训练一个模型”但日志数据上不了台面——没有清晰的schema、没有统一的ID、时间字段全是字符串。工程上建议直接把数据往“离线数仓在线缓存”两套体系做离线侧Parquet文件或数据湖如Iceberg、Hudi用于批量训练和回测。在线侧Redis或键值存储用于实时特征查询。调度侧Airflow或Prefect管理每日/每小时的刷新任务。存储选型我直接给一张对照表新手照着选就行需求推荐方案理由小数据量GB级PostgreSQL Parquet文件简单直接能应付大多数入门项目中数据量TB级ClickHouse / Doris分析性能强支持实时导入特征在线存取Redis读写快TTL机制天然适合特征过期实验数据追踪MLflow / Weights Biases记录参数、指标、模型文件有一点务必要记住训练数据和生产特征必须来源一致。否则模型离线指标和线上表现永远对不上。我现在所有项目都强制“同一套特征代码跑离线批量与在线实时”两种模式这是工程化最重要的实践之一。2.3 模型训练与推理框架怎么定训练框架的选型核心是社区活跃度和部署友好度。我的建议个人项目或中小团队直接用PyTorch生态最完整从NLP到CV到多模态都有现成模型可以用纯结构化数据场景用LightGBM或XGBoost简单高效特征工程空间也更大。推理框架则是另一套逻辑。训练阶段你用的是GPU集群推理阶段可能只是一台2核4G的小机器。所以模型训练完不是终点还要做一次“部署前的减肥”模型剪枝去掉不影响精度的参数。量化从FP32降到FP16或INT8。蒸馏用大模型蒸馏小模型保留效果但大幅压缩体积。导出格式PyTorch模型转ONNX跨语言、跨设备部署都很方便。经验数据供参考一个BERT-base模型直接部署要占几百MB显存做完INT8量化加裁剪体积可以压到原来的四分之一以内推理延迟下降一半以上精度损失通常控制在1%以内。这个性价比非常高。3. 核心实操数据、训练、评估全流程落地有了技术栈和大方向接下来就是动手环节。我按照自己项目里真实落地的顺序把数据清洗、训练管理、评估定标这三步完整过一遍。3.1 数据清洗与特征工程数据清洗看起来不起眼却决定算法工程师和AI工程师的分水岭。算法手里拿的是干净表格工程手里拿的是原始日志。从原始数据到可训练样本至少有这么几件事要做字段类型校正字符串日期统一转时间戳。缺失值统计与填充策略建模不是简单dropna。异常值处理例如用户行为时长小于0、金额超过业务上限。去重逻辑同一用户重复曝光如何处理。数据切分安全防止标签泄漏。特征工程这部分很多人喜欢堆几百个特征我不建议。特征不是越多越好而是越稳越好。线上推理的时候一个特征获取失败就可能导致整条请求报错。我踩过最大的坑是离线训练时某个特征取的是当天的聚合值线上推理时这个特征根本还没生成导致线上分数全部偏移。这件事的解法是“特征时间对齐”。训练样本里每个特征的时间戳必须早于标签时间线上推理时使用相同的时间窗口逻辑。做特征清洗时我习惯对每一个特征记录下来三个信息特征来源表、计算逻辑、生成时点。这张特征血统表Feature Lineage是后面排查线上问题的重要依据。示例特征清洗代码用一个简单的过滤加标准化操作展示思路import pandas as pd from datetime import datetime def clean_raw_data(df: pd.DataFrame) - pd.DataFrame: # 日期统一 df[event_time] pd.to_datetime(df[event_time], unitms) # 合法范围过滤 df df[(df[amount] 0) (df[amount] 10000)] # 缺失处理连续特征填列位均值类别特征新增unknown df[category] df[category].fillna(unknown) df[amount] df.groupby(category)[amount].transform(lambda x: x.fillna(x.mean())) # 切分防泄漏只保留事件发生在标签时间之前的数据 df df[df[event_time] df[label_time]] return df.drop_duplicates(subset[user_id, event_id], keeplast)这段代码没什么高深算法但每一行背后都是一个生产环境教训。比如最后一行去重如果不做模型训练时同一用户可能同时出现在训练集和验证集造成指标虚高。3.2 训练脚本与实验管理训练脚本不是把Notebook整理成.py文件就结束了。工程化训练脚本的核心是“可配置化”和“可追溯性”。我看到太多项目的训练脚本里硬编码了一堆超参数每次改batch size都要打开源文件改然后重新跑。这个习惯在工程上必须改。推荐做法是使用配置文件统一管理参数或者用命令行参数注入。最简单的做法就是yaml加argparse# config/train_config.yaml model: name: xgb_classifier params: max_depth: 6 learning_rate: 0.05 n_estimators: 500 data: train_path: ./data/train.parquet valid_path: ./data/valid.parquet feature_cols: [amount, category_encoded, hour_of_day] label_col: is_click train: seed: 42 early_stopping_rounds: 50训练代码用配置文件读取import yaml import argparse import mlflow from xgboost import XGBClassifier def load_config(path: str): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def train(cfg): # 读取数据... model XGBClassifier(**cfg[model][params]) model.fit(X_train, y_train, early_stopping_roundscfg[train][early_stopping_rounds]) return model if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--config, defaultconfig/train_config.yaml) args parser.parse_args() cfg load_config(args.config) with mlflow.start_run(): model train(cfg) mlflow.log_params(cfg[model][params]) mlflow.sklearn.log_model(model, model)把参数放进配置文件之后好处是很明显的实验复现只需要存一份yaml快照超参数搜索只用改配置文件循环调参不需要切来切去改代码。实验管理这块我强烈建议从第一天就用MLflow。不用只盯着模型参数数据版本、代码commit号、训练时长、当时用的GPU型号都记录下来。这样三个月后回溯问题时有据可查。3.3 评估指标与模型选型评估这件事比大多数人想得复杂。很多团队默认用accuracy但对不平衡数据来说是坑。我的建议是先做业务定义再选评估指标。比如用户点击预测真实业务是“在有限资源下多找到可能点击的人”所以更合理的指标是PrecisionK或者RecallK而不是全局准确率。做工程评估我习惯分三层离线评估在历史数据上算指标用交叉验证或时间序列切分。影子评估模型上线后不真实生效把预测结果写入日志和线上行为做对照。A/B评估小流量真实分流对比。这三层缺一不可。离线评估是基线影子评估查BugA/B评估定效果。跳过影子评估直接上A/B是我见到的家常便饭结果经常是模型分数不对、特征取错、类别标签偏了白白跑了一周流量。模型选型同样不是看排行榜而是看约束条件。如果线上推理机只有CPU就别选大Transformer如果可接受每周重训一次那LightGBM可能比神经网络更实用。给大家一个选型建议表场景推荐模型原因结构化表格数据LightGBM / XGBoost训练快、特征工程友好、部署简单文本分类/NER小型BERT蒸馏模型效果好量化后能上CPU图像识别ResNet系 / MobileNet部署生态成熟推理库丰富序列预测LSTM / Transformer建模时序依赖但推理资源要跟上4. 部署与上线让模型真正跑起来训练和评估只是前半程真正让人头秃的多半是后半程模型服务化、资源管理、监控告警。4.1 用FastAPI把模型封装成服务模型部署第一步是写一个推理服务。FastAPI是我目前的主力方案原因有三个异步支持好、自带OpenAPI文档、类型校验方便。下面这个示例是一个最简可用的推理服务from fastapi import FastAPI from pydantic import BaseModel import joblib import numpy as np app FastAPI() model joblib.load(./models/xgb_model.joblib) class PredictRequest(BaseModel): feature_vector: list[float] class PredictResponse(BaseModel): prediction: float probability: float app.post(/predict, response_modelPredictResponse) def predict(req: PredictRequest): x np.array(req.feature_vector).reshape(1, -1) prob model.predict_proba(x)[0][1] pred int(prob 0.5) return PredictResponse(predictionpred, probabilityround(float(prob), 4))这个示例看起来很简单但工程上要补充几个关键点模型加载不能放在热路径里要启动时加载一次。请求特征要校验长度和类型不能指望模型自己兜底。响应里最好带模型版本号方便问题排查。4.2 容器化部署与资源管理容器化是AI工程绕不开的一步。没有人愿意在生产环境里重新安装xvfb、cuda、libstdc这些底层依赖。Docker镜像一旦构建好环境就锁定了。我常用的镜像设计分两层基础层装系统依赖和CUDA项目层装Python包和代码。这样每次代码更新只需重新构建项目层基础层缓存在CI里构建速度能快很多。一个基本功提示镜像里不要包含训练数据也不要把数据集COPY进去。数据通过外部存储挂载模型文件可以通过启动命令从模型仓库自动下载。这样镜像体积小、传输快、也更安全。给一份参考Dockerfile片段FROM pytorch/pytorch:2.1.0-cuda11.8-cudnn8-runtime WORKDIR /app RUN pip install --no-cache-dir fastapi uvicorn joblib scikit-learn pandas COPY ./api /app/api COPY ./artifacts /app/artifacts EXPOSE 8000 CMD [uvicorn, api.main:app, --host, 0.0.0.0, --port, 8000, --workers, 2]不要在单容器内启动多个worker跑CPU密集的模型推理那样很容易耗尽CPU。我当时遇到过容器OOMKilled的问题排查后发现是因为在2C4G的容器里启动8个worker内存全部爆掉。正确的做法是根据资源规格计算worker数或者用消息队列削峰比如把请求写入Redis队列由消费端异步处理。4.3 监控与自动重训监控不是等报警了才做。我在线上一共放三个监控面板业务指标、预测分布、与基础设施饱和度。其中“预测分布监控”最有价值。如果模型上线后预测均值突然从0.3跳到0.7大概率不是业务变了而是特征管道出问题了。对比预测分布能提前发现特征漂移和样本分布偏移。实践方法很简单每小时对预测结果做一次统计均值、分位数画成趋势线一旦偏离历史均值超过阈值就告警。自动重训则是另一个话题。不要盲目时间触发重训重训前先看三件事离线评估是否达标。特征管道是否仍然可用。数据漂移指标是否触发。只有在条件都满足时才跑训练任务重训完成后自动做回归测试对比新旧模型在一组固定的历史样本上的指标差异。指标下降就自动回滚旧模型指标上升就发布新模型。这个流程可以用Python脚本加CI/CR工具跑成定时任务。5. 踩坑实录与工程化经验最后一部分我把这几年从零搭AI工程踩过的坑整理成速查表再分享几条个人觉得最有价值的工程经验。5.1 常见问题速查表现象可能原因处理方式线上效果远差于离线特征泄漏或数据不一致检查训练/推理特征口径是否完全一致调用推理服务时偶发超时模型加载没有预热服务启动后主动做一次推理预热内存持续上涨每次请求都在加载模型模型只加载一次放在进程级全局变量模型指标漂移数据分布发生变化做数据监控设置漂移告警阈值训练任务跑挂但不报错OOM被自动杀掉查看dmesg日志限制worker数CPU利用率不到10%推理被IO阻塞用异步IO、批量推理或换更合适的后端这张表里最想强调还是训练与线上特征一致性问题至少一半的AI事故都源自这一点。建议用一句话总结项目规范离线训练只能消费线上数据库存在的字段任何线上取不到的特征都不允许进模型。5.2 我个人的三条核心经验第一先把数据管道搭扎实再去碰模型调参。数据不可靠时所有准确率都是一种自我安慰。我曾经花三周调参效果纹丝不动最后发现训练集和验证集之间有重复用户清理后指标直接涨停。这个教训的价值远高于任何调参技巧。第二把部署当一等公民。很多人把部署放在最后一步这是顺序上的错误。从项目第一天起就应该准备好推理接口和测试集。每个训练好的模型都能立刻跑服务立刻跑回归测试这样迭代周期会大幅缩短。不要把“部署”变成只有新模型发布时才做的事。第三日志是最重要的AI基础设施。训练日志、数据质量日志、推理日志、特征日志都要结构化存储。日志的关键定位在于白盒化。模型出问题时没有这些日志你只能靠猜。我把每一次预测请求的模型版本、特征值hash、输出概率、响应耗时全部记录下来排查线上缺陷的效率提升了至少一个数量级。还有个细节值得提一下版本管理模型文件时不要只用V2、V3这种文件夹名。模型文件命名最好带上训练时间、数据版本、评估指标比如model_xgb_20250115_data_v3_auc0.882.joblib一眼就能看出模型来历。别看这个细节不起眼线上模型回滚时能省掉一大半的心力。这些经验总结下来就是一句话AI工程化没有魔法就是把每一个不确定性变成确定性。数据来源确定、特征口径确定、训练流程确定、部署路径确定、监控项确定剩下的只是执行。谁先完成这个“确定过程”谁的项目就能先稳定运行起来。