Scikit-learn 工作流
fit/predict、调参、Pipeline。
Scikit-learn 工作流
这一课你将能做到
- 用 ColumnTransformer 对数值列和类别列分别做不同的预处理,缺失值不再报错
- 用 Pipeline 把预处理和模型串成一条流水线,交叉验证时自动防数据泄漏
- 用 GridSearchCV 按「步骤名__参数名」调参,一行代码搜出最优超参数
- 用 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))