TensorFlow 与 PyTorch
这一课你将能做到
- 一张表说清 PyTorch / TensorFlow / JAX / XGBoost / scikit-learn 各守哪块阵地
- 用 FastAPI + pydantic 把上一课保存的 pipeline 部署成一个 HTTP 预测服务
- 用 uvicorn 启动服务并用命令行验证接口
- 说清训练/推理分离、版本管理、特征漂移监控、灰度发布这些上线工程题
为什么要学
「模型在 notebook 里准确率 0.9」和「模型在服务器上稳定服务 10 万用户」之间隔着一条工程鸿沟。面试官问深度学习框架,多半不是考 API,而是考你知不知道模型从实验到上线要经过什么。
本课先给框架地图定方位,再把上一课保存的 mastery_pipe.joblib 真正变成一个可调用的 HTTP 服务——这一步做完,你的项目才算闭环。
先建立图像
训练侧(离线,可以慢) 服务侧(在线,必须快而稳)
notebook / 脚本 FastAPI 进程
│ 交叉验证调参 │ 启动时加载 joblib 工件
▼ │ 收到 JSON → DataFrame
joblib.dump(pipeline) ────工件───► ▼
pipeline.predict_proba → JSON 返回
跟着做
步骤 1:框架地图
| 框架 | 定位 | 什么场景选它 |
|---|---|---|
| PyTorch | 研究与工业主流,动态图调试友好 | 深度模型、论文复现、团队从零起步 |
| TensorFlow / Keras | 部署生态完善(Serving / TFLite) | 移动端、嵌入式、存量 TF 团队 |
| JAX | 函数式 + 编译加速 | 前沿研究、TPU 大规模训练 |
| XGBoost / LightGBM | 表格数据之王 | 特征清晰的中小规模结构化数据 |
| scikit-learn | 常规机器学习标准件 | 基线模型、快速验证想法 |
结论:先跑通 sklearn/XGBoost 基线,再决定要不要上 PyTorch,顺序别反。
步骤 2:写预测服务(main.py)
# pip install fastapi uvicorn joblib pandas scikit-learn
import joblib
import pandas as pd
from fastapi import FastAPI
from pydantic import BaseModel, Field
class Answer(BaseModel):
accuracy: float = Field(..., ge=0, le=1) # pydantic 做入参校验
attempts: float | None = None
topic: str
app = FastAPI(title="掌握度预测服务")
pipe = joblib.load("mastery_pipe.joblib") # 启动时加载一次
@app.post("/predict")
def predict(a: Answer):
X = pd.DataFrame([a.model_dump()]).astype({"attempts": "float64"})
proba = float(pipe.predict_proba(X)[0, 1])
return {"mastery_probability": round(proba, 3),
"mastery": proba >= 0.6}
两个细节:None 经 astype("float64") 转成 NaN 交给 Imputer 兜底;接口返回概率,阈值 0.6 留在业务层可随时改。
步骤 3:启动并用 PowerShell 验证
uvicorn main:app --reload --port 8000
另开一个终端:
Invoke-RestMethod -Uri http://127.0.0.1:8000/predict -Method Post `
-ContentType "application/json" `
-Body '{"accuracy":0.6,"attempts":null,"topic":"图论"}'
返回 mastery_probability 与 mastery 布尔值,服务闭环。浏览器打开 http://127.0.0.1:8000/docs 还有自动生成的交互文档。
步骤 4:上线之后要管四件事
- 版本登记:模型工件 + sklearn 版本 + 特征定义一起记录,出问题才能回滚到「上一个能用的组合」;
- 监控:预测分布突然偏移(特征漂移)往往先于业务指标恶化;
- 灰度:新旧模型并存,小流量验证后逐步放量,随时可切回;
- 再训练:线上积累了新数据,按周期触发重训与评估流程。
讲清楚要点
- 训练与推理分离是铁律:notebook 里的实验代码(随机打印、临时变量、探索逻辑)不能进服务;服务只做「加载工件 + 前向计算」。
- Pipeline 工件是接口契约:上游只需按训练时的原始列名传 JSON,所有预处理都在工件里——这就是上一课坚持存整条流水线的原因。
- 校验要在入口做:pydantic 的类型与范围约束(ge/le)挡住脏数据,别让异常输入直接打到模型。
- 概率比标签值钱:返回概率后,推难题、推复习、发勋章可以各用各的阈值,一次部署服务多个业务决策。
你可能会懵的地方
Q1:训练和推理为什么要分开部署? 资源特性相反:训练重吞吐、可离线、要 GPU;推理重延迟、常驻内存、多为 CPU。混在一起既慢又容易把实验代码的崩溃带给线上。
Q2:模型更新怎么做到不中断服务? 版本路由 + 滚动发布:新版本模型先起独立实例,健康检查通过后逐步切流量,旧实例待命以便秒级回滚——和后端服务的发布套路一致。
Q3:什么时候才需要 ONNX? 当你需要脱离 Python 运行时(C++ 服务、移动端、浏览器)或追求跨硬件加速时;纯 Python + FastAPI 的服务没必要多一层转换成本。
Q4:线上来了训练时没见过的类别怎么办?
短期靠 handle_unknown="ignore" 兜底不崩,中期靠监控发现新类别占比,长期触发重训把新类别纳入词表。
Q5:监控模型看什么指标? 三层:输入层看特征分布/漂移(如 PSI),模型层看预测分布与置信度,业务层看线上准确率与转化——离线 AUC 好不代表线上没问题。
错法对照
| 错法 | 后果 | 正确做法 |
|---|---|---|
| 训练代码直接当服务跑 | 慢、脆、依赖混乱 | 导出工件,服务只做前向 |
| 只存模型不记版本 | 出事无法回滚 | 工件 + 库版本 + 特征定义一起登记 |
| 接口返回硬编码 0/1 | 改阈值要重新发版 | 返回概率,阈值配置化 |
| pydantic 不做约束 | 脏数据打到模型 | Field(ge=0, le=1) + Optional 类型 |
| 上线后不看预测分布 | 漂移发现不了 | 监控输入/预测/业务三层指标 |
卡点急救
- pydantic v1 报
model_dump不存在 → 旧版用a.dict(),或升级 pydantic v2。 - Invoke-RestMethod 连不上 → 确认 uvicorn 窗口在运行、端口一致、URL 没写 https。
- joblib.load 报版本不兼容 → 训练端与服务端的 scikit-learn 大版本保持一致。
面试常问
Q1:说说模型从训练到上线的完整链路? 数据 → 训练评估 → 导出工件(含预处理) → 版本登记 → 服务封装(FastAPI/gRPC) → 灰度发布 → 监控与再训练,重点讲每步的失败模式。
Q2:特征漂移是什么,怎么发现? 线上输入分布随时间偏离训练分布(如新学期题目类型变化)。监控特征分布统计量(PSI/KL)与预测分布,超阈值告警并触发重训。
Q3:为什么推荐接口返回概率? 阈值是业务策略不是模型参数,返回概率可以让产品侧灵活调整判定线,还能支持排序类需求(只推 Top-K)。
自测三问
handle_unknown="ignore"遇到新类别的行为?(编码成全 0,服务不崩)- 为什么接口返回概率而不是 0/1 标签?(阈值归业务层,一份概率服务多个决策)
- joblib 加载失败最常见原因?(训练与服务端 sklearn 版本不一致)
小结一句话
造模型是课程题,供模型是工程题——把工件、版本、监控、灰度补齐,模型才真正从 notebook 走进生产。
延伸
- 官方文档:PyTorch API 文档 | scikit-learn 模型持久化
- 下一课预告:最后一课综合演练——用 5000 条真实形态的做题记录,完整走一遍从特征构建到推题清单的端到端项目。