
经常有人问我想转AI工程方向或者正在做算法想补工程能力到底该从哪里下手。说实话我自己的答案也变过好几轮。一开始我跟大多数人一样先啃深度学习理论再读论文复现模型结果在最基础的环境和工程环节上反复卡壳。后来我把这条摸索过程整理成了一个开源仓库名字就叫ai-engineering-from-scratch中文理解就是“AI工程从零开始”。这篇文章就围绕这个主题把这条路上的关键选择、实操步骤和踩过的坑一次说清楚。内容偏向实际动手适合刚开始接触AI工程、想把模型真正用起来的开发者也适合那些已经会调包训练但一部署就头疼的算法工程师。1. 为什么“从零开始”不看AI概念先看工程习惯1.1 算法和AI工程是两套动作很多人一听AI工程第一反应是“那我得先把神经网络、Transformer、数学推导都学透”。这是最大的误区。AI工程里占比最高的不是发明新算法而是把已有模型稳定、高效、可维护地跑起来。就像你开餐厅重点不只是菜谱有多创新还包括后厨动线、食材供应链、出餐速度和卫生检查。菜谱可以买后厨必须自己搭。我从这个仓库的笔记里总结出四个阶段工程基础、数据与实验、部署发布、端到端项目。这四个阶段每个都不能跳但也不需要等上一个学到满分再进下一个。比如你只需要会用Python、懂基本的Linux命令、了解进程和端口就能开始不需要先成为Linux内核专家。1.2 先动手跑通一次全流程比先读三本书重要我在仓库早期记录过一段自己的经历花了差不多两周时间看各种深度学习教程觉得理论差不多了结果第一次想跑一个图片分类的公开代码先在虚拟环境上卡了两天又因为CUDA版本不对折腾了一天。那时候我才意识到所谓的“从零开始”第一步不是知识不够而是工程习惯没建立。所以后来我给自己的路线里加了一条硬性规定第一周内必须跑通一个完整的小项目不管是训练还是推理必须让它端到端转起来。只要跑通一次你就知道哪些环节是纸老虎哪些是拦路虎。2. 搭建一套能坚持到项目结束的本地开发环境2.1 Python、虚拟环境和依赖管理系统Python不能随便动第一次实操的人最容易犯的错就是在系统自带Python里直接pip install。过一阵你就会发现不同项目依赖同一个包的冲突版本或者某个系统工具突然不可用。我的习惯是在仓库首页就写上使用pyenv管理Python版本使用venv或poetry管理项目依赖。pyenv的好处是能按目录自动切换Python版本比如老项目需要3.9新项目用3.11互不干扰。具体操作上先安装pyenv然后为项目指定Python版本pyenv install 3.11.6 pyenv local 3.11.6 python -m venv venv source venv/bin/activate pip install --upgrade pip这套动作看起来基础但把“环境稳定”这个目标落实到了每次协作里。让队友直接看requirements.txt或pyproject.toml就能复现比口头交代“我这边能跑”可靠得多。2.2 Conda与GPU库的适配细节如果你要做深度学习尤其要处理CUDA生态那么Conda依然是一个值得考虑的选择。Conda不仅能隔离Python环境还能管理CUDA Toolkit等非Python依赖。我踩过最典型的坑是用pip安装的torch版本自带的CUDA runtime和系统里nvcc显示的CUDA版本不一致。这不是不能跑但在编译自定义算子或者用到某些扩展库时就会冒出一些奇怪的段错误。我的做法是先用nvidia-smi确认驱动支持的CUDA版本再选择PyTorch官方提供的对应版本。安装时给一个经验值conda create -n ai-first python3.10 conda activate ai-first pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118如果只是做推理CPU环境也完全够起步。不要被“没有GPU就不能学AI”的话吓住很多工程实践与算力无关。2.3 Docker从“在我机器上能跑”到“在哪都能跑”环境问题最大的痛点是对“另一个人复现时为什么跑不起来”感到困惑。Docker是解决这个问题的第一道闸门。我习惯在每个项目里写一份最小可用的Dockerfile基础镜像选官方Python或PyTorch镜像并固定版本号。比如FROM pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . CMD [python, train.py]需要注意的一点是不要一开始就追求镜像体积最小和构建最快的优化先保证能一次构建成功。之后再去了解dockerignore、镜像分层、非root用户运行这些进阶内容。2.4 实验跟踪工具早期不觉得后面真香跑第一个训练脚本时你可能觉得记录loss和打印输出就够了。等过了两周你会面对几十次实验根本想不起哪个参数组合对应哪条曲线。所以从一开始就引入实验跟踪工具是最便宜的技术债偿还方式。我的首选是MLflow因为它既支持本地运行也支持远程服务接口也简单。import mlflow with mlflow.start_run(): mlflow.log_param(learning_rate, 1e-3) mlflow.log_metric(val_acc, val_acc) mlflow.log_artifact(model.pt)每次训练存一次run参数、指标、模型文件全在一起。回看实验时不用再翻聊天记录和注释掉的代码。3. 数据处理比模型训练更值得投入精力的原因和做法3.1 先做数据摸底再做数据清洗很多人拿到数据集就开始训练等到结果不好才回头检查数据。我的经验是进入训练前至少要花一天时间对数据做摸底。要看统计信息样本数、特征缺失率、目标分布、文本长度、图片分辨率等。用几张图表帮助定位问题。比如分类问题中类别极度不均衡会导致模型偏向多数类时间序列问题中如果直接用随机切分会造成严重的数据泄漏。我在仓库里专门写了一个“数据体检清单”字段含义是否清楚、是否有重复样本、是否有异常值、标签是否有矛盾、训练集和测试集是否来自同一分布。每一条都值得在项目文档里写清楚因为后面所有模型性能问题大概率都要回到这里找原因。3.2 数据管道脚本化拒绝手工操作最不工程化的做法是从网盘里下载数据手动解压再用Excel标几个标签然后写代码时引用一个本地绝对路径。这种流程短则一两周就会失控因为你不知道数据是哪个版本、怎么生成的。正确做法是写一个可重复执行的数据准备脚本输入是原始数据路径输出是处理后的特征文件。用Python脚本或者make命令管理。如果项目稍大可以引入dvc做数据版本管理。dvc的工作方式和git类似但它记录的是大文件的版本比如dvc init dvc add data/raw git add data/raw.dvc这样你在做实验时明确知道自己用的是哪个版本的数据。这个习惯在协作时尤其重要因为别人拿到代码后只需要跟踪data/raw.dvc就能把同份数据拉下来。3.3 数据泄漏是排名靠前但隐蔽的错误数据泄漏听起来是学术概念实操里非常常见。最经典的场景是先做特征工程再用全部数据做标准化最后切训练集和测试集。标准化时均值和方差理应只从训练集计算结果你用了全局统计量测试集的信息就偷偷混进了训练过程线上效果自然会掉。另一个容易犯错的场景是时间序列的随机打乱。预测明天的天气如果用随机采样的方式切分数据模型就能“偷看”到未来信息。正确的做法是按时间顺序切分或者使用时间序列交叉验证。我自己在第一次做时间序列项目时就被这里坑过离线评估分数很高上线后完全不是一回事后来排查才发现是滑动窗口没设置好。3.4 处理类别不均衡和标签噪声类别不均衡最直接的处理有重采样、欠采样、调整损失函数权重。我的建议是先用简单的class_weight方案如果不行再上其他复杂方案。标签噪声则需要建立人工抽检机制。我习惯在训练前随机抽100条自己看一遍标注是否合理准确率如果低于95%那训练出来的模型也不会好到哪里去。一个工程上的小技巧把训练集中预测错误的样本单独存成一个CSV定期人工复核。这既是数据问题排查的依据也是后续给标注团队反馈的素材。模型本身也可以帮忙找错双向迭代。4. 训练实验的工程化让每次运行都可复现、可比较4.1 用配置文件驱动训练而不是改代码我最早训练模型时习惯直接改脚本里的参数比如把learning_rate 0.01改成0.001然后重新运行。这么做的后果是整个实验历史只存在于你的记忆里甚至有些参数改来改去自己都记不住了。后来我采用配置驱动的方式。训练脚本只负责读取一个YAML或JSON配置文件所有超参数、数据路径、输出路径都从配置里读取。比如新建一个configs/exp1.yamldata: train_path: data/processed/train.parquet val_path: data/processed/val.parquet model: name: bert-base-chinese num_classes: 10 train: batch_size: 32 learning_rate: 1e-5 epochs: 5每次跑实验就是指定一份配置python train.py --config configs/exp1.yaml这样每次实验都有明确的参数档案后续要复现任何一个历史结果只需要找到当时的配置文件和代码commit即可。4.2 随机种子与可达性问题深度学习里到处是随机性随机初始化、数据加载顺序、dropout、GPU算子。为了保证实验可复现需要在所有需要随机的地方固定种子。代码里一般这样写import random import numpy as np import torch def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False但注意即使固定了种子GPU上的某些非确定性算子仍然可能导致微小差异。所以不要把“可复现”理解为“每次结果一模一样”而是同一份代码和配置结果差异在可接受范围内。比较实验时也不能只看一次训练结果至少要跑2到3次取平均尤其当任务本身的随机性比较大时。4.3 checkpoint和早停是实验安全网训练一个模型动不动几小时如果因为断电或显存溢出从头再来会非常打击信心。所以要养成定期保存checkpoint的习惯。PyTorch里的做法是保存模型参数、优化器状态、当前epoch和best metrictorch.save({ epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), best_acc: best_acc, }, fcheckpoints/epoch_{epoch}.pt)同时使用早停策略当验证集指标连续N个epoch不提升时就停止训练并载入最好的那版权重。这不仅是提升质量的手段也是节约算力的工程做法。4.4 评估指标不能只有一个我在仓库里专门写过一段吐槽有些项目把所有希望都押在accuracy上。当样本不均衡时准确率再高也可能没有现实价值。比如99%是负样本全预测负样本准确率就99%但毫无用处。合理的评估矩阵应包含多个维度精确率、召回率、F1、AUC还有业务指标。业务指标很关键比如在文本分类场景里误判一个高价值用户和漏掉一个普通用户代价完全不同。所以模型评估阶段就要跟业务方对齐“好模型”的定义。我自己常用的做法是在评估脚本里同时输出混淆矩阵和各分类的precision/recall然后单独查看最难分类的样本。4.5 做控制变量的对照实验模型的改动一旦多了最后很难判断哪个改动真正有效。我给自己定了一个规矩每次只动一个变量。比如想试新模型结构就保持数据、超参数不变想试数据增强就保持模型和训练流程不变。这类实验也叫ablation study虽然是论文里常见的词但在工程实践里也非常重要。用一张表格把实验编号、改动点、指标变化记录下来比零散的聊天记录清晰得多。5. 从“能跑”到“能用”部署推理系统的关键环节5.1 模型不是产品服务才是训练完的模型文件只能算半成品。一次完整的部署至少需要一个进程常驻在服务器上接收请求返回结果。最简单的做法是使用FastAPI封装模型推理from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Input(BaseModel): text: str app.post(/predict) def predict(item: Input): result model_predict(item.text) return {result: result}然后通过uvicorn启动服务。这个阶段的关键是输入输出要有明确格式比如统一转成JSON字符串并且考虑异常输入时如何返回错误码而不是直接抛500。5.2 同步还是异步看你的业务场景如果请求量不大每个请求都能在几百毫秒内返回同步HTTP接口就够用。但如果推理时间较长比如一个语音识别请求需要几秒或者经常有突发流量就需要考虑异步方案先把请求放到消息队列如RabbitMQ、Kafka再由worker消费并推理客户端轮询结果。这个架构会复杂一些但性能上限高很多。我在早期做个人项目时直接同步接口加高并发配置就足够了。不要一上来就上消息队列那是在给自己增加运维负担。等真正出现超时问题再去演进完全来得及。5.3 推理性能优化量化、批处理、缓存部署后如果响应时间不达标可以从三个方向优化。一是模型优化包括ONNX导出、TensorRT加速、剪枝、量化。其中最轻量的是动态量化对于torch模型几乎可以一行代码完成torch.quantization.quantize_dynamic(model, {torch.nn.Linear}, dtypetorch.qint8)。二是推理批处理把多个请求合并成一次模型前向计算能大幅提升吞吐。但要注意批处理会带来额外延迟适合离线或准实时场景。三是缓存对重复性高的请求比如相同文本的预测可以加一层Redis缓存效果立竿见影。5.4 监控和日志在线上的眼睛很多从零开始做AI工程的人部署完成就觉得结束了。但模型上线后还得知道它是否还在正常服务性能有没有下降。至少要监控四类指标服务可用性、推理延迟、请求量、模型输出分布。最后一类尤其重要因为输入分布可能发生变化导致模型效果下降。如果输出集中在某个类别或者分数的平均值异常升高就很可能是数据漂移。日志方面除了记录访问日志还要记录模型预测时的关键上下文比如输入样本、预测结果、耗时。这些日志能帮你事后排查线上问题也能沉淀为下一轮训练的数据来源。5.5 灰度发布与回滚模型更新不能直接全量替换否则线上出问题连反应时间都没有。比较稳妥的做法是先让流量的一部分指向新模型观察一段时间确认指标正常后再逐步放量。实现上有两种思路一种是在网关层按权重分流更复杂一点另一种是在应用层通过配置中心动态切换模型版本更轻量。即使是个人项目也建议保留上一版模型文件。这样发现问题时可以快速切换回去而不是重新训练一个。我在部署自己的问答系统时就保留model_v1.pth和model_v2.pth命名清晰回滚只需要改环境变量。6. 第一个AI工程项目的选题思路与验收标准6.1 不建议直接复现论文原因很现实很多人学AI工程喜欢“搞个大项目”比如复现一篇顶会论文。但对初期来说论文复现最大的问题是环境配置复杂、训练时间太长、调试难度高很容易一卡就是一周直接消磨积极性。更好的方式是选一个数据能下载、业务逻辑清晰、效果可感知的小项目先跑通全流程。等你对训练、部署、监控都有了体感再回去碰论文难度就低很多。6.2 选题的三个原则我会用三个原则衡量一个项目是否适合作为第一个AI工程项目数据可得且规模可控比如公开数据集可以下载或者自己造一个小数据集不需要申请太多权限。指标明确能做到“好就是好坏就是坏”比如准确率、F1分数避免主观评价。周期短从项目启动到第一次完整部署最好控制在两周以内。周期短反馈快成就感也来得快。6.3 几个可以直接上手的选题例子我推荐三个方向难度递增。第一个是文本情感分类。可以使用公开评论数据集训练一个BERT或简单的TextCNN然后用FastAPI包装成接口。这个项目能覆盖数据清洗、模型训练、模型部署、简单监控的全流程。第二个是图像分类服务。比如猫狗分类、手写数字识别还可以用ONNX做推理加速顺便了解模型转换。第三个是知识库问答机器人。用向量数据库存储文档通过检索增强生成的思路做问答既工程化又容易延展。这三个项目的共同点是数据容易处理模型结构成熟评估标准清晰适合作为入门地基。6.4 项目验收不能只看“能跑”在仓库的README里我给项目验收列了一个清单训练脚本能否通过一条命令完整执行验证集指标是否可复现是否有完整的配置文件和环境安装说明推理服务能否承受至少每秒10次请求是否有简单的监控日志是否写了项目文档说明数据来源、模型选择依据和已知问题这些条件看起来琐碎但它们决定了一个项目是“demo”还是“工程”。我当时就是从按着这个验收单一个个打勾开始才慢慢建立起工程交付的框架感。7. 过来人踩过的坑环境、数据、部署三个重灾区7.1 环境坑不要混用包管理器我在早期项目中同时使用过apt install python3-numpy和pip install numpy结果两个版本不一样导致代码里出现莫名其妙的类型错误。后续排查花了大半天还找不到原因。这个教训让我养成习惯一个项目里依赖安装方式要统一要么只用pip和venv要么只用conda环境。Docker内部尽量不装多余的系统包优先在requirements.txt里声明一切。7.2 数据坑路径硬编码是隐患我曾经把一个项目的数据路径写成/Users/myname/Documents/data/train.csv后来换电脑和同事协作时就破坏掉了。现在我的所有代码里路径要么用相对路径要么通过环境变量或配置文件传入。比如export DATA_PATH./data/train.csv python train.py --data-path $DATA_PATH这样既灵活又不会泄漏机器的个人目录结构。7.3 部署坑中文乱码和请求体大小部署中比较隐蔽但容易遇到的一个问题是中文编码。在Linux服务器上如果没有设置LANGC.UTF-8环境变量处理中文文本时可能出现乱码或报错。另一个容易被忽略的是请求体大小限制。默认情况下一些Web框架只允许接收最大几百KB的请求体如果图片是base64编码传入很容易直接报413错误需要主动调整配置。7.4 心态坑AI工程是长跑不是冲刺最后想说的经验不算技术但影响很大。从零开始做AI工程会遇到一连串问题且很多问题之间没有直接关系。你在环境配置上卡住不是因为你智商不够在部署时被网络问题整到半夜也不代表你方向错了。回头看我走过的路最大的进步其实是心态转变每次报错都是训练素材每多解决一次问题就多一分独立上手的底气。把“从零开始”当成一次持续的工程习惯建设而不是一个一蹴而就的目标这条路会走得稳得多。