ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI工程从零到一:数据、模型、部署与迭代的完整实践

AI工程从零到一:数据、模型、部署与迭代的完整实践 1. AI工程到底在工程什么先搞清楚这四件事很多人第一次看到 ai-engineering-from-scratch 这个标题第一反应是又要从线性代数开始啃了或者是不是要手写一个神经网络才算数。我最初也这么想直到真正上手做过几个端到端的AI项目后才发现AI工程的核心不是造模型而是把模型变成别人能用、可靠、可持续迭代的产品。算法、数学、框架只是工具链的一部分甚至不是最花时间的那部分。我从一个具体场景说起。假设你要给公司做一个客户评论自动分类系统——把好评、差评、物流问题、质量问题分开。这件事的难度不在你用多花哨的模型而在于数据从哪来、数据脏到什么程度、线上跑推理要多少延迟、模型预测错了用户会不会骂、下个月业务调整了你改起来要多快。这一连串问题才是工程二字的分量所在。所以从零开始迈进这个领域之前我建议你先建立四件事的认知框架数据工程真实世界里没有现成的、干净的、标注好的数据等你下载。数据采集、去重、清洗、标注、切分、版本管理这些活通常占掉项目六成以上的时间。不信你去看任何一个Kaggle竞赛获胜方案复盘里写得最多的永远是特征和数据处理而不是网络结构。模型开发这是大家最熟悉的部分——选模型、调参、训练、评估。但请记住模型只是整个系统中的一个环节而且目前绝大多数业务场景用成熟的开源模型微调就够了真正需要从零设计网络结构的场景少之又少。部署与交付训练好的模型要变成一个可以被调用的服务——API封装、性能优化、容器化、监控、灰度发布。这块在国内技术社区里是讨论最少的但它恰恰是工程师和算法研究员的分水岭。迭代与运维模型上线只是开始。数据分布会漂移业务需求会变你要有机制持续监控模型效果、收集新样本、定期重新训练。我见过太多项目死在上线即停工因为团队只做了预测服务没做反馈回路。这篇文章写给谁写给准备转行AI工程的软件开发者和刚入门的算法实习生也写给那些已经能跑通教程但不知道如何落地完整项目的自学者。下面的所有步骤我都用一套可复现的小项目串起来你跟着走一遍就能搭出真正意义上的AI工程最小闭环。这也是我把文章标题定为from scratch的原因——不预设你已经有GPU、不预设你懂框架源码只预设你愿意跟着动手。2. 选型是第一道分水岭环境怎么搭、框架怎么选、为什么从零起步最容易犯的错是先学一堆东西再开始正确做法是边做边学遇到什么补什么。但有两个前置选型必须提前定好否则后面反复折腾的成本很高开发环境和深度学习框架。2.1 Python环境别再用系统Python裸奔了我见过太多新手在配置环境这一步上浪费了两三天——明明模型代码很简单时间全耗在为什么pip装一半报错为什么两个项目依赖打架上。你只需要做一件事装Miniconda任何项目开一个独立conda环境。# 安装Miniconda后执行 conda create -n ai-engineering python3.10 conda activate ai-engineering pip install ipykernel jupyter # 方便在Notebook里切换到这个环境为什么要3.10因为目前PyTorch、Transformers这些主流库对3.10的兼容性最稳定3.11、3.12也能用但没必要在入门阶段给自己增加环境变量上的风险。这个习惯我建议你保持到成为老手——每一个项目一个环境环境里只装你实际用到的包别管那个环境多空干净就是生产力。2.2 深度学习框架为什么我推荐从PyTorch起步AI工程领域现在基本是PyTorch和TensorFlow二分天下但新项目、开源模型、论文复现绝大多数都长在PyTorch生态里。原因很现实生态即效率。HuggingFace的Transformers库原生基于PyTorch最快速度用上最新开源模型训练加速库如DeepSpeed、Accelerate对PyTorch的支持最及时招聘市场上PyTorch经验也更容易换算成实际生产力。调试体验好。PyTorch是动态计算图你可以打印任意张量、任意中间结果这对于新手理解数据到底怎么流动的极其重要。TensorFlow 2.x虽然也默认动态图但历史包袱多网上答案新旧混杂容易让新手陷入信息泥潭。如果你担心以后公司用TensorFlow怎么办——相信我框架是工具思想是通用的。你把PyTorch的数据流、自动求导、训练循环吃透了切任何框架都只需要一两周的适应期。2.3 算力问题没有GPU到底能不能学这是被问得最多的一个问题我的回答是入门阶段完全能。这篇文章配套的示例项目——垃圾短信分类——用CPU跑一个简化版的文本分类模型训练时间控制在几分钟到十几分钟。你不需要一开始就面对大模型、大数据集。选任务的时候刻意选小数据、快迭代的项目等整套流程打通了再考虑用GPU加速和上大规模数据也不迟。真的需要云GPU的时候也别直接上高配。先用免费的很多平台提供免费CPU实例把代码逻辑调通再租按小时计费的GPU实例跑正式训练。代码没跑通之前GPU每一分钟都在烧钱这是新手最容易无视的成本项。2.4 项目目录从第一天起就按工程规范组织既然做的是工程而不是脚本目录结构一开始就别乱。我现在做任何项目都沿用同一套骨架ai-engineering-from-scratch/ ├── data/ # 原始数据与处理后数据 │ ├── raw/ │ └── processed/ ├── notebooks/ # 探索性分析、可视化、demo ├── src/ # 可复用代码模块 │ ├── data/ │ ├── models/ │ └── deployment/ ├── scripts/ # 训练、评估、导出脚本 ├── tests/ # 单元测试和集成测试 └── requirements.txt你可能会觉得我就做个练习项目搞这么复杂干嘛。但你很快就会体会到当你三天后再打开自己的代码如果数据、脚本、Notebook全堆在一个文件夹里你会连自己当时做了什么都想不起来。工程化习惯不是拿来炫耀的是拿来省时间的。3. 最小闭环实操垃圾短信分类的完整实现路径理论框架说完了现在进入正题。我选的任务是垃圾短信二分类理由是数据公开可下载、任务足够简单让新手专注流程、但文本处理-模型训练-评估每个环节都在麻雀虽小五脏俱全。3.1 数据准备第一课就是处理脏数据我用的是经典的SMS Spam Collection数据集公开渠道都能下到。下载下来后你会发现它是个txt文件每一行是一条短信格式是标签\t内容标签只有ham正常和spam垃圾两种。我们来亲手处理它。第一步是把txt读进来切成DataFrameimport pandas as pd # 数据文件路径按你的实际位置调整 data_path data/raw/SMSSpamCollection df pd.read_csv( data_path, sep\t, headerNone, names[label, text], encodingutf-8 ) # 标签二值化spam1, ham0 df[label] (df[label] spam).astype(int) print(df[label].value_counts())跑完你会看到输出大概是这样的0 4827 1 747这里立刻暴露一个真实业务中极其常见的问题类别不平衡——垃圾短信只占约13%。如果不做处理模型只需要把所有短信都判为正常准确率就能到87%但这个模型没有任何实用价值。这就是为什么我们接下来要选正确的评估指标。然后是切分数据集。我强调一条铁律训练集、验证集、测试集的切分必须在任何特征处理之前完成否则会发生数据泄漏——你用全体数据的信息去训练模型测试结果会虚高得离谱上线后立刻现原形。from sklearn.model_selection import train_test_split train_df, test_df train_test_split( df, test_size0.2, random_state42, stratifydf[label] ) train_df, val_df train_test_split( train_df, test_size0.1, random_state42, stratifytrain_df[label] ) print(f训练集: {len(train_df)}验证集: {len(val_df)}测试集: {len(test_df)})stratify参数按标签比例分层采样保证切完后每份数据的正负样本比例与原数据一致。3.2 模型选择先跑通一个笨模型再上聪明模型在文本分类上你可能会看到很多教程直接带你上BERT。我的看法是入门阶段先跑一个简单模型基线模型再升级到复杂模型。这样做有三个好处——快速验证数据链路没有bug、让基线结果成为你后面改进的参照系、帮助你理解复杂模型到底好在哪里。我的基线方案是TF-IDF 逻辑回归。TF-IDF是一种把文本转成数值向量的方法核心思想是一个词在文档中出现的次数越多越重要但如果在所有文档里都频繁出现重要性就得打折。逻辑回归则是经典线性分类器训练快、可解释性强。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline # 在训练集上fit在验证/测试集上只用transform vectorizer TfidfVectorizer(max_features5000, stop_wordsenglish) X_train vectorizer.fit_transform(train_df[text]) X_val vectorizer.transform(val_df[text]) X_test vectorizer.transform(test_df[text]) model LogisticRegression(max_iter1000) model.fit(X_train, train_df[label]) val_acc model.score(X_val, val_df[label]) test_acc model.score(X_test, test_df[label]) print(f验证集准确率: {val_acc:.4f}测试集准确率: {test_acc:.4f})重点说一下最后几行代码的用意vectorizer先用fit_transform学习训练集的词表之后验证集和测试集只允许transform。这是防止数据泄漏的关键一步——如果让测试数据也参与词表构建模型等于提前看到了答案的位置。3.3 评估指标准确率会骗人要盯住这四张表准确率在类别不平衡时非常误导。我们要同时看精确率、召回率、F1分数和混淆矩阵。这四个指标的关系我用一个生活类比解释你要在海量邮件中挑出垃圾邮件正样本。精确率你说是垃圾邮件的那些邮件里真的有多少是垃圾邮件精确率高意味着你很少冤枉好人。召回率所有真正的垃圾邮件中你拦住了多少召回率高意味着你很少放过坏人。F1精确率和召回率的调和平均两者冲突时的一个综合平衡。在垃圾短信场景里我觉得召回率应该优先关注——漏掉一条垃圾短信用户被骚扰的代价通常高于把一条正常短信误判为垃圾用户能自己识别。但不同业务权重不同这就是评估指标必须由业务场景决定的含义。from sklearn.metrics import classification_report, confusion_matrix y_pred model.predict(X_test) print(classification_report(test_df[label], y_pred, target_names[ham, spam])) print(confusion_matrix(test_df[label], y_pred))跑完这套基线你手上就有了一个可对比的基准。下面是数据不完全相同的示意输出但结构一致precision recall f1-score support ham 0.98 0.99 0.98 965 spam 0.96 0.88 0.92 1503.4 升级模型BERT微调其实没有想象中神秘基线模型的F1已经不错但业务要求更高的时候就该上深度学习模型了。我用Transformers库微调一个轻量级BERT变体全流程也就几十行代码。下面是最关键的一段——训练循环from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments ) checkpoint distilbert-base-uncased tokenizer AutoTokenizer.from_pretrained(checkpoint) model AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels2) # 文本编码padding到相同长度truncation截断超长文本 def tokenize_func(batch): return tokenizer(batch[text], paddingmax_length, truncationTrue, max_length128) train_enc tokenize_func(train_df) val_enc tokenize_func(val_df) training_args TrainingArguments( output_dir./results, evaluation_strategyepoch, num_train_epochs3, per_device_train_batch_size32, per_device_eval_batch_size64, logging_dir./logs, logging_steps200, ) trainer Trainer( modelmodel, argstraining_args, train_datasetto_dataset(train_enc, train_df[label]), eval_datasetto_dataset(val_enc, val_df[label]), tokenizertokenizer, ) trainer.train()我特意不展开to_dataset的实现细节因为Transformers库每个版本接口略有差异你只要查一下当前版本的数据集格式要求就能对上。想表达的核心是BERT微调的代码骨架远比你想的简单难点全在数据准备和理解Trainer的配置项。这里我补充一个Node训练时验证集指标好不等于测试集指标好更不等于线上效果好。BERT这类预训练模型尤其要留意过拟合——训练集上表现接近100%但验证集上却更差那就说明学过头了可以降低训练轮数或加早停。4. 能训练不等于能交付部署的最后一公里我见过很多从教程里走出来的学习者模型训练得有模有样但一聊到能不能给同事用一个网页调一下人就愣住了。训练环境和交付环境是完全不同的两套工程语境你本地有Python全套依赖、有成千上万的训练数据、有充足显存而线上服务器可能是精简环境别人只通过HTTP接口调用根本不关心你的训练细节。4.1 用FastAPI把你的模型包成一个服务部署其实就是回答一个问题别人怎么用我的模型最标准答案是封装成RESTful API——调用方发一个POST请求把文本放进JSON服务器返回预测结果。FastAPI是当前Python社区里写API最顺手的框架性能和开发体验都很好。下面最小可用的模型服务就这么点代码# src/deployment/app.py import pickle from fastapi import FastAPI from pydantic import BaseModel # 加载训练好的TF-IDF向量器和逻辑回归模型 with open(models/tfidf_vectorizer.pkl, rb) as f: vectorizer pickle.load(f) with open(models/logistic_model.pkl, rb) as f: model pickle.load(f) app FastAPI(titleSMS Spam Classifier) class InputText(BaseModel): text: str app.post(/predict) def predict(input: InputText): X vectorizer.transform([input.text]) proba model.predict_proba(X)[0][1] # 垃圾短信的概率 label spam if proba 0.5 else ham return {label: label, spam_probability: round(proba, 4)}如果你用的是BERT模型差别只是把加载代理由pickle换成torch.load或from_pretrained逻辑一模一样。我建议你基线模型、BERT模型的服务各写一次对比一下两者的区别——你会发现服务的骨架完全一样变的只是模型加载方式。这样你在面试或实际工作中碰到快速上线一个模型的任务心里就有底了。启动服务uvicorn src.deployment.app:app --host 0.0.0.0 --port 8000然后随便找个HTTP工具测一下curl -X POST http://localhost:8000/predict \ -H Content-Type: application/json \ -d {text: 恭喜您中奖点击链接领取奖品}返回结果会告诉你这是spam概率多少服务跑通了。4.2 Docker打包让模型服务在别人机器上也能跑本地能跑的服务不算完成Docker打包才是交付的正式姿势。Docker解决的核心问题是环境一致性——你总不能要求每个要用你服务的人都去装一遍Python和所有依赖。基础Dockerfile长这样FROM python:3.10-slim WORKDIR /app # 先拷贝依赖文件并安装利用Docker层缓存加速后续构建 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再拷贝代码和模型文件 COPY src/ ./src/ COPY models/ ./models/ EXPOSE 8000 CMD [uvicorn, src.deployment.app:app, --host, 0.0.0.0, --port, 8000]构建并运行docker build -t sms-classifier . docker run -p 8000:8000 sms-classifier这里有几个工程细节想提一下。为什么先复制requirements.txt再复制源码因为Docker构建时会缓存每一条指令的结果只要你没改requirements.txtpip install这一步就不会重新执行如果顺序反了每改一行代码都要重装所有依赖开发体验极其痛苦。模型文件可能很大建议单独做一层或者用启动时下载的方式避免镜像体积膨胀。4.3 服务上线前的自测清单我在一次次部署中总结了一份自测清单每次上线前都过一遍本地启动服务后用curl请求过正常文本和边界文本比如空字符串、超长文本、全角符号确认不会报500。模型预测的置信度低的分界区域是什么样的比如spam概率0.45~0.55之间这摊模糊地带的样本长什么样子服务重启后能否正常工作如果模型是加载到内存里的大模型要重点测算加载时间。并发请求上来时服务会不会挂至少要知道单进程能扛多少并发心里有数。其中模糊地带这个观察格外值得做。因为它能帮你判断模型整体质量中最不确定的部分是否可接受如果很多正常短信都落在0.5临界值附近那用户很快就会发现系统不太聪明。5. 从零到一最容易踩的六个坑我替你提前踩过了最后一个章节我把从零开始做AI工程最常见的坑集中写在一起全部来自我自己的实战经历按出现频率排序。5.1 环境依赖的版本地狱新手最常见的报错是ModuleNotFoundError和RuntimeError: CUDA error: no kernel image is available这类版本错配问题。PyTorch、CUDA、cuDNN、Transformers之间有一张巨大的兼容性矩阵你直接装最新版往往没事但跟着某个博客装了一套老版本组合就寸步难行。我给的建议不要自己造轮子去解决版本问题永远用虚拟环境隔离并且把最终可运行的依赖版本组合固定到requirements.txt里。pip freeze requirements.txt这个文件就是你的环境快照哪天搞坏了环境一条命令重建conda create -n ai-engineering python3.10 conda activate ai-engineering pip install -r requirements.txt5.2 数据泄漏所有错误里最隐蔽、代价最大的一种数据泄漏有很多表现形式。最常见的是先做全量数据的标准化/词表构建再切训练测试集。这会让测试集见过训练集的信息测试分数虚高上线后策略效果直接打折。另外一个隐蔽变体你用测试集的表现反复调优模型调了几十次以后测试集其实已经变成了验证集。严格的做法是训练集、验证集、测试集三分离验证集用来调参测试集只在最终评估时用一次。5.3 只看准确率业务一上线就翻车这个坑我在评估指标那节说过但值得再强调一次。类别不平衡时准确率没有任何参考价值。建议你养成一个习惯每次跑完模型先看混淆矩阵再看分类报告。混淆矩阵能直观告诉你误报和漏报分别是哪些样本这两个错误方向在业务上代价完全不同。5.4 把Notebook里的变量状态当成了代码用Jupyter Notebook做实验没有问题但很多新手会依赖先跑过某个cell变量还在内存里的状态。今天能出结果明天重开Notebook全忘了。我的建议是Notebook只做探索性分析和可视化正式代码都写成.py脚本这样训练流程可以在命令行一键复跑。python scripts/train.py --model logistic --data data/processed/train.csv5.5 训练时的显存管理不当即便你后面用上了GPU显存管理也是一门课。最容易犯的错是训练循环里忘记写optimizer.zero_grad()导致梯度跨batch累积训练曲线直接发散另一个是backward之后没做梯度裁剪一遇到梯度爆炸就NaN损失。下面是PyTorch训练循环里几个必写项# 每个batch都要清零上一轮的梯度 optimizer.zero_grad() loss.backward() # 梯度裁剪防止梯度过大导致训练发散 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step()5.6 一上来就追求最优模型最后一个坑也是心态问题。我见过太多人一上来就研究SOTA模型、超参数搜索、复杂的特征工程结果项目两周了还没跑通一条完整的pipeline。正确的推进顺序永远是一小时内跑通最小闭环 → 评估 → 改进 → 再评估。先用笨蛋模型把整条链路打通包括数据、训练、评估、部署然后每一轮只改一个变量看效果变化。这个方法论可以用在你未来任何AI项目上它不是简化版的做项目而是做项目的正确方式本身。写在最后的几句实在话做完这个最小项目你其实已经把AI工程的主干全部过了一遍——数据处理、模型训练、评估、部署、迭代意识。接下来每个人的路会分岔想深入算法去啃你项目中某个具体模型的论文和源码想走工程方向去把部署这块做扎实学容器编排、监控、CI/CD想转业务方向去把一个真正业务问题完整走一遍。我自己走下来的体会是AI工程没有学完的那一天但从零到一这个阶段确实有个明确终点——当你面对一个陌生任务时脑子里不再是我不会而是我知道流程也知道关键风险在哪。文章里的这套项目路径你认真跑一遍就会到达这个终点。之后就是经验积累和时间问题了。
返回列表