很久以前我在团队里带过一批实习生大家简历上都写着熟悉机器学习但真正新项目立项以后能独立把数据、训练、评估、部署整个链路走通的人一只手数得过来。不是他们理论不行而是大多数教程都在教某个模型怎么调没人教一个AI项目从零到上线需要经历什么。所以当我看到ai-engineering-from-scratch这个题目时反而觉得这是一个特别清醒的起点把工程拆开一步步亲手搭起来而不是站在别人的Demo上假装自己会了。这篇文章就是围绕从零开始做AI工程这个主题展开的。我会沿着一条真实可落地的学习路径讲清楚AI工程和AI研究的区别、一个完整项目的工作流、环境搭建的关键决策点再用一个文本分类项目串起数据处理、基线模型、预训练模型和部署上线的全过程最后整理我这些年踩过的高频问题和排查思路。无论你是刚接触AI的学生还是想转行做AI工程师的开发这篇文章都适合拿来当第一份从零开始的路线图——因为这里的每一个环节我都用实际项目验证过。1. 先想清楚AI工程到底在解决什么问题1.1 AI工程和AI研究是两条不同的路很多新人会把AI工程和AI研究混在一起上来就去啃论文、复现SOTA模型结果几个月过去模型还没跑通过一次。我的看法是AI研究的核心是探索模型能不能做得更好它的产出是论文、算法和实验结论而AI工程的核心是把模型变成稳定、可复现、可维护的系统它的产出是一个能服务用户的接口、一条能自动更新的数据链路、一套能监控模型效果的平台。打个比方研究是发明一道新菜谱工程是开一家能稳定出餐的餐厅。研究只需要在实验室环境里证明这道菜好吃工程却要考虑食材供应、厨房流程、出餐速度、口味一致性。回到AI项目上如果没人关心数据从哪来、模型怎么上线、效果怎么监控那再好的模型也落不了地。所以from scratch的第一步不是选模型而是建立工程视角把一个AI项目拆成数据、训练、评估、部署四个连续环节每一环都有独立的质量标准。有了这个框架你会发现后续的所有学习都是在往这四个环节里填充具体工具和技巧。1.2 一条完整AI项目的流水线长什么样一个典型的AI项目从需求到上线基本会经历下面这些环节业务与问题定义明确要解决什么问题分类、回归、检索还是生成成功的标准是什么数据采集与清洗确定数据来源处理缺失、重复、噪音做好标注和格式统一。特征工程/模型选择根据数据规模和任务类型判断用传统机器学习还是深度学习需要做哪些预处理。训练与调优划分训练/验证/测试集选择损失函数、优化器、学习率跑基线再逐步迭代。评估与验证用准确率、精确率、召回率、F1等指标衡量效果并检查是否存在数据泄漏或过拟合。部署与监控把模型封装成服务做好并发、延迟、日志和版本管理持续追踪线上表现。很多新人容易栽在业务定义和数据清洗上。他们觉得这两步不酷一上来就想训练模型结果用了几天时间调试模型却发现是标注数据本身错了。我在实际项目中见过太多这样的案例数据里标签错了一半再好的模型也救不回来。所以在这条流水线里数据环节的地位一点都不比模型环节低。1.3 为什么先跑通一个小项目比学完所有理论更重要如果让我给一个从零开始的人定优先级我的答案很明确用最小成本跑通一个完整项目比什么都重要。理论可以边做边补但完整闭环带来的全局感是看再多教程都换不来的。我当年入门的时候花了三周看机器学习理论决策树、SVM、神经网络全都背得滚瓜烂熟但第一次独立做项目还是懵数据要归一化吗类别不均衡怎么办模型保存成什么格式训练完怎么调用这些问题没有一个是课本直接讲的全是在项目里踩坑踩出来的。所以我的建议是选一个小数据集、做一个简单任务先跑通数据→模型→预测的最小闭环。哪怕模型效果只有60%准确率你也已经超越了只会调API的阶段。之后再去深入每个环节效率会高很多。这个理念也是整篇文章的组织思路先有骨架再填血肉。2. 动手第一步把开发环境配到一次到位2.1 Python版本与虚拟环境是第一个坑环境配置看起来是杂活但它决定了你后面能不能顺利复现别人的代码。Python的版本差异3.8、3.10、3.12会影响很多依赖库的兼容性尤其是PyTorch、TensorFlow这类重型库版本不对轻则警告重则直接装不上。我建议从第一天就养成两个习惯用版本管理工具管理Python版本比如pyenv或conda不要直接在系统里装一个全局Python就开干。每个项目创建独立的虚拟环境把依赖隔离在项目内部。这样项目A升级了某个库不会影响项目B的运行。虚拟环境的原理其实很简单每个环境是一个独立的目录里面放着属于这个环境的Python解释器和已安装的库。用venv创建环境只需要一条命令python -m venv .venv source .venv/bin/activate # 在Windows上是 .venv\Scripts\activate激活后你用pip install装的库都会被安装到.venv里不会污染全局。团队协作时把依赖导出到requirements.txt别人就能一键复现你的环境pip freeze requirements.txt2.2 显卡不是刚需但你要知道自己卡在哪很多新手一上来就问要不要买4090我的回答通常是先别买。因为学习阶段跑的都是小数据集、小模型CPU完全能应付。等你真正开始训练BERT级别以上的模型或者处理百万级数据的时候再考虑GPU也不迟。不过你要理解GPU和CPU的本质区别CPU擅长串行任务适合数据预处理、逻辑控制GPU有几千个计算核心适合并行的大规模矩阵运算而神经网络训练本质上就是海量矩阵乘法。所以如果你用PyTorch训练一个ResNetGPU比CPU快几十倍都不夸张。判断自己是否需要GPU看两件事数据量多大、模型多深。如果你的数据是几万条级别、模型是逻辑回归或浅层神经网络CPU足够如果数据是百万级、模型要跑深度学习那就需要GPU了。即便没有本地GPU也可以用云GPU服务或者免费的Colab跑深度学习实验。训练代码不用改只需要把数据传到云端、在笔记本里指定GPU运行时即可。这一点对从零开始的人来说非常友好你不需要一开始就花大价钱买硬件先把流程学会再决定要不要添设备。2.3 一张表看清AI工程的常用工具栈我把自己做AI工程最常用的工具按环节整理成了一张表方便你对照着准备环节常用工具作用开发语言PythonAI生态最丰富的语言没有之一数据处理Pandas、NumPy表格数据清洗、统计、变换可视化Matplotlib、Seaborn数据分布、训练曲线、混淆矩阵机器学习scikit-learn传统算法、特征工程、评估指标深度学习PyTorch、TensorFlow神经网络模型的训练与推理预训练模型Hugging Face Transformers加载BERT、GPT等预训练模型实验管理MLflow、WB记录参数、指标、模型产物模型部署FastAPI、Flask、ONNX把模型封装成HTTP服务对于从零开始的人我建议优先掌握Pandas、scikit-learn、PyTorch、FastAPI这四样。它们分别对应数据、传统模型、深度模型、部署四个核心场景学完就能覆盖绝大多数项目的需要。其他工具在遇到具体问题时再学效率更高。3. 完整实操从零搭建一个文本分类项目3.1 数据先行加载数据、查看分布、切分数据集理论讲得再多不如亲手做一遍。下面我用一个文本分类任务来演示完整流程。为了让你能快速复现我选用的是一个公开可得的新闻分类数据集并聚焦在数据准备、模型训练和部署三个关键环节。第一步永远是看数据长什么样。不要急着训练先加载数据查看类别分布、文本长度、是否有缺失值import pandas as pd df pd.read_csv(news.csv) print(df.head()) print(df[category].value_counts()) print(缺失值数量, df.isnull().sum().sum())这一步的目的有三个确认数据能正常读取、了解类别是否均衡、判断是否需要处理缺失值。我在真实项目中见过有人跳过这一步直接训练结果发现某个类别只有几十条样本训练出来的模型对这个类别完全失效。确认数据质量后用train_test_split划分数据集。这里有一个容易忽略的细节如果数据带有时间属性比如新闻按日期排列应该按时间切分而不是随机切分否则会引入数据泄漏——模型在训练时见过未来的信息线上表现会打折扣。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( df[text], df[category], test_size0.2, random_state42, stratifydf[category] )注意这里用了stratify参数做分层采样保证训练集和测试集里各个类别的比例一致。对于类别不均衡的数据这一步非常重要。3.2 基线模型用TF-IDF加逻辑回归跑通第一个版本我强烈建议每个项目的第一步都先做一个笨模型基线而不是直接上深度学习。基线的意义在于它给你一个最低效果参考后面所有复杂模型的效果都要跟它比同时它跑起来快几十秒就能让你看到完整流程。文本分类最简单的基线是TF-IDF加逻辑回归。TF-IDF词频-逆文档频率把每篇文本转成一个向量表达每个词在文档中的重要性逻辑回归在这个向量上做分类。代码很简单from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline model make_pipeline( TfidfVectorizer(max_features5000, ngram_range(1, 2)), LogisticRegression(max_iter1000) ) model.fit(X_train, y_train) print(测试集准确率, model.score(X_test, y_test))这里有一个参数值得说明max_features5000表示只保留出现频率最高的5000个特征。为什么不是全部词汇因为中文文本的词表动辄上十万但大部分词只出现一两次对分类没有帮助反而会稀释有效特征。限制特征数量既加快训练又能减少过拟合。ngram_range(1, 2)表示同时考虑单个词和相邻两个词的组合能捕捉一些简单的短语语义。跑完基线之后不要只看准确率还要看每类的精确率和召回率from sklearn.metrics import classification_report print(classification_report(y_test, model.predict(X_test)))这一步能告诉你模型具体在哪些类别上表现差。如果是类别不均衡导致的后面可以考虑做类别加权或数据增强。3.3 进阶用预训练模型替代手工特征基线跑通之后接下来可以引入预训练模型。很多人以为from scratch意味着每个模型都要从头训练这其实是一个误解。在现代AI工程里绝大多数场景不会从头训练一个大模型——那样需要海量数据和算力得不偿失。更常见的做法是加载别人已经预训练好的模型在自己的数据上做微调。用Hugging Face的Transformers库加载一个文本分类模型只需要几行代码。以中文场景为例from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments model_name bert-base-chinese tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained( model_name, num_labelslen(df[category].unique()) )这里的关键点在于预训练和微调的关系。预训练模型在海量通用文本上学会了语言的语法和语义相当于一个读了万卷书的通才微调则是在你的具体任务数据上再学一遍让它变成这个领域的专家。相比从头训练微调需要的样本量少、训练时间短、效果还好这是工程上的最优解。训练数据需要先经过Tokenizer编码成模型能理解的格式def tokenize_function(texts): return tokenizer(list(texts), paddingTrue, truncationTrue, max_length128) train_encodings tokenize_function(X_train) test_encodings tokenize_function(X_test)max_length128表示每条文本最多截取128个token。这个值不是越大越好太长会拖慢训练速度太短会丢失信息。我一般会看文本长度的分布选择能覆盖90%样本的长度即可。随后用Trainer训练它会自动处理batch、梯度更新、日志记录等繁琐环节training_args TrainingArguments( output_dir./results, num_train_epochs3, per_device_train_batch_size16, evaluation_strategyepoch, save_strategyepoch, ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_encodings, eval_datasettest_encodings, ) trainer.train()这里per_device_train_batch_size16表示每批16条样本。如果显存不够可以调小如果训练很慢可以调大。num_train_epochs3是常见的初始值太多会让模型过拟合。3.4 部署把模型变成一个可调用的服务模型训练完只完成了一半工作另一半是让其他人或系统能用上它。最简单的部署方式是用FastAPI封装一个HTTP接口让前端、后端或测试脚本通过POST请求发送文本返回分类结果。先保存训练好的模型model.save_pretrained(./news_classifier) tokenizer.save_pretrained(./news_classifier)然后写一个FastAPI服务from fastapi import FastAPI from pydantic import BaseModel from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch app FastAPI() model AutoModelForSequenceClassification.from_pretrained(./news_classifier) tokenizer AutoTokenizer.from_pretrained(./news_classifier) class Item(BaseModel): text: str app.post(/predict) def predict(item: Item): inputs tokenizer(item.text, return_tensorspt, truncationTrue, max_length128) with torch.no_grad(): outputs model(**inputs) pred_id torch.argmax(outputs.logits, dim-1).item() return {prediction: pred_id}这里有一个工程要点模型在服务启动时加载一次之后每个请求都复用同一个模型实例而不是每次请求都重新加载。加载一个大模型可能要几秒甚至几十秒如果每个请求都加载一次服务根本没法用。启动服务uvicorn app:app --host 0.0.0.0 --port 8000用curl测试curl -X POST http://localhost:8000/predict \ -H Content-Type: application/json \ -d {text: 这是一条测试新闻}到这里一个完整的AI工程闭环就成立了从数据到模型到服务每个环节都亲手跑了一遍。这时候你对from scratch的理解会比看十篇教程都深。4. 训练过程的真实战场高频问题与排查技巧4.1 数据问题90%的模型效果差都是数据惹的祸我先说一个可能让新手意外的事实在工业界模型效果不达预期时我第一个怀疑的不是模型结构而是数据。数据里的问题常见的有三类第一类是标签噪声。标注人员标准不一致或者标注工具出错导致同类文本被标成不同类别。我遇到过有项目里科技和体育两个标签混了一批新闻模型怎么调都分不清最后发现是标注时把体育科技类的文章随手归错了类。排查方法很简单把训练集中模型预测错且置信度高的样本打印出来人眼扫一遍往往能发现标签问题。第二类是类别不均衡。有的类别样本上千条有的只有几十条。模型为了降低整体损失会倾向于把样本都预测为大类小类完全被忽略。解决办法有三种用class_weight给少数类更大权重、对少数类做过采样/对多数类做欠采样、或者用F1等均衡指标代替准确率评估。第三类是文本清洗不彻底。HTML标签、多余空格、特殊符号、全角半角混用都会干扰模型。比如爬虫抓下来的文本经常带\u3000全角空格看起来没区别但在分词阶段会变成两个不同的token。我的习惯是在数据准备阶段做一个统一的清洗函数把所有文本过一遍去HTML、去不可见字符、统一大小写、统一全角半角。4.2 训练问题loss不下降、显存溢出、训练崩溃怎么办训练过程中最常见的三个问题我一个个说。loss不下降。先检查学习率是不是太大或太小。学习率太大会导致loss震荡甚至发散太小则收敛极慢。我建议用学习率预热加衰减的策略前几百步用一个较小学习率让模型稳定起步之后逐步增大到设定值再按余弦曲线衰减。很多框架都内置了这个功能比如Hugging Face的Trainer里有get_linear_schedule_with_warmup。再检查数据是不是没做shuffle。如果不打乱数据顺序模型会集中在连续几个batch里见到同一类样本梯度方向被带偏loss会在一个区间反复横跳。把DataLoader的shuffleTrue打开很多时候问题就消失了。显存溢出。PyTorch会报CUDA out of memory。最直接的解决办法是把batch_size调小比如从16降到8或4。如果batch太小效果不稳定可以开启梯度累积每跑几个小batch才做一次梯度更新等效于大batch的效果accumulation_steps 4 optimizer.zero_grad() for i, batch in enumerate(dataloader): loss model(batch) loss loss / accumulation_steps # 归一化 loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()另外可以用混合精度训练AMP。FP16比FP32省一半显存在NVIDIA GPU上速度还更快。PyTorch里的实现非常简洁from torch.cuda.amp import autocast, GradScaler scaler GradScaler() with autocast(): loss model(batch) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()训练崩溃比如loss变成NaN。这通常是因为学习率太大导致的梯度爆炸调小学习率一般能解决。如果是文本任务还可能是因为embedding层出现了极端值给输入做一下标准化或者增加梯度裁剪clip_grad_norm_会有帮助。4.3 评估陷阱别让准确率骗了你我见过太多团队在演示的时候报喜我们的模型准确率95%结果上线后发现完全没法用。为什么因为准确率在类别不均衡时是一个极具欺骗性的指标。举个例子一个二分类任务里90%是负样本你什么都不做、全部预测为负类准确率就有90%。但这个模型没有任何实用价值。所以评估分类模型时一定要看混淆矩阵和精确率、召回率、F1值精确率预测为某类的样本里有多少是真的。召回率某类的真实样本里有多少被找出来了。F1值精确率和召回率的调和平均兼顾两者。不同业务场景对精确率和召回率的偏好不同。垃圾邮件拦截更看重精确率——误杀正常邮件比漏拦垃圾邮件代价更高癌症筛查则更看重召回率——宁可多检查一些人也不放过一个真病人。我在评估时还有两个习惯一是把预测错误的样本打印出来逐条看分析错误模式比只看数字更有价值二是用置信度阈值调整来控制精确率和召回率的平衡大多数模型都提供概率输出可以按业务需求选择阈值。5. 从跑通项目到工程化的几个关键习惯5.1 实验可复现固定随机种子和记录配置代码能跑通只是第一步别人甚至三个月后的你能否重新跑出同样的结果才是工程化的分水岭。机器学习里充满了随机性数据洗牌、权重初始化、dropout每一步都可能导致结果波动。如果不做控制你可能昨天跑出85%准确率今天跑出82%却说不清差异来自代码改动还是随机波动。最简单的做法是固定随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)但这只是基础。更工程化的做法是用配置文件把每次实验的所有参数记录下来学习率、batch size、epoch数、数据版本、模型结构、随机种子。用yaml或json保存一份配置训练时输出到日志目录。这样你随时能回答一个问题这个效果好的模型是用什么配置跑出来的我见过很多团队用实验记录.xlsx来登记实验但Excel在并发记录和数据量大时会很痛苦。用MLflow这样的实验管理工具可以自动记录参数、指标和模型文件后续对比实验也会很方便。5.2 项目结构训练代码和推理代码分离初学者的项目里最常见的状态是训练和推理都堆在一个Jupyter Notebook里前面是数据处理中间是模型训练最后是几个预测示例。自己调试的时候还行但要部署上线、要给别人维护就是一场灾难。我建议从项目一开始就建立清晰的结构project/ ├── configs/ # 配置文件 ├── data/ # 数据文件 ├── src/ │ ├── data.py # 数据加载与预处理 │ ├── train.py # 训练脚本 │ ├── infer.py # 推理脚本 │ └── utils.py # 常用工具函数 ├── models/ # 保存训练好的模型 └── requirements.txt训练代码和推理代码分离是因为它们的生命周期完全不同。训练代码改得频繁但只在训练时运行推理代码相对稳定但需要长期在线服务。把它们混在一起任何一处改动都可能影响线上服务稳定。infer.py里只做一件事加载模型、接收输入、返回预测。它不依赖训练时的数据清洗细节只需要把输入转换成模型能理解的格式。做到这一点部署和维护的成本会直线下降。5.3 工程思维能用规则不用模型能用小模型不用大模型最后分享一个可能不太AI味的经验工程的核心是解决问题不是堆模型。很多场景用不上深度学习甚至用不上机器学习。我接手过一个文本分类任务需求是判断用户反馈是否属于退款问题。最初的技术方案是训练一个BERT分类模型一轮调优下来效果也还可以。但后来我们分析了一下数据发现这类反馈有极强的关键词特征一条包含退款退钱申请退款的规则就能覆盖八成以上的场景准确率接近99%延迟几乎为零维护成本远远低于训练一个BERT模型。这不是说模型没用而是说在工程上简单方案能解决就不上复杂方案。模型是工具不是目的。当你的简单规则覆盖不了、或者场景需要泛化的时候再上机器学习当小模型效果不够、大数据量需要更强表示的时候再上大模型。每一步决策都应该有成本收益分析。这个思维是从零开始做AI工程最容易忽略、也最值钱的一课。学会了它你已经不只是会用AI工具的人而是一个真正能对系统负责的工程师。我在实际带项目时还发现一个规律凡是能坚持从简单到复杂、一步一步亲手做完完整项目的同学后面成长速度都非常快。相反那些一开始就盯着最前沿模型、急着复现最复杂算法的同学反而容易在某个环节卡住后彻底放弃。所以如果你正走在ai-engineering-from-scratch的路上我的建议很简单先选一个几万条数据的小任务把数据清洗、基线模型、评估、部署整条链路跑通再回头补理论。这一轮下来你对AI工程的理解会超过大多数停留在教程里的人。
阅读完成 · 觉得有帮助?