做了这么多年AI相关的工程落地我发现一个挺有意思的现象身边很多朋友能用现成的框架跑通模型也能调API做点智能应用但一旦遇到生产环境里的刁钻问题就明显底气不足。模型效果为什么波动、数据管线哪里出了岔子、评估指标和线上表现为什么对不上这些“黑盒”一旦失效整条链路就跟着瘫。所以才会有“ai-engineering-from-scratch”这种项目标题的出现——它要解决的不是“怎么用AI”而是“怎么把一个AI系统从零到一真正掌控在自己手里”。这个标题拆开看就两条线索一条是“ai-engineering”指的是AI系统的工程化能力涉及数据、模型、评估、部署、监控一整条链路另一条是“from-scratch”强调不依赖现成的黑盒方案从原理、代码、数据流开始一步步重建。适合的人群也很明确有一定编程基础、正在做AI落地项目却总觉得“差一层理解”的工程师或者已经在用现成AI平台但想深入底层、具备独立排查和优化能力的人。接下来我会把这条路径完整拆开从项目定位、核心环节、实操步骤到高频踩坑问题按我自己的实践经验逐个讲透。1. 项目定位拆解为什么“从零搭建AI工程能力”是一条少有人走的路1.1 from-scratch背后的三层深意先说“from-scratch”这个词。很多人一听就觉得是要把神经网络从反向传播手写一遍其实对工程方向的人来说真正的“从零”并不是去复现Transformer论文而是指你在使用任何工具、框架、云服务之前先建立对整条链路每个环节的掌控力。拿我自己带团队的经验来说三层深意最值钱第一层是数据意识的建立。大多数线上AI系统的效果瓶颈不在模型结构而在数据质量。从零搭建意味着你要自己处理原始日志、清洗脏数据、做版本管理这个过程会逼着你理解“模型吃进去什么才会吐出来什么”。直接调接口的人永远不会被脏数据折磨也就永远学不会怎么设计数据管线。第二层是评估体系的构建。很多教程教你训练模型却几乎不教你“怎么确认模型真的做好了”。from-scratch的做法是让你从空白开始自己定义评估指标、划分数据集、建立回归测试。这就像学开车不能只学踩油门还得学会看仪表盘和判断路况。第三层是部署和监控的掌控。一个模型训练出来只是起点怎么用最低延迟提供服务、怎么监控线上效果变化、怎么在数据漂移时发出告警这些才是工程能力的分水岭。从零搭建会让你亲手经历“模型在离线评估里很好上线却崩了”的完整过程这种教训比任何文档都深刻。1.2 这个项目适合谁学完能获得什么基于对这个标题背后的内容设定理解我认为它的目标受众主要有三类第一类是从算法转向工程的人。你懂模型原理但是对数据管线、服务化部署、监控告警这套工程体系不熟。这类项目能把你的能力版图补齐真正做到“模型能训练也能落地”。第二类是应用开发工程师。你会写代码也用过AI接口但你想知道接口背后的事。这类项目会帮你打开黑盒理解请求到了服务端之后发生了什么、为什么会超时、为什么结果不稳。第三类是技术决策者或技术负责人。你需要评估AI项目的可行性和成本如果不懂底层链路很容易被供应商或团队里的“技术黑话”带着走。自己动手搭一遍最小系统是建立判断力最有效的方式。学完这套内容你获得的不是某一招技巧而是一张完整的AI工程能力地图知道每个环节有哪些常见方案、各自的成本收益、遇到问题该从哪一层开始排查。这种系统性的掌控感才是“from-scratch”最核心的交付物。2. AI工程全链路拆解数据、模型、评估、部署、监控的选型逻辑2.1 数据管线喂给模型的第一口粮我把数据管线放在第一个讲是因为它最容易被低估也最容易出大问题。从零搭建时一般的数据管线至少要包含采集、清洗、标注、版本管理、切分五个环节。采集环节要回答“数据从哪来”的问题。可能是业务数据库、日志文件、第三方API或者是公开数据集。每种来源都有不同的时效性、权限和数据格式工程上需要设计统一的接入层把异构数据源转成标准化格式。清洗环节的核心是去重、去噪、处理缺失值。这里有一个亲测有效的原则清洗规则的每一步都要有据可查最好写成可复跑的脚本而不是在Notebook里手动点选。比如处理用户评论数据时纯文本去重要基于归一化后的内容做否则“我很好”和“我很好”会被当成两条不同数据白白浪费标注成本。标注环节是多数团队绕不开的坎。如果预算充足可以用人工标注平台预算有限就得设计规则标注和主动学习策略。我常用的方案是先写一套启发式规则打底再用小模型挑出置信度低的样本送人工精标这样能把标注成本压到全量人工的30%左右。版本管理是数据工程里最容易偷懒、也最致命的一环。数据不是静态的业务一变、清洗规则一改数据集就变了。没有版本管理的话你上周训练A模型用的数据集和这周训练B模型用的很可能已经是两套东西到时候对比实验效果都是无效的。我在项目里会用类似dvc的工具配合对象存储来管理数据版本每次清洗完打个tag关联到对应的模型实验上。最后是数据切分。这里要特别强调时序泄漏问题。凡是带时间戳的数据切分时必须以时间点为界严禁随机打乱。否则模型相当于“偷看了未来”离线指标再漂亮上线必崩。2.2 模型训练与微调从预训练到业务适配在模型环节from-scratch并不意味着必须从随机初始化开始训练而是指你要清楚当前方案所处的位置。如果是文本分类这类任务通常走预训练模型微调路线加载一个通用bert类模型在业务数据上继续训练。关键点是学习率的设置、冻结层数的选择、以及对抗过拟合的正则手段。我习惯先用较小的学习率1e-5到3e-5区间试跑一个epoch观察loss曲线下降是否平稳再决定要不要调整。如果是生成类任务或大规模语言模型应用重点则从“训练”转向“适配”。常见做法有三种提示词工程、检索增强生成RAG、参数高效微调LoRA等。三者的成本从低到高效果上限也从低到高。我的选型逻辑是先试prompt不够再加RAG再不够才上LoRA微调。需要注意的是从零搭建的语境下你至少要把训练脚本、loss曲线记录、checkpoint保存、随机种子固定这些基本功做扎实。很多人跑通一次训练就以为完事了其实训练过程中最值钱的是可复现性。同一份数据和代码固定好随机种子和依赖版本跑出来的结果应当完全一致做不到这一点后续所有优化实验都会被“玄学”干扰。2.3 评估体系给模型建立质检关卡评估是AI工程里最容易被稻草化处理的一环。大多数人训练完只看一个accuracy就完事但真实业务场景里单一指标会掩盖大量问题。以分类任务为例至少要同时看精确率、召回率、F1并且按业务场景加权。比如垃圾内容识别场景误杀一条正常用户发言带来的体验伤害可能比漏掉一条垃圾内容更大那么精确率的权重就要高于召回率。另外两件容易被忽略的事是坏case分析和子集评估。坏case分析要求你亲自去看模型预测错误的样本找出错误模式——是数据标注错了、还是文本太相似、还是训练分布有偏。子集评估则是把测试集按维度切分比如按内容长度、按来源渠道、按时间区间分别计算指标这样才能发现模型在哪个细分场景下悄悄退化。我在模板项目里会专门搭一个评估模块把测试集、评估脚本、指标报告打包成一个可重复执行的命令。这样每次模型变更后一键输出完整报告再和基线对比。这一套动作看起来朴实其实是整个AI工程体系里投入产出比最高的部分。2.4 部署与推理优化模型上线的最后一公里部署环节是“从零搭建”和“调包跑通”分道扬镳最明显的地方。离线训练时模型只是文件上线后它要以服务的形式响应请求这个过程中有三类问题必须面对。第一类是模型格式和推理引擎的选择。PyTorch模型直接提供服务可以但通常不是最优解。可以考虑导出为ONNX格式做推理加速或者针对特定硬件使用优化引擎。我的经验是中小项目直接用ONNX Runtime加FastAPI封装代码量不大性能提升明显维护成本也低。这个方案我用了很久实测下来很稳。第二类是资源估算和扩容策略。模型服务是内存密集型还是CPU密集型直接决定了机器规格。一个亿级参数模型加载到GPU显存就要占用2-4GB加上运行时开销单机并发量很容易触到天花板。工程上要提前压测测定单实例的吞吐和延迟曲线再根据业务流量峰值倒推实例数。第三类是服务的可观测性。上线只是开始你得知道服务有没有在正常工作。最基本的要做到三件事记录每个请求的延迟和状态码、监控模型推理的输入输出分布、设置异常告警。这样出了问题你是从日志和指标入手而不是靠用户投诉会是完全不同的体验。2.5 可观测性与持续迭代让模型效果不滑坡模型上线后性能下降是一个常态。原因可能是线上数据分布变了也可能是业务规则变了导致标注口径变了。无论哪种没有监控体系就只能事后救火。我的做法是建立一套“黄金样本集”从训练集里抽出一批有代表性的样本固定下来每次要上线新模型时先在黄金样本集上跑一遍保证关键case的效果不退步。同时监控线上请求的输入分布定期和训练分布做对比一旦KL散度超阈值就触发告警提示该考虑增量训练了。持续迭代的流程应该是数据回流 - 增量标注 - 定期重训 - 灰度对比 - 全量发布。整个周期里每一步都要有数据记录和可回滚方案。这个话题展开说内容很多我这里先点到为止后面实操环节会给出具体配置参考。3. 从零实操搭建最小可用AI工程系统的四个关键步骤3.1 第一步搭建基础设施与项目骨架开始动手前先把工程骨架确定好。我建议目录结构参考以下布局ai-engineering-from-scratch/ ├── data/ │ ├── raw/ # 原始数据 │ ├── processed/ # 清洗后数据 │ └── versions/ # 数据集版本记录 ├── src/ │ ├── pipeline/ # 数据管线 │ ├── models/ # 模型定义与训练 │ ├── evaluation/ # 评估模块 │ └── serving/ # 服务化部署 ├── experiments/ │ ├── logs/ # 训练日志 │ └── checkpoints/ # 模型权重 ├── tests/ # 单元测试 └── requirements.txt # 依赖清单基础设施方面Python环境用venv或conda隔离即可核心依赖包括pandas、numpy、scikit-learn、transformers、torch、fastapi、uvicorn、onnxruntime。版本锁定很重要建议requirements.txt把所有依赖的主版本号固定下来。注意在开始任何模型训练之前先花时间把“数据版本记录”和“实验记录”机制搭好。这看起来不是当务之急但等你在第20次实验后想回溯某个效果好的模型时会发现这两样是救命稻草。3.2 第二步实现数据管线和训练基线假设我们的场景是垃圾评论识别先看数据清洗脚本怎么写。核心是保持可复现性# src/pipeline/clean.py import pandas as pd import re def normalize_text(text: str) - str: text text.lower().strip() text re.sub(r\s, , text) text re.sub(r[^\w\s\u4e00-\u9fff], , text) return text def clean_raw_data(input_path: str, output_path: str) - None: df pd.read_csv(input_path) df[clean_text] df[text].apply(normalize_text) df df.drop_duplicates(subset[clean_text]) df df.dropna(subset[clean_text, label]) df.to_csv(output_path, indexFalse) if __name__ __main__: clean_raw_data(data/raw/train.csv, data/processed/train_cleaned.csv)数据切分要强调时序原则# src/pipeline/split.py from sklearn.model_selection import train_test_split def temporal_split(df, test_ratio0.2): df df.sort_values(timestamp) split_idx int(len(df) * (1 - test_ratio)) train_set df.iloc[:split_idx].copy() test_set df.iloc[split_idx:].copy() # 再从训练集中切出验证集同样按时间顺序 valid_ratio 0.1 valid_idx int(len(train_set) * (1 - valid_ratio)) valid_set train_set.iloc[valid_idx:].copy() train_set train_set.iloc[:valid_idx].copy() return train_set, valid_set, test_set训练脚本用transformers库加载预训练模型做微调。固定随机种子是关键这点一定要写进代码# src/models/train.py import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification from transformers import Trainer, TrainingArguments def set_seed(seed: int 42): torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) import numpy as np import random np.random.seed(seed) random.seed(seed) set_seed(42) model_name bert-base-chinese tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name, num_labels2) training_args TrainingArguments( output_direxperiments/checkpoints, learning_rate2e-5, per_device_train_batch_size16, num_train_epochs3, logging_direxperiments/logs, save_strategyepoch, evaluation_strategyepoch, load_best_model_at_endTrue, metric_for_best_modeleval_f1, )这里有个很实际的建议不要一开始就盯着最先进的模型。先拿一个中等体量的预训练模型跑通全流程再考虑换更大的模型。因为全流程的坑数据、训练、评估、部署不会因为模型换大而消失先用小模型把流程理顺后面迭代速度快很多。3.3 第三步建立评估模块和回归检测评估脚本要达到“一键出报告”的效果。我的实现思路是把所有评估逻辑封装成一个命令行入口# src/evaluation/evaluate.py import json import numpy as np from sklearn.metrics import classification_report def evaluate_model(model, tokenizer, test_dataset, label_names): # 这里省略模型推理代码核心是生成预测结果 predictions [] golden [] # ... 推理循环 ... report classification_report( golden, predictions, target_nameslabel_names, output_dictTrue, zero_division0 ) return report def save_report(report, output_pathexperiments/eval_report.json): with open(output_path, w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2)报告内容至少要包含整体指标、按样本长度的分层指标、错误样本清单。我用一个简单办法保留错误样本评估时把预测错的样本id和原文一起存进文件方便人工分析错误模式。灰度对比的回归检测也应该在这步搭起来。保存一份“黄金样本集”的预测结果作为基线之后每次模型变更都在这份黄金样本上重新预测计算和基线预测一致的比例。如果关键case发生变化说明模型行为有变更需要人工确认是否合理。3.4 第四步服务化部署部署端我用FastAPI加ONNX Runtime实现轻量推理服务。先把训练好的PyTorch模型导出为ONNX格式python -m transformers.onnx --modelexperiments/checkpoints/best-model/ onnx/model.onnx服务代码核心如下# src/serving/app.py from fastapi import FastAPI, Request import onnxruntime as ort from transformers import AutoTokenizer app FastAPI() tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) ort_session ort.InferenceSession(onnx/model.onnx, providers[CPUExecutionProvider]) app.post(/predict) async def predict(request: Request): payload await request.json() text payload[text] inputs tokenizer(text, return_tensorsnp, max_length128, truncationTrue, paddingmax_length) logits ort_session.run( None, {input_ids: inputs[input_ids], attention_mask: inputs[attention_mask], token_type_ids: inputs[token_type_ids]} )[0] pred int(logits.argmax(axis1)[0]) return {prediction: pred, prob: float(logits.max(axis1)[0])}启动服务的命令很简单但生产环境里一定要加两个东西请求量限流和超时控制。用uvicorn启动前面再套一层nginx做负载均衡。uvicorn src.serving.app:app --host 0.0.0.0 --port 8000 --workers 2部署完成后用curl做一个冒烟测试curl -X POST http://localhost:8000/predict \ -H Content-Type: application/json \ -d {text: 这个商品质量太差了再也不买了}正常情况下返回json格式的预测结果。冒烟测试通过后部署环节最核心的一件事才算完成。4. 踩坑实录AI工程从零到一的五个高频问题排查4.1 数据泄漏最隐蔽的精度杀手第一次从零搭建的同学最容易掉进数据泄漏的坑。典型场景是数据清洗时用到了全量数据的统计信息比如用全量数据的均值做填充然后才切分训练测试集。这就导致测试集的信息在训练阶段“被看见”离线指标虚高。排查方法很直接检查数据预处理步骤里所有涉及全局统计的操作确认它们只在训练集上fit在测试集上只transform。更隐蔽的是文本特征里有用户id这类高基数列模型可能学到“记住用户”而不是“理解内容”。这种情况在评估报告里表现为测试集指标高得离谱泛化却惨不忍睹。我的经验是对每一列特征问自己三个问题——这个特征在当前时间点能拿到吗它是否携带了目标信息换一批新数据它还稳定存在吗任何一个回答不肯定这个特征就要慎重使用。4.2 损失震荡与收敛判断的误区训练时loss曲线上下跳得厉害新手第一反应是调低学习率。我踩过这个坑之后才意识到loss震荡的原因可能是数据处理细节文本序列长度padding差异大、batch大小太小、或者学习率预热不够。排查顺序按成本从低到高先确认batch size是否合理过小会导致梯度估计噪声大再检查数据预处理是否统一了长度分布最后再动学习率。固定好种子后如果同一配置两次训练结果不一致优先怀疑数据读取顺序是否有随机shuffle以及GPU算子是否引入不确定。收敛判断也别只看loss绝对值。训练集loss很低但验证集loss偏高是过拟合信号训练和验证loss都偏高但持续缓慢下降说明模型容量不够或者学习率偏低。两种情况的处理方式完全相反先判断类别再动手才是正道。4.3 训练与推理行为不一致模型“换脸”问题模型在离线评估里表现良好线上推理结果却对不上。这个问题的根源往往在预处理环节离线pipeline里做过的文本归一化、去停用词在线服务里没有一模一样地复制。我遇到过一个具体案例离线训练时把英文字母统一转成小写线上服务代码里却忘了这一步。结果用户在线上输入“VIP”和“vip”得到不同的分类结果而离线评测完全没暴露这个问题。解决办法是把预处理代码抽成独立模块训练和推理共同引用同一份代码而不是各写各的。记住一条铁律训练时对数据做了什么推理时必须原封不动再做一遍。4.4 部署后延迟飙升瓶颈定位思路模型上线后接口延迟从50毫秒涨到500毫秒第一反应是加机器配置但通常瓶颈另有其人。排查路径应该是先看日志确认耗时分布在哪个阶段。是网络层耗时高还是推理引擎耗时高还是预处理耗时高。用ONNX Runtime时有一个常见坑如果数据没有转成模型期望的格式或者每次都做动态padding推理引擎内部会触发重新编译图延迟会比正常情况高出数倍。我后来把输入统一padding到固定长度用bfloat16精度做推理单次延迟降了40%不止。还有一个容易忽略的地方是接口的批量推理能力——如果你的业务允许异步批量处理请求合并推理的效率会显著高于单条推理尤其对GPU部署场景来说更是这样。4.5 线上效果退化模型漂移的早期发现模型上线时效果很好三个月后明显变差。排除代码bug之后最常见的原因是数据分布漂移。早期发现的数据指标有两个一个是线上请求输入的特征分布比如文本长度均值、关键词频率另一个是模型预测的置信度分布比如平均置信度是否持续走低。这两个指标可以做成定时监控任务每天对比当天的分布和历史基线一旦偏差超过阈值就告警。线上效果退化和新版本业务规则也要区分开。我曾经排查过一个问题模型本身没变化但业务方更改了内容审核标准导致标注口径变了。这类问题靠模型侧优化解决不了需要业务方和数据团队对齐口径。最后分享一点个人体会从零搭建AI工程能力这件事真正值钱的地方不在于你最终搭出来什么平台而在于搭建过程中建立的“直觉”。什么是直觉就是当效果不对的时候你脑子里能浮现出可能是哪几个环节出了问题当你看到一份评估报告时你一眼能看出哪些指标高得可疑哪些case背后藏着数据缺陷。这种直觉没有任何捷径只能从亲手处理脏数据、亲手排查延迟问题、亲手对比评估结果中攒出来。如果你正在做这件事不用着急把每一步的“为什么”都想明白后面越走越快。