
很多朋友问我整天说“AI工程”它和“AI算法”“机器学习”到底是不是一回事我最初也以为写几个模型脚本、调通一个训练流程就算入门了真正动手做项目才发现从模型到可用系统之间隔着一条叫做“工程化”的河。这篇博文就围绕ai-engineering-from-scratch这条主线把我的爬坑经历、项目设计和落地经验完整梳理了一遍。不管你是想转行AI工程、还是已经在做算法想补工程短板这篇文章应该能帮你省下大把试错时间。1. 内容整体设计与思路拆解1.1 AI工程到底在解决什么问题先打破一个误区AI工程不是“把模型训练出来”而是“让模型稳定、可控、可维护地跑在生产环境里”。训练一个准确率90%的分类模型在Jupyter Notebook里可能只需要几十行代码但要让这个模型每天处理百万请求做到延迟稳定在100毫秒以内、异常自动告警、数据分布漂移时可追溯、模型可以随时回滚版本这就是另一套完全不同的知识体系了。我从零开始搭建AI工程能力时首先梳理的就是完整链路。一个真正落地的AI系统至少包含数据层采集、清洗、特征工程、模型层训练、评估、调优、服务层API封装、推理优化、并发控制和运维层监控、日志、CI/CD、模型版本管理。这四个层不是孤立的它们之间通过数据流、特征协议、模型接口和部署脚本紧密耦合。很多人在第一步就犯了错一上来就追新模型把大量精力花在模型结构的排列组合上结果数据管道稀烂、特征质量堪忧、服务上线后四处漏风。我的思路是“工程先于模型”。先搭好数据管道和评估体系再谈模型选型和训练优化。因为模型可以换但数据质量差、评估方式不靠谱换什么模型都救不回来。这条原则贯穿了我整个从零到一的过程。1.2 技术路线选型的核心考量选型这件事我前后推翻了三版方案。第一版贪多觉得什么都得会TensorFlow、PyTorch、Spark、Flink全都要碰。结果就是样样通、样样松项目推进极慢。第二版又走向另一个极端只盯住一个模型库其他一概不管结果部署时发现推理性能和业务需求脱节。第三版才摸索出合理套路按业务场景倒推技术栈按团队能力选熟悉工具按系统瓶颈定优化方向。以我做的文本分类项目为例数据量在百万级以内单机训练可以搞定就没必要上分布式框架推理放在CPU上跑就要关注模型量化和推理框架的选择而不是盲目堆GPU。更关键的是我给自己定了一条选型红线新工具必须能解决当前存在的具体痛点否则一律不引入。这条红线帮我避开了无数“看起来有用”的陷阱比如那些需要维护两套环境才能跑通的实验框架、和现有监控体系没法打通的调度平台统统在评估阶段就被淘汰了。2. 核心细节解析与实操要点2.1 从零搭建AI工程环境的完整步骤环境搭建是第一道坎也是劝退最多人的地方。我不推荐在一个系统里混装所有东西那样后期维护成本极高。实操中我采用的方案是这样的第一层是Python版本管理强推pyenv好处是按项目隔离Python版本避免系统Python被污染。第二层是虚拟环境用venv或者conda都可以我一般只在GPU相关的深度学习项目里用conda方便管理CUDA和cuDNN依赖其余项目一律venv。第三层是依赖锁定必须用pip freeze或poetry.lock锁定全量依赖版本避免“在我机器上能跑”这种经典纠纷。下面给出我常用的初始化脚本把这个跑完就得到一套干净的基础环境# 安装 pyenvmacOS 示例Linux 类似 brew install pyenv echo export PYTHON_CONFIGURE_OPTS--enable-shared ~/.zshrc # 安装指定 Python 版本并创建虚拟环境 pyenv install 3.10.13 pyenv virtualenv 3.10.13 ai-eng-scratch pyenv activate ai-eng-scratch # 核心依赖类别 pip install numpy pandas scikit-learn matplotlib jupyter pip install torch --index-url https://download.pytorch.org/whl/cpu # CPU版示例 pip install fastapi uvicorn onnxruntime pytest每一类依赖我都是刻意组合的。numpy/pandas 管数据处理scikit-learn 管基线模型和评估指标torch 是深度学习主力fastapiuvicorn 管服务化onnxruntime 管推理优化。没有安装任何多余的东西这个原则后面省了我太多麻烦依赖冲突概率大降。2.2 数据工程的三个核心原则数据是AI工程的地基但我见过太多人在地基上画图纸却从没真正打桩。我踩过的坑总结下来有三个原则第一一切数据操作必须可复现。清洗逻辑不能靠手动在Excel里改必须写成脚本、带上版本。我用dvcData Version Control管理数据和特征文件它像给代码用的git一样管理数据版本回滚数据跟回滚代码一样方便。操作也非常直白dvc init dvc add data/raw/training_set.csv git add data/raw/training_set.csv.dvc .dvc/config git commit -m add raw training data v1第二训练集、验证集、测试集的划分必须在第一时间完成而且在特征工程之前。我见过土法炼钢式的划分先做完整数据清洗再随机切分结果特征工程时不小心用了全量数据的信息验证集指标虚高上线后被真实数据打回原形。正确姿势是数据一进来就切分之后所有操作只基于训练集拟合验证集和测试集只做变换不参与拟合。第三每一个特征都要能溯源。我维护了一张特征字典表包含特征名、含义、来源SQL/脚本、数据类型、取值范围、缺失率。这不仅是文档责任问题更是排障利器。有一次线上效果骤降排查了半天最后发现是上游数据表某个字段改成了新的枚举值特征字典帮我秒速锁定了受影响的特征。2.3 基线先行先建评估体系再碰模型评估体系的建立顺序同样关键。很多人拿到数据集第一件事就是跑个神经网络这在我看来是本末倒置。正确顺序是先设定评估指标、再做简单基线、最后才上复杂模型。评估指标必须根据业务目标定。我处理的文本分类项目是情感识别业务方关注的是整体准确率与类别召回率的平衡所以我选了F1-score作为主指标同时监控各类别的精确率与召回率。指标定了之后我做了一个日频基线用TF-IDF加Logistic回归跑了一版耗时半天拿到了一个F1在80附近的分数。这个基线的价值怎么强调都不过分。它是复杂模型的“及格线”是对业务方的“预期管理工具”更是判断数据质量是否有问题的探针。如果后续BERT模型连TF-IDF基线都打不过说明问题不在模型能力而在数据本身——要么标签噪声太大要么特征分布有重大缺陷。我的经验是基线模型必须最早跑通这是AI工程里性价比最高的第一步。3. 实操过程与核心环节实现3.1 从数据处理到特征工程的一次完整实操我以情感分类项目为案例完整走一遍数据处理到特征工程的流程。原始数据是从业务系统里导出的用户评论格式是JSON约80万条。第一步是用pandas载入并做基础探查我要看三件事字段完整性、标签分布、文本长度分布。这个探查不能省它决定了后续所有处理策略import pandas as pd import json with open(data/raw/comments.json, r) as f: data [json.loads(line) for line in f] df pd.DataFrame(data) print(df.info()) print(df[label].value_counts(normalizeTrue)) df[text_len] df[text].str.len() print(df[text_len].describe())探查结果标签分布正负比例约6:4尚可接受文本长度中位数在120字左右有少量极长文本部分字段缺失率达15%。基于这些信息我做了清洗和特征工程。清洗上统一简繁、去除URL和乱码、连续空格压缩但没有做过分激进的去停用词因为后来做深度学习时这些“停用词”往往携带语气信息。特征工程分两类。一类是统计特征文本长度、标点密度、大写字母比例、情感词命中数基于词典。一类是文本向量特征先用TF-IDF做基线模型的输入后面做深度学习时直接用BERT的tokenizer做编码。关键点在于统计特征的计算只基于训练集的统计量做归一化验证和测试集复用训练集的scaler参数绝不重新拟合。这一步做完我得到了三份文件清洗后的训练/验证/测试CSV、特征字典表、特征构建脚本。全部走dvc追踪为后续复现提供基础。3.2 模型训练的完整流程与参数取舍模型训练是整个AI工程里最“性感”的部分但从工程视角看它也只是流水线上的一环。我的训练流程分两步先用基线模型验证管道通畅性再迭代深度模型。基线段直接用scikit-learn的Pipeline把TF-IDF向量化、标准化和LogisticRegression串在一个对象里from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline pipeline Pipeline([ (tfidf, TfidfVectorizer(max_features50000, ngram_range(1, 2))), (clf, LogisticRegression(C1.0, max_iter1000)) ]) pipeline.fit(X_train, y_train)模型评估用交叉验证加测试集双重验证。交叉验证保证结果是稳健的不依赖某一次随机划分测试集是最终裁决。得到F1大约在81.2标记为基线版本。之后进入深度模型环节我用的是预训练中文BERT做微调。这个环节有不少值得特别注意的工程细节学习率不是固定不变的我用的是warmup加线性衰减前10%的step做warmup避免预训练权重被大步长破坏。batch size选了16因为文本平均长度较长显存和训练速度之间取平衡。如果显存充足可以尝试32但收益不大。早停策略配合验证集F1patience设为3超过3轮不涨就停。这有效防止过拟合能省不少训练时间。训练脚本记录每次实验的配置超参数、数据版本、代码commit号到一份JSON日志这为我后续排查“为什么某个版本效果更好”提供了关键依据。做了这一层之后BERT微调模型在测试集上的F1提升到了88.6涨了约7个点算是一个符合预期的结果。3.3 推理优化与模型部署的落地选型训练完毕并不代表工程结束模型上线前的推理优化、服务封装可以说才是AI工程真正的分水岭。我用ONNX Runtime做推理加速用FastAPI做HTTP服务封装这个组合在中小型项目中性价比极高。PyTorch模型转ONNX代码不太多但坑不少import torch from transformers import BertForSequenceClassification model BertForSequenceClassification.from_pretrained(your_model_path, num_labels2) model.eval() dummy_input torch.randint(0, 30000, (1, 128), dtypetorch.long) # 关键点1: 用 transformers 内建函数生成 onnx兼容性更好 from transformers.onnx import export from transformers import AutoConfig, AutoTokenizer tokenizer AutoTokenizer.from_pretrained(your_model_path) config AutoConfig.from_pretrained(your_model_path, num_labels2) export( modelmodel, configconfig, tokenizertokenizer, opset14, outputmodel.onnx )转换后就踩到了我记忆最深的坑ONNX的输入包含input_ids、attention_mask、token_type_ids三个tensor但用onnxruntime直接跑的时候经常会遇到动态维度不匹配。解决方案是锁定序列长度为128不够的pad超出的截断并在服务端做同样的预处理保证推理输入完全对齐。服务封装部分我用FastAPI写了推理接口。完整的服务逻辑需要包含三块模型加载、输入校验和预测。模型在服务启动时加载一次放到全局变量里输入校验要处理异常文本和超长文本预测模块负责tokenizer编码、推理、softmax和返回结果。这三点缺一不可。推理耗时从BERT原始的120毫秒压到了40毫秒左右QPS接近翻倍。我用下列代码示例说明服务端核心部分from fastapi import FastAPI from pydantic import BaseModel import onnxruntime as ort import numpy as np app FastAPI() session ort.InferenceSession(model.onnx, providers[CPUExecutionProvider]) tokenizer AutoTokenizer.from_pretrained(your_model_path) MAX_LEN 128 class TextInput(BaseModel): text: str app.post(/predict) def predict(input: TextInput): inputs tokenizer( input.text, max_lengthMAX_LEN, paddingmax_length, truncationTrue, return_tensorsnp ) outputs session.run( None, { input_ids: inputs[input_ids].astype(np.int64), attention_mask: inputs[attention_mask].astype(np.int64), token_type_ids: inputs[token_type_ids].astype(np.int64), } ) logits outputs[0] prob 1 / (1 np.exp(-logits[0][1])) return {positive_prob: float(prob)}3.4 容器化部署与CI/CD实践模型服务要稳定跑在生产环境容器化是第一步。我的Dockerfile很简单但每一层都经过思考FROM python:3.10-slim WORKDIR /app # 先拷贝依赖文件并安装再拷贝代码 # 这能利用 Docker 层缓存改代码时不需要重新装依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app/ app/ COPY model.onnx . ENV OMP_NUM_THREADS4 EXPOSE 8000 CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000, --workers, 2]关于worker数量我特别说明一下不是越多越好。因为ONNX Runtime默认也会开线程池当uvicorn --workers 4再叠加ONNX的4线程在4核机器上会造成严重CPU争抢延迟反而上升。我最终的配置是--workers 2加OMP_NUM_THREADS4压测下来吞吐量和延迟达到最佳平衡点。CI/CD我用GitHub Actions搞定。推送代码到main分支后自动跑测试单元测试模型精度烟雾测试然后构建Docker镜像推到镜像仓库再SSH到服务器拉镜像重启服务。这套流程跑通后上线变成了一件无感的事——改代码、推代码、自动部署、自动验证全程不需要ssh进服务器手动操作。4. 常见问题与排查技巧实录4.1 训练与服务环境不一致引发的隐患这是我踩过最深的一个坑值得单独写一节。训练时我用GPU跑了PyTorch模型推理时用了CPU上的ONNX Runtime。按理说结果应该一致但我第一次压测就发现线上预测结果和离线离线评估差了不少。排查后发现了两个原因。第一训练时tokenizer的版本和服务端tokenizer的版本不一致具体来说是transformers库升级后词表有了细微变化导致编码结果不同。第二ONNX转换时某些算子在CPU和GPU上的数值精度略有差异softmax的结果在小数点后第三位开始分叉。解决方案说穿了也很简单把训练环境的依赖版本完整锁定在服务端复刻一套一模一样的依赖环境所有模型转换和精度比对动作做成自动化测试在CI流程里强制校验。此后我再也没被“线上离线不一致”这个问题困扰过。凡涉及模型环境即代码版本即契约。4.2 推理延迟突刺的定位与解决又一次被甲方催着修问题线上接口P99延迟突然从50毫秒飙到500毫秒。一开始我怀疑是模型问题查了一圈没发现异常。后来用Python自带的cProfile压测才发现是日志打印环节在搞鬼——每次请求都把完整输入文本打到日志文件文本又长磁盘IO直接成为瓶颈。优化方案有两个一是日志降级只在debug模式打印全量文本平时只记录文本哈希和长度二是把日志改成异步写入用logging的QueueHandler加独立线程消费避免请求线程阻塞在IO上。修改后P99延迟回到55毫秒以内。这个经历让我养成一个习惯任何IO操作默认异步凡是写日志、写缓存、调外部API都不能占用请求线程的同步时间。4.3 线上效果衰减的排查思路模型上线一个月后业务方反馈效果明显下降。我第一反应是模型漂移了赶紧看数据分布。做法是每天做一个数据分布监控统计线上新数据的文本长度、关键词覆盖率、标签分布和训练集做对比用KL散度量化差异。查出来的结果是某个类别的评论量暴涨了三倍内容以短文本为主和训练集的长文本分布差异显著。这个漂移被检出后我启动了增量数据收集流程两周后重新训练并上线新版本效果恢复到原有水平。这个案例让我意识到AI工程的“工程”二字里包含了一个重要职责持续监控系统边界而不是守着模型一劳永逸。从数据漂移的角度出发重新设计监控指标是我事后复盘时最值得的一笔投入。4.4 模型回滚机制与AB测试的落地模型总会有失效的时候所以“回滚”不是可选功能而是必备能力。我的做法是每个模型版本用一个独立目录存储目录里包含模型文件、配置文件、评估报告和全量依赖锁定文件。服务启动时从环境变量读取当前模型版本切换版本只需要改动一个环境变量然后重启服务不需要重新构建镜像。回滚的另一面是灰度发布。小流量AB测试的实现比想象中简单在服务入口加一个随机数根据配置的比例把请求分发到新旧两个模型版本每个版本的返回结果都打上版本标签写入日志。跑两三天之后对比两个版本的在线指标准确率、平均响应时间、异常率数据说话决定是继续上线还是回滚。这套机制跑顺后我对于模型迭代的心态完全变了不再是“小心求证、生怕上线出事”而是“每个版本都默认可以上线反正随时可以回滚”。工程能力本身就是对创新的一种释放。5. AI工程能力的持续进阶路径5.1 从单点能力到体系化能力如果说前面的内容还是在讲“点”那这一步就必须要跳出来看“面”。单点技能再强如果缺少全局视角AI工程这件事是做不好的。我用一张待办清单推动自己完成从点到面的跃迁特征存储统一管理避免训练和推理特征口径不一致、实验管理平台化让每一次实验都有据可查、模型版本台账化把模型文件、训练数据、评估指标、源码状态打包成不可变物件、监控告警联动化数据漂移、延迟突刺、错误率上升自动触发处理流程。这些模块单独看都不是核心技术但拼在一起才是真正的AI工程体系。我在实践过程中发现一个规律很多技术难题攻关成功不是因为聪明而是因为流程把错误挡在了门外。体系的价值不在于增加多少新功能而在于消除了多少个“偶然才能发现的问题”。5.2 保持学习节奏的实用建议AI工程领域技术迭代太快容易产生“永远在追新”的焦虑感。我的对策是建立两条学习线。一条是底层线数据结构、算法、操作系统、网络原理这些十年不变是大厦地基另一条是应用线模型架构、部署框架、监控工具这些跟着项目走遇到什么学什么学完即用。具体实操上我每个季度逼自己做一个两周内完成的微型项目原则是——必须是没接触过的工具、必须有可量化的产出、必须整理成文档输出。这个习惯让我在保持主线工作之外每年稳定扩展四个边缘能力点。有些当初觉得没用的能力比如系统性能剖析、基础运维脚本后来都直接解决了关键问题。工程能力就是这样一点一滴垒起来的。5.3 适合上手练习的三个项目方向如果看完这篇文章想动手练一练我帮你排了几个从易到难的项目方向。第一个是文本分类/情感分析API技术栈覆盖数据清洗、模型微调、ONNX转换、FastAPI部署、Docker容器化这是完整闭环的第一步。第二个是图像分类服务涉及图像预处理、CNN微调、批量推理优化、GPU推理配置难度略高但对理解推理引擎更有帮助。第三个是推荐系统的召回排序简化实现会涉及特征平台、向量检索、多路召回、重排策略可以接触完整的推荐工程链路。每个项目做完都要强制自己写清楚架构文档和部署文档。写文档的过程就是查漏补缺的过程。很多“我以为我懂了”的内容在提笔写文档的那一刻就暴露了盲区。从我个人的经历来看AI工程能力不是靠看教程看出来的是靠一个项目一个项目做出来的。过程中会不断怀疑自己、推翻自己但每推翻一次对工程的理解就深了一层。这个领域最大的魅力在于方法可以迁移、经验可以复用只要你愿意从零开始把事情做扎实能力的增长只是时间问题。