我自己是从一个只会写业务代码的后端开发,硬生生转到AI工程方向的。当时网上找“ai-engineering”相关的内容,要么是纯算法论文解读,要么是调包训练模型的保姆教程,真到把模型做成一个稳定服务、推进到线上跑起来的环节,反而没人系统讲清楚。所以看到“ai-engineering-from-scratch”这个标题,我特别有共鸣。如果你也正打算从零开始进入AI工程领域,或者已经在跑模型但总觉得“工程化”差点意思,这篇内容就是我想写给当年的自己的实操总结。不涉及高大上的理论推导,只聊一个AI工程师从立项到上线遇到的真实问题、思考逻辑和可复现的解决路径。
1. 从零开始搞AI工程:先想清楚要解决什么问题
1.1 AI工程不是搭个模型那么容易
很多人对AI工程有个误区,以为就是跑通一个训练脚本,看到准确率不错就算完事。我踩过这个坑,而且摔得挺疼。当时在内部做了一个文本分类模型,离线测试F1值到了0.92,觉得自己挺了不起。结果上线第一周就发现,线上数据的分布和训练集差异很大,新词、简称、特殊符号把模型打得措手不及,准确率直接掉到0.7以下。后来才明白,AI工程是“数据—模型—服务—监控—迭代”的闭环,模型训练只是中间一小段。
从工程视角看,模型更像是系统里的一个组件,需要像对待数据库连接、消息队列一样考虑它的稳定性、可测试性和可维护性。这要求你不光会写Python脚本,还得了解API设计、容器化、CI/CD、日志监控这些基础设施。我在团队里经常说一句话:算法岗拼的是单点模型能力,AI工程岗拼的是让模型持续产生价值的系统能力。这也是“ai-engineering-from-scratch”真正要练的东西。
1.2 我踩过的规划坑:先定场景再选技术
刚开始学的时候,我犯过一个特别常见的错误:先选了一堆炫酷的技术栈,比如BERT、PyTorch Lightning、Kubeflow,然后才开始找场景。结果学了一个月,发现实际项目里根本用不到那么重的编排工具,反而在数据清洗和环境部署上花了80%的时间。
正确的顺序应该反着来:先锁定一个足够具体、有明确业务收益的场景,再反推需要什么技术。我建议从这三个要素判断场景是否适合起步:一是数据可得性,至少要能稳定拿到几千条带标签的数据;二是评估标准清晰,比如“客服工单自动分派准确率从70%提到85%”就比“做个智能助手”好落地得多;三是容错空间,小规模内部工具比直接面向C端用户的纯黑盒模型更适合练手。
具体到技术选型,我的经验是“够用就好”。起步阶段不需要上分布式训练,也不需要搞复杂的特征平台。一台带GPU的开发机(哪怕云端租的),加上Python环境、Jupyter Notebook、Git和Docker,已经可以跑完一个端到端的最小闭环。先跑通,再谈扩展。
2. 完整学习路线:从Python到模型上线的关键节点
2.1 第一站:Python与数据处理基本功
AI工程师的底子是Python,但和普通后端开发不一样,更看重的是数据处理能力。你不需要把Python语言特性抠到极致,但一定要熟练操作NumPy、Pandas和PyArrow这三大件。我可以给你一个自测标准:给你一张5000万行的CSV,你能不能在10分钟内完成去重、缺失值填充、按时间窗口聚合,并且把内存占用控制在合理范围。如果不能,说明数据基本功还需要补。
实操上我建议多练几个经典场景。比如处理用户行为日志,经常会出现同一个用户在同一天重复点击多次的情况,需求是“每个用户每天只保留最后一次点击”。代码很简单,但这种“脏逻辑”才考验工程功底:
import pandas as pd df = pd.read_csv("user_clicks.csv", parse_dates=["click_time"]) df_sorted = df.sort_values(["user_id", "click_time"]) result = df_sorted.groupby(["user_id", "click_date"], as_index=False).tail(1) result.to_parquet("user_clicks_dedup.parquet", index=False)为什么我特别强调数据基本功?因为后续的模型特征工程、训练集验证集划分、线上推理的预处理逻辑,全部建立在你对数据源的理解上。我见过太多人把精力放在调模型上,结果连训练集和测试集之间有特征重叠都没发现,最后上线被业务方追着骂。
2.2 第二站:机器学习/深度学习基础
这一站不需要你把数学推到很高深,但至少要理解四个核心问题:模型在学什么(损失函数)、怎么学(梯度下降)、怎么避免学偏(正则化)、怎么知道学得好不好(评估指标)。我的建议是,先从经典的机器学习模型入手,比如逻辑回归、决策树、XGBoost,再过渡到深度学习。
为什么推荐这个顺序?因为很多深度学习的坑,在传统模型里更容易解释清楚。比如“过拟合”这个概念,你在XGBoost上调max_depth和min_child_weight,能直观感受到模型从欠拟合到过拟合的过程;而直接上手CNN,你可能会对着Loss曲线一头雾水。我自己带过的实习生里,凡是传统模型底子扎实的,转深度学习都很快。
到深度学习阶段,建议按这个路径走:先搞懂MLP(多层感知机),然后用CNN做图像分类,用RNN/Transformer做文本分类,最后尝试一个小型的生成式应用。每一步都要亲手写数据加载、训练循环和评估代码,不要一上来就无脑调用model.fit()。我当年训练第一个CNN时,自己实现了反向传播的玩具版本,虽然性能差得远,但彻底搞懂了梯度在每一层怎么流动,这个基础让我之后排查训练不收敛问题时快很多。
2.3 第三站:工程化必备:版本控制、依赖管理与实验追踪
这几乎是自学AI工程最容易忽视的环节。我始终认为,如果模型训练代码不能一键复现,那结果再漂亮也是空中楼阁。这就要做到三件事:代码版本化、依赖锁定化、实验可追溯化。
代码版本化大家都用Git,但我强调要把数据和模型权重也纳入版本管理思路。实际操作中,训练脚本和配置文件的Git提交信息会包含一个数据版本号,数据本身放在对象存储里按版本目录管理,比如s3://bucket/data/20250201/。这样回溯时能准确找到当时用的哪批数据。
依赖锁定化尤其重要。Python依赖冲突能把人折磨疯。我推荐在项目根目录维护requirements.in和requirements.txt,前者写顶层依赖,后者用pip-tools生成完整锁定版本。还有个更省心的方案是直接用Poetry或PDM。记住一个原则:不要在你的生产环境里裸奔安装torch和transformers,一定要用虚拟环境或容器隔离。
实验追踪我用的是MLflow,轻量且够用。每个实验记录下超参数、数据版本、代码提交哈希、评估指标。这样你回头看时,能说出“20250201那批数据,在max_lr=1e-4、batch_size=32条件下,验证集F1是0.883”,而不是靠Excel表记录——我当年就是靠Excel,结果有一次手滑覆盖了单元格,悔得肠子都青了。
3. 我的第一个端到端AI项目实操记录
3.1 项目需求与数据准备
我选的第一个从零开始的端到端项目是“工单智能分派”。背景是客服团队每天收到几百封来自不同渠道的工单,需要人工判断属于哪个业务线,再转给对应小组。这个场景数据容易拿(历史工单都有标注归属),评估指标清晰(直接看分派准确率),而且哪怕模型分错了,也只是内部流转时才回退客服那边,容错空间大。
数据准备阶段,我从客服系统导出最近12个月的工单文本和最终处理小组。当时拿到了大约8万条记录,其中有些工单被反复转派,我直接把最后一次处理小组作为标签,同时把文本做了去重——因为同一用户会重复提交类似问题,不去重就会造成训练集和验证集泄漏。
清洗规则我列成了清单:去掉HTML标签、统一半角/全角标点、过滤掉纯数字/纯签名的短文本、合并同义小写转换。这里有个非常重要的细节:数据清洗代码必须复用,不能只在训练时用一次。我写了一个TextNormalizer类,训练前端用它预处理,推理时服务器也调用同一个类。很多团队就栽在这——训练时做了清洗,上线推理时忘了做,导致模型看到的文本格式完全不对,性能差距巨大。
3.2 建模阶段:模型选型与训练调优
因为是做文本分类,我对比了两条路线:一条是TF-IDF + 线性模型/XGBoost,另一条是预训练语言模型微调。考虑到数据量只有8万,且都是短文本,我决定先跑一个TF-IDF + Logistic Regression的基线,预期能到0.75左右,再用BERT类模型微调看能提升多少。这样做的好处是,如果业务上0.75已经够用,就没必要上重型模型,省下的推理成本很可观。
基线模型结果果然在验证集到了0.78,而微调一个bert-base-chinese后能到0.89。不过我发现推理速度差异明显:CPU上单条文本基线只要5毫秒,BERT要120毫秒。最终方案是折中:用微调后的蒸馏模型distilbert-base-chinese,准确率还有0.87,单条推理降到35毫秒,部署成本也低很多。
训练调优方面,我个人经验是先固定几个关键配置:max_seq_length=128(工单平均长度不到80个字),epochs=3,batch_size=32,learning_rate=2e-5。第一次跑完看训练loss和验证loss的差距,发现验证loss在第二个epoch就开始抬高,明显过拟合。于是加了early_stopping,同时把dropout从0.1调到0.3,最终稳定在验证F1=0.874。这里提个细节:监控训练时不要只看最终准确率,要把每个batch的loss曲线存下来。我在MLflow里记录loss曲线,发现前200步loss从1.1降到0.4,但之后下降非常缓慢,这种信息对判断学习率是否合适极有帮助。
3.3 部署阶段:从API到容器化的完整路径
部署我踩过最大的坑就是“模型文件多大,服务就多臃肿”。一开始我把整个transformers库和Torch都打进了Docker镜像,镜像体积接近3GB,冷启动时间要3分钟,简直噩梦。后来做了三件事:一是模型序列化格式从pytorch_model.bin换成torchscript或ONNX,推理时不再需要动态构建网络结构;二是用torchserve或者FastAPI自己写推理服务,只加载模型运行时需要的文件;三是在镜像里只安装CPU版本的Torch和ONNX Runtime,推理速度受影响不大,镜像体积直接降到了700MB。
服务端我用的FastAPI,如果不熟悉这个框架可以理解为一个“把Python函数变成HTTP接口”的工具。核心推理接口逻辑很简洁:
from fastapi import FastAPI, Request from model_loader import load_predictor app = FastAPI() predictor = load_predictor("/models/distilbert.onnx") @app.post("/predict") async def predict(request: Request): payload = await request.json() text = payload.get("text", "") prob, label = predictor.predict(text) return {"label": label, "prob": float(prob)}上线前我专门压了压接口性能。用的是locust,模拟20个并发持续打5分钟,观察P99延迟和CPU占用。当时暴露了一个问题:因为模型推理是CPU密集型,FastAPI的异步并发并不能加速,反而因为线程切换增加开销。后来我改用进程池方式,开4个worker进程,P99从210ms降到了95ms。这里有经验要分享:AI推理服务瓶颈绝大多数在模型计算,Web框架层的异步优化帮不了太多,重点是把模型推理做成无状态且可水平扩展。
容器化我用的是Docker加Kubernetes。云厂商维护的K8s集群,配合HPA(水平自动伸缩),配置了“当CPU超过60%且持续2分钟时,扩容副本数到最大4”,这样白天工单高峰时自动多拉几个Pod,夜间没人时缩回一个,月成本省了不少。不过研究这个配置也耗了我不少时间,YAML里的targetAverageUtilization和stabilizationWindowSeconds需要反复试验才能找到平衡,写死一个值就很容易频繁扩缩容。
3.4 监控与迭代:上线之后才是工程的开始
我见过很多团队把模型上线当作项目结束,但AI工程恰恰从这里才开始。你要监控的不只是CPU和内存,更要监控模型“有没有出错”。最直接的手段是预测结果日志回捞——把线上每条推理请求的输入文本、预测标签、置信度、用户后续是否手动改判全部落日志,定期分析。
在工单分派场景里,我在后台记录了一个关键指标:人工改派率。如果模型分派的结果被客服改成了其他小组,说明这次预测大概率有问题。上线第一个月,整体改派率从5.8%降到了3.2%,但我注意到其中有一类工单的改派率始终高达15%,点进去一看,全是“发票金额不对”这类包含具体数字的投诉——模型对数字推理本来就弱,而且这类样本在训练集里确实偏少。
于是我做了一次针对性的数据补充:从历史工单里筛出所有含数字的“发票”类工单,清洗后人工复核标签,凑了3000条,用部分旧数据加部分新数据做增量训练。刷新后的模型在验证集上的总体F1没变化多少,但“发票”这个细类的改派率降到了8%以下。这就是我所说的迭代——围绕数据短板做小而快的更新,而不是动不动就重新训练整个模型。
4. 常见问题与避坑指南
4.1 数据坑:脏数据与数据泄漏
数据泄漏是AI工程里隐藏最深的坑。我举一个实际例子:在做工单分派时,如果我没去重,同一个用户用几乎相同的文本提交了两次工单,一次被分到“账单”,一次被分到“网络”,那么模型在训练时见过类似文本,在验证时再遇到,准确率虚高就很正常。要判断是否泄漏,一个简单的做法是做文本模糊查重,把相似度超过阈值的样本全挑出来看一遍分布。
另一个高频坑是标签泄漏。有段时间我为了提升准确率,把“工单创建时间”也做成了特征,结果线上推理时这个特征根本就是“未来信息”——训练集模型知道了工单最终流向,而推理时还不知道结果。这样训练指标漂亮得很,线上完全不匹配。所以我建议在做特征时对每个特征都问一句:预测这个时刻,这个值是否能实时获取?这是一个AI工程师的基本职业素养。
4.2 环境坑:依赖冲突与复现困难
Python依赖依赖冲突是“从零开始”最容易劝退人的地方。我现在已经养成了铁律:所有AI项目必须从Docker镜像开始。项目根目录放一个Dockerfile,基础镜像固定到某个版本的python:3.10-slim,然后pip install -r requirements.txt。而不是在自己的电脑上装一套深度学习环境。
还有一个我踩过的坑是CUDA和PyTorch版本不匹配。有次在云服务器上跑训练,torch.cuda.is_available()一直返回False,折腾了半天发现是PyTorch是CPU版。后来我固定使用官方镜像,比如pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime,从此再没被环境问题困扰过。如果你也要复现别人的项目,第一件事是看它的environment.yml或者Dockerfile,不要直接下载代码就run。
4.3 性能坑:推理延迟与资源浪费
模型部署后的性能优化,优先考虑三板斧:量化、批处理、缓存。我的工单分类模型最初是FP32的ONNX版本,单条推理35ms。我用onnxruntime.quantization做了动态量化,模型体积从260MB降到89MB,单条推理降到22ms,而F1只掉了0.3个百分点——对短文本分类业务来说这个损耗完全可接受。
批处理的思路是:在模型服务里维护一个双缓冲队列,积累到一定条数再一次性喂给模型。这样做能把GPU的利用率拉高,但前提是你的业务不是每一条都需要毫秒级响应。工单分派允许几秒延迟,所以我大胆上了批量推理,吞吐量翻了将近三倍。
缓存则主要针对高频重复请求。客服那边隔几分钟重新提交同一个问题很常见,我在Redis里对文本SHA256哈希后放了个6小时的缓存,命中率有35%左右。注意这里要做精确匹配,不要用模糊匹配,否则可能出现“相似但不同”的请求拿到错误结果。
4.4 组织坑:跨团队协作与模型交接
AI工程极少是一个人闷头做的。我负责模型、平台团队负责推理集群、业务团队负责标注和反馈,三拨人对“完成”的定义完全不同。最让我头疼的就是发现平台团队默认所有API都是同步调用,而我在设计里用了异步回调,结果联调阶段才发现节奏对不上。后来我在项目启动时就归纳了一个接口约定文档,里面明确同步/异步、超时时间、重试策略和错误码规范,从那以后跨团队沟通顺畅多了。
模型交接给下游工程师时,只给一个模型文件是绝对不够的。我会把四样东西打包交付:模型文件、推理服务代码、可复现训练代码和一份“模型说明文档”。说明文档里写明输入输出格式、预处理对齐方式、已知边界案例、版本变更记录。这有点像交班日志,能让接手者不用反复问“这个字段是干嘛的”。我见过最有良心的模型交付代码里,甚至把“模型上线后必须观察哪三个指标”写进了README,这才是真正的工程化。
结尾
回头看我自己的“ai-engineering-from-scratch”过程,最大的变化不是学会了多少模型,而是思维方式变了:从“怎么把准确率刷上去”变成“怎么让模型稳定可靠地产生业务价值”。如果你也正走在这条路上,我的建议是不要急着追求最新的流行技术,先找一个小而具体的场景,用最常见的工具做完一个端到端闭环。哪怕最后模型效果只是及格水平,你掌握的数据处理、环境管理、部署监控这些工程能力,才是这个领域最值钱的护城河。最后再分享一个小技巧——遇到任何不理解的报错,先看完整的堆栈信息,不要只看第一行;你90%的问题,云厂商官方文档和开源社区里都有人解答过,只是你搜索的关键词可能还不够准确。调整好心态,一步步来,这条路并没有想象中那么陡峭。