
做AI工程这个方向一年多我踩过的坑比写过的模型还多。ai-engineering-from-scratch这个项目最初只是我给自己定的一份学习路线图用来搞清楚一件事不靠读博、不靠系统班一个人能不能从零开始把AI工程的核心能力完整地拉起来。后来这份笔记越写越厚沉淀成了现在这套从环境搭建到线上监控、从算法原理到API设计都有覆盖的项目文档。这段时间的实践让我明确了一个判断——AI工程这词听起来唬人但真正要解决的问题其实非常具体怎么把训练好的模型当成一个稳定好用的服务交出去怎么处理数据漂移怎么应对每次迭代都不完全一致的效果复现。这篇文章我就以这份项目为线索把从零起步时需要具备的技术栈、实操路径、避坑经验完整梳理一遍覆盖AI工程师与算法工程师的分工、常用工具选型、数据处理与训练部署全链路以及我在实际搭建过程中反复踩过的坑。无论你是想转行做AI工程还是已经在做后端正想往前走一步这篇文章应该能帮你省掉不少摸索时间。刚开始我会被“AI工程”这四个字劝退觉得这里面必然涉及大量的高深算法推导前路漫漫。但真正上手之后才发现工程化的核心障碍反而不在算法本身而在于那些算法之外的琐碎问题环境依赖冲突、数据接口不一致、模型版本混乱、上线之后效果衰减却找不到原因。所有这些问题都没有什么炫酷的解决方案靠的只是一套扎实的工程习惯。所以这套从零整理的知识框架解决的核心痛点就是算法能跑不重要稳定能跑、随时能复现、出问题能定位才重要。1. 项目定位搞清楚AI工程究竟在做什么1.1 先分清AI工程师和算法工程师这是这个项目最开头部分的内容也是我认为决策价值最高的一部分。很多准备入行的人会把算法工程师和AI工程师混为一谈但这俩在实际工作中干的活差异极大。算法工程师的核心任务是模型创新和调优他们关心的是精度指标能不能再涨两个点损失函数是否收敛训练策略要不要调整。而AI工程师的核心任务是把一个已经验证可行的模型变成稳定可靠的线上服务。换句话说算法工程师是在实验室里做出一台“样机”AI工程师则要想办法把这台样机变成可以量产、可以维护、出故障能快速修复的产品。我见过太多从后端转过来的人一上来就开始猛啃Transformer和BERT理由是不懂模型算法就干不了AI工程。其实恰恰相反AI工程岗用到的基础算法知识远比想象中浅真正占工作量的是数据处理管道、服务封装、性能调优、监控告警这一整套“脏活累活”。这个理解调整之后学习路径就完全不一样了不需要从高数重新开始而是直接奔着工程化的技能点去补。1.2 什么人适合从这份项目入手这个项目的定位是给两类人准备的。第一类是后端工程师或全栈工程师工作里已经开始接触AI功能背着层层的沟通成本和技术黑盒想知道模型服务到底是怎么建起来的第二类是算法工程师的“外围队友”可能需要负责模型上线、特征平台或者推理优化对训练侧有了解但工程体系不完整。还有一类人其实也很适合参考这个思路打算独立做AI应用或者搞点副业小产品的开发者。市面上现成的AI接口足够多但真正要把这些接口集成进产品、控制成本、保证稳定依然需要一套完整的工程思维。这套项目正好把这些抽象需求拆成了具体模块。1.3 总体路线设计为什么从工程链路切入而不是从算法切入项目早期的路线设计和市面大多数教程不一样。市面上很多课程是模型驱动的会花很大篇幅讲CNN、RNN、Transformer的结构演进讲各种Loss函数的数学原理然后才在最后草草带一下部署。这个项目的设计正好反着来——从数据的接入和存储开始经过特征处理、训练、评估、打包、上线、监控、迭代把这一条完整的生产链路先搭出来在链路中按需补算法知识。这样做有一个明显的好处学习反馈极快。我每完成一个环节都能看到实际效果比如写完数据校验模块立刻能看到脏数据导致的线上精度下降问题被拦住了完成推理性能优化后延迟从200毫秒降到30毫秒。这种即时反馈对学习的持续性有很强的支撑作用。而且这种链路视角正是实际工作中最缺的很多团队不是某个环节能力弱而是环节之间的衔接经常断裂打通全链路的思维可以帮助发现那些最常见的系统性问题。2. 技术栈选型从零开始怎么挑工具2.1 核心语言选择Python是绝对主力整个项目我几乎全部用Python实现这一点估计没人有异议。Python在AI领域的学习资料最多、框架封装最完善更重要的是不管做模型训练的PyTorch做特征处理的Pandas还是做服务部署的FastAPI全部在一个语言生态内沟通成本极低。但这里有个容易忽略的点Python能应付大多数业务场景但性能敏感的高并发推理服务不能全指望Python裸跑。所以在工程实践中我建议补一个门Java或Go至少要理解线程模型、连接池、内存管理的基本概念。真正高吞吐的场景可以用C做推理引擎外面用Python做逻辑编排和控制面这种混合架构在很多生产系统里都能见到。为了便于展开我梳理了一份技术栈对照表覆盖从数据到运维的主要环节环节推荐工具替代方案选择理由数据存储PostgreSQL RedisMongoDB / ClickHouse关系型库适合业务元数据Redis做在线特征缓存特征处理Pandas / PolarsSpark数据量大时单机处理简单直接数据量上来再上分布式模型训练PyTorchTensorFlow / JAX生态最活跃社区资源多调试友好模型部署FastAPI DockerTriton / TorchServe大规模时FastAPI轻量易写适合快速验证和中小规模上线任务调度Apache AirflowPrefect / Dagster生态成熟数据管道定时调度基本标配监控告警Prometheus Grafana云厂商自带方案模型指标和系统指标统一收口可视化直观CI/CDGitLab CIGitHub Actions / Jenkins和代码仓库集成紧密流水线配起来顺手这套选型的核心思路是先用最简单直接的方案跑通闭环瓶颈明确了再上重型组件。2.2 离线部分数据处理和分析工具链数据环节是整个AI工程最容易被低估的部分。很多人以为数据工作就是把CSV读进来扔给模型完事。实际操作中数据需要清洗、去重、格式标准化、异常值处理还要应对数据源合并、字段对齐、样本时间窗口不匹配等复杂场景。项目里我用的主力是Pandas和Polars。Pandas大家都熟功能全但数据量大了会明显变慢。Polars是后起之秀底层的Arrow格式多线程处理处理效率高内存占用也低不少。一个7GB左右的表Pandas做复杂聚合可能直接卡死Polars基本上几秒到十几秒能跑出来。日常原型验证用Pandas需要性能的时候切成Polars接口设计接近切换成本不高。写数据处理逻辑的时候有个重要原则必须守住每一段清洗逻辑都要可追溯、可复现。比如从原始日志里提取用户行为序列正则匹配规则、时间窗口划分参数、字段截断策略全部要固化下来形成配置文件或独立函数。项目做到后期真正让人头痛的不是清洗写不出来而是当初究竟是怎么清洗的已无从查起。2.3 模型训练部分PyTorch为主模型训练框架方面项目选择了PyTorch核心原因有三个动态图机制适合研究和快速迭代断点调试体验好生态工具链完善无论是HuggingFace的Transformers还是常见目标检测库基本都优先支持PyTorch社区活跃度高遇到问题搜索一下几乎都有对应答案。训练部分的工程化也包括一些容易遗忘的点。实验记录是第一个要养成的习惯每次训练至少记录数据版本、代码commit号、超参数、随机种子、对应的精度指标。这个习惯能直接解决很多实验效果忽好忽坏的问题。其次学习率调度和早停机制也要提前设计好别一股脑跑满固定epoch这样既费算力又容易过拟合。早停的判定要结合验证集指标的震荡幅度设置合理阈值不然指标稍微波动一下就停掉白白浪费训练进度。2.4 上线部署部分FastAPI作为主力服务框架服务层面项目选择了FastAPI。它的优缺点都很鲜明优点是异步性能好、自动生成OpenAPI文档、类型校验天然集成Pydantic上手和调试都很舒服。缺点是生态比Flask还年轻一些插件没那么多极端压测下不如纯C网关稳定。小规模并发场景下FastAPI足够可靠我用它做过一个在线文本分类服务QPS在几百这个量级完全无压力。要注意的地方是模型加载和推理不要在请求处理函数里同步执行应该在服务启动时把模型加载到内存请求来时只做前向推理。另外推理逻辑最好独立封装成模块输入数据的预处理和后处理放在接口层外面这样模型替换时不会牵动服务代码。有一个设计细节值得单独说明模型推理结果的结构化输出。比如文本分类除了返回类别标签最好也返回各标签的概率分布推荐系统返回命中列表的同时也把召回和排序阶段的得分带出来。这些细粒度的输出不仅方便排查问题对于后续做模型效果分析和数据回流也是必要的。部署方式采用Docker容器化将模型文件和推理代码打进同一镜像部署时通过环境变量控制模型版本和运行参数。这种方式保证了开发环境和线上环境的高度一致性也能少踩很多环境类问题。3. 实操过程从零搭建一套可运行的AI工程基线3.1 环境准备与项目结构设计项目的第一步是把基础环境弄干净。我建议用Python 3.11配合venv或uv做虚拟环境管理。这里要提醒一句直接装在系统Python里是最容易出问题的做法依赖冲突几乎无法避免后期一换库版本就是一场灾难。虚拟环境把每个项目的依赖隔离开才能安心调试。推荐直接用poetry或uv管理依赖起一个新项目的标准流程大致是mkdir ai-engineering-from-scratch cd ai-engineering-from-scratch uv init uv add fastapi uvicorn[standard] pydantic pandas polars scikit-learn torch --resolvers web依赖管理工具选一个用熟就行不必反复切换。我在项目中更偏爱uv因为它解析依赖的速度极快也支持从工具源或者本地路径安装。项目目录结构我按模块职能拆得很明确ai-engineering-from-scratch/ ├── configs/ # 全局配置数据源配置、模型参数、部署参数 ├── data/ │ ├── raw/ # 原始数据尽量只读不写 │ └── processed/ # 清洗后的数据定期快照备份 ├── features/ # 特征工程特征生成、归一化、拼接逻辑 ├── models/ # 训练代码、模型定义、实验配置 ├── services/ # API服务、推理封装、业务逻辑 ├── monitors/ # 监控脚本、指标上报、告警规则 ├── tests/ # 单元测试与集成测试 └── scripts/ # 运维脚本、数据回刷脚本、调度触发结构到位之后后续每一个环节的增改都有了明确归属不用每次翻找代码文件。尤其建议把原始数据目录设成只读权限这样数据不会被无意污染清洗和回刷都通过独立脚本进行过程留痕。3.2 数据处理管道从原始日志到结构化特征数据管道是建模前的关键环节一个典型场景可以很好地说明问题。假设要做用户行为预测原始数据是不停追加的App操作日志日志里包含设备编码、操作时间、页面停留、点击位置等字段。第一步要做数据体检看数据量、空值率、枚举分布确认样本质量。清洗的关键步骤包括时间戳统一化把不同时区、不同格式的时间统一转成ISO 8601标准处理夏令时和时区偏移要用库实现别自己写时间逻辑实体ID归一化同一用户有多种标识渠道需要维护ID映射表避免一个user_id出现多个版本缺失值处理先看缺失原因再做填充不要无脑填0或者填均值。有些字段缺失本身可能就是重要特征去重策略同一用户短时间内的重复行为只保留首条或末条取决于预测目标定义数据清洗完成之后特征工程紧接着展开。特征分两类离线批量特征和在线实时特征。离线特征如过去7天活跃天数、平均会话时长等静态统计在线特征如实时上下文、用户在本次会话内的即时行为。离线特征可以离线算好存入特征库在线特征要在请求链路中实时拼装两条链路都要在特征命名和计算口径上与离线保持一致。项目里我给特征保存成了一个Parquet文件分区的结构按日期分区存储每天的数据独立一个文件夹。这个设计让特征回看、离线评估、线上拼接都方便很多而且回刷某一时间的特征时不需要重构整张表只需要单独处理对应的分区。3.3 模型训练与实验管理在数据管道稳定之后就进入模型训练。我建议从简单模型开始先用逻辑回归或者XGBoost之类基线模型跑一遍不是为了出多好的效果而是确定数据管道在可用性上没问题标签定义合理特征与目标存在相关性。很多团队一上来就上大模型结果发现数据泄漏或标签错误底层的基线模型反而更容易暴露这些问题。基线验证OK之后再用PyTorch逐步替换或叠加更多模型结构。训练代码要有几个固定的部分不能省# 训练配置示例 config { random_seed: 42, epochs: 30, batch_size: 64, learning_rate: 1e-3, early_stop_patience: 5, model_save_path: fruns/{run_name}/model.pt, }实验管理可以用MLflow或Weights Biases。我实际用得比较多的是MLflow原因在于可以自托管不用把数据推到第三方平台满足一些数据不外流的场景。每次实验的代码、数据版本、配置、指标统一记录下次想复现或对比只需要一键拉取。训练过程中也容易踩到一个问题数据泄漏。所谓数据泄漏就是某些特征在训练时包含了样本之后才能知道的信息。比如用整段时间的行为统计量预测用户次日留存稍不留神就会把未来数据算进去导致验证集上效果好到离谱线上直接崩溃。所以划分训练集与测试集的时候一定要按时间切分同时用特征创建时间切特征不让未来信息反向渗透。3.4 API服务封装与线上部署模型训练完成后接下来的核心环节是服务化封装。一个标准的推理服务要处理好输入合法性校验、模型推理、后处理这三个环节。服务代码的骨架大概长这样from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI() class PredictRequest(BaseModel): user_id: str features: dict class PredictResponse(BaseModel): score: float version: str trace_id: str model None app.on_event(startup) async def load_model(): global model model load_model_from_disk(runs/exp_001/model.pt) # 模型加载进内存等待请求进来 app.post(/predict) async def predict(req: PredictRequest): vector featurize(req.user_id, req.features) score model.predict(vector) return PredictResponse(scorescore, versionmodel.version, trace_idreq.trace_id)模型版本信息放进返回值里是我特别想强调的一点设计。线上模型发生异常或者效果产生波动时可以通过trace_id快速定位版本而无版本信息则只能靠猜。后端同学应该深有体会排查一个“看起来没问题但就是不对劲”的接口版本对不上是非常折磨人的。服务部署方面Dockerfile的构建尽量把依赖层与代码层分离改动代码后重新构建镜像的速度会快很多FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [uvicorn, services.main:app, --host, 0.0.0.0, --port, 8080]线上环境的Docker容器建议不要以root用户运行镜像内新建低权限用户减少安全风险。容器内存限制要设置好因为模型在内存中占用很大一旦发生内存膨胀整机资源会被拖垮。3.5 监控告警与模型迭代上线只是开始线上监控反而要比离线训练投入更多精力。我维护了一套三层监控体系系统层监控CPU、内存、GPU利用率、磁盘IO接口层监控请求量、错误率、P99延迟模型层监控预测均值漂移、特征分布变化、线上效果指标回落系统层和接口层用Prometheus Grafana就能搞定模型层则需要业务侧打点配合。比如预测分数均值在过去7天是0.55今天突然变成0.72这个信号说明线上数据分布可能出问题了模型开始面对它没见过的输入。这时候不要急着优化模型先拉日志观察特征分布变化排查上线策略和样本采集逻辑。监控的告警阈值也不能拍脑袋定。我的做法是先积累两周的基线数据取均值和标准差报警条件设为超出均值三倍标准差。这样既不会提早告警也不会漏掉明显异常。模型迭代方面要坚持小步试错的原则。每次新版本上线前都先切一部分流量给新模型和旧模型进行A/B对比观察业务指标和算力消耗确认胜出后再逐步切全量。我见过太多次新模型在离线测试集上精度提升上线全量后业务效果反而掉下来的情况。A/B测试的成本不高却能避免很多线上事故。4. 常见问题与排错记录4.1 环境与依赖相关的坑这类问题新手遇到最多。典型场景是换一台电脑或新容器里部署代码运行时疯狂报ImportError或者不同库之间版本冲突。我在项目里养成了一个习惯锁版本。所有依赖都记录精确版本号而不只是写个大版本范围。使用uv的话uv.lock会把依赖树锁得非常细致保证任何机器装出来的环境几乎完全一致。另外如果项目里同时用到GPU和CPU环境依赖项要小心区分。torch的CPU和CUDA版本完全不是同一个包混着装会直接导致训练起不来。不要试图在一个环境里兼容所有场景用多个虚拟环境分别管理是更稳妥的做法。4.2 数据处理环节偶尔出现的幽灵数据环节的问题本身不好排查因为报错不多更多是静默产生错误数据。最典型的一个坑是ID类型对齐问题训练的时候user_id读成了字符串线上请求时传过来的是数字模型推出来结果错得离谱但接口毫无异常。这类问题要在服务入口做严格类型检查Pydantic模型都指定好字段类型同时特征拼装函数里显式转换。另一个容易出问题的地方是时间窗口对齐。离线特征按T1的方式更新线上请求却要实时特征两个口径差一天预测偏差会非常大。解决方法是定期做日志抽样比对比较离线特征与线上实时特征在相同时刻的计算结果是否一致。如果你发现模型在离线评估集上效果不错线上却一塌糊涂优先排查这类“线上线下特征口径不一致”问题很大程度上问题就出在这里。4.3 训练与推理不一致的问题类似的问题还会出现在模型行为上。训练时开启Dropout或者Batch Normalization处于训练模式模型输出的预测完全不稳定。一旦上线时忘了切换成model.eval()模式线上效果就会飘忽不定。虽然PyTorch里,eval()已经是很基础的常识但实际工程中如果模型封装得复杂推理函数里忘记调用的情况并不罕见。更隐蔽的是使用PyTorch的JIT或ONNX导出后算子的实现差异也可能造成微小的结果不一致。我的建议是模型导出之后用一批固定样本做一致性测试对比原始模型和导出模型的输出误差范围超过千分位就要警惕了。4.4 线上模型效果衰减的处理套路模型衰减是这个行业里最常见的问题也是最难定位的问题之一。它不是突然崩溃而是业务指标一点点掉等到明显察觉时已经损失大量业务了。处理步骤大致是先看监控面板确认是系统波动还是模型效果衰减然后看数据侧对比线上实时特征分布和训练样本的特征分布找出发生漂移的具体特征列再看用户行为是否发生变化比如新的推广渠道带来了新客群模型没见过这种模式自然表现不佳最后根据原因执行数据切换、样本补充或模型重训。这个处理思路帮助我在很多次效果回落中快速定位根因。有几次排查到最后发现根本不是模型问题而是上游埋点改了字段特征没变但数据含义已经变了。监控看板上的特征分布热力图能帮你在几分钟内锁住这一类问题。4.5 模型服务性能优化的记录推理性能优化是AI工程一项绕不开的任务。做这块之前先要确定瓶颈到底在模型计算、特征拼装还是网络IO。用py-spy或cProfile先定位耗时分布宁可多花时间分析不要上来就重写代码。常规方案包括模型换成半精度推理、批处理合并短请求、用ONNX Runtime或者TensorRT加速算子执行、常用特征加Redis缓存、把大对象预加载进内存。这些手段组合起来常常能把延迟从几百毫秒降到数十毫秒遇到极端高并发场景再考虑横向扩容。有一点要专门提醒优化过程里要压测不同版本的行为保留旧版本作为对比基线。有一次我把模型换成了TensorRT导出版本单线程延迟确实降了但并发一高反而出现崩溃回退旧版本才恢复正常。性能优化要在压测和稳定性之间做好权衡。尾声最值得投入的那部分永远是工程本身这个项目推进到后期我对AI工程的理解发生了明显的变化。最初总以为难点在于模型结构、算法理论这些“硬核”内容后来发现真正决定一个AI系统能不能长期稳定用的是工程基本功数据质量有没有保证、流程能否复现、服务能否优雅降级、出了问题能不能快速定位。这套能力没有人会在课程里专门教你只能靠反复踏坑再爬起来。从实操角度再补一个建议这份项目的代码不求精美但每个模块都要能独立跑通、稳定复现。把全链路的联动先建立起来比一开始就追求大模型、高并发要实在得多。后续再往扩展方向延伸比如引入模型版本回滚机制、多环境自动发布、数据质量校验框架这里面的每一步都会让你受益很久。