news 2026/9/28 7:13:02

AI工程从零到上线:系统思维、模型部署与监控实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI工程从零到上线:系统思维、模型部署与监控实战

我把自己两年多来围绕 AI Engineering 攒下的笔记整理成了一个项目,名字就叫 ai-engineering-from-scratch。起因很实际:团队里能在 Jupyter Notebook 里调出漂亮 AUC 的人不少,但能把模型稳定送上线、出问题能十分钟内定位的人,掰着手指头数得过来。我自己就是从后者被骂出来的。这个项目不是算法课,也不是某个框架的官方文档搬运,它是一条从零开始搭建 AI 工程能力的完整路线:环境和依赖怎么管、数据怎么进管道、模型怎么训练和记录、服务怎么上线、上线之后看什么指标,以及每一步踩过的坑。它围绕一个可以真实复现的案例展开——用公开的 MovieLens 评分数据构建一个推荐服务,从原始 CSV 一直做到带监控的线上 API。如果你正在从算法或开发岗位转向 AI 工程方向,或者学完理论之后总觉得缺了落地那一环,这份笔记大概率对你有用。

1. 为什么"从零开始"比"从框架开始"更适合入门AI工程

1.1 算法工程师与AI工程师,差在"系统思维"

我的第一份相关工作是在算法组。那时候的工作节奏很单一:拿到一个业务问题,找特征、调模型、看离线指标,周会上汇报 AUC 涨了 0.3 个点,大家都挺开心。直到有一次模型上线,业务方反馈转化率反而掉了,我拿着离线实验记录去对线,才发现问题根本不在模型:训练数据里的用户活跃度特征是在凌晨批处理算出来的,而线上请求发生在白天,两边的特征分布对不上。那一刻我意识到,AI 工程师要管的不是模型本身,而是模型周围的一整圈系统——数据是否准时、特征是否一致、服务是否可用、指标是否可信。

这就是 AI Engineering 和纯算法工作的分水岭。算法工程师可以只对模型负责,AI 工程师必须对整个系统的闭环负责。模型只是这个闭环里的一环,而且往往不是最脆弱的那一环。最脆弱的是数据管道、特征逻辑、部署配置和监控告警。我把这个观点放到项目最前面的理由很简单:如果一开始就奔着"训练一个高精度模型"去,很容易重复我走过的弯路——模型很强,系统很脆。

1.2 复盘结论:亲手搭一遍最小闭环,比套用现成平台更划算

ai-engineering-from-scratch 这个项目最核心的决策,是拒绝一开始就上现成的机器学习平台或 AutoML 工具。原因不是这些工具不好,而是它们帮你隐藏了大量必须自己理解的东西。用 AutoML 跑出一个高分模型很容易,但线上延迟超了、特征缺失了、数据漂移了,平台给你的报错会像天书一样。类比一下:会用一体机的人很多,但真到了电脑出故障,还是得靠那些自己动手装过机的人。从零搭一遍最小系统,不是为了重复造轮子,而是为了获得排查问题的底感。

所以项目里我给自己定了一条规矩:每个环节都用最朴素的工具先跑通。数据管道先用 Python 脚本,模型服务先用 FastAPI,实验记录先用 MLflow,数据库先用 PostgreSQL。整套东西加起来,一台带 GPU 的开发机就能撑起来,成本很低,但每个环节的问题都会真实暴露在你面前。先痛苦,后顺畅,这是从零开始最大的回报。

2. 起步阶段先搭能力地图:我选定的环境与最小技术栈

2.1 环境层:Python版本、Docker、GPU驱动的不一致问题

环境是绝大多数从零开始的开发者第一个崩溃点。我曾经在同一台机器上装过三个 Python 版本,系统 Python、Anaconda Python,还有某个项目自带的 Python,结果 pip 安装的包互相污染,一个项目升级依赖把另一个项目搞挂。后来我强制自己使用 pyenv 管理 Python 版本,所有项目都声明.python-version文件,配合 poetry 管理依赖,pyproject.toml里锁定直接依赖,poetry.lock锁定完整依赖树。这一步花了我一个周末,但之后再也没有因为换环境浪费过一整天。

GPU 环境更麻烦。CUDA 驱动、cuDNN、PyTorch 三者的版本必须匹配,很多人在这里反复试错。我的建议是:别直接装最新版,先查一下框架官方对 CUDA 版本的支持矩阵,然后固化成 Docker 镜像。下面是我在项目里用过的启动命令片段:

# 基于官方PyTorch镜像构建,避免宿主机驱动与容器库版本错位 docker build -t ai-from-scratch:0.1 -f docker/Dockerfile . docker run --gpus all -it --rm -v $(pwd):/workspace ai-from-scratch:0.1 bash

这个做法的核心收益是:项目的可复现性不再取决于某台机器的状态,而取决于一个镜像的 tag。任何人任何时候拉取同一个镜像,得到的运行环境一致,这是 AI 工程底座的第一个习惯,也是后面所有实验可复现的前提。

2.2 应用层:FastAPI、MLflow、PostgreSQL组成的底座

技术栈我只选了三个东西,并且每一个都有明确理由。FastAPI 负责把模型封装成 HTTP 服务,选它是因为自带基于 Pydantic 的请求校验和 OpenAPI 文档,写一个推荐接口只需要几十行代码,后续加限流、加监控也不别扭。MLflow 负责实验跟踪和模型注册,它可以记录每次训练的超参数、指标和模型文件,比自己在 Excel 里记实验日志可靠太多。PostgreSQL 负责存三类数据:原始特征快照、模型版本元数据、线上请求日志。这三者组合起来,基本覆盖了一个小型 AI 系统的全部需要。

不要一上来就上 Kubernetes、上 Feature Store、上完整的 MLOps 平台。我见过不少团队,模型还没跑通,先花两周搭平台,最后平台比模型还难维护。最小技术栈的原则是:单机能跑、三小时能学会、出问题能自己修。等流量和复杂度真的上来了,再按需替换。项目里我把这套栈的选择过程也写进了 README,方便后来者理解每个组件解决什么问题。

2.3 数据层:我先用脚本加版本管理,而不是上重型平台

数据管道是另一个容易过度设计的地方。对于从零开始的项目,我建议先用脚本加版本管理,而不是一上来就上 Airflow 或 Feature Store。我的处理方式:所有原始数据放到data/raw目录,清洗脚本放到src/data目录,每次清洗都输出一个带时间戳和内容哈希的 dataset 快照。内容哈希可以用简单的 md5 实现,但要在记录里写明数据来自哪个版本。伪代码类似这样:

import hashlib import pandas as pd df = pd.read_csv("data/raw/movielens/ratings.csv") # 清洗逻辑... snapshot_hash = hashlib.md5(df.to_csv().encode()).hexdigest()[:8] df.to_parquet(f"data/processed/ratings_{snapshot_hash}.parquet")

这个看起来很朴素的方案,解决了 AI 工程里最隐蔽的问题:数据版本不可追溯。当你发现某个特征在线上异常,能快速确认"这个特征是用哪份数据、哪个清洗脚本、什么时候生成的",比任何花哨平台都管用。等管道数量超过十个、调度依赖复杂到脚本维护不了,再考虑换平台也不迟。

3. 端到端案例:用MovieLens数据把一个推荐服务跑上线

3.1 数据清洗:评分数据里的缺失、偏置和时间陷阱

项目采用的公开数据是 MovieLens 的评分记录,虽然已经做过基本清洗,但仍有很多现实问题:评分时间戳分布不均、部分用户只有一条评分、热门电影占据大量样本。我用它来模拟真实业务数据里的偏置。清洗时做了三件事。第一,把时间戳转成可读时间并拆出星期、小时等特征,因为后续要验证"时间型特征漂移"。第二,筛掉评分记录少于 5 条的用户,避免冷启动噪音影响早期模型判断,同时单独留存一份冷启动测试集,后面专门验证服务策略。第三,按用户 ID 做分层切分,而不是随机切分,这样能防止同一个用户的数据同时出现在训练集和验证集里,造成虚假的高指标。

这一步的教训是:数据清洗不只是"去掉脏数据",它直接决定了你能发现什么问题。如果你不做分层切分,模型上线后大概率会在新用户身上翻车,而你离线验证时根本看不出来。我在项目里专门写了一个脚本对比两种切分方式的指标差,这个对比结果比模型本身更能说明工程思维的重要性。

3.2 训练阶段:每一步都要记录,否则等于白做

训练部分我选择了一个中等规模的协同过滤模型加一点特征工程,没有追求最先进的深度学习结构,因为重点是工程闭环。但每轮训练我都会用 MLflow 记录四类信息:代码版本,也就是 git commit 的完整 hash;数据版本,也就是清洗后数据集的哈希;超参数,比如 Embedding 维度和学习率;离线指标,比如精确率、召回率、覆盖率;以及随机种子。记录这些信息,意味着任何一次实验结果都可以被完整重现。代码示例很简单:

import mlflow with mlflow.start_run(): mlflow.log_param("embedding_dim", 64) mlflow.log_param("lr", 1e-3) mlflow.log_metric("precision@10", 0.312) mlflow.log_artifact("model.pt")

很多人觉得这是在增加负担,但实际排查问题时,你才知道"能回到过去"有多重要。我线上出过一次事故,最后定位到是某个特征计算脚本改了逻辑,如果当时没有记录数据版本,我不可能在一个下午内确认影响范围,可能要回滚整个服务。

3.3 服务化:用FastAPI把模型包成可观测的API

模型训练完,真正的 AI 工程才刚开始。我把模型文件、特征处理逻辑、预测函数打包成一个 FastAPI 应用。这里有一个关键点:特征处理逻辑必须和训练时完全一致,否则上线就是事故。所以我把特征处理抽成了独立模块,训练和推理共用同一个函数,而不是各写一份。下面是服务端点的简化示例:

from fastapi import FastAPI from schemas import RecommendRequest from features import build_features app = FastAPI() @app.post("/recommend") def recommend(req: RecommendRequest): user_features = build_features(req.user_id, req.context) items = model.recommend(req.user_id, top_k=req.top_k) return {"user_id": req.user_id, "items": items}

除了业务接口,我还加了两个工程必备的东西:健康检查端点和基础限流。健康检查是为了让后续的负载均衡和服务发现能判断实例存活性,限流是为了防止某个突发流量把模型服务打挂。这些不写在算法教程里,但却是 AI 工程上线前必须考虑的。

3.4 上线第一周:我在监控面板上看到的三类异常

服务上线后,我搭了一个最简单的监控面板,记录请求量、P95 延迟、错误率、推荐覆盖率,以及推荐列表里物品的平均热度。第一周就发现三类问题。

第一,P95 延迟在晚上八点到十点明显升高,原因是晚高峰请求量大,加上缓存命中率下降,热门电影推荐列表在高峰时段几乎全部要实时计算。解决方式是把 Top-N 热门推荐结果做定时缓存,并且给实时计算加了一个超时熔断,超时直接返回缓存结果。

第二,错误率里有一批 422 校验错误,查日志后发现是部分客户端没有传 context 上下文,但我把它设成了必填字段。改成可空字段并给出默认特征后,错误率马上降下来。

第三,也是最值得记录的:推荐覆盖率很高,但新用户被推荐的内容几乎全是热门电影。业务上这不算 bug,但它暴露了"离线指标好看不代表产品效果好"的问题。我把这个问题记录为一个实验待办,后来通过给新用户单独走探索策略解决了。

监控的价值不在于面板好看,而在于它能强迫你回答"什么叫系统正常"。AI 工程里最怕的不是指标差,而是没人知道指标什么时候开始差的。

4. 工程化必须较真的四个细节:可复现、评估、监控与成本

4.1 可复现性:锁定依赖、数据与随机种子

可复现性是 AI 工程和算法实验之间最明显的分水岭。算法阶段,你重跑一遍代码,指标差一点可能无所谓;工程阶段,指标差一点,你会怀疑代码、数据、环境、随机数四个层面,排查成本翻倍。我在项目里做了一次彻底的可复现性改造,把依赖升级为带哈希锁定的 poetry.lock,把整个训练环境固化进 Docker 镜像,数据快照记录 hash,同时固定了随机种子,并为任何使用随机数的模块显式传入 seed。

做完这些之后,我每周跑一次同样的训练脚本,指标波动控制在千分之一以内。千分之一这个数字很重要,它意味着我能区分"代码改动带来的真实变化"和"环境噪音带来的随机波动"。没有这个基线,每次实验对比都是在猜。

4.2 线下评估不等于线上表现:影子模式怎么救了我

离线指标与线上表现的差距,几乎每个做过 AI 工程的人都会遇到。差距来自很多方面:离线数据是历史数据,线上特征可能是实时的;离线评估里采样分布和被服务用户分布不同;推荐系统还会改变用户行为,形成反馈循环。我踩过一次印象很深的坑:离线 AUC 提升了 0.5%,觉得很稳,结果线上点击率纹丝不动。后来用影子模式验证才知道,那个 AUC 提升来自一小撮极活跃用户,而这部分用户早已被收敛偏好,无论如何推荐都不会增加点击。

影子模式的做法是:让新模型在后台跟随线上流量做预测,但预测结果不直接上线,而是记录到日志里,隔天用真实的用户行为对比新旧模型的选择差异。这样可以在不影响线上体验的前提下评估模型改进。实现成本不高,一个日志表加一个离线对比脚本而已,但它是连接离线实验和线上表现的桥梁。从零开始的项目,我强烈建议一上来就预留影子模式的日志字段,后面做实验会非常省事。

4.3 成本思维:学会给每一行代码估算开销

AI 工程最后拼的是成本。模型在离线评估里效果好,不代表它在预算内能服务好。我给自己定了一个习惯:每个模型上线前,先估算单次推理的算力开销。计算公式不复杂:每秒请求数乘以单次推理耗时,就是所需并发算力;单次推理耗时又由模型结构、输入长度、批处理大小决定。一个 Embedding 维度过大的协同过滤模型,单次推理要 30 毫秒,QPS 50 时就需要每秒 1.5 秒的计算量,这在 CPU 上根本扛不住,必须上 GPU 或者裁剪模型。

我还会在代码里记录每一个模型的推理耗时和显存占用,作为模型注册时的元数据。时间久了积累出来的成本台账,可以直接回答业务方最常见的两个问题:"这个推荐位还能不能加更多候选?"和"我们能不能多上几个模型做实时重排?"。工程思维不是省到极致,而是每一分算力花得明明白白。

5. 从零到一阶段踩过的坑,以及我的排查链路

5.1 坑一:换台机器分数就变,原生依赖不一致

项目做到第三周,我把代码从开发机同步到另一台带卡的服务器上跑,同一个训练脚本,离线指标莫名掉了两个百分点。我第一反应是数据没同步,对比了目录里的 hash,发现数据一致;第二反应是代码版本不同,git log 确认是同一个 commit;最后怀疑依赖不一致,一对比锁定文件才发现,一台机器上 numpy 是 1.24,另一台是 2.0,而某个库的底层算法在 numpy 2.0 下行为发生了变化。这种依赖漂移在 AI 工程里特别隐蔽,因为它不影响程序报错,只影响数值结果。

我的排查链路是这样的:先固定代码与数据,排除最显眼的变量;再对比关键依赖版本,特别关注 numpy、pandas、scikit-learn 这类基础库;最后用 Docker 把训练环境固化。现在我在新机器上跑训练之前,会先跑一个项目自带的 smoke_test 脚本,里面内置了一个小额数据训练,输出指标必须落在预期区间。这个脚本五分钟就能跑完,却是整个项目最值得的投资之一。

5.2 坑二:训练时正常、服务化后反常,预处理链路不一致

另一个让我印象深刻的坑发生在服务化后的第二天。离线测试推荐结果看着很正常,但通过 API 调用返回的结果里,部分用户的 item 列表和离线完全对不上。一开始我认为是模型加载问题,重新加载了很多次都一样。后来我打印了 API 请求进入服务后的中间特征,发现服务器端对用户历史的编码方式和训练时不一致:训练时用户 ID 做了全局编号映射,服务端加载模型时却把编号重置了,导致同一个用户预测出来完全不同的结果。

定位到根因后,我把用户编号映射表、特征处理函数、模型权重打包成同一个版本发布,并且要求任何线上服务只能加载与模型同版本的 artifact。排查这个坑花了我大半天,但它让我养成了一个习惯:凡是在线服务涉及的预处理逻辑,一律复用训练时的同一份代码,绝不单独重写。很多看起来诡异的线上问题,根源都是训练和推理各写了一套逻辑。

提示:训练和推理共用一份特征处理代码,是 AI 工程里性价比最高的约定。

5.3 坑三:线上指标平稳,业务方却说效果变差了

最诡异的坑出现在上线一个月后。技术指标一切正常,P95 延迟稳定、错误率几乎为零、推荐覆盖率也没变化,但业务方反馈转化率在持续下滑。我第一反应是业务方自己的统计口径问题,沟通之后发现对方看的是"推荐位点击后到下单的转化",而这个指标在我的监控面板里根本没有。补上这个指标后,确实看到连续三周的缓慢下滑。

进一步排查发现,不是模型变差了,而是用户行为分布变了:随着时间推移,新用户比例上升,而训练数据的历史分布里老用户占比高。模型对老用户拟合得很好,对新用户却几乎是在盲猜。这就是数据漂移的一种——人群分布漂移。解决办法是每周对线上特征分布跑一次分布偏移检验,超过阈值就自动触发重训练;同时把冷启动用户单独建一个探索策略,不让模型直接背锅。这个坑给我的教训很直接:监控不是技术指标的陈列,而是业务指标的翻译。技术指标再稳,翻译不成业务语言,就等于没有监控。

6. 这个项目走到现在,我的真实体会和下一步打算

6.1 坚持记录的好处:这个仓库本身就是最大的产出

ai-engineering-from-scratch 这个项目走到今天,给我最大的收获反而不是技术能力,而是记录的习惯。每做一次实验,我都会写一小段复盘:当时想解决什么问题、用了什么方案、结果如何、踩了什么坑。这些记录平时看不出价值,但三个月后回看,它们就是"为什么当初这么选"的最佳答案。项目代码本身可能会过时,技术栈也会被替换,但那些关于决策原因的记录,会在你面对相似问题时直接节省大量试错时间。

我现在的做法是每次改动都先更新 README 里的决策日志,再写代码。这个顺序反过来,代码就很容易变成没人能解释的"黑盒"。对从零开始的人来说,养成记录决策的习惯,比学会某个框架更重要。

6.2 后续扩展:从传统推荐走向LLM应用,工程底座没有变

项目下一步的方向,是把这套最小闭环迁到 LLM 应用场景里。很多人以为从传统推荐到 LLM 应用是换一套技术栈,其实不是。你依然要管数据管道、实验追踪、服务化、监控和成本。LLM 的 API 调用延迟更高、成本更贵,所以成本思维和影子模式反而更重要;模型输出不稳定,所以可复现性和评估指标会更难设计,但那一整套工程习惯完全没有变。这也是我建议所有想追大模型热点的人,先把传统 AI 工程闭环走一遍的原因:磨刀不误砍柴工。

如果说最后还能分享一点个人体会,那就是"从零开始"这四个字听起来慢,实际却是最快的路径。直接套模板、抄配置,能让你今天跑通一个 demo,但解决不了明天线上出现的任何问题。亲手把环境、数据、训练、部署、监控走一遍,你就拥有了一个可以随时复现、随时扩展、随时排查的底座,这才是 AI Engineering 真正值钱的地方。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 7:12:42

Python+OpenCV双目视觉测尺寸:从标定到三维换算的完整实战

简介:这是一份面向计算机、通信、人工智能、自动化等专业学生与从业者的双目视觉测量项目资料,以Python结合OpenCV实现被摄物体尺寸的非接触式测量,可作为毕业设计、课程大作业或期末课程设计的参考方案,也适合具备一定基础后在此…

作者头像 李华
网站建设 2026/9/28 7:12:33

Prompt Engineering实战:用TaoToken统一Key打通结构化Prompt工程化链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 7:11:50

用Dify打造hindsight复盘助手:从后见之明偏差到自动化经验重放

hindsight这个词我一直觉得直接翻译成“事后诸葛亮”有点委屈它。英文里的hindsight,本意是“回看过去时的理解”,它是一面镜子,让你看清自己当时究竟漏掉了什么、哪里被认知盲区遮住了。真正的问题在于,这面镜子大多数人不会主动…

作者头像 李华
网站建设 2026/9/28 7:11:17

S7-1500博图产线例程精读:从OB/FB架构到通信报警实战

第一次真正看懂西门子S7-1500生产线例程,是在一个汽车零部件焊装项目上。那会儿我已经写了两三年单机设备程序,自认为对博图(TIA Portal)熟得很,结果打开总控程序还是被震了一下——不是指令用得有多花哨,而…

作者头像 李华
网站建设 2026/9/28 7:10:52

MSPM0G3507 GPIO控制实战:VS Code+逐飞库快速上手

1. 项目概述:为什么选MSPM0G3507配逐飞库做GPIO控制?TI的MSPM0G3507不是一块“新贵”,而是被很多老工程师悄悄盯上的“性价比黑马”。它属于MSPM0系列,是TI在2023年主推的超低功耗、高集成度Cortex-M0 MCU,主频48MHz&a…

作者头像 李华
网站建设 2026/9/28 7:10:50

FMT飞控移植RT-Thread实战:内核适配、SCons构建与协同调试

1. 为什么飞控移植不能只靠“抄代码”:FMT RT-Thread 的真实协作逻辑FMT 开源飞控、RT-Thread 实时操作系统、SCons 构建系统——这三个词凑在一起,不是简单拼凑的关键词堆砌,而是一条正在被越来越多嵌入式开发者验证的高效开发路径。我第一…

作者头像 李华