ai-engineering-from-scratch这个标题第一次看到的人大概会有两种反应要么觉得又是一个标题党要么觉得从零开始意味着得先把数学和编程学个三五年再谈其他。实际上两边都不太对。我自己带过不少从其他方向转过来的工程师也照着类似的学习路线实操过好几轮慢慢发现所谓从零开始的AI工程核心不是堆知识而是搭出一条能跑通的路。这篇文章就把这条路掰开揉碎讲清楚给那些不知道从哪里下手的读者也给那些已经在跑训练脚本、但对整个工程链路心里没底的人。标题里的关键词是engineering而不是research这个区别很关键。学术研究可以只关心模型论文里的几个点但工程要面对的是数据脏乱差、环境装不上、模型上线后没人用这些破事。从零开始做AI工程本质上是把数据、模型、部署、维护这一整条流水线都走通每一步都不留死角。1. 先拆解项目从零开始学AI工程到底在学什么1.1 AI工程不等于训练模型看清全链路很多人以为学会PyTorch就是AI工程了这是最大的误解。训练模型在整个AI工程链路里可能只占20%的时间。一个真实可用的AI系统通常包含下面这些环节数据获取从业务系统、日志、外部数据源把数据捞出来。数据清洗处理缺失值、重复值、异常值统一字段格式。特征工程把原始字段变成模型能理解的输入包括编码、标准化、构造新特征。模型训练选择算法、调整超参数、迭代版本。模型评估在验证集和测试集上验证效果选指标。模型部署把训练好的模型封装成服务供外部调用。监控运维上线后持续观察预测效果和数据分布变化及时更新。这整条链路才是AI工程。我见过很多新手在模型训练上猛下功夫调参调到凌晨最后卡在模型保存下来怎么给同事用这一步。训练脚本跑通只是上半场下半场的部署和维护才是工程能力的真正体现。1.2 学习路径怎么规划三个阶段走通闭环从零开始最怕的是贪多嚼不烂。我给的建议是分三个阶段走每个阶段都有明确的产出目标。第一阶段是工具链和基本功。Python语法、Git版本管理、Linux常用命令、Docker基础这些东西是AI工程的脚手架。不用学得很深够用就行。比如Docker知道怎么写Dockerfile、怎么构建镜像、怎么跑容器就够起步了等你后面要部署线上服务时再深入也不迟。第二阶段是数据处理和基线建模。掌握Pandas和NumPy处理表格数据学会用Scikit-learn快速训练一个基线模型。这个阶段的目标不是做出多牛的模型而是把一个完整流程走通读数据、清洗、训练、评估、保存模型。第三阶段才是工程化进阶。引入PyTorch做深度学习、用FastAPI封装推理服务、用MLflow管理实验和模型版本、用Docker和编排工具做部署。这个阶段会碰到大量环境问题、性能问题但正是在这些坑里能力和经验才会真正长出来。三阶段不完全是线性的可以穿插但总体的原则是先跑通最简闭环再往深里挖。1.3 入门到进阶的能力矩阵我在带人时习惯把AI工程能力分成三档这里列个表大家可以对号入座能力档位核心内容典型产出入门必备Python、Pandas、Scikit-learn、基本机器学习概念、简单Web接口能独立完成一个数据分析和基线模型训练的小项目进阶提升PyTorch、深度学习、特征工程、模型调优、Docker部署能完成一个端到端的预测服务并上线运行长期加分分布式训练、K8s、MLOps平台、特征存储、大模型应用能设计和管理中型规模的AI平台架构心态上要接受一件事你的第一个模型大概率是个垃圾。这不是坏事baseline就是用来当靶子的。先跑起来再优化比一开始就想憋一个大招靠谱得多。2. 环境准备与最小工程骨架先把地基打牢2.1 环境准备与依赖管理别在第一步就劝退我见过太多项目死在环境配置上。新手刚装了Python又不知道从哪冒出来一套Anaconda然后pip和conda混着用最后包版本冲突到怀疑人生。这里我推荐一个稳定的起步组合用Miniconda管理Python版本和环境而不是直接用系统自带的Python。原因是conda可以隔离不同项目的依赖还能搞定一些非Python的底层库。在项目的根目录放一个requirements.txt把所有依赖和版本号固定下来。别小看这一步等三个月后你重新回来看这个项目会发现版本固定能救命。如果项目稍微复杂建议直接用poetry或者uv这类工具它们能生成lock文件保证每个人装出来的环境完全一致。一个最小可运行的requirements.txt长这样numpy1.26.4 pandas2.2.2 scikit-learn1.4.2 fastapi0.111.0 uvicorn0.30.1 joblib1.4.2注意我全部固定了版本号。有人喜欢装最新版但在我机器上能跑和在别人机器上也能跑完全是两回事。固定版本是工程化的第一课它牺牲了一点点灵活性换来的是可复现性。提示创建虚拟环境的核心命令是conda create -n ai-env python3.10和conda activate ai-env。装完依赖后用pip freeze requirements.txt可以把环境里所有安装的包都导出。2.2 训练第一个基线模型跑通比跑好重要很多人一上来就想着用什么高级模型其实最快的入门方式是用Scikit-learn训练一个最基础的模型。下面这段代码就是很多AI工程项目的Hello World用鸢尾花数据集训练一个随机森林分类器import pandas as pd from sklearn.datasets import load_iris from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score import joblib # 1. 加载数据 iris load_iris() df pd.DataFrame(iris.data, columnsiris.feature_names) df[target] iris.target # 2. 切分训练集和测试集 X df.drop(columns[target]) y df[target] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 3. 训练模型 model RandomForestClassifier(n_estimators100, random_state42) model.fit(X_train, y_train) # 4. 评估模型 y_pred model.predict(X_test) print(f测试集准确率: {accuracy_score(y_test, y_pred):.4f}) # 5. 保存模型 joblib.dump(model, models/iris_rf.joblib)这段代码的运行逻辑就是所有AI项目的缩影读取数据、切分数据集、训练、评估、保存。你要做的就是把load_iris换成你自己的数据来源把RandomForestClassifier换成适合你业务的模型整个流程就成立了。这里有一个关键点random_state这个参数一定不要省。固定随机种子才能保证你每次训练的结果是可控的也方便别人复现你的实验。很多人不设这个参数结果每次跑出来的指标都不一样最后连自己都搞不清楚哪个模型更好。2.3 工程目录结构从第一天就别乱项目目录结构是工程素养的直接体现。我见过一个极端的例子有人把数据和训练脚本全部堆在一个文件夹里文件名是final_v2_真的最终版.py两周之后自己都找不到哪个是能跑的版本。一个规范的AI项目目录长这样project/ ├── data/ │ ├── raw/ # 原始数据只读不写 │ ├── processed/ # 清洗后的数据 │ └── external/ # 外部引入的数据 ├── notebooks/ # 探索性分析用Jupyter Notebook ├── src/ # 核心代码 │ ├── data_preprocessing.py │ ├── train.py │ ├── evaluate.py │ └── predict.py ├── models/ # 训练好的模型文件 ├── tests/ # 测试代码 ├── requirements.txt ├── README.md └── .gitignore这个结构的核心思想是原始数据永远不动代码、数据、模型互相分离。数据目录只用来放数据代码目录只用来放代码模型文件单独存放这样每一步出问题都能快速定位。另外从第一天开始就用Git管理代码每次改动留下记录这是所有工程项目的底线。3. 数据管道与特征工程实操AI工程的造血系统3.1 数据清洗的五个常见动作拿到一份新数据直接训练模型是大忌。真实数据基本都是脏的最常见的清洗动作就五个第一是去重。很多业务数据里有重复记录尤其是日志类数据。用df.drop_duplicates()处理前先想想重复的定义是什么是整行一模一样还是某些关键字段一样别一股脑全删。第二是处理缺失值。有三个方向如果缺失比例很低直接删除如果缺失有规律可以填充平均值、中位数或者特殊值如果字段本身没有分析价值整列删掉。例如用户年龄缺失可以用中位数填充这个量级够稳。第三是处理异常值。比如一个正常的交易金额字段突然出现了100亿多半是单位错误或者脏数据。可以用分位数的办法截断超过99.9%分位数的值单独标记或剔除。第四是格式统一。时间字段有的是字符串有的是时间戳有的带时区不统一后面会很痛苦。字符串类型和数值类型混用的情况也要处理。第五是类型转换。Pandas读CSV时经常会把数值列读成字符串或者把ID读成数值这一步把每列的类型都过一遍能避免很多隐蔽问题。3.2 特征编码与标准化让模型吃得舒服模型本质上是数学函数只能吃数值。所以类别特征要做编码数值特征有时要做标准化。类别特征编码最常用的是One-Hot Encoding和Label Encoding。用Scikit-learn的OneHotEncoder可以把红黄蓝变成[1,0,0]、[0,1,0]、[0,0,1]这种形式适合类别之间没有大小关系的场景。但如果类别本身有顺序比如低中高Label Encoding也可以直接用。数值特征标准化最常用的是Z-score标准化把数据变成均值为0、方差为1的分布。公式很简单(x - mean) / std。在Scikit-learn里用StandardScaler注意这个scaler要在训练集上fit然后分别transform训练集和测试集绝对不能用整个数据集一起fit。特征工程要有个度。我见过新手把特征做到几百维训练时间翻倍效果不但没提升还过拟合了。判断特征有没有用的一个土办法单独拿每个特征训练一个弱模型看效果或者直接看特征重要性排序把排名末尾的砍掉。3.3 数据切分与数据泄漏最隐蔽的坑训练集、验证集、测试集三者的切分是数据环节最容易埋雷的地方。常规做法是train_test_split随机切分这在很多场景下够用。但如果你是处理时间序列数据比如按天统计的订单量预测那就必须按时间顺序切分前70%做训练中间15%做验证最后15%做测试。数据泄漏是最阴险的问题。我举一个真实踩过的例子当时做客户流失预测把所有特征做了一遍标准化顺手把scaler在完整的数据集上fit了然后再切训练集和测试集。结果训练集准确率95%测试集却只有60%。查了两天才发现标准化时把测试集的信息也融进去了模型在开卷考试一到真刀真枪就露馅。正确的做法一定是先切分再在各数据集上分别做标准化。这里核心原则一句话任何用到全量数据的统计操作都会造成数据泄漏。包括标准化、缺失值填充、异常值截断都要在训练集上完成参数计算。4. 模型训练、评估与部署从本地脚本到可用服务4.1 训练配置管理别把超参数硬编码在代码里很多人训练模型的代码里直接写死超参数比如随机森林的100棵树、学习率0.001。这种做法短期看着省事一旦项目规模变大就头疼每次调参都要改源码、记不清跑了哪些配置、实验无法复现。更好的方式是单独管理配置。最简单的可以用Python的字典或者config.py文件稍微规范一点可以用YAML文件。下面是一个最小示例model: name: RandomForestClassifier n_estimators: 200 max_depth: 10 random_state: 42 data: raw_path: data/raw/train.csv processed_path: data/processed/train.parquet test_size: 0.2 train: save_dir: models/训练脚本里读取配置文件这样每次实验改配置就行代码一个字都不用动。再做认真一点可以用MLflow记录每次实验的参数和指标这样训练历史一目了然选模型不再靠记忆力。4.2 评估指标怎么选别被准确率骗了分类任务大家都爱看准确率但这个指标在样本不均衡的时候会骗人。比如信用卡欺诈检测99.9%的样本都是正常交易只要模型全预测正常准确率就是99.9%但这种模型毫无价值。这时候应该看精确率、召回率和F1。精确率回答你预测为欺诈的那些里有多少真的欺诈召回率回答所有真实的欺诈里你找出了多少。F1是两者的调和平均。做用户推荐、异常检测、疾病筛查均衡性的指标比准确率有用得多。回归任务里MAE和RMSE各有特点。RMSE因为对误差做了平方会把大误差的样本点放大所以如果你特别不希望出现极端错误RMSE更合适。MAE更直观就是平均差多少。模型评估还有一个容易被忽略的细节把评估结果落到一张报表里。包含不同模型、不同参数在每个指标上的表现记录下来。否则过了两周就会忘记A模型比B模型好这个结论是怎么得出来的。4.3 部署方案与上线细节模型不只是文件训练好的模型不部署上线就是一堆没用的字节。对于从零开始的AI工程我最推荐的方案是FastAPI加Docker轻量、灵活、绕开复杂框架的陡峭学习曲线。保存模型时用joblib.dump就能搞定但要注意模型的版本命名。别用model_final.joblib这种名字建议带上日期和指标rf_20240101_acc095.joblib这样一眼就能看出是哪个版本、效果如何。部署推理服务用FastAPI写一个极简接口from fastapi import FastAPI from pydantic import BaseModel import joblib import numpy as np app FastAPI(titleIris Predictor) model joblib.load(models/iris_rf.joblib) class InputData(BaseModel): features: list[float] app.post(/predict) def predict(data: InputData): X np.array(data.features).reshape(1, -1) pred model.predict(X)[0] prob model.predict_proba(X).tolist() return {prediction: int(pred), probability: prob}启动服务的命令是uvicorn main:app --host 0.0.0.0 --port 8000。这个接口把模型包装成了一个HTTP服务任何系统都可以通过Post请求来调用。上线之前有几件必须做的事第一是输入校验如果客户端传了格式不对的数据接口不应该直接报500错误而是返回一个清晰的400错误。第二是兜底逻辑模型预测失败时要有响应策略至少不能影响主流程。第三是日志记录每一条预测请求都要留下记录否则模型上线后效果变差你连从哪里排查都不知道。注意Docker打包时模型文件要一并COPY进镜像里千万别把模型路径写成你本机的绝对路径。镜像构建时常见的问题是依赖装不完、镜像体积巨大。用python:3.10-slim做基础镜像而不是完整的python:3.10能省出不少空间。5. 常见问题排查与避坑实录那些踩过的坑5.1 环境依赖的经典翻车现场环境问题大概是AI工程里出现频率最高的问题。最经典的是CUDA版本不匹配。装了PyTorch之后跑代码提示CUDA driver版本太低但电脑里明明装了很高的驱动。这通常是因为PyTorch编译时对应的CUDA运行时版本和驱动版本不匹配。解决思路是先执行nvidia-smi看驱动支持的最大CUDA版本然后安装对应版本的PyTorch比如pip install torch --index-url https://download.pytorch.org/whl/cu121选定CUDA 12.1版本。另一个高频问题是pip装包和conda装包混用导致同一个包出现两个版本行为千奇百怪。我的原则是在一个虚拟环境里尽量只用一种包管理器要么conda要么pip不要混用。遇到resolve不了的问题直接重建一个干净环境比在那里耗半小时强。5.2 模型表现与预期不符的三类情况模型训练完经常出现三种症状。第一种是训练集表现极好、测试集表现差这是明显的过拟合。解决方向有三个加更多数据、降低模型复杂度、增加正则化。随机森林就减少树的深度或增加树的棵数。第二种是训练集本身表现就很差这通常是欠拟合或者数据问题。先检查数据有没有大量缺失、特征有没有信息量、目标列的分布是不是有问题然后再考虑换更强的模型。第三种是指标忽高忽低每次训练结果都不一样。原因多半是没固定随机种子或者数据集切分没有固定。这个最容易修把random_state全部固定即可。排查模型问题有一个好习惯把训练日志和评估结果一起归档。这样每次调试都有据可查而不是总在同一批问题上反复折腾。5.3 部署阶段的三个坑部署阶段新手最常踩的坑第一个是路径写死。代码里写的模型路径是C:\Users\name\models\model.joblib换个环境就崩。正确做法是用相对路径或者通过环境变量传入路径。第二个坑是接口性能。本地调用一次预测可能只需要10毫秒但上线后发现大量请求堆积延迟飙升。检查方向有两个看模型加载是否在每次请求时重复执行看推理过程是否有不必要的批量计算。模型应该在服务启动时加载一次而不是在请求函数里加载。第三个坑是模型监控缺失。模型上线时会觉得任务完成了但真实世界里数据分布会变今天预测准确率90%三个月后可能掉到60%而且没人察觉。最简单的监控方案是在日志里记录预测值和特征摘要每天对比分布变化一旦发现数据漂移就要考虑重新训练。写在最后的个人体会跑完一个从零开始的AI工程项目最大的感受就是这个领域的从零不是一个点而是一个链条。环境、数据、训练、部署每一步都有各自的底层逻辑和潜藏陷阱但有一条主线贯穿始终——把一个东西真正跑起来比追求完美重要太多。我自己第一次做这个流程时光环境就装了两天当时觉得特别挫败但后来才发现那两天踩的坑在后续每个项目里都在帮我避雷。如果你打算按这个路线来一遍我建议你给自己定一个目标两周之内不管模型效果多差先让它以一个服务的形式跑在8090端口上。这个目标的达成感和意义比任何所谓的学习进度都大。最后再分享一个小技巧每解决一个问题就把它记在项目的ISSUE或者笔记里。AI工程入门阶段你走过的弯路、排过的雷就是你最值钱的经验。