简介
- 执行完手动调参后,接下来重点讨论机器学习调参的理论基础,并介绍sklearn中调参的核心工具
GridSearchCV
理论基础
- 调参其实就是去寻找一组最优参数,但最优参数中的 “最优” 如何界定?
调参目标
- 先要明确的一点,我们针对哪一类参数进行调参,以及围绕什么目的进行调参?
- 影响机器学习建模结果的参数有两类,其一是参数,其二是超参数
- 其中参数的数值计算由一整套数学过程决定,在选定方法后,其计算过程基本不需要人工参与
- 只需要算
- 因此,常说的模型调参,实际上是调整模型超参数
- 超参数种类繁多,也无法通过一个严谨的数学流程给出最优解,因此需要人工参与进行调节
- 不仅要算,还要分析,这才是发挥聪明才智的地方
- 这也是此前我们为什么对评估器进行详细的超参数的解释的原因,例如逻辑回归参数解释时的超参数
参数 解释 penalty 正则化项 dual 是否求解对偶问题* tol 迭代停止条件:两轮迭代损失值差值小于tol时,停止迭代 C 经验风险和结构风险在损失函数中的权重 fit_intercept 线性方程中是否包含截距项 intercept_scaling 相当于此前讨论的特征最后一列全为1的列,当使用liblinear求解参数时用于捕获截距 class_weight 各类样本权重* random_state 随机数种子 solver 损失函数求解方法* max_iter 求解参数时最大迭代次数,迭代过程满足 max_iter 或 tol 其一即停止迭代 multi_class 多分类问题时求解方法* verbose 是否输出任务进程 warm_start 是否使用上次训练结果作为本次运行初始参数 l1_ratio 当采用弹性网正则化时, l 1 l1 l1正则项权重,就是损失函数中的 ρ \rho ρ - 超参数调整的目标是什么?是提升模型在测试集上的预测效果么?
- 无论是机器学习还是统计模型,只要是进行预测的模型,核心的建模目标都是为了更好的进行预测,也就是希望模型能够有更好的预测未来的能力,换言之,就是希望模型能够有更强的泛化能力
- 之前谈到,机器学习类算法的可信度来源则是训练集和测试集的划分理论,也就是机器学习会认为,只要能够在模拟真实情况的测试集上表现良好,模型就能够具备良好的泛化能力
- 也就是说,超参数调整的核心目的是为了提升模型的泛化能力,而测试集上的预测效果只是模型泛化能力的一个具体表现,并且相比与一次测试集上的运行结果,其实借助交叉验证,能够提供更有效、更可靠的模型泛化能力的证明
- 如何提升模型泛化能力?
- 当然,这其实是一个很大的问题,我们可以通过更好的选择模型(甚至是模型创新)、更好的特征工程、更好的模型训练等方法来提高模型泛化能力,而此处将要介绍的,是围绕某个具体的模型、通过更好的选择模型中的超参数,来提高模型的泛化能力
- 超参数无法通过一个严谨的数学流程给出最优解,因此超参数的选择其实是经验+一定范围内枚举(也就是网格搜索)的方法来决定的
- 这个过程虽然看起来不是那么的让人五体投地,但确实是目前超参数调整选择的通用方式,并且当我们深入进行了解之后就会发现,尽管是经验+枚举,但经验的积累和枚举技术的掌握,其实也是算法工程师建模水平的重要证明
基于网格搜索的超参数调整方法
- 尽管超参数众多,但能够对模型的建模结果产生决定性影响的超参数却不多
- 对于大多数超参数,我们采用经验结合实际的方式来决定超参数的取值,如数据集划分比例、交叉验证的折数等等(直接定),而对于一些如正则化系数、特征衍生阶数等,则需要采用一个流程来对其进行调节(网格搜索)
- 所谓搜索与枚举,指的是将备选的参数一一列出,多个不同参数的不同取值将组成一个参数空间(parameter space),在这个参数空间中选取不同的值带入模型进行训练,最终选取一组最优的值作为模型的最终超参数
- 当然,正如前面所讨论的,此处“最优”的超参数,应该是那些尽可能让模型泛化能力更好的参数(交叉验证),不要单一的理解成准确率高
- 在这个过程中,有两个核心问题需要注意,其一是参数空间的构成,其二是选取能够代表模型泛化能力的评估指标
参数空间
- 所谓参数空间,其实就是我们挑选出来的、接下来需要通过枚举和搜索来进行数值确定的参数取值范围所构成的空间
- 例如对于逻辑回归模型来说,如果选择
penalty
参数和C
来进行搜索调参,则这两个参数就是两个不同维度,而这两个参数的不同取值就是这个参数空间中的一系列点,例如 (penalty=‘l1’, C=1)、(penalty=‘l1’, C=0.9)、(penalty=‘l2’, C=0.8) 等等,我们需要挑选出最优组合 - 构造思路
- 需要选择哪些参数进行调参呢?切记,调参的目的是为了提升模型的泛化能力,而保证泛化能力的核心是同时控制模型的经验风险和结构风险(既不让模型过拟合也不让模型前拟合)
- 因此,对于逻辑回归来说,我们需要同时代入能够让模型拟合度增加、同时又能抑制模型过拟合倾向的参数来构造参数空间,即需要代入控制特征衍生的相关参数、以及正则化的相关参数
交叉验证与评估指标
- 实际的超参数的搜索过程和之前讨论的模型结构风险中的参数选取过程略有不同
- 此前的过程是:先在训练集中训练模型,然后计算训练误差和泛化误差,通过二者误差的比较来观察模型是过拟合还是欠拟合(即评估模型泛化能力),然后再决定这些超参数应该如何调整
- 而在一个更加严谨的过程中,我们需要将上述“通过对比训练误差和测试误差的差异,来判断过拟合还是欠拟合”的这个偏向主观的过程变成一个更加客观的过程,即我们需要找到一个能够基于目前模型建模结果的、能代表模型泛化能力的评估指标,这既是模型建模流程更加严谨的需要,同时也是让测试集回归其本来定位的需要
- 评估指标选取
- 对于分类模型来说,一般就是
ROC-AUC
或F1-Score
,并且是基于交叉验证之后的指标 - 也是因为这两个指标的敏感度要强于准确率,并且如果需要重点提升模型识别 1 类的能力(偏态数据),则可考虑 F1-Score,其他时候更推荐使用 ROC-AUC
- 对于分类模型来说,一般就是
- 交叉验证过程
- 超参数的调整也需要同时兼顾模型的结构风险和经验风险,而能够表示模型结构风险的,就是不带入模型训练、但是能够对模型建模结果进行评估并且指导模型进行调整的验证集上的评估结果
- 具体过程
- 在数据集中进行验证集划分(几折待定)
- 带入训练集进行建模、带入验证集进行验证,并输出验证集上的模型评估结果
- 计算多组验证集上的评估指标的均值,作为该组超参数下模型的最终表现
- 大多数情况下,网格搜索和交叉验证(CV)是同时出现的,这也是为什么 sklearn 中执行网格搜索的类名称为
GridSearchCV
的原因
- 需要强调一点
- 验证集和测试集不是一回事
- 由于交叉验证的存在,此时测试集的作用就变成了验证网格搜索是否有效,而非去验证模型是否有效(模型是否有效由验证集来验证)
- 提交给测试集进行测试的,都是经过交叉验证挑选出来的最好的一组参数、或者说至少是在验证集上效果不错的参数(往往也是评估指标比较高的参数)
- 而此时如果模型在测试集上评估指标表现不佳,则说明模型仍然还是过拟合,之前执行的网格搜索过程并没有很好的控制住模型的结构风险,需要调整此前的调参策略(调整参数空间、或者更改交叉验证策略等)
- 当然,如果是对网格搜索的过程比较自信,也可以不划分测试集
- 上面是理论部分,接下来实践一把,看看具体流程是怎样的
基于Scikit-Learn的网格搜索调参
- 基本说明
- 由于网格搜索确定超参数的过程实际上是在帮助进行模型筛选,因此我们可以在sklearn的
model_selection
模块查找相关内容 - 最好还是从查阅官网的说明文档开始,我们可以在3.2节中看到关于网格搜索的相关说明
- 该说明文档重点指出了网格搜索中的核心要素,分别是:评估器、参数空间、搜索策略、交叉验证以及评估指标
- 集成了两种不同的参数搜索的方法
- 尽管都是进行网格搜索,但两种方法还是各有不同,
GridSearchCV
会尝试参数空间内的所有组合,而RandomizedSearchCV
则会先进行采样再来进行搜索,即对某个参数空间的某个随机子集进行搜索- 牺牲精度换效率,当参数空间很庞大,建议使用
- 并且上文重点强调,这两种方法都支持先两两比对、然后逐层筛选的方法来进行参数筛选(Halving),即HalvingGridSearchCV和HalvingRandomSearchCV方法(后续介绍)
- 注意,这是sklearn最新版、也就是0.24版才支持的功能,该功能的出现也是0.24版最大的改动之一,而该功能的加入,也将进一步减少网格搜索所需的计算资源、加快网格搜索的速度
- 说明文档中也强调,为了不增加网格搜索过程计算量,推荐谨慎构造参数空间,部分参数仍然以默认参数为主
- 由于网格搜索确定超参数的过程实际上是在帮助进行模型筛选,因此我们可以在sklearn的
- GridSearchCV的参数解释
- 该方法的搜索策略是“全搜索”,即对参数空间内的所有参数进行搜索,该方法也是以评估器形式存在,我们可以通过如下方式进行导入
from sklearn.model_selection import GridSearchCV GridSearchCV? # 建议好好看看官方注释 GridSearchCV( estimator, param_grid, *, scoring=None, n_jobs=None, refit=True, cv=None, # to use the default 5-fold cross validation verbose=0, pre_dispatch='2*n_jobs', error_score=nan, return_train_score=False, )
- 可以看到参数分为三类
- 核心参数:
estimator
和param_grid
,指定对象,最重要的参数 - 评估参数:主要是
scoring
、refit
和cv
三个参数。当然这三个参数都不是必要参数(有默认值),但这三个参数却是直接决定模型结果评估过程、并且对最终模型参数选择和模型泛化能力提升至关重要的三个参数 - 性能参数:主要包括
n_jobs
和pre_dispatch
,用于规定调用的核心数和一个任务按照何种方式进行并行运算;在网格搜索中,由于无需根据此前结果来确定后续计算方法,所以可以并行计算。在默认情况下并行任务的划分数量和n_jobs相同。当然,这组参数的合理设置能够一定程度提高模型网格搜索效率,但如果需要大幅提高执行速度,建议使用RandomizedSearchCV、或者使用Halving方法来进行加速
- 核心参数:
- 后续会详细介绍 HalvingSearch
- 该方法的搜索策略是“全搜索”,即对参数空间内的所有参数进行搜索,该方法也是以评估器形式存在,我们可以通过如下方式进行导入
训练过程
- 创建评估器
- 需要实例化一个评估器,这里可以是一个模型、也可以是一个机器学习流,网格搜索都可以对其进行调参
- 我们先从简单入手,实例化逻辑回归模型并对其进行调参
# 数据导入 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)
- 使用
saga
求解,逻辑回归中不同的损失函数、多分类策略,它都可以解决,避免后续还要调整clf = LogisticRegression(max_iter=int(1e6), solver='saga') clf.get_params()
- 创建参数空间
- 挑选评估器中的超参数构造参数空间
- 需要挑选能够控制模型拟合度的超参数来进行参数空间的构造,例如挑选类似 verbose、n_jobs等此类参数构造参数是毫无意义的
- 此处我们挑选
penalty
和C
这两个参数来进行参数空间的构造 - 参数空间可以是一个字典
param_grid_simple = {'penalty': ['l1', 'l2'], 'C': [1, 0.5, 0.1, 0.05, 0.01]}
- 我们需要在这 10 个超参数组合中,找一组最佳的
- 某些参数之间是冲突或者衍生的,可以创建多个参数空间(字典)放在同一个列表中
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]} ]
- 实例化网格搜索评估器
- 网格搜索也是评估器,也是先实例化,然后对其进行训练
- 此处先实例化一个简单的网格搜索评估器,需要输入此前设置的评估器和参数空间
search = GridSearchCV(estimator=clf, param_grid=param_grid_simple)
- 训练网格搜索评估器
- 同样是 fit 方法
search.fit(X_train, y_train)
- 所谓的训练网格搜索评估器,本质上是在挑选不同的超参数组合,进行逻辑回归模型的训练
- 而训练完成后相关结果都保存在search对象的属性中
- 同样是 fit 方法
- 查看结果
- 逐个解读
Name Description cv_results_ 交叉验证过程中的重要结果 best_estimator_ 最终挑选出的最优 best_score_ 在最优参数情况下,训练集的交叉验证的平均得分 best_params_ 最优参数组合 best_index_ CV过程会对所有参数组合标号,该参数表示最优参数组合的标号 scorer 在最优参数下,计算模型得分的方法 n_splits_ 交叉验证的折数 - 结果包含了已设置最优超参数、已训练好的逻辑回归评估器
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_score_ # 验证集准确率的平均值 # 0.9644268774703558 # 包含了各组超参数交叉验证的结果 search.cv_results_ # 选评估指标也是用于CV的 search.best_params_ # {'C': 1, 'penalty': 'l1'}
- 逐个解读
- 至此,我们完整执行了一遍网格搜索调参
- 但该过程大多只使用了默认参数在小范围内进行的运算,如果我们希望更换模型评估指标、并且在一个更加完整的参数范围内进行搜索,则需要对上述过程进行修改
- 进一步掌握关于评估器中 scoring 参数和 refit 参数的相关使用方法
- 小结
- 参数空间的每组超参数都会被代入模型训练,使用交叉验证的方式训练和验证,取平均值作为这组超参数对应的最终参数(cv_result),ROC-AUC 也取均值
- 得到所有的超参数组的 cv_results 后,取 ROC-AUC 分数最高的,直接作为逻辑回归模型的最优超参数和参数;或者再在测试集上测一遍,看结果是否依然良好(需要预留测试集,别代进去练)
多分类评估指标
- 在正式讨论网格搜索的进阶使用方法之前,需要先补充一些关于多分类问题的评估指标计算过程
- 前面的章节介绍过分类模型在解决多分类问题时的不同策略,也介绍过二分类问题的更高级评估指标,如f1-score和roc-auc等
- 接下来将详细讨论使用 F1-socre 和 ROC-AUC 作为模型评估指标,在sklearn中如何调用函数进行计算
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?
Name Description y_true 数据集真实标签 y_pred 标签预测结果 labels 允许以列表形式输入其他形态的标签,一般不进行修改 pos_label positive类别标签 average 多分类时指标计算方法 sample_weight 不同类别的样本权重 zero_division 当分母为0时返回结果 -
重点介绍多分类问题时
average
参数不同取值时的计算方法 -
此处以
recall
为例进行计算,重点介绍当average取值为macro
、micro
和weighted
的情况,其他指标也类似,有简单多分类问题如下(已按某种多分类问题求解策略得到如下预测结果,F1-Score作为指标时不需要指明)- 令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]) # 注:预测结果为类别标签,概率结果是进一步算出来的
- 构造多分类混淆矩阵如下
- 计算三个类别的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,该过程也就是average参数取值
micro
时的计算结果tp = tp1 + tp2 + tp3 fn = fn1 + fn2 + fn3 tp / (tp+fn) # 0.7 recall_score(y_true, y_pred, average='micro') # 0.7
- 上述的计算公式在前面的章节有详细解释
- 令1类标签为0、2类标签为1、3类标签为2
-
对于上述三个不同参数的选取文章来源:https://www.toymoban.com/news/detail-473081.html
- 首先,如果是样本不平衡问题(如果是要侧重训练模型判别小类样本的能力),则应排除weighted参数,以避免赋予大类样本更高的权重
- 在大多数情况下这三个不同的参数其实并不会对最后评估器的选取结果造成太大影响,只是在很多要求严谨的场合下需要说明多分类的评估结果的计算过程,此时需要简单标注下是按照何种方法进行的计算
- 如果是混淆矩阵中相关指标和roc-auc指标放在一起讨论,由于新版sklearn中roc-auc本身不支持在多分类时按照micro计算、只支持macro计算,因此建议混淆矩阵的多分类计算过程也选择macro过程,以保持一致
- 后续在没有特殊说明的情况下,统一采用 macro 方式进行多分类问题评估指标的计算
-
小结文章来源地址https://www.toymoban.com/news/detail-473081.html
- precision/recall/f1-score -> macro/micro/weighted
ROC-AUC
- 再看看评估指标 ROC-AUC 的计算函数
from sklearn.metrics import roc_auc_score
- roc_auc_score 评估指标函数中大多数参数都和此前介绍的混淆矩阵中评估指标类似
- 尝试使用 roc-auc 函数进行评估指标计算
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
- 接下来介绍其他参数,还是前面的多分类问题
Name Description max_fpr fpr最大值,fpr是roc曲线的横坐标 multi_class 分类器在进行多分类时进行的多分类问题处理策略 - 关于
multi_class
,一般是二分类器中用于解决多元分类问题时的参数(如逻辑回归)- 由于roc-auc需要分类结果中的概率来完成最终计算,因此需要知道概率结果对应的分类标签,即到底是以ovo还是ovr模式在进行多分类(第一步是选策略训练基础分类器,然后才是这里的用roc-auc进行评估),拆分策略是不一样的!因此如果是进行多分类 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
- ovr/ovo 的基本思想都是拆分数据集,训练多个基础分类器(二分类),再集成
- 新版的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
-
均值如下,该结果应当和 macro+multi_class 参数计算结果相同
np.mean([r1, r2, r3]) # 0.8501984126984127 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 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')
- 单独计算每个类别的 roc-auc 值
- 小结
- 至此,我们较为清楚的了解了在解决多分类问题时,使用 F1-Score 和 ROC-AUC 作为评估指标的具体步骤
- 注:这里介绍计算评估指标的方法,还没有涉及 OvR/OvO 的计算(划分集成),用的是直接给出的预测结果
- 多分类问题可以算单个分类的 Recall/F1-Score/ROC-AUC,再按不同方法(average)求整体得分
GridSearchCV进阶使用
- 进一步介绍网格搜索的进阶使用方法
构建全域参数搜索空间
- 首先是关于评估器(逻辑回归)全参数的设置方法
- 在此前的实验中,我们只是保守的选取了部分(penalty、C)我们认为会对模型产生比较大影响的超参数来构建参数空间,但在实际场景中,调参应该是纳入所有对模型结果有影响的参数进行搜索
- 并且是全流程中的参数来进行搜索。也就是说我们设置参数的空间的思路不应该更加“激进”一些
- 对逻辑回归评估器来说,应该是排除无用的参数外纳入所有参数进行调参,并且就逻辑回归模型来说,往往我们需要在模型训练前进行特征衍生以增强模型表现
- 因此我们应该先构建一个包含多项式特征衍生的机器学习流、然后进行参数搜索,这才是一个更加完整的调参过程
- 创建数据集
np.random.seed(24) X = np.random.normal(0, 1, size=(1000, 2)) y = np.array(X[:,0]+X[:, 1]**2 < 1.5, int) np.random.seed(24) for i in range(200): y[np.random.randint(1000)] = 1 y[np.random.randint(1000)] = 0 plt.scatter(X[:, 0], X[:, 1], c=y)
- 构建机器学习流
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()
- 构造参数空间,选取的超参数包括:特征衍生阶数、penalty、C、solver
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']} ]
选取评估指标
- 想要更好的评估模型的泛化能力,建议使用 F1-Score 或 ROC-AUC
- 由于涉及到在参数中调用评估指标函数,因此需要补充一些关于常用分类评估指标在sklearn中的使用方法,以及不同评估指标函数在网格搜索评估器中的调用方法
GridSearchCV? GridSearchCV( estimator, param_grid, *, scoring=None, n_jobs=None, refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', error_score=nan, return_train_score=False, )
- 从评估器的说明文档中能够看出,
scoring
参数最基础的情况下可以选择输入str(字符串)或者callable(可调用)对象,也就是可以输入指代某个评估函数的字符串,或者直接输入某评估指标函数(通过make_score函数创建的函数),来进行模型结果的评估- 当然,也可以在该参数位上直接输入一个字典或者list,如果是字典的话value需要是str(字符串)或者callable(可调用)对象
- 上述参数列表也可以在这里获取
import sklearn sorted(sklearn.metrics.SCORERS.keys())
- 不难看出,在网格搜索中输出评估指标参数,和调用评估指标函数进行数据处理还是有很大的区别
- 例如,metrics.roc_auc_score函数能够同时处理多分类问题和二分类问题,但如果作为参数输入到网格搜索中,
roc_auc
参数只能指代metrics.roc_auc_score函数的二分类功能,如果需要进行多分类,则需要在scoring参数中输入roc_auc_ovr
、roc_auc_ovo
或者roc_auc_ovr_weighted
、roc_auc_ovo_weighted
,即需要指明求解多分类问时的划分策略
- 当然,也可以在该参数位上直接输入一个字典或者list,如果是字典的话value需要是str(字符串)或者callable(可调用)对象
- 小试牛刀
from sklearn.metrics import roc_auc_score roc_auc_score? GridSearchCV(estimator=pipe, param_grid=param_grid, scoring='roc_auc_ovr') # 字符串 # 或者 from sklearn.metrics import make_scorer acc = make_scorer(roc_auc_score) # 可调用对象 GridSearchCV(estimator=pipe, param_grid=param_grid, scoring=acc)
- 但此时我们无法修改评估指标函数的默认参数(只能指定用哪个评估指标)
- 值得注意的是,此处make_scorer函数实际上会将一个简单的评估指标函数转化为评估器结果评估函数
- 对于评估指标函数来说,只需要输入标签的预测值和真实值即可进行计算
- 而评估器结果评估函数,则需要同时输入评估器、特征矩阵以及对应的真实标签,其执行过程是先将特征矩阵输入评估器、然后将输出结果和真实标签进行对比
# 评估指标函数 accuracy_score([1, 1, 0], [1, 1, 1]) # 评估器结果评估函数 acc = make_scorer(accuracy_score) acc(search.best_estimator_, X_train, y_train) search.score(X_train, y_train)
- 在网格搜索或者交叉验证评估器中,只支持输入经过 make_scorer 转化后的评估指标函数(评估函数),通过字符串指定的函数也是转化过的?
- 同时输入多组评估指标
- 有时我们可能需要同时看同一参数下多项评估指标的结果,就可以在 scoring 中输入列表、元组或者字典
- 当然字典对象会较为常用,例如如果我们需要同时选用roc-auc和accuracy作为模型评估指标,则需要创建如下字典
scoring = {'AUC': 'roc_auc', 'Accuracy': make_scorer(accuracy_score)} # 需要转化 # {'AUC': make_scorer(roc_auc_score), 'Accuracy': 'accuracy'} 这样也可,那到底需要手动转化吗? # 需要注意的是,尽管此时网格搜索评估器将同时计算一组参数下的多个评估指标结果并输出,但我们只能选取其中一个评估指标作为挑选超参数的依据,由 refit 决定 # 而其他指标尽管仍然会计算,但结果只作参考
- 然后将其作为参数传入网格搜索评估器内
search = GridSearchCV(estimator=clf, param_grid=param_grid_simple, scoring=scoring, refit='Accuracy') # refit参数中输入的评估指标,就是最终决定参数/超参数选择的评估指标
- 最终选择何种参数,可以参考如下依据
- 有明确模型评估指标的
- 很多竞赛或者项目的算法验收环节,可能都会明确指定模型评估指标,例如模型排名根据f1-score计算结果得出等
- 没有明确模型评估指标的
- 如果没有明确的评估指标要求,则选择评估指标最核心的依据就是尽可能提升/确保模型的泛化能力
- 根据此前的讨论结果,如果数据集的各类并没有明确的差异(偏向),在算力允许的情况下,应当优先考虑 roc-auc;而如果希望重点提升模型对类别1(或者某类别)的识别能力,则可以优先考虑 f1-score 作为模型评估指标
- 注:真实标签或预测结果都是标签,只是个记号,为了统计数量,不会代入运算,无需考虑是 1 还是 0,关键是你更关注哪类(阳性),哪类就是 F1-Score 中的 “1”,将其数量正确地统计,代入到公式中相应的位置即可
- 有明确模型评估指标的
- 关于评估指标的选取,可以多做尝试,观察结果
优化后建模流程
- 整理一下优化后的建模流程
# 构造机器学习流 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']} ] # 实例化搜索评估器 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) accuracy_score(search.best_estimator_.predict(X_test), y_test)
- 结果分析
- 最终模型结果准确率在78%上下
- 当然,如果只看模型准确率结果,我们发现该结果相比之前结果较差(上一节手动调参)
- 但是,相比手动调参模型,该模型基本没有过拟合隐患(测试集分数甚至高于训练集),因此该模型在未来的使用过程中更有可能能够确保一个稳定的预测输出结果(泛化能力更强),这也是交叉验证和 ROC-AUC 共同作用的结果
- 如果有明确要求根据准确率判断模型效果,则上述过程应该选择准确率,同时如果算力允许,也可以近一步扩大搜索空间(手动调参的准确率就是在15阶多项式特征衍生基础上得到的)
- 至此,我们就完成了在实验数据上的建模和利用网格搜索调优,接下来,我们将把上述技巧应用到一项 kaggle 数据集上来进行建模分析,补充更多实战过程中会用到的方法和技巧
到了这里,关于机器学习14(网格搜索调参)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!