如果你准备开始一个AI项目,最常见的念头是——先把数据扔进模型,跑出个像样的指标再说。我当初启动“ai-engineering-from-scratch”这个项目的时候,也是这么想的,但很快就被现实教育了。AI工程不是训练模型,而是把一个想法变成一套稳定、可维护、能迭代的系统的全过程。算法只占其中一小部分,数据、实验管理、部署、监控,每一样都能让人踩到怀疑人生。
这个项目就是我从零开始,把一个AI小项目完整走完工程化的全过程总结。从环境搭建、数据管道,到模型训练、部署上线,再到线上监控,一个不落。这篇文章我把整套思路、每一步的踩坑记录、以及最终沉淀的“能直接抄作业”的工程方法都写出来。适合正在学AI开发的初学者、刚负责AI落地项目的算法工程师,以及想把模型真正送上线的开发者。如果你也想搞清楚“AI项目到底怎么从零做成产品”,这篇文章应该能帮上大忙。
1. 项目初衷:先搞清楚AI工程到底在解决什么问题
1.1 AI算法和AI工程,差的不是一点半点
我见过太多团队,模型在Jupyter Notebook里跑得飞起,各种指标好看到可以直接写论文,但一旦要上线,就各种翻车:训练好的模型换个环境就load不起来,数据格式和线上对不上,推理速度慢到用户等不到结果,跑几天之后线上数据分布一变,模型效果肉眼可见地崩。
这就是典型的“算法思维”在做事。算法思维关注的是“在给定的干净数据上,怎么把正确率做高”。而工程思维关注的是“在不可控的真实环境里,怎么让系统持续、稳定、有效地工作”。ai-engineering-from-scratch这个项目,本质上就是在训练自己切换这两种思维。
说得直白一点:算法解决的是“能不能”,工程解决的是“行不行”。能训练出一个98%准确率的模型,但上线后每秒钟只能处理两个请求,那它依然是个玩具。反过来说,一个93%准确率的模型,只要能稳定跑上三个月、能快速迭代,它就比那个玩具值钱太多。
1.2 为什么从零开始比直接学工具更重要
市面上关于AI工程的材料不少,但大多散落在各个工具的文档里:MLflow怎么用、Docker怎么写、FastAPI怎么接。这些东西单独看都挺简单,但拼在一起就懵了——因为没有人告诉你它们之间是怎么协同的,更没有人告诉你每一步背后要防什么坑。
所以我选择从零开始自己搭一套最小的AI工程流程。不用重量级平台,不依赖付费服务,就用开源工具和普通Python脚本,把数据、训练、部署、监控串起来。这样做的好处是:每一个环节的能力都是自己亲手搭出来的,出问题的时候你知道该去查哪里,而不是对着黑盒系统干瞪眼。
这就像学做饭。你直接用预制菜包,十分钟出三道菜,看起来很高效。但一旦食材变了、火候不对,你就不会调整了。从买食材、切菜、调味、看火一步步来,虽然慢,但你掌握的是真正的厨艺。AI工程也是这个道理。
1.3 项目目标与适用人群定位
我给这个项目定了个非常务实的目标:用我手头真实可用的业务数据(客户流失预测),完整走一遍AI工程生命周期,把每一个环节做成可复现、可交接、可扩展的工程制品。学习者在项目结束后,应该能独立回答这几个问题:
- 你的数据是怎么来的、怎么清洗的、怎么保证一致的?
- 你的实验是怎么记录的?换个人来,能不能复现你最好的结果?
- 你的模型是怎么对外提供服务的?挂了怎么办?漂移了怎么发现?
这些问题,每一个都是面试里的高频题,也是实际工作中团队协作最容易扯皮的痛点。这不是一个“教你调参拿高分”的教程,是一个“教你从头到尾把模型做成系统”的实战记录。
如果你本身已经在做算法开发,却总觉得自己是在“孤岛”上训练模型,不懂工程侧的同事在说什么,那这个项目就是你的桥梁。如果你是刚从后端转过来做AI的开发者,这套流程也可以帮你迅速补齐从训练到上线的完整链路。反正我的结论是:AI工程不是某个工具能解决的,而是一整套思维方式和操作习惯的集合,越早建立越好。
2. 技术栈选型与整体架构设计
2.1 技术栈这样选,少走半年弯路
先放一张最终的选型清单,都是我实际用下来觉得稳定、文档全、社区活跃的方案。
| 环节 | 工具选型 | 选择理由 |
|---|---|---|
| 语言与解释器 | Python 3.10+,pyenv | Python是AI生态核心语言,pyenv能灵活切换版本 |
| 依赖管理 | poetry / venv + pip | 锁定依赖版本,杜绝“在我电脑上能跑” |
| 数据操作 | pandas / numpy / polars | 数据清洗与特征工程的标准选择 |
| 实验追踪 | MLflow | 统一记录参数、指标、模型产物,自托管免费 |
| 模型训练 | scikit-learn / XGBoost / PyTorch | 覆盖传统机器学习与深度学习两类场景 |
| 服务框架 | FastAPI + uvicorn | 轻量、自带Swagger文档、性能足够 |
| 容器化 | Docker + docker-compose | 一致性部署,本地和服务器行为一致 |
| 数据校验 | great_expectations / 手写断言 | 上线前拦截脏数据,比事后补救便宜得多 |
| 监控告警 | 自写统计脚本 + Prometheus(可选) | 检测数据漂移和接口健康状态 |
这个组合不是最炫的,但绝对是最稳的。我见过团队一上来就上Kubernetes、上各种重量级MLOps平台,结果复杂到没人敢动配置,版本升级都成了大工程。从零开始做AI工程,第一原则是能用简单方案就别上复杂系统,够用即可。
2.2 端到端流水线的模块划分
整个项目我从一开始就按“可插拔”的思路拆模块,每个环节做到可以独立测试、独立替换。目录结构大概长这样:
ai-engineering-from-scratch/ ├── data/ │ ├── raw/ # 原始数据,永不修改 │ ├── processed/ # 清洗后的数据 │ └── external/ # 外部字典、辅助信息 ├── src/ │ ├── data_ingestion/ # 数据读取与合并 │ ├── data_cleaning/ # 缺失值、异常值、重复值处理 │ ├── feature_engineering/ # 特征构建与编码 │ ├── model_training/ # 训练逻辑,配置驱动 │ ├── model_evaluation/ # 指标计算与对比 │ └── serving/ # FastAPI接口与预处理 ├── configs/ # 全流程YAML配置 ├── notebooks/ # 探索性分析,不作为正式流程 ├── scripts/ # run_pipeline.py 等入口脚本 ├── experiments/ # MLflow实验目录 ├── models/ # 模型产物与版本 ├── tests/ # 单元测试与数据校验 └── docker/ # Dockerfile与启动脚本这里有两个关键思路值得展开说一下。
第一,raw数据永不修改。这是数据工程里的铁则。任何清洗操作都应该生成一份新数据,保留完整的处理历史。否则你某天想回溯“这个字段我到底有没有做过标准化”,就只能在内存和聊天记录里考古了。
第二,notebooks只做探索,不做正式处理。我允许自己在Notebook里随便画图、试特征组合,但一旦确定某个处理逻辑,必须把它搬进src/pipeline里写成可执行的模块。Notebook是草稿纸,代码库才是正稿。如果让Notebook成为流程的一部分,后面每改一步都要手动跑一遍单元格,潜在的错误能埋到你怀疑人生。
2.3 从Notebook到工程流水线的思维转变
很多初学者最大的坎不是写不出训练代码,而是不知道训练完怎么办。在Notebook里,变量都在内存里,模型对象可以直接pickle,数据都是已经预处理好的。但在真实项目里,你落盘的每个文件都要能被下一个环节无脑加载,格式约定必须统一。
我在这里吃的亏值得大讲特讲。第一版流水线我为了省事,在训练脚本里直接读原始CSV,然后在内存里做清洗和特征工程。当时没觉得有什么问题,直到我需要把同一个预处理流程用在部署阶段的新请求上,才发现要么复制一份代码,要么序列化一个“预处理管道对象”。复制代码意味着两处维护,迟早不一致;预处理对象又不好调试,一旦线上数据格式变了,报错信息你能研究的只有“expected 64 features, got 63”这种。
所以我在重构时做了一个现在看起来无比正确的决定:把预处理流程和训练流程写成一个带状态的Pipeline对象,训练完成后连同模型一起保存。部署时加载同一个Pipeline,先走预处理再进模型,保证线上逻辑和训练逻辑完全一致。这个思路后来帮我省了无数线上故障排查的精力,强烈建议你也从一开始就按这个来。
3. 核心实操:从零跑通一个AI工程全流程
这一部分我以一个客户流失预测项目为例,带大家一步步走完整套流程。数据集用的是开源电信客户数据集,包含账户信息、套餐信息、服务使用行为等,任务是预测客户未来是否流失。模型用什么不是重点,重点是每一步工程环节的标准做法。
3.1 环境准备:让“在我电脑上能跑”成为历史
环境问题是AI工程里最烦人但最基础的一环。很多项目死在不该死的依赖冲突上:TensorFlow要numpy 1.x,PyTorch要2.x,两个一装就互相打架。我在项目启动初期就立了规矩:用pyenv隔离Python版本,用venv+poetry锁定依赖。
具体的初始化步骤我直接列出来,都是我实践过最顺的路径。
# 安装并切换Python 3.10.12 pyenv install 3.10.12 pyenv local 3.10.12 # 创建虚拟环境 python -m venv .venv source .venv/bin/activate # 使用poetry管理依赖 poetry init poetry add pandas numpy scikit-learn xgboost mlflow fastapi uvicorn这里我特别想提醒一个很多人忽略的点:requirements.txt一定要固定到具体的版本号,不要用>=和<=的范围约束。我见过一个项目因为某个依赖从1.2.3自动升到1.2.4,结果底层C扩展行为变了,训练出来的模型效果直接掉了几个点。排查了整整两天,最后才发现是“小版本更新”惹的祸。所以在项目初始就把版本锁死,截图记录当时的依赖树,这才是“可复现实验”的地基。
pyenv和venv的区别也值得说清楚。pyenv管的是“用哪个Python版本”,venv管的是“这个项目装了哪些包”。两者配合才能做到不同项目互不干扰。如果你用conda,也可以实现类似的效果,核心思路是一样的:环境隔离是底线,不是可选步骤。
3.2 数据管道:清洗、特征工程、数据校验一个都不能少
拿到原始数据后,第一件事不是立刻切训练集,而是先看数据的“长相”。我用pandas.DataFrame的describe()和info()做初步扫描,这能快速暴露明显的缺失值模式和类型问题。
以这个客户流失数据为例,有以下几个真实碰到的坑:
- 客户总消费金额是0的,可能压根没用过服务,也可能是数据没采集到,二者处理方式完全不同;
- 某些分类字段里存在“Unknown”和“ ”(空格)这样无法直接归类的值;
- 连续特征里存在极端离群值,比如某个客户的上网时长比其他人大了100倍,多半是单位错误。
处理这些问题的原则是:能不删样本就不删样本,能保留原始字段就保留原始字段。清洗逻辑写好后,我用great_expectations定义了一组数据校验断言,用于跑流水线时自动检查数据结构是否符合预期。
数据校验这块很多人不重视,但我强烈建议至少做一个简化版:写一个validate_data(df)函数,在里面死检查字段数量对不对、关键字段缺失率是否超过阈值、类别取值是否在预期集合里。上传到服务器的模型,前置一个简易校验,总比线上收到脏数据再backfill强得多。
特征工程方面,我做的最有价值的操作是把数值型字段做标准化,把偏态分布字段做log变换。别小看这两步,对树模型可能影响不大,但对逻辑回归这类线性模型来说就是天壤之别。我在实验记录里看到同一个模型在有无log变换的两个版本上,AUC差了0.06。这就是工程细节的价值——每一个不起眼的处理步骤,最后都会在指标上看得到。
3.3 模型训练与实验追踪:没有记录的实验等于白做
训练环节我做得相对“笨”:没上复杂的分布式训练,就坚持一个原则——一切参数配置化,一切实验可追踪。
所谓配置化,就是不在代码里写死超参,而是用一个YAML文件统一管理。比如这样:
model: name: xgboost params: max_depth: 5 learning_rate: 0.05 n_estimators: 300 subsample: 0.8 data: train_path: data/processed/train.csv val_path: data/processed/val.csv target_col: churn训练脚本读取这个配置文件,自动把参数注入模型。这样做的直接好处是:当你要试下一组超参时,不需要去翻代码、改常量,只需要改配置文件并重新运行。代码永远不需要看参数脸色。
实验追踪我用的MLflow,这是目前最省心的方案。你只需要在训练代码里加短短几行:
import mlflow with mlflow.start_run(run_name="experiment_log_scale_v2"): mlflow.log_params(cfg["model"]["params"]) mlflow.log_metrics({"auc": auc_score, "logloss": logloss}) mlflow.sklearn.log_model(model, artifact_path="model")这几行的价值,等你回看三周前的实验时才能真正体会到。没有它们,你只能靠文件名和日期去猜,比如“model_v2_final_真的最终版.pkl”——这种命名方式我见得太多,真的是灾难级别。有MLflow之后,每一次实验的输入数据版本、模型超参、最终指标、模型文件本身全都在一次run里归档。会议室里讨论“哪个版本效果好”,直接看dashboard,不用靠嘴争。
训练本身我坚持做几种基础设置:固定随机种子、设置early stopping、用分层采样切分数据。固定随机种子至关重要,否则同一个代码跑两次结果都不一样,实验对比就全部失真。
3.4 模型评估与调优:离线指标和业务目标要对齐
模型训练出来后,一上来就看AUC、F1这种统计指标是个陷阱。它们只能给你一个模糊的技术印象,真正要问的问题是:“如果把这个模型部署上线,对业务有什么实际影响?”
我做评估时一定会多算三个指标:覆盖率(coverage)、误杀率(false positive rate)、可解释性。
拿客户流失来说,覆盖率的含义是:模型能识别出多少真正流失的用户。误杀率的含义是:模型把多少本来不会流失的用户误判为高风险,导致运营团队白费力气做挽回。这两个指标直接决定业务成本。
计算过程不复杂:
- 覆盖率 = 被模型正确识别为流失的用户数 / 实际流失用户总数
- 误杀率 = 被模型误判为流失的用户数 / 实际未流失用户总数
我印象最深的是一个实验,直接用默认阈值0.5时,精确率很高但覆盖率只有42%。业务侧一看就摇头:这模型漏掉了一半以上的流失用户,作用有限。后面我调整了决策阈值,把覆盖率拉到75%,误杀率从8%升到18%,业务侧反而点头了——因为他们更在意“不要把真正流失的人放跑”,多召回几个有点误伤的客户,运营成本还可以接受。
这就是调优的关键:调的不是模型的“科学指标”,而是业务权衡。模型调优不该只盯着超参搜索,更应该盯着阈值、成本矩阵、误判代价这些决策环节。在从零开始的AI工程里,这一步是最容易被忽略却最能体现成熟度的分水岭。
3.5 模型部署:把pkl文件变成线上接口
模型定稿之后,下一步是把模型“工业界化”——也就是部署成对外提供服务的HTTP接口。我用FastAPI来做这件事,主要因为它在性能和易用性上取得了很好的平衡。
核心代码很简单,一个最小的推理接口长这样:
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import joblib import pandas as pd app = FastAPI() class CustomerFeatures(BaseModel): tenure: float monthly_charges: float total_charges: float contract_type: str payment_method: str # 加载训练好的完整Pipeline(包含预处理) pipeline = joblib.load("models/pipeline_v3.pkl") @app.post("/predict") def predict(features: CustomerFeatures): try: df = pd.DataFrame([features.model_dump()]) prob = pipeline.predict_proba(df)[0][1] return {"churn_probability": round(prob, 4)} except Exception as e: raise HTTPException(status_code=400, detail=str(e))这里有个细节值得画重点:我保存的不是裸模型,而是“预处理+模型”的完整Pipeline。我一开始犯过错,只存了模型,把特征工程放在了部署代码里手写了一遍。结果训练时用了log变换和标准化,部署时漏了log变换,预测结果直接崩坏。后来学乖了,在训练脚本里用sklearn的Pipeline把Scaler和Model包在一起,作为单一对象保存。这之后,训练和预测用的永远是同一套逻辑,稳稳当当。
模型文件的管理也要规范化。我不允许出现Model_final_v2这种命名,而是用一个带版本号的目录:
models/ ├── v1/ │ ├── pipeline_v1.pkl │ ├── metrics.json │ └── metadata.yaml ├── v2/ ...Docker部署是让这套服务在任何机器上行为一致的关键。我写了一个非常基础的Dockerfile,重点是先把依赖拷贝到镜像,再用非root用户运行服务,避免容器里权限蔓延的安全隐患。
FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt && \ useradd -m -u 1000 appuser COPY --chown=appuser . . USER appuser EXPOSE 8000 CMD ["uvicorn", "src.serving.app:app", "--host", "0.0.0.0", "--port", "8000"]然后一行命令启动服务:
docker build -t churn-api:v3 . docker run -d -p 8000:8000 churn-api:v3 curl -X POST http://localhost:8000/predict \ -H "Content-Type: application/json" \ -d '{"tenure":12,"monthly_charges":59.5,"total_charges":714,"contract_type":"month-to-month","payment_method":"electronic_check"}'你会看到一个类似{"churn_probability": 0.7231}的返回。到这里,模型已经变成别人可以直接调用的接口了。但从“能调用”到“能长期稳定运行”,中间还隔着监控这条河。
3.6 模型监控:上线只是开始,不是结束
很多团队把模型部署上线的那天当作终点庆功,但我的经验是:那只是训练和工程事故的起点。上线后的前两周是事故高发期,最常见的三个问题我全遇上了:
第一个是数据漂移。客户群体的结构和训练集有偏差,导致模型表现快速下降。拿流失预测来说,某个季度客户套餐结构大变,新用户比例飙升,模型在旧数据上学到的模式瞬间失效。
第二个是特征漂移。线上请求里的字段分布逐渐偏离,比如总消费金额字段开始出现大量负值,或者某个分类特征出现训练时从未见过的取值。
第三个是效果衰减。就算所有特征分布都正常,模型效果也可能因为外部因素(竞品策略、季节因素)持续衰减。
针对这些,我写了一个简易的监控脚本,每天对比线上请求的特征分布和训练集的特征分布,计算每个特征的PSI(Population Stability Index)。PSI超过0.1就提示“轻度过往经验积累”,超过0.25触发告警。实现并不复杂,核心就是分箱后对比占比的差异,几十行代码就能搞定。
实际操作里,我还加了一个“预测记录落库”的逻辑:每收到一条预测请求,把特征、预测概率、时间戳写入一个存储表。等一段时间后,如果这批用户的实际行为结果出来了,就可以和预测做对照,回算线上真实的AUC。这是一条宝贵的反馈闭环——没有这个闭环,你的模型做得再精致也都是在盲人摸象。
4. 常见问题与排查技巧实录
4.1 环境冲突:CUDA、Python版本与依赖地狱
环境问题是最常见、也最容易劝退新手的。我这里直接列一个我自己写的“环境排查五步曲”:
- 先查Python版本是否匹配:
python --version; - 再用
pip list | grep torch检查框架关联的依赖版本; - 查看CUDA是否可用:
python -c "import torch; print(torch.cuda.is_available())"; - 用
poetry check检查当前环境与锁定文件是否一致; - 如果全查不出来,最有效的方法是删掉虚拟环境重建,别硬修。
我见过太多人在“修环境”这件事上花了整整一天,最后发现重装一个干净的venv只需要十五分钟。环境问题不存在“修复之王”,绝大多数所谓修复都是浪费时间的安抚行为。直接在全新的环境里重装依赖,往往更干净。
还有一次我特别想吐槽的场景:同事A用的是Windows,同事B用的是Mac,同一个项目A能跑B就报错,原因是代码里用了绝对路径。所以我坚持一个规范——项目代码里一律不允许出现绝对路径,路径统一从项目根目录的相对路径解析。这个规范后来救了项目组无数次。
4.2 数据泄露:最隐蔽的AI工程事故
数据泄露是所有AI问题里最危险的一个,因为它通常不会让训练报错,而是让所有指标虚高。等模型上线才发现真实效果一塌糊涂,那时候排查成本就很大了。
最常见的泄露场景有这么几种:
- 使用全局统计量做标准化:特征工程时对整个数据集做了标准化,而不是只对训练集拟合scaler。泄露点在于验证集的信息污染了训练过程,导致离线指标虚高。
- 使用未来信息:做时间序列任务时不小心把未来时段的统计特征(比如“未来一周均值”)当作特征用。
- 重复样本:同一个用户的多条记录同时出现在训练集和验证集,相当于让模型“背答案”。
预防措施其实不复杂:第一,任何需要fit状态的处理逻辑(标准化、编码)都必须在训练集上fit完再transform到验证集。第二,切分数据时先打乱再切分,分类任务要用stratify参数保证类别比例一致。第三,做去重:按用户ID检查训练集和验证集是否有重叠样本。
我亲身经历过一次AUC从0.93跌到0.81的事故。排查了三天,最后发现是Notebook里做标准化时用了全量数据的均值和方差。这类错误隐蔽在几乎所有新手项目里,排查思路很死板但有效:把一个熟悉的人(比如训练集里的某条样本)单独拉出来,强行通过模型预测一遍,然后把预测结果和特征逻辑一步一步推回去,看有没有不该出现的“参考”在里面。
4.3 训练过程不稳定的经验教训
训练过程不稳定有几个明显的症状:Loss曲线忽高忽低、同一份数据两次训练指标差很多、早停后的最佳epoch每次都不一样。
我排查下来最匹配的症状是随机种子没有固定。在NumPy、Python、以及所依赖的模型框架里分别设置随机种子,确保shuffle、初始化、数据增强的随机性都被锁定。如果你的代码在当前环境中无论如何都稳定了,但换个机器又不稳定,那多半是某个后端起随机逻辑的库没有跟着锁种子。
另外还有一个训练上的小技巧:如果Loss曲线抖动地厉害,先检查是否该调小学习率。别人告诉你Adam自适应调节不也意味着可以无脑设learning_rate=0.01,当前数据量少的时候这个值就是过大会震荡。
养成看曲线密集检查的习惯,发现前几十个batch的loss不降反升,第一反应别是改结构,先把学习率和batch_size降到原来的十分之一试试。这条经验省掉了我非常多无意义的模型结构修改。
4.4 部署后线上线下结果不一致
这是AI工程领域最经典的问题:训练时模型在测试集上表现优秀,部署后处理新请求却一塌糊涂。原因大多不是模型本身崩了,而是线上的预处理和训练时不统一。
我的标准排错流程是这样的:
- 抓一条线上真实反馈的数据,用它走一遍本地加载模型预测,对比线上返回结果;
- 如果差异明显,把线上输入变成CSV,和本地训练流程首次处理这个输入时生成的中间特征对比;
- 找出是哪个字段、哪一步特征处理产生差异,修复后再次回归测试。
产生差异的根源还是前面强调的问题:训练时把预处理逻辑写在Notebook里,部署时又重新写了一遍。用了Pipeline对象整体保存后,这问题几乎绝迹。如果非要用自己写的预处理函数,那就务必把函数文件在训练和部署两端同时提交进代码库,并加单元测试覆盖核心字段的处理结果。
这里再推荐一个习惯:给最终模型建立“黄金样例”测试集。准备10~20条有代表性的测试数据,把它们的真实预测结果固化成JSON文件。以后任何一次代码改动,只要把这几条样例的预测结果跑一遍,和黄金文件里的结果比对,就能快速发现回归问题。这是工程可靠性的廉价保险。
4.5 常见问题速查表
把我在整个项目中踩过的坑整理成一张表,遇到类似症状可以直接按图索骥。
| 症状 | 可能性 | 排查优先级 |
|---|---|---|
| 换台机器就报ImportError | 未锁版本/未建虚拟环境 | 先检查requirements.lock |
| 模型指标极好但线上很烂 | 数据泄漏/线上预处理不一致 | 先查标准化时机 |
| 不同epoch模型差距极大 | 随机种子未固定 | 先检查种子设置 |
| 内存持续增长不释放 | 使用了全局缓存list未清理 | 先查服务进程里是否有循环append |
| 标准化后字段出现NaN | 全零方差字段除零 | 先查该字段的方差 |
| 预测接口偶尔超时 | 模型推理未做批处理 | 先看CPU/GPU占用和等待队列 |
5. 工程化落地的三个核心习惯,以及我最后想说的
5.1 从“能跑”到“能上线”的距离
回头看整个ai-engineering-from-scratch项目,最深的体会是:这是一个把标准从“能跑”抬升到“能上线”的过程。“能跑”意味着代码能出结果,“能上线”意味着结果可复现、接口可调用、故障可发现、迭代可继续。两者之间的差距,比大部分人想象的更大。
具体来说,我从这个项目里收获了三样改变工作习惯的东西:持续的数据校验、强制性的实验记录、以部署为导向的模型保存方式。这三样东西单独拿出来都算“琐碎小事”,但合在一起,它们才是AI项目能不能从个人玩具变成协作产物的分水岭。任何一步缺失,都会在某个你完全想不到的深夜以线上事故的形式敲你的门。
5.2 最后一句话:AI工程不是某个工具,而是一套纪律
我在做这个项目的时候,一直提醒自己:AI工程的核心能力不在某一个库或某一个平台,而是一套做事的方法论。它要求你对数据负责、对实验过程负责、对线上的每一个预测负责。这套纪律一旦建立起来,以后无论学什么新工具,上手速度都会快很多,因为你不再“只会用”,而是“知道为什么这么用”。
如果你正在规划自己的AI工程之路,我的建议是别从最复杂的平台开始,也别一上来就追求Kubernetes级别的服务编排。挑一个你熟悉的业务场景,从数据校验和环境管理做起,先实现一条极简的端到端链路,再逐环节加固。这条路径看起来比抄底神器慢,但却是唯一能通向“亲自掌控每一个环节”的路。我自己正是这样走过来的,也希望这篇文章能帮你少试几次错,把这套纪律踏踏实实地建立起业。