Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

UnbiasedGBM

概要

CatBoostとの違いは?

CatBoostは「勾配を不偏推定する」という考えで、決定木の分割点探索において、勾配のleave-one-out 推定(ii番目のサンプルの勾配をそれ以外のn1n-1サンプルで推定する)を提案し、またその計算を効率化するordered boostingを提案した。

UnbiasedGBMもサンプルを分割することでGainを不偏推定するという点で考え方は似ている。

しかし、CatBoostのordered boostingはoblivious tree(対称木)で使えるトリックであり、一般のGBDTに使えるものではない。
UnbiasedGBMはサンプルを3分割ないし2分割するというものでシンプルかつ決定木のタイプを選ばない手法。

先行研究

  • Quinlan (1986) は 分類木のバイアスに対処するため、 information gain ratio を提案

  • Sandri & Zuccolotto (2008) は分類木の分割の改善を損失の減少と正のバイアスへと分解。疑似データセットを使ってバイアスを推定して減算する方法を提案。Nembrini et al. (2018) はこれを効率化させた。

    • → UnbiasedGBMでは計算量を大きく増加させる疑似データは使わない

  • Li, et al. (2019) はRandom ForestのMean Decrease Impurityのバイアスを改善したが、完全にはバイアスを除去できず残った。

  • Zho & Hooker (2021) はRandom Forest とGBDT向けにUnbiased な Feature Importance を提案した。しかし平均二乗誤差を前提にした不偏性であったためGBDTに一般化は難しい。

    • → UnbiasedGBMでは特定の誤差関数の形状に依存せず、GBDTに一般化できる方法で不偏性の獲得をする

GBDTのおさらい

モデル

データセット D={(x,y)}D=\{(\mathbf{x}, y)\} を考える。ここで (x,y)(\mathbf{x}, y) は未知の分布T\mathcal{T}から独立に得られたとする。 決定木のアンサンブルは予測値を次のように出力する:

y^=ϕ(x)=k=1Kfk(x),fkF\hat{y}=\phi(\mathbf{x})=\sum_{k=1}^K f_k(\mathbf{x}), f_k \in \mathcal{F}

ここで F={f(x)=wq(x)}\mathcal{F}=\left\{f(\mathbf{x})=w_{q(\mathbf{x})}\right\} は回帰木の空間である。qqは木の構造を示し、q(x)q(\mathbf{x}) は 事例 x\mathbf{x} の葉のインデックスへの写像である。

誤差関数

次の目的関数を最小化するように学習する。

L(ϕ)=Ex,y[l(ϕ(x),y)]\mathcal{L}(\phi)=\mathbb{E}_{\mathbf{x}, y}[l(\phi(\mathbf{x}), y)]

ここでϕt\phi_ttt番目のイテレーションにおけるモデルを意味する。学習では貪欲的に、新たな回帰木ftf_tを追加したとき目的関数 L(ϕt1+ft)\mathcal{L}\left(\phi_{t-1}+f_t\right) を最小化するような回帰木を追加する。 この目的関数は二次近似で得られる

L(ϕt1+ft)Ex,y[l(y^t1,y)+g(x,y)ft(x)+12h(x,y)ft(x)2]\begin{gathered} \mathcal{L}\left(\phi_{t-1}+f_t\right) \approx \mathbb{E}_{\mathbf{x}, y}\left[l\left(\hat{y}_{t-1}, y\right)+g(\mathbf{x}, y) f_t(\mathbf{x}) + \frac{1}{2} h(\mathbf{x}, y) f_t(\mathbf{x})^2\right] \end{gathered}

ここで

g(x,y)=l(ϕt1(x),y)ϕt1(x),h(x,y)=2l(ϕt1(x),y)(ϕt1(x))2g(\mathrm{x}, y)=\frac{\partial l\left(\phi_{t-1}(\mathrm{x}), y\right)}{\partial \phi_{t-1}(\mathrm{x})}, \quad h(\mathrm{x}, y)=\frac{\partial^2 l\left(\phi_{t-1}(\mathrm{x}), y\right)}{\left(\partial \phi_{t-1}(\mathrm{x})\right)^2}

定数項を消すことで

L~(ϕt1+ft)=Ex,y[g(x,y)ft(x)+12h(x,y)ft(x)2]\tilde{\mathcal{L}}\left(\phi_{t-1}+f_t\right)=\mathbb{E}_{\mathbf{x}, y}\left[g(\mathbf{x}, y) f_t(\mathbf{x})+\frac{1}{2} h(\mathbf{x}, y) f_t(\mathbf{x})^2\right]

となる。

葉ノードIIによる損失関数への貢献は

L(I)=Ex,y[1{q(x)=I}(g(x,y)f(x)+12h(x,y)f(x)2)]=Ex,y[1{q(x)=I}(g(x,y)wI+12h(x,y)wI2)]=P(xI)(μg(I)wI+12μh(I)wI2)\begin{aligned} \mathcal{L}(I) & =\mathbb{E}_{\mathbf{x}, y}\left[\mathbf{1}_{\{q(\mathbf{x})=I\}}\left(g(\mathbf{x}, y) f(x)+\frac{1}{2} h(\mathbf{x}, y) f(x)^2\right)\right] \\ & =\mathbb{E}_{\mathbf{x}, y}\left[\mathbf{1}_{\{q(\mathbf{x})=I\}}\left(g(\mathbf{x}, y) w_I+\frac{1}{2} h(\mathbf{x}, y) w_I^2\right)\right] \\ & =P(\mathbf{x} \in I)\left(\mu_g(I) w_I+\frac{1}{2} \mu_h(I) w_I^2\right) \end{aligned}

で、ここで

μg(I)=Ex,y[g(x,y)]μh(I)=Ex,y[h(x,y)]\mu_g(I)=\mathbb{E}_{\mathbf{x}, y}[g(\mathbf{x}, y)] \\ \mu_h(I)=\mathbb{E}_{\mathbf{x}, y}[h(\mathbf{x}, y)]

である。葉ノード II の最適な重み wIw_I は、微分してゼロ(L(I)/w=0\partial \mathcal{L}(I)/\partial w = 0)とおいて整理すれば

wI=μg(I)μh(I)w_I=-\frac{\mu_g(I)}{\mu_h(I)}

となるため、このwIw_Iを損失関数に代入すれば、対応する最適な損失は

L(I)=12μg(I)2μh(I)P(xI).\mathcal{L}(I)=-\frac{1}{2} \frac{\mu_g(I)^2}{\mu_h(I)} P(\mathrm{x} \in I) .

となる。

gain推定

左右に分割した子ノードの損失の和がもとのノードの損失より小さくなった分が情報利得(gain)である:

Gain(I,θ)=L(I){L(IL)+L(IR)}\operatorname{Gain}(I, \theta)=\mathcal{L}(I)- \{ \mathcal{L}(I_L)+\mathcal{L}(I_R) \}

実際には真の分布は未知なので標本を使った推定量(経験損失)を使う:

L~(I)=12(1nIiIgi)21nIiIhinIn=12nGI2HIGain~(I,θ)=12n(GL2HL+GR2HRGI2HI)\begin{aligned} \widetilde{\mathcal{L}}(I) & =-\frac{1}{2} \frac{\left(\frac{1}{n_I} \sum_{i \in I} g_i\right)^2}{\frac{1}{n_I} \sum_{i \in I} h_i} \frac{n_I}{n}\\ &=-\frac{1}{2 n} \frac{G_I^2}{H_I} \\ \widetilde{\operatorname{Gain}}(I, \theta) & =\frac{1}{2 n}\left(\frac{G_L^2}{H_L}+\frac{G_R^2}{H_R}-\frac{G_I^2}{H_I}\right) \end{aligned}

ここで

  • GI=iIgiG_I=\sum_{i \in I} g_i

  • HI=iIhiH_I=\sum_{i \in I} h_i

  • nIn_I:ノードIIの数

Gain推定のバイアス

Gain~\widetilde{\operatorname{Gain}} はある特徴量での分割による損失の減少を推定する。

直感的には、もし Gain~\widetilde{\operatorname{Gain}} が不偏であれば、目的変数と独立な特徴量でランダムに分割した場合、期待値がゼロになるとおもわれる。
しかし実際には、どの特徴量のどの分割点に対しても常に非負な値をもつ。

また、Gain~(I,θ)=0\widetilde{\operatorname{Gain}}(I, \theta) = 0 になることはめったにない

証明(Appendix Aより)

まず、L~(I)\tilde{\mathcal{L}}(I) を最適化の形に書き換える:

L~(I)=12nGI2HI=1nminw(12HIw2+GIw)=1nminwiI(12hiw2+giw)=1nminwiIli(w)\begin{aligned} \tilde{\mathcal{L}}(I) &= -\frac{1}{2n} \frac{G_I^2}{H_I} \\ &= \frac{1}{n} \min_w \left( \frac{1}{2} H_I w^2 + G_I w \right) \\ &= \frac{1}{n} \min_w \sum_{i \in I} \left( \frac{1}{2} h_i w^2 + g_i w \right)\\ &= \frac{1}{n} \min_w \sum_{i \in I} l_i(w) \end{aligned}

ここで、li(w)=12hiw2+giwl_i(w) = \frac{1}{2} h_i w^2 + g_i w である。

I=ILIRI = I_L \cup I_R であり、分割して個別に最適化したほうが損失は下げやすいため、LL および RR の最適損失の合計は II の最適損失よりも小さい。したがって、

Gain~(I,θ)=L~(I)L~(IL)L~(IR)=1n(minwiIli(w)minwLiILli(wL)minwRiIRli(wR))0\begin{aligned} \widetilde{\mathrm{Gain}}(I, \theta) &= \tilde{\mathcal{L}}(I) - \tilde{\mathcal{L}}(I_L) - \tilde{\mathcal{L}}(I_R)\\ &= \frac{1}{n} \left( \min_w \sum_{i \in I} l_i(w) - \min_{w_L} \sum_{i \in I_L} l_i(w_L) - \min_{w_R} \sum_{i \in I_R} l_i(w_R) \right)\\ &\ge 0 \end{aligned}
Discussion:Gainが0となる場合について

上式から、Gain~(I,θ)=0\widetilde{\mathrm{Gain}}(I, \theta) = 0 が成り立つのは次の場合に限られる:

w=wL=wRw^* = w_L^* = w_R^*

すなわち次の関係が成り立つときである:

GLHL=GRHR\frac{G_L}{H_L} = \frac{G_R}{H_R}

これは、Gain~(I,θ)=0\widetilde{\mathrm{Gain}}(I, \theta) = 0 となるための必要十分条件であるが、実際の応用においてこの条件が成立することは極めて稀である。

このため

  1. 有益な情報をもたらさない特徴量の上で不要な分割をする可能性がある(→学習に悪影響)

  2. 正のgainであっても、その特徴量が本当にモデルに貢献しているとは限らない(→Feature Importanceが不正確になる)

勾配推定のバイアス

gain推定のバイアスの原因のひとつは、勾配の期待値の推定量 GI2=(iIgi)2G_I^2 = (\sum_{i \in I} g_i)^2 が真値μg2=(Ex,y[g(x,y)])2\mu_g^2 = (\mathbb{E}_{\mathbf{x}, y}[g(\mathbf{x}, y)])^2の不偏推定量ではないこと

ED[(1nIiIgi)2]=ED[1nI2i,jI,ij2gigj]+ED[1nI2iIgi2]=nI1nIμg(I)2+1nI(μg(I)2+σg(I)2)=μg(I)2+1nIσg(I)2\begin{aligned} \mathbb{E}_{\mathcal{D}}\left[\left(\frac{1}{n_I} \sum_{i \in I} g_i\right)^2\right] &= \mathbb{E}_{\mathcal{D}}\left[\frac{1}{n_I^2} \sum_{i, j \in I, i \neq j} 2 g_i g_j\right]+\mathbb{E}_{\mathcal{D}}\left[\frac{1}{n_I^2} \sum_{i \in I} g_i^2\right] \\ &= \frac{n_I-1}{n_I} \mu_g(I)^2+\frac{1}{n_I}\left(\mu_g(I)^2+\sigma_g(I)^2\right) \\ &= \mu_g(I)^2+\frac{1}{n_I} \sigma_g(I)^2 \end{aligned}

例:MSE

MSEによる回帰問題は、ヘシアンが定数であることを考えると(2乗誤差12(yy^)2\frac{1}{2}(y -\hat{y})^2のgradientは残差yy^y - \hat{y}、hessianは-1)、葉IIの損失

L~(I)=12(1nIiIgi)21nIiIhinIn=12nnI(1nIiIgi)2\begin{aligned} \widetilde{\mathcal{L}}(I) &= -\frac{1}{2} \frac{\left(\frac{1}{n_I} \sum_{i \in I} g_i\right)^2}{\frac{1}{n_I} \sum_{i \in I} h_i} \frac{n_I}{n}\\ &= \frac{1}{2 n} n_I \left(\frac{1}{n_I} \sum_{i \in I} g_i\right)^2\\ \end{aligned}

の期待値は

ED[L~(I)]=12n(ED[nI]μg(I)2+σg(I)2)\mathbb{E}_{\mathcal{D}}[\widetilde{\mathcal{L}}(I)]=\frac{1}{2 n}\left(\mathbb{E}_{\mathcal{D}}\left[n_I\right] \mu_g(I)^2+\sigma_g(I)^2\right)

となる。

目的変数と独立であり有益な情報のない(uninformativeな)特徴量については、任意の分割点において常に

μg(I)=μg(IL)=μg(IR)σg(I)=σg(IL)=σg(IR)\mu_g(I)=\mu_g\left(I_L\right)=\mu_g\left(I_R\right) \\ \sigma_g(I)=\sigma_g\left(I_L\right)=\sigma_g\left(I_R\right)

となる。(例えば二乗誤差の勾配は残差だが、uninformativeな特徴量ならどこで分割しても残差の期待値は変わらない、みたいな。)

よって、uninformativeな特徴量における分割のgainは

ED[Gain~(I,θ)]=ED[L~(IL)]+ED[L~(IR)]ED[L~(I)]=12n(σg(I)2+ED[nL+nRnI]μg(I)2)=12nσg(I)20\begin{aligned} \mathbb{E}_{\mathcal{D}}[\widetilde{\operatorname{Gain}}(I, \theta)] &= \mathbb{E}_{\mathcal{D}}\left[\widetilde{\mathcal{L}}\left(I_L\right)\right]+\mathbb{E}_{\mathcal{D}}\left[\widetilde{\mathcal{L}}\left(I_R\right)\right]-\mathbb{E}_{\mathcal{D}}[\widetilde{\mathcal{L}}(I)] \\ &= \frac{1}{2 n}\left(\sigma_g(I)^2+\mathbb{E}_{\mathcal{D}}\left[n_L+n_R-n_I\right] \mu_g(I)^2\right) \\ &= \frac{1}{2 n} \sigma_g(I)^2 \geq 0 \end{aligned}

分割点探索におけるバイアス

木は学習の際に損失をもっとも下げる最適な分割点を探索する。

この戦略は2つの問題をもたらす:

  1. 分割点の探索はカーディナリティの高い特徴量(連続変数やカテゴリ数の多いカテゴリカル変数)を高く評価する。カーディナリティが高いと分割候補点が多いため、分割される尤もらしさが大きくなる。

  2. 分割探索アルゴリズムは汎化性能を考慮せず訓練データでの最適解を選ぶ

この問題について、例を使ってさらに説明する

合成データを生成するとする。

  • X1X_1は二値変数

  • X2X_2は6つのカテゴリをもつカテゴリカル変数とする(各カテゴリは一様に分布)

  • X3N(0,1)X_3 \sim N(0,1)は連続変数

  • y=0.1X1+ϵy = 0.1 X_1 + \epsilon の目的変数への回帰問題を考える

    • ここで ϵN(0,1)\epsilon \sim N(0, 1)

つまり、真のimportanceとしてはX1X_1だけが正の値になるはず、というデータになっている。

これを実行してみるとX3X_3のimportanceが高く推定される(なおLightGBMのsplit importanceでも同様)

Source
import numpy as np
import pandas as pd
from sklearn.inspection import permutation_importance
from lightgbm import LGBMRegressor
import shap
import contextlib
import io

def gen_data(n, seed):
    rng = np.random.default_rng(seed)
    # X1: 二値, X2: 6カテゴリ(一様), X3: 連続 N(0,1)
    X1 = rng.integers(0, 2, size=n)
    X2 = rng.integers(0, 6, size=n)
    X3 = rng.normal(0, 1, size=n)
    # y = 0.1 * X1 + ε,  ε ~ N(0,1)
    epsilon = rng.normal(0, 1, size=n)
    y = 0.1 * X1 + epsilon
    df = pd.DataFrame({
        "X1": X1,
        "X2": pd.Series(X2, dtype="category"),  # LightGBM sklearn APIはcategory dtypeを解釈可能
        "X3": X3,
        "y": y
    })
    X = df[["X1", "X2", "X3"]]
    y = df["y"]
    return X, y


def trial(n, seed):
    X_train, y_train = gen_data(n=n, seed=seed)
    X_test, y_test = gen_data(n=n, seed=seed + 1)
    params = dict(
        n_estimators=200,
        learning_rate=0.05,
        num_leaves=31,
        subsample=0.9,
        colsample_bytree=0.9,
        min_child_samples=20,
        force_col_wise=True,  # 小規模データでの起動オーバーヘッド削減
        random_state=0,
    )
    feat_names = ["X1", "X2", "X3"]
    results = []

    for importance_type in ["gain", "split"]:
        model = LGBMRegressor(**params, importance_type=importance_type)
        with contextlib.redirect_stdout(io.StringIO()):
            model.fit(X_train, y_train)

        # LightGBM built-in importance
        importances = model.feature_importances_
        feat_imp = dict(zip(feat_names, importances))
        results.append(dict(importance_type=importance_type, **feat_imp))

        # Permutation importance
        pi = permutation_importance(model, X_test, y_test, n_repeats=10, random_state=123)
        pi_mean = pi.importances_mean
        perm_imp = dict(zip(feat_names, pi_mean))
        results.append(dict(importance_type=f"permutation-{importance_type}", **perm_imp))

        # SHAP importance(global mean absolute shap)
        explainer = shap.TreeExplainer(model)
        shap_values = explainer.shap_values(X_test)
        # shap_values.shape = (n_samples, n_features)
        shap_mean_abs = np.abs(shap_values).mean(axis=0)
        shap_imp = dict(zip(feat_names, shap_mean_abs))
        results.append(dict(importance_type=f"shap-{importance_type}", **shap_imp))

    return results


# run ----------------------------------------
n_trials = 10
results = [trial(n=1000, seed=seed) for seed in range(n_trials)]
results = sum(results, [])
results = pd.DataFrame(results)

# plot ----------------------------------------
import matplotlib.pyplot as plt
import seaborn as sns

fig, axis = plt.subplots(figsize=[10, 9], ncols=2, nrows=3, dpi=70)

sns.boxplot(results.query("importance_type == 'gain'"), ax=axis[0,0])
axis[0,0].set(title="Gain importance", ylabel="importance")

sns.boxplot(results.query("importance_type == 'split'"), ax=axis[0,1])
axis[0,1].set(title="Split importance", ylabel="importance")

sns.boxplot(results.query("importance_type == 'permutation-gain'"), ax=axis[1,0])
axis[1,0].set(title="Permutation importance\n(gain-based train)", ylabel="importance")

sns.boxplot(results.query("importance_type == 'permutation-split'"), ax=axis[1,1])
axis[1,1].set(title="Permutation importance\n(split-based train)", ylabel="importance")

sns.boxplot(results.query("importance_type == 'shap-gain'"), ax=axis[2,0])
axis[2,0].set(title="SHAP global importance\n(gain-based train)", ylabel="|mean SHAP|")

sns.boxplot(results.query("importance_type == 'shap-split'"), ax=axis[2,1])
axis[2,1].set(title="SHAP global importance\n(split-based train)", ylabel="|mean SHAP|")

fig.suptitle("Bias of Importance with LightGBM (true importance is X1>0, X2=0, X3=0)")
fig.tight_layout()
plt.show()
<Figure size 700x630 with 6 Axes>
Source
import numpy as np
import pandas as pd
from sklearn.inspection import permutation_importance
from catboost import CatBoostRegressor, Pool
import contextlib
import io

def gen_data(n, seed):
    rng = np.random.default_rng(seed)
    # X1: 二値, X2: 6カテゴリ(一様), X3: 連続 N(0,1)
    X1 = rng.integers(0, 2, size=n)
    X2 = rng.integers(0, 6, size=n)
    X3 = rng.normal(0, 1, size=n)
    # y = 0.1 * X1 + ε,  ε ~ N(0,1)
    epsilon = rng.normal(0, 1, size=n)
    y = 0.1 * X1 + epsilon
    df = pd.DataFrame({
        "X1": X1,
        "X2": pd.Series(X2, dtype="category"),  # CatBoostではcat_features指定で扱う
        "X3": X3,
        "y": y
    })
    X = df[["X1", "X2", "X3"]]
    y = df["y"]
    return X, y


def trial(n, seed):
    X_train, y_train = gen_data(n=n, seed=seed)
    X_test, y_test = gen_data(n=n, seed=seed + 1)

    params = dict(
        iterations=200,
        learning_rate=0.05,
        depth=6,
        subsample=0.9,
        rsm=0.9,
        l2_leaf_reg=3.0,
        random_seed=0,
        loss_function='RMSE',
        verbose=False,
        allow_writing_files=False
    )
    cat_idx = [1]  # X2のみカテゴリ変数
    feat_names = ["X1", "X2", "X3"]
    results = []

    # CatBoost内蔵のimportanceタイプ
    importance_types = [
        ("PredictionValuesChange", "pvc"),
        ("LossFunctionChange", "lfc"),
    ]

    for imp_type, short in importance_types:
        model = CatBoostRegressor(**params)
        with contextlib.redirect_stdout(io.StringIO()):
            model.fit(X_train, y_train, cat_features=cat_idx)

        train_pool = Pool(X_train, y_train, cat_features=cat_idx)
        test_pool = Pool(X_test, y_test, cat_features=cat_idx)

        # (1) 内部重要度
        importances = model.get_feature_importance(train_pool, type=imp_type)
        feat_imp = dict(zip(feat_names, importances))
        results.append(dict(importance_type=imp_type, **feat_imp))

        # (2) パーミュテーション重要度
        pi = permutation_importance(model, X_test, y_test, n_repeats=10, random_state=123)
        pi_mean = pi.importances_mean
        perm_imp = dict(zip(feat_names, pi_mean))
        results.append(dict(importance_type=f"permutation-{short}", **perm_imp))

        # (3) SHAP(global mean absolute shap)
        shap_values = model.get_feature_importance(test_pool, type="ShapValues")
        # shap_values.shape = (n_samples, n_features + 1) 最後の列がbase value
        shap_values = shap_values[:, :-1]
        shap_mean_abs = np.abs(shap_values).mean(axis=0)
        shap_imp = dict(zip(feat_names, shap_mean_abs))
        results.append(dict(importance_type=f"shap-{short}", **shap_imp))

    return results


# run ----------------------------------------
n_trials = 10
results = [trial(n=1000, seed=seed) for seed in range(n_trials)]
results = sum(results, [])
results = pd.DataFrame(results)

# plot ----------------------------------------
import matplotlib.pyplot as plt
import seaborn as sns

fig, axis = plt.subplots(figsize=[10, 9], ncols=2, nrows=3, dpi=70)

sns.boxplot(results.query("importance_type == 'PredictionValuesChange'"), ax=axis[0,0])
axis[0,0].set(title="CatBoost: PredictionValuesChange", ylabel="importance")

sns.boxplot(results.query("importance_type == 'LossFunctionChange'"), ax=axis[0,1])
axis[0,1].set(title="CatBoost: LossFunctionChange", ylabel="importance")

sns.boxplot(results.query("importance_type == 'permutation-pvc'"), ax=axis[1,0])
axis[1,0].set(title="Permutation importance\n(PredictionValuesChange-trained model)", ylabel="importance")

sns.boxplot(results.query("importance_type == 'permutation-lfc'"), ax=axis[1,1])
axis[1,1].set(title="Permutation importance\n(LossFunctionChange-trained model)", ylabel="importance")

sns.boxplot(results.query("importance_type == 'shap-pvc'"), ax=axis[2,0])
axis[2,0].set(title="SHAP global importance\n(PredictionValuesChange-trained model)", ylabel="|mean SHAP|")

sns.boxplot(results.query("importance_type == 'shap-lfc'"), ax=axis[2,1])
axis[2,1].set(title="SHAP global importance\n(LossFunctionChange-trained model)", ylabel="|mean SHAP|")

fig.suptitle("Bias of Importance with CatBoost (true importance is X1>0, X2=0, X3=0)")
fig.tight_layout()
plt.show()
<Figure size 700x630 with 6 Axes>

バイアスの原因まとめ

バイアスの原因は

  1. 最適分割探索のアルゴリズムのバイアス

    • 最適な分割の探索とGainの評価を同じデータセットで行っているため過学習のバイアスが入る

  2. Gainの推定量におけるバイアス

Unbiased Gain

2つのバイアスの原因に対処するために、データの分割と推定の計算を変える

前提

訓練データセット D={(xi,yi)} \mathcal{D} = \{(x_i, y_i)\} と検証データセット D={(xi,yi)} \mathcal{D}' = \{(x_i', y_i')\} を持っていると仮定する。

ある葉ノード II と分割 I=ILIRI = I_L \cup I_R に対して、葉 I,IL,IRI, I_L, I_R にそれぞれ nI,nL,nRn_I, n_L, n_R 個の訓練データとnI,nL,nRn'_I, n'_L, n'_R 個の検証データが含まれるとする。

手順

まず、訓練データを用いて μg(I)\mu_g(I) を次のように推定する。

μ^g(I)=1nIGI=1nIq(xi)=Igi\hat{\mu}_g(I) = \frac{1}{n_I} G_I = \frac{1}{n_I} \sum_{q(x_i)=I} g_i

次に、検証データの中から各ノードにおいて k=min(nL,nR)k = \min(n'_L, n'_R) 個のサンプルをランダムに選択する。

その kk 個の検証サンプルを用いて、μg(I)\mu_g(I) および μh(I)\mu_h(I) を次のように推定する。

μ^g(I)=1kGI=1kq(xi)=Igiδ(I,i)\hat{\mu}'_g(I) = \frac{1}{k} G'_I = \frac{1}{k} \sum_{q(x_i')=I} g_i' \cdot \delta(I, i)
μ^h(I)=1kHI=1kq(xi)=Ihiδ(I,i)\hat{\mu}'_h(I) = \frac{1}{k} H'_I = \frac{1}{k} \sum_{q(x_i')=I} h_i' \cdot \delta(I, i)

ここで、δ(I,i)\delta(I, i) はバイナリ指示関数であり、検証サンプル ii が選択された場合に 1、そうでない場合に 0 となる。

最後に、葉ノード II の損失を次のように計算する。

L~(I)=12μ^g(I)μ^g(I)μ^h(I)nIn=12nGIGIHI\tilde{\mathcal{L}}(I) = \frac{1}{2} \frac{\hat{\mu}_g(I) \cdot \hat{\mu}'_g(I)}{\hat{\mu}'_h(I)} \cdot \frac{n_I}{n} = -\frac{1}{2n} G_I \cdot \frac{G'_I}{H'_I}

ここで、GIG_I は訓練データから計算され、 GIG'_I および HIH'_I は検証データから計算される。

同様にして、L~(IL)\tilde{\mathcal{L}}(I_L) および L~(IR)\tilde{\mathcal{L}}(I_R) も計算できる。 このとき、選択される検証サンプルの数 kkI,IL,IRI, I_L, I_R すべてにおいて同じである。

最終的に、Unbiased Gainは次のように計算される

Gain~ub(I,θ)=L~(I)L~(IL)L~(IR)\widetilde{\operatorname{Gain}}_{\mathrm{ub}}(I, \theta)=\widetilde{\mathcal{L}}(I)-\widetilde{\mathcal{L}}\left(I_L\right)-\widetilde{\mathcal{L}}\left(I_R\right)

Unbiased gain の設計上の重要な点は、μg(I)\mu_g(I)μg(IL)\mu_g(I_L)、および μg(IR)\mu_g(I_R) を ノード I,IL,IRI, I_L, I_R に含まれるすべての検証サンプルを用いて推定するのではなく、それぞれのノードからランダムに kk 個のサンプルを抽出して推定を行う点にある。

この設計により、不偏性が保証される。

証明

(Appendix Bより)

μ^g(I)\hat{\mu}_g'(I)μ^g(IL)\hat{\mu}_g'(I_L)μ^g(IR)\hat{\mu}_g'(I_R)、および μ^h(I)\hat{\mu}_h'(I)μ^h(IL)\hat{\mu}_h'(I_L)μ^h(IR)\hat{\mu}_h'(I_R) はすべて、同じ数のサンプル kk によって推定されるので、

k,E ⁣[μ^g(I)μ^h(I)|k]=E ⁣[μ^g(IL)μ^h(IL)|k]=E ⁣[μ^g(IR)μ^h(IR)|k](8)\forall k, \quad \mathbb{E}\!\left[\frac{\hat{\mu}_g'(I)}{\hat{\mu}_h'(I)} \,\middle|\, k\right] = \mathbb{E}\!\left[\frac{\hat{\mu}_g'(I_L)}{\hat{\mu}_h'(I_L)} \,\middle|\, k\right] = \mathbb{E}\!\left[\frac{\hat{\mu}_g'(I_R)}{\hat{\mu}_h'(I_R)} \,\middle|\, k\right] \tag{8}

ここで、E\mathbb{E}ED\mathbb{E}_{\mathcal{D}'} の略記である。したがって、

E ⁣[Gain~ub]=E ⁣[L~ub(I)]E ⁣[L~ub(IL)]E ⁣[L~ub(IR)]=GI2nE ⁣[μ^g(I)μ^h(I)]+GL2nE ⁣[μ^g(IL)μ^h(IL)]+GR2nE ⁣[μ^g(IR)μ^h(IR)]=GL+GRGI2nkP(k)E ⁣[μ^g(I)μ^h(I)|k]=0\begin{aligned} \mathbb{E}\!\left[\widetilde{\mathrm{Gain}}_{\text{ub}}\right] &= \mathbb{E}\!\left[\widetilde{\mathcal{L}}_{\text{ub}}(I)\right] - \mathbb{E}\!\left[\widetilde{\mathcal{L}}_{\text{ub}}(I_L)\right] - \mathbb{E}\!\left[\widetilde{\mathcal{L}}_{\text{ub}}(I_R)\right] \\[4pt] &= -\frac{G_I}{2n}\,\mathbb{E}\!\left[\frac{\hat{\mu}_g'(I)}{\hat{\mu}_h'(I)}\right] + \frac{G_L}{2n}\,\mathbb{E}\!\left[\frac{\hat{\mu}_g'(I_L)}{\hat{\mu}_h'(I_L)}\right] + \frac{G_R}{2n}\,\mathbb{E}\!\left[\frac{\hat{\mu}_g'(I_R)}{\hat{\mu}_h'(I_R)}\right] \\[4pt] &= \frac{G_L + G_R - G_I}{2n} \sum_k P(k)\, \mathbb{E}\!\left[\frac{\hat{\mu}_g'(I)}{\hat{\mu}_h'(I)} \,\middle|\, k\right] \\[4pt] &= 0 \end{aligned}

The Motivation Behind the Unbiased Gain (Appendix B.1)

なぜ追加の検証セットが必要なのか?
直感的な説明としては、「同じデータセットを使用して最適な分割を見つけて評価するべきではない」ということ。

検証セットを用いて損失の減少を再計算することは可能か?
検証セットを用いる直感的な方法は、ツリー構造を固定し、検証セットを用いて損失の減少を再計算すること。しかし、情報価値のない特徴量に基づく分割の場合、検証セットを用いて評価される分割ゲインは(ゼロではなく)負の値になることが予想される。

なぜランダムにkkサンプルを抽出する必要があるのか?
もし μ^g(I)\hat{\mu}_g'(I)μ^g(IL)\hat{\mu}_g'(I_L)μ^g(IR)\hat{\mu}_g'(I_R)、および μ^h(I)\hat{\mu}_h'(I)μ^h(IL)\hat{\mu}_h'(I_L)μ^h(IR)\hat{\mu}_h'(I_R) が異なる数のサンプルから推定されると、証明の式(8)は成り立たない
(サンプルサイズが違うと、大数の法則によって一致する速度が異なってしまうため?)

人工データでの例

前出の人工データによるシミュレーション実験においてもUnbiasedGain (Figure 1(b))は X1X_1のGainを適切に評価している

UnbiasedGBM

GBDTのバイアスは2点あった:

  1. カーディナリティが高い特徴についての分割選択がバイアスをもつ

  2. 汎化性能は考えずに訓練データでの最適分割を選ぶため、過学習しやすい

これらに対応するため、最適分割探索アルゴリズムを改良する。

Remark

  • 最適な分割の探索と最適な特徴量の探索を異なるデータセットで行っているので、高いカーディナリティによるバイアスを除去できる。

  • Gain~ub0\widetilde{\mathrm{Gain}}_{\mathrm{ub}} \leq 0 なら学習を停止するのはfeature importanceの不偏性を保つために行っている。

    • gainが小さいときに学習を止めるleaf-wise early-stoppingは既存手法(LightGBMなど)にもあるが、UnbiasedGBMは汎化性能を考慮している点と、学習停止の閾値の設定に理論的根拠がある点が異なる

データの分割比率は?

異なる比率での分割を試す実験を行ったところ、同数に分割する、つまり D=D1=D2|\mathcal{D}| = |\mathcal{D}_1'| = |\mathcal{D}_2'| が一番よかったとのこと。

しかし、 Appendix E のmid-scale data (n = 4000 ~ 30000程度) での実験では、D\mathcal{D}D1=D2\mathcal{D}_1' = \mathcal{D}_2'1:1+11: 1+1 とする(つまりearly stoppingと特徴量の決定はデータを共通化する)のが 1:1:11:1:1 よりもよかったとのこと

数値実験

予測性能の実験

提案論文における実験では、60個以上のオープンデータセットのもとでUnbiasedGBMとXGBoost, LightGBM, CatBoostを比較している。

ハイパーパラメータチューニングはOptunaで100以上のepochsをチューニングする方法で行った

UnbiasedGBMは平均で1.72位で最も良かった

特徴選択の実験

次の手法と比較する

  • Gain importance [Breiman et al., 1984]

  • Permutation feature importance (PFI) [Breiman, 2001]

  • SHAP [Lundberg et al., 2018]

Forman and others (2003) の手法に従い、feature importanceの手法を比較する。

Forman and others (2003)の比較法
  1. 所与のデータセットのもとで、training setを用いて feature importance を推定する

  2. feature importanceに従ってtop kk% の特徴量を選択する

  3. 選択した特徴量でGBDTモデルを構築する

  4. test setのもとでAUCを計算する

高いAUCが得られる特徴量の集合を選べた手法ほど、よい特徴量選択ができていると考える。

k{10,20,30}k \in\{10,20,30\} として、30以上の特徴量をもつ14個のデータセットのもとで比較した結果が Figure 4である。縦軸は14データセットでの平均AUCと分散を示している。

unbiased gainが平均的に良いパフォーマンスを示している。

計算量

nn をサンプル数、mm をデータセットの基本特徴量の数とする。
各サンプルは各深さにおいてちょうど一度ずつ現れるため、最大深さを dd としたとき、UnbiasedGBMは次の計算量で動作する:

O(Tdnmlogn)O(T d n m \log n)

ここで、TT は木の本数を表す。
この計算量は XGBoost とまったく同じであり、同様にブロック構造上では O(Tdnm+nmlogn)O(T d n m + n m \log n) のコストを要する。
(XGBoostはLightGBMのようなhistogram-tree実装もあるが、exact treeの話をしている??)

実際のところ、UnbiasedGBMの手法を既存の GBDT 実装に適用しても時間計算量は保たれる。
なぜなら、これは 2 つの分割済みデータセット D1D'_1 および D2D'_2 を別々に計算する場合よりも悪化することはないからである。

(Appendix E.3より)

References
  1. (2023, August). 10.24963/ijcai.2023
  2. Sandri, M., & Zuccolotto, P. (2008). A Bias Correction Algorithm for the Gini Variable Importance Measure in Classification Trees. Journal of Computational and Graphical Statistics, 17(3), 611–628. 10.1198/106186008x344522
  3. Zhou, Z., & Hooker, G. (2021). Unbiased Measurement of Feature Importance in Tree-Based Methods. ACM Transactions on Knowledge Discovery from Data, 15(2), 1–21. 10.1145/3429445