
小白python入门 - 74. 模型服务化0. 写给初学模型躺在 Notebook 里不算「交付」你已经会训练了分类、回归、树、甚至第 73 课的文本管道。但老师/实习导师常问的是另一句「别人怎么用你的模型」如果答案是「打开我的 Jupyter找到第 47 个 cell改一行再 Run All」——那叫演示不叫服务。模型服务化白话版把「预处理 模型」整条链存成文件另写一个小程序加载文件接收新数据吐出预测最好用HTTP 接口像网站接口那样让别的系统来调用。本课你要建立的直觉joblib存的是整条Pipeline不是只存「最后那个分类器」HTTP 请求 ≈点外卖下单后面用这个比喻讲透文件能被加载 ≠ 安全pickle 类格式可以执行恶意代码训练时一套预处理、线上另一套 →训练-服务偏差分数再好看也白搭交付时附一张模型卡说明书比只甩一个.joblib专业一百倍。接阶段 CFastAPI若你上过 Web 课本课是「把 ML 接到 API」。没上过也没关系照抄最小例子先跑通再理解。1. 背景例子从「我电脑上的准确率」到「同学的脚本能调」场景你用历史订单表训了一个「会不会复购」的二分类模型运营同学想在他们的 Excel 流程里每天对新用户列表打分他们不会 Python只会「往某个地址 POST 一行 JSON」。你需要交付的不是 Notebook而是大致这样的东西models/repurchase_pipe.joblib ← 整条管道 app.py ← FastAPI 服务 README.md ← 怎么启动、怎么调用 MODEL_CARD.md ← 数据、指标、用途、局限 requirements.txt ← 依赖版本别人启动服务后POST /predict {features: {age: 22, orders_30d: 3, city: 上海}} → {label: 1, proba: 0.81}这就是最小的推理服务inference service。2. 图解一服务化长什么样三层分工记这个就够层干什么谁碰训练洗数据、调参、评估、导出管道你 / 数据同学离线产物.joblib 版本号 指标记录文件仓库 / 模型目录服务加载产物、校验输入、返回预测在线进程7×24 或按需启动大模型时代的对照一句大模型常见形态是Token API发一段话回一段话表格/传统 ML 常见形态是特征向量 API发一组字段回标签/分数形态不同工程纪律相同版本、校验、日志、训练与线上一致。3. HTTP 白话就像点外卖下单你没写过后端也没关系。把调用模型想成点外卖点外卖调模型 API你打开 App选好菜点「提交订单」客户端curl / 前端 / 别的服务发请求订单发到商家系统请求发到你的服务器地址如http://127.0.0.1:8000请求里写清菜品、地址、备注请求体里写清特征字段JSON商家校验地址能不能送、菜有没有服务校验字段齐不齐、类型对不对商家出餐 / 拒单模型预测/ 返回 422 错误你收到订单状态你收到 JSONlabel、proba等几个词对照词白话HTTP浏览器和服务器说话的常用规矩URL / 路径门牌号如/predict是「预测窗口」POST带正文提交适合送特征GET 多用于查询JSON一种双方都看得懂的数据包装键值对状态码 200成功状态码 422你订单填错了字段校验失败状态码 500店里锅炸了服务内部异常所以POST /predict不是玄学就是按规定格式下了一单「请帮我预测」。4. 图解二训练-服务偏差最阴的坑Training-serving skew白话考试前用「计算器 A 公式表」练习上场却只给「心算」——分数能一样吗常见不一致训练时线上却后果缺失用训练集中位数填直接填 0 或丢行分布偏移类别 One-Hot 含 20 城新来第 21 城没处理维度对不齐或静默错列先标准化再进模型忘了标准化距离/线性模型全歪文本小写 去 HTML原文直接向量化词对不上特征列顺序 age, income顺序 income, age灾难除非用 dict/DataFrame 按列名解药核心导出整条 Pipeline预处理 模型线上只调用pipeline.predict/predict_proba不要重写一套「差不多」的清洗用同一套字段名与 schema 校验。5. 代码训练并保存「整条」管道下面用 sklearn 自带的乳腺癌数据做二分类数字特征好演示。你换成自己的 68/70 课管道也完全一样。frompathlibimportPathimportjoblibfromsklearn.datasetsimportload_breast_cancerfromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportStandardScalerfromsklearn.pipelineimportPipelinefromsklearn.linear_modelimportLogisticRegressionfromsklearn.metricsimportclassification_report X,yload_breast_cancer(return_X_yTrue,as_frameTrue)feature_nameslist(X.columns)X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,random_state0,stratifyy)pipePipeline([(scaler,StandardScaler()),(clf,LogisticRegression(max_iter2000)),])pipe.fit(X_train,y_train)print(classification_report(y_test,pipe.predict(X_test),digits3))out_dirPath(models)out_dir.mkdir(exist_okTrue)artifact{pipeline:pipe,feature_names:feature_names,target_names:[malignant,benign],# 仅示例说明model_version:0.1.0,}joblib.dump(artifact,out_dir/cancer_pipe.joblib)print(saved:,out_dir/cancer_pipe.joblib)为什么塞进 dict以后加载时能核对特征名、版本号比「裸 dump 一个 pipe」更像可维护产物。加载自检loadedjoblib.load(models/cancer_pipe.joblib)pipe2loaded[pipeline]assertloaded[feature_names]feature_namesprint(reload score:,pipe2.score(X_test,y_test))6. 代码FastAPI 最小/predict先安装若尚未安装pipinstallfastapi uvicorn pydantic joblib scikit-learn pandasapp.py示例frompathlibimportPathfromtypingimportDict,ListimportjoblibimportpandasaspdfromfastapiimportFastAPI,HTTPExceptionfrompydanticimportBaseModel,Field ARTIFACT_PATHPath(models/cancer_pipe.joblib)bundlejoblib.load(ARTIFACT_PATH)pipebundle[pipeline]FEATURE_NAMES:List[str]bundle[feature_names]MODEL_VERSIONbundle.get(model_version,unknown)appFastAPI(titleCancer Demo Predictor,versionMODEL_VERSION)classPredictRequest(BaseModel):# 用 dict 接特征键必须是训练时的列名features:Dict[str,float]Field(...,description特征名到数值的映射必须覆盖全部 feature_names,)classPredictResponse(BaseModel):label:intproba:floatmodel_version:strapp.get(/health)defhealth():return{status:ok,model_version:MODEL_VERSION}app.post(/predict,response_modelPredictResponse)defpredict(req:PredictRequest):missing[cforcinFEATURE_NAMESifcnotinreq.features]ifmissing:raiseHTTPException(status_code422,detailf缺少特征:{missing[:5]}... 共{len(missing)}个,)row{c:req.features[c]forcinFEATURE_NAMES}Xpd.DataFrame([row],columnsFEATURE_NAMES)try:probafloat(pipe.predict_proba(X)[0,1])labelint(pipe.predict(X)[0])exceptExceptionase:raiseHTTPException(status_code500,detailstr(e))returnPredictResponse(labellabel,probaproba,model_versionMODEL_VERSION)启动uvicorn app:app--reload--host127.0.0.1--port8000浏览器打开http://127.0.0.1:8000/docs可以看到自动文档Swagger——点外卖菜单一样点一点就能试。6.1 用 curl「下单」把下面的特征换成你真实的一列示例仅示意结构curl-XPOSThttp://127.0.0.1:8000/predict^-HContent-Type: application/json^-d{\features\: {\mean radius\: 14.0}}特征不全时应收到422和缺少字段的说明——这是好事说明校验在干活。Python 客户端importrequests# 演示用测试集第一行构造完整 featurespayload{features:X_test.iloc[0].to_dict()}rrequests.post(http://127.0.0.1:8000/predict,jsonpayload,timeout5)print(r.status_code,r.json())6.2 文本管道同样套路接 73 课# 训练侧text_pipe.fit(X_train_text,y_train)joblib.dump({pipeline:text_pipe,model_version:text-0.1},models/text_pipe.joblib)# 服务侧请求体可以改成:# {text: 恭喜中奖点击领取}# 内部: pipe.predict([req.text])关键仍是向量器与分类器一起 dump。7. pickle / joblib 安全别加载「来路不明」的模型文件sklearn 官方说得很直白持久化格式基于pickle而 pickle可以在反序列化时执行任意代码。白话有人发给你一个hack.joblib你joblib.load它等于在自己电脑上运行对方埋好的程序。原则做法只加载可信来源自己训的、公司内网制品库、校验过哈希的文件不要加载邮件附件模型尤其是「帮我跑一下这个模型」生产环境权限最小化、扫描依赖、锁定版本进阶点名ONNX 等更偏「可移植推理」的格式跨语言部署时再学参考https://scikit-learn.org/stable/model_persistence.html依赖版本也要锁scikit-learn1.5.x numpy... joblib...换大版本 sklearn 后旧文件可能加载失败或行为微变——requirements.txt不是形式主义。8. 模型卡Model Card像电器说明书2019 年 Mitchell 等人提出Model Cards用固定栏目描述模型方便别人判断「能不能用、有啥坑」。你交作业/实习项目用 Markdown 写一页即可。8.1 最小模板直接复制填空# 模型卡项目名 v版本 ## 1. 模型详情 - 开发者 / 日期 - 任务类型分类 / 回归 / ... - 算法与管道如 StandardScaler LogisticRegression - 框架版本scikit-learn x.y、Python x.y - 产物文件models/xxx.joblib ## 2. 预期用途 - 主要用途 - 适用用户 - 明确 **不** 适用的场景 ## 3. 训练数据 - 来源与时间范围 - 样本量、划分方式train/val/test 或 CV - 敏感属性是否涉及性别、地域… - 已知数据偏差 ## 4. 评估 - 指标与数值写清在哪个集合上算的 - 基线对比 - 错误分析摘要13 条 ## 5. 伦理与风险 - 误判代价假阳/假阴哪个更贵 - 是否需要人工复核 - 公平性备注若未评估写「未评估」 ## 6. 运维 - 输入 schema字段名、类型、缺失策略 - 输出含义label / proba 阈值 - 重训触发条件数据漂移、指标下降… - 联系人监管与招聘语境里「会写模型卡」比「多刷 0.2% 准确率」更像靠谱工程。9. 校验、日志与版本服务不是print一下项最小做法输入校验Pydantic 模型缺字段 422输出契约固定 JSON 字段别有时返回数组有时返回字符串版本响应里带回model_version换模型改版本号健康检查GET /health给监控/负载均衡探活日志记录时间、版本、耗时慎记原始隐私字段限流点名公开接口防刷阶段 C 有缓存与限流课可回看异常输入测试清单缺字段类型错误字符串塞进数值极端值 / NaN空 body正常样例与离线predict结果对照。10. 常见坑坑正确直觉只保存clf不保存 scaler/向量器线上特征尺度全错 → 存整条 Pipeline训练用 DataFrame 列名线上用裸 list 且顺序乱按列名组DataFrameNotebook 里手动清洗服务里重写一份清洗进 Pipeline 或共享同一函数模块加载不可信.pkl安全风险等同跑陌生程序改了预处理没重导模型经典 skew改代码必须重新fitdump没有模型卡三个月后你自己也不记得适用边界把「本机 uvicorn」当生产本课只求跑通生产还要进程管理、HTTPS、鉴权等用训练准确率当线上 SLA线上要监控延迟、流量、业务指标11. 小结服务化 序列化产物 加载推理 通常HTTP 接口。joblib 存整条 Pipeline并带上特征名与版本。HTTP 就像点外卖下单路径、JSON、状态码都是订单规矩。pickle 不安全加载只信自己的制品。严防训练-服务偏差预处理变更必须重导。交模型卡写清用途与局限。下一课端到端项目把 6674 收成可复现交付包。12. 练笔任选第 68 或 70 课的最佳管道导出为models/xxx.joblib。写最小 FastAPI/health/predict用 curl 成功一次、失败一次缺字段。故意制造 skew线上跳过 scaler对比预测差异写 3 行观察。填完整页模型卡可MODEL_CARD.md。README 写清如何安装、启动、示例请求、依赖版本。加分文本分类管道73 课同样服务化请求体为{text: ...}。13. 引用与参考引用了用途链接scikit-learn Model persistence保存/加载与安全说明https://scikit-learn.org/stable/model_persistence.htmlscikit-learn Pipelines整链导出https://scikit-learn.org/stable/modules/compose.htmlscikit-learn Common pitfalls预处理不一致https://scikit-learn.org/stable/common_pitfalls.htmlFastAPI 文档最小 Web APIhttps://fastapi.tiangolo.com/UvicornASGI 服务器https://www.uvicorn.org/Pydantic请求体校验https://docs.pydantic.dev/Mitchell et al. Model Cards (2019)模型说明书范式https://arxiv.org/abs/1810.03993ONNX可移植推理点名https://onnx.ai/欧盟 AI Act 入口治理背景一句即可https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai14. 与前后课方向内容前6871 训练评估73 文本管道阶段 C FastAPI本课joblib、/predict、模型卡、skew、pickle 安全后75 综合实战可选挂上推理接口与完整交付清单课堂讨论题可分组 10 分钟如果老板只要一个数字「准确率」你怎么用两分钟说服他看混淆矩阵数据只有 80 条你还上机器学习吗为什么你更愿意维护100 条清晰业务规则还是一个 90 分但没人能解释的模型把讨论结论写在笔记里——比多抄 50 行 API 更接近真实工作。自我检测不看稿口头答我能不看笔记讲清本课最重要的一张图在说什么我能指出一段「错误代码」错在哪我能举一个生活例子对应本课任务类型我知道下一课大概要解决什么痛点全部打勾再进入下一课效率更高。附录给初学的 FAQ本课补充下面这些问题是第一次学本课内容时最容易卡住的地方。用白话再过一遍。Q1我是不是一定要背公式不必先背公式。你要先会讲故事输入是什么、输出是什么、模型在怕什么过拟合、泄漏、指标骗人。公式是为了精确表达故事故事通了公式只是翻译。Q2代码跑不通怎么办按这个顺序排查虚拟环境激活了吗提示符前有没有 .venv包装了吗python -c “import sklearn; print(sklearn.version)”报错最后一行是什么把Error 类型 最后一行记下来再搜路径、文件名、中文引号有没有混用仍不行换一个最小例子本课最前面的 10 行代码确认环境 OKQ3我和同学分数差很多是不是我很差不一定。可能是andom_state 不同、数据划分不同、指标不同、甚至泄漏导致虚高。先对齐评估协议再比分数。Q4这课和「人工智能 / ChatGPT」是什么关系ChatGPT 一类是很大的深度学习系统偏语言与对话。本课练的是表格/经典机器学习基本功分类、回归、评估、Pipeline。基本功会了你以后学深度学习或用大模型 API才知道自己在解决什么问题、如何公平比较。Q5我需要买 GPU 吗本阶段不需要。sklearn 在普通笔记本 CPU 上就够。GPU 主要是深度学习训练时才刚需。Q6作业要做到什么程度算合格最低标准能用自己的话讲清本课 3 个核心概念能跑通本课主线代码并看懂输出含义能指出至少 2 个常见坑小练笔完成一半以上鼓励全做Q7我想继续深入课外看什么优先官方文档对应章节见文末引用其次 ISLR 中文/英文入门章节。别一上来就啃很厚的证明书——容易劝退。本课概念速记卡可抄笔记本我用大白话怎么说对应术语用历史数据猜新情况机器学习 / 预测拿来学的那部分数据训练集假装是新客户的那部分测试集背答案背过头过拟合笨到学不会欠拟合偷看了考题数据泄漏步骤焊成一条龙Pipeline建议学习节奏时间做什么第 1 小时只读例子与图不写代码第 2 小时抄跑主线代码改一个参数观察变化第 3 小时做小练笔 写 5 句笔记之后隔一天不看稿子复述一遍记住初学阶段「讲清楚 跑得通 知道坑」比「一次记住全部 API」重要得多。