CatBoostRegressor 树中叶值的比例是多少?

What is the scale of the leaf values in a CatBoostRegressor tree?

谜题

我无法解释 CatBoostRegressor 树的叶子中的值。拟合模型正确地捕获了数据集的逻辑,但是当我绘制树图时值的比例与实际数据集的比例不匹配。

在此示例中,我们预测 size,其值约为 15-30,具体取决于观察的 colorage

import random
import pandas as pd
import numpy as np
from catboost import Pool, CatBoostRegressor

# Create a fake dataset.
n = 1000
random.seed(1)
df = pd.DataFrame([[random.choice(['red', 'blue', 'green', 'yellow']),
                    random.random() * 100]
                   for i in range(n)],
                  columns=['color', 'age'])
df['size'] = np.select([np.logical_and(np.logical_or(df.color == 'red',
                                                     df.color == 'blue'),
                                       df.age < 50),
                        np.logical_or(df.color == 'red',
                                      df.color == 'blue'),
                        df.age < 50,
                        True],
                       [np.random.normal(loc=15, size=n),
                        np.random.normal(loc=20, size=n),
                        np.random.normal(loc=25, size=n),
                        np.random.normal(loc=30, size=n)])

# Fit a CatBoost regressor to the dataset.
pool = Pool(df[['color', 'age']], df['size'],
            feature_names=['color', 'age'], cat_features=[0])
m = CatBoostRegressor(n_estimators=10, max_depth=3, one_hot_max_size=4,
                      random_seed=1)
m.fit(pool)

# Visualize the first regression tree (saves to a pdf).  Values in leaf nodes
# are not on the scale of the original dataset.
m.plot_tree(tree_idx=0, pool=pool).render('regression_tree')

模型在 age 上以正确的值(大约 50)拆分,并且它正确地了解到红色和蓝色观察值不同于绿色和黄色观察值。叶子中的值排序正确(例如,red/blue 50 以下的观测值最小),但比例完全不同。

predict() 函数 returns 原始数据集规模上的值。

>>> df['predicted'] = m.predict(df)
>>> df.sample(n=10)
      color        age       size  predicted
676  yellow  66.305095  30.113389  30.065519
918  yellow  55.209821  29.944622  29.464825
705  yellow   1.742565  24.209283  24.913988
268    blue  76.749979  20.513211  20.019020
416    blue  59.807800  18.807197  19.949336
326     red   4.621795  14.748898  14.937314
609  yellow  99.165027  28.942243  29.823422
421   green  40.731038  26.078450  24.846742
363  yellow   2.461971  25.506517  24.913988
664     red   5.206448  16.579706  14.937314

我试过的

我想知道是否正在进行某种简单的规范化,但事实显然并非如此。例如,年龄 < 50 的红色观察值在树中被指定为 -3.418,这与真实值的 z 分数(大约 15)相去甚远。

>>> (15 - np.mean(df['size'])) / np.std(df['size'])
-1.3476124913754326

问了一个关于 XGBoost 的类似问题。接受的答案解释说,这些值都应该添加到 base_score 参数中;但是,如果 CatBoost 中有类似的参数,我就找不到了。 (如果参数在 CatBoost 中使用不同的名称,我不知道它叫什么。)此外,CatBoost 树中的值不仅仅与原始数据集有一些常量的不同;最大和最小叶节点之间的差异约为7,而原始数据集中size的最大值和最小值之间的差异约为15.

我查看了 CatBoost 文档但没有成功。 “Model values”部分表示回归值是“应用模型产生的数字”,这对我来说表明它们应该在原始数据集的范围内。 (predict() 的输出也是如此,所以我不清楚这部分是否适用于绘制的决策树。)

搜索此函数get_scale_and_bias Return 模型的规模和偏差。

这些值会影响应用模型的结果,因为模型预测结果的计算方式如下: \sum leaf_values \cdot scale + bias∑leaf_values⋅scale+bias

问题中例子的应用

这里是适用于同一数据集的稍微不同的模型(使用与上述相同的代码)。

要将叶值转换为原始数据比例,请使用 get_scale_and_bias() 返回的比例和偏差。我使用 _get_tree_leaf_values() 提取了叶子;此函数 returns 表示叶子的字符串,因此我们必须进行一些正则表达式解析以获取实际值。我还 hand-coded 每个叶子的期望值,基于上面的 data-generating 过程。

# Get the scale and bias from the model.
sb = m.get_scale_and_bias()

# Apply the scale and bias to the leaves of the tree; compare to expected
# values for each leaf.
import re
[{'expected': [15, 25, 25, None, 20, 30, 30, None][i],
  'actual': (float(re.sub(r'^val = (-?[0-9]+([.][0-9]+)?).*$', '\1', leaf))
             * sb[0]) + sb[1]}
 for i, leaf in enumerate(m._get_tree_leaf_values(0))]

我们看到预测值并不完美,但至少在正确的范围内。

[{'expected': 15, 'actual': 19.210155044555663},
 {'expected': 25, 'actual': 24.067155044555665},
 {'expected': 25, 'actual': 24.096155044555665},
 {'expected': None, 'actual': 22.624155044555664},
 {'expected': 20, 'actual': 21.309155044555663},
 {'expected': 30, 'actual': 26.244155044555665},
 {'expected': 30, 'actual': 26.249155044555664},
 {'expected': None, 'actual': 22.624155044555664}]