一、管道 pipeline 概述
pipeline在机器学习领域可以翻译为“管道”,也可以翻译为“流水线”,是机器学习中一个重要的概念。
在机器学习中,通常会按照一定的顺序对数据进行预处理、特征提取、模型训练和模型评估等步骤,以实现机器学习模型的训练和评估。为了方便管理这些步骤,我们可以使用pipeline来构建一个完整的机器学习流水线。
pipeline是一个用于组合多个估计器(estimator)的 estimator,它实现了一个流水线,其中每个估计器都按照一定的顺序执行。在pipeline中,每个估计器都实现了fit和transform方法,fit方法用于训练模型,transform方法用于对数据进行预处理和特征提取。
在此之前我们先介绍下 转换器(transformer)和估计器(estimator)的概念。
1、转换器(transformer)
转换器(transformer)是一个用于对数据进行预处理和特征提取的 estimator,它实现一个 transform 方法,用于对数据进行预处理和特征提取。
转换器通常用于对数据进行预处理,例如对数据进行归一化、标准化、缺失值填充等。转换器也可以用于对数据进行特征提取,例如对数据进行特征选择、特征组合等。转换器的特点是无状态的,即它们不会存储任何关于数据的状态信息(指的是不存储内参)。转换器仅根据输入数据学习转换规则(比如函数规律、外参),并将其应用于新的数据。因此,转换器可以在训练集上学习转换规则,并在训练集之外的新数据上应用这些规则。
常见的转换器包括数据缩放器(如StandardScaler、MinMaxScaler)、特征选择器(如SelectKBest、PCA)、特征提取器(如CountVectorizer、TF-IDFVectorizer)等。
之前我们都是说对xxxx类进行实例化,现在可以换一个更加准确的说法,如下:
# 导入StandardScaler转换器
from sklearn.preprocessing import StandardScaler
# 初始化转换器
scaler = StandardScaler()
# 1. 学习训练数据的缩放规则(计算均值和标准差),本身不存储数据
scaler.fit(X_train)
# 2. 应用规则到训练数据和测试数据
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 也可以使用fit_transform一步完成
# X_train_scaled = scaler.fit_transform(X_train)
2、估计器(estimator)
估计器(Estimator)是实现机器学习算法的对象或类。它用于拟合(fit)数据并进行预测(predict)。估计器是机器学习模型的基本组成部分,用于从数据中学习模式、进行预测和进行模型评估。
估计器的主要方法是 fit 和 predict 。fit 方法用于根据输入数据学习模型的参数和规律,而predict 方法用于对新的未标记样本进行预测。估计器的特点是有状态的,即它们在训练过程中存储了关于数据的状态信息,以便在预测阶段使用。估计器通过学习训练数据中的模式和规律来进行预测。因此,估计器需要在训练集上进行训练,并使用训练得到的模型参数对新数据进行预测。
常见的估计器包括分类器(classifier)、回归器(regresser)、聚类器(clusterer)。
from sklearn.linear_model import LinearRegression
# 创建一个回归器
model = LinearRegression()
# 在训练集上训练模型
model.fit(X_train_scaled, y_train)
# 对测试集进行预测
y_pred = model.predict(X_test_scaled)
3、管道(pipeline)
了解了转换器和估计器,所以可以理解为在机器学习是由转换器(Transformer)和估计器(Estimator)按照一定顺序组合在一起的来完成了整个流程。
机器学习的管道(Pipeline)机制通过将多个转换器和估计器按顺序连接在一起,可以构建一个完整的数据处理和模型训练流程。在管道机制中,可以使用 Pipeline类 来组织和连接不同的转换器和估计器。Pipeline类提供了一种简单的方式来定义和管理机器学习任务的流程。
管道机制是按照封装顺序依次执行的一种机制,在机器学习算法中得以应用的根源在于,参数集在新数据集(比如测试集)上的重复使用。且代码看上去更加简洁明确。这也意味着,很多个不同的数据集,只要处理成管道的输入形式,后续的代码就可以复用。(这里为我们未来的python文件拆分做铺垫),也就是把很多个类和函数操作写进一个新的pipeline中。
这符合编程中的一个非常经典的思想:don't repeat yourself。(dry原则),也叫做封装思想,我们之前提到过类似的思想的应用: 函数、类,现在我们来说管道。
Pipeline最大的价值和核心应用场景之一,就是与交叉验证和网格搜索等结合使用,来:
防止数据泄露: 这是在使用交叉验证时,Pipeline自动完成预处理并在每个折叠内独立fit / transform的关键优势。
简化超参数调优: 可以方便地同时调优预处理步骤和模型的参数。
下面我们将对我们的信贷数据集进行管道工程,重构整个代码。之所以提到管道,是因为后续你在阅读一些经典的代码的时候,尤其是官方文档,非常喜欢用管道来构建代码,甚至深度学习中也有类似的代码,初学者往往看起来很吃力。
二、代码演示
1、没有 pipeline 的代码
# 先运行之前预处理好的代码
import pandas as pd #用于数据处理和分析,可处理表格数据。
import numpy as np #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt #用于绘制各种类型的图表
import seaborn as sns #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
import warnings
warnings.filterwarnings("ignore")
# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
data = pd.read_csv('data.csv') #读取数据
# 先筛选字符串变量
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
# Home Ownership 标签编码
home_ownership_mapping = {
'Own Home': 1,
'Rent': 2,
'Have Mortgage': 3,
'Home Mortgage': 4
}
data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)
# Years in current job 标签编码
years_in_job_mapping = {
'< 1 year': 1,
'1 year': 2,
'2 years': 3,
'3 years': 4,
'4 years': 5,
'5 years': 6,
'6 years': 7,
'7 years': 8,
'8 years': 9,
'9 years': 10,
'10+ years': 11
}
data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)
# Purpose 独热编码,记得需要将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])
data2 = pd.read_csv("data.csv") # 重新读取数据,用来做列名对比
list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名
for i in data.columns:
if i not in data2.columns:
list_final.append(i) # 这里打印出来的就是独热编码后的特征名
for i in list_final:
data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名
# Term 0 - 1 映射
term_mapping = {
'Short Term': 0,
'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist() #把筛选出来的列名转换成列表
# 连续特征用中位数补全
for feature in continuous_features:
mode_value = data[feature].mode()[0] #获取该列的众数。
data[feature].fillna(mode_value, inplace=True) #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。
# 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多
# 所以这里我们还是只划分一次数据集
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1) # 特征,axis=1表示按列删除
y = data['Credit Default'] # 标签
# 按照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))
输出:
--- 1. 默认参数随机森林 (训练集 -> 测试集) ---
训练与预测耗时: 1.6305 秒
默认随机森林 在测试集上的分类报告:
precision recall f1-score support
0 0.77 0.97 0.86 1059
1 0.79 0.30 0.43 441
accuracy 0.77 1500
macro avg 0.78 0.63 0.64 1500
weighted avg 0.77 0.77 0.73 1500
默认随机森林 在测试集上的混淆矩阵:
[[1023 36]
[ 309 132]]
2、有 pipeline 的代码
1.导入库和数据加载
# 导入基础库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import time # 导入 time 库
import warnings
# 忽略警告
warnings.filterwarnings("ignore")
# 设置中文字体,正常显示负号
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 导入 Pipeline 和相关预处理工具
from sklearn.pipeline import Pipeline # 用于创建机器学习工作流
from sklearn.compose import ColumnTransformer # 用于将不同的预处理应用于不同的列
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder, StandardScaler # 数据预处理(有序编码、独热编码、标准化)
from sklearn.impute import SimpleImputer # 处理缺失值
# 导入机器学习模型和评估工具
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器
from sklearn.metrics import classification_report, confusion_matrix # 用于评估分类器性能
from sklearn.model_selection import train_test_split # 用于划分训练集和测试集
# --- 加载原始数据 ---
# 我们加载原始数据,不对其进行任何手动预处理
data = pd.read_csv('data.csv')
print("原始数据加载完成,形状为:", data.shape)
# print(data.head()) # 可以打印前几行看看原始数据
输出:
原始数据加载完成,形状为: (7500, 18)
2.分离特征和标签,划分数据集
# --- 分离特征和标签 (使用原始数据) ---
y = data['Credit Default'] # 标签
X = data.drop(['Credit Default'], axis=1) # 特征 (axis=1 表示按列删除)
print("\n特征和标签分离完成。")
print("特征 X 的形状:", X.shape)
print("标签 y 的形状:", y.shape)
# --- 划分训练集和测试集 (在任何预处理之前划分) ---
# 按照8:2划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 80%训练集,20%测试集
print("\n数据集划分完成 (预处理之前)。")
print("X_train 形状:", X_train.shape)
print("X_test 形状:", X_test.shape)
print("y_train 形状:", y_train.shape)
print("y_test 形状:", y_test.shape)
输出:
特征和标签分离完成。
特征 X 的形状: (7500, 17)
标签 y 的形状: (7500,)
数据集划分完成 (预处理之前)。
X_train 形状: (6000, 17)
X_test 形状: (1500, 17)
y_train 形状: (6000,)
y_test 形状: (1500,)
3.定义预处理步骤
(ColumnTransformer的核心)
(1)定义不同列的类型和它们对应的预处理步骤:
# --- 定义不同列的类型和它们对应的预处理步骤 ---
# 这些定义是基于原始数据 X 的列类型来确定的
# 识别原始的 object 列 (对应预处理前的 discrete_features)
object_cols = X.select_dtypes(include=['object']).columns.tolist()
# 识别原始的非 object 列(通常是数值列)
numeric_cols = X.select_dtypes(exclude=['object']).columns.tolist()
# print(object_cols)
# for i in object_cols:
# print(data[i].unique())
# 有序分类特征(对应之前的标签编码)
# 注意:OrdinalEncoder 默认编码为0, 1, 2... 对应之前字典里手动定义的1, 2, 3... 需要在模型解释时注意
# 这里的类别顺序需要和之前的映射顺序一致
ordinal_features = ['Home Ownership', 'Years in current job', 'Term']
# 定义每个有序特征的类别顺序,这个顺序决定了每个类别对应的数字(大小)
ordinal_categories = [
['Own Home', 'Rent', 'Have Mortgage', 'Home Mortgage'], # Home Ownership 的顺序 (对应1, 2, 3, 4)
['< 1 year', '1 year', '2 years', '3 years', '4 years', '5 years', '6 years', '7 years', '8 years', '9 years', '10+ years'], # Years in current job 的顺序 (对应1-11)
['Short Term' 'Long Term'] # Term的顺序(对应0、1)
]
# 构建处理有序特征的 Pipeline:先填充缺失值,再进行有序编码
ordinal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填充分类特征缺失值
('onehot', OrdinalEncoder(categories=ordinal_categories, handle_unknown='use_encoded_value', unknown_value=-1)) # 有序编码
])
print("有序特征处理 Pipeline 构建完成。")
# 标称分类特征
nominal_features = ['Purpose'] # 使用原始列名
# 构建处理标称特征的 Pipeline:先填充缺失值,再进行独热编码
nominal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) # 进行独热编码,sparse_output=False 使输出为密集数组
])
print("标称特征处理 Pipeline 构建完成。")
# 连续特征 (对应之前的进行众数填充 + 标准化)
# # 1.从所有列中排除掉分类特征(object型),得到连续特征列表
# continuous_features = X.columns.difference(object_cols).tolist()
# print(continuous_features)
# # 2.从所有特征中排除掉已知的有序分类和标称分类特征
# continuous_features1 = [c for c in X.columns if c not in ordinal_features + nominal_features]
# print(continuous_features1)
# 3.用numeric_cols
continuous_features = numeric_cols
# 构建处理连续特征的 Pipeline:先填充缺失值,再进行标准化
continuous_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('scaler', StandardScaler())
])
print("连续特征处理 Pipeline 构建完成。")
输出:
有序特征处理 Pipeline 构建完成。
标称特征处理 Pipeline 构建完成。
连续特征处理 Pipeline 构建完成。
(2)构建 Columntransformer
# --- 构建 Columntransformer ---
# 将不同的预处理应用于不同的列子集,构造一个完备的转化器
# Columntransformer 接受一个 transformers 列表,每个元素是 (名称,转换器对象,列名列表)
preprocessor = ColumnTransformer(
transformers=[
('ordinal', ordinal_transformer, ordinal_features),
('nominal', nominal_transformer,nominal_features),
('continuous', continuous_transformer, continuous_features)
],
remainder='passthrough' # 如何处理没有在上述列表中指定的列
#'passthrough' 表示保留这些列,不做任何处理
# ‘drop’ 表示丢弃这些列
)
print("\nColumnTransformer (预处理器)构建完成。")
print(preprocessor) # 可以打印 preprocessor 对象看看它的结构
输出:
ColumnTransformer (预处理器)构建完成。
ColumnTransformer(remainder='passthrough',
transformers=[('ordinal',
Pipeline(steps=[('imputer',
SimpleImputer(strategy='most_frequent')),
('onehot',
OrdinalEncoder(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...
('continuous',
Pipeline(steps=[('imputer',
SimpleImputer(strategy='most_frequent')),
('scaler', StandardScaler())]),
['Id', 'Annual Income', 'Tax Liens',
'Number of Open Accounts',
'Years of Credit History',
'Maximum Open Credit',
'Number of Credit Problems',
'Months since last delinquent',
'Bankruptcies', 'Current Loan Amount',
'Current Credit Balance', 'Monthly Debt',
'Credit Score'])])
4.构建完整pipeline
# --- 构建完整的 Pipiline ---
# 将预处理器和模型串联起来
# 使用源代码中的 RandomForestClassifier 的默认参数和 random_state
pipeline = Pipeline(steps=[
('preprocessor', preprocessor), # 第一步:应用所有的预处理
('classifier', RandomForestClassifier(random_state=42)) # 第二步:随机森林分类器
])
print("\n完整的 Pipeline 定义完成。")
输出:
完整的 Pipeline 定义完成。
5.使用 Pipeline 进行训练和评估
# --- 1. 使用 Pipeline 在划分好的训练集和测试集上评估 ---
# 完全模仿你原代码的第一个评估步骤
print("\n--- 1. 默认参数随机森林 (训练集 -> 测试集) ---") # 使用你原代码的输出文本
# import time # 引入 time 库 (已在文件顶部引入)
start_time = time.time() # 记录开始时间
# 在原始的 X_train, y_train 上拟合整个Pipeline
# Pipeline会自动按顺序执行 preprocessor 的 fit_transform(X_train),
# 然后用处理后的数据和 y_train 拟合 classifier
pipeline.fit(X_train, y_train)
# 在原始的 X_test 上进行预测
# Pipeline会自动按顺序执行 preprocessor 的 transform(X_test),
# 然后用处理后的数据进行 classifier 的 predict
pipeline_pred = pipeline.predict(X_test)
end_time = time.time() # 记录结束时间
print(f"训练与预测耗时: {end_time - start_time:.4f} 秒") # 使用你原代码的输出格式
print("\n默认随机森林 在测试集上的分类报告:") # 使用你原代码的输出文本
print(classification_report(y_test, pipeline_pred))
print("默认随机森林 在测试集上的混淆矩阵:") # 使用你原代码的输出文本
print(confusion_matrix(y_test, pipeline_pred))
输出:
--- 1. 默认参数随机森林 (训练集 -> 测试集) ---
训练与预测耗时: 1.4694 秒
默认随机森林 在测试集上的分类报告:
precision recall f1-score support
0 0.76 0.99 0.86 1059
1 0.89 0.24 0.38 441
accuracy 0.77 1500
macro avg 0.82 0.61 0.62 1500
weighted avg 0.80 0.77 0.72 1500
默认随机森林 在测试集上的混淆矩阵:
[[1046 13]
[ 336 105]]
三、代码汇总
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import time # 导入 time 库
import warnings
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False # 防止负号显示问题
# 导入 Pipeline 和相关预处理工具
from sklearn.pipeline import Pipeline # 用于创建机器学习工作流
from sklearn.compose import ColumnTransformer # 用于将不同的预处理应用于不同的列,之前是对datafame的某一列手动处理,如果在pipeline中直接用standardScaler等函数就会对所有列处理,所以要用到这个工具
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder, StandardScaler # 用于数据预处理
from sklearn.impute import SimpleImputer # 用于处理缺失值
# 机器学习相关库
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, precision_score, recall_score, f1_score
from sklearn.model_selection import train_test_split # 只导入 train_test_split
# --- 加载原始数据 ---
data = pd.read_csv('data.csv')
# Pipeline 将直接处理分割后的原始数据 X_train, X_test
# 原手动预处理步骤 (将被Pipeline替代):
# Home Ownership 标签编码
# Years in current job 标签编码
# Purpose 独热编码
# Term 0 - 1 映射并重命名
# 连续特征用众数补全
# --- 分离特征和标签 (使用原始数据) ---
y = data['Credit Default']
X = data.drop(['Credit Default'], axis=1)
# --- 划分训练集和测试集 (在任何预处理之前划分) ---
# X_train 和 X_test 现在是原始数据中划分出来的部分,不包含你之前的任何手动预处理结果
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# --- 定义不同列的类型和它们对应的预处理步骤 (这些将被放入 Pipeline 的 ColumnTransformer 中) ---
# 这些定义是基于原始数据 X 的列类型来确定的
# 识别原始的 object 列 (对应你原代码中的 discrete_features 在预处理前)
object_cols = X.select_dtypes(include=['object']).columns.tolist()
# 有序分类特征 (对应你之前的标签编码)
# 注意:OrdinalEncoder默认编码为0, 1, 2... 对应你之前的1, 2, 3...需要在模型解释时注意
# 这里的类别顺序需要和你之前映射的顺序一致
ordinal_features = ['Home Ownership', 'Years in current job', 'Term']
# 定义每个有序特征的类别顺序,这个顺序决定了编码后的数值大小
ordinal_categories = [
['Own Home', 'Rent', 'Have Mortgage', 'Home Mortgage'], # Home Ownership 的顺序 (对应1, 2, 3, 4)
['< 1 year', '1 year', '2 years', '3 years', '4 years', '5 years', '6 years', '7 years', '8 years', '9 years', '10+ years'], # Years in current job 的顺序 (对应1-11)
['Short Term', 'Long Term'] # Term 的顺序 (对应0, 1)
]
# 先用众数填充分类特征的缺失值,然后进行有序编码
ordinal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填充分类特征的缺失值
('encoder', OrdinalEncoder(categories=ordinal_categories, handle_unknown='use_encoded_value', unknown_value=-1))
])
# 分类特征
nominal_features = ['Purpose'] # 使用原始列名
# 先用众数填充分类特征的缺失值,然后进行独热编码
nominal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填充分类特征的缺失值
('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) # sparse_output=False 使输出为密集数组
])
# 连续特征
# 从X的列中排除掉分类特征,得到连续特征列表
continuous_features = X.columns.difference(object_cols).tolist() # 原始X中非object类型的列
# 先用众数填充缺失值,然后进行标准化
continuous_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填充缺失值 (复现你的原始逻辑)
('scaler', StandardScaler()) # 标准化,一个好的实践
])
# --- 构建 ColumnTransformer ---
# 将不同的预处理应用于不同的列子集,构造一个完备的转化器
preprocessor = ColumnTransformer(
transformers=[
('ordinal', ordinal_transformer, ordinal_features),
('nominal', nominal_transformer, nominal_features),
('continuous', continuous_transformer, continuous_features)
],
remainder='passthrough' # 保留没有在transformers中指定的列(如果存在的话),或者 'drop' 丢弃
)
# --- 构建完整的 Pipeline ---
# 将预处理器和模型串联起来
# 使用你原代码中 RandomForestClassifier 的默认参数和 random_state,这里的参数用到了元组这个数据结构
pipeline = Pipeline(steps=[
('preprocessor', preprocessor), # 第一步:应用所有的预处理 (ColumnTransformer)
('classifier', RandomForestClassifier(random_state=42)) # 第二步:随机森林分类器
])
# --- 1. 使用 Pipeline 在划分好的训练集和测试集上评估 ---
print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---")
start_time = time.time() # 记录开始时间
# 在原始的 X_train 上拟合整个Pipeline
# Pipeline会自动按顺序执行preprocessor的fit_transform(X_train),然后用处理后的数据拟合classifier
pipeline.fit(X_train, y_train)
# 在原始的 X_test 上进行预测
# Pipeline会自动按顺序执行preprocessor的transform(X_test),然后用处理后的数据进行预测
pipeline_pred = pipeline.predict(X_test)
end_time = time.time() # 记录结束时间
print(f"训练与预测耗时: {end_time - start_time:.4f} 秒") # 使用你原代码的输出格式
print("\n默认随机森林 在测试集上的分类报告:") # 使用你原代码的输出文本
print(classification_report(y_test, pipeline_pred))
print("默认随机森林 在测试集上的混淆矩阵:") # 使用你原代码的输出文本
print(confusion_matrix(y_test, pipeline_pred))