xgboost(八) -- 置信度

今天想和大家讲解xgboost的一个是神仙用法,我们进行回归任务的时候,拿到一个冰冷冷的预测值,不知道你是否有些迷茫,如果这个时候给你一个置信度的概念,是否能让你欣喜若狂,今天就和大家讲解一下这个神仙用法。
首先对于回归任务置信度的概念可以理解成方差的大小,也就是当你预测这个值的时候,有多大的不确定性。

预测值的来源

先思考一个问题,xgb模型是符合做到回归预测的? 通过树的判断逻辑应该是叶子节点的存储了某个值,那么这个值是怎么来的呢?

初始预测值

在 XGBoost 中,初始预测值通常是训练数据目标值的均值(这可以通过 base_score 参数进行设置)。

每棵树的增量预测

每棵树都在初始预测值的基础上进行增量预测。具体来说,每棵树都拟合了当前残差(即真实值和当前预测值的差异)。
对于每个样本,树根据特征进行分裂,最终将样本分配到叶子节点,叶子节点中存储的是预测的增量值(残差的预测值)。

累加树的输出

对于每个样本,所有树的输出值(叶子节点中的增量值)累加起来,再加上初始预测值,得到最终的预测值。

举例说明

假设我们有一个 XGBoost 回归模型,包含 3 棵树:
例如,初始预测值为 5(基于 base_score 参数)。
第一棵树的输出:

对于样本 1,第一棵树的输出是 0.3。
对于样本 2,第一棵树的输出是 -0.1。
第二棵树的输出:

对于样本 1,第二棵树的输出是 0.2。
对于样本 2,第二棵树的输出是 0.1。
第三棵树的输出:

对于样本 1,第三棵树的输出是 -0.1。
对于样本 2,第三棵树的输出是 0.2。
最终预测值:

对于样本 1,最终预测值 = 初始预测值 + 所有树的输出 = 5 + 0.3 + 0.2 - 0.1 = 5.4。
对于样本 2,最终预测值 = 初始预测值 + 所有树的输出 = 5 - 0.1 + 0.1 + 0.2 = 5.2。

置信度的定义

这里我们知道,最终的预测值是经过多棵树以后最终的值累加起来的结果。这个过程看起来也挺复杂的。置信度是刻画一个预测值的稳定程度,如果我们知道每棵树的叶子节点的训练样本真值的方差是不是就从一定程度上可以判断这个值的置信程度?说干就干。

import xgboost as xgb
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
df = pd.read_csv('./data/raw_avg_3day_reward_10day_data_label.txt', sep='\t', header=None, encoding='utf8',
                 converters={'0': str})
idx = -1
trainX = df.iloc[:idx, 2:-2].values
day20_trainY = df.iloc[:idx, -1].values

X_train, X_test, y_train, y_test = train_test_split(trainX, day20_trainY, random_state=11)
dtrain = xgb.DMatrix(X_train, y_train)
dtest = xgb.DMatrix(X_test, y_test)
params = {
    'objective': 'reg:squarederror',  # 回归任务的损失函数 reg:squarederror
    'max_depth': 5,
    # "eval_metric": "rmse",
    'learning_rate': 0.01,
    'subsample': 0.8,  # 适当调整子样本比例
    'colsample_bytree': 0.8,  # 适当调整列采样比例
}

eval_set = [(X_train, y_train), (X_test, y_test)]
model = xgb.XGBRegressor(**params,
                         n_estimators=300,
                         random_state=42,
                         )
model.fit(X_train,
          y_train,
          eval_set=eval_set,
          eval_metric="rmse",
          verbose=True,
          early_stopping_rounds=10,
          )
y_preds = model.predict(X_test)
# 将数据转换为DMatrix格式

# 获取测试集每个样本落入的叶子节点
leaf_indices_test = model.get_booster().predict(dtest, pred_leaf=True)

# 获取训练集每个样本落入的叶子节点
leaf_indices_train = model.get_booster().predict(dtrain, pred_leaf=True)
# 打印叶子节点索引的形状以检查其是否正确
print(f"leaf_indices_test shape: {leaf_indices_test.shape}")
print(f"leaf_indices_train shape: {leaf_indices_train.shape}")
# 如果shape是二维的,我们只取第一棵树的叶子节点索引
if len(leaf_indices_test.shape) > 1:
    leaf_indices_test = leaf_indices_test[:, 0]
    leaf_indices_train = leaf_indices_train[:, 0]

# 创建一个字典来存储每个叶子节点的方差
leaf_leaf_std_dict = {}

# 计算训练集中每个叶子节点的方差并存储到字典中
for leaf_index in np.unique(leaf_indices_train):
    leaf_samples = np.where(leaf_indices_train == leaf_index)[0]
    leaf_std = np.std(y_train[leaf_samples])
    leaf_leaf_std_dict[leaf_index] = leaf_std

# 计算每个测试样本对应的叶子节点的方差
variances = [leaf_leaf_std_dict[leaf_index] for leaf_index in leaf_indices_test]
# 输出结果
for i in range(len(y_preds)):
    if y_preds[i] >= 5:
        print(f"样本 {i}: 预测值 = {y_preds[i]}, 真实值 = {y_test[i]}, 叶子节点样本方差 = {variances[i]}")

上面的代码思路比较简单,通过一个dict记录不同的节点编号下的真值样本的方差,当以后预测到了类似的样本上,也会给出同样的方差值作为置信度的概念。最后的输出如下

样本 34910: 预测值 = 5.0109171867370605, 真实值 = 6, 叶子节点样本方差 = 4.122781275352553
样本 35375: 预测值 = 5.116219997406006, 真实值 = 5, 叶子节点样本方差 = 4.122781275352553
样本 35418: 预测值 = 5.05549955368042, 真实值 = 6, 叶子节点样本方差 = 4.122781275352553
样本 36959: 预测值 = 5.574563980102539, 真实值 = 8, 叶子节点样本方差 = 4.122781275352553
样本 37377: 预测值 = 5.745668411254883, 真实值 = 7, 叶子节点样本方差 = 4.122781275352553
样本 37861: 预测值 = 5.923800468444824, 真实值 = 4, 叶子节点样本方差 = 4.122781275352553

拓展

到了这里应该能打开你的思路了, 还有很多类似的方式可以定义置信度的概念,例如获取每棵树的拟合误差,最后计算一个样本拟合以后所有树的拟合误差的方差。

总而言之

一个很小的例子,希望能推开模型使用的一点点门扉, 当然有一些模型是支持概率分布预测的,也就是能够给出预测值和置信度的概念, 但是思路和上文的思路是完全不一样的。本站也有介绍,当我们有了置信度以后是能够极大的提升模型的使用空间和场景的,希望你能感受到。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×