在机器学习领域,梯度提升算法是一种强大的集成学习方法,它通过迭代地训练模型来最小化损失函数。一个关键的挑战是确定最佳的迭代次数,即提升树的数量。本文将探讨如何使用袋外(OOB)误差和交叉验证来估计最优的迭代次数。
袋外误差是一种无需重复模型拟合即可即时计算的估计方法,它与交叉验证误差非常相似。OOB误差仅适用于随机梯度提升(即样本的子采样比例小于1.0),它基于未包含在自助样本中的样本(即所谓的袋外样本)的损失改进来计算。虽然OOB估计器是对真实测试损失的悲观估计,但对于少量树来说,它仍然是一个相当好的近似。
下图显示了负OOB改进的累积和作为提升迭代的函数。如图所示,它在前一百次迭代中跟踪测试损失,但随后以悲观的方式发散。图还显示了3折交叉验证的性能,这通常能给出更好的测试损失估计,但计算上更耗费资源。
在代码实现方面,首先导入了必要的库,包括matplotlib用于绘图,numpy用于数值计算,scipy.special中的expit函数用于计算逻辑函数,以及sklearn中的ensemble和metrics模块用于模型训练和评估。使用train_test_split函数将数据集分为训练集和测试集,然后使用GradientBoostingClassifier进行模型训练,并计算准确率。
import matplotlib.pyplot as plt
import numpy as np
from scipy.special import expit
from sklearn import ensemble
from sklearn.metrics import log_loss
from sklearn.model_selection import KFold, train_test_split
接下来,定义了heldout_score函数来计算测试集上的偏差分数,并定义了cv_estimate函数来进行交叉验证。使用交叉验证来估计最佳的n_estimators(树的数量),并计算测试数据上的偏差分数。然后,计算了OOB改进的负累积和,并找到了最小损失对应的迭代次数。
def heldout_score(clf, X_test, y_test):
"""计算X_test和y_test上的偏差分数。"""
score = np.zeros((n_estimators,), dtype=np.float64)
for i, y_proba in enumerate(clf.staged_predict_proba(X_test)):
score[i] = 2 * log_loss(y_test, y_proba[:, 1])
return score
def cv_estimate(n_splits=None):
cv = KFold(n_splits=n_splits)
cv_clf = ensemble.GradientBoostingClassifier(**params)
val_scores = np.zeros((n_estimators,), dtype=np.float64)
for train, test in cv.split(X_train, y_train):
cv_clf.fit(X_train[train], y_train[train])
val_scores += heldout_score(cv_clf, X_train[test], y_train[test])
val_scores /= n_splits
return val_scores
最后,绘制了OOB损失、测试损失和交叉验证损失的曲线,并为最佳迭代次数添加了垂直线。使用不同的颜色和线型来区分这三条曲线,并在x轴上添加了标签和图例。
plt.figure(figsize=(8, 4.8))
plt.plot(x, cumsum, label="OOB损失", color=oob_color, linestyle=oob_line)
plt.plot(x, test_score, label="测试损失", color=test_color, linestyle=test_line)
plt.plot(x, cv_score, label="CV损失", color=cv_color, linestyle=cv_line)
plt.axvline(x=oob_best_iter, color=oob_color, linestyle=oob_line)
plt.axvline(x=test_best_iter, color=test_color, linestyle=test_line)
plt.axvline(x=cv_best_iter, color=cv_color, linestyle=cv_line)
plt.legend(loc="upper center")
plt.ylabel("归一化损失")
plt.xlabel("迭代次数")
plt.show()