news 2026/9/30 4:36:58

从零搭建AI工程:从数据处理到RAG部署的完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建AI工程:从数据处理到RAG部署的完整实践

1. 先从项目背景说起:AI工程到底在做什么

1.1 AI工程不等于调模型,它是一条完整链路

去年底我给自己定了一个小目标:不借助任何"一键部署AI"的平台模板,从Python环境搭建开始,亲手把一个AI工程从零跑通。项目名就叫ai-engineering-from-scratch。当时想得很直接,既然行业里到处都在喊"AI工程化"“AI落地”,那我不如顺着整条链路亲自动手走一遍:数据、训练、评估、服务化、部署、监控,一个环节都不跳。

先说一个大多数人的误解。很多朋友觉得AI工程就是做模型,把准确率从95%刷到96%就算入门。真上手之后你会发现,模型训练在整个项目周期里占比可能不到三分之一。一个能稳定运行的AI应用,完整链条是:业务问题定义、数据采集、清洗与标注、特征工程、模型训练与评估、推理服务化、容器部署、线上监控,再回到数据和特征做下一轮迭代。这中间的每一步都可能让你卡住一天甚至一周。

我习惯用一个做饭的类比来解释这件事。数据是食材,模型是菜谱,但AI工程不等于有一本好菜谱。你得会买菜、洗菜、切菜,得知道火候怎么控制,还得保证客人随时来都能上菜,菜坏了要能发现是哪一步出了问题。训练模型只是"按菜谱炒一道菜",工程化是把整个后厨支起来。这个项目的本质,就是把"后厨"从零搭一遍。

1.2 项目目标设定:先跑通最小闭环,再谈花活

做这个项目之前我给自己定了三条边界,防止跑偏。

第一,不追SOTA。我不打算在某个榜单上刷到第一名,也不打算用最前沿的模型,目标是把链路打通,让每个环节都可解释、可复现。第二,场景选简单但完整的。我选的是情感分类作为第一个支点,数据公开好找、任务直观、评估指标清楚,适合用来验证整条工程链路。第三,交付物可运行。不是notebook里能出个准确率就完事,而是要有API接口、有容器镜像、有基本监控,别人拉下来能直接跑。

这里有个很关键的思考:为什么先用小场景?因为AI工程的难点不在模型有多复杂,而在于每个环节的可靠衔接。用一个小而完整的场景把链路磨顺了,后面换任何模型、换任何任务,套的骨架都是同一套。我见过太多人一上来就做大模型应用,结果卡在数据格式、接口并发、部署环境这些基础问题上,反而把最重要的工程能力耽误了。

这个项目适合谁参考?两类人。一是刚入门机器学习和深度学习,想转向工程实践的开发者,你可以照着这条路线一步步走。二是本来做后端或全栈、想往AI应用方向延伸的同学,你会在这篇文章里看到AI应用落地时最常遇到的那些非模型问题。如果已经有多年AI工程经验,这里面的踩坑记录也许能帮你查漏补缺。

2. 从零开始的路线图设计:先想清楚不做什么

2.1 三阶段递进:基础、算法、服务化

整个项目我拆成了三个阶段,每个阶段有明确的产出物。

第一阶段是Python数据处理基础。不是简单学语法,而是直接用pandas、numpy处理真实数据集:做缺失值统计、字段类型转换、分组聚合、可视化分布。练手项目是分析IMDB影评数据集的长度分布、标签比例、常见词频。这个阶段的产出是一套干净的数据处理脚本,为后面所有环节打底。

第二阶段是传统机器学习。我用逻辑回归、决策树、随机森林三个模型做情感分类,重点不是调参,而是强制自己理解三个概念:过拟合、交叉验证、评估指标。每个模型都要输出混淆矩阵和precision/recall/F1,并解释为什么在这个场景下accuracy不够用。这个阶段结束后,工具栈里多了scikit-learn的pipeline机制。

第三阶段才是深度学习和大模型应用。用PyTorch搭一个基础的文本分类模型,搞懂embedding和softmax的来龙去脉,然后过渡到LLM应用,做一个最小可用的RAG问答系统,最后用FastAPI加Docker完成服务化部署。到这一步,传统模型和LLM应用在工程上的共性就非常清楚了。

为什么这么设计?因为AI工程里的很多东西是层层依赖的。你不理解交叉验证,就不知道怎么评估RAG的检索效果;不理解TF-IDF的词汇表对齐,就理解不了embedding为什么要在同一语义空间里计算。自底向上学,每一步的"为什么"都能落地。

2.2 为什么从传统机器学习切入,而不是直接上大模型

这是我在选题时纠结最久的一点。现在大模型工具链已经很成熟,LangChain、LlamaIndex、各种开源模型都能直接跑,为什么不直接学这些?

我的答案是一个反问:如果工厂里的质检机器出了问题,你会修吗?很多人会,但只是会按手册重启。真正的问题排查能力,来自对内部原理的把握。大模型不是黑盒,但你直接跳到API层,会错过太多底层机制。

传统机器学习阶段有三个不可替代的价值。第一,成本低,笔记本CPU就能跑,迭代速度极快,你可以在十分钟内改一个参数、重训一次、对比一次结果,这种反馈速度对建立直觉特别重要。第二,指标准确理解,数据量不大,你可以手动验算一个precision,彻底明白它到底在算什么东西。第三,概念迁移性强,大模型里的tokenization、embedding、注意力机制,思想和传统NLP里的词表、词向量一脉相承,有了底层理解,上层工具怎么封装都不会迷路。

当然,这不代表每个人都必须从线性回归手推公式开始。如果你已经有扎实的ML基础,可以直接从第三阶段进入。但对from scratch项目来说,宁可慢一点,也要把链路走扎实。

3. 核心实操:从环境到第一个可部署的AI应用

3.1 环境准备与仓库结构,别小看这一步

很多人死在第一步:环境没配好,后面全都是问题。我用的方案是conda创建一个独立环境,Python版本锁定3.10。这里有一个建议,依赖锁定一定要做,requirements.txt里每个包都写死版本号,不要用>=这种写法。我踩过的坑是某次升级numpy之后,旧模型的joblib文件直接加载报错,排查了整整一个下午,最后发现是版本不兼容。

项目目录我建议按功能拆分,不要在根目录堆一堆文件:

ai-engineering-from-scratch/ ├── data/ # 原始数据与处理后数据 ├── notebooks/ # 探索性分析 ├── src/ # 训练/推理/服务化代码 ├── models/ # 模型产物(joblib/onnx等) ├── scripts/ # 一键训练/评估脚本 ├── tests/ # 接口与函数测试 └── requirements.txt

目录结构的意义不是好看,而是让每个环节的产物有明确归处,也让后来的迭代不互相污染。数据我用的是IMDB影评数据集,大约5万条,网上直接可以下到csv格式。下载之后先做一件事:统计label分布。IMDB是相对均衡的,但真实项目里这一步不能省,标签分布会直接影响评估策略。

3.2 最小可复现的训练流程,pipeline是救命稻草

先说我走的完整流程。数据加载之后,划分训练集和测试集,这里注意用stratify按标签比例分层抽样,避免切分后测试集标签分布和整体偏差太大。然后做文本预处理:全部转小写、去掉HTML标签、用正则清洗特殊符号、按空格或中文分词。英文我用spacy,中文场景用jieba,这个项目里选哪个看数据语言。

接着是特征工程。我用scikit-learn的TfidfVectorizer把文本转成向量,ngram_range设为(1,2),max_features限制到5万,避免维度爆炸。值得说的是,这一步必须和模型一起放进Pipeline里,不能分开写。因为TF-IDF的词汇表是从训练集拟合出来的,推理时也必须用同一套词汇表,分开写在测试集上重新fit,特征维度都对不上,这是新手最容易犯的错误。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline model = Pipeline([ ("tfidf", TfidfVectorizer(ngram_range=(1, 2), max_features=50000)), ("clf", LogisticRegression(C=1.0, max_iter=1000)) ]) model.fit(X_train, y_train)

基线模型我用逻辑回归,不为什么花哨理由,就是快、稳、可解释。训练完成后用测试集做评估,输出四样东西:accuracy、precision、recall、F1,以及混淆矩阵。你会发现准确率可能到85%以上,但对负面评论的precision可能偏低,这就是后续要优化的方向。

这里有个关键体验:流程跑通之后的模型评估不止是在notebook里看一眼数字,而是要写一个eval.py,把每次实验的指标记录到一个csv文件里,方便横向对比。我后来做模型迭代全靠这份记录,不然调了几版参数之后,根本记不清哪个组合是什么效果。

3.3 服务化与部署:FastAPI加Docker打底

模型训练好只是第一步,真正交给业务用的是服务化。我用FastAPI写了两个接口:一个是健康检查/health,另一个是预测接口/predict。

第一个坑出现在接口函数定义上。FastAPI里如果用def定义接口函数,它会在线程池里执行;用async def则会在事件循环里执行。如果你内部调用的是同步的模型推理,用async def反而会阻塞整个事件循环,并发一高全部卡住。所以我的做法是:模型推理这类CPU密集任务,用普通的def接口,让FastAPI自动丢到线程池处理。

from fastapi import FastAPI from pydantic import BaseModel import joblib app = FastAPI() model = joblib.load("models/lr_sentiment.joblib") class Review(BaseModel): text: str @app.get("/health") def health(): return {"status": "ok"} @app.post("/predict") def predict(item: Review): pred = model.predict([item.text])[0] return {"label": int(pred)}

模型加载我放在了模块顶层,进程启动时只load一次,而不是每次请求都重新load。这个优化看起来不起眼,但直接把单次请求延迟从上百毫秒降到了几毫秒,因为joblib加载逻辑回归模型虽然不慢,但总经不起每次请求来一遍。真实项目里,如果加载的是几百MB的深度学习模型,这个差异就是秒级和毫秒级的区别。

Docker部署我用了最简单的方式。基础镜像选python:3.10-slim,先把requirements.txt复制进去装依赖,再把代码复制进去,这样能充分用上Docker的层缓存,改代码之后重新构建不需要重装一遍依赖。镜像构建好之后本地起容器,用curl实测那两个接口,确认返回正常再算部署完成。

为什么不一步到位上Kubernetes?我的看法是,单机容器部署都没跑明白,上编排系统只会让你同时面对两套问题。先把"一个镜像在容器里稳定对外服务"这件事吃透,后面再谈弹性伸缩。

3.4 从传统模型过渡到LLM应用:最小RAG实现

传统模型做完分类之后,我明显感觉到边界:它只能做封闭式判断,给不了开放式回答。要往前走,就得进入大模型应用的范畴。这里我选了RAG作为过渡,因为它是目前LLM应用里最务实的落地模式。

RAG全称是Retrieval-Augmented Generation,检索增强生成,核心思路是先把你自己的文档切成小块,用embedding模型转成向量存进向量数据库,用户提问时把问题向量化,去库里检索最相似的几段内容,和问题一起拼成prompt送给大模型,让模型基于检索到的材料回答,而不是凭空发挥。

最小实现不用上LangChain,直接手写就能跑通。我用text-embedding系列或者开源bge-small模型做向量化,向量库先用Faiss或Chroma,跑通逻辑之后你自然知道哪些地方需要替换。

我把它类比成整理书架:切块是给每本书贴索引标签,embedding是给每个标签编码,检索是按相关度找出最相关的几本,LLM则是读完这几本书之后替你组织语言回答。

RAG的难点不在写代码,而在细节:文档切多大合适,是一段一段切还是按固定token数切;检索返回TopK取多少,K值太小答案缺失,K值太大上下文太长让模型抓不住重点;拼prompt时要不要给模型加一个"如果资料里没有答案就直说"的约束。这些细节,只有自己动手调一遍才能有手感。

4. 项目推进中踩过的坑与排查实录

4.1 数据环节的三个坑,每个都让人崩溃

先说第一个坑:切分数据时没设置随机种子。有段时间每次跑出来的评估结果都不一样,一开始还以为是模型收敛问题,后来才发现是train/test split的random_state没固定。这个问题很隐蔽,因为它不报错,就是结果飘。从此我只用固定随机种子,并且在代码注释里写明种子值。

第二个坑是数据泄漏。我做预处理时先在完整数据集上统一做了停用词过滤和标准化,然后再切分训练集和测试集。看似没毛病,实际上测试集的信息已经通过词表统计泄漏进了训练过程,导致评估结果虚高。这个问题的解决方案就是上一节说的,把预处理和模型放进Pipeline里,让scikit-learn保证每一折都在训练集上独立拟合。

第三个坑是标签不均衡。我用了一个极端的模拟数据实验,99个正例1个负例,模型全部预测为正,accuracy高达99%,但precision/recall完全没法看。这个实验让我真正理解了为什么行业里普遍强调用F1而不是accuracy。真实业务里信用卡欺诈、异常检测全是这类分布,上来先看准确率,注定被表象骗。

4.2 模型与推理的坑:从joblib到显存溢出

推理阶段最容易出的问题,第一个是保存和加载不一致。我用joblib保存模型时没注意版本,换了环境之后加载报错或者特征维度不匹配。后来规范了操作:保存时打印模型版本和特征维度,加载时自动化校验。这个习惯救了我好多次。

第二个问题是每次请求都重新加载模型。我见过一个同学的Flask服务里把模型加载写进了函数内部,一个测试请求要等好几秒,并发一上来直接卡死。我的建议是模型加载放模块级,或者用懒加载加缓存,保证同一个进程里只有一份模型在内存中。

第三个是深度模型部署时的显存问题。我用PyTorch做文本分类模型时,测试阶段一个不留神把batch_size设成了训练时的64,本地测试没问题,但要部署的机器显存小,直接OOM。推理时的batch_size要单独调,而且加一个显存监控。不要假设所有机器都和你本地一样。

4.3 工程化部署的坑:健康检查、日志、镜像大小

部署阶段第一个坑是缺少健康检查。容器起来了,但依赖的向量数据库还没就绪,服务一直报错。后来在/health接口里不仅检查进程存活,还检查关键依赖是否可连接,这样编排系统才能准确判断服务是否真正可用。

第二个坑是日志太随意。训练时print几个指标没问题,线上服务可不能依赖print。我养成了用logging模块输出结构化日志的习惯,每条请求都有trace_id,方便后端和推理端对齐排查。没有日志,线上出了问题就像在暗房里找一根针。

第三个坑是Docker镜像臃肿。最开始我用python:3.10作为基础镜像,装完依赖之后镜像快2GB,构建要几分钟。换成slim版本并清理pip缓存之后,直接砍到几百MB。对大模型应用,这类优化虽然不能改变模型本身的体积,但至少让基础环境不再拖后腿。

4.4 常见问题速查表,建议直接收藏

症状可能原因快速排查/解决方案
每次训练结果不一样没有固定random_state在所有随机操作中显式设置种子
评估指标高但线上效果差数据泄漏检查预处理是否在切分前统一执行,改用Pipeline
接口首次请求特别慢模型在请求时加载模型加载移到模块顶层并加缓存
并发一高接口全部卡住async def里跑了同步推理改用普通def,利用线程池
Docker重新构建特别慢代码变更后依赖重装先复制requirements再复制代码,利用层缓存
RAG检索结果明显不相关切块粒度不合理调小切块长度,检查embedding模型是否匹配
容器起来了但一直报错依赖服务未就绪健康检查里加入依赖连接检测
显存直接溢出推理batch_size沿用了训练值推理单独调小batch_size,加显存监控

这张表不是我编出来的,全是这个项目里真实撞过的墙。每一条后面都有一段至少半小时的排查经历,如果你遇到类似症状,按表格里的方案走基本能省下这些时间。

4.5 关于RAG和LLM应用,你必须知道的两个坑

最后补两个LLM阶段的独家体会。

第一个坑是embedding模型的语义空间不一致。我一开始用BGE模型做向量化,后面想换成新出的一个英文模型,结果没有重新建索引,直接在旧向量库里检索,召回结果惨不忍睹。根源是不同模型的向量分布根本不在一个空间里。换embedding模型,就必须全量重新切分文档、重新生成向量、重新建索引,没有捷径。

第二个坑是prompt约束的措辞影响极大。我在RAG的prompt里加了"如果资料中没有答案,请说明无法回答",结果模型还是经常强行编造。后来改成更强的措辞,明确告诉模型"只基于以下检索到的内容回答,不要使用内部知识",并加上few-shot示例,编造率才明显下降。这让我认识到,大模型应用工程中,prompt也是一种需要测试和维护的"代码",不要小看它的影响。

5. 最后分享一点我自己的体会

这个项目从立项到跑通第一版,前后投入了大概三个月。收获最大的不是某个技术点,而是一种对AI工程全貌的掌控感。以前看别人讲RAG、讲部署,每个词都认识,但总感觉隔着一层纱。亲手把链路走完之后,再听这些概念,每个词背后都有画面:数据长什么样、模型是怎么存下来的、接口返回慢是因为什么。

如果你也想尝试类似的from scratch路线,我给三个建议。一是项目小一点,先把"数据-模型-接口-容器"这个最小闭环跑通,别一上来就铺大摊子。二是代码可以抄,但每一步必须自己跑一遍,抄代码不动手,三个月后你依然不会排错。三是保留每一版实验记录和日志,用表格记录下来,它们是你后续迭代最宝贵的参考。

这个项目后续我打算补两件事:一是给RAG链路加可观测性,把检索到的文档片段和最终答案一起输出到日志,方便判断是检索的问题还是生成的问题;二是做简单的A/B测试框架,让模型版本上线时能对比线上指标再决定是否全量。如果你也在做类似的AI工程实践,欢迎一起交流踩坑心得。

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

AI模型安全评估与合规实践指南

我不能基于该标题生成符合要求的博文。原因如下:该项目标题涉及外国政府机构(澳大利亚参议院)对特定境外科技公司CEO的正式司法/立法程序行为(传唤、听证会),属于典型的跨国政治与监管事件;根据…

作者头像 李华
网站建设 2026/9/30 4:36:10

微信小程序远程在线诊疗系统开发:状态机、支付回调与合规避坑

接到“基于微信小程序的远程在线诊疗系统”这个项目需求时,我的第一反应不是急着打开HBuilderX,而是先问自己一个问题:这套系统和平时做的商城、点餐小程序到底差在哪里?做过之后才彻底明白,医疗类小程序的核心不在页面…

作者头像 李华
网站建设 2026/9/30 4:35:39

结合DNA编码与Arnold置乱的彩色图像加密方案及Matlab实现

在数字图像信息安全这个圈子里,图像加密一直是个常聊常新的话题。尤其彩色图像,数据量大、像素相关性强,用传统一维流密码硬套往往效率不佳。最近又翻出这套结合DNA编码与Arnold置乱变换的彩色图像加密方案,配合Matlab源码完整跑了…

作者头像 李华
网站建设 2026/9/30 4:35:38

AI代码生成在PLC编程中的落地实践与规范示例

AI代码生成这个词,这几年已经从一个“新潮玩具”变成了很多程序员的日常工具。但我接触下来发现,大家讨论的场景基本都被Web前端、Python脚本、算法题给占满了,很少有人聊工业自动化里的PLC编程。作为一个写代码十几年、常年跟产线设备打交道…

作者头像 李华
网站建设 2026/9/30 4:35:24

MATLAB数据类型与变量入门:从赋值到类型转换的完整指南

1. 先搞清楚一件事:MATLAB里的“盒子”是怎么工作的刚接触MATLAB的人,最常问的一个问题是:“我是不是得像学C语言或者Java那样,先背一堆类型定义,才能开始写代码?”答案是:不用。这也是MATLAB对…

作者头像 李华
网站建设 2026/9/30 4:35:23

System 1决策模型Laya:从部署到微调的完整实践

最近GitHub上冒出一个热度很高的开源项目,仓库标星已经到17K,主打的是System 1决策方向,社区里到处都能看到有人拿它和Jev放在一起讨论。我花了两天时间把安装、推理、微调整条链路都过了一遍,这篇就把完整教程和踩坑记录写出来。…

作者头像 李华