XGBoost における単調性制約

XGBoost の単調性制約

XGBoost などの Tree 系アルゴリズムは表現力が大きいため、容易に bias を小さくできます。

下の例では、増加するトレンドにある特徴量と目的変数がにノイズが乗ったデータに対して XGBoost でフィッティングすると、全体的には上昇トレンドを捉えますが、局所的に振動してしまいます。

これは、XGBoost が表現力(データにフィットする能力)が高いゆえにできることなのですが、一方で過度にデータにフィットしているとも言えます。

もちろん、それはモデル自身が判断することではなく、課題設定や事前情報、ビジネス上の考慮などで決まる場合が多いです。

ただ、諸理由によりこの「増加するトレンド」であったり「減少するトレンド」を制約として加え、制約の中で最大限にフィッティングさせたい場合に、この単調性の制約(単調に増加(減少)する制約)は有効です。

一般的に、このような制約を加える理由としては「精度向上のために」というよりも「解釈しやすくするため」という動機が多いように思います。

下図が単調性制約を与えてフィッティングしたモデルの結果です。 これを見ると、大局的な傾向は捉えていながら、局所的に増減する振動が消えています。

というようなことが下のページに書いてあるのですが、簡単に内容と実際のコードを書いておきます。

xgboost.readthedocs.io

例では次のデータを用いています(上図2つも同様です)

𝑦= 5x_1+\sin(10 \pi x_1)−5x_2 − \cos( 10\pi x_2)+ Norm(0,0.01)

x_1,x_2 \in [0,1]

# jupyter notebook での実行
import numpy as np
import xgboost as xgb
import matplotlib.pylab as plt
from sklearn.cross_validation import train_test_split

%matplotlib inline

# データの作成
N = 1000
x1 = np.random.random(N)
x2 = np.random.random(N)
y = (5 * x1 + np.sin(10 * np.pi * x1)) - (5 * x2 + np.cos(10 * np.pi * x2)) + np.random.normal(0.0, 0.01)

# x1 の分布(単調増加)
plt.plot(x1, y, 'o', alpha=0.5)

# x2 の分布(単調減少)
plt.plot(x2, y, 'o', alpha=0.5)

# 学習データ
X = np.array([x1, x2]).T
X_train, X_test, y_train, y_test = train_test_split(X, y)

# 描画用
# 片方の変数を描画するときはもう片方を 0.5 で固定(影響を最小限にするため)
def plot_response_x1(model):
    
    # x1 (単調増加変数のレスポンス)
    _x1 = np.linspace(0, 1, 100)
    _x2 = np.zeros(100) + 0.5
    _X = np.array([_x1, _x2]).T
    
    _y = model.predict(_X)
    plt.plot(X_test[:,0], y_test, 'o', alpha=0.5)
    plt.plot(_x1, _y, color = 'black')

def plot_response_x2(model):

    # x2 (単調減少変数のレスポンス)
    _x1 = np.zeros(100) + 0.5
    _x2 = np.linspace(0, 1, 100)
    _X = np.array([_x1, _x2]).T
    
    _y = model.predict(_X)
    plt.plot(X_test[:, 1], y_test, 'o', alpha=0.5)
    plt.plot(_x2, _y, color = 'black')
    
# 単調性制約のない XGBoost で回帰モデルを構築します.
model = xgb.XGBRegressor()
model.fit(X_train, y_train)
plot_response_x1(model)
plot_response_x2(model)

# 単調性制約を課した XGBoost で回帰モデルを構築します.
model = xgb.XGBRegressor(monotone_constraints=(1,-1))
model.fit(X_train, y_train)
plot_response_x1(model)
plot_response_x2(model)

パラメータについて

monotone_constraints

1:単調増加制約、0:制約を加えない(デフォルト)、-1:単調減少制約、で説明変数毎にリストで設定します。例えば

  • (1, 0): 1つ目の説明変数に単調増加制約を与え、2つ目の説明変数には制約を与えない
  • (0,-1): 1つ目の説明変数に単調増加制約を与えず、2つ目の説明変数には単調減少制約を与える

となります。

tree_method

monotone_constraints を利用する場合は tree_method として次のうちのどれかを指定する必要があります。

  • tree_method='exact','hist', gpu_hist