我刚开始带团队做AI落地项目的时候,最容易遇到的尴尬局面是:算法同事交过来的模型在离线指标上漂亮得不行,一上真实流量就露馅。后来我逐渐想明白一件事——AI工程从来不是“训练出一个高精度模型”那么简单,而是从数据、模型、训练、部署到持续维护的一整条流水线。今天想借“ai-engineering-from-scratch”这个话题,把我从零开始搭建AI工程能力体系的经验、踩过的坑、以及现在团队新人入职用的训练路径,完整梳理一遍。不管你是在校学生、刚转行AI的工程师,还是已经在做算法想补工程短板,这篇文章都值得收藏慢慢看。
AI工程的核心不是某个模型有多先进,而是系统能否稳定、可复现、可持续地迭代。太多人从零开始学AI,一猛子扎进Transformer源码和论文复现里,结果发现自己连环境都装不好、数据预处理要写半天、模型训练没监控、上线靠手工重启。所以这篇文章我会按实操顺序,从环境搭建、最小可运行系统、工程化进阶、常见问题排查一路写下来,尽量把“为什么这么做”也讲清楚。内容偏工程实践,不堆公式,适合动手党。
1. 先想清楚再动手:AI工程到底在解决什么问题
1.1 AI工程与算法研究、传统软件工程的区别
很多人把“AI工程”等同于“搞算法”,这是第一个大误区。算法研究的核心是探索新模型、新方法,在公开数据集上刷出更好结果;而AI工程的核心是把模型变成生产环境里稳定可用的服务,并持续保持其效果。两者关注的问题完全不同。
举个例子:算法研究关心“这个分类器在ImageNet上准确率提升0.5%”,AI工程关心“这个分类器在用户真实上传的模糊图片上,准确率会不会掉到不可接受,掉了我怎么及时发现,发现了怎么快速回滚”。
传统软件工程则强调模块化、测试、CI/CD、可维护性。AI项目里软件工程那套依然适用,但多出了几个独特难点:数据无法像代码一样简单diff,模型训练不是确定性过程,模型上线后面对的数据分布会变化。所以AI工程是软件工程 + 数据工程 + 机器学习系统设计的融合体。
1.2 从零开始的正确心态:不是堆模型,而是交付可用系统
如果你刚接触AI工程,请先放下“我要复现Stable Diffusion”“我要微调Llama 3”的执念。从零开始的意思,是先把地基打牢。地基是什么?是用最朴素的工具,把一个真实问题完整跑通——从拿到数据,到训练一个简单模型,再到用接口把结果暴露出去。这个过程中你会接触数据清洗、特征工程、模型训练、评估、部署、API设计这些环节,每一个都是AI工程的骨骼。
我见过太多新人一上来就抢着上大模型,结果卡在显存不足、依赖冲突、CUDA版本不对这些环境问题上,一个周末全耗进去,情绪直接崩。这完全没必要。真正高效的路子是:先用一个小型数据集、一个简单的逻辑回归或小型CNN,甚至先用规则跑通全流程。哪怕效果差点,但你对“系统长什么样”有了体感,后面加模型复杂度、加数据量、加分布式,都是水到渠成的事。
1.3 能力地图:数据、模型、训练、部署、监控、迭代
我把AI工程从零开始需要掌握的能力拆成六块,你在心里有个地图,学起来就不会乱:
- 数据工程:采集、清洗、标注、版本管理、特征工程。数据质量决定模型上限,哪怕模型再烂,数据干净往往会带来惊喜。
- 模型开发:掌握常见任务(分类、回归、目标检测、文本生成)的基线模型,理解损失函数、优化器、正则化。
- 训练工程:配置化训练脚本、日志记录、checkpoint保存、分布式训练、混合精度。
- 评估与实验管理:离线指标设计、badcase分析、实验记录、可复现性。
- 部署与服务化:模型格式转换、推理优化、API封装、容器化、上线策略。
- 监控与迭代:线上指标监控、数据分布漂移检测、模型回滚、持续训练。
这六块不是线性关系,实际项目里会来回串。但每一块都需要你亲手做一遍,踩过坑才算会。
2. 搭建第一套AI工程环境:工具链选型与安装
2.1 Python环境管理:从“装个包”到“环境不崩”
AI项目跑不起来,十有八九是Python环境搞崩了。我早期也经历过:为了装TensorFlow,把系统的Python搞坏了,然后又为了修复Python,把另一个项目的依赖搞乱了。后来固定了一套组合,再也没出过这种问题。
我的推荐方案:pyenv 管 Python 版本,poetry 管项目依赖。如果团队统一用Anaconda,也可以,但conda在大项目里解析依赖特别慢,而且在CI里不太友好。pyenv让你能随便切换Python版本,poetry用pyproject.toml锁定依赖,并且能生成poetry.lock,保证每个人安装的版本一模一样。如果你在国内网络环境,可以把pypi源换到清华镜像,速度提升明显。
实操步骤很简单:
# 安装 pyenv(macOS/Linux) curl https://pyenv.run | bash # 安装 Python 3.11 pyenv install 3.11.8 pyenv global 3.11.8 # 用 poetry 管理依赖 pip install poetry poetry new my-ai-project cd my-ai-project poetry add numpy pandas scikit-learn torch这一步的“为什么”在于:AI项目依赖极其敏感,比如PyTorch和CUDA版本不匹配、numpy版本和opencv冲突,都是家常便饭。用锁文件隔离项目环境,等于给你的项目盖了一层保护罩。
2.2 深度学习框架选型:PyTorch为什么是默认选择
现在新项目我基本无脑选PyTorch,不是因为它性能最好,而是它的生态和调试体验最符合工程迭代需求。
PyTorch的优点很实在:
- 动态图机制,打印张量、断点调试非常顺手,对初学者极其友好;
- Hugging Face Transformers、Diffusers等主流模型库全基于它,社区资料多,出了问题一搜就有答案;
- TorchScript / ONNX / TensorRT 的推理链比较成熟,部署路径清晰。
TensorFlow适合什么时候用?如果你维护的是老项目,或者需要TensorFlow Serving这样的工业化部署组件,那继续用没问题。JAX在搞科研、追求极致性能的团队里口碑很好,但在工程生产环境里生态还不够大众。所以结论是:从零开始学AI工程,先把PyTorch吃透,之后需要再横向扩展。
安装PyTorch有个常见的坑:直接pip install torch会装CPU版,如果你有NVIDIA GPU,白装了。正确做法是去PyTorch官网的安装向导页面,选你的操作系统、包管理器、CUDA版本,复制对应命令。比如CUDA 12.1的命令是:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121装完之后验证一下:
import torch print(torch.__version__) print(torch.cuda.is_available()) # 必须输出 True2.3 训练基础设施:本地GPU、云GPU还是Colab
从零开始做实验,硬件怎么选?我的建议按阶段来:
- 入门阶段:Google Colab免费版足够,或者用Kaggle Notebook,有免费GPU额度,跑小模型没问题。缺点是会话会断,不适合长训练。好处是零成本,能快速上手。
- 中期阶段:租云GPU按需用,国内外都有不少厂商,价格按小时计费。这个阶段适合跑你的第一个完整训练流程,比如微调一个小型BERT模型。记得及时关机,避免费用失控。
- 进阶阶段:如果自己有台带GPU的电脑(比如RTX 3060以上),本地训练效率高很多。再往上,就需要考虑公司的GPU集群或云上的多卡资源了。
这里我要重点说一个认知:你不需要等到买了GPU才开始学。很多入门教程在Kaggle上都有免费GPU,足够跑完图像分类、文本分类这类基础项目。真正卡住你的永远不是硬件,而是不知道怎么组织代码、怎么分析实验。
2.4 从一个项目模板开始
我建议你建项目目录时,从一开始就按工程化方式组织,避免后面改来改去。这是一个我用了很久的最小模板:
my-ai-project/ ├── data/ # 原始数据和中间数据 │ ├── raw/ │ └── processed/ ├── configs/ # yaml配置文件 │ └── baseline.yaml ├── src/ # 源代码 │ ├── data/ # 数据加载和预处理 │ ├── models/ # 模型定义 │ ├── train.py # 训练入口 │ ├── evaluate.py # 评估入口 │ └── predict.py # 推理入口 ├── experiments/ # 实验输出 │ └── run_20250101/ ├── notebooks/ # 分析用notebook ├── pyproject.toml └── README.md这样做的好处是,实验输出和代码隔离,配置和代码分离,你换参数不用改代码,只要改yaml。这个习惯越早养成,后面越轻松。
3. 从零完成一个最小可用的AI系统(实操)
3.1 选一个能跑通全流程的问题
与其看十篇教程,不如自己完完整整做一个项目。我建议第一个项目选文本分类,比如情感分析,或者垃圾短信识别。原因有三:数据好获取、模型不用太大、评估指标直观。
我这次就用“垃圾短信二分类”当例子,数据集可以从公开渠道找,比如UCI的SMS Spam Collection。这个数据集不大,几千条短信,正好适合在CPU上跑基线模型,也能在GPU上秒级训练。
这里要强调一个观念:第一个项目的目的不是做出SOTA,而是打通全流程。哪怕你用TF-IDF + 逻辑回归达到90%准确率,也比用精调BERT但只能在别人写的脚本里运行强得多。因为前者让你理解了特征、模型、评估、部署的每个环节,后者只是调包侠。
3.2 数据获取与清洗:垃圾进垃圾出
数据清洗是AI工程里最脏最累但最值钱的环节。直接用一个真实数据集开始,你会碰到一堆现实问题:类别不均衡、文本里有HTML标签、大小写混乱、重复样本、标签噪声。
拿SMS数据集举例,原始数据大概是标签\t文本内容的格式。第一步先加载,看看类别的分布:
import pandas as pd df = pd.read_csv('data/raw/SMSSpamCollection', sep='\t', header=None, names=['label', 'text']) print(df['label'].value_counts())你大概率会看到ham远多于spam,这是典型的类别不均衡。这时候不要急着上复杂模型,先做几个常规操作:
- 清洗文本:去掉HTML实体、特殊符号、多余空白;
- 统一大小写(如果是英文任务);
- 去重(同一个短信可能出现在垃圾样本里多次);
- 划分训练集、验证集、测试集,注意按标签分层采样,保证类别比例一致。
分层采样代码很简单:
from sklearn.model_selection import train_test_split train_df, temp_df = train_test_split(df, test_size=0.3, stratify=df['label'], random_state=42) valid_df, test_df = train_test_split(temp_df, test_size=0.5, stratify=temp_df['label'], random_state=42)这一步的“为什么”很关键:如果直接随机划分,因为垃圾短信数量少,可能全落进训练集,验证集里一条垃圾都没有,你的模型评估就会虚高,上线就翻车。分层采样保证验证集、测试集和训练集的类别比例接近,评估结果才可信。
清洗完数据,记得把处理后的结果存成独立的文件,别覆盖原始数据。数据是资产,原始数据不可再生。
3.3 基线模型:先跑规则,再跑模型
我强烈建议你从规则基线开始。什么意思?比如垃圾分类短信,可以先写一条规则:如果短信里有“免费”“领取”“点击链接”等词,就判为垃圾。然后把规则在验证集上的效果跑出来。通常规则基线准确率不会太高,但没关系,它给了你一个参照系。后面无论用什么模型,如果连规则都不如,说明模型或特征没做好。
接下来上一个简单但经典的模型:TF-IDF + 逻辑回归。这个组合在文本分类上依然很能打,是我遇到工业级简单任务时的首选。sklearn封装得很好:
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline model = make_pipeline( TfidfVectorizer(max_features=10000, ngram_range=(1, 2)), LogisticRegression(max_iter=1000) ) model.fit(train_df['text'], train_df['label'])用验证集评估一下:
from sklearn.metrics import classification_report y_pred = model.predict(valid_df['text']) print(classification_report(valid_df['label'], y_pred))一般这个组合在短信数据集上能到95%以上准确率。你可能会惊讶:这么简单也能这么好?这恰恰说明了很多时候数据质量和问题定义比模型复杂度更重要。
3.4 训练脚本怎么写:配置化、日志、checkpoint
当你准备从sklearn切换到PyTorch,开始写自己的训练循环时,千万别把所有东西都塞进一个临时notebook里。notebook适合探索,不适合训练。写一个结构清晰的训练脚本,包含三大件:配置加载、训练循环、日志与checkpoint。
配置用yaml存,比如我在configs/baseline.yaml里写:
data: train_path: data/processed/train.csv valid_path: data/processed/valid.csv model: name: text_cnn embedding_dim: 100 num_filters: 128 filter_sizes: [3, 4, 5] train: batch_size: 64 lr: 0.001 epochs: 10 device: cuda save_dir: experiments/run_20250101/训练脚本的核心逻辑大概长这样:
def train(config): train_loader = load_data(config['data']['train_path'], config['train']['batch_size']) valid_loader = load_data(config['data']['valid_path'], config['train']['batch_size']) model = build_model(config['model']) optimizer = torch.optim.Adam(model.parameters(), lr=config['train']['lr']) criterion = nn.CrossEntropyLoss() for epoch in range(config['train']['epochs']): model.train() total_loss = 0 for batch in train_loader: optimizer.zero_grad() logits = model(batch['input_ids']) loss = criterion(logits, batch['label']) loss.backward() optimizer.step() total_loss += loss.item() # 每个epoch结束做验证 valid_acc = evaluate(model, valid_loader, config) print(f"Epoch {epoch} loss={total_loss:.4f} valid_acc={valid_acc:.4f}") # 保存checkpoint torch.save(model.state_dict(), f"{config['train']['save_dir']}/epoch_{epoch}.pt")这里有几个我特别想强调的坑:
- 一定要设随机种子。在main函数开始处加
set_seed(42),包括Python的random、numpy、torch的种子,必要时设置torch.backends.cudnn.deterministic = True。否则同样的代码每次跑的结果都不一样,没法排查问题。 - 每个epoch都做验证,不要只在最后评估一次。你可以通过验证曲线提前发现过拟合。
- 保存checkpoint时除了模型参数,最好保存optimizer状态、epoch数、随机种子。这样你可以从断点恢复训练,不会因为意外中断白跑。
3.5 评估与badcase分析:别只看准确率
准确率高不代表模型好用。二分类里如果正负样本比例是1:9,那么全预测负样本能拿到90%准确率,毫无意义。所以一定要看精确率、召回率、F1,以及混淆矩阵。
在短信分类这个场景里,漏判垃圾短信(假阴性)的代价比误判正常短信(假阳性)更高吗?其实不一定,取决于产品。如果是反垃圾系统,召回率太低了等于没拦截;如果是支付风控,误杀正常用户(假阳性)损失更大。所以你要和业务方对齐,到底优化哪个指标。
我的习惯是,在验证集上跑完模型,把所有预测错的样本(badcase)打印出来,一条条看。你会发现很有意思的事情:有些正常短信因为包含“代开发票”被判成垃圾,有些垃圾短信伪装成正常聊天骗过了模型。分析badcase是改进数据、特征的灵感来源。比如看到模型把“加微信领红包”误判为正常,说明训练样本里缺少这类变体,那就去补充数据,或者增加相应的规则。
3.6 部署:用FastAPI把模型包成一个接口
训练好模型只是开始,你得让别人能用。最轻量的方式是FastAPI,一步到位写成一个HTTP服务。
在src/predict.py里写一个简单的加载函数:
class SMSClassifier: def __init__(self, model_path, vectorizer_path): self.model = joblib.load(model_path) self.vectorizer = joblib.load(vectorizer_path) def predict(self, text): vec = self.vectorizer.transform([text]) proba = self.model.predict_proba(vec)[0][1] return {"prob": float(proba), "label": "spam" if proba > 0.5 else "ham"}然后写FastAPI入口:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() classifier = SMSClassifier("models/lr.joblib", "models/tfidf.joblib") class Item(BaseModel): text: str @app.post("/predict") def predict(item: Item): result = classifier.predict(item.text) return result启动:
uvicorn src.api:app --host 0.0.0.0 --port 8000用curl测一下:
curl -X POST http://localhost:8000/predict -H "Content-Type: application/json" -d '{"text": "恭喜你中奖了,点击链接领取奖品"}'到这里,你已经完成了一个从数据到接口的最小可用AI系统。别小看这件事,很多工作了三四年的人,离开了公司现成的平台,自己都搭不起来这套流程。
4. 进阶工程化:让模型真正稳定运行
4.1 数据版本管理与可复现性
当你的项目开始有多个数据集、多轮清洗逻辑,你会面临“这个模型到底是用哪份数据训练出来的”这种问题。代码可以用Git管,数据不行——数据文件动辄几个G,没法直接放Git。
我的建议是按轻量级方案来:
- 数据文件存到远程存储(云对象存储或NAS),并在项目里维护一个
data/manifest记录每个文件的哈希值、生成日期、生成脚本。 - 简单做法,用脚本生成数据时顺便输出
md5到记录文件; - 进阶做法,用DVC(Data Version Control)管理数据。DVC和Git配合,能像Git管理代码一样管理数据版本,回退、切换实验都很方便。
不管用哪种方式,核心原则是:任何实验产出,都必须能追溯它用的是什么代码、什么数据、什么配置。这一条做不到,后续所有实验对比都是空中楼阁。
4.2 实验跟踪:别再用记事本记实验结果了
早期的我,每个实验结果写在Excel里,模型文件命名的后缀是v2_final_真final,这种惨痛教训让我彻底改用了实验跟踪工具。
如果你不想搭重型平台,MLflow是最容易上手的,开源免费,本地可以用,也能部署到服务器。用它记录每个实验的参数、指标、模型文件,以后想看哪次实验跑得最好,直接搜。
简单用法:
import mlflow with mlflow.start_run(): mlflow.log_params(config['train']) mlflow.log_metric("accuracy", valid_acc) mlflow.log_artifact("results/confusion_matrix.png") mlflow.pytorch.log_model(model, "model")之后在MLflow的UI里,你就能看到每次实验的参数对比、指标曲线。即使只有你一个人做项目,这个工具也能让你多活三年,因为你不必再靠回忆判断“上次那个结果是哪个参数跑出来的”。
4.3 分布式训练入门:首先想清楚是否需要
很多人大一大二就开始学分布式训练,我觉得这有点早。单卡没跑明白之前,别碰分布式。分布式训练引入的问题(数据并行、梯度同步、进程通信、超参数调整)会完全掩盖你本来想解决的问题。
如果有一天模型大了、数据多了,单卡训练一个epoch要三小时,那再考虑分布式。第一个入门方案是PyTorch DDP(DistributedDataParallel),它比老的DataParallel更稳定高效。
核心思路是:多张卡各自持有一份模型副本,每个卡处理一个子batch,然后梯度汇总更新。代码上改动不算大,但启动方式变了。比如:
torchrun --nproc_per_node=4 train.py然后脚本里需要初始化进程组:
import torch.distributed as dist dist.init_process_group(backend="nccl") model = DDP(model, device_ids=[local_rank])同时,日志只让rank 0进程打印,否则会刷屏。数据集抽样也要考虑DDP的sampler,让每个进程拿到的数据互不重复。新手第一次跑DDP经常遇到卡死、通信超时,我的建议是先在一台多卡机器上跑通,再去碰多机。
4.4 模型优化:量化、裁剪、蒸馏
模型训练完之后,部署是个新问题。尤其在CPU上推理、或在边缘设备上跑,原始模型往往太大了。比如一个BERT-base有1.1亿参数,FP32下占400多MB,接口延迟高得没法用。常见三板斧:
- 量化:把权重从FP32降到INT8,模型体积缩小4倍,推理速度提升明显,精度损失一般可接受。PyTorch里推荐用
torch.quantization或ONNX Runtime的量化工具。 - 裁剪:把不重要的参数或注意力头减掉,需要一点重训练技巧,但能大幅减少计算量。
- 知识蒸馏:训练一个大模型(teacher)指导一个小模型(student)。经典例子是蒸馏BERT,用大BERT教一个小BERT,能保持大部分效果但快很多。
我的建议是:先量化,再考虑其他。因为量化改动最小,准确率通常掉得不多。如果量化后模型还是扛不住线上延迟,再想蒸馏或者换更小的模型结构。
部署时还要考虑模型格式。PyTorch的.pt文件不能直接用于所有场景,常见的转换路径是:
PyTorch -> ONNX -> TensorRT / ONNX RuntimeONNX是一个开放格式,很多推理引擎都支持。你可以把模型转成ONNX后,用ONNX Runtime跑,推理速度通常比PyTorch原始加载快。转换代码也很简单:
import torch.onnx dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)4.5 监控与告警:模型上线只是开始
模型上线根本不是终点,而是运维的起点。很多项目死在那句“模型上线后效果还行,我就没管了”,结果一个月后用户投诉暴涨,一查才发现数据分布早就变了。
你需要关注三类监控:
- 业务指标:比如垃圾短信拦截率、用户投诉率,这是最直接的效果反馈。
- 模型指标:线上预测为正样本的比例、平均置信度。如果这些数值和离线评估时差异很大,说明有数据漂移。
- 输入特征监控:比如线上输入文本的长度、用词分布。如果新词比例越来越高,说明模型需要重训了。
检测数据漂移的轻量办法:记录线上输入特征的历史分布,定期计算和训练集分布的差异,用KL散度或PSI(Population Stability Index)做指标。当超过阈值就告警。
告警工具我推荐用Prometheus + Alertmanager或者商用平台的监控模块,也可以先简单用脚本定时跑,把异常结果发到企业微信/钉钉。重点是先把监控跑起来,别追求完美基础设施。
当监控发现效果衰减时,处理路径要提前定义好:是回滚到上一个模型,还是用最新标注数据重训?这都需要你和业务方提前约定流程,而不是等事故发生了再开会讨论。
5. 常见问题与排查技巧实录
5.1 环境依赖冲突,装包装到心态爆炸
这个问题几乎人人都会遇到,症状是:装A包把B包升级了,B包又不兼容C包,然后整个环境处于崩溃边缘。
我的解法:
- 所有项目都用poetry或conda独立环境,别用全局Python;
- 遇到装包冲突,先看错误信息里提到的包名,用
poetry remove删掉冲突包,再重新poetry add指定版本; - 如果poetry解析依赖超时,可以先
poetry config pypi-trusted-host或者换镜像源; - 实在不行,直接删掉虚拟环境重建。反正有
pyproject.toml和poetry.lock,重建成本很低。
记住一个原则:不要为了装个包去改系统Python。这我之前栽过很多次。
5.2 训练loss不下降,排查思路是什么
训练不收敛是最让人烦躁的问题。我总结了一套快速定位套路:
- 先减到一个batch,看看loss能不能降。如果连一个样本都学不了,说明模型实现有bug或者数据标签不对;
- 检查数据预处理:输入有没有归一化,标签有没有对齐,有没有大量空数据;
- 检查优化器:学习率是不是太大或太小。太大loss会震荡,太小收敛太慢,通常从1e-3开始;
- 检查模型初始化:换个更稳定的初始化,或者用预训练权重;
- 观察loss曲线:如果是NaN,几乎可以肯定是数值问题,降低学习率,或者检查输入有没有NaN。
一个小技巧:先让模型记忆训练集。把batch size设为全量数据集,跑几百步,如果loss能降到接近0,说明模型有足够容量学习,后面再逐步调正则化和数据增强。如果连训练集都学不会,别指望泛化。
5.3 GPU显存不足怎么办
“CUDA out of memory”的频次,可能比很多人写的代码行数还高。解决办法从轻到重:
减小batch size,这是最直接有效的;
用梯度累积,模拟更大的batch size:
accumulation_steps = 4 loss = loss / accumulation_steps loss.backward() if (step + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()使用混合精度训练(amp),显存占用几乎减半:
scaler = torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): logits = model(batch) loss = criterion(logits, label) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()检查是否有张量被意外保留引用,比如在循环里把每个batch的logits存在列表里,导致显存不断增加。用
del手动释放大变量;终极办法:换更大的GPU卡,或者把模型分片。
5.4 模型上线后效果衰减,用户投诉增加
这是个让人头大但非常常见的问题。原因通常是线上数据和训练数据分布不一致,俗称“数据漂移”。
排查步骤:
- 先看监控面板:线上预测分布和训练集分布是否差很大;
- 抽样线上输入,人工判断是不是出现了新类型样本;
- 如果是,需要收集新的标注数据,重新训练模型;
- 如果短期内没有新数据,考虑做一个“兜底规则”,比如关键词黑名单,先把极端case拦住;
- 上线方式建议用“灰度发布”,先让新模型服务5%的流量,对比老模型效果,稳定后再放量。
这个过程中,可回滚是底线。你部署模型时,一定保留上一个版本的模型文件和配置,保证出问题能一分钟切回。
5.5 测试集过拟合,报告虚高怎么办
很多刚接触AI工程的同学有个坏习惯:反复用测试集调参,最后测试集指标越调越高,但上线就垮。这就是测试集过拟合。
正确姿势:
- 测试集只能用一次。调参用训练集和验证集,真正的评估在测试集上做一次,做完就再也不碰测试集;
- 如果测试集用完了,又改了特征,需要用“新的测试集”,那就从原始数据里重新划分一块,不要和之前重叠;
- 实在要反复评估,用K折交叉验证,每折留出不同的验证集,最后看平均效果。
还有一条:不要拿badcase直接往训练集里塞。我看到有人把验证集上所有错误样本都加进去重训,结果验证集指标飞速上涨,但测试集一测更差了。因为那些badcase本身可能带噪声,你这样干等于让模型去记住噪声。
最后再分享一点个人体会
从零开始搭建AI工程能力,本质上是在构建一套“理性、耐心、可控制”的工作流。我见过很多聪明人因为急着上高大上的模型,反而忽略了最基础的工程素养,最后项目一塌糊涂。我自己也在这条路上交过不少学费——第一次部署模型忘记加锁导致并发崩溃,第一次因为不设随机种子导致实验无法复现,第一次因为监控缺失导致一个垃圾短信模型上线两周才被用户投诉打醒。
这些坑,写下来都简单,但没踩过的人就是记不住。所以我特别建议你用一个小项目从头到尾走一遍,别跳步,别嫌任务简单。等你做完一个“无聊”的文本分类,或者更小的图像分类,你会发现自己对AI工程的理解比看了几十篇论文都扎实。后面再学大模型微调、分布式训练、模型推理优化,心态会完全不同——因为你知道每一块砖该往哪里放,而不是在一堆华丽名词里迷路。
如果你在照着这条路走的过程里遇到了具体问题,欢迎带着报错信息来讨论。这类问题通常比“哪种模型更好”更有价值,因为后者是别人的答案,前者才是你自己的功课。