Lesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法

Lesson 6.5 机器学习调参基础理论与网格搜索

在上一小节执行完手动调参之后,接下来我们重点讨论关于机器学习调参的理论基础,并且介绍sklearn中调参的核心工具——GridSearchCV。

# 科学计算模块
import numpy as np
import pandas as pd

# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt

# 自定义模块
from ML_basic_function import *

# Scikit-Learn相关模块
# 评估器类
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline

# 实用函数
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

一、机器学习调参理论基础

在利用sklearn进行机器学习调参之前,我们先深入探讨一些和调参相关的机器学习基础理论。尽管我们都知道,调参其实就是去寻找一组最优参数,但最优参数中的“最优”如何定义?面对模型中的众多参数又该如何“寻找”?要回答这些问题,我们就必须补充更加完整的关于机器学习中参数和调参的理论知识。

1.机器学习调参目标及基本方法

首先需要明确的一点,我们针对哪一类参数进行调参,以及围绕什么目的进行调参?

  • 参数与超参数

根据此前对参数的划分,我们知道,影响机器学习建模结果的参数有两类,其一是参数,其二是超参数。其中参数的数值计算由一整套数学过程决定,在选定方法后,其计算过程基本不需要人工参与。因此我们经常说的模型调参,实际上是调整模型超参数。超参数种类繁多,而且无法通过一个严谨的数学流程给出最优解,因此需要人工参与进行调节。

而在围绕具体的机器学习评估器进行调参时,其实就是在调整评估器实例化过程中所涉及到的那些超参数,例如此前进行逻辑回归参数解释时的超参数,当然,这也是我们为什么需要对评估器进行如此详细的超参数的解释的原因之一。

  • 超参数调整目标

那么紧接着的问题就是,超参数的调整目标是什么?是提升模型测试集的预测效果么?

无论是机器学习还是统计模型,只要是进行预测的模型,其实核心的建模目标都是为了更好的进行预测,也就是希望模型能够有更好的预测未来的能力,换而言之,就是希望模型能够有更强的泛化能力。而在Lesson 3中我们曾谈到,机器学习类算法的可信度来源则是训练集和测试集的划分理论,也就是机器学习会认为,只要能够在模拟真实情况的测试集上表现良好,模型就能够具备良好的泛化能力。也就是说,超参数调整的核心目的是为了提升模型的泛化能力,而测试集上的预测效果只是模型泛化能力的一个具体表现,并且相比与一次测试集上的运行结果,其实借助交叉验证,能够提供更有效、更可靠的模型泛化能力的证明。

  • 交叉验证与评估指标

如果需要获得更可靠的模型泛化能力的证明,则需要进行交叉验证,通过多轮的验证,来获得模型的更为一般、同时也更为准确的运行结果。当然,我们还需要谨慎的选择一个合适的评估指标对其进行结果评估。

  • 如何提升模型泛化能力

如果拥有了一个更加可信的、用于验证模型是否具有泛化能力的评估方式之后,那么接下来的问题就是,我们应该如何提升模型泛化能力呢?

当然,这其实是一个很大的问题,我们可以通过更好的选择模型(甚至是模型创新)、更好的特征工程、更好的模型训练等方法来提高模型泛化能力,而此处我们将要介绍的,是围绕某个具体的模型、通过更好的选择模型中的超参数,来提高模型的泛化能力。不过正如此前所说,超参数无法通过一个严谨的数学流程给出最优解,因此超参数的选择其实是经验+一定范围内枚举(也就是网格搜索)的方法来决定的。这个过程虽然看起来不是那么的cooooool,但确实目前机器学习超参数选择的通用方式,并且当我们深入进行了解之后就会发现,尽管是经验+枚举,但经验的积累和枚举技术的掌握,其实也是算法工程师建模水平的重要证明。

2.基于网格搜索的超参数的调整方法

在了解机器学习中调参的基础理论之后,接下来我们考虑一个更加具体的调参流程。实际上,尽管对于机器学习来说超参数众多,但能够对模型的建模结果产生决定性影响的超参数却不多,对于大多数超参数,我们都主要采用“经验结合实际”的方式来决定超参数的取值,如数据集划分比例、交叉验证的折数等等,而对于一些如正则化系数、特征衍生阶数等,则需要采用一个流程来对其进行调节。而这个流程,一般来说就是进行搜索与枚举,或者也被称为网格搜索(gridsearch)。

所谓搜索与枚举,指的是将备选的参数一一列出,多个不同参数的不同取值最终将组成一个参数空间(parameter space),在这个参数空间中选取不同的值带入模型进行训练,最终选取一组最优的值作为模型的最终超参数,当然,正如前面所讨论的,此处“最优”的超参数,应该是那些尽可能让模型泛化能力更好的参数。当然,在这个过程中,有两个核心问题需要注意,其一是参数空间的构成,其二是选取能够代表模型泛化能力的评估指标。接下来我们对其进行逐个讨论。

2.1 参数空间

  • 参数空间的定义

所谓参数空间,其实就是我们挑选出来的、接下来需要通过枚举和搜索来进行数值确定的参数取值范围所构成的空间。例如对于逻辑回归模型来说,如果选择penalty参数和C来进行搜索调参,则这两个参数就是参数空间的不同维度,而这两个参数的不同取值就是这个参数空间中的一系列点,例如(penalty=‘l1’, C=1)、(penalty=‘l1’, C=0.9)、(penalty=‘l2’, C=0.8)等等,就是这个参数空间内的一系列点,接下来我们就需要从中挑选组一个最优组合。

  • 参数空间构造思路

那么我们需要带入那些参数去构造这个参数空间呢?也就是我们需要选择那些参数进行调参呢?切记,调参的目的是为了提升模型的泛化能力,而保证泛化能力的核心是同时控制模型的经验风险和结构风险(既不让模型过拟合也不让模型前拟合),因此,对于逻辑回归来说,我们需要同时带入能够让模型拟合度增加、同时又能抑制模型过拟合倾向的参数来构造参数空间,即需要带入特征衍生的相关参数、以及正则化的相关参数。

一个建模流程中的特征衍生的相关参数,也是可以带入同一个参数空间进行搜索的。

2.2 交叉验证与评估指标

实际的超参数的搜索过程和我们上面讨论的模型结构风险一节中的参数选取过程略有不同,此前我们的过程是:先在训练集中训练模型,然后计算训练误差和泛化误差,通过二者误差的比较来观察模型是过拟合还是欠拟合(即评估模型泛化能力),然后再决定这些超参数应该如何调整。而在一个更加严谨的过程中,我们需要将上述“通过对比训练误差和测试误差的差异,来判断过拟合还是欠拟合”的这个偏向主观的过程变成一个更加客观的过程,即我们需要找到一个能够基于目前模型建模结果的、能代表模型泛化能力的评估指标,这即是模型建模流程更加严谨的需要,同时也是让测试集回归其本来定位的需要。

  • 评估指标选取

而这个评估指标,对于分类模型来说,一般来说就是ROC-AUC或F1-Score,并且是基于交叉验证之后的指标。我们通常会选取ROC-AUC或F1-Score,其实也是因为这两个指标的敏感度要强于准确率(详见Lesson 5中的讨论),并且如果需要重点识别模型识别1类的能力,则可考虑F1-Score,其他时候更推荐使用ROC-AUC。

  • 交叉验证过程

而为何要进行交叉验证,则主要原因是超参数的调整也需要同时兼顾模型的结构风险和经验风险,而能够表示模型结构风险的,就是不带入模型训练、但是能够对模型建模结果进行评估并且指导模型进行调整的验证集上的评估结果。

上述过程可以具体表示成如下步骤:

  • 在训练集中进行验证集划分(几折待定);
  • 带入训练集进行建模、带入验证集进行验证,并输出验证集上的模型评估指标;
  • 计算多组验证集上的评估指标的均值,作为该超参数下模型最终表现。

因此,在大多数情况下,网格搜索(gridsearch)都是和交叉验证(CV)同时出现的,这也是为什么sklearn中执行网格搜索的类名称为GridSearchCV的原因。

另外需要强调的一点是,由于交叉验证的存在,此时测试集的作用就变成了验证网格搜索是否有效,而非去验证模型是否有效(此时模型是否有效由验证集来验证)。由于此时我们提交给测试集进行测试的,都是经过交叉验证挑选出来的最好的一组参数、或者说至少是在验证集上效果不错的参数(往往也是评估指标比较高的参数),而此时如果模型在测试集上运行效果不好、或者说在测试集上评估指标表现不佳,则说明模型仍然还是过拟合,之前执行的网格搜索过程并没有很好的控制住模型的结构风险,据此我们需要调整此前的调参策略,如调整参数空间、或者更改交叉验证策略等。

当然,如果是对网格搜索的过程比较自信,也可以不划分测试集,直接带入全部数据进行模型训练。

二、基于Scikit-Learn的网格搜索调参

在了解机器学习调参基础理论之后,接下来我们来借助sklearn中的相关工具,来执行更加高效的调参工作。

1.sklearn中网格搜索的基本说明

由于网格搜索确定超参数的过程实际上帮助进行模型筛选,因此我们可以在sklearn的model_selection模块查找相关内容。要学习sklearn中的网格搜索相关功能,最好还是从查阅官网的说明文档开始,我们可以在sklearn的User Guide的3.2节中我们能看到关于网格搜索的相关内容。首先介绍官网给出的相关说明:
Lesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法_第1张图片
该说明文档开宗明义的介绍了网格搜索根本目的是为了调整超参数(Hyper-parameters),也就是评估器(estimators)中的参数,每个评估器中的参数可以通过.get_params()的方法来查看,并且建议配合交叉验证来执行。

同时,该说明文档重点指出了网格搜索中的核心要素,分别是:评估器、参数空间、搜索策略、交叉验证以及评估指标。其中参数空间、交叉验证以及评估指标我们都在此前介绍过了,而根据下文的介绍,sklearn中实际上是集成了两种不同的进行参数搜索的方法,分别是GridSearchCVRandomizedSearchCVLesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法_第2张图片
尽管都是进行网格搜索,但两种方法还是各有不同,GridSearchCV会尝试参数空间内的所有组合,而RandomizedSearchCV则会先进行采样再来进行搜索,即对某个参数空间的某个随机子集进行搜索。并且上文重点强调,这两种方法都支持先两两比对、然后逐层筛选的方法来进行参数筛选,即HalvingGridSearchCV和HalvingRandomSearchCV方法。注意,这是sklearn最新版、也就是0.24版才支持的功能,该功能的出现也是0.24版最大的改动之一,而该功能的加入,也将进一步减少网格搜索所需计算资源、加快网格搜索的速度。

由于目前sklearn中最通用的版本还是0.23版,因此0.24版中的提供的网格筛选的新功能在正课内容暂时不做介绍,后续将以加餐形式补充进行讲解。

当然,说明文档中也再次强调,由于sklearn的评估器中集成了非常多的参数,而并非所有参数都对最终建模结果有显著影响,因此为了不增加网格搜索过程计算量,推荐谨慎的构造参数空间,部分参数仍然以默认参数为主。

在介绍完基本说明文档后,接下来我们尝试调用sklearn中集成的相关方法来进行建模试验。

2.sklearn中GridSearchCV的参数解释

接下来我们详细介绍GridSearchCV的相关参数,我们知道该方法的搜索策略是“全搜索”,即对参数空间内的所有参数进行搜索,该方法在model_selection模块下,同样也是以评估器形式存在,我们可以通过如下方式进行导入:

from sklearn.model_selection import GridSearchCV

不难发现该评估器的参数主体就是此前介绍的评估器、参数空间、交叉验证以及评估指标,我们对该评估器的完整参数进行解释:

GridSearchCV?
#GridSearchCV(
#    estimator,
#    param_grid,
#    *,
#    scoring=None,
#    n_jobs=None,
#    iid='deprecated',
#    refit=True,  参数应用到所用数据
#    cv=None, 默认是五折交叉验证
#    verbose=0,
#    pre_dispatch='2*n_jobs',
#    error_score=nan,
#    return_train_score=False,
)

Lesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法_第3张图片
整体来看,上面的主要参数分为三类,分别是核心参数、评估参数和性能参数。

  • 核心参数

所谓性能参数,也就是涉及评估器训练(fit)的最核心参数,也就是estimator参数和param_grid参数,同时也是实例化评估器过程中最重要的参数。

  • 评估参数

所谓评估参数,指的是涉及到不同参数训练结果评估过程方式的参数,主要是scoring、refit和cv三个参数。当然这三个参数都不是必要参数,但这三个参数却是直接决定模型结果评估过程、并且对最终模型参数选择和模型泛化能力提升直观重要的三个参数。这三个参数各自都有一个默认值,我们先解释在默认值情况下这三个参数的运作方式,然后在进阶应用阶段讨论如何对这三个参数进行修改。

首先是关于scoring参数的选取,scoring表示选取哪一项评估指标来对模型结果进行评估。而根据参数说明文档我们知道,在默认情况下scoring的评估指标就是评估器的.score方法默认的评估指标,对于逻辑回归来说也就是准确率。也就是说在默认情况下如果是围绕逻辑回归进行网格搜索,则默认评估指标是准确率。此外,scoring参数还支持直接输入可调用对象(评估函数)、代表评估函数运行方式的字符串、字典或者list。而refit参数则表示选择一个用于评估最佳模型的评估指标,然后在最佳参数的情况下整个训练集上进行对应评估指标的计算。而cv则是关于交叉验证的相关参数,默认情况下进行5折交叉验证,并同时支持自定义折数的交叉验证、输入交叉验证评估器的交叉验证、以及根据指定方法进行交叉验证等方法。当然此组参数有非常多的设计方法,我们将在进阶应用阶段进行进一步的详解。

  • 性能参数

第三组则是关于网格搜索执行性能相关的性能参数,主要包括n_jobs和pre_dispatch参数两个,用于规定调用的核心数和一个任务按照何种方式进行并行运算。在网格搜索中,由于无需根据此前结果来确定后续计算方法,所以可以并行计算。在默认情况下并行任务的划分数量和n_jobs相同。当然,这组参数的合理设置能够一定程度提高模型网格搜索效率,但如果需要大幅提高执行速度,建议使用RandomizedSearchCV、或者使用Halving方法来进行加速。

3.sklearn中GridSearchCV的使用方法

在了解了GridSearchCV的基本方法之后,接下来我们以逻辑回归在鸢尾花数据集上建模为例,来尝试使用GridSearchCV方法进行网格调参,并同时介绍网格搜索的一般流程:

3.1 GridSearchCV评估器训练过程

  • Step 1.创建评估器

首先我们还是需要实例化一个评估器,这里可以是一个模型、也可以是一个机器学习流,网格搜索都可以对其进行调参。此处我们先从简单入手,尝试实例化逻辑回归模型并对其进行调参。

# 数据导入
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=24)

clf = LogisticRegression(max_iter=int(1e6), solver='saga')

此处将solver设置成saga,也是为了方便后续同时比较l1正则化和l2正则化时无需更换求解器。

clf.get_params()
#{'C': 1.0,
# 'class_weight': None,
# 'dual': False,
# 'fit_intercept': True,
# 'intercept_scaling': 1,
# 'l1_ratio': None,
# 'max_iter': 1000000,
# 'multi_class': 'auto',
# 'n_jobs': None,
# 'penalty': 'l2',
# 'random_state': None,
# 'solver': 'saga',
# 'tol': 0.0001,
# 'verbose': 0,
# 'warm_start': False}
  • Step 2.创建参数空间

接下来,我们就需要挑选评估器中的超参数构造参数空间,需要注意的是,我们需要挑选能够控制模型拟合度的超参数来进行参数空间的构造,例如挑选类似verbose、n_jobs等此类参数构造参数是毫无意义的。此处我们挑选penalty和C这两个参数来进行参数空间的构造。参数空间首先可以是一个字典:

param_grid_simple = {'penalty': ['l1', 'l2'],
                     'C': [1, 0.5, 0.1, 0.05, 0.01]} #C大对经验风险惩罚力度大

其中,字典的Key用参数的字符串来代表不同的参数,对应的Value则用列表来表示对应参数不同的取值范围。也就是字典的Key是参数空间的维度,而Value则是不同纬度上可选的取值。而后续的网格搜索则是在上述参数的不同组合中挑选出一组最优的参数取值。

当然,由于如此构造方法,此处自然会衍生出一个新的问题,那就是如果某个维度的参数取值对应一组新的参数,应该如何处理?例如,对于逻辑回归来说,如果penalty参数中选择弹性网参数,则会衍生出一个新的参数l1_ratio,如果我们还想考虑penalty参数选取elasticnet参数,并且同时评估l1_ratio取不同值时模型效果,则无法将上述参数封装在一个参数空间内,因为当penalty取其他值时l1_ratio并不存在。为了解决这个问题,我们可以创造多个参数空间(字典),然后将其封装在一个列表中,而该列表则表示多个参数空间的集成。例如上述问题可以进行如下表示:

param_grid_ra = [
    {'penalty': ['l1', 'l2'], 'C': [1, 0.5, 0.1, 0.05, 0.01]}, 
    {'penalty': ['elasticnet'], 'C': [1, 0.5, 0.1, 0.05, 0.01], 'l1_ratio': [0.3, 0.6, 0.9]}
]

即可表示网格搜索在l1+1、l1+0.5…空间与elasticnet+1+0.3、elasticnet+1+0.6…空间同时进行搜索。

  • Step 3.实例化网格搜索评估器

和sklearn中其他所有评估器一样,网格搜索的评估器的使用也是先实例化然后进行对其进行训练。此处先实例化一个简单的网格搜索评估器,需要输入此前设置的评估器和参数空间。

search = GridSearchCV(estimator=clf,
                      param_grid=param_grid_simple)
  • Step 4.训练网格搜索评估器

同样,我们通过fit方法即可完成评估器的训练。

search.fit(X_train, y_train)
#GridSearchCV(estimator=LogisticRegression(max_iter=1000000, solver='saga'),
#             param_grid={'C': [1, 0.5, 0.1, 0.05, 0.01],
#                         'penalty': ['l1', 'l2']})

需要知道的是,所谓的训练网格搜索评估器,本质上是在挑选不同的参数组合进行逻辑回归模型训练,而训练完成后相关结果都保存在search对象的属性中。

3.2 GridSearchCV评估器结果查看

此处我们先介绍关于网格搜索类的所有属性和方法,再来查看挑选其中重要属性的结果进行解读。

  • best_estimator_:训练完成后的最佳评估器

实际上返回的就是带有网格搜索挑选出来的最佳参数(超参数)的评估器。

search.best_estimator_
#LogisticRegression(C=1, max_iter=1000000, penalty='l1', solver='saga')

上述评估器就相当于一个包含最佳参数的逻辑回归评估器,可以调用逻辑回归评估器的所有属性:

# 查看参数
search.best_estimator_.coef_
#array([[ 0.        ,  0.        , -3.47349066,  0.        ],
#       [ 0.        ,  0.        ,  0.        ,  0.        ],
#       [-0.55506614, -0.34227663,  3.03238721,  4.12147362]])

# 查看训练误差、测试误差
search.best_estimator_.score(X_train,y_train), search.best_estimator_.score(X_test,y_test)
#(0.9732142857142857, 0.9736842105263158)

# 查看参数
search.best_estimator_.get_params()
# {'C': 1,
#  'class_weight': None,
#  'dual': False,
#  'fit_intercept': True,
#  'intercept_scaling': 1,
#  'l1_ratio': None,
#  'max_iter': 1000000,
#  'multi_class': 'auto',
#  'n_jobs': None,
#  'penalty': 'l1',
#  'random_state': None,
#  'solver': 'saga',
#  'tol': 0.0001,
#  'verbose': 0,
#  'warm_start': False}
  • best_score_:最优参数时交叉验证平均得分
search.best_score_
#0.9644268774703558

在默认情况下(未修改网格搜索评估器中评估指标参数时),此处的score就是准确率。此处有两点需要注意:

  • 其一:该指标和训练集上整体准确率不同,该指标是交叉验证时验证集准确率的平均值,而不是所有数据的准确率;
  • 其二:该指标是网格搜索在进行参数挑选时的参照依据。

其他属性方法测试

search.cv_results_
#{'mean_fit_time': array([0.05980272, 0.02458634, 0.03755803, 0.02086544, 0.00778856,
#         0.012042  , 0.00317636, 0.00887041, 0.00049868, 0.00350785]),
#  'std_fit_time': array([5.92201677e-03, 1.05405387e-03, 1.34667070e-03, 5.44148798e-04,
#         3.86861563e-04, 2.92282252e-04, 1.52451976e-04, 1.55462472e-04,
#         3.12557937e-05, 6.50887088e-05]),
#  'mean_score_time': array([0.00021739, 0.00026002, 0.00019102, 0.00014124, 0.00024142,
#         0.00024619, 0.00019336, 0.0002326 , 0.00011868, 0.00018349]),
#  'std_score_time': array([5.97641527e-05, 1.38007910e-05, 7.29972748e-05, 5.19692566e-05,
#         2.59789181e-05, 1.48881665e-05, 3.58951028e-05, 1.17117652e-05,
#         1.74159836e-06, 4.23601048e-05]),
#  'param_C': masked_array(data=[1, 1, 0.5, 0.5, 0.1, 0.1, 0.05, 0.05, 0.01, 0.01],
#               mask=[False, False, False, False, False, False, False, False,
#                     False, False],
#         fill_value='?',
#              dtype=object),
#  'param_penalty': masked_array(data=['l1', 'l2', 'l1', 'l2', 'l1', 'l2', 'l1', 'l2', 'l1',
#                     'l2'],
#               mask=[False, False, False, False, False, False, False, False,
#                     False, False],
#         fill_value='?',
#              dtype=object),
#  'params': [{'C': 1, 'penalty': 'l1'},
#   {'C': 1, 'penalty': 'l2'},
#   {'C': 0.5, 'penalty': 'l1'},
#   {'C': 0.5, 'penalty': 'l2'},
#   {'C': 0.1, 'penalty': 'l1'},
#   {'C': 0.1, 'penalty': 'l2'},
#   {'C': 0.05, 'penalty': 'l1'},
#   {'C': 0.05, 'penalty': 'l2'},
#   {'C': 0.01, 'penalty': 'l1'},
#   {'C': 0.01, 'penalty': 'l2'}],
#  'split0_test_score': array([1.        , 1.        , 1.        , 1.        , 1.        ,
#         1.        , 0.82608696, 1.        , 0.30434783, 0.91304348]),
#  'split1_test_score': array([0.91304348, 0.91304348, 0.82608696, 0.86956522, 0.82608696,
#         0.73913043, 0.69565217, 0.73913043, 0.39130435, 0.69565217]),
#  'split2_test_score': array([1.        , 1.        , 1.        , 1.        , 0.95454545,
#         0.95454545, 0.86363636, 0.90909091, 0.36363636, 0.86363636]),
#  'split3_test_score': array([0.95454545, 0.95454545, 0.95454545, 0.90909091, 0.95454545,
#         0.95454545, 0.86363636, 0.90909091, 0.36363636, 0.90909091]),
#  'split4_test_score': array([0.95454545, 0.95454545, 0.95454545, 0.95454545, 0.95454545,
#         0.90909091, 0.86363636, 0.95454545, 0.36363636, 0.90909091]),
#  'mean_test_score': array([0.96442688, 0.96442688, 0.94703557, 0.94664032, 0.93794466,
#         0.91146245, 0.82252964, 0.90237154, 0.35731225, 0.85810277]),
#  'std_test_score': array([0.03276105, 0.03276105, 0.06379941, 0.05120065, 0.05863407,
#         0.09083516, 0.06508431, 0.08830786, 0.02856808, 0.08323326]),
#  'rank_test_score': array([ 1,  1,  3,  4,  5,  6,  9,  7, 10,  8], dtype=int32)}
search.best_params_
#{'C': 1, 'penalty': 'l1'}
search.best_index_
#0
# 等价于search.best_estimator_.score
search.score(X_train,y_train), search.score(X_test,y_test)
#(0.9732142857142857, 0.9736842105263158)
search.n_splits_
#5
search.refit_time_
#0.07661604881286621

至此,我们就执行了一个完整的网格搜索的调参过程。但该过程大多只使用了默认参数在小范围内进行的运算,如果我们希望更换模型评估指标、并且在一个更加完整的参数范围内进行搜索,则需要对上述过程进行修改,并更近一步掌握关于评估器中scoring参数和refit参数的相关使用方法,相关内容我们将在哦下一小节进行详细讨论。

Lesson 6.6.1 多分类评估指标的macro与weighted过程

在正式讨论关于网格搜索的进阶使用方法之前,我们需要先补充一些关于多分类问题的评估指标计算过程。在此前的课程中,我们曾经介绍过分类模型在解决多分类问题时的不同策略,同时也介绍过二分类问题的更高级评估指标,如f1-score和roc-auc等,接下来我们将详细讨论关于多分类预测结果在f1-socre和roc-auc中的评估过程,以及在sklearn中如何调用函数进行计算。

# 科学计算模块
import numpy as np
import pandas as pd

# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt

# 自定义模块
from ML_basic_function import *

# Scikit-Learn相关模块
# 评估器类
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import GridSearchCV

# 实用函数
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 数据准备
from sklearn.datasets import load_iris
  • 多分类F1-Score评估指标

首先导入和F1-Score相关的评估指标计算函数

from sklearn.metrics import precision_score,recall_score,f1_score

然后简单查看相关说明文档,发现这几组和混淆矩阵相关的评估指标基本是共用了一套参数命名,并且大多数参数其实都是作用于多分类问题,对于二分类问题,我们可以简单调用相关函数直接计算:

y_true = np.array([1, 0, 0, 1, 0, 1])
y_pred = np.array([1, 1, 0, 1, 0, 1])

precision_score(y_true, y_pred), recall_score(y_true, y_pred), f1_score(y_true, y_pred)
#(0.75, 1.0, 0.8571428571428571)
precision_score?
#Signature:
#precision_score(
#    y_true,
#    y_pred,
#    *,
#    labels=None,
#    pos_label=1,
#    average='binary',
#    sample_weight=None,
#    zero_division='warn',
#)

具体参数含义解释如下:

其中,需要重点介绍多分类问题时average参数不同取值时的计算方法。此处以recall为例进行计算,重点介绍当average取值为’macro’、'micro’和’weighted’的情况,其他指标也类似,例如有简单多分类问题如下:

我们令1类标签为0、2类标签为1、3类标签为2,则上述数据集真实标签为:

y_true = np.array([0, 1, 2, 2, 0, 1, 1, 2, 0, 2])

并且最终分类预测结果为:

y_pred = np.array([0, 1, 0, 2, 2, 1, 2, 2, 0, 2])

据此我们可以构造多分类混淆矩阵如下:
Lesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法_第4张图片
据此我们可以计算三个类别的TP和FN:

tp1 = 2
tp2 = 2
tp3 = 3
fn1 = 1
fn2 = 1
fn3 = 1

接下来有两种计算recall的方法,其一是先计算每个类别的recall,然后求均值:

re1 = 2/3
re2 = 2/3
re3 = 3/4
np.mean([re1, re2, re3])
#0.6944444444444443

这也就是average参数取值为macro时的计算结果:

recall_score(y_true, y_pred, average='macro')
#0.6944444444444443

当然,如果上述手动实现过程不求均值,而是根据每个类别的数量进行加权求和,则就是参数average参数取值为weighted时的结果:

re1 * 3/10 + re2 * 3/10 + re3 * 4/10
#0.7
recall_score(y_true, y_pred, average='weighted')
#0.7

当然,还有另外一种计算方法,那就是先计算整体的TP和FN,然后根据整体TP和FN计算recall:

tp = tp1 + tp2 + tp3
fn = fn1 + fn2 + fn3
tp / (tp+fn)
#0.7

该过程也就是average参数取值micro时的计算结果:

recall_score(y_true, y_pred, average='micro')
#0.7

对于上述三个不同参数的选取,首先如果是样本不平衡问题(如果是要侧重训练模型判别小类样本的能力的情况下)、则应排除weighted参数,以避免赋予大类样本更高的权重。除此以外,在大多数情况下这三个不同的参数其实并不会对最后评估器的选取结果造成太大影响,只是在很多要求严谨的场合下需要说明多分类的评估结果的计算过程,此时需要简单标注下是按照何种方法进行的计算。

不过,如果是混淆矩阵中相关指标和roc-auc指标放在一起讨论,由于新版sklearn中roc-auc本身不支持在多分类时按照micro计算、只支持macro计算,因此建议混淆矩阵的多分类计算过程也选择macro过程,以保持一致。后续在没有进行其他特殊说明的情况下,课上统一采用macro指标进行多分类问题评估指标的计算。

不过值得注意的是,还有一种观点,尽管micro和macro方法在混淆矩阵相关指标的计算过程中差别不大,在roc-auc中,macro指标并不利于非平衡样本的计算(混淆矩阵中可以通过positive的类别选择来解决这一问题),需要配合ovr分类方法才能够有所改善。

  • 多分类ROC-AUC评估指标

接下来继续讨论关于多分类的ROC-AUC评估指标的相关问题:

from sklearn.metrics import roc_auc_score

能够发现,roc_auc_score评估指标函数中大多数参数都和此前介绍的混淆矩阵中评估指标类似。接下来我们简单尝试使用roc-auc函数进行评估指标计算,根据roc-auc的计算流程可知,此处我们需要在y_pred参数位中输入模型概率预测结果:

y_true = np.array([1, 0, 0, 1, 0, 1])
y_pred = np.array([0.9, 0.7, 0.2, 0.7, 0.4, 0.8])
roc_auc_score(y_true, y_pred)
#0.9444444444444444

当然,如果我们在y_pred参数中输入分类结果,该函数也能计算出最终结果:

y_true = np.array([1, 0, 0, 1, 0, 1])
y_pred = np.array([1, 1, 0, 1, 0, 1])
roc_auc_score(y_true, y_pred)
#0.8333333333333334

不过,此时模型会默认预测标签为0的概率结果为0.4、预测标签为1的概率预测结果为0.6,即上述结果等价于:

y_true = np.array([1, 0, 0, 1, 0, 1])
y_pred = np.array([0.6, 0.6, 0.4, 0.6, 0.4, 0.6])
roc_auc_score(y_true, y_pred)
#0.8333333333333334

即计算过程会默认模型概率预测结果更差。

接下来详细解释roc-auc中其他参数:

roc_auc_score?
#Signature:
#roc_auc_score(
#    y_true,
#    y_score,
#    *,
#    average='macro',
#    sample_weight=None,
#    max_fpr=None,
#    multi_class='raise',
#    labels=None,

#multi_class : {'raise', 'ovr', 'ovo'}, default='raise'
#    Multiclass only. Determines the type of configuration to use. The
#    default value raises an error, so either ``'ovr'`` or ``'ovo'`` must be
#    passed explicitly.
#raise多分类报错,mvm很多时候和ovr有相通的地方
#二分类分类器在解决多分类问题,用什么这边用什么

#average : {'micro', 'macro', 'samples', 'weighted'} or None,             #default='macro'
#    If ``None``, the scores for each class are returned. Otherwise,
#    this determines the type of averaging performed on the data:
#    Note: multiclass ROC AUC currently only handles the 'macro' and
#    'weighted' averages.
#)

Lesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法_第5张图片
此处需要注意的是关于multi_class参数的选择。一般来说sklearn中的multi_class参数都是二分类器中用于解决多元分类问题时的参数(如逻辑回归),而由于roc-auc需要分类结果中的概率来完成最终计算,因此需要知道概率结果对应分类标签——即到底是以ovo还是ovr模式在进行多分类,因此如果是进行多分类roc-auc计算时,需要对其进行明确说明。

不过对于多分类逻辑回归来说,无论是ovr还是mvm策略,最终分类结果其实都可以看成是ovr分类结果,因此如果是多分类逻辑回归计算roc-auc,需要设置multi_class参数为ovr。同时由于根据roc-auc的函数参数说明可知,在multi_class参数取为ovr时,average参数取值为macro时能够保持一个较高的偏态样本敏感性,因此对于roc-auc来说,大多数时候average参数建议取值为macro。总结一下,对于roc-auc进行多分类问题评估时,建议选择的参数组合是ovr/ovo+macro,而ovr/ovo的参数选择需要根据具体的多分类模型来定,如果是围绕逻辑回归多分类评估器来进行结果评估,则建议roc-auc和逻辑回归评估器的multi_class参数都选择ovr。

在新版的sklearn中,roc-auc函数的multi_class参数已不支持micro参数,面对多分类问题,该参数只能够在macro和weighted中进行选择。

接下来我们简单测算average参数中macro和weighted的计算过程。还是围绕上述数据集进行计算:
据此我们可以计算每个类别单独的roc-auc值:

y_true_1 = np.array([1, 0, 0, 0, 1, 0, 0, 0, 1, 0])
y_pred_1 = np.array([0.8, 0.2, 0.5, 0.2, 0.3, 0.1, 0.3, 0.3, 0.9, 0.3])
r1 = roc_auc_score(y_true_1, y_pred_1)
r1
#0.8809523809523809
y_true_2 = np.array([0, 1, 0, 0, 0, 1, 1, 0, 0, 0])
y_pred_2 = np.array([0.2, 0.6, 0.3, 0, 0.2, 0.8, 0.2, 0.3, 0, 0.1])
r2 = roc_auc_score(y_true_2, y_pred_2)
r2
#0.8571428571428571
y_true_3 = np.array([0, 0, 1, 1, 0, 0, 0, 1, 0, 1])
y_pred_3 = np.array([0, 0.2, 0.2, 0.8, 0.5, 0.1, 0.5, 0.4, 0.1, 0.6])
r3 = roc_auc_score(y_true_3, y_pred_3)
r3
#0.8125

此时r1、r2、r3的均值如下:

np.mean([r1, r2, r3])
#0.8501984126984127

该结果应当和macro+multi_class参数计算结果相同

y_pred = np.concatenate([y_pred_1.reshape(-1, 1), y_pred_2.reshape(-1, 1), y_pred_3.reshape(-1, 1)], 1)
y_pred
#array([[0.8, 0.2, 0. ],
#       [0.2, 0.6, 0.2],
#       [0.5, 0.3, 0.2],
#       [0.2, 0. , 0.8],
#       [0.3, 0.2, 0.5],
#       [0.1, 0.8, 0.1],
#       [0.3, 0.2, 0.5],
#       [0.3, 0.3, 0.4],
#       [0.9, 0. , 0.1],
#       [0.3, 0.1, 0.6]])
y_true = np.array([0, 1, 2, 2, 0, 1, 1, 2, 0, 2])
roc_auc_score(y_true, y_pred, average='macro', multi_class='ovr')
#0.8501984126984127

当然,如果roc-auc函数的参数是ovr+weighted,则计算结果过程验证如下:

r1 * 3/10 + r2 * 3/10 + r3 * 4/10
#0.8464285714285713
roc_auc_score(y_true, y_pred, average='weighted', multi_class='ovr')
#0.8464285714285713

至此,我们就能够较为清楚的了解关于f1-score和roc-auc评估指标在调用sklearn中相关函数解决多分类问题评估的具体方法。

Lesson 6.6.2 GridSearchCV的进阶使用方法

在Lesson 6.5中我们已经完整总结了机器学习调参的基本理论,同时介绍了sklearn中网格搜索(GridSearchCV)评估器的参数及基本使用方法。本节我们将进一步介绍网格搜索的进阶使用方法,并同时补充多分类问题评估指标在sklearn中实现的相关方法,然后围绕Lesson 6.4中提出的问题给出一个基于网格搜索的解决方案。

# 科学计算模块
import numpy as np
import pandas as pd

# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt

# 自定义模块
from ML_basic_function import *

# Scikit-Learn相关模块
# 评估器类
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import GridSearchCV

# 实用函数
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 数据准备
from sklearn.datasets import load_iris

一、借助机器学习流构建全域参数搜索空间

首先是关于评估器全参数的设置方法。在此前的实验中,我们只是保守的选取了部分我们觉得会对模型产生比较大影响的超参数来构建参数空间,但在实际场景中,调参应该是纳入所有对模型结果有影响的参数进行搜索、并且是全流程中的参数来进行搜索。也就是说我们设置参数的空间的思路不应该更加“激进”一些,首先是对逻辑回归评估器来说,应该是排除无用的参数外纳入所有参数进行调参,并且就逻辑回归模型来说,往往我们需要在模型训练前进行特征衍生以增强模型表现,因此我们应该先构建一个包含多项式特征衍生的机器学习流、然后围绕这个机器学习流进行参数搜索,这才是一个更加完整的调参过程。

首先,仿造Lesson 6.4中展示过程创造数据集如下:

np.random.seed(24)
X = np.random.normal(0, 1, size=(1000, 2))
y = np.array(X[:,0]+X[:, 1]**2 < 1.5, int)

plt.scatter(X[:, 0], X[:, 1], c=y)

Lesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法_第6张图片

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state = 42)

然后开始构造机器学习流

# 构造机器学习流
pipe = make_pipeline(PolynomialFeatures(), 
                     StandardScaler(), 
                     LogisticRegression(max_iter=int(1e6)))
# 查看参数
pipe.get_params()
# {'memory': None,
#  'steps': [('polynomialfeatures', PolynomialFeatures()),
#   ('standardscaler', StandardScaler()),
#   ('logisticregression', LogisticRegression(max_iter=1000000))],
#  'verbose': False,
#  'polynomialfeatures': PolynomialFeatures(),
#  'standardscaler': StandardScaler(),
#  'logisticregression': LogisticRegression(max_iter=1000000),
#  'polynomialfeatures__degree': 2,
#  'polynomialfeatures__include_bias': True,
#  'polynomialfeatures__interaction_only': False,
#  'polynomialfeatures__order': 'C',
#  'standardscaler__copy': True,
#  'standardscaler__with_mean': True,
#  'standardscaler__with_std': True,
#  'logisticregression__C': 1.0,
#  'logisticregression__class_weight': None,
#  'logisticregression__dual': False,
#  'logisticregression__fit_intercept': True,
#  'logisticregression__intercept_scaling': 1,
#  'logisticregression__l1_ratio': None,
#  'logisticregression__max_iter': 1000000,
#  'logisticregression__multi_class': 'auto',
#  'logisticregression__n_jobs': None,
#  'logisticregression__penalty': 'l2',
#  'logisticregression__random_state': None,
#  'logisticregression__solver': 'lbfgs',
#  'logisticregression__tol': 0.0001,
#  'logisticregression__verbose': 0,
#  'logisticregression__warm_start': False}

# 构造参数空间
param_grid = [
    {'polynomialfeatures__degree': np.arange(2, 10).tolist(), 'logisticregression__penalty': ['l1'], 'logisticregression__C': np.arange(0.1, 2, 0.1).tolist(), 'logisticregression__solver': ['saga']}, 
    {'polynomialfeatures__degree': np.arange(2, 10).tolist(), 'logisticregression__penalty': ['l2'], 'logisticregression__C': np.arange(0.1, 2, 0.1).tolist(), 'logisticregression__solver': ['lbfgs', 'newton-cg', 'sag', 'saga']},
    {'polynomialfeatures__degree': np.arange(2, 10).tolist(), 'logisticregression__penalty': ['elasticnet'], 'logisticregression__C': np.arange(0.1, 2, 0.1).tolist(), 'logisticregression__l1_ratio': np.arange(0.1, 1, 0.1).tolist(), 'logisticregression__solver': ['saga']}
]

二、优化评估指标选取

1.高级评估指标的选用方法

根据此前介绍,如果需要更好的验证模型本身泛化能力,建议使用f1-score或者roc-auc,当然调整网格搜索过程的模型评估指标过程其实并不难理解,核心就是修改scoring参数取值。但由于涉及到在参数中调用评估函数,因此需要补充一些关于常用分类评估指标在sklearn中的函数使用方法,以及不同评估指标函数在不同参数取值时在网格搜索评估器中的调用方法。

GridSearchCV?

从评估器的说明文档中能够看出,scoring参数最基础的情况下可以选择输入str(字符串)或者callable(可调用)对象,也就是可以输入指代某个评估过程的字符串(一个字符串代表不同参数取值下的某评估函数),或者直接输入某评估指标函数(或者通过make_score函数创建的函数),来进行模型结果的评估。当然,也可以在该参数位上直接输入一个字典或者list,其中,如果是字典的话字典的value需要是str(字符串)或者callable(可调用)对象。这里看似复杂但实际上不难理解,由于sklearn中的评估指标函数一般都是有多个不同参数,而不同参数代表不同的计算过程,因此这些评估指标函数作为参数输入网格搜索评估器中的时候,必须通过“某种方式”确定这些参数取值,因此就有了如下方法,即通过字符串对应表来查看不同字符串所代表的不同参数取值下的评估指标函数,如下所示:

相关说明可以在Scikit-Learn官网的的3.3. Metrics and scoring: quantifying the quality of predictions处查阅:

Lesson 6.5&Lesson 6.6.1&Lesson 6.6.2 机器学习调参基础理论与网格搜索&多分类评估指标的macro与weighted过程&GridSearchCV的进阶使用方法_第7张图片
不难看出,在网格搜索中输出评估指标参数,和调用评估指标函数进行数据处理还是有很大的区别。例如,metrics.roc_auc_score函数能够同时处理多分类问题和二分类问题,但如果作为参数输入到网格搜索中,roc_auc参数只能指代metrics.roc_auc_score函数的二分类功能,如果需要进行多分类,则需要在scoring参数中输入roc_auc_ovrroc_auc_ovo或者roc_auc_ovr_weightedroc_auc_ovo_weighted。我们先简单尝试在scoring中输入字符串的基本操作,然后在深入解释roc-auc评估指标的使用方法。

同时,该参数列表也可以通过如下方式获得:

import sklearn
sorted(sklearn.metrics.SCORERS.keys())
['accuracy',
#  'adjusted_mutual_info_score',
#  'adjusted_rand_score',
#  'average_precision',
#  'balanced_accuracy',
#  'completeness_score',
#  'explained_variance',
#  'f1',
#  'f1_macro',
#  'f1_micro',
#  'f1_samples',
#  'f1_weighted',
#  'fowlkes_mallows_score',
#  'homogeneity_score',
#  'jaccard',
#  'jaccard_macro',
#  'jaccard_micro',
#  'jaccard_samples',
#  'jaccard_weighted',
#  'max_error',
#  'mutual_info_score',
#  'neg_brier_score',
#  'neg_log_loss',
#  'neg_mean_absolute_error',
#  'neg_mean_gamma_deviance',
#  'neg_mean_poisson_deviance',
#  'neg_mean_squared_error',
#  'neg_mean_squared_log_error',
#  'neg_median_absolute_error',
#  'neg_root_mean_squared_error',
#  'normalized_mutual_info_score',
#  'precision',
#  'precision_macro',
#  'precision_micro',
#  'precision_samples',
#  'precision_weighted',
#  'r2',
#  'recall',
#  'recall_macro',
#  'recall_micro',
#  'recall_samples',
#  'recall_weighted',
#  'roc_auc',
#  'roc_auc_ovo',
#  'roc_auc_ovo_weighted',
#  'roc_auc_ovr',
#  'roc_auc_ovr_weighted',
#  'v_measure_score']
from sklearn.metrics import roc_auc_score
roc_auc_score?

例如字符串roc_auc_ovr就代表roc_auc_score函数中multi_class参数取值为ovr时的计算流程,也就是说,当网格搜索的scoring参数取值为字符串roc_auc_ovr时,就代表调用了multi_class=`ovr`、而其他参数选用默认参数的roc_auc_score函数作为模型评估函数。

GridSearchCV(estimator=pipe,
             param_grid=param_grid,
             scoring='roc_auc_ovr')
# GridSearchCV(estimator=Pipeline(steps=[('polynomialfeatures',
#                                         PolynomialFeatures()),
#                                        ('standardscaler', StandardScaler()),
#                                        ('logisticregression',
#                                         LogisticRegression(max_iter=1000000))]),
#              param_grid=[{'logisticregression__C': [0.1, 0.2,
#                                                     0.30000000000000004, 0.4,
#                                                     0.5, 0.6,
#                                                     0.7000000000000001, 0.8,
#                                                     0.9, 1.0, 1.1,
#                                                     1.2000000000000002,
#                                                     1.3000000000000003,
#                                                     1.4000000000000001...
#                                                     1.4000000000000001,
#                                                     1.5000000000000002, 1.6,
#                                                     1.7000000000000002,
#                                                     1.8000000000000003,
#                                                     1.9000000000000001],
#                           'logisticregression__l1_ratio': [0.1, 0.2,
#                                                            0.30000000000000004,
#                                                            0.4, 0.5, 0.6,
#                                                            0.7000000000000001,
#                                                            0.8, 0.9],
#                           'logisticregression__penalty': ['elasticnet'],
#                           'logisticregression__solver': ['saga'],
#                           'polynomialfeatures__degree': [2, 3, 4, 5, 6, 7, 8,
#                                                          9]}],
#              scoring='roc_auc_ovr')

当然,scoring参数还支持直接输入可调用对象,即支持输入经过make_scorer函数转化之后的评估指标函数:

from sklearn.metrics import make_scorer
acc = make_scorer(roc_auc_score)
GridSearchCV(estimator=pipe,
             param_grid=param_grid,
             scoring=acc)
# GridSearchCV(estimator=Pipeline(steps=[('polynomialfeatures',
#                                         PolynomialFeatures()),
#                                        ('standardscaler', StandardScaler()),
#                                        ('logisticregression',
#                                         LogisticRegression(max_iter=1000000))]),
#              param_grid=[{'logisticregression__C': [0.1, 0.2,
#                                                     0.30000000000000004, 0.4,
#                                                     0.5, 0.6,
#                                                     0.7000000000000001, 0.8,
#                                                     0.9, 1.0, 1.1,
#                                                     1.2000000000000002,
#                                                     1.3000000000000003,
#                                                     1.4000000000000001...
#                                                     1.4000000000000001,
#                                                     1.5000000000000002, 1.6,
#                                                     1.7000000000000002,
#                                                     1.8000000000000003,
#                                                     1.9000000000000001],
#                           'logisticregression__l1_ratio': [0.1, 0.2,
#                                                            0.30000000000000004,
#                                                            0.4, 0.5, 0.6,
#                                                            0.7000000000000001,
#                                                            0.8, 0.9],
#                           'logisticregression__penalty': ['elasticnet'],
#                           'logisticregression__solver': ['saga'],
#                           'polynomialfeatures__degree': [2, 3, 4, 5, 6, 7, 8,
#                                                          9]}],
#              scoring=make_scorer(roc_auc_score))

但此时我们无法修改评估指标函数的默认参数。

值得注意的是,此处make_scorer函数实际上会将一个简单的评估指标函数转化为评估器结果评估函数。对于评估指标函数来说,只需要输入标签的预测值和真实值即可进行计算,例如:

accuracy_score([1, 1, 0], [1, 1, 1])
#0.6666666666666666

而评估器结果评估函数,则需要同时输入评估器、特征矩阵以及对应的真实标签,其执行过程是先将特征矩阵输入评估器、然后将输出结果和真实标签进行对比:

acc = make_scorer(accuracy_score)
acc(search.best_estimator_, X_train, y_train)
#0.9732142857142857
search.score(X_train, y_train)
#0.9732142857142857

而在网格搜索或者交叉验证评估器中,只支持输入经过make_scorer转化后的评估指标函数。

2.同时输入多组评估指标

当然,有的时候我们可能需要同时看不同参数下多项评估指标的结果,此时我们就可以在scoring中输入列表、元组或者字典,当然字典对象会较为常用,例如如果我们需要同时选用roc-auc和accuracy作为模型评估指标,则需要创建如下字典:

scoring = {'AUC': 'roc_auc', 'Accuracy': make_scorer(accuracy_score)}

然后将其作为参数传入网格搜索评估器内:

search = GridSearchCV(estimator=clf,
                     param_grid=param_grid_simple
                     scoring=scoring)

当然,roc-auc指标也可以用make_score来传输,accuracy也可以用字符串形式来传输,即来上述多评估指标的字典等价于:

{'AUC': make_scorer(roc_auc_score), 'Accuracy': 'accuracy'}
#{'AUC': make_scorer(roc_auc_score), 'Accuracy': 'accuracy'}

不过,需要注意的是,尽管此时网格搜索评估器将同时计算一组参数下的多个评估指标结果并输出,但我们只能选取其中一个评估指标作为挑选超参数的依据,而其他指标尽管仍然会计算,但结果只作参考。而refit参数中输入的评估指标,就是最终选择参数的评估指标

尽管网格搜索支持依据不同的评估指标进行参数搜索,但最终选择何种参数,可以参考如下依据:

  • 有明确模型评估指标的

在很多竞赛或者项目算法验收环节,可能都会存在较为明确的模型评估指标,例如模型排名根据f1-score计算结果得出等。在该情况下,应当尽量选择要求的评估指标。

  • 没有明确模型评估指标的

但是,如果没有明确的评估指标要求,则选择评估指标最核心的依据就是尽可能提升/确保模型的泛化能力。此时,根据Lesson 5中对各评估指标的讨论结果,如果数据集的各类别并没有明确的差异,在算力允许的情况下,应当优先考虑roc-auc;而如果希望重点提升模型对类别1(或者某类别)的识别能力,则可以优先考虑f1-score作为模型评估指标。

search = GridSearchCV(estimator=clf,
                     param_grid=param_grid_simple
                     scoring=scoring)
                     refit="AUC"

三、优化后建模流程

接下来,依据上述优化后的过程,来执行网格搜索。完整流程如下:

  • 构造机器学习流
# 构造机器学习流
pipe = make_pipeline(PolynomialFeatures(), 
                     StandardScaler(), 
                     LogisticRegression(max_iter=int(1e6)))
  • 构造包含多个评估器的机器学习流的参数空间
# 构造参数空间
param_grid = [
    {'polynomialfeatures__degree': np.arange(2, 10).tolist(), 'logisticregression__penalty': ['l1'], 'logisticregression__C': np.arange(0.1, 2, 0.1).tolist(), 'logisticregression__solver': ['saga']}, 
    {'polynomialfeatures__degree': np.arange(2, 10).tolist(), 'logisticregression__penalty': ['l2'], 'logisticregression__C': np.arange(0.1, 2, 0.1).tolist(), 'logisticregression__solver': ['lbfgs', 'newton-cg', 'sag', 'saga']},
    {'polynomialfeatures__degree': np.arange(2, 10).tolist(), 'logisticregression__penalty': ['elasticnet'], 'logisticregression__C': np.arange(0.1, 2, 0.1).tolist(), 'logisticregression__l1_ratio': np.arange(0.1, 1, 0.1).tolist(), 'logisticregression__solver': ['saga']}
]
  • 实例化网格搜索评估器

考虑到实际参数空间较大,网格搜索需要耗费较长时间,此处使用单一指标roc作为参数选取指标进行搜索:

search = GridSearchCV(estimator=pipe,
                      param_grid=param_grid, 
                      scoring='roc_auc', 
                      n_jobs=5)
  • 执行训练
search.fit(X_train, y_train)
  • 查看结果
search.best_score_
#0.7879905483853072
search.best_params_
#{'logisticregression__C': 0.2,
# 'logisticregression__penalty': 'l1',
# 'logisticregression__solver': 'saga',
# 'polynomialfeatures__degree': 3}

需要注意的是,上述best_score_属性查看的结果是在roc-auc评估指标下,默认五折交叉验证时验证集上的roc-auc的平均值,但如果我们对训练好的评估器使用.socre方法,查看的仍然是pipe评估器默认的结果评估方式,也就是准确率计算结果:

search.best_estimator_.score(X_train,y_train)
#0.7857142857142857
search.best_estimator_.score(X_test,y_test)
#0.7866666666666666

验证准确率计算结果:

accuracy_score(search.best_estimator_.predict(X_train), y_train)
#0.7857142857142857
accuracy_score(search.best_estimator_.predict(X_test), y_test)
#0.7866666666666666
  • 结果分析

最终模型结果准确率在78%上下。当然,如果只看模型准确率结果,我们发现该结果相比Lesson 6.4中结果较差(Lesson 6.4中测试集最高得分达到0.8)。但是,该模型相比Lesson 6.4中模型来看,该模型基本没有过拟合隐患(测试集分数甚至高于训练集),因此该模型在未来的使用过程中更有可能能够确保一个稳定的预测输出结果(泛化能力更强)。这也是交叉验证和roc-auc共同作用的结果。

当然,如果有明确要求根据准确率判断模型效果,则上述过程应该选择准确率,同时如果算力允许,也可以近一步扩大搜索空间(Lesson 6.4中0.8的准确率就是在15阶多项式特征衍生基础上进行的运算)。

至此,我们就完成了在实验数据上的建模调优,在下一小节,我们将把上述技巧应用到一项kaggle数据集上来进行建模分析,届时我们还将补充更多实战过程中会用到的方法和技巧。

你可能感兴趣的:(机器学习,机器学习,分类,sklearn,网格搜索)