根据菜菜的课程进行整理,方便记忆理解
代码位置如下:
机器学习中调参的基本思想
画学习曲线,或者网格搜索,我们能够探索到调参边缘(代价可能是训练一次模型要跑三天三夜),高手调参恐怕还是多依赖于经验,而这些经验,来源于:
- 非常正确的调参思路和方法
- 对模型评估指标的理解
- 对数据的感觉和经验
- 用洪荒之力去不断地尝试
那我们首先来讲讲正确的调参思路。
-
模型调参
-
第一步是要找准目标:我们要做什么?
- 一般来说,这个目标是提升某个模型评估指标,比如对于随机森林来说,我们想要提升的是模型在未知数据上的准确率(由score或oob_score_来衡量)。找准了这个目标,我们就需要思考:模型在未知数据上的准确率受什么因素影响?在机器学习中,我们用来衡量模型在未知数据上的准确率的指标,叫做泛化误差(Genelization error)。
-
泛化误差
准确地描绘了泛化误差与模型复杂度的关系
- 当模型太复杂,模型就会过拟合,泛化能力就不够,所以泛化误差大。
- 当模型太简单,模型就会欠拟合,拟合能力就不够,所以误差也会大。
- 只有当模型的复杂度刚刚好的才能够达到泛化误差最小的目标。
那模型的复杂度与我们的参数有什么关系呢?对树模型来说,树越茂盛,深度越深,枝叶越多,模型就越复杂。所以树模型是天生位于图的右上角的模型,随机森林是以树模型为基础,所以随机森林也是天生复杂度高的模型。随机森林的参数,都是向着一个目标去:减少模型的复杂度,把模型往图像的左边移动,防止过拟合。当然了,调参没有绝对,也有天生处于图像左边的随机森林,所以调参之前,我们要先判断,模型现在究竟处于图像的哪一边。
泛化误差的背后其实是“偏差-方差困境”
- 模型太复杂或者太简单,都会让泛化误差高,我们追求的是位于中间的平衡点
- 模型太复杂就会
过拟合,模型太简单就会欠拟合 - 对树模型和树的集成模型来说,树的深度越深,枝叶越多,模型越复杂
- 树模型和树的集成模型的目标,都是减少模型复杂度,把模型往图像的左边移动
可以将那些对复杂度影响巨大的参数挑选出来,研究他们的单调性,然后专注调整那些能最大限度让复杂度降低的参数。
对于那些不单调的参数,或者反而会让复杂度升高的参数,我们就视情况使用,大多时候甚至可以退避。基于经验,下面各个参数对模型的影响程度做了一个排序
| 参数 | 对模型在未知数据上的评估性能的影响 | 影响程度 |
|---|---|---|
| n_estimators | 提升至平稳,n_estimators↑,不影响单个模型的复杂度 | ⭐⭐⭐⭐ |
| max_depth | 有增有减,默认最大深度,即最高复杂度,向复杂度降低的方向调参 max_depth↓,模型更简单,且向图像的左边移动 | ⭐⭐⭐ |
| min_samples_leaf | 有增有减,默认最小限制1,即最高复杂度,向复杂度降低的方向调参 min_samples_leaf↑,模型更简单,且向图像的左边移动 | ⭐⭐ |
| min_samples_split | 有增有减,默认最小限制2,即最高复杂度,向复杂度降低的方向调参 min_samples_split↑,模型更简单,且向图像的左边移动 | ⭐⭐ |
| max_features | 有增有减,默认auto,是特征总数的开平方,位于中间复杂度,既可以 向复杂度升高的方向,也可以向复杂度降低的方向调参 max_features↓,模型更简单,图像左移 max_features↑,模型更复杂,图像右移 max_features是唯一的,既能够让模型更简单,也能够让模型更复杂的参 数,所以在调整这个参数的时候,需要考虑我们调参的方向 | ⭐ |
| criterion | 有增有减,一般使用gini | 看具体情况 |
有了以上的知识储备,我们现在也能够通过参数的变化来了解,模型什么时候到达了极限,当复杂度已经不能再降低的时候,我们就不必再调整了,因为调整大型数据的参数是一件非常费时费力的事。除了学习曲线和网格搜索,我们现在有了基于对模型和正确的调参思路的“推测”能力,这能够让我们的调参能力更上一层楼。
偏差 vs 方差
一个集成模型(f)在未知数据集(D)上的泛化误差E(f;D),由方差(var),偏差(bais)和噪声(ε)共同决定。
偏差与方差
观察下面的图像,每个点就是集成算法中的一个基评估器产生的预测值。红色虚线代表着这些预测值的均值,而蓝色的线代表着数据本来的面貌。
- 偏差:模型的预测值与真实值之间的差异,即每一个红点到蓝线的距离。在集成算法中,每个基评估器都会有自己的偏差,集成评估器的偏差是所有基评估器偏差的均值。模型越精确,偏差越低。
- 方差:反映的是模型每一次输出结果与模型预测值的平均水平之间的误差,即每一个红点到红色虚线的距离,衡量模型的稳定性。模型越稳定,方差越低。
一个好的模型,要对大多数未知数据都预测得”准“又”稳“。即是说,当偏差和方差都很低的时候,模型的泛化误差就小,在未知数据上的准确率就高。
- 偏差衡量模型是否预测得准确,偏差越小,模型越“准”;
- 方差衡量模型每次预测的结果是否接近,即是说方差越小,模型越“稳”;
- 噪声是机器学习无法干涉的部分,为了让世界美好一点,我们就不去研究了。
| 偏差大 | 偏差小 | |
|---|---|---|
| 方差大 | 模型不适合这个数据 换模型 | 过拟合 模型很复杂 对某些数据集预测很准确 对某些数据集预测很糟糕 |
| 方差小 | 欠拟合 模型相对简单 预测很稳定 但对所有的数据预测都不太准确 | 泛化误差小,我们的目标 |
通常来说,方差和偏差有一个很大,泛化误差都会很大。然而,方差和偏差是此消彼长的,不可能同时达到最小值。
从图上可以看出,
- 模型复杂度大的时候,方差高,偏差低。偏差低,就是要求模型要预测得“准”。模型就会更努力去学习更多信息,会具体于训练数据,这会导致,模型在一部分数据上表现很好,在另一部分数据上表现却很糟糕。模型泛化性差,在不同数据上表现不稳定,所以方差就大。而要尽量学习训练集,模型的建立必然更多细节,复杂程度必然上升。所以,复杂度高,方差高,总泛化误差高。
- 复杂度低的时候,方差低,偏差高。方差低,要求模型预测得“稳”,泛化性更强,那对于模型来说,它就不需要对数据进行一个太深的学习,只需要建立一个比较简单,判定比较宽泛的模型就可以了。结果就是,模型无法在某一类或者某一组数据上达成很高的准确度,所以偏差就会大。所以,复杂度低,偏差高,总泛化误差高。
我们调参的目标是,达到方差和偏差的完美平衡!虽然方差和偏差不能同时达到最小值,但他们组成的泛化误差却可以有一个最低点,而我们就是要寻找这个最低点。对复杂度大的模型,要降低方差,对相对简单的模型,要降低偏差。随机森林的基评估器都拥有较低的偏差和较高的方差,因为决策树本身是预测比较”准“,比较容易过拟合的模型,装袋法本身也要求基分类器的准确率必须要有50%以上。所以以随机森林为代表的装袋法的训练过程旨在降低方差,即降低模型复杂度,所以随机森林参数的默认设定都是假设模型本身在泛化误差最低点的右边。
所以,我们在降低复杂度的时候,本质其实是在降低随机森林的方差,随机森林所有的参数,也都是朝着降低方差的目标去。有了这一层理解,我们对复杂度和泛化误差的理解就更上一层楼了,对于我们调参,也有了更大的帮助。
随机森林在乳腺癌数据上的调参
基于方差和偏差的调参方法,在乳腺癌数据上进行一次随机森林的调参。
- 导入需要的库
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
- 导入数据集,探索数据
cancer = load_breast_cancer()
cancer.data
cancer.target
cancer_data = pd.DataFrame(cancer.data)
cancer_data.head()
# 数据569个样本,30个特征
cancer.data.shape
- 进行一次简单的建模,看看模型本身在数据集上的效果
rfc = RandomForestClassifier(n_estimators=100,random_state=20)
cross_score = cross_val_score(rfc,cancer.data,cancer.target,cv=10).mean()
cross_score
# 0.9648809523809524
- 随机森林调整的第一步:无论如何先来调n_estimators
# 在调参的时候,n_estimators对随机森林的影响比较大,我们可以先处理n_estimators的学习曲线,定到差不多的范围,在进行调参
n_e_score = []
for i in range(0,200,10):
rfc = RandomForestClassifier(n_estimators=i+1,random_state=20,n_jobs=-1)
score_c = cross_val_score(rfc,cancer.data,cancer.target,cv=10).mean()
n_e_score.append(score_c)
print(max(n_e_score),n_e_score.index(max(n_e_score)))
plt.figure()
plt.plot(range(1,200,10),n_e_score,color="r",label="n_estimators")
plt.ylabel("predict rate")
plt.xlabel("tree num")
plt.legend()
plt.show()
# 0.968421052631579 6
- 在确定好的范围内,进一步细化学习曲线
# 我们可以看到上面的值在60棵树的时候得到了预测的最大值,我们可以对其进行细化,寻找更加高准确率对应的n_estimators的取值
n_e_score = []
for i in range(50,70):
rfc = RandomForestClassifier(n_estimators=i+1,random_state=20,n_jobs=-1)
score_c = cross_val_score(rfc,cancer.data,cancer.target,cv=10).mean()
n_e_score.append(score_c)
print(max(n_e_score),n_e_score.index(max(n_e_score)))
plt.figure()
plt.plot(range(50,70),n_e_score,color="r",label="n_estimators")
plt.ylabel("predict rate")
plt.xlabel("tree num")
plt.legend()
plt.show()
# 0.968421052631579 8
- 为网格搜索做准备,书写网格搜索的参数
"""
有一些参数是没有参照的,很难说清一个范围,这种情况下我们使用学习曲线,看趋势
从曲线跑出的结果中选取一个更小的区间,再跑曲线
param_grid = {'n_estimators':np.arange(0, 200, 10)}
param_grid = {'max_depth':np.arange(1, 20, 1)}
param_grid = {'max_leaf_nodes':np.arange(25,50,1)}
对于大型数据集,可以尝试从1000来构建,先输入1000,每100个叶子一个区间,再逐渐缩小范围
有一些参数是可以找到一个范围的,或者说我们知道他们的取值和随着他们的取值,模型的整体准确率会如何变化,这
样的参数我们就可以直接跑网格搜索
param_grid = {'criterion':['gini', 'entropy']}
param_grid = {'min_samples_split':np.arange(2, 2+20, 1)}
param_grid = {'min_samples_leaf':np.arange(1, 1+10, 1)}
param_grid = {'max_features':np.arange(5,30,1)}
"""
- 开始按照参数对模型整体准确率的影响程度进行调参,首先调整max_depth
# 可以从上面的图中我们可以看出58~60的时候,我们能得到最高的预测概率
# 我们首先来调整max_depth的值
param_grid = {'max_depth':np.arange(1, 20, 1)}
# 一般根据数据的大小来进行一个试探,乳腺癌数据很小,所以可以采用1~10,或者1~20这样的试探
# 但对于像digit recognition那样的大型数据来说,我们应该尝试30~50层深度(或许还不足够
# 更应该画出学习曲线,来观察深度对模型的影响
rfc = RandomForestClassifier(n_estimators=58
,random_state=20)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(cancer.data,cancer.target)
GS.best_params_
# {'max_depth': 9}
# 准确率下降,模型变简单,图像左移
# 而模型整体的准确率下降了,即整体的泛化误差上升了,这说明模型现在位于图像左边
# 有可能是我们调整的n_estimators对于数据集来说太大,因此将模型拉到泛化误差最低点去了
GS.best_score_ # 0.9666666666666668
- 调整max_features
### 调整max_features的值
param_grid = {'max_features':np.arange(5, 30, 1)}
"""
max_features是唯一一个即能够将模型往左(低方差高偏差)推,也能够将模型往右(高方差低偏差)推的参数。我
们需要根据调参前,模型所在的位置(在泛化误差最低点的左边还是右边)来决定我们要将max_features往哪边调。
现在模型位于图像左侧,我们需要的是更高的复杂度,因此我们应该把max_features往更大的方向调整,可用的特征
越多,模型才会越复杂。max_features的默认最小值是sqrt(n_features),因此我们使用这个值作为调参范围的
最小值。
"""
rfc = RandomForestClassifier(n_estimators=58
,random_state=20)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(cancer.data,cancer.target)
GS.best_params_
# {'max_features': 5}
GS.best_score_ # 0.9666353383458647
- 调整min_samples_leaf
#调整min_samples_leaf
param_grid={'min_samples_leaf':np.arange(1, 1+10, 1)}
#对于min_samples_split和min_samples_leaf,一般是从他们的最小值开始向上增加10或20
#面对高维度高样本量数据,如果不放心,也可以直接+50,对于大型数据,可能需要200~300的范围
#如果调整的时候发现准确率无论如何都上不来,那可以放心大胆调一个很大的数据,大力限制模型的复杂度
rfc = RandomForestClassifier(n_estimators=39,random_state=90)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(cancer.data,cancer.target)
GS.best_params_
# {'min_samples_leaf': 4}
GS.best_score_ # 0.9613721804511279
- 调整min_samples_split
#调整min_samples_split
param_grid={'min_samples_split':np.arange(2, 2+20, 1)}
rfc = RandomForestClassifier(n_estimators=39,random_state=90)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(cancer.data,cancer.target)
GS.best_params_
# {'min_samples_split': 3}
GS.best_score_ # 0.9613721804511279
- 调整Criterion
#调整Criterion
param_grid = {'criterion':['gini', 'entropy']}
rfc = RandomForestClassifier(n_estimators=39,random_state=90)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(cancer.data,cancer.target)
GS.best_params_
# {'criterion': 'entropy'}
GS.best_score_ # 0.9649122807017543
在整个调参过程之中,我们首先调整了n_estimators(无论如何都请先走这一步),然后调整max_depth,通过max_depth产生的结果,来判断模型位于复杂度-泛化误差图像的哪一边,从而选择我们应该调整的参数和调参的方向。如果感到困惑,也可以画很多学习曲线来观察参数会如何影响我们的准确率,选取学习曲线中单调的部分来放大研究(如同我们对n_estimators做的)。学习曲线的拐点也许就是我们一直在追求的,最佳复杂度对应的泛化误差最低点(也是方差和偏差的平衡点)。