📘 Day 23 实战作业:机器学习工程化 ------ Pipeline 管道流
1. 作业综述
核心目标 :
本作业旨在将机器学习工作流从"手动脚本"升级为"工业级管道"。我们将利用 sklearn.pipeline 模块,将数据清洗(缺失值填充)、特征处理(编码、标准化)与模型训练封装为一个原子对象。
为什么要使用 Pipeline?
在之前的作业中,我们在数据预处理时往往需要手动执行 fillna -> map/get_dummies -> StandardScaler。这种分散的操作在实际工程中存在巨大隐患:
- 数据泄露 (Data Leakage):如果在整个数据集上做标准化再切分,测试集的信息就泄露到了训练集中。Pipeline 能确保预处理参数(如均值、方差)只从训练集学习,并正确应用到测试集。
- 代码复用性差:针对新数据,需要重新写一遍清洗逻辑。Pipeline 可以直接保存并复用。
- 调参困难:无法在一个搜索空间内同时调整预处理参数(如 PCA 维度)和模型参数(如 树的数量)。
本作业关键技术点:
Pipeline: 串联处理步骤。ColumnTransformer: 针对不同列(数值/分类)应用不同的处理逻辑。SimpleImputer,OrdinalEncoder,OneHotEncoder: 标准预处理组件的使用。
步骤 1:数据加载与预处理规划
任务描述:
- 加载信贷数据集。
- 不进行任何手动预处理 (如手动 fillna 或 map),直接划分 X X X 和 y y y。
- 识别出哪些列是数值型、哪些是有序分类、哪些是无序分类,为构建管道做准备。
python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
# 1. 加载数据
data = pd.read_csv(r'F:\Training_camp\test\Credit_Data.csv')
# 2. 分离特征和标签 (直接使用原始数据)
y = data['Credit Default']
X = data.drop(['Credit Default'], axis=1)
# 3. 划分数据集 (在预处理之前划分!这是Pipeline的一大优势)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"数据划分完成!训练集: {X_train.shape}, 测试集: {X_test.shape}")
# 4. 自动识别列类型 (为 Pipeline 做准备)
# 连续数值特征
numeric_features = X.select_dtypes(exclude=['object']).columns.tolist()
# 离散分类特征 (需要进一步区分有序和无序,这里先统称为分类)
categorical_features = X.select_dtypes(include=['object']).columns.tolist()
print(f"数值特征: {len(numeric_features)} 个")
print(f"分类特征: {len(categorical_features)} 个")
数据划分完成!训练集: (6000, 17), 测试集: (1500, 17)
数值特征: 13 个
分类特征: 4 个
步骤 2:构建处理组件 (Transformers)
核心概念 :
Pipeline 就像一条汽车组装线,我们需要先定义好每个环节的"机械臂":
- 有序特征 :填充缺失值 ->
OrdinalEncoder(编码为 1, 2, 3) - 无序特征 :填充缺失值 ->
OneHotEncoder(独热编码) - 连续特征 :填充缺失值 ->
StandardScaler(标准化)
任务 :
使用 Pipeline 类分别封装这三类特征的处理逻辑。
python
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.compose import ColumnTransformer
# --- 1. 定义 有序特征 处理流 ---
# 必须手动指定顺序,否则模型不知道大小关系
ordinal_cols = ['Home Ownership', 'Years in current job', 'Term']
ordinal_categories = [
['Own Home', 'Rent', 'Have Mortgage', 'Home Mortgage'],
['< 1 year', '1 year', '2 years', '3 years', '4 years', '5 years', '6 years', '7 years', '8 years', '9 years', '10+ years'],
['Short Term', 'Long Term']
]
ordinal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 众数填补
('encoder', OrdinalEncoder(categories=ordinal_categories, handle_unknown='use_encoded_value', unknown_value=-1))
])
# --- 2. 定义 无序特征 处理流 ---
nominal_cols = ['Purpose']
nominal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('encoder', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) # 独热编码
])
# --- 3. 定义 连续特征 处理流 ---
# 排除掉上面用过的列,剩下的就是连续特征
continuous_cols = [col for col in X.columns if col not in ordinal_cols + nominal_cols]
continuous_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')), # 中位数填补
('scaler', StandardScaler()) # 标准化 (这对逻辑回归/SVM等很重要,对树模型影响较小但无害)
])
# --- 4. 组装成一个大的处理器 (ColumnTransformer) ---
# 它的作用是:把数据按列劈开,分别扔给上面定义好的三个处理器,最后再拼起来
preprocessor = ColumnTransformer(
transformers=[
('num', continuous_transformer, continuous_cols),
('ord', ordinal_transformer, ordinal_cols),
('nom', nominal_transformer, nominal_cols)
],
remainder='passthrough' # 其他未指定的列保持原样
)
print("✅ 预处理组件构建完成!")
✅ 预处理组件构建完成!
步骤 3:组装总流水线与模型训练
任务描述 :
将 preprocessor (数据处理) 和 classifier (分类模型) 串联成最终的 Pipeline 对象。
之后,我们只需要对这个 Pipeline 对象调用 .fit(),它就会自动完成所有清洗、转换和训练工作。
python
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
import time
# 1. 组装最终管道
# 逻辑:原始数据 -> preprocessor (清洗+编码+标准) -> classifier (随机森林)
full_pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('model', RandomForestClassifier(random_state=42))
])
# 2. 训练 (Fit)
print("🚀 开始训练 Pipeline...")
start_time = time.time()
# 注意:这里直接传入原始的 X_train,管道会自动处理一切
full_pipeline.fit(X_train, y_train)
# 3. 预测 (Predict)
# 注意:直接传入原始的 X_test,管道会自动 transform 之后再预测
y_pred = full_pipeline.predict(X_test)
end_time = time.time()
print(f"✅ 训练完成!耗时: {end_time - start_time:.4f} 秒")
# 4. 评估
print("\n--- 分类报告 ---")
print(classification_report(y_test, y_pred))
🚀 开始训练 Pipeline...
✅ 训练完成!耗时: 1.5297 秒
--- 分类报告 ---
precision recall f1-score support
0 0.76 0.97 0.85 1059
1 0.77 0.28 0.41 441
accuracy 0.76 1500
macro avg 0.77 0.62 0.63 1500
weighted avg 0.77 0.76 0.72 1500
🎓 Day 23 总结:工程化的力量
通过今天的实战,我们将原本分散、混乱的数据处理代码,重构为了一个优雅的 Pipeline 对象。
Pipeline 的核心优势:
- 封装性 (Encapsulation) :
full_pipeline.fit(X, y)一行代码搞定所有,不用担心漏掉哪一步预处理。 - 安全性 (Safety) :自动在训练集上计算均值/方差,并应用到测试集,严防数据泄露。
- 可移植性 (Portability) :这个
full_pipeline对象可以直接保存 (Pickle),以后来了新数据,加载出来直接.predict()即可,无需重写清洗逻辑。
这是从"写作业"迈向"写项目"的重要一步!