DAY 26 机器学习流水线

在 Scikit-learn(Python 机器学习核心库)中,转化器(Transformer)估计器(Estimator) 是构建机器学习流程的两大核心抽象类,分别承担数据转换模型学习 / 预测的核心职责,也是实现数据预处理、特征工程与模型训练标准化的基础。

一、估计器(Estimator)

估计器是 Scikit-learn 中所有需要拟合数据以学习模式的类的基类,覆盖了几乎所有机器学习模型和部分数据处理类,是整个库的核心抽象之一。

1. 核心定义

通过拟合(fit) 数据来学习其中的统计规律、模型参数或数据分布,进而实现预测(predict) 、**评估(score)**等功能,是监督学习、无监督学习模型的基础。

2. 核心方法

  • fit(X, y=None) :接收特征矩阵X和标签y(监督学习需传入y,无监督学习可选),学习数据中的模式并更新类内参数(如线性回归的系数、KMeans 的聚类中心)。
  • predict(X) :基于拟合后的参数,对新的特征矩阵X输出预测结果(分类为标签、回归为数值、聚类为类别索引)。
  • score(X, y):评估模型在测试集上的性能,返回量化指标(如分类的准确率、回归的\(R^2\)值)。

|------------|------------------------|-----------|
| 估计器类型 | 具体类名 | 应用场景 |
| 监督学习 - 回归 | LinearRegression | 线性回归预测连续值 |
| 监督学习 - 分类 | RandomForestClassifier | 随机森林分类 |
| 无监督学习 - 聚类 | KMeans | 基于距离的聚类 |
| 模型选择 | GridSearchCV | 超参数网格搜索 |

二、转化器(Transformer)

转化器是Estimator 的子类,专门用于数据转换 / 预处理,是特征工程的核心工具,其核心逻辑是 "先拟合数据获取统计信息,再用该信息转换数据"。
1. 核心定义

通过拟合(fit)学习数据的转换所需统计量(如均值、方差、主成分),再通过转换(transform)将这些规则应用到数据上,实现特征缩放、降维、编码等操作。
2. 核心方法

fit(X, y=None):学习数据的转换规则(如StandardScaler计算均值和标准差,PCA学习主成分),仅更新内部参数,不改变输入数据。

transform(X):用fit学到的规则对特征矩阵X进行转换,输出转换后的新特征矩阵。

fit_transform(X, y=None):Scikit-learn 的优化方法,一次性完成 "拟合 + 转换",避免重复计算,效率更高(多数转化器会重写此方法以优化性能)。

|--------|-----------------|----------------|
| 转化器类型 | 具体类名 | 转换功能 |
| 特征缩放 | StandardScaler | 标准化(均值 0,方差 1) |
| 特征降维 | PCA | 主成分分析降维 |
| 类别特征编码 | OneHotEncoder | 独热编码 |
| 文本特征提取 | CountVectorizer | 词频统计向量化 |

三、两者的关系与核心区别

1. 关系
  • 转化器是估计器的子类 :所有转化器都实现了 Estimator 的fit方法,同时扩展了transform方法。
  • 估计器是更通用的抽象:包含模型类 (无transform,有predict)和转化器类 (有transform,无predict)两类。

2.核心区别

|------|---------------------|-------------------------------|
| 维度 | 估计器 | 转化器 |
| 核心功能 | 学习数据模式,用于预测 / 评估 | 转换数据,用于预处理 / 特征工程 |
| 关键方法 | fit + predict/score | fit + transform/fit_transform |
| 输出结果 | 预测标签 / 数值 / 评估分数 | 转换后的特征矩阵 |
| 应用阶段 | 模型训练与预测阶段 | 数据预处理与特征工程阶段 |

在 Scikit-learn 中,Pipeline(管道工程) 是串联多个数据处理步骤(转化器)和最终模型(估计器)的核心工具,能将机器学习流程标准化、自动化,是解决 "数据预处理→特征工程→模型训练→预测" 全流程的最佳实践。

一、为什么需要 Pipeline?

手动分步处理数据(比如先标准化训练集、再标准化测试集、再训练模型)容易出现数据泄露(如用测试集的统计量做标准化)、代码冗余、步骤混乱等问题。Pipeline 的核心价值:

  1. 避免数据泄露:所有转化器仅从训练集学习统计规则(如均值 / 方差),再统一应用到训练 / 测试集;
  2. 简化流程 :将 "预处理 + 模型" 封装为一个整体,只需调用fit/predict即可完成全流程;
  3. 便于调优:可对管道内所有步骤的超参数统一网格搜索;
  4. 高可复现性:固定流程,避免手动操作的随机性。

二、Pipeline 核心原理

  • Pipeline 接收一个 **(名称,操作对象)** 的列表,按顺序执行:
    • 前 N-1 个必须是转化器 (有fit/transform方法);
    • 最后 1 个必须是估计器 (有fit/predict方法);
  • 调用fit时:依次对每个转化器执行fit_transform,将结果传递给下一个步骤,最后对估计器执行fit
  • 调用predict时:依次对每个转化器执行transform,将结果传递给估计器执行predict

三、进阶用法:处理多类型特征(ColumnTransformer)

实际场景中,数据常包含数值特征和分类特征,需分别处理(如数值特征标准化、分类特征独热编码)。此时需结合ColumnTransformer与 Pipeline:

核心逻辑:

ColumnTransformer:按列拆分特征,对不同列应用不同转化器,最后合并结果;

嵌套进 Pipeline:先分栏处理特征,再传入模型。

四、Pipeline + 网格搜索:超参数调优

Pipeline 可与GridSearchCV/RandomizedSearchCV结合,对所有步骤的超参数统一调优(无需手动拆分步骤)

Pipeline 是 Scikit-learn 中标准化机器学习流程的 "标配工具":
简单场景:直接串联转化器 + 估计器;
复杂场景:用ColumnTransformer处理多类型特征,再嵌套进 Pipeline;
调优场景:结合网格搜索统一优化全流程超参数。

python 复制代码
# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 读取数据
data = pd.read_csv('D:\Study\PythonStudy\python60-days-challenge-master\heart.csv')
print(f"数据形状: {data.shape}")
print(f"\n前5行数据:")
data.head()

# 读取数据
data = pd.read_csv("D:\Study\PythonStudy\python60-days-challenge-master\heart.csv")
# 备份原始数据(用于后续列名对比)
data2 = data.copy()

# 数据预处理
# 识别离散特征(基于心脏病数据业务逻辑:分类编码型特征)
# 英文特征名映射:['sex', 'cp', 'fbs', 'restecg', 'exang', 'slope', 'ca', 'thal']
discrete_features = ['性别', '胸痛类型', '空腹血糖>120mg/dl', '静息心电图结果', '运动诱发心绞痛', 'ST段斜率', '染色血管数', '地中海贫血']

# 先将原始英文列名改为中文
column_mapping = {
    'sex': '性别',
    'cp': '胸痛类型',
    'fbs': '空腹血糖>120mg/dl',
    'restecg': '静息心电图结果',
    'exang': '运动诱发心绞痛',
    'slope': 'ST段斜率',
    'ca': '染色血管数',
    'thal': '地中海贫血',
    'age': '年龄',
    'trestbps': '静息血压',
    'chol': '血清胆固醇',
    'thalach': '最大心率',
    'oldpeak': 'ST段压低'
}
data.rename(columns=column_mapping, inplace=True)

# 独热编码
# 胸痛类型(多分类无顺序关系)独热编码
data = pd.get_dummies(data, columns=['胸痛类型'], prefix='胸痛类型')
# ST段斜率独热编码
data = pd.get_dummies(data, columns=['ST段斜率'], prefix='ST段斜率')
# 地中海贫血独热编码
data = pd.get_dummies(data, columns=['地中海贫血'], prefix='地中海贫血')
# 染色血管数独热编码
data = pd.get_dummies(data, columns=['染色血管数'], prefix='染色血管数')

# 处理独热编码生成的新列,转为int类型
# 对比原始数据(未独热编码前)的列名,找出新增列
original_columns = data2.rename(columns=column_mapping).columns.tolist()
list_final = [col for col in data.columns if col not in original_columns]
for col in list_final:
    data[col] = data[col].astype(int)

# 连续特征用众数补全(生理指标)
# 英文特征名映射:['age', 'trestbps', 'chol', 'thalach', 'oldpeak']
continuous_features = ['年龄', '静息血压', '血清胆固醇', '最大心率', 'ST段压低']
for feature in continuous_features:
    mode_value = data[feature].mode()[0]
    data[feature].fillna(mode_value, inplace=True)

print("✅ 数据预处理完成!")
print(f"最终特征数量: {data.shape[1]}")
print(f"处理后数据前5行:")
print(data.head())

from sklearn.model_selection import train_test_split
X = data.drop(['target'], axis=1)  # 特征,axis=1表示按列删除
y = data['target'] # 标签

from sklearn.model_selection import train_test_split
X = data.drop(['target'], axis=1)  # 特征,axis=1表示按列删除
y = data['target'] # 标签
# 按照8:2划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 80%训练集,20%测试集

from sklearn.ensemble import RandomForestClassifier #随机森林分类器

from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标
from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵
import warnings #用于忽略警告信息
warnings.filterwarnings("ignore") # 忽略所有警告信息
# --- 1. 默认参数的随机森林 ---
# 评估基准模型,这里确实不需要验证集
print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---")
import time # 这里介绍一个新的库,time库,主要用于时间相关的操作,因为调参需要很长时间,记录下会帮助后人知道大概的时长
start_time = time.time() # 记录开始时间
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train) # 在训练集上训练
rf_pred = rf_model.predict(X_test) # 在测试集上预测
end_time = time.time() # 记录结束时间

print(f"训练与预测耗时: {end_time - start_time:.4f} 秒")
print("\n默认随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))
python 复制代码
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier

# 1. 读入数据
data = pd.read_csv('D:\Study\PythonStudy\python60-days-challenge-master\heart.csv')
column_mapping = {
    'sex': '性别',
    'cp': '胸痛类型',
    'fbs': '空腹血糖>120mg/dl',
    'restecg': '静息心电图结果',
    'exang': '运动诱发心绞痛',
    'slope': 'ST段斜率',
    'ca': '染色血管数',
    'thal': '地中海贫血',
    'age': '年龄',
    'trestbps': '静息血压',
    'chol': '血清胆固醇',
    'thalach': '最大心率',
    'oldpeak': 'ST段压低'
}
data.rename(columns=column_mapping, inplace=True)


# 2. 划分特征组
ordinal_features = ['ST段斜率']                    # 有序特征
ordinal_categories = [['下坡', '平', '上坡']]      # 实际顺序
nominal_features = ['胸痛类型', '地中海贫血', '染色血管数']    # 多类别无序
binary_features = ['性别', '空腹血糖>120mg/dl', '静息心电图结果', '运动诱发心绞痛']  # 二分类
numeric_features = ['年龄', '静息血压', '血清胆固醇', '最大心率', 'ST段压低']        # 连续

# 3. 数据集划分
X = data.drop(['target'], axis=1)
y = data['target']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# === 在这里添加关键的类型转换步骤 ===
# 将所有类别型特征(有序、无序、二分类)强制转换为字符串类型
categorical_features = ordinal_features + nominal_features + binary_features
for col in categorical_features:
    # 确保列存在,以防万一
    if col in X_train.columns:
        X_train[col] = X_train[col].astype(str)
    if col in X_test.columns:
        X_test[col] = X_test[col].astype(str)
# ====================================

# 4. 构造各类预处理
ordinal_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')), # 在这里,imputer会处理字符串类型的缺失值
    ('ordinal', OrdinalEncoder(categories=ordinal_categories, handle_unknown='use_encoded_value', unknown_value=-1))
])

nominal_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

binary_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(drop='if_binary', handle_unknown='ignore', sparse_output=False))
])

numeric_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='mean')),
    ('scaler', StandardScaler())
])

# 5. 总的预处理组合
preprocessor = ColumnTransformer([
    ('ord', ordinal_transformer, ordinal_features),
    ('nom', nominal_transformer, nominal_features),
    ('bin', binary_transformer, binary_features),
    ('num', numeric_transformer, numeric_features)
])

# 6. 构造总的机器学习Pipeline
clf = Pipeline([
    ('pre', preprocessor),
    ('model', RandomForestClassifier(random_state=42))
])

# 7. 模型训练与评估
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
print("准确率:", accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))
print("混淆矩阵:\n", confusion_matrix(y_test, y_pred))

@浙大疏锦行

相关推荐
找了一圈尾巴39 分钟前
Python 学习-深入理解 Python 进程、线程与协程(上)
python·学习·并发
276695829241 分钟前
雷池waf 逆向
java·开发语言·前端·python·wasm·waf·雷池waf
Keep__Fighting1 小时前
【机器学习:线性回归】
人工智能·python·算法·机器学习·支持向量机·线性回归·scikit-learn
MadPrinter1 小时前
FindQC 实战 (一):基于 SerpApi 的电商高质量图片自动化筛选算法初探
运维·python·算法·自动化
C++业余爱好者1 小时前
Java Stream API介绍
java·windows·python
程序员三藏1 小时前
如何编写一份规整完美的测试报告?
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
w***74401 小时前
使用python进行PostgreSQL 数据库连接
数据库·python·postgresql
郝学胜-神的一滴1 小时前
Python object、type和class之间的关系
开发语言·python·程序人生
Hcoco_me1 小时前
大模型面试题11:余弦相似度 & 牛顿迭代法
人工智能·python·决策树·机器学习·计算机视觉