算法链与管道(上):建立管道
对于许多机器学习算法,你提供的特定数据表示非常重要。
- 首先对数据进行缩放,然后手动合并特征,再利用无监督机器学习来学习特征。
- 因此,大多数机器学习应用不仅需要应用单个算法,而且还需要将许多不同的处理步骤和机器学习模型链接在一起。
举一个例子来说明模型链的重要性。
我们知道,可以通过使用 MinMaxScaler 进行预处理来大大提高核 SVM 在 cancer 数据集上的性能。
下面这些代码实现了划分数据、计算最小值和最大值、缩放数据与训练 SVM:
from sklearn.datasets import load_breast_cancer
from sklearn.svm import SVC
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
#加载和划分数据
cancer = load_breast_cancer()
X_train,X_test,y_train,y_test = train_test_split(cancer.data,cancer.target,random_state=0)
#数据缩放
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
#在缩放后的数据上学习SVM
svc = SVC().fit(X_train_scaled,y_train)
X_test_scaled = scaler.transform(X_test)
print("Test score:{}".format(svc.score(X_test_scaled,y_test)))
'''
`Test score:0.972027972027972`
'''
1、用预处理进行参数选择
现在,假设我们希望利用 GridSearchCV 找到更好的 SVC 参数。 我们应该怎么做?一种简单的方法可能如下所示:
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]}
#创建GridSearchCV实例,折数为五折
grid = GridSearchCV(SVC(),param_grid,cv=5)
#拟合
grid.fit(X_train_scaled,y_train)
#打印最优参数
print("Best parammetes:{}".format(grid.best_params_))
print("Best cross-validation accuracy:{:.3f}".format(grid.best_score_))
print("Test score:{:.3f}".format(grid.score(X_test_scaled,y_test)))
'''
```
Best parammetes:{'C': 1, 'gamma': 1}
Best cross-validation accuracy:0.981
Test score:0.972
```
'''
📣
这里我们利用缩放后的数据对 SVC 参数进行网格搜索。但是,上面的代码中有一个不易察觉的陷阱。
在缩放数据时,我们使用了训练集中的所有数据来找到训练的方法。然后,我们使用缩放后的训练数据来运行带交叉验证的网格搜索。
对于交叉验证中的每次划分,原始训练集的一部分被划分为训练部分,另一部分被划分为测试部分。测试部分用于度量在训练部分上所训练的模型在新数据上的表现。但是,我们在缩放数据时已经使用过测试部分中所包含的信息。
⭐交叉验证每次划分的测试部分都是训练集的一部分,我们使用整个训练集的信息来找到数据的正确缩放。
对于模型来说,这些数据与新数据看起来截然不同。
如果我们观察新数据(比如测试集中的数据),那么这些数据并没有用于对训练数据进行缩放,其最大值和最小值也可能与训练数据不同。
下面这个例子显示了交叉验证与最终评估这两个过程中数据处理的不同之处:
mglearn.plots.plot_improper_processing()
📣
因此,对于建模过程,交叉验证中的划分无法正确地反映新数据的特征。
- 我们已经将这部分数据的信息泄露(leak)给建模过程。
- 这将导致在交叉验证过程中得到过于乐观的结果, 并可能会导致选择次优的参数。
⭐
为了解决这个问题,在交叉验证的过程中,应该在进行任何预处理之前完成数据集的划分。
任何从数据集中提取信息的处理过程都应该仅应用于数据集的训练部分,因此,任何交叉验证都应该位于处理过程的 “最外层循环”。
在 scikit-learn 中,要想使用 cross_val_score 函数和 GridSearchCV 函数实现这一点,可以使用 Pipeline 类。
- Pipeline 类可以将多个处理步骤合并(glue)为单个 scikit-learn 估计器。
- Pipeline 类本身具有 fit、predict 和 score 方法,其行为与 scikit-learn 中的其他模型相同。
- Pipeline 类最常见的用例是将预处理步骤(比如数据缩放)与一个监督模型 (比如分类器)链接在一起。
2、构建管道
使用 Pipeline 类来表示在使用 MinMaxScaler 缩放数据之后再训练一个 SVM 的工作流程(暂时不用网格搜索)。
#构建一个由步骤列表组成的管道对象
from sklearn.pipeline import Pipeline
#每个步骤都是一个元组,其中包含一个名称(你选定的任意字符串)和一个估计器的实例
pipe = Pipeline([('scaler',MinMaxScaler()),('svm',SVC())])
'''
这里我们创建了两个步骤:第一个叫作 "scaler",是 MinMaxScaler 的实例;第二个叫作 "svm",是 SVC 的实例。
现在我们可以像任何其他 scikit-learn 估计器一样来拟合这个管道:
'''
#首先对第一个步骤(缩放器)调用 fit,然后使用该缩放器对训练数据进行变换,最后用缩放后的数据来拟合 SVM。
pipe.fit(X_train,y_train)
#要想在测试数据上进行评估,我们只需调用 pipe.score
print("Test score:{:.2f}".format(pipe.score(X_test,y_test)))
'''
```
Test score:0.97
```
'''
3、在网格搜索中使用管道
在网格搜索中使用管道的工作原理与使用任何其他估计器都相同。我们定义一个需要搜索的参数网格,并利用管道和参数网格构建一个 GridSearchCV。
不过在指定参数网格时存在一处细微的变化。
- 我们需要为每个参数指定它在管道中所属的步骤。
- 我们要调节的两个参数 C 和 gamma 都是 SVC 的参数,属于第二个步骤。
- 我们给这个步骤的名称是 "svm":
- 为管道定义参数网格的语法是为每个参数指定步骤名称,后面加上 __(双下划线),然后是参数名称。
- 因此,要想搜索 SVC 的 C 参数,必须使用 "svm__C" 作为参数网格字典的键,对 gamma 参数也是同理:
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:
#注意这里的模型用的是pipe
grid = GridSearchCV(pipe,param_grid,cv=5)
grid.fit(X_train,y_train)
#打印
print("Best parammetes:{}".format(grid.best_params_))
print("Best cross-validation accuracy:{:.3f}".format(grid.best_score_))
print("Test score:{:.3f}".format(grid.score(X_test,y_test)))
'''
```
Best parammetes:{'svm__C': 1, 'svm__gamma': 1}
Best cross-validation accuracy:0.981
Test score:0.972
```
'''
#与前面所做的网格搜索不同,现在对于交叉验证的每次划分来说,仅使用训练部分对 MinMaxScaler 进行拟合,测试部分的信息没有泄露到参数搜索中。
mglearn.plots.plot_proper_processing()
在交叉验证中,信息泄露的影响大小取决于预处理步骤的性质。使用测试部分来估计数据的范围,通常不会产生可怕的影响,但在特征提取和特征选择中使用测试部分,则会导致结果的显著差异。
4、参考文献
《python机器学习基础教程》