CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
机器学习入门/课时/Scikit-learn 工作流
RawBlame
课时目录12 节

机器学习入门

  1. 01机器学习是什么
  2. 02数学基础
  3. 03Python 与数据栈
  4. 04数据预处理
  5. 05监督学习
  6. 06无监督学习
  7. 07模型评估
  8. 08Scikit-learn 工作流
  9. 09深度学习基础
  10. 10NLP 与 Transformer
  11. 11TensorFlow 与 PyTorch
  12. 12综合练习

Scikit-learn 工作流

fit/predict、调参、Pipeline。

35 分钟

Scikit-learn 工作流

这一课你将能做到

  1. 用 ColumnTransformer 对数值列和类别列分别做不同的预处理,缺失值不再报错
  2. 用 Pipeline 把预处理和模型串成一条流水线,交叉验证时自动防数据泄漏
  3. 用 GridSearchCV 按「步骤名__参数名」调参,一行代码搜出最优超参数
  4. 用 joblib 把整条流水线保存成文件,推理端加载后直接吃原始数据

为什么要学

前几课的代码有个隐患:标准化、填充缺失值这些预处理是在训练集上手动做的,推理时你得凭记忆复刻同一套操作。特征口径差一点,线上效果就掉一截。

Scikit-learn 的答案是 Pipeline:把所有依赖数据的变换和模型绑成一个对象,fit 一起学、predict 一起算、保存一起存。这不是语法糖,而是把「训练与推理口径一致」从纪律变成结构。

先建立图像

原始 DataFrame                Pipeline 内部
┌──────────────────────┐      ┌─────────────────────────────┐
│ accuracy  attempts   │      │ pre(ColumnTransformer)      │
│ 0.62      3.5        │ ───► │  num: 填中位数 → 标准化      │
│ NaN       2.1        │      │  cat: OneHot(未知类别全0)    │
│ 0.71      4.0 topic  │      └──────────────┬──────────────┘
└──────────────────────┘                     ▼
                                 clf(LogisticRegression) → mastery

跟着做

步骤 1:造一份「脏」数据

import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 500
accuracy = rng.uniform(0.1, 0.95, n).round(3)
attempts = (rng.uniform(1, 8, n) + (0.6 - accuracy) * 5).round(1)
topic    = rng.choice(["栈与队列", "动态规划", "图论", "字符串"], n)
mastery  = (accuracy > 0.55) & (attempts > 3)      # 标签先算好
attempts[rng.choice(n, 40, replace=False)] = np.nan # 再造 8% 缺失

df = pd.DataFrame({"accuracy": accuracy, "attempts": attempts,
                   "topic": topic, "mastery": mastery})
X, y = df.drop(columns="mastery"), df["mastery"]
print(X.isna().sum())   # attempts 有 40 个缺失

数值列有缺失、类别列是中文——手动预处理要写一堆判断,Pipeline 一步到位。

步骤 2:ColumnTransformer 分列处理

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

pre = ColumnTransformer([
    ("num", Pipeline([("imp", SimpleImputer(strategy="median")),
                      ("sc",  StandardScaler())]), ["accuracy", "attempts"]),
    ("cat", OneHotEncoder(handle_unknown="ignore"), ["topic"]),
])

handle_unknown="ignore":线上来了训练时没见过的题目分类,不报错,编码成全 0 向量。

步骤 3:Pipeline + 交叉验证

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

pipe = Pipeline([("pre", pre), ("clf", LogisticRegression(max_iter=1000))])
print(cross_val_score(pipe, X, y, cv=5, scoring="roc_auc").mean().round(3))

关键:cross_val_score 会对整条 pipe 在每一折重新 fit——填充用的中位数、标准化用的均值都只来自当折训练部分,验证折的信息绝无泄漏。

步骤 4:GridSearchCV 调参

from sklearn.model_selection import GridSearchCV

param_grid = {
    "clf__C": [0.01, 0.1, 1, 10],                    # 正则强度
    "pre__num__imp__strategy": ["mean", "median"],    # 连预处理参数一起调
}
gs = GridSearchCV(pipe, param_grid, cv=5, scoring="roc_auc", n_jobs=-1)
gs.fit(X, y)
print(gs.best_params_, gs.best_score_.round(3))

双下划线是「往下钻」:clf__C 是 clf 步骤的 C 参数;嵌套子 Pipeline 继续点下去就是 pre__num__imp__strategy。

步骤 5:joblib 保存,推理端直接用

import joblib

joblib.dump(gs.best_estimator_, "mastery_pipe.joblib")

loaded = joblib.load("mastery_pipe.joblib")
new = pd.DataFrame([{"accuracy": 0.6, "attempts": np.nan, "topic": "图论"}])
print(loaded.predict_proba(new).round(3))   # 原始数据进,概率出

注意:加载的是整条流水线,推理端不需要、也不应该复刻任何预处理代码。

讲清楚要点

  • Pipeline 防泄漏的原理:所有「从数据学统计量」的变换(均值、中位数、类别词表、PCA 主轴)都必须只见过训练集。绑进 Pipeline 后每折重 fit,结构上杜绝手工顺序错误。
  • 命名规则是契约:步骤名__参数名 贯穿 GridSearchCV、learning_curve 等所有工具,写错直接报错,不会静默失效。
  • handle_unknown="ignore" 是上线标配:真实系统总会出现新类别(新题目分类、新渠道),默认 strict 模式会让服务崩掉。
  • joblib 是 sklearn 生态标配:对大 numpy 数组比 pickle 更高效且支持压缩;注意加载端 sklearn 版本需与保存端兼容。

你可能会懵的地方

Q1:Pipeline 到底防的是什么泄漏? 任何 fit 过的数据统计(均值、词表、主成分)都携带信息。若先在整个数据上 fit scaler 再交叉验证,验证折的分布信息就混进了训练——分数虚高,上线就掉。

Q2:双下划线参数看不懂怎么办? 用 pipe.named_steps 或 pipe.get_params() 打印全部参数路径,照着写准没错。名字就是你建 Pipeline/ColumnTransformer 时起的步骤名。

Q3:网格太大搜不动? 换 RandomizedSearchCV:从参数分布里随机采样,通常 60 次迭代就能逼近最优。参数空间超过百级组合时首选它。

Q4:joblib 和 pickle 选哪个? 都行,但 joblib 对 numpy 数组更高效、支持 compress=3 压缩,是 sklearn 官方推荐。别用 pickle 存新版 sklearn 模型再拿旧版加载,会不兼容。

Q5:所有变换都必须进 Pipeline 吗? 凡是「依赖数据统计」的都要进;纯逻辑映射(时间戳转小时段、字符串截断)可以放外面,但要保证训练和推理执行同一份代码。

错法对照

错法后果正确做法
全量 fit scaler 再交叉验证验证信息泄漏,分数虚高pre 放进 Pipeline 每折重 fit
类别列先 LabelEncoder 再喂模型编码引入假序,线上新类别崩OneHotEncoder(handle_unknown="ignore")
手写网格循环 + 手动切分代码冗长且易泄漏GridSearchCV 一行完成
手工拼新样本特征训练推理口径漂移保存整条 pipeline,输入原始列
只 dump 模型参数不存流水线复现困难joblib.dump(best_estimator_)

卡点急救

  1. 报错 could not convert string to float → 类别列没放进 ColumnTransformer,模型收到了字符串。
  2. 报错 X has N features, but ... → 新样本列名或顺序与训练不一致;用与训练同结构的 DataFrame 传入。
  3. GridSearchCV 太慢 → n_jobs=-1 并行;先粗网格锁定量级,再细网格收敛。

面试常问

Q1:Pipeline 如何防止数据泄漏? 把预处理器和模型绑成单一估计器,交叉验证/学习曲线时每折整体重 fit,所有统计量只来自当折训练数据,结构上不可能偷看验证集。

Q2:GridSearchCV 与 RandomizedSearchCV 怎么选? 小空间(几十组合)用 Grid 穷举;大空间用 Random 采样,同样的算力探索面更广。两者都支持 n_jobs=-1 并行。

Q3:模型上线为什么保存 pipeline 而不只是模型? 推理端输入是原始数据,预处理必须和训练完全一致。保存整条流水线才能保证口径唯一,避免「训练一套、线上一套」的特征漂移。

自测三问

  1. clf__C 里的 clf 是什么?(Pipeline 中你给模型步骤起的名字)
  2. 交叉验证时 SimpleImputer 的中位数来自哪?(当前折的训练部分)
  3. OneHotEncoder 遇到没见过的类别且设了 ignore,结果是?(该类别对应位置全 0,不报错)

小结一句话

Pipeline 的价值不是少写几行代码,而是把「训练与推理口径一致」和「无泄漏评估」从人工纪律变成结构保证。

延伸

  • 官方文档:scikit-learn Pipeline 与复合估计器
  • 下一课预告:当特征交互复杂到线性模型学不动,就该深度学习出场了——用 PyTorch 手写第一个神经网络。
加载本节练习…
← 上一课模型评估下一课 →深度学习基础