先聊一个很多人都踩过的坑你看了不少深度学习的书也跟着教程跑通了好几个开源模型手写数字识别、猫狗分类、情感分析都能出结果然后你觉得自己已经会“AI”了。但你试着把模型部署成一个别人能用的服务或者想把它接进一个真实的业务系统里就会发现一堆完全没有见过的问题——模型文件怎么管理、接口怎么设计、线上数据长什么样完全不可控、效果变差了怎么排查、模型怎么更新才不影响线上。到了这一步你才真正碰到“AI工程”这个概念。“ai-engineering-from-scratch”这个标题本质上就是在说一件事不靠零散的教程片段而是从零开始把AI从“能跑通的脚本”变成“能稳定运行的系统”需要的那一整套工程能力补齐。它不是再讲一遍反向传播公式也不是介绍某个框架的API而是站在“造一个真正能用的AI系统”的角度把数据、模型、部署、监控、迭代这条链路从头走一遍。这篇文章就是给那些准备系统入行AI工程、或者已经在做算法但始终觉得工程能力是短板的人梳理一条从零开始的完整路径并且会用一个真实的端到端案例——文本分类服务——把每个环节拆开讲透。1. 先想清楚AI工程到底在解决什么问题1.1 一个“能跑的模型”和一个“能用的系统”之间隔着什么很多初学者最大的认知偏差是觉得训练出一个准确率不错的模型工作就完成了。实际上在一个真实的AI项目里模型训练可能只占整个工作量的一小部分。你随便打开一个招聘网站的AI工程岗位要求会发现它要求的技能通常是Python开发、数据处理、Docker容器化、模型服务化部署、CI/CD、监控告警、实验管理……这些才是AI工程的核心。你可以把AI系统想象成一家餐厅。模型只是那个最擅长做某一道菜的厨师。但一家餐厅要正常运转需要有人买菜数据获取、洗菜切菜数据清洗、设计菜单特征与模型选型、制定出餐流程服务化部署、有人盯着后厨别起火监控告警、定期更新菜品模型迭代。AI工程做的事情就是把“一个厨师”变成“一家能稳定营业的餐厅”。所以这个问题的答案很直接隔着一整套工程基础设施。模型是AI系统的心脏但AI工程是整个身体。你只训练模型相当于只培养了一颗心脏它没法独自存活。1.2 from scratch 的真实含义不是重造轮子而是重建认知顺序我见过不少人看到“from scratch”这个词误以为是要从零手写神经网络、自己实现反向传播、不用任何深度学习框架。这个理解是把“教学手段”和“工程目标”搞混了。从零学的意思是不要靠碎片化的知识拼凑而是系统性地、按照合理的依赖顺序把AI工程所需要的各项能力逐个建立起来。打个比方学开车的时候你会学交通规则、练直线行驶、倒车入库最后上路这个顺序是固定的。AI工程的顺序也很明确工程底座Python、Git、Linux→ 数据能力 → 模型开发 → 部署运维 → 监控迭代。它的核心是建立一套完整的“问题拆解能力”——遇到任何AI项目你知道第一步做什么、第二步做什么、每一步做到什么程度算完成。之所以强调这一点是因为大部分网上资料都是“场景驱动”的——教你用某个框架跑一个具体任务但不会告诉你这些知识点在整个工程体系里的位置。等你真正进入项目遇到问题时你会发现每个点好像都见过但串不起来。这就是缺乏系统性认知的典型症状。1.3 这个内容适合谁需要什么前置基础最适合读这条路径的人大致有三类。第一类是想转行做AI工程的学生或初级开发者你缺少的是一张地图告诉你该往哪走。第二类是算法工程师你模型做得不错但部署、运维、上线这些环节总依赖别人补齐工程短板之后你的方案才能真正独立落地。第三类是已经在做传统后端开发、想切入AI方向的工程师你的工程基础很扎实但对数据、模型训练这些环节比较陌生需要补齐的是另外半边。前置基础其实不需要多高。会Python基础语法、懂一点机器学习的入门概念哪怕只是听说过损失函数和梯度下降就足够了。如果连这些也没有也不是不能学只是你需要在学习过程中同步补Python基础知识稍微慢一点但路径是一样的。2. 从零起步的知识地图该学什么、为什么按这个顺序学2.1 阶段一工程底座决定你后续所有环节的效率上限很多人会忽略这个阶段觉得Python语法会写循环、会用列表推导式就够了。但进入AI工程之后你会发现真正消耗时间的不是模型本身而是代码组织、环境管理、别人协作时搞坏你的代码、在服务器上装了一个包导致环境崩了等这类问题。所以工程底座这关至少要过三样东西。第一是Python开发能力的进阶。不是学语法而是学会怎么写“可维护的代码”函数怎么拆分、类型注解怎么用、异常怎么处理、日志怎么打。这些看起来很基础但决定了你后续能不能debug、能不能跟别人合作。Python在这条链路里是粘合剂语言数据清洗用Python、训练脚本用Python、部署服务还是用Python。第二是Git版本控制。这是所有工程协作的地基。你需要熟练掌握的不是那几个炫酷命令而是最日常的流程创建分支、提交代码、合并分支、解决冲突。几乎所有AI项目的失败都是从代码混乱开始的——谁也不知道当前这份模型代码是哪一版的跑出来的结果不可复现。第三是Linux基础。因为几乎所有AI系统的生产环境都是Linux服务器。你得会看日志、查进程、管理文件、配环境变量还要能在命令行里用vim或者nano改配置。如果你看到这里觉得有点虚可以自己建一台云服务器或者哪怕在Windows上用WSL也行实际操练一遍不然后面部署环节你会卡得很痛苦。2.2 阶段二数据工程能力AI项目里最耗时也最决定上限的环节一个经常被忽略的事实是在真实的AI项目里数据准备的时间通常会占到60%甚至更多。因为现实中的数据不会像Kaggle比赛那样整齐地躺在CSV文件里等你下载。数据可能散落在数据库里、日志文件里、第三方接口里而且极其肮脏——字段缺失、格式混乱、值域离谱、标注错误。如果你没有数据工程能力你会发现自己训练的根本不叫模型而是“垃圾进、垃圾出”的豪华版。这个阶段的核心技能有四个。第一是数据采集会从各种来源拿数据SQL查库、API拉取、爬虫采集、读日志文件。第二是数据清洗处理缺失值、去重、格式转换、异常值过滤这一块是pandas的主场你得熟练到条件筛选、groupby、apply这些操作不需想就能写出来。第三是特征工程把原始数据变成模型能用的特征这里需要你理解“特征”的本质——它是你对数据规律的先验总结好的特征工程师其实是在用自己对业务的理解指导模型学习。第四是数据版本管理这是很多团队忽略的你的模型是用哪一版数据训练的不记录这个后面模型出问题你连排查的入口都找不到。这一块还有个容易被忽略的点数据质量评估。不要以为数据清洗完了就没事了你需要用统计方法检查数据的分布是否合理、训练集和线上数据是否同分布、有没有数据泄漏。上面提到的每一个环节后面都会用案例代码演示。2.3 阶段三模型开发能力知道“调”和“训”的区别很多过来人都会说一句话“训练模型不难难的是训练出一个能在真实场景表现的模型。”这里有三个层次。第一个层次是会用框架。比如PyTorch或TensorFlow能搭一个网络、跑通训练循环这其实是最基本的几天就能学会。第二个层次是理解训练过程。知道损失函数怎么设计的、学习率怎么影响收敛、过拟合怎么判断和抑制、数据增强怎么用、Batch Size怎么影响训练速度和模型表现。到了这个层次你才算是“在训练模型”而不是“在跑代码”。第三个层次是评估能力。不要只看准确率还要看精确率、召回率、F1、AUC更重要的是要会结合业务场景选指标——例如垃圾邮件识别里漏掉一封垃圾邮件和误杀一封正常邮件代价完全不同。有个例子我记得特别清楚。有个朋友做了一个风险识别模型线下测试的准确率做到了98%他自己很满意。但上线之后他发现大部分风险案例根本查不出来。后来我们排查发现他用的训练数据里风险样本占比是15%但真实业务里风险样本的可能占比只有千分之几。模型在训练集上学会了“只要像正常样本就输出正常”因为它的损失函数被绝大多数正常样本主导了。这就是典型的“不考虑业务分布”的训练。所以在模型开发阶段你不仅要学会训还要学会“用业务视角审视训练任务”。2.4 阶段四部署与运维把模型从笔记本搬到服务器上训练结束不是终点。在真实的AI系统里模型要变成可以被外部调用的服务。这一阶段有两个关键技能模型服务化和容器化。模型服务化最主流的方式是用FastAPI或者Flask包一个HTTP接口让业务方可以发送请求、接收预测结果。听起来简单但细节非常多——怎么处理并发请求、怎么控制请求超时、怎么做批量预测的优化、模型文件怎么加载到内存里、GPU怎么分配。这些细节决定了你的服务在高负载下会不会崩。容器化是另一道坎。为什么需要Docker因为模型训练和部署的环境通常不一致你在本地装了一堆依赖包到服务器上可能版本对不上、缺库、系统不兼容。Docker把整个环境打包成一个镜像不管到哪里都能重现一模一样的运行环境这就从根本上解决了“在我机器上明明是好的”这个经典问题。再往后是CI/CD也就是持续集成和持续部署。它的意义在于把代码提交、测试、构建镜像、发布服务这些操作自动化每一次改动都走一条标准流水线避免手动操作带来的不确定性。这一阶段是整个AI工程里最“硬核”的部分也是很多算法工程师最头疼的部分但它恰恰是AI系统能否长期稳定运行的关键。2.5 阶段五监控与迭代让系统在变化中保持稳定模型上线只是开始。AI系统比较特殊的地方在于它运行在动态变化的环境里。用户的分布会变、数据模式会变、坏人的攻击方式也会变。一个上线时表现很好的模型可能三个月后就在不知不觉中退化。你如果只看整体准确率是发现不了问题的——因为准确率通常不会断崖式下跌而是以缓慢的方式持续劣化等你注意到时已经晚了。所以监控的核心是持续观察模型输入数据的分布、预测结果的分布、以及你关心的核心业务指标。具体来说你需要记录模型每一次请求的输入特征分布定期和训练时的分布做对比发现明显偏移就要及时预警。同时还要监控服务的延迟、吞吐量、错误率确保服务本身健康。这就是为什么我在前面的知识地图里把监控与迭代放在最后——它不是独立的技能而是综合考验你对数据、模型、系统三者的理解。数据漂移你要会检测模型重训你要会设计训练流程代码更新你要会走发布流程。到这里一个AI系统才真正形成了闭环。3. 实操闭环从零构建一个文本分类服务3.1 项目选型和整体设计思路理论聊再多不如动手做一个项目。我推荐从零构建一个“垃圾短信文本分类服务”这个选题有三个原因。第一数据非常容易获取你可以用公开数据集也可以自己收集标注几百条样本门槛极低。第二问题足够典型——从文本预处理到模型训练再到服务部署每个环节都有代表性。第三它是一个完整的闭环场景可以清晰地展示AI工程怎么做端到端交付。整个系统拆解下来一共四层数据层负责收集和清洗短信文本模型层负责训练一个文本分类模型判断一条短信是正常短信还是垃圾短信服务层用FastAPI把模型包装成一个HTTP接口业务方POST一条短信文本进去返回分类结果和置信度运维层则负责容器化部署、日志记录和基础监控。下面我按照这个设计把每层的实现细节一步步讲清楚。这一段会非常实操你可以直接照着做。我也建议你不要只抄代码而是跑通之后自己改一改比如换一个数据集、调整模型结构——这是把技能内化的唯一方式。3.2 数据准备与清洗用pandas把脏数据变成模型能吃的格式首先准备一个训练数据集。假设我们手头有一批短信文本每条短信带有标签1代表垃圾短信0代表正常短信。原始数据通常是CSV文件但里面的文本往往非常脏有超链接、HTML标签、特殊符号、各种Emoji、繁体简体混杂、大小写混杂。这些杂讯对模型训练没有帮助反而会引入噪声所以先写一个文本清洗函数。import re import pandas as pd def clean_text(text: str) - str: # 移除HTML标签 text re.sub(r.*?, , text) # 移除URL text re.sub(rhttp\S|https\S|www\.\S, , text) # 移除连续数字短信中常见的验证码、电话号码等 text re.sub(r\d{5,}, , text) # 移除特殊符号只保留中文、英文、基础标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s。.!?], , text) # 统一转为小写 text text.lower() # 压缩连续空白 text re.sub(r\s, , text).strip() return text df pd.read_csv(sms_data.csv) df[clean_text] df[text].apply(clean_text) # 去除空文本和重复内容 df df[df[clean_text].str.len() 0] df df.drop_duplicates(subsetclean_text) print(df[label].value_counts())这一步里有很多容易踩的坑。一个是清洗顺序很讲究比如一定要先移除URL再移除链接文本里的http残留否则光匹配http\S可能把以“http://xxx”开头的整块文本去掉后又把残留的http当成正常词留下来。另一个是尽量不要把文本中所有数字都删掉因为验证码短信里的验证码数字是有业务含义的只删连续5位以上的长数字通常更合理。清洗完之后还有两个关键动作。第一个是检查类别分布——如果垃圾短信和正常短信的比例严重失衡比如100:1你需要考虑采样策略。第二个是划分训练集和验证集时必须使用分层抽样也就是保证训练集和验证集里的正负比例一致。from sklearn.model_selection import train_test_split # stratify参数保证分层抽样测试集占20% train_df, val_df train_test_split( df, test_size0.2, random_state42, stratifydf[label] ) print(f训练集大小: {len(train_df)}, 验证集大小: {len(val_df)})3.3 模型训练与评估训练参数怎么定、怎么判断效果文本分类的方法有很多传统机器学习用TF-IDF加逻辑回归深度学习可以用BERT等预训练模型。对于一个小型短信分类项目我建议先跑一个TF-IDF加逻辑回归的基线模型它的训练速度快、可解释性强、在中小规模数据上效果往往不差而且不需要昂贵的GPU资源。这一步的核心不是模型有多新而是让你建立“训练闭环”的概念特征提取 → 模型训练 → 效果评估 → 错误分析 → 改进迭代。下面给出基线模型的完整代码from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline from sklearn.metrics import classification_report # TF-IDF参数说明 # max_features5000 控制特征维度文本词表过大容易过拟合 # ngram_range(1,2) 保留单个词和相邻两个词的组合能捕捉一些短语特征 vectorizer TfidfVectorizer(max_features5000, ngram_range(1,2)) model Pipeline([ (tfidf, vectorizer), (clf, LogisticRegression(max_iter1000, C1.0)) ]) model.fit(train_df[clean_text], train_df[label]) # 在验证集上评估 val_pred model.predict(val_df[clean_text]) print(classification_report(val_df[label], val_pred, target_names[正常短信, 垃圾短信]))看评估结果时不要只看准确率要看查准率和查全率的平衡。一个特别常见的坑是模型对所有文本都预测为“正常短信”因为绝大多数样本本来就是正常的这时准确率也会很高但这个模型毫无价值。你要检查分类报告里的各类别查准率和查全率特别是少数类垃圾短信的查全率。如果基线模型效果不理想可以依次尝试调整三个方向。第一个方向是文本预处理比如保留数字、添加自定义分词词典。中文短信需要选用合适的分词工具比如jieba不同的分词策略对效果影响很大。第二个方向是模型参数比如增大max_features调整正则强度C或者切换成朴素贝叶斯等其他模型。第三个方向是改变特征比如加入文本长度特征、是否含URL特征等。每一轮调整后都在验证集上重新评估记录效果变化。3.4 部署成HTTP服务这一步会暴露所有你没想过的问题模型训练好之后保存成文件让它成为一个可对外提供预测的服务。我用FastAPI来实现这个环节它有几个明显的优势代码量小、自带交互式API文档、异步支持好、性能足够。import joblib from fastapi import FastAPI from pydantic import BaseModel app FastAPI(title垃圾短信识别服务) model joblib.load(sms_model.joblib) class SmsRequest(BaseModel): text: str app.post(/predict) def predict(req: SmsRequest): # 注意线上请求的文本必须走和训练时相同的清洗流程 cleaned clean_text(req.text) prob model.predict_proba([cleaned])[0] label int(prob[1] 0.5) return { label: label, confidence: float(prob[1] if label 1 else prob[0]) }这里藏着一个非常关键、但很多人会忽略的问题线上推理时的数据预处理必须和训练时完全一致。你在训练前做了一套清洗函数在线服务里也必须调用同一个函数一个字符都不能差。我曾经见过一个线上服务因为少做了一个strip()操作导致所有请求的预测置信度都偏低排查了很久才发现是清洗流程不一致。验证服务可以这样测一下# 启动服务 uvicorn main:app --host 0.0.0.0 --port 8000 # 测试请求 curl -X POST http://localhost:8000/predict \ -H Content-Type: application/json \ -d {text: 恭喜您获得100万大奖点击链接领取}服务跑通之后一定要问自己一个问题如果同时来100个请求服务能不能扛住如果不能就要考虑优化点要不要加并发处理要不要用批处理要不要上GPU这些优化方向是AI工程里非常典型的性能调优场景。3.5 容器化部署与基础监控往生产环境迈出最后一步为了在真实的服务器上稳定运行我用Docker把服务和环境一起打包。FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY main.py . COPY sms_model.joblib . EXPOSE 8000 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]Dockerfile写好后构建镜像并运行docker build -t sms-classifier . docker run -p 8000:8000 sms-classifier做完这步你的服务已经被装进一个可移植的“集装箱”里了在任何有Docker的机器上都能一键启动不会再出现“代码在笔者的电脑能跑在服务器上就报错”的情况。基础监控这块我建议起步阶段先做两件事就够。第一件在服务里埋日志记录每次请求的文本、预测结果、置信度、处理耗时。这不仅是审计需要也是排查线上问题的最重要依据。第二件定期统计线上调用中垃圾短信的占比把它和训练集的分布做对比一旦发现偏差超过阈值就说明模型可能已经不能适应当前数据分布需要重新训练了。import logging import time logging.basicConfig( filenameservice.log, levellogging.INFO, format%(asctime)s %(message)s ) app.post(/predict) def predict(req: SmsRequest): start time.time() cleaned clean_text(req.text) prob model.predict_proba([cleaned])[0] label int(prob[1] 0.5) latency time.time() - start logging.info(flabel{label} confidence{float(max(prob)):.4f} latency{latency:.3f}s text{req.text[:50]}) return {label: label, confidence: float(max(prob))}4. 常见问题与排查技巧实录4.1 本地跑得通上线就报错环境和依赖的隐形差异这是我见过出现频次最高的一类问题。代码在本地好好的环境变量一换就崩。典型症状包括ModuleNotFoundError: No module named xxx、系统库版本不兼容、Python版本不一致导致的语法错误。这个问题的根源就是环境不一致。排查思路只有一个方向把你的环境完整地交付给目标机器。在个人实践里Docker是解决这个问题的最强手段没有之一。如果你因为某些原因用不了Docker也要用requirements.txt配合pip freeze精确锁定所有依赖的版本号。我的建议是项目从一开始就容器化不要等到部署的时候再临时抱佛脚。还有一个小技巧本地开发时使用虚拟环境virtualenv或conda不要直接装在系统Python里。不然你的环境会被不同项目的依赖搞成一团乱麻最终你甚至不知道当前Python环境里装了哪些包排查起来非常痛苦。4.2 测试集表现好线上效果差数据分布不一致与特征穿越这个问题的背后通常有三个原因。第一个是采样偏差训练数据的分布和真实业务分布不一样比如前面提到的风险识别例子训练集正负比例与真实场景严重不一致。第二个是特征泄漏你在训练时使用了未来信息或不可在线获取的信息导致离线评估虚高。第三个是线上数据格式发生了漂移线上来的文本里大量出现训练阶段没有见过的新词汇和格式。排查方法很简单拿出线上日志里的真实请求数据重新组成一个“伪测试集”让模型预测一遍对比线上表现和测试表现。如果伪测试集上的效果远差于原始测试集问题就出在分布不一致上。这个操作在工程上叫“线上样本回放”是非常好用的一招。至于解决手段除了重新按真实分布采样训练数据还有一个思路是给服务加一个输入校验层把异常的输入请求拦截下来而不是硬生生喂给模型。4.3 训练时GPU显存溢出搜索空间太大导致的资源管理问题训练脚本很好写但把训练塞进一台机器时显存管理就成了一门手艺。最常见的报错是CUDA out of memory。这跟模型本身大小关系不大更多是你在一个batch里放了太多数据或者显存被上一次训练残留占着。排查和调整的顺序是先检查是否有残留进程占用显存用nvidia-smi看进程列表关掉不需要的进程。然后尝试减小batch_size这是最简单直接的方案。接着检查输入数据的尺寸比如文本长度有没有异常的长样本——一条超长文本会显著增加显存消耗。最后再考虑梯度累积、混合精度这些高级手段。我自己的习惯是把batch_size定成一个能整除训练数据总量的数字并设置num_workers控制数据加载进程数。给一个参考一个BERT-base模型在普通24G显存的卡上batch_size设置为16到32通常没什么问题。如果你用的是超大模型或长文本就要在性能和显存之间做权衡不断做减法。4.4 模型更新上线后效果变差回归测试与发布策略缺失很多团队犯过一个错误训练了一个效果更好的模型直接替换线上的旧模型结果用户反馈“变笨了”。为什么离线评估更好线上反而更差除了数据分布问题还有一个非常重要的原因是缺乏回归测试。回归测试的思路是保留一份历史真实请求样本集新模型上线前先在这个样本集上跑一遍和旧模型输出做对比。重点看两类差异新模型把原本正确的预测改错了多少把原本错误的预测改对了多少。只关注准确率提升是不够的必须关注“预测回退”。比如一个垃圾短信识别服务新模型可能提高了5%的准确率但同时有0.2%的正常短信被新模型误判成了垃圾短信对业务来说这可能是不可接受的损失。发布策略上也有讲究。不要直接全量替换可以先灰度发布——给5%的流量用新模型其余95%还是旧模型线上对比效果后再逐步放量。如果发现异常一键切回旧模型。这是AI工程里很常见的“灰度回滚”策略它不增加多少工作量但能避免很多线上事故。这里额外分享一个经验模型要管理好版本不要只存一个“final_model.joblib”。推荐在文件名里带上训练时间和评估指标例如sms_model_20250110_f1_0.942.joblib同时保留一份训练配置的记录文件。这样你才能回答“这个模型是怎么来的、凭什么用这个”这种问题。5. 我个人的一些体会最后闲聊几句。做了这么多年AI工程我最想对准备走这条路的朋友说的一句话是不要急着去追新模型、新框架先把端到端的流水线能力打扎实。一个能把模型按标准流程上线、监控、迭代的人在团队里的价值远高于一个只会读论文调参数的人。因为算法可以学但工程能力必须靠一个个项目磨出来。我自己刚入行的时候也走过弯路连续几个月醉心于改进模型结构结果一上生产环境全傻眼后来才开始回头补工程短板。所以你们的起点已经比我好很多——至少一开始就知道要从系统的角度看AI。拿一个实际项目走完整条链路里面每个环节你都亲手做一遍遇到问题把它记下来下次你就不再是“踩坑”而是“避坑”了。从零到一很难但从一到一百其实都是同一套方法论的重复应用。祝你们都能跑通属于自己的第一个AI工程闭环。