Python机器学习基础教程9

一、算法链与管道

大多数机器学习应用不仅需要应用单个算法,而且还需要将许多不同的处理步骤和机器学习模型链接在一起。本章将介绍如何使用Pipeline 类来简化构建变换和模型链的过程。我们将重点介绍如何将 Pipeline 和 GridSearchCV 结合起来,从而同时搜索所有
处理步骤中的参数。
举例说明模型连的重要性:

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)

# 对训练数据进行缩放
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.97

用预处理进行参数选择

希望利用 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]}

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进行网格搜索,但是,在代码中有一个不容易察觉的错误,在缩放数据时候,我们使用了训练集中的所有数据来找到训练的方法,然后我们使用缩放后的数据来运行带交叉验证的网格搜索。对于交叉验证中的每次划分,原始数据集的一部分被划分为训练部分,另一部分被划分为测试部分。测试部分用于度量在训练部分上所训练的模型在新数据上的表现。但是,我们在缩放数据时已经使用过测试部分中所包含的信息。请记住,交叉验证每次划分的测试部分都是训练集的一部分,我们使用整个训练集的信息来找到数据的正确缩放。
对于模型来说,这些数据与新数据看起来截然不同。
在这里插入图片描述
对于建模过程,交叉验证中的划分无法正确地反映新数据的特征。我们已经将这部分数据的信息泄露(leak)给建模过程。这将导致在交叉验证过程中得到过于乐观的结果,并可能会导致选择次优的参数。
为了解决这个问题,在交叉验证的过程中,应该在进行任何预处理之前完成数据集的划分。任何从数据集中提取信息的处理过程都应该仅应用于数据集的训练部分,因此,任何交叉验证都应该位于处理过程的“最外层循环”。
Pipeline 类可以将多个处理步骤合并(glue)为单个 scikit-learn 估计器。Pipeline 类本身具有 fit、predict 和 score 方法,其行为与 scikit-learn 中的其他模型相同。Pipeline 类最常见的用例是将预处理步骤(比如数据缩放)与一个监督模型(比如分类器)链接在一起。

构建管道

使用 Pipeline 类来表示在使用 MinMaxScaler 缩放数据之后再训练一个SVM 的工作流程:首先我们构建一个由步骤列表组成的管道对象。每个步骤都是一个元组,其中包含一个名称(选定的任意字符串)和一个估计器的实例,像其他任何scikit-learn估计器一样来拟合这个管道。fit首先对第一个步骤调用fit,然后使用该缩放器对训练数据进行变换。最后用缩放后的数据来拟合SVM,要想在测试数据上进行评估,只需要调用pipe.score。

from sklearn.pipeline import Pipeline
pipe = Pipeline([("scaler", MinMaxScaler()), ("svm", SVC())])
pipe.fit(X_train, y_train)
print("Test score: {:.2f}".format(pipe.score(X_test, y_test)))

运行结果:
Test score: 0.97

利用管道,我们减少了“预处理 + 分类”过程所需要的代码量。但是,使用管道的主要优点在于,现在我们可以在 cross_val_score 或
GridSearchCV 中使用这个估计器。

在网格搜索中使用管道

我们定义一个需要搜索的参数网格,并利用管道和参数网格构建一个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]}

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_))

与前面所做的网格搜索不同,现在对于交叉验证的每次划分来说,仅使用训练部分对
MinMaxScaler 进行拟合,测试部分的信息没有泄露到参数搜索中。
在这里插入图片描述
在交叉验证中,信息泄露的影响大小取决于预处理步骤的性质。使用测试部分来估计数据
的范围,通常不会产生可怕的影响,但在特征提取和特征选择中使用测试部分,则会导致
结果的显著差异。

通用的管道接口

Pipeline 类不但可用于预处理和分类,实际上还可以将任意数量的估计器连接在一起。例
如,你可以构建一个包含特征提取、特征选择、缩放和分类的管道,总共有 4 个步骤。同样,最后一步可以用回归或聚类代替分类。
对于管道中估计其的唯一要求就是,除了最后一步之外的所有步骤都需要具有transform方法,这样它们可以生成新的数据表示,以供下一个步骤使用。
在调用 Pipeline.fit 的过程中,管道内部依次对每个步骤调用 fit 和 transform2,其输入
是前一个步骤中 transform 方法的输出。对于管道中的最后一步,则仅调用 fit。
请记住,pipeline.steps 是由元组组成的列表,所以 pipeline.steps[0][1] 是第一个估计器,pipeline.steps[1][1] 是第二个估计器,以此类推。

def fit(self, X, y):
 X_transformed = X
 for name, estimator in self.steps[:-1]:
    # 遍历除最后一步之外的所有步骤
    # 对数据进行拟合和变换
    X_transformed = estimator.fit_transform(X_transformed, y)
    # 对最后一步进行拟合
    self.steps[-1][1].fit(X_transformed, y)
 return self
'''
使用pipeline进行预测时,利用除最后一步之外的所有步骤对数据进行变换
对最后一步调用predict
'''
def predict(self, X):
    X_transformed = X
    for step in self.steps[:-1]:
        # 遍历除最后一步之外的所有步骤
        # 对数据进行变换 
        X_transformed = step[1].transform(X_transformed)
# 利用最后一步进行预测
    return self.steps[-1][1].predict(X_transformed)

在这里插入图片描述
管道的最后一步不需要具有 predict 函数,比如说,我们可以创建一个只包含一个缩放器和一个 PCA 的管道。由于最后一步(PCA)具有 transform 方法,所以我们可以对管道调用 transform,以得到将 PCA.transform 应用于前一个步骤处理过的数据后得到的输出。

使用make_pipeline方便的创建管道

我们通常不需要为每一个步骤提供用户指定的名称。有一个很方便的函数make_pipeline,可以为我们创建管道并根据每个步骤所属的类为其自动命名。

from sklearn.pipeline import make_pipeline
# 标准语法
pipe_long = Pipeline([("scaler", MinMaxScaler()), ("svm", SVC(C=100))])
# 缩写语法
pipe_short = make_pipeline(MinMaxScaler(), SVC(C=100))

# 通过查看steps属性来查看步骤的名称
print("Pipeline steps:\n{}".format(pipe_short.steps))

# 相同步骤命名
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
pipe = make_pipeline(StandardScaler(), PCA(n_components=2), StandardScaler())
print("Pipeline steps:\n{}".format(pipe.steps))

运行结果:
Pipeline steps:
[('minmaxscaler', MinMaxScaler()), ('svc', SVC(C=100))]
Pipeline steps:
[('standardscaler-1', StandardScaler()), ('pca', PCA(n_components=2)), ('standardscaler-2', StandardScaler())]

步骤名称只是类名称的小写版本,如果多个步骤属于同一个类,则会附加一个数字。

访问步骤属性

检查管道中某一步骤的属性:比如线性模型的系数或PCA提取的成分。要想访问管道中的步骤,最简单的方法时通过named_steps属性,它是一个字典,将步骤名称映射为估计器。

# 用前面定义的管道对cancer数据集进行拟合
pipe.fit(cancer.data)
# 从"pca"步骤中提取前两个主成分
components = pipe.named_steps["pca"].components_
print("components.shape: {}".format(components.shape))

运行结果:
components.shape: (2, 30)

访问网格搜索管道中的属性

使用管道的主要原因之一就是进行网格搜索。一个常见的任务是在网格搜索内访问管道的某些步骤。

from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
'''
对 cancer 数据集上的 LogisticRegression 分类器进行网格搜索
在将数据传入 LogisticRegression 分类器之前
先用 Pipeline 和 StandardScaler对数据进行缩放。
'''

# make_pipeline函数创建一个管道
pipe = make_pipeline(StandardScaler(), LogisticRegression())

'''
创建一个参数网格
LogisticRegression 需要调节的正则化参数是参数 C
对这个参数使用对数网格,在 0.01100 之间进行搜索
由于使用了 make_pipeline 函数,管道中 LogisticRegression 步骤的名称是小写的类名称 logisticregression
为了调节参数 C,我们必须指定 logisticregression__C的参数网格
'''
param_grid = {'logisticregression__C': [0.01, 0.1, 1, 10, 100]}

# 将cancer数据集划分为训练集和测试集 并对网格搜索进行拟合
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=4)
grid = GridSearchCV(pipe, param_grid, cv=5)
grid.fit(X_train, y_train)

# GridSearchCV 找到的最佳模型(在所有训练数据上训练得到的模型)保存在grid.best_estimator_ 中
print("Best estimator:\n{}".format(grid.best_estimator_))
#  使 用 管 道 的 named_steps 属性来访问logisticregression 步骤
print("Logistic regression step:\n{}".format(grid.best_estimator_.named_steps["logisticregression"]))

# 得到训练过的LogisticRegression 实例,下面我们可以访问与每个输入特征相关的系数(权重)
print("Logistic regression coefficients:\n{}".format(grid.best_estimator_.named_steps["logisticregression"].coef_))

运行结果:
Best estimator:
Pipeline(steps=[('standardscaler', StandardScaler()),
                ('logisticregression', LogisticRegression(C=1))])
Logistic regression step:
LogisticRegression(C=1)
Logistic regression coefficients:
[[-0.43570655 -0.34266946 -0.40809443 -0.5344574  -0.14971847  0.61034122
  -0.72634347 -0.78538827  0.03886087  0.27497198 -1.29780109  0.04926005
  -0.67336941 -0.93447426 -0.13939555  0.45032641 -0.13009864 -0.10144273
   0.43432027  0.71596578 -1.09068862 -1.09463976 -0.85183755 -1.06406198
  -0.74316099  0.07252425 -0.82323903 -0.65321239 -0.64379499 -0.42026013]]

网格搜索预处理与模型参数

利用管道将机器学习工作流程中的所有处理步骤封装成一个 scikit-learn 估计器。这样做的好处在于,我们可以使用监督任务的输出来调节预处理参数。

# 管道包含3个步骤:缩放数据、计算多项式特征与岭回归
from sklearn.datasets import load_boston
boston = load_boston()
X_train, X_test, y_train, y_test = train_test_split(boston.data, boston.target,random_state=0)

from sklearn.preprocessing import PolynomialFeatures
pipe = make_pipeline(StandardScaler(),PolynomialFeatures(),Ridge())
'''
如何知道选择几次多项式 是否选择多项式或交互项
根据分类结果来选择degree参数
可以利用管道搜索 degree 参数以及 Ridge 的 alpha参数
为了做到这一点,我们要定义一个包含这两个参数的 param_grid,
并用步骤名称作为前缀
'''
param_grid = {'polynomialfeatures__degree': [1, 2, 3],
 'ridge__alpha': [0.001, 0.01, 0.1, 1, 10, 100]}

# 运行网格搜索
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)
# 使用热力图将交叉验证的结果可视化
plt.matshow(grid.cv_results_['mean_test_score'].reshape(3, -1),vmin=0, cmap="viridis")
plt.xlabel("ridge__alpha")
plt.ylabel("polynomialfeatures__degree")
plt.xticks(range(len(param_grid['ridge__alpha'])), param_grid['ridge__alpha'])
plt.yticks(range(len(param_grid['polynomialfeatures__degree'])),param_grid['polynomialfeatures__degree'])
plt.colorbar()
plt.show()

# 输出最佳参数
print("Best parameters: {}".format(grid.best_params_))
# 输出最佳参数的分数
print("Test-set score: {:.2f}".format(grid.score(X_test, y_test)))

# 作为对比,运行一个没有多项式特征的网格搜索
param_grid = {'ridge__alpha': [0.001, 0.01, 0.1, 1, 10, 100]}
pipe = make_pipeline(StandardScaler(), Ridge())
grid = GridSearchCV(pipe, param_grid, cv=5)
grid.fit(X_train, y_train)
print("Score without poly features: {:.2f}".format(grid.score(X_test, y_test)))

运行结果:
Best parameters: {'polynomialfeatures__degree': 2, 'ridge__alpha': 10}
Test-set score: 0.77
Score without poly features: 0.63

在这里插入图片描述
从交叉验证的结果来看,使用二次多项式也很有用,但三次多项式的效果比一次和二次都要差很多。
同时搜索预处理参数与模型参数是一个非常强大的策略。但是GridSearchCV 会尝试指定参数的所有可能组合。向网格中添加更多参数,需要构建的模型数量将称指数增长。

网格搜索选择使用哪个模型

甚至可以进一步将 GridSearchCV 和 Pipeline 结合起来:还可以搜索管道中正在执行的实际步骤(比如用 StandardScaler 还是用 MinMaxScaler)。这样会导致更大的搜索空间,应该予以仔细考虑。尝试所有可能的解决方案,通常并不是一种可行的机器学习策略。

'''
在 iris 数据集上比较 RandomForestClassifier 和 SVC
SVC可能需要对数据进行缩放,所以我们还需要搜索是使用 StandardScaler 还是不使用预处理
RandomForestClassifier 不需要预处理
先定义管道,显式对步骤进行命名。需要两个步骤:一个用于预处理一个用于分类器
'''

# 实例化
pipe = Pipeline([('preprocessing', StandardScaler()), ('classifier', SVC())])

'''
定义需要搜索的parameter_grid 希望classifier 是 RandomForestClassifier或 SVC
由于这两种分类器需要调节不同的参数,并且需要不同的预处理,所以我们可以使用 “在非网格的空间中搜索”中所讲的搜索网格列表
为了将一个估计器分配给一个步骤,使用步骤名称作为参数名称
如果我们想跳过管道中的某个步骤(例如,RandomForest 不需要预处理),则可以将该步骤设置为 None
'''

from sklearn.ensemble import RandomForestClassifier
param_grid = [
 {'classifier': [SVC()], 'preprocessing': [StandardScaler(), None],
 'classifier__gamma': [0.001, 0.01, 0.1, 1, 10, 100],
 'classifier__C': [0.001, 0.01, 0.1, 1, 10, 100]},
 {'classifier': [RandomForestClassifier(n_estimators=100)],
 'preprocessing': [None], 'classifier__max_features': [1, 2, 3]}]
# 将网格搜索实例化并在cancer()数据集山运行
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)
grid = GridSearchCV(pipe, param_grid, cv=5)
grid.fit(X_train, y_train)

print("Best params:\n{}\n".format(grid.best_params_))
print("Best cross-validation score: {:.2f}".format(grid.best_score_))
print("Test-set score: {:.2f}".format(grid.score(X_test, y_test)))

运行结果:
Best params:
{'classifier': SVC(C=10, gamma=0.01), 'classifier__C': 10, 'classifier__gamma': 0.01, 'preprocessing': StandardScaler()}

Best cross-validation score: 0.99
Test-set score: 0.98

网格搜索的结果是 SVC 与 StandardScaler 预处理,在 C=10 和 gamma=0.01 时给出最佳结果。

小结

本章介绍了 Pipeline 类,这是一种通用工具,可以将机器学习工作流程中的多个处理步骤链接在一起。使用管道可以将多个步骤封装为单个 Python 对象,这个对象具有我们熟悉的scikit-learn 接口 fit、predict 和 transform。特别是使用交叉验证进行模型评估与使用网格搜索进行参数选择时,使用 Pipeline 类来包括所有处理步骤对正确的评估至关重要。利用 Pipeline 类还可以让代码更加简洁,并减少不用 pipeline 类构建处理链时可能会犯的错误(比如忘记将所有变换器应用于测试集,或者应用顺序错误)的可能性。选择特征提取、预处理和模型的正确组合,通常需要一些试错。但是有了管道,这种“尝试”多个不同的处理步骤是非常简单的。在进行试验时,要小心不要将处理过程复杂化,并且一定要评估一下模型中的每个组件是否必要。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值