资讯详情

AI工程从零实战:构建模型到上线的完整闭环

📅 2026/10/3 21:49:50 | 华诺云谱 👁 阅读
AI工程从零实战:构建模型到上线的完整闭环
最近很多人问我一个问题作为一个刚开始接触 AI 的人到底该怎么规划自己的学习路径才能不变成“只会调库的调包侠”。我的答案一直很明确——如果真有所谓“ai-engineering”这条路那它的起点一定不是某个框架的官方文档而是你亲手把一个最基础的模型从零开始推到能稳定上线运行的完整闭环。这篇文章就围绕我的“ai-engineering-from-scratch”个人项目讲讲我这段时间踩过的坑、拆过的轮子以及最终沉淀下来的一套思路。它不是什么标准教材更像是我自己重新定义“入门”的一份实操笔记适合那些已经看过一些 Python 和机器学习概念、但始终觉得“没真正上手”的人。如果只是追求“能跑通一个 demo”那么真没什么好写的随便找个开源项目克隆下来就能出结果。但工程的本质从来不是“能跑”而是“可控、可解释、可复现、可迭代”。所以我给自己定的目标是不依赖任何一站式平台不跳过任何关键环节把所有内容补全成一套自己完全理解的最小系统。接下来我会把我当时的设计思路、技术选型、以及后来反复推翻重做的地方完整拆开来讲。1. 先从最容易被忽略的定义讲起AI 工程和机器学习建模是两回事1.1 为什么我坚持用“工程”这个词而不是“建模”市面上百分之八十的入门教程其实都在教“建模”拿一份现成的数据集做特征工程跑一个模型然后看一下准确率。这套流程当然重要但是你会发现一旦要处理真实场景里的脏数据、实时请求、模型过期、AB 实验、灰度发布这些问题教程里的那一套立刻就会失灵。因为建模解决的是“从数据到预测”的问题而工程解决的是“从预测到交付”的问题。我的体会是AI 工程的“从零开始”真正的难点不在于推导某一个数学公式而是要建立一个能把模型放进生产环境、并且持续服务用户的系统能力。这个能力至少包括数据如何更新、代码如何回滚、监控如何报警、特征如何对齐、模型如何重新训练。对听起来很无聊但这就是工程的核心。为了不让自己逃课我给自己定的项目边界是不使用任何 AutoML 平台自动生成的 pipeline不直接调用预训练大模型的 API 来做“套壳”项目不使用一站式机器学习平台来管理数据和模型所有核心代码从数据处理到模型推理必须自己一行一行写出来这个边界看起来很激进但是它的价值在于——你被迫面对每一个细节。比如一个简单的数据去重用 pandas 一行代码就完了但当我要求自己用 Python 原生的方式去实现时可以倒排索引、内存占用控制、哈希冲突处理才真正理解了那句“所有数据库都能做成 apis”的老话不是随便说的。1.2 重新拆分“from scratch”的含义这个项目名字里的“from scratch”对不同的人来说几乎有不同含义。对初学者它可能是“不用现成框架从头写神经网络”对资深工程师它可能是“一切从需求文档开始包括基础设施选型”。我的理解放在了中间层不用整套平台但允许使用底层的库和工具因为你不可能真的从晶体管开始造一台计算机。具体来说我的“scratch”边界是这样划定的框架层面允许用 PyTorch但不用它的高层封装比如 Lightning数据处理层面允许用 numpy、pandas但不用现成的特征平台部署层面允许用 Docker 和云服务但每次部署必须知道每一行配置的含义监控层面允许用 Prometheus 这类监控系统但报警规则必须自己设计这个“允许”列表是经过认真权衡的。因为工程的本质是控制复杂度如果你连底层框架都重写那项目大概率做不完但如果你什么都用现成的项目就变成单纯地“拼接”没有真正内化知识。在这两者之间找到平衡是“ai-engineering-from-scratch”这个项目给我的第一个启发。2. 环境构建的第一课你的开发环境本身就是一个“AI 工程问题”2.1 从裸机到可复现环境我花了 3 天时间试错很多人会忽略环境搭建的意义觉得装个 conda、pip install 一把梭就是了。但我想说环境搭建这件事本身就是一次“从零开始”的锻炼因为它涉及到依赖管理、版本冲突、跨平台兼容性而这恰好是工程能力的缩影。我当时给自己出了一道题在一台全新的 Ubuntu 服务器上不借助任何云厂商的预装镜像从裸系统开始搭出一套可以跑完整 AI 流水线的环境。我最开始以为半天就能搞定结果实际用了 3 天多。踩的第一个大坑是 Python 版本。系统自带的 Python 版本常常是 3.8 这类版本但很多新库已经要求 3.10 以上而某些老库反而还不支持太新的版本。如果你直接改系统 Python后面排查起来非常痛苦。我的建议是无论什么情况下都使用虚拟环境来隔离项目依赖。这里我用的是 conda 的虚拟环境因为它不仅管理 Python还能管理 CUDA、cuDNN 这些 GPU 相关的底层库。第二个坑是 GPU 驱动和 PyTorch 的匹配问题。当时我在一台只装了 NVIDIA 驱动、但没装 CUDA toolkit 的机器上跑训练发现 PyTorch 会静默地回退到 CPU 模式因为它在用 pip 安装时默认带了一套 CUDA runtime但如果驱动版本过低运行时会直接警告但不报错。这个“警告但不报错”非常迷惑人。直到我加了这几行代码才定位到问题import torch print(torch.cuda.is_available()) # 这里会显示 False原因很隐蔽 print(torch.version.cuda) # 查看编译时捆绑的 CUDA 版本 print(torch.backends.cudnn.enabled) # cudnn 是否启用是另一个静态开关如果你发现自己训练速度异常缓慢而 CPU 占用率又很高八成就是出现了这个情况。关键是报错信息永远不如你想的直接因为很多时候它只是默默地在 CPU 上执行。2.2 依赖管理的三条军规经过那几天折腾我总结出三条之后一直遵守的规则现在分享出来第一环境文件必须代码化。不要靠“在一个机器上装好了然后人工记忆装了什么”这种方式。我最后把所有依赖用一个 environment.yml 文件管理包括 pip 部分和 conda 部分分开写。这样任何同事或未来的我都能通过一条命令还原相同环境。第二不要吞噬警告。我在项目初期为了图省事在脚本开头加入了warnings.filterwarnings(ignore)后来这成为我最后悔的决定之一。因为很多依赖库的版本问题最开始都是以警告形式出现的你忽略多了系统就变成一个“不知道哪个环节已经悄悄不对劲”的黑盒。AI 工程里最怕的就是这种黑盒。第三把来自控制台的输出全部保存下来。我写了一个统一的日志模块所有训练的中间输出、指标变化、异常堆栈都写入本地文件并且按时间戳归档。这个习惯后的好处是你可以在很多天之后回过头来看某一版模型当时训练时到底发生了什么而不用凭记忆猜。列举一个当时非常典型的依赖陷阱numpy的版本浮动一度导致scikit-learn行为不一致两个库基于不同版本的 BLAS 接口最终模型结果虽然没变但训练时间忽快忽慢。我排查了很久才意识到问题根本不在代码逻辑而在底层的线性代数库。这件事真的让我明白——AI 工程的环境构建不是“一次性的准备动作”它是整个项目里必须持续维护的基础设施。3. 用最笨的代码搭数据管道从解析到清洗都不假手于人3.1 拒绝直接用现成数据集很多学习项目首选是 Kaggle 上已经整理好的 CSV 文件但我故意避开了这条路。我从网上找了一个非常混乱的原始数据源它是一个第三方平台爬下来的用户行为日志里面包含拼写错误、缺失字段、格式不统一、时间戳来自不同时区等等问题。这个决定让我后面对“真实数据”有了非常具象的认知——真实世界的数据从来不是整齐的。这既然是“from scratch”项目我坚持自己写数据解析逻辑。举个例子数据源返回的是多行的 JSON Lines 格式但部分行是破损的。pandas 的read_json遇到这种情况通常直接抛出异常。解决办法是逐行读取尝试解析解析失败的先搁置到一个 error bucket。实现起来也不复杂import json from pathlib import Path def load_jsonl_with_tolerance(file_path: Path, error_bucket: list): valid_records [] with open(file_path, r, encodingutf-8) as f: for line_num, line in enumerate(f, 1): line line.strip() if not line: continue try: record json.loads(line) valid_records.append(record) except json.JSONDecodeError as e: error_bucket.append((line_num, line[:200], str(e))) return valid_records, error_bucket这段代码当然不算聪明但它提供了“逐行可追踪”的能力。之后我又写了一个独立的离线脚本对 error bucket 做统计分析看出错集中在哪些行、哪些字段再去源头排查。你会发现这个笨办法比用一个大而全的库一次性加载后dropna()要更接近真实工程的诉求你可视化地知道每一步丢了什么、为什么丢。3.2 数据库选型的思考数据清洗完不能总是放在 CSV 里需要有一个本地存储层。我当时在 SQLite 和 DuckDB 之间犹豫了很久最后还是选了 SQLite。虽然 DuckDB 在分析查询上性能更好但 SQLite 足够简单、稳定而且没有混淆“工程问题”和“性能问题”。我建立了几张核心表raw_events存储原始日志clean_events存储清洗后的数据feature_store存储特征计算的中间结果。理念也很直白原始层、清洗层、特征层必须严格分离任何下游任务都只能读清洗层和特征层不能碰原始层。这个设计让我后续的调试变得异常顺畅。当发现模型效果异常时我可以逐步回溯数据是在哪一层出了偏差。如果你从一开始就把所有中间结果都混在一起这种回溯能力根本不存在。3.3 清洗规则里的“领域知识”远比“代码技巧”重要有一类问题是我花了很多时间才意识到的清洗规则本质上不是你代码写得多漂亮而是你对业务场景的理解足够深。比如我处理的那份用户行为日志里面有一个字段表示“用户在上一个页面的停留时长”它出现了大量-1一开始我以为是缺失值的占位符后来才通过查看原始日志的上下文发现-1其实是一种合法的状态标志表示“用户通过外部链接直接进入没有上一个页面”。如果直接做缺失值填充等于把一部分真实信息给抹掉了。这种理解层面的门槛库函数是帮不了你的。所以我想说的是真正难的数据工程不是你会不会用各种数据框架而是你是否能敏锐地识别“数据里藏着什么样的业务状态”。这里我的建议是拿到任何数据不要急着写代码清洗先花足够多的时间做探索性分析打印出异常值的取值分布、频率、以及和上下文的关联。4. 特征工程和模型训练的实战复盘让模型先“笨笨地跑通”再“聪明地优化”4.1 第一版模型故意不优化只求闭环很多做项目的人容易一下子陷入某个环节的“完美主义”。比如有人会花一周做特征也有人会花一周调参。我给自己立的规矩是第一版模型必须用一个最简单的方法比如逻辑回归或小型决策树在三天内跑通整个链路。为什么这么设置因为工程是要看到“系统运转起来”才能继续讨论优化。你不可能在零闭环的基础上评估任何环节的好坏。当我把第一版逻辑回归跑通后精确实的 baseline 就已经固化了。之后所有的改进包括换模型、做特征选择、调超参数都拿这个 baseline 做对比。这很重要——没有 baseline所有的“优化”都是自说自话。我的第一版 pipeline 大体是这样的从 SQLite 清洗层读出事件数据时间窗口聚合生成基础特征计数、均值、最近一次行为距离当前时长用逻辑回归做用户分类输出准确率、精确率、召回率把模型权重序列化保存这个流程非常粗糙但关键是它完整地走了一圈。我明确记得那个时刻的真实感觉看着终端里打印出第一版评估指标突然意识到“从数据到模型再到结果”这条链路已经真正打通了。4.2 “白盒优先”的训练策略我在训练模型时给自己加了一个硬性要求每一次训练都必须产出模型的可解释性报告。逻辑回归阶段很容易可以打印权重系数。后来切换到 Tree 模型我就必须输出特征重要性。这个“白盒优先”策略的直接好处是你能在第一时间发现特征泄漏。我第一次做特征重要性分析时发现排名第一的特征是“目标变量的滞后值”准确率飙升到了接近完美。表面看是模型效果极好差点就准备上线了。但仔细思考后我意识到这个特征只有在事后才能得到在真实预测场景中根本无法获取。这种泄漏问题如果你只看准确率数字你几乎永远不会发现。所以这里给我的深刻教训是AI 工程的评估不只是看一个结果数字而是必须把“结果为什么好/坏”作为第一优先级。换句话说模型的黑箱可以存在但工程师不能黑箱化自己的思考过程。4.3 超参数调优的“机制性理解”碾压“暴力搜索”关于调参我的观点可能跟主流不太一样。我认为你用 Grid Search 或者 Random Search 找出来一组参数如果不能解释“为什么这些参数在这个数据集上有效”那这个调参过程的意义就非常有限。因为换一批数据这套参数可能完全失效。我拿我项目的决策树模型举个例子。当我调整max_depth时我观察到深度从 4 增加到 8训练集准确率上升很多但验证集准确率开始下降——这是一个典型的过拟合信号。于是我不再继续增大深度并且反过来设置了min_samples_split的最小值来抑制叶子节点的进一步分裂。这个过程的关键不是“找到最优参数”而是通过调参的过程去验证你对模型偏差-方差权衡的理解。这就是“机制性理解”和“暴力搜索”最大的差别。5. 部署与监控AI 工程真正拉开差距的地方5.1 一次痛苦的上线经历教会我的事项目的第一个模型在测试环境表现完美但上线后的第三天整体准确率突然下降。我当时的第一反应是模型出了问题于是重启服务、回滚代码、检查硬件都没找到原因。最后查了半天才发现上游数据源的字段格式偷偷改了一个数值型字段变成了带单位后缀的字符串而我们的特征解析代码直接将其忽略。这个经历给我留下一个非常深的教训——“上线”不是一个动作而是一个持续的过程。模型的服务化部署只是开始更重要的是一套完整的监控体系。你不仅需要监控服务器 CPU、内存还需要监控模型的输入分布、预测分布、特征缺失率一句话你必须在模型的“生命周期”里盯住数据的变化。5.2 用 FastAPI 构建轻量推理服务在服务化方面我没有选择 Serverless 或复杂的微服务框架而是用 FastAPI 写了一个轻量级推理接口。选择它有几个原因原生异步支持性能足够跑中小流量的推理自带 OpenAPI 文档调试方便结合 Pydantic 做请求参数校验能直接把脏请求挡在外面一个要点是推理接口里不应该包含任何数据处理逻辑。所有的特征转换逻辑都被封装成一个独立的模块在模型加载时一并初始化输入进来之后直接走同一套转换逻辑。如果你把特征转换散落在请求函数里后面做离线训练和在线推理时特征不一致的问题就会出现。离线在线特征不一致是一个很隐蔽但杀伤力巨大的坑它排名我在 AI 工程里遇到过的“最隐蔽的 bug”前三名。训练时的习惯是所有特征计算逻辑单独抽成一个特征工程库训练时调用推理时也调用两端必须使用同一版本。我的做法是把这个特征库按版本号打成一个独立 Python 包严格要求训练环境和推理环境安装的版本一致。这样虽然麻烦但保证了线上预测用的特征变换方式和训练时完全一致。5.3 模型更新策略我为什么不直接用最新模型另一个容易被忽视的工程问题是对模型版本的管理。很多人的直觉是“新模型效果更好就立刻替换”。但在我的实践里直接替换风险很大因为你无法确定新模型在哪些样本上的表现变差了可能总体指标上升但在某个细分群体上发生严重退化。我采取的策略是“灰度替换 影子对比”。具体做法是让新模型在后台“影子模式”运行一段时间也就是它同步接收线上请求、同步计算预测结果但不会真正影响线上业务决策。等积累了足够的对比样本之后再根据指标决定是否放量。这套流程听上去简单实现起来也不难写一个中间层把请求同时发给新旧两个模型然后把两者的预测结果都存入一条日志表。这个过程让我体会到模型迭代的本质不是追求“绝对最优”而是追求“有把握的升级”。如果每一次模型变更都能清晰地解释哪些方面变好了、哪些方面变差了那么这套系统就是一个工程师真正可以把控的系统。否则即使暂时效果不错也随时可能因为一次数据波动而翻车。6. 完整项目管理复盘时间投入、卡点以及如果重来会做什么6.1 时间都花在哪里了把整个“ai-engineering-from-scratch”项目从头到尾做完我大约用了两个月左右的业余时间。如果按模块拆分时间消耗大概是这样的阶段耗时占比主要内容环境与基线搭建15%反复折腾 GPU 驱动、依赖管理、项目结构数据清洗与存储设计30%解析原始日志、错误数据回溯、字段语义研究特征工程与第一版模型20%从逻辑回归 baseline 到特征迭代模型评估与调优15%特征泄漏排查、过拟合实验、异常分析服务化部署与监控20%API 封装、影子模式、日志与报警有几个地方让我比较意外原本以为只占 5% 的环境搭建实际占了 15% 多原本以为要抠很久的模型调参反而只花了 15%。这印证了一个观点在真实的 AI 工程项目中数据工作所耗费的时间一般在所有环节中占比最大。6.2 那些让我想砸键盘的卡点回顾整个过程最痛苦的一件事发生在数据处理阶段的某天晚上。由于原始日志里时间戳来自不同时区而我一开始没注意到直接把所有字符串当成 UTC 格式解析了导致“时间间隔”特征大面积出现负数模型训练出来的权重也是歪的。这个 bug 其实只要在清洗代码里加一行时区转换就能避免但我因为没有做足够的抽样检查白白浪费了一周。这件事后来让我养成一个习惯每完成一个阶段的数据处理都要抽出少量样本做人工检查比如打印出最大值、最小值、均值、异常值比例。数据质量检查的成本其实远低于事后排查的成本但很多人包括我自己总是在吃过亏之后才明白这个道理。6.3 如果重新开始我唯一会大幅改变的事如果让我重新开始这个项目我唯一会大幅改变的是在项目启动的第一周就引入“端到端骨架”。什么意思就是不要先花一个月把数据处理、特征工程做到完美再开始跑模型。更好的做法是第一周就用最简单的逻辑回归、几个手工构造的粗糙特征跑出一个完整可用的、能出预测结果的端到端链条。即使准确率只有 60%资产也清晰存在。后面做的所有事都基于这条“已有链路”进行迭代式改进。我当初是反过来的花了大量时间把数据基础设施打磨到自认为“满意”才开始接模型。结果在模型训练阶段意外发现某些数据清洗规则并不合理导致不得不回头重新修改上游逻辑改完数据后又要重新验证特征。这就是典型的“瀑布式开发”在 AI 项目里的反面教材。在数据、特征和模型高度耦合的场景中先打通闭环、再做精细打磨永远是更优策略。7. 这套方法带给我的可迁移经验这个项目做完之后我在自己的能力地图里增加的不只是“会训练模型”更多的是一套如何处理未知复杂问题的思考框架。这里有几个我认为具备可迁移性的经验值得展开聊。第一个经验把“不可见”变成“可见”。AI 工程里很多问题是不可见的比如特征泄漏、数据漂移、依赖条件悄悄变化。对抗这些问题的唯一武器是可视化与日志记录。我的体会是所有中间产物包括清洗后的数据、特征矩阵、模型预测输出、模型性能报告都应该被保存下来并且具备按版本回溯的能力。你需要能看到“某个时间点上某个模型用的到底是什么特征和什么数据”这个能力在调试问题时会节约你大量时间。第二个经验用“假设驱动”代替“无脑优化”。很多刚入门的人看到模型效果不好第一反应是换一个更强的模型或增加更多层。但我觉得每一步优化之前都应该先写下你的假设。例如“我假设增加这个特征可以改善模型因为当前的模型对于新用户的预测偏差较大而这个特征可以直接刻画用户的新旧程度。”有了这个假设后你再去做实验验证。如果实验结果与假设不符你得到的反馈也很有价值——它能帮你修正对数据或模型的理解。第三个经验给自己设计“安全网”。在工程里安全网是报警系统、灰度发布、回滚机制、影子模式。在学习过程中安全网同样重要。我的做法是一切改动都从 baseline 分支出发先保留一个稳定版本在稳定的撑下再进行任何实验性探索。这样就算实验失败了也可以快速回到稳定状态而不用从头来一遍。这些经验让我在处理 AI 之外的项目时也有了更多底气因为它们的根基不在于某个具体工具而在于一套思维模式永远为不确定性留有余地永远让过程可回溯永远把“假设”摆到台面上检验。最后再分享一个很小但贯穿始终的实践细节我坚持给每个实验写一份极简的 README记录当天的实验目的、改了什么、结果如何、下一步打算。几个月后回看这些笔记整条思维链路清晰得令人感慨。这种感觉就像是给未来的自己留下了一份完整的地图避免在同一条路上反复迷路。如果你也想走一遍类似的项目不用急着追求多复杂的模型试着从最朴素的方式开始把链路完整跑通你学到的东西会比想象中多得多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑