GBDT算法超参数评估(一)+https://developer.aliyun.com/article/1544806?spm=a2c6h.13148508.setting.23.22454f0e4mZEBN
评估器的不纯度衡量指标:参数criterion
GBDT算法的弱评估器为决策树(确切地说是回归树),我们已经熟悉各种剪枝参数对模型的影响。因此,我们对于Boosting算法中控制弱评估器的参数应该也不陌生:
这些参数在GBDT中的用法与默认值与决策树类DecisionTreeRegressor中基本上一致(除了GBDT中max_depth=3),专门用于对决策树进行剪枝、控制单个弱评估器的结构,考虑到大家在决策树中已经充分掌握这些参数,我们不再对这些参数一一进行详细说明了。在这里,需要重点说明的有两部分内容,一部分梯度提升树中默认的弱评估器复杂度所带来的问题,另一部分则是梯度提升树独有的不纯度衡量指标。
- 基尼系数(Gini Impurity):
- 表示为gini。
- 计算公式:(gini = 1 - p2 - (1-p)2),其中p是样本属于某一类的概率。
- 基尼系数越小,表示数据集的不纯度越低,即数据的纯度越高。
- 基尼系数的计算不涉及对数,因此相对于信息熵来说,计算速度更快。
- 信息熵(Entropy):
- 表示为entropy。
- 计算公式:(entropy = -\sum_{i=1}^{n} p_i \log_2(p_i)),其中(p_i)是样本属于第i类的概率,n是类别总数。
- 信息熵用于表示数据的不确定性或混乱程度;熵值越高,数据的不确定性越大。
- 信息熵对不纯度更加敏感,因此它作为指标时,决策树的生长可能会更加“精细”。然而,在高维数据或噪音较多的情况下,这可能导致过拟合。
- 信息熵的计算涉及对数运算,因此相对于基尼系数来说,计算速度可能稍慢。
梯度提升树中的弱评估器复杂度:max_depth
在随机森林中,控制过拟合的参数基本都处于“关闭状态”,比如:max_depth的默认值为None,表示弱评估器不限制深度,因此随机森林中长出的树基本上都是剪枝前的树,如果随机森林算法出现过拟合现象,那么我们就可以通过对弱评估器进行剪枝来限制集成算法的过拟合。然而,这种情况并不适用于Boosting算法一族。
从GBDT的默认参数我们可以看到,对GBDT来说,无论是分类器还是回归器,默认的弱评估器最大深度都为3,这说明GBDT默认就对弱评估器进行了剪枝操作。所以当GBDT等Boosting算法处于过拟合状态时,很难再通过剪枝的手段来控制过拟合,只能从数据上下手控制过拟合了(例如,使用参数max_features,在GBDT中其默认值为None)。
也因此,通常认为Boosting算法比Bagging算法更不容易过拟合,也就是说在相似的数据上,Boosting算法表现出的过拟合程度会较轻。
cross_validate和KFold:
from sklearn.datasets import load_iris from sklearn.model_selection import cross_validate, KFold from sklearn.svm import SVC iris = load_iris() X = iris.data y = iris.target clf = SVC(kernel='linear', C=1, random_state=42) kf = KFold(n_splits=5, shuffle=True, random_state=42) scoring = ['accuracy', 'precision_macro', 'recall_macro'] cv_results = cross_validate(clf, X, y, cv=kf, scoring=scoring) print(cv_results)
- cross_validate:这是一个用于评估模型性能的函数,它执行交叉验证并返回每次迭代的评分以及其他相关信息。
- KFold:这是一个类,用于实现k折交叉验证的数据划分。它本身不进行评估,而是为交叉验证提供数据划分的机制。
- GridSearchCV:这是一个类,用于执行网格搜索和交叉验证,以找到模型的最佳超参数组合。它不仅进行数据划分和模型评估,还搜索参数空间以找到最优配置。
from sklearn.model_selection import cross_validate,KFold cv = KFold(n_splits=5,shuffle=True,random_state=12) X_c,y_c = load_breast_cancer(return_X_y=True,as_frame=True) modelname = ["GBDT","RF"] colors = ["green","orange"] clf_models = [GBC(random_state=12),RFC(random_state=12)] xaxis = range(1,6) plt.figure(figsize=(10,6),dpi=65) for name,model,color in zip(modelname,clf_models,colors): result = cross_validate(model,X_c,y_c,cv=cv ,return_train_score=True) plt.plot(xaxis,result["train_score"], color=color, label = name+"_Train") plt.plot(xaxis,result["test_score"], color=color, linestyle="--",label = name+"_Test") plt.xticks([1,2,3,4,5]) plt.xlabel("CVcounts",fontsize=12) plt.ylabel("Accuracy",fontsize=12) plt.title("GBDT vs RF") plt.legend() plt.show()
cross_validate
和KFold
是两个用于模型选择和评估的工具
X_r,y_r = fetch_california_housing(return_X_y=True,as_frame=True) reg_models = [GBR(random_state=12),RFR(random_state=12)] xaxis = range(1,6) plt.figure(figsize=(10,6),dpi=65) for name,model,color in zip(modelname,reg_models,colors): result = cross_validate(model,X_r,y_r,cv=cv,scoring="neg_mean_squared_error" ,return_train_score=True) plt.plot(xaxis,result["train_score"], color=color, label = name+"_Train") plt.plot(xaxis,result["test_score"], color=color, linestyle="--",label = name+"_Test") plt.xticks([1,2,3,4,5]) plt.xlabel("CVcounts",fontsize=12) plt.ylabel("MSE",fontsize=12) plt.title("GBDT vs RF") plt.legend() plt.show()
对GBDT来说,不纯度的衡量指标有2个:
- friedman_mse:弗里德曼均方误差
- squared_error:平方误差
其中平方误差我们非常熟悉,就是直接计算父节点的平方误差与子节点平方误差的加权求和之间的差异。弗里德曼均方误差是由Friedman在论文《贪婪函数估计:一种梯度提升机器》(GREEDY FUNCTION APPROXIMATION: A GRADIENT BOOSTING MACHINE)中提出的全新的误差计算方式。根据论文中的描述,弗里德曼均方误差使用调和平均数来控制左右叶子节点上的样本数量,相比普通地求均值,调和平均必须在左右叶子节点上的样本量/样本权重相差不大的情况下才能取得较大的值(F1 score也是用同样的方式来调节Precision和recall)。这种方式可以令不纯度的下降得更快,让整体分枝的效率更高
- 大部分时候,使用弗里德曼均方误差可以让梯度提升树得到很好的结果,因此GBDT的默认参数就是Friedman_mse。不过许多时候,我们会发现基于平方误差的分割与基于弗里德曼均方误差的分割会得到相同的结果。
梯度提升树的提前停止
在学习机器学习理论与方法时,我们极少提及迭代的提前停止问题。在机器学习中,依赖于迭代进行工作的算法并不算多,同时课程中的数据量往往也比较小,因此难以预见需要提前停止迭代以节省计算资源或时间的情况。但对于工业界使用最广泛的GBDT而言,提前停止是需要考虑的关键问题。
对于任意需要迭代的算法,迭代的背后往往是损失函数的最优化问题。例如在逻辑回归中,我们在进行梯度下降的迭代时,是希望找到交叉熵损失函数的最小值;而在梯度提升树中,我们在一轮轮建立弱评估器过程中,也是希望找到对应损失函数的最小值。理想状态下,无论使用什么算法,只要我们能够找到损失函数上真正的最小值,那模型就达到“收敛”状态,迭代就应该被停止。
然而遗憾的是,我们和算法都不知道损失函数真正的最小值是多少,而算法更不会在达到收敛状态时就自然停止。在机器学习训练流程中,我们往往是通过给出一个极限资源来控制算法的停止,比如,我们通过超参数设置允许某个算法迭代的最大次数,或者允许建立的弱评估器的个数。因此无论算法是否在很短时间内就锁定了足够接近理论最小值的次小值、或者算法早已陷入了过拟合状态、甚至学习率太低导致算法无法收敛,大多数算法都会持续(且无效地)迭代下去,直到我们给与的极限资源全部被耗尽。对于复杂度较高、数据量较大的Boosting集成算法来说,无效的迭代常常发生,因此作为众多Boosting算法的根基算法,梯度提升树自带了提前停止的相关超参数。另外,逻辑回归看起来会自然停止,是因为逻辑回归内置提前停止机制。
我们根据以下原则来帮助梯度提升树实现提前停止:
- 当GBDT已经达到了足够好的效果(非常接近收敛状态),持续迭代下去不会有助于提升算法表现
- GBDT还没有达到足够好的效果(没有接近收敛),但迭代过程中呈现出越迭代算法表现越糟糕的情况
- 虽然GBDT还没有达到足够好的效果,但是训练时间太长/速度太慢,我们需要重新调整训练
在实际数据训练时,我们往往不能动用真正的测试集进行提前停止的验证,因此我们需要从训练集中划分出一小部分数据,专用于验证是否应该提前停止。那我们如何找到这个验证集损失不再下降、准确率不再上升的“某一时间点”呢?此时,我们可以规定一个阈值,例如,当连续n_iter_no_change次迭代中,验证集上损失函数的减小值都低于阈值tol,或者验证集的分数提升值都低于阈值tol的时候,我们就令迭代停止。此时,即便我们规定的n_estimators或者max_iter中的数量还没有被用完,我们也可以认为算法已经非常接近“收敛”而将训练停下。这种机制就是提前停止机制Early Stopping。这种机制中,需要设置阈值tol,用于不断检验损失函数下降量的验证集,以及损失函数连续停止下降的迭代轮数n_iter_no_change。在GBDT当中,这个流程刚好由以下三个参数控制:
- validation_fraction:从训练集中提取出、用于提前停止的验证数据占比,值域为[0,1]。
- n_iter_no_change:当验证集上的损失函数值连续n_iter_no_change次没有下降或下降量不达阈值时,则触发提前停止。平时则设置为None,表示不进行提前停止。
- tol:损失函数下降的阈值,默认值为1e-4,也可调整为其他浮点数来观察提前停止的情况。