Python 机器学习 基础 之 算法链与管道 【算法链与管道/预处理进行参数选择/构建管道/在网格搜索中使用管道】的简单说明
目录
[Python 机器学习 基础 之 算法链与管道 【算法链与管道/预处理进行参数选择/构建管道/在网格搜索中使用管道】的简单说明](#Python 机器学习 基础 之 算法链与管道 【算法链与管道/预处理进行参数选择/构建管道/在网格搜索中使用管道】的简单说明)
[2、使用 Pipeline 的示例](#2、使用 Pipeline 的示例)
一、简单介绍
Python是一种跨平台的计算机程序设计语言。是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。Python是一种解释型脚本语言,可以应用于以下领域: Web 和 Internet开发、科学计算和统计、人工智能、教育、桌面界面开发、软件开发、后端开发、网络爬虫。
Python 机器学习是利用 Python 编程语言中的各种工具和库来实现机器学习算法和技术的过程。Python 是一种功能强大且易于学习和使用的编程语言,因此成为了机器学习领域的首选语言之一。Python 提供了丰富的机器学习库,如Scikit-learn、TensorFlow、Keras、PyTorch等,这些库包含了许多常用的机器学习算法和深度学习框架,使得开发者能够快速实现、测试和部署各种机器学习模型。
Python 机器学习涵盖了许多任务和技术,包括但不限于:
- 监督学习:包括分类、回归等任务。
- 无监督学习:如聚类、降维等。
- 半监督学习:结合了有监督和无监督学习的技术。
- 强化学习:通过与环境的交互学习来优化决策策略。
- 深度学习:利用深度神经网络进行学习和预测。
通过 Python 进行机器学习,开发者可以利用其丰富的工具和库来处理数据、构建模型、评估模型性能,并将模型部署到实际应用中。Python 的易用性和庞大的社区支持使得机器学习在各个领域都得到了广泛的应用和发展。
二、算法链与管道
对于许多机器学习算法,你提供的特定数据表示非常重要,正如前面中讲过,首先对数据进行缩放,然后手动合并特征,再利用无监督机器学习来学习特征。因此,大多数机器学习应用不仅需要应用单个算法,而且还需要将许多不同的处理步骤和机器学习模型链接在一起。在机器学习中,算法链与管道(pipeline)是非常重要的概念,它们用于简化工作流程,确保数据处理步骤和模型训练步骤能够无缝衔接。通过使用管道,你可以将数据预处理、特征选择、模型训练等步骤串联起来,使整个过程更加高效和易于管理。
1、算法链与管道的概念
算法链(Chaining Algorithms):
- 算法链指的是将多个机器学习算法按顺序连接起来,每个算法的输出作为下一个算法的输入。例如,可以先使用特征缩放(如标准化或归一化),然后再进行模型训练。
管道(Pipeline):
- 管道是
scikit-learn
提供的一个高级工具,它允许你将多个数据处理和模型训练步骤串联起来,使得整个工作流程可以像一个单独的估计器一样被使用。- 使用管道的主要好处包括:
- 简化代码,使其更加清晰和模块化。
- 防止数据泄漏,确保在交叉验证或网格搜索时,数据处理步骤在训练集和测试集上是独立进行的。
- 更容易进行超参数调优和模型评估。
2、使用
Pipeline
的示例以下是一个使用
Pipeline
的简单示例,展示了如何将数据标准化和支持向量机(SVM)分类器连接在一起:
pythonimport numpy as np from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.pipeline import Pipeline # 创建一个示例数据集 X, y = make_classification(n_samples=1000, n_features=20, random_state=42) # 将数据集分割成训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 创建一个管道,包含标准化和SVM分类器 pipeline = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC()) ]) # 设置超参数搜索空间 param_grid = { 'svm__C': [0.1, 1, 10], 'svm__gamma': [0.001, 0.01, 0.1, 1] } # 使用GridSearchCV进行超参数调优 grid_search = GridSearchCV(pipeline, param_grid, cv=5) grid_search.fit(X_train, y_train) # 打印最佳参数和最佳得分 print("Best parameters: {}".format(grid_search.best_params_)) print("Best cross-validation score: {:.2f}".format(grid_search.best_score_)) # 在测试集上评估最佳模型 best_model = grid_search.best_estimator_ test_score = best_model.score(X_test, y_test) print("Test set score: {:.2f}".format(test_score))
3、关键点说明
- Pipeline:定义了一个管道,其中包含数据预处理(标准化)和模型(SVM)。
- param_grid :定义了超参数搜索空间,其中包含 SVM 的超参数
C
和gamma
。- GridSearchCV:用于超参数调优,确保每次交叉验证时都对整个管道进行评估。
- fit:在训练集上训练管道并进行超参数搜索。
- best_estimator_:获取最佳模型,并在测试集上进行评估。
通过使用管道,你可以确保数据预处理和模型训练步骤紧密结合,并且在超参数调优和交叉验证时不会发生数据泄漏。这种方法在实践中非常有用,尤其是在处理复杂的机器学习工作流程时。
接下来将介绍如何使用 Pipeline
类来简化构建变换和模型链的过程。我们将重点介绍如何将 Pipeline
和 GridSearchCV
结合起来,从而同时搜索所有处理步骤中的参数。
举一个例子来说明模型链的重要性。我们知道,可以通过使用 MinMaxScaler
进行预处理来大大提高核 SVM 在 cancer
数据集上的性能。下面这些代码实现了划分数据、计算最小值和最大值、缩放数据与训练 SVM:
python
from sklearn.svm import SVC
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
# 加载并划分数据
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, random_state=0)
# 计算训练数据的最小值和最大值
scaler = MinMaxScaler().fit(X_train)
python
# 对训练数据进行缩放
X_train_scaled = scaler.transform(X_train)
svm = SVC()
# 在缩放后的训练数据上学习SVM
svm.fit(X_train_scaled, y_train)
# 对测试数据进行缩放,并计算缩放后的数据的分数
X_test_scaled = scaler.transform(X_test)
print("Test score: {:.2f}".format(svm.score(X_test_scaled, y_test)))
Test score: 0.95
三、用预处理进行参数选择
现在,假设我们希望利用 GridSearchCV
找到更好的 SVC
参数,正如前面所做的那样。我们应该怎么做?一种简单的方法可能如下所示:
python
from sklearn.model_selection import GridSearchCV
# 只是为了便于说明,不要在实践中使用这些代码!
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100],
'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
grid = GridSearchCV(SVC(), param_grid=param_grid, cv=5)
grid.fit(X_train_scaled, y_train)
print("Best cross-validation accuracy: {:.2f}".format(grid.best_score_))
print("Best set score: {:.2f}".format(grid.score(X_test_scaled, y_test)))
print("Best parameters: ", grid.best_params_)
Best cross-validation accuracy: 0.98
Best set score: 0.97
Best parameters: {'C': 1, 'gamma': 1}
这里我们利用缩放后的数据对 SVC
参数进行网格搜索。但是,上面的代码中有一个不易察觉的陷阱。在缩放数据时,我们使用了训练集中的所有数据 来找到训练的方法。然后,我们使用缩放后的训练数据 来运行带交叉验证的网格搜索。对于交叉验证中的每次划分,原始训练集的一部分被划分为训练部分,另一部分被划分为测试部分。测试部分用于度量在训练部分上所训练的模型在新数据上的表现。但是,我们在缩放数据时已经使用过测试部分中所包含的信息。请记住,交叉验证每次划分的测试部分都是训练集的一部分,我们使用整个训练集的信息来找到数据的正确缩放。
对于模型来说,这些数据与新数据看起来截然不同 。如果我们观察新数据(比如测试集中的数据),那么这些数据并没有用于对训练数据进行缩放,其最大值和最小值也可能与训练数据不同。下面这个例子(图 6-1)显示了交叉验证与最终评估这两个过程中数据处理的不同之处:
python
import mglearn
import matplotlib.pyplot as plt
mglearn.plots.plot_improper_processing()
# plt.tight_layout()
plt.savefig('Images/01ParameterSelectionWithPreprocessing-01.png', bbox_inches='tight')
plt.show()
图 6-1:在交叉验证循环之外进行预处理时的数据使用情况
因此,对于建模过程,交叉验证中的划分无法正确地反映新数据的特征。我们已经将这部分数据的信息泄露(leak)给建模过程。这将导致在交叉验证过程中得到过于乐观的结果,并可能会导致选择次优的参数。
为了解决这个问题,在交叉验证的过程中,应该在进行任何预处理之前 完成数据集的划分。任何从数据集中提取信息的处理过程都应该仅应用于数据集的训练部分,因此,任何交叉验证都应该位于处理过程的"最外层循环"。
在 scikit-learn
中,要想使用 cross_val_score
函数和 GridSearchCV
函数实现这一点,可以使用 Pipeline
类。Pipeline
类可以将多个处理步骤合并(glue)为单个 scikit-learn
估计器。Pipeline
类本身具有 fit
、predict
和 score
方法,其行为与 scikit-learn
中的其他模型相同。Pipeline
类最常见的用例是将预处理步骤(比如数据缩放)与一个监督模型(比如分类器)链接在一起。
四、构建管道
我们来看一下如何使用 Pipeline
类来表示在使用 MinMaxScaler
缩放数据之后再训练一个 SVM 的工作流程(暂时不用网格搜索)。首先,我们构建一个由步骤列表组成的管道对象。每个步骤都是一个元组,其中包含一个名称(你选定的任意字符串(只有一个例外,就是该名称不能包含双下划线 __
))和一个估计器的实例:
python
from sklearn.pipeline import Pipeline
pipe = Pipeline([("scaler", MinMaxScaler()), ("svm", SVC())])
这里我们创建了两个步骤:第一个叫作 "scaler"
,是 MinMaxScaler
的实例;第二个叫作 "svm"
,是 SVC
的实例。现在我们可以像任何其他 scikit-learn
估计器一样来拟合这个管道:
python
pipe.fit(X_train, y_train)
这里 pipe.fit
首先对第一个步骤(缩放器)调用 fit
,然后使用该缩放器对训练数据进行变换,最后用缩放后的数据来拟合 SVM。要想在测试数据上进行评估,我们只需调用 pipe.score
:
python
print("Test score: {:.2f}".format(pipe.score(X_test, y_test)))
Test score: 0.97
如果对管道调用 score
方法,则首先使用缩放器对测试数据进行变换,然后利用缩放后的测试数据对 SVM 调用 score
方法。如你所见,这个结果与我们从本章开头的代码得到的结果(手动进行数据变换)是相同的。利用管道,我们减少了"预处理 + 分类"过程所需要的代码量。但是,使用管道的主要优点在于,现在我们可以在 cross_val_score
或 GridSearchCV
中使用这个估计器。
五、在网格搜索中使用管道
在网格搜索中使用管道的工作原理与使用任何其他估计器都相同。我们定义一个需要搜索的参数网格,并利用管道和参数网格构建一个 GridSearchCV
。不过在指定参数网格时存在一处细微的变化。我们需要为每个参数指定它在管道中所属的步骤。我们要调节的两个参数 C
和 gamma
都是 SVC
的参数,属于第二个步骤。我们给这个步骤的名称是 "svm"
。为管道定义参数网格的语法是为每个参数指定步骤名称,后面加上 __
(双下划线),然后是参数名称。因此,要想搜索 SVC
的 C
参数,必须使用 "svm__C"
作为参数网格字典的键,对 gamma
参数也是同理:
python
param_grid = {'svm__C': [0.001, 0.01, 0.1, 1, 10, 100],
'svm__gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
有了这个参数网格,我们可以像平常一样使用 GridSearchCV
:
python
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
pipe = Pipeline([("scaler", MinMaxScaler()), ("svm", SVC())])
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5)
grid.fit(X_train, y_train)
print("Best cross-validation accuracy: {:.2f}".format(grid.best_score_))
print("Test set score: {:.2f}".format(grid.score(X_test, y_test)))
print("Best parameters: {}".format(grid.best_params_))
Best cross-validation accuracy: 0.98
Test set score: 0.97
Best parameters: {'svm__C': 1, 'svm__gamma': 1}
与前面所做的网格搜索不同,现在对于交叉验证的每次划分来说,仅使用训练部分对 MinMaxScaler
进行拟合,测试部分的信息没有泄露到参数搜索中。将图 6-2 与图 6-1 进行对比。
python
import mglearn
import matplotlib.pyplot as plt
mglearn.plots.plot_proper_processing()
# plt.tight_layout()
plt.savefig('Images/03UsingPipelinesInGridSearch-01.png', bbox_inches='tight')
plt.show()
图 6-2:使用管道在交叉验证循环内部进行预处理时的数据使用情况
在交叉验证中,信息泄露的影响大小取决于预处理步骤的性质。使用测试部分来估计数据的范围,通常不会产生可怕的影响,但在特征提取和特征选择中使用测试部分,则会导致结果的显著差异。
1、举例说明信息泄露
在 Hastie、Tibshirani 与 Friedman 合著的《统计学习基础》一书中给出了交叉验证中信息泄露的一个很好的例子,这里我们复制了一个修改版本。我们考虑一个假想的回归任务,包含从高斯分布中独立采样的 100 个样本与 10 000 个特征。我们还从高斯分布中对响应进行采样:
python
import numpy as np
rnd = np.random.RandomState(seed=0)
X = rnd.normal(size=(100, 10000))
y = rnd.normal(size=(100,))
考虑到我们创建数据集的方式,数据 X
与目标 y
之间没有任何关系(它们是独立的),所以应该不可能从这个数据集中学到任何内容。现在我们将完成下列工作。首先利用 SelectPercentile
特征选择从 10 000 个特征中选择信息量最大的特征,然后使用交叉验证对 Ridge
回归进行评估
python
from sklearn.feature_selection import SelectPercentile, f_regression
select = SelectPercentile(score_func=f_regression, percentile=5).fit(X, y)
X_selected = select.transform(X)
print("X_selected.shape: {}".format(X_selected.shape))
X_selected.shape: (100, 500)
python
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import Ridge
print("Cross-validation accuracy (cv only on ridge): {:.2f}".format(
np.mean(cross_val_score(Ridge(), X_selected, y, cv=5))))
Cross-validation accuracy (cv only on ridge): 0.91
交叉验证计算得到的平均 R2 为 0.91,表示这是一个非常好的模型。这显然是不对的,因为我们的数据是完全随机的。这里的特征选择从 10 000 个随机特征中(碰巧)选出了与目标相关性非常好的一些特征。由于我们在交叉验证之外 对特征选择进行拟合,所以它能够找到在训练部分和测试部分都相关的特征。从测试部分泄露出去的信息包含的信息量非常大,导致得到非常不切实际的结果。我们将这个结果与正确的交叉验证(使用管道)进行对比:
python
pipe = Pipeline([("select", SelectPercentile(score_func=f_regression,
percentile=5)),
("ridge", Ridge())])
print("Cross-validation accuracy (pipeline): {:.2f}".format(
np.mean(cross_val_score(pipe, X, y, cv=5))))
Cross-validation accuracy (pipeline): -0.25
这一次我们得到了负的 分数,表示模型很差。利用管道,特征选择现在位于交叉验证循环内部 。也就是说,仅使用数据的训练部分来选择特征,而不使用测试部分。特征选择找到的特征在训练集中与目标相关,但由于数据是完全随机的,这些特征在测试集中并不与目标相关。在这个例子中,修正特征选择中的数据泄露问题,结论也由"模型表现很好"变为"模型根本没有效果"。
六、小结
用预处理进行参数选择(Preprocessing for Parameter Selection):
- 定义:在进行参数选择时,包含预处理步骤,例如特征缩放、特征选择或特征变换。这些预处理步骤在交叉验证过程中被正确应用,以避免数据泄漏。
- 示例:在进行超参数调优时,将特征缩放和特征选择作为参数选择的一部分。
构建管道(Constructing Pipelines):
- 定义 :通过
Pipeline
对象,将多个机器学习步骤(如预处理、模型训练)串联起来,形成一个整体工作流。每个步骤按顺序执行。 - 示例:构建一个包含标准化、PCA和SVM分类器的管道。
在网格搜索中使用管道(Using Pipelines in Grid Search):
- 定义 :在使用
GridSearchCV
进行超参数调优时,将管道作为基础模型,这样可以同时对预处理步骤和模型参数进行调优。 - 示例:将标准化、PCA和SVM集成到一个管道中,并对PCA的组件数和SVM的超参数进行网格搜索。
功能目标:
- 用预处理进行参数选择:主要目的是在参数选择过程中正确应用预处理步骤,确保数据预处理不会引入数据泄漏。
- 构建管道:主要目的是将多个机器学习步骤整合成一个整体工作流,简化代码并提高可维护性。
- 在网格搜索中使用管道:主要目的是同时调优预处理步骤和模型参数,找到最佳的预处理和模型参数组合。
实现方式:
- 用预处理进行参数选择:通常直接在参数选择过程中包含预处理步骤。
- 构建管道 :通过
Pipeline
类,将预处理步骤和模型训练步骤串联起来。 - 在网格搜索中使用管道 :将构建好的管道作为基础模型,传入
GridSearchCV
进行网格搜索。
共同点
- 整合多个步骤:三者都涉及将预处理步骤和模型训练步骤结合起来,以确保数据处理流程的一致性。
- 避免数据泄漏:都旨在避免数据泄漏,通过确保预处理步骤在每次交叉验证中都被正确应用。
- 提高工作流效率:都能使机器学习工作流更加简洁和模块化,提高代码的可维护性和重用性。
如何选择使用
-
用预处理进行参数选择:
- 使用场景:当你在进行简单的超参数调优时,需要确保预处理步骤不会引入数据泄漏。例如,在选择模型的正则化参数时,需要确保特征缩放在交叉验证的每个折叠中都被正确应用。
- 优点:保证预处理步骤的正确应用,避免数据泄漏。
-
构建管道:
- 使用场景:当你的机器学习工作流包含多个步骤,并且希望这些步骤能够按顺序执行。特别适用于复杂的工作流,包含多个预处理步骤和模型训练步骤。
- 优点:代码更加简洁和模块化,易于维护和调试。
-
在网格搜索中使用管道:
- 使用场景:当你需要同时调优预处理步骤和模型参数。例如,调优PCA的组件数和SVM的超参数。使用管道可以确保在网格搜索的每次迭代中都能正确应用所有步骤。
- 优点:能够找到最佳的预处理和模型参数组合,提高模型性能。
以下是一个参考的示例,展示了如何构建管道并在网格搜索中使用管道进行超参数调优:
python
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
# 创建示例数据集
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 构建包含标准化、PCA和SVM的管道
pipeline = Pipeline([
('scaler', StandardScaler()),
('pca', PCA()),
('svm', SVC())
])
# 设置超参数搜索空间
param_grid = {
'pca__n_components': [5, 10, 15],
'svm__C': [0.1, 1, 10],
'svm__gamma': [0.001, 0.01, 0.1, 1]
}
# 使用GridSearchCV进行超参数调优
grid_search = GridSearchCV(pipeline, param_grid, cv=5)
grid_search.fit(X_train, y_train)
# 打印最佳参数和最佳得分
print("Best parameters: {}".format(grid_search.best_params_))
print("Best cross-validation score: {:.2f}".format(grid_search.best_score_))
# 在测试集上评估最佳模型
best_model = grid_search.best_estimator_
test_score = best_model.score(X_test, y_test)
print("Test set score: {:.2f}".format(test_score))
Best parameters: {'pca__n_components': 15, 'svm__C': 10, 'svm__gamma': 0.01}
Best cross-validation score: 0.86
Test set score: 0.83
附录
一、参考文献
参考文献:[德] Andreas C. Müller [美] Sarah Guido 《Python Machine Learning Basics Tutorial》