多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

从零搭建AI工程能力:数据、模型与推理服务实战指南

从零搭建AI工程能力:数据、模型与推理服务实战指南 1. 从零搭建AI工程能力为什么我劝你别一上来就调包这两年AI应用开发的门槛肉眼可见地降低了随便拉个框架、调个API就能跑出一个能对话的Demo。但我在团队里带过不少新人也面试过上百个号称“做过AI项目”的候选人发现一个很普遍的问题大家会用工具但不知道工具背后发生了什么。模型输出不稳定不知道从哪查推理速度慢不知道瓶颈在哪换个场景效果崩了只能反复试提示词试到怀疑人生。ai-engineering-from-scratch这个方向说白了就是把AI工程当成一门手艺来练而不是当成一堆API来调。它适合那些不满足于“能跑就行”、想真正理解从数据到模型再到服务这条链路上每个环节的人。不管你是刚转行想入局AI的开发者还是已经在做应用但总觉得根基不牢的工程师从零搭建一套自己的AI工程能力都是绕不开的一步。我自己的经历比较典型最早做推荐系统后来转做NLP应用踩过不少坑。最开始也是调包侠后来一次线上事故逼着我从头读了一遍Transformer的源码才发现很多“玄学”问题其实都有明确的工程解释。从那以后我开始有意识地补全自己在AI工程方面的短板从数据处理、模型训练、推理优化到服务部署一个环节一个环节地啃。这篇文章就是把我这些年积累的经验和教训整理出来给想走这条路的朋友一个参考。2. 整体思路拆解从零搭建AI工程能力到底在搭什么2.1 先搞清楚“AI工程”和“AI研究”的区别很多人把这两个概念混在一起导致学习路径完全跑偏。AI研究的目标是探索新方法、刷新榜单指标关注的是“能不能更好”AI工程的目标是把已有的方法稳定、高效、低成本地跑在生产环境里关注的是“能不能一直好用”。这两个目标对能力的要求差别很大。举个例子研究场景下你可以在Jupyter Notebook里跑一个模型数据加载慢一点无所谓推理时间长一点也能忍反正只跑一次。但工程场景下你的服务可能要同时处理上千个请求每个请求的延迟必须控制在几百毫秒以内还要考虑内存占用、并发安全、故障恢复。这些在研究里基本不会涉及但在工程里是每天都要面对的问题。所以从零搭建AI工程能力第一步不是去学最新的模型架构而是把数据管道、训练流程、推理服务、监控体系这四个基础模块搞清楚。这四个模块构成了AI工程的主干其他所有技术都是围绕它们展开的。2.2 为什么选择“从零手写”而不是直接上框架现在成熟的AI框架很多功能也很全为什么还要强调“from scratch”我的理由有三个。第一框架会掩盖细节而细节决定成败。比如你用某个高层API做文本分类一行代码就能搞定。但如果效果不好你根本不知道是分词出了问题、embedding没对齐、还是损失函数选错了。手写一遍之后每个环节的输入输出你都清清楚楚排查问题的时候就能快速定位。第二手写能帮你建立正确的直觉。很多工程决策依赖直觉比如batch size设多大、学习率怎么调、要不要做梯度裁剪。这些直觉不是看书能看出来的必须自己动手跑过、调过、错过才能形成。我见过太多人调参全靠随机搜索问他为什么选这个范围答不上来。这就是缺乏直觉的表现。第三手写过的代码才是真正属于你的。调包调多了你会发现自己的能力停留在“知道有这个工具”的层面一旦工具更新或者换了个场景又得重新学。但如果你理解底层原理不管上层工具怎么变你都能快速适应。当然我不是说所有东西都要手写。实际工作中该用框架的地方还是要用效率优先。但学习阶段手写一遍是值得的。我的建议是核心模块手写一遍辅助工具直接用现成的。比如数据加载你可以用现成的库但模型的前向传播和反向传播最好自己实现一次。2.3 能力搭建的四个阶段根据我的经验从零搭建AI工程能力可以分成四个阶段每个阶段的目标和重点不一样。阶段核心目标关键能力建议投入时间基础夯实理解AI工程全貌数据处理、模型训练基础1-2个月动手实践能独立完成小项目端到端流程搭建2-3个月性能优化让服务跑得又快又稳推理加速、资源管理3-6个月体系化形成自己的方法论架构设计、故障排查持续积累这个阶段划分不是绝对的每个人基础不同节奏也不一样。但大方向是这样先广后深先跑通再优化。3. 核心细节解析数据、模型、服务三个环节的关键点3.1 数据处理AI工程里最容易被低估的环节我在面试的时候经常问一个问题“你觉得AI项目里最耗时间的是哪个环节”大部分人回答模型训练或者调参。但实际上数据处理往往占了整个项目60%以上的时间。而且数据处理出问题后面所有环节都白搭。数据处理的核心任务包括数据清洗、格式转换、特征提取、数据增强、划分训练验证测试集。每个任务都有不少坑。先说数据清洗。真实场景的数据往往很脏有缺失值、异常值、重复值、格式不一致等等。我见过一个项目模型训练效果一直不好排查了两周才发现是数据里有大量重复样本导致模型过拟合。所以清洗这一步一定要做扎实该去重的去重该修正的修正。再说格式转换。不同来源的数据格式可能完全不同有的是CSV有的是JSON有的是数据库导出的。你需要把它们统一成模型能吃的格式。这里有个经验尽量早做格式统一不要等到训练的时候再转换。因为训练时转换会拖慢速度而且容易出错。特征提取是另一个关键点。对于文本数据你需要决定用词袋、TF-IDF还是embedding对于图像数据你需要决定用原始像素还是预训练特征。这个选择直接影响模型效果和训练速度。我的建议是先从简单的方法开始跑通之后再尝试复杂方法。不要一上来就用最先进的embedding因为你不一定需要而且调试成本高。数据增强在数据量不足的时候特别有用。文本可以用同义词替换、回译、随机插入删除图像可以用旋转、裁剪、颜色抖动。但要注意增强方法要和任务匹配。比如做情感分类你把“我喜欢这个”改成“我不喜欢这个”那就适得其反了。注意划分数据集的时候一定要保证分布一致。我见过有人随机划分结果训练集和测试集的类别比例差很多导致评估结果不可信。正确做法是分层采样确保每个类别在训练集和测试集里的比例接近。3.2 模型训练从能跑到跑得好模型训练这个环节新手最容易犯的错误是“一把梭”——把所有数据丢进去设个学习率跑完看结果。这样跑出来的模型效果好不好全看运气。正确的做法是小步快跑逐步调优。具体来说可以分成几步第一步用一小部分数据跑通流程。这一步的目标不是效果而是确认代码没有bug数据能正常流入流出损失能正常下降。我一般会取1%的数据跑几个epoch看看loss曲线是不是正常。第二步用全量数据跑一个baseline。这个baseline不需要调参用默认配置就行。它的作用是给你一个参照点后面所有优化都跟它比。第三步逐步调优。调优的顺序建议是先调学习率再调batch size然后调模型结构最后调正则化。学习率是最重要的超参数一般从1e-3开始试如果loss震荡就调小如果下降太慢就调大。batch size影响训练速度和内存占用一般设成2的幂次比如32、64、128。模型结构可以从简单到复杂先试小的效果不够再加层。正则化包括dropout、weight decay等主要在过拟合的时候用。这里有个经验不要同时调多个参数。因为如果效果变好了你不知道是哪个参数起了作用如果变差了你也不知道该回退哪个。每次只调一个记录结果这样才能积累经验。还有一个容易被忽视的点是随机种子。深度学习训练有随机性同样的配置跑两次结果可能不一样。所以做对比实验的时候一定要固定随机种子否则结论不可靠。我一般会跑三次取平均减少随机性的影响。3.3 推理服务让模型真正产生价值模型训练完只是第一步把它部署成服务、让业务方能用起来才是产生价值的地方。推理服务这个环节核心关注点是延迟、吞吐、稳定性。延迟是指单个请求的处理时间。对于实时交互场景延迟一般要求控制在几百毫秒以内。影响延迟的因素包括模型大小、输入长度、硬件性能等。优化延迟的方法有模型量化、剪枝、蒸馏、使用更快的推理引擎等。吞吐是指单位时间内能处理的请求数。对于批量处理场景吞吐比延迟更重要。提高吞吐的方法有批处理、异步推理、多实例部署等。稳定性是指服务在长时间运行和高负载下的表现。这里有几个常见的坑内存泄漏、线程安全问题、模型加载失败等。我建议在服务上线前做压力测试模拟高并发场景看看服务能不能扛住。提示推理服务一定要加监控。至少监控三个指标请求延迟、错误率、资源使用率。这样出问题的时候能快速定位。我见过一个服务上线后偶尔超时查了半天才发现是某个请求的输入特别长导致处理时间暴涨。如果早点加监控这个问题几分钟就能发现。4. 实操过程手把手搭建一个文本分类服务4.1 环境准备与依赖安装这一节我以文本分类为例完整走一遍从数据处理到服务部署的流程。选择文本分类是因为它足够简单适合练手同时又能覆盖AI工程的主要环节。环境方面我建议用Python 3.9以上版本主要依赖包括numpy、pandas、scikit-learn、torch、flask。如果你有GPU更好没有的话CPU也能跑只是慢一点。pip install numpy pandas scikit-learn torch flask这里解释一下为什么选这些库。numpy和pandas是数据处理的基础scikit-learn提供了很多现成的工具函数torch用来实现模型flask用来搭建服务。这些都是很成熟的库文档齐全遇到问题容易找到答案。注意安装torch的时候要根据你的硬件选择版本。如果有NVIDIA显卡装CUDA版本如果没有装CPU版本。具体命令可以去官网查不要直接pip install torch那样可能装到不匹配的版本。4.2 数据准备与预处理我用的数据集是一个公开的中文情感分类数据集包含正面和负面两类评论。数据格式是CSV两列text和label。import pandas as pd from sklearn.model_selection import train_test_split # 读取数据 df pd.read_csv(sentiment_data.csv) print(f数据总量: {len(df)}) print(f类别分布:\n{df[label].value_counts()}) # 划分训练集和测试集分层采样 train_df, test_df train_test_split( df, test_size0.2, stratifydf[label], random_state42 ) print(f训练集: {len(train_df)}, 测试集: {len(test_df)})这段代码做了两件事读取数据、划分数据集。注意stratify参数它保证训练集和测试集的类别比例一致。random_state固定随机种子保证结果可复现。接下来做文本预处理。中文文本需要分词我用jieba来做。import jieba def preprocess(text): # 分词 words jieba.lcut(text) # 去除停用词和标点 words [w for w in words if w.strip() and w not in stopwords] return words train_df[tokens] train_df[text].apply(preprocess) test_df[tokens] test_df[text].apply(preprocess)停用词表可以自己整理也可以用现成的。我建议自己整理一份因为不同场景的停用词不一样。比如做情感分类“不”这个词就不能去掉因为它会反转情感。4.3 模型实现与训练模型方面我用一个简单的TextCNN。选择TextCNN是因为它结构简单、训练快、效果还不错适合入门。import torch import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim, num_classes): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim) self.convs nn.ModuleList([ nn.Conv2d(1, 128, (k, embed_dim)) for k in [2, 3, 4] ]) self.fc nn.Linear(128 * 3, num_classes) self.dropout nn.Dropout(0.5) def forward(self, x): x self.embedding(x) # (batch, seq_len, embed_dim) x x.unsqueeze(1) # (batch, 1, seq_len, embed_dim) x [torch.relu(conv(x)).squeeze(3) for conv in self.convs] x [torch.max_pool1d(i, i.size(2)).squeeze(2) for i in x] x torch.cat(x, 1) x self.dropout(x) return self.fc(x)这个模型的核心思想是用不同大小的卷积核提取不同长度的n-gram特征然后拼接起来做分类。卷积核大小选了2、3、4分别对应bigram、trigram、4-gram。每个卷积核输出128个特征图最后拼接成384维向量。训练循环的代码比较长这里说几个关键点。损失函数用交叉熵优化器用Adam学习率设1e-3。每个epoch结束后在验证集上评估保存效果最好的模型。criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(10): model.train() for batch in train_loader: optimizer.zero_grad() outputs model(batch[input_ids]) loss criterion(outputs, batch[label]) loss.backward() optimizer.step() # 验证 model.eval() with torch.no_grad(): # 计算验证集准确率 pass训练过程中要关注loss曲线。正常的loss曲线应该是先快速下降然后逐渐平缓。如果loss震荡厉害说明学习率太大如果loss下降很慢说明学习率太小如果loss先降后升说明过拟合了。4.4 推理服务搭建与测试模型训练好之后用flask搭一个简单的HTTP服务。from flask import Flask, request, jsonify import torch app Flask(__name__) model load_model(best_model.pt) model.eval() app.route(/predict, methods[POST]) def predict(): text request.json[text] tokens preprocess(text) input_ids convert_to_ids(tokens) with torch.no_grad(): output model(input_ids) pred torch.argmax(output, dim1).item() return jsonify({label: pred}) if __name__ __main__: app.run(host0.0.0.0, port5000)这个服务很简单接收POST请求返回预测结果。实际生产环境还需要考虑更多东西比如批处理、超时控制、错误处理等。但作为练手这个已经够了。测试的时候可以用curl或者Python的requests库。curl -X POST http://localhost:5000/predict \ -H Content-Type: application/json \ -d {text: 这个产品非常好用}如果返回{label: 1}说明服务正常。5. 常见问题与排查技巧实录5.1 训练不收敛怎么办训练不收敛是最常见的问题表现是loss不下降或者震荡厉害。排查思路如下现象可能原因解决方法loss完全不降学习率太小、数据有问题、模型结构错误调大学习率、检查数据、打印中间输出loss震荡厉害学习率太大、batch size太小调小学习率、增大batch sizeloss先降后升过拟合加正则化、早停、增加数据loss降但指标不升评估方式有问题、数据泄漏检查评估代码、检查数据划分我遇到最多的情况是学习率设得不对。很多人直接用默认的1e-3但这个值不一定适合你的任务。我的经验是先用一个较大的学习率跑几步看看loss有没有爆炸然后逐步调小。比如从1e-2开始如果loss变成nan就降到1e-3再试。还有一个容易被忽视的原因是数据有问题。比如标签错了、特征全是0、样本顺序有问题等。排查方法很简单打印几个batch的数据看看人工检查一下。5.2 推理速度慢怎么优化推理速度慢的原因可能有很多需要逐步排查。首先确认瓶颈在哪。用profiler工具跑一下看看时间花在哪个环节。常见瓶颈包括数据预处理、模型前向传播、后处理。如果是模型前向传播慢可以考虑减小模型、量化、用更快的推理引擎。量化是把float32转成int8能显著减少计算量和内存占用但可能损失一点精度。我一般先试动态量化效果不够再试静态量化。如果是数据预处理慢可以考虑预处理好数据、用更快的分词工具、并行处理。我见过一个服务瓶颈居然在分词上因为用的分词工具是单线程的。换成多线程之后吞吐直接翻倍。如果是后处理慢比如排序、过滤可以考虑用numpy替代Python循环或者用Cython加速。提示优化之前一定要先测量不要凭感觉猜。我见过有人花了一周优化模型最后发现瓶颈在数据加载上。用profiler跑一下几分钟就能定位问题。5.3 服务上线后不稳定怎么办服务不稳定表现为偶尔超时、内存持续增长、并发高了就崩。这些问题往往和代码质量有关。内存泄漏是最常见的。Python虽然有垃圾回收但如果对象被全局变量引用就不会被回收。比如你把模型加载到全局变量里每次请求都往里面加东西内存就会一直涨。解决方法是请求级别的数据用完就释放不要挂在全局对象上。线程安全问题也常见。如果多个请求共享同一个模型实例而模型内部有可变状态就可能出问题。解决方法是要么加锁要么每个请求用独立的模型副本。加锁会影响并发性能独立副本会占更多内存需要权衡。超时问题一般是某个请求处理时间特别长导致的。解决方法有设置超时时间、限制输入长度、异步处理。我一般会设置一个合理的超时时间超过就返回错误避免拖垮整个服务。5.4 效果不好怎么排查效果不好是最难排查的因为原因可能有很多。我的排查顺序是数据、模型、训练、评估。先看数据。数据量够不够标签对不对分布有没有问题我见过一个项目效果一直上不去最后发现是数据里有30%的标签是错的。所以数据检查一定要做而且要认真做。再看模型。模型容量够不够结构合不合理对于文本分类TextCNN一般够用但如果任务复杂可能需要BERT之类的预训练模型。然后看训练。训练充分了吗有没有过拟合学习率调好了吗这些可以通过loss曲线和验证集指标来判断。最后看评估。评估方式对不对测试集有没有泄漏指标选得合不合适我见过有人用准确率评估不平衡数据结果模型全预测多数类准确率还有90%但实际没用。6. 我踩过的坑和总结的经验6.1 不要过早优化我刚开始做AI工程的时候总想着一步到位用最先进的模型、最复杂的架构。结果往往是花了很多时间效果还不如简单方法。后来我学乖了先用最简单的方法跑通确认流程没问题再逐步优化。这样即使出问题排查范围也小。比如做文本分类先用TF-IDF加逻辑回归跑一个baseline可能就能达到不错的效果。如果不够再上深度学习。这样既省时间又能建立一个参照点。6.2 日志和监控比你想的重要我早期做服务的时候不太重视日志和监控觉得能跑就行。后来一次线上事故服务突然挂了我查了半天不知道问题在哪因为没有日志。从那以后我养成了习惯关键环节一定要打日志核心指标一定要监控。日志要包含请求ID、输入摘要、处理时间、输出摘要、错误信息。监控要包含QPS、延迟分布、错误率、资源使用率。这些数据不仅能帮你排查问题还能帮你发现优化点。6.3 版本管理不只是代码AI项目里除了代码数据和模型也需要版本管理。我见过一个项目模型效果突然变差查了半天发现是数据更新了但没人记录。所以数据版本、模型版本、配置版本都要管理起来。简单的方法是用文件名加日期和版本号比如model_v1.2_20240101.pt。复杂一点可以用专门的工具比如DVC。但不管用什么方法关键是要有记录能追溯。6.4 测试要覆盖边界情况AI服务的测试和普通服务不太一样因为输入是自然语言边界情况特别多。空输入、超长输入、特殊字符、多语言混合这些都要测。我一般会准备一个测试集包含各种边界情况每次上线前跑一遍。还有一个容易忽视的是模型加载失败的测试。如果模型文件损坏或者路径不对服务应该能优雅地报错而不是直接崩掉。这个也要测。6.5 持续学习但不要盲目追新AI领域发展很快新模型、新工具层出不穷。保持学习是必要的但不要盲目追新。我的原则是先把基础打牢再关注新东西。基础包括数据处理、模型训练、推理优化、服务部署。这些是相对稳定的学会了长期有用。新东西可以关注但不要每个都跟选几个和你的方向相关的深入就好。最后分享一个我自己的习惯我会定期回顾自己做过的项目把遇到的问题和解决方法整理成文档。这个习惯帮我积累了很多经验也让我在遇到类似问题的时候能快速找到答案。如果你也在做AI工程建议你也试试。
返回列表